Skip to content

Instantly share code, notes, and snippets.

@phaistonian
Created July 19, 2016 04:39
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save phaistonian/1a3f3f5801a5de1e3b31358e71024b6e to your computer and use it in GitHub Desktop.
Save phaistonian/1a3f3f5801a5de1e3b31358e71024b6e to your computer and use it in GitHub Desktop.
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;
@phaistonian
Copy link
Author

Example:

myElement.addEventListener('click', Modal.render(<MyComponent />);

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment