All files / components/Overlay Overlay.jsx

100% Statements 17/17
83.33% Branches 15/18
100% Functions 0/0
100% Lines 17/17
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162              120x             120x               120x                                                                                                   120x                 14x               5x 5x         5x 5x             1x 1x           4x           2x 1x                     14x       14x   14x                                                      
import _ from 'lodash';
import React from 'react';
import Portal from '../Portal/Portal';
import ReactCSSTransitionGroup from 'react-addons-css-transition-group';
import { lucidClassNames } from '../../util/style-helpers';
import { createClass, omitProps }  from '../../util/component-types';
 
const cx = lucidClassNames.bind('&-Overlay');
 
const {
	string,
	bool,
	func,
	node,
} = React.PropTypes;
 
/**
 * {"categories": ["utility"], "madeFrom": ["Portal"]}
 *
 * Overlay is used to block user interaction with the rest of the app until they
 * have completed something.
 */
const Overlay = createClass({
	displayName: 'Overlay',
 
	propTypes: {
		/**
		 * Appended to the component-specific class names set on the root element.
		 */
		className: string,
 
		/**
		 * Generally you should only have a single child element so the centering
		 * works correctly.
		 */
		children: node,
 
		/**
		 * Controls visibility.
		 */
		isShown: 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.
		 */
		isModal: bool,
 
		/**
		 * 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.
		 */
		portalId: string,
 
		/**
		 * Fired when the user hits escape.
		 *
		 * Signature: `({ event, props }) => {}`
		 */
		onEscape: func,
 
		/**
		 * Fired when the user clicks on the background, this may or may not be
		 * visible depending on `isModal`.
		 *
		 * Signature: `({ event, props }) => {}`
		 */
		onBackgroundClick: func,
	},
 
	getDefaultProps() {
		return {
			isShown: false,
			isModal: true,
			onEscape: _.noop,
			onBackgroundClick: _.noop,
		};
	},
 
	getInitialState() {
		return {
			// This must be in state because getDefaultProps only runs once per
			// component import which causes collisions
			portalId: this.props.portalId || _.uniqueId('Overlay-Portal-'),
		}
	},
 
	componentDidMount() {
		Eif (window && window.document) {
			window.document.addEventListener('keydown', this.handleDocumentKeyDown);
		}
	},
 
	componentWillUnmount() {
		Eif (window && window.document) {
			window.document.removeEventListener('keydown', this.handleDocumentKeyDown);
		}
	},
 
	handleDocumentKeyDown(event) {
		// If the user hits the "escape" key, then fire an `onEscape`
		// TODO: use key helpers
		Eif (event.keyCode === 27) {
			this.props.onEscape({event, props: this.props });
		}
	},
 
	handleDivRef(divDOMNode) {
		// Store the dom node so we can check if it's clicked on later
		this._divDOMNode = divDOMNode;
	},
 
	handleBackgroundClick(event) {
		// Use the reference we previously stored from the `ref` to check what
		// element was clicked on.
		if (this._divDOMNode && event.target === this._divDOMNode) {
			this.props.onBackgroundClick({event, props: this.props });
		}
	},
 
	render() {
		const {
			className,
			isShown,
			isModal,
			children,
			...passThroughs,
		} = this.props;
 
		const {
			portalId,
		} = this.state;
 
		return (
			<Portal portalId={portalId}>
				<ReactCSSTransitionGroup
					transitionName={cx('&')}
					transitionEnterTimeout={300}
					transitionLeaveTimeout={300}
				>
					{isShown ?
						<div
							{...omitProps(passThroughs, Overlay)}
							className={cx(className, '&', {
								'&-is-not-modal': !isModal,
							})}
							onClick={this.handleBackgroundClick}
							ref={this.handleDivRef}
						>
							{children}
						</div>
					: null}
				</ReactCSSTransitionGroup>
			</Portal>
		);
	},
});
 
export default Overlay;