You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
197 lines
4.0 KiB
197 lines
4.0 KiB
7 years ago
|
import PropTypes from 'prop-types';
|
||
|
import React, { Component } from 'react';
|
||
|
import ReactDOM from 'react-dom';
|
||
|
import Portal from 'react-portal';
|
||
|
import classNames from 'classnames';
|
||
|
import elementClass from 'element-class';
|
||
|
import getUniqueElememtId from 'Utilities/getUniqueElementId';
|
||
|
import * as keyCodes from 'Utilities/Constants/keyCodes';
|
||
|
import { sizes } from 'Helpers/Props';
|
||
|
import styles from './Modal.css';
|
||
|
|
||
|
const openModals = [];
|
||
|
|
||
|
function removeFromOpenModals(id) {
|
||
|
const index = openModals.indexOf(id);
|
||
|
|
||
|
if (index >= 0) {
|
||
|
openModals.splice(index, 1);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
class Modal extends Component {
|
||
|
|
||
|
//
|
||
|
// Lifecycle
|
||
|
|
||
|
constructor(props, context) {
|
||
|
super(props, context);
|
||
|
|
||
|
this._modalId = getUniqueElememtId();
|
||
|
}
|
||
|
|
||
|
componentDidMount() {
|
||
|
if (this.props.isOpen) {
|
||
|
this._openModal();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
componentDidUpdate(prevProps) {
|
||
|
const {
|
||
|
isOpen
|
||
|
} = this.props;
|
||
|
|
||
|
if (!prevProps.isOpen && isOpen) {
|
||
|
this._openModal();
|
||
|
} else if (prevProps.isOpen && !isOpen) {
|
||
|
this._closeModal();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
componentWillUnmount() {
|
||
|
if (this.props.isOpen) {
|
||
|
this._closeModal();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
//
|
||
|
// Control
|
||
|
|
||
|
_openModal() {
|
||
|
openModals.push(this._modalId);
|
||
|
window.addEventListener('keydown', this.onKeyDown);
|
||
|
|
||
|
if (openModals.length === 1) {
|
||
|
elementClass(document.body).add(styles.modalOpen);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
_closeModal() {
|
||
|
removeFromOpenModals(this._modalId);
|
||
|
window.removeEventListener('keydown', this.onKeyDown);
|
||
|
|
||
|
if (openModals.length === 0) {
|
||
|
elementClass(document.body).remove(styles.modalOpen);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
_isBackdropTarget(event) {
|
||
|
const targetElement = this._findEventTarget(event);
|
||
|
|
||
|
if (targetElement) {
|
||
|
const modalElement = ReactDOM.findDOMNode(this.refs.modal);
|
||
|
|
||
|
return !modalElement || !modalElement.contains(targetElement);
|
||
|
}
|
||
|
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
_findEventTarget(event) {
|
||
|
const changedTouches = event.changedTouches;
|
||
|
|
||
|
if (!changedTouches) {
|
||
|
return event.target;
|
||
|
}
|
||
|
|
||
|
if (changedTouches.length === 1) {
|
||
|
const touch = changedTouches[0];
|
||
|
|
||
|
return document.elementFromPoint(touch.clientX, touch.clientY);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
//
|
||
|
// Listeners
|
||
|
|
||
|
onBackdropBeginPress = (event) => {
|
||
|
this._isBackdropPressed = this._isBackdropTarget(event);
|
||
|
}
|
||
|
|
||
|
onBackdropEndPress = (event) => {
|
||
|
if (this._isBackdropPressed && this._isBackdropTarget(event)) {
|
||
|
this.props.onModalClose();
|
||
|
}
|
||
|
|
||
|
this._isBackdropPressed = false;
|
||
|
}
|
||
|
|
||
|
onKeyDown = (event) => {
|
||
|
const keyCode = event.keyCode;
|
||
|
|
||
|
if (keyCode === keyCodes.ESCAPE) {
|
||
|
if (openModals.indexOf(this._modalId) === openModals.length - 1) {
|
||
|
event.preventDefault();
|
||
|
event.stopPropagation();
|
||
|
|
||
|
this.props.onModalClose();
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
onClosePress = (event) => {
|
||
|
this.props.onModalClose();
|
||
|
}
|
||
|
|
||
|
//
|
||
|
// Render
|
||
|
|
||
|
render() {
|
||
|
const {
|
||
|
className,
|
||
|
backdropClassName,
|
||
|
size,
|
||
|
children,
|
||
|
isOpen
|
||
|
} = this.props;
|
||
|
|
||
|
return (
|
||
|
<Portal
|
||
|
isOpened={isOpen}
|
||
|
>
|
||
|
<div>
|
||
|
{
|
||
|
isOpen &&
|
||
|
<div
|
||
|
className={styles.modalContainer}
|
||
|
>
|
||
|
<div
|
||
|
className={backdropClassName}
|
||
|
onMouseDown={this.onBackdropBeginPress}
|
||
|
onMouseUp={this.onBackdropEndPress}
|
||
|
>
|
||
|
<div
|
||
|
ref="modal"
|
||
|
className={classNames(
|
||
|
className,
|
||
|
styles[size]
|
||
|
)}
|
||
|
>
|
||
|
{children}
|
||
|
</div>
|
||
|
</div>
|
||
|
</div>
|
||
|
}
|
||
|
</div>
|
||
|
</Portal>
|
||
|
);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
Modal.propTypes = {
|
||
|
className: PropTypes.string,
|
||
|
backdropClassName: PropTypes.string,
|
||
|
size: PropTypes.oneOf(sizes.all),
|
||
|
children: PropTypes.node,
|
||
|
isOpen: PropTypes.bool.isRequired,
|
||
|
onModalClose: PropTypes.func.isRequired
|
||
|
};
|
||
|
|
||
|
Modal.defaultProps = {
|
||
|
className: styles.modal,
|
||
|
backdropClassName: styles.modalBackdrop,
|
||
|
size: sizes.LARGE
|
||
|
};
|
||
|
|
||
|
export default Modal;
|