Skip to content

Commit 9d255e1

Browse files
committed
Merge pull request reactstrap#10 from reactstrap/modal
Modal Component
2 parents 5dadc6f + d97873a commit 9d255e1

14 files changed

+721
-2
lines changed

.travis.yml

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,11 @@
11
language: node_js
22

33
node_js:
4-
- 4
5-
- 5
4+
- stable
5+
6+
cache:
7+
directories:
8+
- node_modules
69

710
addons:
811
firefox: "latest"

example/js/Layout.jsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import React from 'react';
22
import ButtonsExample from './ButtonsExample';
33
import DropdownsExample from './DropdownsExample';
4+
import ModalExample from './ModalExample';
45
import TetherExample from './TetherExample';
56
import TooltipExample from './TooltipExample';
67
import LabelsExample from './LabelsExample';
@@ -19,6 +20,7 @@ class Layout extends React.Component {
1920
<DropdownsExample/>
2021
<TetherExample/>
2122
<TooltipExample/>
23+
<ModalExample/>
2224
<PopoverExample/>
2325
<LabelsExample/>
2426
</div>

example/js/ModalExample.jsx

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
/* eslint react/no-multi-comp: 0, react/prop-types: 0 */
2+
3+
import React from 'react';
4+
import { Button, Modal, ModalHeader, ModalBody, ModalFooter } from 'lib/index';
5+
6+
class ModalExample extends React.Component {
7+
constructor(props) {
8+
super(props);
9+
this.state = {
10+
modal: false
11+
};
12+
13+
this.toggle = this.toggle.bind(this);
14+
}
15+
16+
toggle() {
17+
this.setState({
18+
modal: !this.state.modal
19+
});
20+
}
21+
22+
render() {
23+
return (
24+
<div>
25+
<h3>Modals</h3>
26+
<hr/>
27+
<p>
28+
<Button color="danger" onClick={this.toggle}>Launch Modal</Button>
29+
</p>
30+
<Modal isOpen={this.state.modal} toggle={this.toggle}>
31+
<ModalHeader toggle={this.toggle}>Modal title</ModalHeader>
32+
<ModalBody>
33+
Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.
34+
</ModalBody>
35+
<ModalFooter>
36+
<Button color="primary" onClick={this.toggle}>Do Something</Button>
37+
<Button color="secondary" onClick={this.toggle}>Cancel</Button>
38+
</ModalFooter>
39+
</Modal>
40+
</div>
41+
);
42+
}
43+
}
44+
45+
export default ModalExample;

lib/Fade.jsx

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
import React, { PropTypes } from 'react';
2+
import classNames from 'classnames';
3+
4+
const propTypes = {
5+
className: PropTypes.string
6+
};
7+
8+
const defaultProps = {};
9+
10+
class Fade extends React.Component {
11+
constructor(props) {
12+
super(props);
13+
14+
this.state = {
15+
fadeIn: false
16+
};
17+
18+
this.fade = this.fade.bind(this);
19+
this.fadeIn = this.fadeIn.bind(this);
20+
this.fadeOut = this.fadeOut.bind(this);
21+
}
22+
23+
fade(fade, cb, delay) {
24+
this.setState({
25+
fadeIn: fade
26+
});
27+
28+
if (cb) {
29+
setTimeout(cb, delay);
30+
}
31+
}
32+
33+
fadeIn(cb, delay) {
34+
this.fade(true, cb, delay);
35+
}
36+
37+
fadeOut(cb, delay) {
38+
this.fade(false, cb, delay);
39+
}
40+
41+
render() {
42+
const classes = classNames(
43+
this.props.className,
44+
'fade',
45+
{
46+
in: this.state.fadeIn
47+
}
48+
);
49+
50+
return (
51+
<div {...this.props} className={classes} />
52+
);
53+
}
54+
}
55+
56+
Fade.propTypes = propTypes;
57+
Fade.defaultProps = defaultProps;
58+
59+
export default Fade;

lib/Modal.jsx

Lines changed: 158 additions & 0 deletions
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;

lib/ModalBody.jsx

Lines changed: 21 additions & 0 deletions
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;

lib/ModalFooter.jsx

Lines changed: 21 additions & 0 deletions
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;

lib/ModalHeader.jsx

Lines changed: 46 additions & 0 deletions
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;

0 commit comments

Comments
 (0)