react-bootstrap
Version:
Bootstrap 3 components build with React
180 lines (150 loc) • 4.75 kB
JavaScript
import React from 'react';
import classNames from 'classnames';
import BootstrapMixin from './BootstrapMixin';
import FadeMixin from './FadeMixin';
import domUtils from './utils/domUtils';
import EventListener from './utils/EventListener';
// TODO:
// - aria-labelledby
// - Add `modal-body` div if only one child passed in that doesn't already have it
// - Tests
const Modal = React.createClass({
mixins: [BootstrapMixin, FadeMixin],
propTypes: {
title: React.PropTypes.node,
backdrop: React.PropTypes.oneOf(['static', true, false]),
keyboard: React.PropTypes.bool,
closeButton: React.PropTypes.bool,
animation: React.PropTypes.bool,
onRequestHide: React.PropTypes.func.isRequired,
dialogClassName: React.PropTypes.string
},
getDefaultProps() {
return {
bsClass: 'modal',
backdrop: true,
keyboard: true,
animation: true,
closeButton: true
};
},
render() {
let modalStyle = {display: 'block'};
let dialogClasses = this.getBsClassSet();
delete dialogClasses.modal;
dialogClasses['modal-dialog'] = true;
let classes = {
modal: true,
fade: this.props.animation,
'in': !this.props.animation
};
let modal = (
<div
{...this.props}
title={null}
tabIndex="-1"
role="dialog"
style={modalStyle}
className={classNames(this.props.className, classes)}
onClick={this.props.backdrop === true ? this.handleBackdropClick : null}
ref="modal">
<div className={classNames(this.props.dialogClassName, dialogClasses)}>
<div className="modal-content">
{this.props.title ? this.renderHeader() : null}
{this.props.children}
</div>
</div>
</div>
);
return this.props.backdrop ?
this.renderBackdrop(modal) : modal;
},
renderBackdrop(modal) {
let classes = {
'modal-backdrop': true,
fade: this.props.animation,
'in': !this.props.animation
};
let onClick = this.props.backdrop === true ?
this.handleBackdropClick : null;
return (
<div>
<div className={classNames(classes)} ref="backdrop" onClick={onClick} />
{modal}
</div>
);
},
renderHeader() {
let closeButton;
if (this.props.closeButton) {
closeButton = (
<button type="button" className="close" aria-hidden="true" onClick={this.props.onRequestHide}>×</button>
);
}
return (
<div className="modal-header">
{closeButton}
{this.renderTitle()}
</div>
);
},
renderTitle() {
return (
React.isValidElement(this.props.title) ?
this.props.title : <h4 className="modal-title">{this.props.title}</h4>
);
},
iosClickHack() {
// IOS only allows click events to be delegated to the document on elements
// it considers 'clickable' - anchors, buttons, etc. We fake a click handler on the
// DOM nodes themselves. Remove if handled by React: https://github.com/facebook/react/issues/1169
React.findDOMNode(this.refs.modal).onclick = function () {};
React.findDOMNode(this.refs.backdrop).onclick = function () {};
},
componentDidMount() {
this._onDocumentKeyupListener =
EventListener.listen(domUtils.ownerDocument(this), 'keyup', this.handleDocumentKeyUp);
let container = (this.props.container && React.findDOMNode(this.props.container)) ||
domUtils.ownerDocument(this).body;
container.className += container.className.length ? ' modal-open' : 'modal-open';
this.focusModalContent();
if (this.props.backdrop) {
this.iosClickHack();
}
},
componentDidUpdate(prevProps) {
if (this.props.backdrop && this.props.backdrop !== prevProps.backdrop) {
this.iosClickHack();
}
},
componentWillUnmount() {
this._onDocumentKeyupListener.remove();
let container = (this.props.container && React.findDOMNode(this.props.container)) ||
domUtils.ownerDocument(this).body;
container.className = container.className.replace(/ ?modal-open/, '');
this.restoreLastFocus();
},
handleBackdropClick(e) {
if (e.target !== e.currentTarget) {
return;
}
this.props.onRequestHide();
},
handleDocumentKeyUp(e) {
if (this.props.keyboard && e.keyCode === 27) {
this.props.onRequestHide();
}
},
focusModalContent () {
this.lastFocus = domUtils.ownerDocument(this).activeElement;
let modalContent = React.findDOMNode(this.refs.modal);
modalContent.focus();
},
restoreLastFocus () {
if (this.lastFocus) {
this.lastFocus.focus();
this.lastFocus = null;
}
}
});
export default Modal;