focus-components-v3
Version:
Focus web components to build applications (based on Material Design)
231 lines (219 loc) • 7.76 kB
JavaScript
import React, {PropTypes, Component} from 'react';
import ReactDOM from 'react-dom';
import includes from 'lodash/includes';
import i18next from 'i18next';
/**
* Small overlay component used to listen to scroll and prevent it to leave the Modal component
*/
class ModalOverlay extends Component {
constructor(props) {
super(props);
this._hideBodyOverflow = this._hideBodyOverflow.bind(this);
this._restoreBodyOverflow = this._restoreBodyOverflow.bind(this);
}
componentDidMount() {
this._hideBodyOverflow();
}
/**
* Store the body overgflow property, and set it to hidden
* @private
*/
_hideBodyOverflow() {
document.body.style['overflow-y'] = 'hidden';
};
/**
* Restore body overflow property
* @private
*/
_restoreBodyOverflow() {
document.body.style['overflow-y'] = 'auto';
};
/**
* Component will unmount event handler.
* Remove the mouse wheel listener.
*/
componentWillUnmount() {
// ReactDOM.findDOMNode(this.refs.overlay).removeEventListener('mousewheel', this._onScroll);
this._restoreBodyOverflow();
};
/**
* Render the component
* @return {XML} the rendered HTML
*/
render() {
const {children, clickHandler, show} = this.props;
const otherProps = clickHandler ? { onClick: clickHandler } : {};
return (
<div className='animated fadeIn' data-animation='fadeIn' data-closing-animation='fadeOut' data-focus='modal-overlay' data-visible={show} ref='overlay' {...otherProps}>
{children}
</div>
);
};
};
ModalOverlay.displayName = 'ModalOverlay';
ModalOverlay.propTypes = {
children: PropTypes.object,
clickHandler: PropTypes.func,
show: PropTypes.bool
};
ModalOverlay.defaultProps = {
show: false
};
/**
* The modal component configuration
* @type {Object}
*/
class Modal extends Component {
constructor(props) {
super(props);
this._onWheel = this._onWheel.bind(this);
this.toggleOpen = this.toggleOpen.bind(this);
this._getAnimationProps = this._getAnimationProps.bind(this);
this._closePopin = this._closePopin.bind(this);
this.state = {
opened: this.props.open
};
};
componentWillUnmount() {
window.clearTimeout(this._openTimeoutID);
};
/**
* Wheel event handler.
* @param {object} event wheel event
*/
_onWheel(event) {
ReactDOM.findDOMNode(this.refs['modal-window']).scrollTop += 0 < event.deltaY ? 100 : -100;
};
_closePopin() {
let timeout = 0;
if (opened) {
const modalWindow = ReactDOM.findDOMNode(this.refs['modal-window']);
const modalOverlay = ReactDOM.findDOMNode(this.refs['modal-overlay']);
modalWindow.classList.remove(modalWindow.getAttribute('data-animation'));
modalWindow.classList.add(modalWindow.getAttribute('data-closing-animation'));
modalOverlay.classList.remove(modalOverlay.getAttribute('data-animation'));
modalOverlay.classList.add(modalOverlay.getAttribute('data-closing-animation'));
timeout = 200;
}
const {opened} = this.state;
const {onModalClose} = this.props;
if (opened && onModalClose) {
onModalClose();
}
this._openTimeoutID = setTimeout(() => {
// Store the current modal state
const wasOpened = this.state.opened;
// If it was opened, then we are closing it, so restore the body overflow before closing.
if (wasOpened && this.refs['modal-overlay']) {
this.refs['modal-overlay']._restoreBodyOverflow();
}
this.setState({
opened: !wasOpened
}, () => {
if (this.refs['modal-overlay']) {
if (!wasOpened) {
// We just opened the modal, so store and hide the body overflow.
this.refs['modal-overlay']._hideBodyOverflow();
}
}
});
}, timeout);
}
/**
* Toggle the modal's open state
*/
toggleOpen() {
const {opened} = this.state;
const {ConfirmContentComponent, confirmFunction} = this.props;
if(opened && confirmFunction) {
confirmFunction(
ConfirmContentComponent,
{
resolve: this._closePopin
}
);
} else {
this._closePopin();
}
};
/**
* Render the component
* @return {XML} the rendered HTML
*/
render() { // test for this.state.opened and return an Overlay component if true
const {children, level, modal, overlay, size, type} = this.props;
const animationProps = this._getAnimationProps();
return (
<div data-focus='modal' data-level={level} data-size={size} data-type={type} >
{this.state.opened &&
<ModalOverlay clickHandler={!modal && this.toggleOpen} ref='modal-overlay' resize={'full' === type} show={overlay}>
<div {...animationProps} data-focus='modal-window' onClick={this._preventModalClose} ref='modal-window'>
<i className='material-icons' data-focus='modal-window-close' onClick={this.toggleOpen}>close</i>
<div onWheel={this._onWheel}>
{children}
</div>
</div>
</ModalOverlay>
}
</div>
);
};
/**
* Compute the animation classes
* @return {Object} the props to attach to the component
* @private
*/
_getAnimationProps() {
let openingAnimation;
let closingAnimation;
switch (this.props.type) {
case 'from-menu':
openingAnimation = 'slideInLeft';
closingAnimation = 'slideOutLeft';
break;
case 'from-right':
openingAnimation = 'slideInRight';
closingAnimation = 'slideOutRight';
break;
default:
openingAnimation = 'zoomIn';
closingAnimation = 'zoomOut';
break;
}
return ({
className: `animated ${openingAnimation}`,
'data-animation': openingAnimation,
'data-closing-animation': closingAnimation
});
};
/**
* Prevent modal close when there's a click on the modal window
* @param {Object} event - raised by the click
* @private
*/
_preventModalClose(event) {
event.stopPropagation();
};
};
Modal.displayName = 'Modal';
Modal.defaultProps = {
ConfirmContentComponent: () => <span>{i18next.t('focus.components.modal.confirmation.text')}</span>,
level: 0,
modal: false,
open: false,
overlay: true,
size: 'medium',
type: 'full'
};
Modal.propTypes = {
confirmFunction: PropTypes.func,
ConfirmContentComponent: PropTypes.func,
level: PropTypes.number.isRequired,
modal: PropTypes.bool.isRequired,
onModalClose: PropTypes.func,
open: PropTypes.bool.isRequired,
overlay: PropTypes.bool.isRequired,
size: PropTypes.oneOf(['small', 'medium', 'large']).isRequired,
type: PropTypes.string.isRequired
};
export default Modal;