lucid-ui
Version:
A UI component library from AppNexus.
102 lines (101 loc) • 4.07 kB
JavaScript
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;