UNPKG

lucid-ui

Version:

A UI component library from AppNexus.

102 lines (101 loc) 4.07 kB
import _ from 'lodash'; import React from 'react'; import PropTypes from 'react-peek/prop-types'; import Portal from '../Portal/Portal'; import { CSSTransition } from 'react-transition-group'; import { lucidClassNames, uniqueName } from '../../util/style-helpers'; import { omitProps } from '../../util/component-types'; const cx = lucidClassNames.bind('&-Overlay'); const { string, bool, func, node } = PropTypes; export const defaultProps = { isShown: false, isModal: true, onEscape: _.noop, onBackgroundClick: _.noop, isAnimated: true, }; class Overlay extends React.Component { constructor() { super(...arguments); this.rootHTMLDivElement = React.createRef(); this.state = { // This must be in state because getDefaultProps only runs once per // component import which causes collisions portalId: this.props.portalId || uniqueName('Overlay-Portal-'), }; this.handleDocumentKeyDown = (event) => { // If the user hits the "escape" key, then fire an `onEscape` // TODO: use key helpers if (event.keyCode === 27) { this.props.onEscape({ event, props: this.props }); } }; this.handleBackgroundClick = (event) => { // Use the reference we previously stored from the `ref` to check what // element was clicked on. if (this.rootHTMLDivElement.current && event.target === this.rootHTMLDivElement.current) { this.props.onBackgroundClick({ event, props: this.props }); } }; } componentDidMount() { if (window && window.document) { window.document.addEventListener('keydown', this.handleDocumentKeyDown); } } componentWillUnmount() { if (window && window.document) { window.document.removeEventListener('keydown', this.handleDocumentKeyDown); } } render() { const { className, isShown, isModal, isAnimated, children, ...passThroughs } = this.props; const { portalId } = this.state; const overlayElement = (React.createElement("div", Object.assign({}, omitProps(passThroughs, undefined, Object.keys(Overlay.propTypes)), { className: cx(className, '&', { '&-is-not-modal': !isModal, '&-is-animated': isAnimated, }), onClick: this.handleBackgroundClick, ref: this.rootHTMLDivElement }), children)); return (React.createElement(Portal, { portalId: portalId }, isAnimated ? (React.createElement(CSSTransition, { in: isShown, classNames: cx('&'), timeout: 300, unmountOnExit: true }, overlayElement)) : isShown ? (overlayElement) : null)); } } Overlay.displayName = 'Overlay'; Overlay.peek = { description: ` Overlay is used to block user interaction with the rest of the app until they have completed something. `, categories: ['utility'], madeFrom: ['Portal'], }; Overlay.propTypes = { className: string ` Appended to the component-specific class names set on the root element. `, children: node ` Generally you should only have a single child element so the centering works correctly. `, isShown: bool ` Controls visibility. `, isAnimated: bool, isModal: bool ` Determines if it shows with a gray background. If \`false\`, the background will be rendered but will be invisible, except for the contents, and it won't capture any of the user click events. `, portalId: string ` Set your own id for the \`Portal\` is that is opened up to contain the contents. In practice you should never need to set this manually. `, onEscape: func ` Fired when the user hits escape. Signature: \`({ event, props }) => {}\` `, onBackgroundClick: func ` Fired when the user clicks on the background, this may or may not be visible depending on \`isModal\`. Signature: \`({ event, props }) => {}\` `, }; Overlay.defaultProps = defaultProps; export default Overlay;