Created
July 19, 2016 04:39
-
-
Save phaistonian/1a3f3f5801a5de1e3b31358e71024b6e to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import React, { Component, PropTypes } from 'react'; | |
import ReactDOM from 'react-dom'; | |
import round from 'modules/rounder'; | |
import Icon from 'components/Icon'; | |
import getDisplayName from 'react-display-name'; | |
const Modal = (content = null) => ComposedComponent => class extends Component { | |
static displayName = ComposedComponent | |
? `Modal(${getDisplayName(ComposedComponent)})` | |
: null; | |
static propTypes = { | |
title: PropTypes.string, | |
showCloseButton: PropTypes.bool, | |
escapeToClose: PropTypes.bool, | |
id: PropTypes.string, | |
fit: PropTypes.bool, | |
onClose: PropTypes.func, | |
onMount: PropTypes.func, | |
round: PropTypes.bool, | |
beforeClose: PropTypes.func | |
}; | |
static defaultProps = { | |
showCloseButton: true, | |
escapeToClose: true, | |
round: true, | |
fit: false | |
}; | |
constructor (props) { | |
super(props); | |
this.events = {}; | |
this.state = { | |
title: props.title, | |
id: props.id | |
}; | |
this.propsToPassOn = { ...props, modal: this }; | |
// In case we are dealing with an actual react element, pass it to the props of the element | |
// and also to the props of the children if found (for Provider) | |
if (content && content.type && content.type !== 'div') { | |
content = React.cloneElement( | |
content, | |
this.propsToPassOn, | |
content.props.children | |
? React.cloneElement(content.props.children, { ...content.props.children.props, modal: this }) | |
: null | |
); | |
} | |
} | |
componentDidMount () { | |
const bodyScrollOffset = window.innerWidth - document.documentElement.clientWidth; | |
if (this.props.round) { | |
round(this.refs.modal); | |
} | |
if (this.props.escapeToClose) { | |
this.events.escapeToClose = event => { | |
if (event.keyCode === 27) { | |
// Only if last one is current | |
// or if no modal === rendered directly | |
if ( | |
window.modals[window.modals.length - 1] === this | |
|| !window.modals.length | |
) { | |
event.preventDefault(); | |
event.stopPropagation(); | |
this.close(); | |
} | |
} | |
}; | |
} | |
window.addEventListener('keydown', this.events.escapeToClose); | |
document.documentElement.style.overflow = 'hidden'; | |
document.documentElement.style.marginRight = `${bodyScrollOffset}px`; | |
if (window.touch) { | |
document.body.style.position = 'fixed'; | |
} | |
if (this.props.onMount) { | |
this.props.onMount(this); | |
} | |
this._isMounted = true; | |
} | |
componentWillUnmount () { | |
if (this.events.escapeToClose) { | |
window.removeEventListener('keydown', this.events.escapeToClose); | |
} | |
document.documentElement.style.overflow = 'auto'; | |
document.documentElement.style.marginRight = ''; | |
if (window.touch) { | |
document.body.style.position = 'static'; | |
} | |
this._isMounted = false; | |
} | |
// So that we can change props if needed | |
// TODO: clean this up | |
componentWillReceiveProps (nextProps) { | |
this.propsToPassOn = { ...nextProps, modal: this }; | |
this.forceUpdate(); | |
} | |
resize (delayed = false) { | |
if (!this.props.round) { | |
return; | |
} | |
const fn = () => { | |
round(this.refs.modal); | |
}; | |
if (!delayed) { | |
fn(); | |
} else { | |
setTimeout(fn.bind(this)); | |
} | |
} | |
close () { | |
if (this.shouldClose()) { | |
const parent = ReactDOM.findDOMNode(this).parentNode; | |
if (typeof parent.dataset.reactroot === 'undefined') { | |
ReactDOM.unmountComponentAtNode(parent); | |
window.modals = window.modals.filter(modal => modal !== this); | |
} | |
if (this.props.onClose) { | |
setTimeout(() => this.props.onClose()); | |
} | |
} | |
} | |
setTitle (title) { | |
this.setState({ | |
title | |
}); | |
} | |
shouldClose () { | |
if (this.props.beforeClose) { | |
return this.props.beforeClose(); | |
} | |
return true; | |
} | |
getNode () { | |
return ReactDOM.findDOMNode(this.refs.modal); | |
} | |
render () { | |
return ( | |
<div className="modal-backdrop"> | |
<div | |
className={`modal${(this.props.fit ? ' fit' : '')}`} | |
ref="modal" | |
id={this.props.id}> | |
{this.props.showCloseButton && this.renderCloseButton()} | |
<div className="modal-content"> | |
{this.state.title && <h3>{this.state.title}</h3>} | |
{ComposedComponent | |
? <ComposedComponent {...this.propsToPassOn} isOpen={this.state.isOpen} /> | |
: content | |
} | |
</div> | |
</div> | |
</div> | |
); | |
} | |
renderCloseButton () { | |
return ( | |
<Icon | |
icon="x-24" | |
className="modal-close" | |
onClick={::this.close} /> | |
); | |
} | |
}; | |
window.modals = []; | |
window.modalHolderNode = document.createElement('div'); | |
window.modalHolderNode.id = '__modal_holder'; | |
document.body.appendChild(window.modalHolderNode); | |
/** | |
* This helper function enhanches the given component with the Modal component (HOC) | |
* and automatically renders it (along the props) in the assigned node. | |
* @param {Component} component The React component to write in the modal | |
* @param {object} props The object to describe the props passes to the enhanced component | |
* @param {boolea} inSeparateHolder Whether the modal should be rendered in a separate holder or not | |
* @return {Component} The enhanced component | |
*/ | |
Modal.render = (component, props, inSeparateHolder = false) => { | |
let ModalContent; | |
let holder = window.modalHolderNode; | |
// Check if input is not a class but just raw jsx | |
if (component.type && component.props) { | |
ModalContent = Modal(component)(); | |
} else { | |
ModalContent = Modal()(component); | |
} | |
if (inSeparateHolder) { | |
holder = document.createElement('div'); | |
document.body.appendChild(holder); | |
} | |
const modal = ReactDOM.render( | |
<ModalContent {...props} />, | |
holder | |
); | |
window.modals.push(modal); | |
return modal; | |
}; | |
window.addEventListener('instant', () => { | |
window.modals.forEach(modal => { | |
if (modal._isMounted) { | |
const element = ReactDOM.findDOMNode(modal); | |
if (element && element.parentNode) { | |
ReactDOM.unmountComponentAtNode(element.parentNode); | |
} | |
} | |
}); | |
window.modals = []; | |
}); | |
export default Modal; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Example: