Skip to content

Commit 6c2293e

Browse files
committed
feat(Modal): add modal components
1 parent a84df68 commit 6c2293e

9 files changed

+544
-0
lines changed

Diff for: lib/Modal.jsx

+158
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,158 @@
1+
import React, { PropTypes } from 'react';
2+
import ReactDOM from 'react-dom';
3+
import classNames from 'classnames';
4+
import Fade from './Fade';
5+
6+
const propTypes = {
7+
isOpen: PropTypes.bool,
8+
size: PropTypes.string,
9+
toggle: PropTypes.func.isRequired,
10+
onEnter: PropTypes.func,
11+
onExit: PropTypes.func
12+
};
13+
14+
const defaultProps = {
15+
isOpen: false
16+
};
17+
18+
class Modal extends React.Component {
19+
constructor(props) {
20+
super(props);
21+
22+
this.handleProps = this.handleProps.bind(this);
23+
this.handleBackdropClick = this.handleBackdropClick.bind(this);
24+
this.handleEscape = this.handleEscape.bind(this);
25+
this.destroy = this.destroy.bind(this);
26+
this.onEnter = this.onEnter.bind(this);
27+
this.onExit = this.onExit.bind(this);
28+
}
29+
30+
componentDidMount() {
31+
if (this.props.isOpen) {
32+
this.show();
33+
}
34+
}
35+
36+
componentDidUpdate(prevProps) {
37+
if (this.props.isOpen !== prevProps.isOpen) {
38+
this.handleProps();
39+
}
40+
}
41+
42+
componentWillUnmount() {
43+
this.hide();
44+
}
45+
46+
onEnter() {
47+
this._modal.fadeIn();
48+
if (this.props.onEnter) {
49+
this.props.onEnter();
50+
}
51+
}
52+
53+
onExit() {
54+
this.destroy();
55+
if (this.props.onExit) {
56+
this.props.onExit();
57+
}
58+
}
59+
60+
handleEscape(e) {
61+
if (e.keyCode === 27) {
62+
this.props.toggle();
63+
}
64+
}
65+
66+
handleBackdropClick(e) {
67+
const container = ReactDOM.findDOMNode(this._dialog);
68+
69+
if (e.target && !container.contains(e.target)) {
70+
this.props.toggle();
71+
}
72+
}
73+
74+
handleProps() {
75+
if (this.props.isOpen) {
76+
this.show();
77+
} else {
78+
this.hide();
79+
}
80+
}
81+
82+
destroy() {
83+
let classes = document.body.className.replace('modal-open', '');
84+
this.removeEvents();
85+
86+
if (this._element) {
87+
ReactDOM.unmountComponentAtNode(this._element);
88+
document.body.removeChild(this._element);
89+
this._element = null;
90+
}
91+
92+
document.body.className = classNames(classes).trim();
93+
}
94+
95+
removeEvents() {
96+
document.removeEventListener('click', this.handleBackdropClick, false);
97+
document.removeEventListener('keyup', this.handleEscape, false);
98+
}
99+
100+
hide() {
101+
this.removeEvents();
102+
103+
if (this._modal) {
104+
this._modal.fadeOut();
105+
}
106+
if (this._backdrop) {
107+
this._backdrop.fadeOut(this.onExit, 250);
108+
}
109+
}
110+
111+
show() {
112+
const classes = document.body.className;
113+
this._element = document.createElement('div');
114+
this._element.setAttribute('tabindex', '-1');
115+
116+
document.body.appendChild(this._element);
117+
document.addEventListener('click', this.handleBackdropClick, false);
118+
document.addEventListener('keyup', this.handleEscape, false);
119+
120+
document.body.className = classNames(
121+
classes,
122+
'modal-open'
123+
);
124+
125+
ReactDOM.unstable_renderSubtreeIntoContainer(
126+
this,
127+
this.renderChildren(),
128+
this._element
129+
);
130+
131+
this._element.focus();
132+
this._backdrop.fadeIn(this.onEnter, 100);
133+
}
134+
135+
renderChildren() {
136+
return (
137+
<div>
138+
<Fade className="modal" style={{ display: 'block' }} tabIndex="-1" ref={(c) => this._modal = c }>
139+
<div className="modal-dialog" role="document" ref={(c) => this._dialog = c }>
140+
<div className="modal-content">
141+
{ this.props.children }
142+
</div>
143+
</div>
144+
</Fade>
145+
<Fade className="modal-backdrop" ref={(c) => this._backdrop = c }/>
146+
</div>
147+
);
148+
}
149+
150+
render() {
151+
return null;
152+
}
153+
}
154+
155+
Modal.propTypes = propTypes;
156+
Modal.defaultProps = defaultProps;
157+
158+
export default Modal;

Diff for: lib/ModalBody.jsx

+21
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
import React, { PropTypes } from 'react';
2+
import classNames from 'classnames';
3+
4+
const propTypes = {
5+
className: PropTypes.string
6+
};
7+
8+
const ModalBody = (props) => {
9+
const classes = classNames(
10+
props.className,
11+
'modal-body'
12+
);
13+
14+
return (
15+
<div {...props} className={classes} />
16+
);
17+
};
18+
19+
ModalBody.propTypes = propTypes;
20+
21+
export default ModalBody;

Diff for: lib/ModalFooter.jsx

+21
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
import React, { PropTypes } from 'react';
2+
import classNames from 'classnames';
3+
4+
const propTypes = {
5+
className: PropTypes.string
6+
};
7+
8+
const ModalFooter = (props) => {
9+
const classes = classNames(
10+
props.className,
11+
'modal-footer'
12+
);
13+
14+
return (
15+
<div {...props} className={classes} />
16+
);
17+
};
18+
19+
ModalFooter.propTypes = propTypes;
20+
21+
export default ModalFooter;

Diff for: lib/ModalHeader.jsx

+46
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
import React, { PropTypes } from 'react';
2+
import classNames from 'classnames';
3+
4+
const propTypes = {
5+
toggle: PropTypes.func
6+
};
7+
8+
const defaultProps = {};
9+
10+
class ModalHeader extends React.Component {
11+
render() {
12+
let closeButton;
13+
const {
14+
className,
15+
children,
16+
toggle,
17+
...props } = this.props;
18+
19+
const classes = classNames(
20+
className,
21+
'modal-header'
22+
);
23+
24+
if (toggle) {
25+
closeButton = (
26+
<button type="button" onClick={toggle} className="close" dataDismiss="modal" ariaLabel="Close">
27+
<span ariaHidden="true">&times;</span>
28+
</button>
29+
);
30+
}
31+
32+
return (
33+
<div {...props} className={classes}>
34+
{ closeButton }
35+
<h4 className="modal-title">
36+
{ children }
37+
</h4>
38+
</div>
39+
);
40+
}
41+
}
42+
43+
ModalHeader.propTypes = propTypes;
44+
ModalHeader.defaultProps = defaultProps;
45+
46+
export default ModalHeader;

Diff for: lib/index.js

+8
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,10 @@ import Label from './Label';
1111
import Popover from './Popover';
1212
import PopoverTitle from './PopoverTitle';
1313
import PopoverContent from './PopoverContent';
14+
import Modal from './Modal';
15+
import ModalHeader from './ModalHeader';
16+
import ModalBody from './ModalBody';
17+
import ModalFooter from './ModalFooter';
1418
import TetherContent from './TetherContent';
1519
import Tooltip from './Tooltip';
1620

@@ -28,6 +32,10 @@ export {
2832
Popover,
2933
PopoverContent,
3034
PopoverTitle,
35+
Modal,
36+
ModalHeader,
37+
ModalBody,
38+
ModalFooter,
3139
TetherContent,
3240
Tooltip
3341
};

0 commit comments

Comments
 (0)