Skip to content

Commit

Permalink
feat(modal): add backdrop and keyboard options (#135)
Browse files Browse the repository at this point in the history
Closes #134
Add backdrop prop to modal with the following options
 - `true` show backdrop, close on click (default)
 - `false` hide backdrop
 - `"static"` show backdrop, do not close on click
Add keyboard prop to modal with the following options
 - `true` close on escape (default)
 - `false` do not close on escape

Add tests and docs
  • Loading branch information
TheSharpieOne authored and eddywashere committed Sep 14, 2016
1 parent 74e9d31 commit 7bf5d0a
Show file tree
Hide file tree
Showing 5 changed files with 203 additions and 5 deletions.
25 changes: 24 additions & 1 deletion docs/lib/Components/ModalsPage.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,9 @@ import Helmet from 'react-helmet';
import ModalExample from '../examples/Modal';
const ModalExampleSource = require('!!raw!../examples/Modal');

import ModalBackdropExample from '../examples/ModalBackdrop';
const ModalBackdropExampleSource = require('!!raw!../examples/ModalBackdrop');

export default class ModalsPage extends React.Component {
render() {
return (
Expand Down Expand Up @@ -37,10 +40,30 @@ export default class ModalsPage extends React.Component {
// boolean to control the state of the popover
toggle: PropTypes.func,
// callback for toggling isOpen in the controlling component
size: PropTypes.string
size: PropTypes.string,
// control backdrop, see http://v4-alpha.getbootstrap.com/components/modal/#options
backdrop: PropTypes.oneOfType([
PropTypes.bool,
PropTypes.oneOf(['static'])
]),
keyboard: PropTypes.bool
}`}
</PrismCode>
</pre>

<h4>Backdrop</h4>
<div className="docs-example">
<div className="btn-group">
<div className="btn">
<ModalBackdropExample buttonLabel="Launch Modal" />
</div>
</div>
</div>
<pre>
<PrismCode className="language-jsx">
{ModalBackdropExampleSource}
</PrismCode>
</pre>
</div>
);
}
Expand Down
62 changes: 62 additions & 0 deletions docs/lib/examples/ModalBackdrop.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
/* eslint react/no-multi-comp: 0, react/prop-types: 0 */

import React from 'react';
import { Button, Modal, ModalHeader, ModalBody, ModalFooter, Input, Label, Form, FormGroup } from 'reactstrap';

class ModalExample extends React.Component {
constructor(props) {
super(props);
this.state = {
modal: false,
backdrop: true
};

this.toggle = this.toggle.bind(this);
this.changeBackdrop = this.changeBackdrop.bind(this);
}

toggle() {
this.setState({
modal: !this.state.modal
});
}

changeBackdrop(e) {
let value = e.target.value;
if (value !== 'static') {
value = JSON.parse(value);
}
this.setState({ backdrop: value });
}

render() {
return (
<div>
<Form inline onSubmit={(e) => e.preventDefault()}>
<FormGroup>
<Label for="backdrop">Backdrop value</Label>{' '}
<Input type="select" name="backdrop" id="backdrop" onChange={this.changeBackdrop}>
<option value="true">true</option>
<option value="false">false</option>
<option value="static">"static"</option>
</Input>
</FormGroup>
{' '}
<Button color="danger" onClick={this.toggle}>{this.props.buttonLabel}</Button>
</Form>
<Modal isOpen={this.state.modal} toggle={this.toggle} className={this.props.className} backdrop={this.state.backdrop}>
<ModalHeader toggle={this.toggle}>Modal title</ModalHeader>
<ModalBody>
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.
</ModalBody>
<ModalFooter>
<Button color="primary" onClick={this.toggle}>Do Something</Button>
<Button color="secondary" onClick={this.toggle}>Cancel</Button>
</ModalFooter>
</Modal>
</div>
);
}
}

export default ModalExample;
15 changes: 12 additions & 3 deletions src/Modal.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,21 @@ const propTypes = {
isOpen: PropTypes.bool,
size: PropTypes.string,
toggle: PropTypes.func.isRequired,
keyboard: PropTypes.bool,
backdrop: PropTypes.oneOfType([
PropTypes.bool,
PropTypes.oneOf(['static'])
]),
onEnter: PropTypes.func,
onExit: PropTypes.func,
children: PropTypes.node,
className: PropTypes.any
};

const defaultProps = {
isOpen: false
isOpen: false,
backdrop: true,
keyboard: true
};

class Modal extends React.Component {
Expand Down Expand Up @@ -64,12 +71,14 @@ class Modal extends React.Component {
}

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

handleBackdropClick(e) {
if (this.props.backdrop !== true) return;

const container = this._dialog;

if (e.target && !container.contains(e.target)) {
Expand Down Expand Up @@ -168,7 +177,7 @@ class Modal extends React.Component {
</div>
</Fade>
)}
{this.props.isOpen && (
{this.props.isOpen && this.props.backdrop && (
<Fade
key="modal-backdrop"
transitionAppearTimeout={150}
Expand Down
2 changes: 1 addition & 1 deletion src/TabContent.js
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ export default class TabContent extends Component {
const classes = classnames('tab-content', this.props.className);
return (
<div className={classes}>
{ this.props.children }
{this.props.children}
</div>
);
}
Expand Down
104 changes: 104 additions & 0 deletions test/Modal.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,49 @@ describe('Modal', () => {
wrapper.unmount();
});

it('should render with the backdrop with the class "modal-backdrop" by default', () => {
isOpen = true;
const wrapper = mount(
<Modal isOpen={isOpen} toggle={toggle}>
Yo!
</Modal>
);

jasmine.clock().tick(300);
expect(wrapper.children().length).toBe(0);
expect(document.getElementsByClassName('modal-backdrop').length).toBe(1);
wrapper.unmount();
});

it('should render with the backdrop with the class "modal-backdrop" when backdrop is "static"', () => {
isOpen = true;
const wrapper = mount(
<Modal isOpen={isOpen} toggle={toggle} backdrop="static">
Yo!
</Modal>
);

jasmine.clock().tick(300);
expect(wrapper.children().length).toBe(0);
expect(document.getElementsByClassName('modal-backdrop').length).toBe(1);
wrapper.unmount();
});

it('should not render with the backdrop with the class "modal-backdrop" when backdrop is "false"', () => {
isOpen = true;
const wrapper = mount(
<Modal isOpen={isOpen} toggle={toggle} backdrop={false}>
Yo!
</Modal>
);

jasmine.clock().tick(300);
expect(wrapper.children().length).toBe(0);
expect(document.getElementsByClassName('modal-dialog').length).toBe(1);
expect(document.getElementsByClassName('modal-backdrop').length).toBe(0);
wrapper.unmount();
});

it('should render with class "modal-dialog" and have custom class name if provided', () => {
isOpen = true;
const wrapper = mount(
Expand Down Expand Up @@ -248,6 +291,41 @@ describe('Modal', () => {
wrapper.unmount();
});

it('should not close modal when escape key pressed when keyboard is false', () => {
isOpen = true;
const wrapper = mount(
<Modal isOpen={isOpen} toggle={toggle} keyboard={false}>
Yo!
</Modal>
);
const instance = wrapper.instance();

jasmine.clock().tick(300);

expect(isOpen).toBe(true);
expect(document.getElementsByClassName('modal').length).toBe(1);

instance.handleEscape({ keyCode: 13 });
jasmine.clock().tick(300);

expect(isOpen).toBe(true);
expect(document.getElementsByClassName('modal').length).toBe(1);

instance.handleEscape({ keyCode: 27 });
jasmine.clock().tick(300);

expect(isOpen).toBe(true);

wrapper.setProps({
isOpen: isOpen
});
jasmine.clock().tick(300);

expect(document.getElementsByClassName('modal').length).toBe(1);

wrapper.unmount();
});

it('should close modal when clicking backdrop', () => {
isOpen = true;
const wrapper = mount(
Expand All @@ -274,6 +352,32 @@ describe('Modal', () => {
wrapper.unmount();
});

it('should not close modal when clicking backdrop and backdrop is "static"', () => {
isOpen = true;
const wrapper = mount(
<Modal isOpen={isOpen} toggle={toggle} backdrop="static">
<button id="clicker">Does Nothing</button>
</Modal>
);

jasmine.clock().tick(300);

expect(isOpen).toBe(true);
expect(document.getElementsByClassName('modal').length).toBe(1);

document.getElementById('clicker').click();
jasmine.clock().tick(300);

expect(isOpen).toBe(true);

document.getElementsByClassName('modal-backdrop')[0].click();
jasmine.clock().tick(300);

expect(isOpen).toBe(true);

wrapper.unmount();
});

it('should destroy this._element', () => {
isOpen = true;
const wrapper = mount(
Expand Down

0 comments on commit 7bf5d0a

Please sign in to comment.