box-ui-elements-mlh
Version:
145 lines (131 loc) • 4.22 kB
Flow
// @flow
import * as React from 'react';
import uniqueId from 'lodash/uniqueId';
import TetherComponent from 'react-tether';
import './RadarAnimation.scss';
const BOTTOM_CENTER = 'bottom-center';
const BOTTOM_LEFT = 'bottom-left';
const BOTTOM_RIGHT = 'bottom-right';
const MIDDLE_CENTER = 'middle-center';
const MIDDLE_LEFT = 'middle-left';
const MIDDLE_RIGHT = 'middle-right';
const TOP_CENTER = 'top-center';
const TOP_LEFT = 'top-left';
const TOP_RIGHT = 'top-right';
const positions = {
[BOTTOM_CENTER]: {
attachment: 'top center',
targetAttachment: 'bottom center',
},
[BOTTOM_LEFT]: {
attachment: 'top left',
targetAttachment: 'bottom left',
},
[BOTTOM_RIGHT]: {
attachment: 'top right',
targetAttachment: 'bottom right',
},
[MIDDLE_CENTER]: {
attachment: 'middle center',
targetAttachment: 'middle center',
},
[MIDDLE_LEFT]: {
attachment: 'middle right',
targetAttachment: 'middle left',
},
[MIDDLE_RIGHT]: {
attachment: 'middle left',
targetAttachment: 'middle right',
},
[TOP_CENTER]: {
attachment: 'bottom center',
targetAttachment: 'top center',
},
[TOP_LEFT]: {
attachment: 'bottom left',
targetAttachment: 'top left',
},
[TOP_RIGHT]: {
attachment: 'bottom right',
targetAttachment: 'top right',
},
};
export type Position = $Keys<typeof positions>;
type Props = {
/** A React element to put the radar on */
children: React.Element<any>,
/** A CSS class for the radar */
className?: string,
/** Whether to constrain the radar to the element's scroll parent. Defaults to `false` */
constrainToScrollParent: boolean,
/** Whether to constrain the radar to window. Defaults to `true` */
constrainToWindow: boolean,
/** Forces the radar to be shown or hidden - defaults to true */
isShown: boolean,
/** A string of the form 'vert-offset horiz-offset' which controls positioning */
offset?: string,
/** Where to position the radar relative to the wrapped component */
position: Position,
};
class RadarAnimation extends React.Component<Props> {
static defaultProps = {
constrainToScrollParent: false,
constrainToWindow: true,
isShown: true,
position: MIDDLE_RIGHT,
};
tetherRef = React.createRef<{ position: () => {} }>();
radarAnimationID = uniqueId('radarAnimation');
// Instance API: Forces the radar to be repositioned
position = () => {
const { isShown } = this.props;
if (this.tetherRef.current && isShown) {
this.tetherRef.current.position();
}
};
render() {
const {
children,
className = '',
constrainToScrollParent,
constrainToWindow,
position,
isShown,
...rest
} = this.props;
const constraints = [];
if (constrainToScrollParent) {
constraints.push({
to: 'scrollParent',
attachment: 'together',
});
}
if (constrainToWindow) {
constraints.push({
to: 'window',
attachment: 'together',
});
}
const tetherPosition = positions[position];
return (
<TetherComponent
attachment={tetherPosition.attachment}
classPrefix="radar-animation"
constraints={constraints}
targetAttachment={tetherPosition.targetAttachment}
ref={this.tetherRef}
>
{React.cloneElement(React.Children.only(children), {
'aria-describedby': this.radarAnimationID,
})}
{isShown && (
<div className={`radar ${className}`} id={this.radarAnimationID} {...rest}>
<div className="radar-dot" />
<div className="radar-circle" />
</div>
)}
</TetherComponent>
);
}
}
export default RadarAnimation;