Skip to content

Commit

Permalink
feat(Modal): add modal components
Browse files Browse the repository at this point in the history
  • Loading branch information
eddywashere committed Mar 13, 2016
1 parent a84df68 commit 6c2293e
Show file tree
Hide file tree
Showing 9 changed files with 544 additions and 0 deletions.
158 changes: 158 additions & 0 deletions lib/Modal.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,158 @@
import React, { PropTypes } from 'react';
import ReactDOM from 'react-dom';
import classNames from 'classnames';
import Fade from './Fade';

const propTypes = {
isOpen: PropTypes.bool,
size: PropTypes.string,
toggle: PropTypes.func.isRequired,
onEnter: PropTypes.func,
onExit: PropTypes.func
};

const defaultProps = {
isOpen: false
};

class Modal extends React.Component {
constructor(props) {
super(props);

this.handleProps = this.handleProps.bind(this);
this.handleBackdropClick = this.handleBackdropClick.bind(this);
this.handleEscape = this.handleEscape.bind(this);
this.destroy = this.destroy.bind(this);
this.onEnter = this.onEnter.bind(this);
this.onExit = this.onExit.bind(this);
}

componentDidMount() {
if (this.props.isOpen) {
this.show();
}
}

componentDidUpdate(prevProps) {
if (this.props.isOpen !== prevProps.isOpen) {
this.handleProps();
}
}

componentWillUnmount() {
this.hide();
}

onEnter() {
this._modal.fadeIn();
if (this.props.onEnter) {
this.props.onEnter();
}
}

onExit() {
this.destroy();
if (this.props.onExit) {
this.props.onExit();
}
}

handleEscape(e) {
if (e.keyCode === 27) {
this.props.toggle();
}
}

handleBackdropClick(e) {
const container = ReactDOM.findDOMNode(this._dialog);

if (e.target && !container.contains(e.target)) {
this.props.toggle();
}
}

handleProps() {
if (this.props.isOpen) {
this.show();
} else {
this.hide();
}
}

destroy() {
let classes = document.body.className.replace('modal-open', '');
this.removeEvents();

if (this._element) {
ReactDOM.unmountComponentAtNode(this._element);
document.body.removeChild(this._element);
this._element = null;
}

document.body.className = classNames(classes).trim();
}

removeEvents() {
document.removeEventListener('click', this.handleBackdropClick, false);
document.removeEventListener('keyup', this.handleEscape, false);
}

hide() {
this.removeEvents();

if (this._modal) {
this._modal.fadeOut();
}
if (this._backdrop) {
this._backdrop.fadeOut(this.onExit, 250);
}
}

show() {
const classes = document.body.className;
this._element = document.createElement('div');
this._element.setAttribute('tabindex', '-1');

document.body.appendChild(this._element);
document.addEventListener('click', this.handleBackdropClick, false);
document.addEventListener('keyup', this.handleEscape, false);

document.body.className = classNames(
classes,
'modal-open'
);

ReactDOM.unstable_renderSubtreeIntoContainer(
this,
this.renderChildren(),
this._element
);

this._element.focus();
this._backdrop.fadeIn(this.onEnter, 100);
}

renderChildren() {
return (
<div>
<Fade className="modal" style={{ display: 'block' }} tabIndex="-1" ref={(c) => this._modal = c }>
<div className="modal-dialog" role="document" ref={(c) => this._dialog = c }>
<div className="modal-content">
{ this.props.children }
</div>
</div>
</Fade>
<Fade className="modal-backdrop" ref={(c) => this._backdrop = c }/>
</div>
);
}

render() {
return null;
}
}

Modal.propTypes = propTypes;
Modal.defaultProps = defaultProps;

export default Modal;
21 changes: 21 additions & 0 deletions lib/ModalBody.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import React, { PropTypes } from 'react';
import classNames from 'classnames';

const propTypes = {
className: PropTypes.string
};

const ModalBody = (props) => {
const classes = classNames(
props.className,
'modal-body'
);

return (
<div {...props} className={classes} />
);
};

ModalBody.propTypes = propTypes;

export default ModalBody;
21 changes: 21 additions & 0 deletions lib/ModalFooter.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import React, { PropTypes } from 'react';
import classNames from 'classnames';

const propTypes = {
className: PropTypes.string
};

const ModalFooter = (props) => {
const classes = classNames(
props.className,
'modal-footer'
);

return (
<div {...props} className={classes} />
);
};

ModalFooter.propTypes = propTypes;

export default ModalFooter;
46 changes: 46 additions & 0 deletions lib/ModalHeader.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import React, { PropTypes } from 'react';
import classNames from 'classnames';

const propTypes = {
toggle: PropTypes.func
};

const defaultProps = {};

class ModalHeader extends React.Component {
render() {
let closeButton;
const {
className,
children,
toggle,
...props } = this.props;

const classes = classNames(
className,
'modal-header'
);

if (toggle) {
closeButton = (
<button type="button" onClick={toggle} className="close" dataDismiss="modal" ariaLabel="Close">
<span ariaHidden="true">&times;</span>
</button>
);
}

return (
<div {...props} className={classes}>
{ closeButton }
<h4 className="modal-title">
{ children }
</h4>
</div>
);
}
}

ModalHeader.propTypes = propTypes;
ModalHeader.defaultProps = defaultProps;

export default ModalHeader;
8 changes: 8 additions & 0 deletions lib/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,10 @@ import Label from './Label';
import Popover from './Popover';
import PopoverTitle from './PopoverTitle';
import PopoverContent from './PopoverContent';
import Modal from './Modal';
import ModalHeader from './ModalHeader';
import ModalBody from './ModalBody';
import ModalFooter from './ModalFooter';
import TetherContent from './TetherContent';
import Tooltip from './Tooltip';

Expand All @@ -28,6 +32,10 @@ export {
Popover,
PopoverContent,
PopoverTitle,
Modal,
ModalHeader,
ModalBody,
ModalFooter,
TetherContent,
Tooltip
};
Loading

0 comments on commit 6c2293e

Please sign in to comment.