diff --git a/package.json b/package.json index fb2567188..5c8a0a176 100644 --- a/package.json +++ b/package.json @@ -10,7 +10,8 @@ "babel-core": "^5.4.7", "babel-eslint": "^3.1.10", "babel-loader": "^5.1.3", - "bootstrap-sass": "3.3.1", + "bootstrap-sass": "3.3.5", + "classlist-polyfill": "^1.0.1", "classnames": "^2.1.2", "conventional-changelog": "0.1.0-alpha.1", "conventional-recommended-bump": "0.0.1", diff --git a/spec/pivotal-ui-react/modals/modals_spec.js b/spec/pivotal-ui-react/modals/modals_spec.js index 6a85d4021..959b4819c 100644 --- a/spec/pivotal-ui-react/modals/modals_spec.js +++ b/spec/pivotal-ui-react/modals/modals_spec.js @@ -61,7 +61,7 @@ describe('Modals', function() { }); describe('when mounting', function() { - it('removes the key up event listener', function() { + it('adds the key up event listener', function() { expect(document.body.addEventListener).toHaveBeenCalledWith('keyup', subject.refs.modal.onKeyUp, false); }); }); @@ -74,34 +74,61 @@ describe('Modals', function() { }); }); - describe('clicking on the modal trigger', function() { - beforeEach(function() { - $('#openButton').simulate('click'); - }); - + function itOpensTheModal() { it('renders a modal', function() { expect('.modal').toExist(); - expect('.modal').toHaveClass('modal-basic'); - expect('.modal .modal-backdrop').toHaveClass('fade'); - expect('.modal .modal-backdrop').toHaveClass('in'); expect('.modal .modal-header').toContainText('What a Header!'); expect('.modal .modal-body').toContainText('Text in a body'); expect('.modal .modal-footer').toContainText('Text in a footer'); }); + it('adds the modal-open class to the body', () => { + expect('body').toHaveClass('modal-open'); + }); + + it('renders a modal-backdrop', function() { + expect('.modal-backdrop').toExist(); + }); + } + + function itKeepsTheModalOpen() { itOpensTheModal(); } + + function itClosesTheModal() { + it('closes the modal', function() { + expect('.modal').not.toExist(); + }); + + it('removes the modal-backdrop', function() { + expect('.modal-backdrop').not.toExist(); + }); + + it('removes the modal-open class from the body', function() { + expect('body').not.toHaveClass('modal-open'); + }); + } + + describe('clicking on the modal trigger', function() { + beforeEach(function() { + $('#openButton').simulate('click'); + }); + + itOpensTheModal(); + describe('pressing any key', function() { describe('for the escape key', function() { - it('closes the modal', function() { + beforeEach(function() { document.body.addEventListener.calls.mostRecent().args[1]({keyCode: 27}); - expect(subject.refs.modal.state.isVisible).toBe(false); }); + + itClosesTheModal(); }); describe('any other key', function() { - it('does not close the modal', function() { + beforeEach(function() { document.body.addEventListener.calls.mostRecent().args[1]({keyCode: 13}); - expect(subject.refs.modal.state.isVisible).toBe(true); }); + + itKeepsTheModalOpen(); }); }); @@ -110,29 +137,31 @@ describe('Modals', function() { $('.modal button.close').simulate('click'); }); - it('closes the modal', function() { - expect(subject.refs.modal.state.isVisible).toBe(false); - }); + itClosesTheModal(); describe('opening the modal again', function() { beforeEach(function() { $('#openButton').simulate('click'); }); - it('opens the modal', function() { - expect(subject.refs.modal.state.isVisible).toBe(true); - }); + itOpensTheModal(); }); }); - describe('clicking on the modal backdrop', function() { + describe('clicking inside the modal-dialog', function() { beforeEach(function() { - $('.modal .modal-backdrop').simulate('click'); + $('.modal-dialog').simulate('click'); }); - it('closes the modal', function() { - expect(subject.refs.modal.state.isVisible).toBe(false); + itKeepsTheModalOpen(); + }); + + describe('clicking outside the modal-dialog', function() { + beforeEach(function() { + $('.modal').simulate('click'); }); + + itClosesTheModal(); }); describe('clicking the close button', function() { @@ -140,9 +169,7 @@ describe('Modals', function() { $('#closeButton').simulate('click'); }); - it('closes the modal', function() { - expect(subject.refs.modal.state.isVisible).toBe(false); - }); + itClosesTheModal(); }); }); }); diff --git a/src/pivotal-ui-react/modals/modals.js b/src/pivotal-ui-react/modals/modals.js index 9d80c5b54..dc26ae08c 100644 --- a/src/pivotal-ui-react/modals/modals.js +++ b/src/pivotal-ui-react/modals/modals.js @@ -1,5 +1,6 @@ var React = require('react/addons'); var {DefaultH4} = require('pui-react-typography'); +require('classlist-polyfill'); var ReactCSSTransitionGroup = React.addons.CSSTransitionGroup; /** @@ -56,7 +57,16 @@ var Modal = React.createClass({ }, getInitialState() { - return ({isVisible: false}); + return {isVisible: false}; + }, + + componentWillUpdate(nextProps, nextState) { + if (nextState.isVisible) { + document.body.classList.add('modal-open'); + } + else { + document.body.classList.remove('modal-open'); + } }, open() { @@ -67,6 +77,12 @@ var Modal = React.createClass({ this.setState({isVisible: false}); }, + childrenClick(e) { + if (e.target === this.refs.modal.getDOMNode()) { + this.close(); + } + }, + onKeyUp(e) { if (e.keyCode === 27) { this.close(); @@ -74,10 +90,11 @@ var Modal = React.createClass({ }, render() { - var modalInnards = this.state.isVisible ? - ( -
-
+ let modal = null; + let backdrop = null; + if (this.state.isVisible) { + modal = ( +
@@ -87,14 +104,21 @@ var Modal = React.createClass({ {this.props.title}
- {this.props.children} + {this.props.children}
- ) : - null; + ); + backdrop = (
); + } + + return ( +
+ {backdrop} + {modal} +
+ ); - return {modalInnards}; } }); diff --git a/src/pivotal-ui-react/modals/package.json b/src/pivotal-ui-react/modals/package.json index e339c974b..bd4fcc51f 100644 --- a/src/pivotal-ui-react/modals/package.json +++ b/src/pivotal-ui-react/modals/package.json @@ -3,7 +3,8 @@ "description": "React components for opening and closing modal windows", "homepage": "http://styleguide.pivotal.io/react_beta.html#modal_react", "dependencies": { + "classlist-polyfill": "^1.0.1", "pui-css-modals": "1.10.0", "pui-react-typography": "1.10.0" } -} \ No newline at end of file +} diff --git a/src/pivotal-ui/components/bootstrap/bootstrap.scss b/src/pivotal-ui/components/bootstrap/bootstrap.scss index d18cfaf5e..8206afb09 100644 --- a/src/pivotal-ui/components/bootstrap/bootstrap.scss +++ b/src/pivotal-ui/components/bootstrap/bootstrap.scss @@ -2,4 +2,4 @@ @import "../pui-variables.scss"; @import "../mixins.scss"; -@import "../../frameworks/bootstrap-sass-v3.2.0.scss"; +@import "../../frameworks/bootstrap-sass-v3.3.5.scss"; diff --git a/src/pivotal-ui/components/modals/modals.scss b/src/pivotal-ui/components/modals/modals.scss index 127e2aad3..65593849d 100644 --- a/src/pivotal-ui/components/modals/modals.scss +++ b/src/pivotal-ui/components/modals/modals.scss @@ -19,6 +19,22 @@ opacity: 1; } +.modal-backdrop.in { + // Bootstrap defines the modal-backdrop opacity on the .modal-backdrop.in rule + // so we have to create more specific rules for our React transition group to + // actually see a transition :( + + //Class created in React Animations + &.modal-backdrop-fade-enter, &.modal-backdrop-fade-leave { + @include transition(opacity .15s linear); + opacity: 0; + } + + //Class created in React Animations + &.modal-backdrop-fade-enter-active { + opacity: $modal-backdrop-opacity; + } +} /*doc --- @@ -91,7 +107,7 @@ Modals bring desired content to the foreground and de-emphasize the rest of the .modal { .modal-dialog { - padding-top: 50px; + margin-top: 50px; } .modal-content { border-radius: $modal-basic-content-border-radius; diff --git a/src/pivotal-ui/components/pui-variables.scss b/src/pivotal-ui/components/pui-variables.scss index baa9e57bd..9c10bcc60 100644 --- a/src/pivotal-ui/components/pui-variables.scss +++ b/src/pivotal-ui/components/pui-variables.scss @@ -440,6 +440,10 @@ $btn-disabled-color: $gray-5; $btn-link-disabled-color: $gray-light !default; +$btn-border-radius-base: $border-radius-base; +$btn-border-radius-large: $border-radius-large; +$btn-border-radius-small: $border-radius-small; + // Forms // ------------------------- @@ -462,6 +466,8 @@ $input-height-base: ($line-height-computed + ($padding-base-vertica $input-height-large: (floor($font-size-large * $line-height-large) + ($padding-large-vertical * 2) + 2) !default; $input-height-small: (floor($font-size-small * $line-height-small) + ($padding-small-vertical * 2) + 2) !default; +$form-group-margin-bottom: 15px; + $legend-color: $gray-dark !default; $legend-border-color: #e5e5e5 !default; diff --git a/src/pivotal-ui/frameworks/bootstrap-sass-v3.2.0.scss b/src/pivotal-ui/frameworks/bootstrap-sass-v3.3.5.scss similarity index 100% rename from src/pivotal-ui/frameworks/bootstrap-sass-v3.2.0.scss rename to src/pivotal-ui/frameworks/bootstrap-sass-v3.3.5.scss