diff --git a/docs/examples/ModalTrigger.js b/docs/examples/Modal.js similarity index 100% rename from docs/examples/ModalTrigger.js rename to docs/examples/Modal.js diff --git a/docs/examples/ModalCustomSizing.js b/docs/examples/ModalCustomSizing.js index 4122e352d4..701d67338a 100644 --- a/docs/examples/ModalCustomSizing.js +++ b/docs/examples/ModalCustomSizing.js @@ -1,40 +1,52 @@ -const MyModal = React.createClass({ - render() { +const Example = React.createClass({ + + getInitialState(){ + return { show: false }; + }, + + render(){ + let close = e => this.setState({ show: false }); + return ( - - - Modal heading - - -

Wrapped Text

-

Ipsum molestiae natus adipisci modi eligendi? Debitis amet quae unde commodi aspernatur enim, consectetur. Cumque deleniti temporibus ipsam atque a dolores quisquam quisquam adipisci possimus laboriosam. Quibusdam facilis doloribus debitis! Sit quasi quod accusamus eos quod. Ab quos consequuntur eaque quo rem! - Mollitia reiciendis porro quo magni incidunt dolore amet atque facilis ipsum deleniti rem! Dolores debitis voluptatibus ipsum dicta. Dolor quod amet ab sint esse distinctio tenetur. Veritatis laudantium quibusdam quidem corporis architecto veritatis. Ex facilis minima beatae sunt perspiciatis placeat. Quasi corporis - odio eaque voluptatibus ratione magnam nulla? Amet cum maiores consequuntur totam dicta! Inventore adipisicing vel vero odio modi doloremque? Vitae porro impedit ea minima laboriosam quisquam neque. Perspiciatis omnis obcaecati consequatur sunt deleniti similique facilis sequi. Ipsum harum vitae modi reiciendis officiis. - Quas laudantium laudantium modi corporis nihil provident consectetur omnis, natus nulla distinctio illum corporis. Sit ex earum odio ratione consequatur odit minus laborum? Eos? Sit ipsum illum architecto aspernatur perspiciatis error fuga illum, tempora harum earum, a dolores. Animi facilis inventore harum dolore accusamus - fuga provident molestiae eum! Odit dicta error dolorem sunt reprehenderit. Sit similique iure quae obcaecati harum. Eum saepe fugit magnam dicta aliquam? Sapiente possimus aliquam fugiat officia culpa sint! Beatae voluptates voluptatem excepturi molestiae alias in tenetur beatae placeat architecto. Sit possimus rerum - fugiat sapiente aspernatur. Necessitatibus tempora animi dicta perspiciatis tempora a velit in! Doloribus perspiciatis doloribus suscipit nam earum. Deleniti veritatis eaque totam assumenda fuga sapiente! Id recusandae. Consectetur necessitatibus eaque velit nobis aliquid? Fugit illum qui suscipit aspernatur alias ipsum - repudiandae! Quia omnis quisquam dignissimos a mollitia. Suscipit aspernatur eum maiores repellendus ipsum doloribus alias voluptatum consequatur. Consectetur quibusdam veniam quas tenetur necessitatibus repudiandae? Rem optio vel alias neque optio sapiente quidem similique reiciendis tempore. Illum accusamus officia - cum enim minima eligendi consectetur nemo veritatis nam nisi! Adipisicing nobis perspiciatis dolorum adipisci soluta architecto doloremque voluptatibus omnis debitis quas repellendus. Consequuntur assumenda illum commodi mollitia asperiores? Quis aspernatur consequatur modi veritatis aliquid at? Atque vel iure quos. - Amet provident voluptatem amet aliquam deserunt sint, elit dolorem ipsa, voluptas? Quos esse facilis neque nihil sequi non? Voluptates rem ab quae dicta culpa dolorum sed atque molestias debitis omnis! Sit sint repellendus deleniti officiis distinctio. Impedit vel quos harum doloribus corporis. Laborum ullam nemo quaerat - reiciendis recusandae minima dicta molestias rerum. Voluptas et ut omnis est ipsum accusamus harum. Amet exercitationem quasi velit inventore neque doloremque! Consequatur neque dolorem vel impedit sunt voluptate. Amet quo amet magni exercitationem libero recusandae possimus pariatur. Cumque eum blanditiis vel vitae - distinctio! Tempora! Consectetur sit eligendi neque sunt soluta laudantium natus qui aperiam quisquam consectetur consequatur sit sint a unde et. At voluptas ut officiis esse totam quasi dolorem! Hic deserunt doloribus repudiandae! Lorem quod ab nostrum asperiores aliquam ab id consequatur, expedita? Tempora quaerat - ex ea temporibus in tempore voluptates cumque. Quidem nam dolor reiciendis qui dolor assumenda ipsam veritatis quasi. Esse! Sit consectetur hic et sunt iste! Accusantium atque elit voluptate asperiores corrupti temporibus mollitia! Placeat soluta odio ad blanditiis nisi. Eius reiciendis id quos dolorum eaque suscipit - magni delectus maxime. Sit odit provident vel magnam quod. Possimus eligendi non corrupti tenetur culpa accusantium quod quis. Voluptatum quaerat animi dolore maiores molestias voluptate? Necessitatibus illo omnis laborum hic enim minima! Similique. Dolor voluptatum reprehenderit nihil adipisci aperiam voluptatem soluta - magnam accusamus iste incidunt tempore consequatur illo illo odit. Asperiores nesciunt iusto nemo animi ratione. Sunt odit similique doloribus temporibus reiciendis! Ullam. Dolor dolores veniam animi sequi dolores molestias voluptatem iure velit. Elit dolore quaerat incidunt enim aut distinctio. Ratione molestiae laboriosam - similique laboriosam eum et nemo expedita. Consequuntur perspiciatis cumque dolorem.

-
- - - -
+ + + + + + Modal heading + + +

Wrapped Text

+

Ipsum molestiae natus adipisci modi eligendi? Debitis amet quae unde commodi aspernatur enim, consectetur. Cumque deleniti temporibus ipsam atque a dolores quisquam quisquam adipisci possimus laboriosam. Quibusdam facilis doloribus debitis! Sit quasi quod accusamus eos quod. Ab quos consequuntur eaque quo rem! + Mollitia reiciendis porro quo magni incidunt dolore amet atque facilis ipsum deleniti rem! Dolores debitis voluptatibus ipsum dicta. Dolor quod amet ab sint esse distinctio tenetur. Veritatis laudantium quibusdam quidem corporis architecto veritatis. Ex facilis minima beatae sunt perspiciatis placeat. Quasi corporis + odio eaque voluptatibus ratione magnam nulla? Amet cum maiores consequuntur totam dicta! Inventore adipisicing vel vero odio modi doloremque? Vitae porro impedit ea minima laboriosam quisquam neque. Perspiciatis omnis obcaecati consequatur sunt deleniti similique facilis sequi. Ipsum harum vitae modi reiciendis officiis. + Quas laudantium laudantium modi corporis nihil provident consectetur omnis, natus nulla distinctio illum corporis. Sit ex earum odio ratione consequatur odit minus laborum? Eos? Sit ipsum illum architecto aspernatur perspiciatis error fuga illum, tempora harum earum, a dolores. Animi facilis inventore harum dolore accusamus + fuga provident molestiae eum! Odit dicta error dolorem sunt reprehenderit. Sit similique iure quae obcaecati harum. Eum saepe fugit magnam dicta aliquam? Sapiente possimus aliquam fugiat officia culpa sint! Beatae voluptates voluptatem excepturi molestiae alias in tenetur beatae placeat architecto. Sit possimus rerum + fugiat sapiente aspernatur. Necessitatibus tempora animi dicta perspiciatis tempora a velit in! Doloribus perspiciatis doloribus suscipit nam earum. Deleniti veritatis eaque totam assumenda fuga sapiente! Id recusandae. Consectetur necessitatibus eaque velit nobis aliquid? Fugit illum qui suscipit aspernatur alias ipsum + repudiandae! Quia omnis quisquam dignissimos a mollitia. Suscipit aspernatur eum maiores repellendus ipsum doloribus alias voluptatum consequatur. Consectetur quibusdam veniam quas tenetur necessitatibus repudiandae? Rem optio vel alias neque optio sapiente quidem similique reiciendis tempore. Illum accusamus officia + cum enim minima eligendi consectetur nemo veritatis nam nisi! Adipisicing nobis perspiciatis dolorum adipisci soluta architecto doloremque voluptatibus omnis debitis quas repellendus. Consequuntur assumenda illum commodi mollitia asperiores? Quis aspernatur consequatur modi veritatis aliquid at? Atque vel iure quos. + Amet provident voluptatem amet aliquam deserunt sint, elit dolorem ipsa, voluptas? Quos esse facilis neque nihil sequi non? Voluptates rem ab quae dicta culpa dolorum sed atque molestias debitis omnis! Sit sint repellendus deleniti officiis distinctio. Impedit vel quos harum doloribus corporis. Laborum ullam nemo quaerat + reiciendis recusandae minima dicta molestias rerum. Voluptas et ut omnis est ipsum accusamus harum. Amet exercitationem quasi velit inventore neque doloremque! Consequatur neque dolorem vel impedit sunt voluptate. Amet quo amet magni exercitationem libero recusandae possimus pariatur. Cumque eum blanditiis vel vitae + distinctio! Tempora! Consectetur sit eligendi neque sunt soluta laudantium natus qui aperiam quisquam consectetur consequatur sit sint a unde et. At voluptas ut officiis esse totam quasi dolorem! Hic deserunt doloribus repudiandae! Lorem quod ab nostrum asperiores aliquam ab id consequatur, expedita? Tempora quaerat + ex ea temporibus in tempore voluptates cumque. Quidem nam dolor reiciendis qui dolor assumenda ipsam veritatis quasi. Esse! Sit consectetur hic et sunt iste! Accusantium atque elit voluptate asperiores corrupti temporibus mollitia! Placeat soluta odio ad blanditiis nisi. Eius reiciendis id quos dolorum eaque suscipit + magni delectus maxime. Sit odit provident vel magnam quod. Possimus eligendi non corrupti tenetur culpa accusantium quod quis. Voluptatum quaerat animi dolore maiores molestias voluptate? Necessitatibus illo omnis laborum hic enim minima! Similique. Dolor voluptatum reprehenderit nihil adipisci aperiam voluptatem soluta + magnam accusamus iste incidunt tempore consequatur illo illo odit. Asperiores nesciunt iusto nemo animi ratione. Sunt odit similique doloribus temporibus reiciendis! Ullam. Dolor dolores veniam animi sequi dolores molestias voluptatem iure velit. Elit dolore quaerat incidunt enim aut distinctio. Ratione molestiae laboriosam + similique laboriosam eum et nemo expedita. Consequuntur perspiciatis cumque dolorem.

+
+ + + +
+
); } }); -const overlayTriggerInstance = ( - }> - - -); -React.render(overlayTriggerInstance, mountNode); +React.render(, mountNode); diff --git a/docs/src/ComponentsPage.js b/docs/src/ComponentsPage.js index c3166eca4d..8e656f7048 100644 --- a/docs/src/ComponentsPage.js +++ b/docs/src/ComponentsPage.js @@ -278,7 +278,7 @@ const ComponentsPage = React.createClass({

Live demo

Use {''} in combination with other components to show or hide your Modal.

- +

Contained Modal

You will need to add the following css to your project and ensure that your container has the modal-container class.

diff --git a/docs/src/ReactPlayground.js b/docs/src/ReactPlayground.js index 02e170c48f..4ab4cd230b 100644 --- a/docs/src/ReactPlayground.js +++ b/docs/src/ReactPlayground.js @@ -26,9 +26,8 @@ import * as modNavbar from '../../src/Navbar'; import * as modNavItem from '../../src/NavItem'; import * as modMenuItem from '../../src/MenuItem'; import * as modModal from '../../src/Modal'; -import * as modModalTrigger from '../../src/ModalTrigger'; import * as modOverlayTrigger from '../../src/OverlayTrigger'; -import * as modOverlayMixin from '../../src/OverlayMixin'; + import * as modPageHeader from '../../src/PageHeader'; import * as modPageItem from '../../src/PageItem'; import * as modPager from '../../src/Pager'; @@ -36,7 +35,7 @@ import * as modPagination from '../../src/Pagination'; import * as modPanel from '../../src/Panel'; import * as modPanelGroup from '../../src/PanelGroup'; import * as modPopover from '../../src/Popover'; -//import * as modPopoverTrigger from '../../src/PopoverTrigger'; + import * as modProgressBar from '../../src/ProgressBar'; import * as modRow from '../../src/Row'; import * as modSplitButton from '../../src/SplitButton'; @@ -45,7 +44,7 @@ import * as modTable from '../../src/Table'; import * as modTabPane from '../../src/TabPane'; import * as modThumbnail from '../../src/Thumbnail'; import * as modTooltip from '../../src/Tooltip'; -//import * as modTooltipTrigger from '../../src/TooltipTrigger'; + import * as modWell from '../../src/Well'; import * as modPortal from '../../src/Portal'; @@ -88,9 +87,9 @@ const Navbar = modNavbar.default; const NavItem = modNavItem.default; const MenuItem = modMenuItem.default; const Modal = modModal.default; -const ModalTrigger = modModalTrigger.default; + const OverlayTrigger = modOverlayTrigger.default; -const OverlayMixin = modOverlayMixin.default; + const PageHeader = modPageHeader.default; const PageItem = modPageItem.default; const Pagination = modPagination.default; diff --git a/docs/src/Samples.js b/docs/src/Samples.js index 47e6720802..e6401dc08f 100644 --- a/docs/src/Samples.js +++ b/docs/src/Samples.js @@ -31,7 +31,7 @@ export default { PanelGroupAccordion: require('fs').readFileSync(__dirname + '/../examples/PanelGroupAccordion.js', 'utf8'), CollapsibleParagraph: require('fs').readFileSync(__dirname + '/../examples/CollapsibleParagraph.js', 'utf8'), ModalStatic: require('fs').readFileSync(__dirname + '/../examples/ModalStatic.js', 'utf8'), - ModalTrigger: require('fs').readFileSync(__dirname + '/../examples/ModalTrigger.js', 'utf8'), + Modal: require('fs').readFileSync(__dirname + '/../examples/Modal.js', 'utf8'), ModalContained: require('fs').readFileSync(__dirname + '/../examples/ModalContained.js', 'utf8'), ModalDefaultSizing: require('fs').readFileSync(__dirname + '/../examples/ModalDefaultSizing.js', 'utf8'), diff --git a/src/Input.js b/src/Input.js index d08eac7b35..ec1917da8e 100644 --- a/src/Input.js +++ b/src/Input.js @@ -5,7 +5,7 @@ import deprecationWarning from './utils/deprecationWarning'; class Input extends InputBase { render() { - if (this.props.type === 'static') { + if (this.props.type === 'static') { //eslint-disable-line react/prop-types deprecationWarning('Input type=static', 'StaticText'); return ; } diff --git a/src/Modal.js b/src/Modal.js index 70b5945bab..3816318c51 100644 --- a/src/Modal.js +++ b/src/Modal.js @@ -7,7 +7,6 @@ import BootstrapMixin from './BootstrapMixin'; import FadeMixin from './FadeMixin'; import domUtils from './utils/domUtils'; import EventListener from './utils/EventListener'; -import deprecationWarning from './utils/deprecationWarning'; import Portal from './Portal'; @@ -37,23 +36,6 @@ function getContainer(context){ domUtils.ownerDocument(context).body; } -function requiredIfNot(key, type){ - return function(props, propName, componentName){ - let propType = type; - - if ( props[ key] === undefined ){ - propType = propType.isRequired; - } - return propType(props, propName, componentName); - }; -} - -function toChildArray(children){ - let result = []; - React.Children.forEach(children, c => result.push(c)); - return result; -} - let currentFocusListener; @@ -113,14 +95,10 @@ function getScrollbarSize(){ const ModalMarkup = React.createClass({ - mixins: [BootstrapMixin, FadeMixin], + mixins: [ BootstrapMixin, FadeMixin ], propTypes: { - /** - * The Modal title text - * @deprecated Use the "Modal.Header" component instead - */ - title: React.PropTypes.node, + /** * Include a backdrop component. Specify 'static' for a backdrop that doesn't trigger an "onHide" when clicked. */ @@ -130,28 +108,17 @@ const ModalMarkup = React.createClass({ */ keyboard: React.PropTypes.bool, - /** - * Specify whether the Modal heading should contain a close button - * @deprecated Use the "Modal.Header" Component instead - */ - closeButton: React.PropTypes.bool, - /** * Open and close the Modal with a slide and fade animation. */ animation: React.PropTypes.bool, + /** * A Callback fired when the header closeButton or non-static backdrop is clicked. * @type {function} * @required */ - onHide: requiredIfNot('onRequestHide', React.PropTypes.func), - - /** - * A Callback fired when the header closeButton or non-static backdrop is clicked. - * @deprecated Replaced by `onHide`. - */ - onRequestHide: React.PropTypes.func, + onHide: React.PropTypes.func.isRequired, /** * A css class to apply to the Modal dialog DOM node. @@ -226,28 +193,12 @@ const ModalMarkup = React.createClass({ }, renderContent() { - let children = toChildArray(this.props.children); // b/c createFragment is in addons and children can be a key'd object - let hasNewHeader = children.some( c => c.type.__isModalHeader); - - if (!hasNewHeader && this.props.title != null){ - deprecationWarning( - 'Specifying `closeButton` or `title` Modal props', - 'the new Modal.Header, and Modal.Title components'); - - children.unshift( -
- { this.props.title && - {this.props.title} - } -
- ); - } - return React.Children.map(children, child => { + return React.Children.map(this.props.children, child => { // TODO: use context in 0.14 if (child.type.__isModalHeader) { return cloneElement(child, { - onHide: createChainedFunction(this._getHide(), child.props.onHide) + onHide: createChainedFunction(this.props.onHide, child.props.onHide) }); } return child; @@ -272,14 +223,6 @@ const ModalMarkup = React.createClass({ ); }, - _getHide(){ - if ( !this.props.onHide && this.props.onRequestHide){ - deprecationWarning('The Modal prop `onRequestHide`', 'the `onHide` prop'); - } - - return this.props.onHide || this.props.onRequestHide; - }, - 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 @@ -360,12 +303,12 @@ const ModalMarkup = React.createClass({ return; } - this._getHide()(); + this.props.onHide(); }, handleDocumentKeyUp(e) { if (this.props.keyboard && e.keyCode === 27) { - this._getHide()(); + this.props.onHide(); } }, @@ -438,26 +381,18 @@ const Modal = React.createClass({ ...ModalMarkup.propTypes }, - defaultProps: { - show: null - }, - render() { let { show, ...props } = this.props; let modal = ( - {this.props.children} + {this.props.children} + ); + + return ( + + { show && modal } + ); - // I can't think of another way to not break back compat while defaulting container - if ( !this.props.__isUsedInModalTrigger && show != null ){ - return ( - - { show && modal } - - ); - } else { - return modal; - } } }); diff --git a/src/ModalTrigger.js b/src/ModalTrigger.js deleted file mode 100644 index e1087dc384..0000000000 --- a/src/ModalTrigger.js +++ /dev/null @@ -1,121 +0,0 @@ -import React, { cloneElement } from 'react'; -import CustomPropTypes from './utils/CustomPropTypes'; -import deprecationWarning from './utils/deprecationWarning'; - -import createChainedFunction from './utils/createChainedFunction'; -import createContextWrapper from './utils/createContextWrapper'; -import { OverlayMixin } from './OverlayMixin'; - -function createHideDepreciationWrapper(hide){ - return function(...args){ - deprecationWarning( - 'The Modal prop `onRequestHide`', 'the `onHide` prop'); - - return hide(...args); - }; -} - -const ModalTrigger = React.createClass({ - - mixins: [ OverlayMixin ], - - propTypes: { - modal: React.PropTypes.node.isRequired, - /** - * The DOM Node that the Component will render it's children into - */ - container: CustomPropTypes.mountable, - onBlur: React.PropTypes.func, - onFocus: React.PropTypes.func, - onMouseOut: React.PropTypes.func, - onMouseOver: React.PropTypes.func - }, - - - getInitialState() { - return { - isOverlayShown: false - }; - }, - - show() { - this.setState({ - isOverlayShown: true - }); - }, - - hide() { - this.setState({ - isOverlayShown: false - }); - }, - - toggle() { - this.setState({ - isOverlayShown: !this.state.isOverlayShown - }); - }, - - renderOverlay() { - let modal = this.props.modal; - - if (!this.state.isOverlayShown) { - return ; - } - - return cloneElement( - modal, - { - onHide: this.hide, - onRequestHide: createHideDepreciationWrapper(this.hide), - __isUsedInModalTrigger: true - } - ); - }, - - render() { - let child = React.Children.only(this.props.children); - let props = {}; - - props.onClick = createChainedFunction(child.props.onClick, this.toggle); - props.onMouseOver = createChainedFunction(child.props.onMouseOver, this.props.onMouseOver); - props.onMouseOut = createChainedFunction(child.props.onMouseOut, this.props.onMouseOut); - props.onFocus = createChainedFunction(child.props.onFocus, this.props.onFocus); - props.onBlur = createChainedFunction(child.props.onBlur, this.props.onBlur); - - return cloneElement(child, props); - } -}); - -/** - * Creates a new ModalTrigger class that forwards the relevant context - * - * This static method should only be called at the module level, instead of in - * e.g. a render() method, because it's expensive to create new classes. - * - * For example, you would want to have: - * - * > export default ModalTrigger.withContext({ - * > myContextKey: React.PropTypes.object - * > }); - * - * and import this when needed. - */ -ModalTrigger.withContext = createContextWrapper(ModalTrigger, 'modal'); - -let DepreciatedModalTrigger = React.createClass({ - componentWillMount(){ - deprecationWarning( - 'The `ModalTrigger` component', 'the `Modal` component directly' - , 'http://react-bootstrap.github.io/components.html#modals'); - }, - - render(){ - return (); - } -}); - -DepreciatedModalTrigger.withContext = ModalTrigger.withContext; -DepreciatedModalTrigger.ModalTrigger = ModalTrigger; - -export default DepreciatedModalTrigger; diff --git a/src/OverlayMixin.js b/src/OverlayMixin.js deleted file mode 100644 index fc60ec99cb..0000000000 --- a/src/OverlayMixin.js +++ /dev/null @@ -1,95 +0,0 @@ -/*eslint-disable react/prop-types */ -import React from 'react'; -import CustomPropTypes from './utils/CustomPropTypes'; -import domUtils from './utils/domUtils'; -import deprecationWarning from './utils/deprecationWarning'; - -export const OverlayMixin = { - propTypes: { - - container: CustomPropTypes.mountable - }, - - - componentDidMount() { - this._renderOverlay(); - }, - - componentDidUpdate() { - this._renderOverlay(); - }, - - componentWillUnmount() { - this._unrenderOverlay(); - this._mountOverlayTarget(); - }, - - _mountOverlayTarget() { - if (!this._overlayTarget) { - this._overlayTarget = document.createElement('div'); - this.getContainerDOMNode() - .appendChild(this._overlayTarget); - } - }, - - _unmountOverlayTarget() { - if (this._overlayTarget) { - this.getContainerDOMNode() - .removeChild(this._overlayTarget); - this._overlayTarget = null; - } - }, - - _renderOverlay() { - - let overlay = this.renderOverlay(); - - // Save reference for future access. - if (overlay !== null) { - this._mountOverlayTarget(); - this._overlayInstance = React.render(overlay, this._overlayTarget); - } else { - // Unrender if the component is null for transitions to null - this._unrenderOverlay(); - this._unmountOverlayTarget(); - } - }, - - _unrenderOverlay() { - if (this._overlayTarget) { - React.unmountComponentAtNode(this._overlayTarget); - this._overlayInstance = null; - } - }, - - getOverlayDOMNode() { - if (!this.isMounted()) { - throw new Error('getOverlayDOMNode(): A component must be mounted to have a DOM node.'); - } - - if (this._overlayInstance) { - if (this._overlayInstance.getWrappedDOMNode) { - return this._overlayInstance.getWrappedDOMNode(); - } else { - return React.findDOMNode(this._overlayInstance); - } - } - - return null; - }, - - getContainerDOMNode() { - return React.findDOMNode(this.props.container) || domUtils.ownerDocument(this).body; - } -}; - -export default { - - ...OverlayMixin, - - componentWillMount() { - deprecationWarning( - 'Overlay mixin', 'the `` Component' - , 'http://react-bootstrap.github.io/components.html#utilities-portal'); - } -}; diff --git a/src/OverlayTrigger.js b/src/OverlayTrigger.js index 2be3bfd9d0..b90215f353 100644 --- a/src/OverlayTrigger.js +++ b/src/OverlayTrigger.js @@ -4,9 +4,6 @@ import React, { cloneElement } from 'react'; import createChainedFunction from './utils/createChainedFunction'; import createContextWrapper from './utils/createContextWrapper'; import Overlay from './Overlay'; -import position from './utils/overlayPositionUtils'; - -import deprecationWarning from './utils/deprecationWarning'; import warning from 'react/lib/warning'; /** @@ -33,7 +30,7 @@ const OverlayTrigger = React.createClass({ * Specify which action or actions trigger Overlay visibility */ trigger: React.PropTypes.oneOfType([ - React.PropTypes.oneOf(['manual', 'click', 'hover', 'focus']), + React.PropTypes.oneOf(['click', 'hover', 'focus']), React.PropTypes.arrayOf(React.PropTypes.oneOf(['click', 'hover', 'focus'])) ]), @@ -178,30 +175,24 @@ const OverlayTrigger = React.createClass({ // create in render otherwise owner is lost... this._overlay = this.getOverlay(); - if (this.props.trigger !== 'manual') { - - props.onClick = createChainedFunction(trigger.props.onClick, this.props.onClick); + props.onClick = createChainedFunction(trigger.props.onClick, this.props.onClick); - if (isOneOf('click', this.props.trigger)) { - props.onClick = createChainedFunction(this.toggle, props.onClick); - } - - if (isOneOf('hover', this.props.trigger)) { - warning(!(this.props.trigger === 'hover'), - '[react-bootstrap] Specifying only the `"hover"` trigger limits the visibilty of the overlay to just mouse users. ' + - 'Consider also including the `"focus"` trigger so that touch and keyboard only users can see the overlay as well.'); + if (isOneOf('click', this.props.trigger)) { + props.onClick = createChainedFunction(this.toggle, props.onClick); + } - props.onMouseOver = createChainedFunction(this.handleDelayedShow, this.props.onMouseOver); - props.onMouseOut = createChainedFunction(this.handleDelayedHide, this.props.onMouseOut); - } + if (isOneOf('hover', this.props.trigger)) { + warning(!(this.props.trigger === 'hover'), + '[react-bootstrap] Specifying only the `"hover"` trigger limits the visibilty of the overlay to just mouse users. ' + + 'Consider also including the `"focus"` trigger so that touch and keyboard only users can see the overlay as well.'); - if (isOneOf('focus', this.props.trigger)) { - props.onFocus = createChainedFunction(this.handleDelayedShow, this.props.onFocus); - props.onBlur = createChainedFunction(this.handleDelayedHide, this.props.onBlur); - } + props.onMouseOver = createChainedFunction(this.handleDelayedShow, this.props.onMouseOver); + props.onMouseOut = createChainedFunction(this.handleDelayedHide, this.props.onMouseOut); } - else { - deprecationWarning('"manual" trigger type', ' the Overlay component'); + + if (isOneOf('focus', this.props.trigger)) { + props.onFocus = createChainedFunction(this.handleDelayedShow, this.props.onFocus); + props.onBlur = createChainedFunction(this.handleDelayedHide, this.props.onBlur); } return cloneElement( @@ -250,32 +241,6 @@ const OverlayTrigger = React.createClass({ this._hoverDelay = null; this.hide(); }, delay); - }, - - // deprecated Methods - calcOverlayPosition() { - let overlay = this.props.overlay; - - deprecationWarning('OverlayTrigger.calcOverlayPosition()', 'utils/overlayPositionUtils'); - - return position.calcOverlayPosition( - overlay.props.placement || this.props.placement - , React.findDOMNode(overlay) - , React.findDOMNode(this) - , React.findDOMNode(overlay.props.container || this.props.container) - , overlay.props.containerPadding || this.props.containerPadding - ); - }, - - getPosition() { - deprecationWarning('OverlayTrigger.getPosition()', 'utils/overlayPositionUtils'); - - let overlay = this.props.overlay; - - return position.getPosition( - React.findDOMNode(this) - , React.findDOMNode(overlay.props.container || this.props.container) - ); } }); diff --git a/src/Portal.js b/src/Portal.js index cc8f350701..b4dde8315a 100644 --- a/src/Portal.js +++ b/src/Portal.js @@ -1,6 +1,6 @@ import React from 'react'; import CustomPropTypes from './utils/CustomPropTypes'; -import { OverlayMixin } from './OverlayMixin'; +import domUtils from './utils/domUtils'; let Portal = React.createClass({ @@ -13,20 +13,81 @@ let Portal = React.createClass({ container: CustomPropTypes.mountable }, - // we use the mixin for now, to avoid duplicating a bunch of code. - // when the deprecation is removed we need to move the logic here from OverlayMixin - mixins: [ OverlayMixin ], + componentDidMount() { + this._renderOverlay(); + }, + + componentDidUpdate() { + this._renderOverlay(); + }, + + componentWillUnmount() { + this._unrenderOverlay(); + this._mountOverlayTarget(); + }, + + _mountOverlayTarget() { + if (!this._overlayTarget) { + this._overlayTarget = document.createElement('div'); + this.getContainerDOMNode() + .appendChild(this._overlayTarget); + } + }, + + _unmountOverlayTarget() { + if (this._overlayTarget) { + this.getContainerDOMNode() + .removeChild(this._overlayTarget); + this._overlayTarget = null; + } + }, + + _renderOverlay() { - renderOverlay() { - if (!this.props.children) { - return null; + let overlay = !this.props.children + ? null + : React.Children.only(this.props.children); + + // Save reference for future access. + if (overlay !== null) { + this._mountOverlayTarget(); + this._overlayInstance = React.render(overlay, this._overlayTarget); + } else { + // Unrender if the component is null for transitions to null + this._unrenderOverlay(); + this._unmountOverlayTarget(); } + }, - return React.Children.only(this.props.children); + _unrenderOverlay() { + if (this._overlayTarget) { + React.unmountComponentAtNode(this._overlayTarget); + this._overlayInstance = null; + } }, render() { return null; + }, + + getOverlayDOMNode() { + if (!this.isMounted()) { + throw new Error('getOverlayDOMNode(): A component must be mounted to have a DOM node.'); + } + + if (this._overlayInstance) { + if (this._overlayInstance.getWrappedDOMNode) { + return this._overlayInstance.getWrappedDOMNode(); + } else { + return React.findDOMNode(this._overlayInstance); + } + } + + return null; + }, + + getContainerDOMNode() { + return React.findDOMNode(this.props.container) || domUtils.ownerDocument(this).body; } }); diff --git a/src/index.js b/src/index.js index 1f4dcefac1..70802d2b28 100644 --- a/src/index.js +++ b/src/index.js @@ -28,7 +28,6 @@ export ListGroupItem from './ListGroupItem'; export MenuItem from './MenuItem'; export Modal from './Modal'; -export ModalTrigger from './ModalTrigger'; export ModalHeader from './ModalHeader'; export ModalTitle from './ModalTitle'; export ModalBody from './ModalBody'; @@ -39,7 +38,6 @@ export Navbar from './Navbar'; export NavItem from './NavItem'; export Overlay from './Overlay'; -export OverlayMixin from './OverlayMixin'; export OverlayTrigger from './OverlayTrigger'; export PageHeader from './PageHeader'; diff --git a/test/FactoriesSpec.js b/test/FactoriesSpec.js index f03810c277..040d636430 100644 --- a/test/FactoriesSpec.js +++ b/test/FactoriesSpec.js @@ -4,7 +4,7 @@ import components from '../tools/public-components'; let props = { ButtonInput: {value: 'button'}, Glyphicon: {glyph: 'star'}, - Modal: {onRequestHide() {}}, + Modal: {onHide() {}}, ModalTrigger: {modal: React.DOM.div(null)}, OverlayTrigger: {overlay: React.DOM.div(null)} }; diff --git a/test/ModalSpec.js b/test/ModalSpec.js index 3d425c14cd..6065c0bbc3 100644 --- a/test/ModalSpec.js +++ b/test/ModalSpec.js @@ -1,18 +1,31 @@ import React from 'react'; import ReactTestUtils from 'react/lib/ReactTestUtils'; import Modal from '../src/Modal'; -import { shouldWarn } from './helpers'; +import { render } from './helpers'; describe('Modal', function () { + let mountPoint; + + beforeEach(()=>{ + mountPoint = document.createElement('div'); + document.body.appendChild(mountPoint); + }); + + afterEach(function () { + React.unmountComponentAtNode(mountPoint); + document.body.removeChild(mountPoint); + }); it('Should render the modal content', function() { let noOp = function () {}; - let instance = ReactTestUtils.renderIntoDocument( - + let instance = render( + Message - ); - assert.ok(ReactTestUtils.findRenderedDOMComponentWithTag(instance, 'strong')); + , mountPoint); + + assert.ok( + ReactTestUtils.findRenderedDOMComponentWithTag(instance.refs.modal, 'strong')); }); it('Should add modal-open class to the modal container while open', function(done) { @@ -27,21 +40,30 @@ describe('Modal', function () { render() { return (
- + Message
); } }); - let instance = ReactTestUtils.renderIntoDocument( - - ); + let instance = render( + + , mountPoint); + + let modal = ReactTestUtils.findRenderedComponentWithType(instance, Modal); + assert.ok(React.findDOMNode(instance).className.match(/\modal-open\b/)); - let backdrop = React.findDOMNode(instance).getElementsByClassName('modal-backdrop')[0]; + let backdrop = React.findDOMNode(modal.refs.modal).getElementsByClassName('modal-backdrop')[0]; ReactTestUtils.Simulate.click(backdrop); + setTimeout(function(){ assert.equal(React.findDOMNode(instance).className.length, 0); done(); @@ -51,55 +73,77 @@ describe('Modal', function () { it('Should close the modal when the backdrop is clicked', function (done) { let doneOp = function () { done(); }; - let instance = ReactTestUtils.renderIntoDocument( - + let instance = render( + Message - ); + , mountPoint); + + let backdrop = React.findDOMNode(instance.refs.modal) + .getElementsByClassName('modal-backdrop')[0]; - let backdrop = React.findDOMNode(instance).getElementsByClassName('modal-backdrop')[0]; ReactTestUtils.Simulate.click(backdrop); }); it('Should close the modal when the modal background is clicked', function (done) { let doneOp = function () { done(); }; - let instance = ReactTestUtils.renderIntoDocument( - + + let instance = render( + Message - ); + , mountPoint); + + let backdrop = React.findDOMNode(instance.refs.modal) + .getElementsByClassName('modal')[0]; - let backdrop = React.findDOMNode(instance).getElementsByClassName('modal')[0]; ReactTestUtils.Simulate.click(backdrop); }); + it('Should close the modal when the modal close button is clicked', function (done) { + let doneOp = function () { done(); }; + + let instance = render( + + + Message + + , mountPoint); + + let button = React.findDOMNode(instance.refs.modal) + .getElementsByClassName('close')[0]; + + ReactTestUtils.Simulate.click(button); + }); + it('Should pass bsSize to the dialog', function () { let noOp = function () {}; - let instance = ReactTestUtils.renderIntoDocument( - + let instance = render( + Message - ); + , mountPoint); - let dialog = React.findDOMNode(instance).getElementsByClassName('modal-dialog')[0]; + let dialog = React.findDOMNode(instance.refs.modal).getElementsByClassName('modal-dialog')[0]; assert.ok(dialog.className.match(/\bmodal-sm\b/)); }); it('Should pass dialogClassName to the dialog', function () { let noOp = function () {}; - let instance = ReactTestUtils.renderIntoDocument( - + let instance = render( + Message - ); + , mountPoint); - let dialog = ReactTestUtils.findRenderedDOMComponentWithClass(instance, 'modal-dialog'); + let dialog = ReactTestUtils.findRenderedDOMComponentWithClass(instance.refs.modal, 'modal-dialog'); assert.match(dialog.props.className, /\btestCss\b/); }); describe('Focused state', function () { let focusableContainer = null; - beforeEach(function () { + + beforeEach(()=>{ focusableContainer = document.createElement('div'); focusableContainer.tabIndex = 0; document.body.appendChild(focusableContainer); @@ -111,142 +155,50 @@ describe('Modal', function () { document.body.removeChild(focusableContainer); }); - it('Should focus on the Modal when it is opened', function (done) { + it('Should focus on the Modal when it is opened', function () { + document.activeElement.should.equal(focusableContainer); - let doneOp = function () { - // focus should be back on the previous element when modal closed - setTimeout(function () { - document.activeElement.should.equal(focusableContainer); - done(); - }, 0); - }; - - let Container = React.createClass({ - getInitialState() { - return {modalOpen: true}; - }, - handleCloseModal() { - this.setState({modalOpen: false}); - doneOp(); - }, - render() { - if (this.state.modalOpen) { - return ( - - Message - - ); - } else { - return ; - } - } - }); - - let instance = React.render(, focusableContainer); - - setTimeout(function () { - // modal should be focused when opened - let modal = React.findDOMNode(instance).getElementsByClassName('modal')[0]; - document.activeElement.should.equal(modal); - - // close the modal - let backdrop = React.findDOMNode(instance).getElementsByClassName('modal-backdrop')[0]; - ReactTestUtils.Simulate.click(backdrop); - }, 0); - }); + let instance = render( + {}} animation={false}> + Message + + , focusableContainer); - it('Should not focus on the Modal when autoFocus is false', function (done) { + document.activeElement.className.should.contain('modal'); - document.activeElement.should.equal(focusableContainer); + instance.renderWithProps({ show: false }); - let Container = React.createClass({ - getInitialState() { - return {modalOpen: true}; - }, - render() { - if (this.state.modalOpen) { - return ( - - {}} container={this}> - Message - - ); - } else { - return ; - } - } - }); - - React.render(, focusableContainer); - - setTimeout(function () { - // modal should be focused when opened - document.activeElement.should.equal(focusableContainer); - done(); - }, 0); + document.activeElement.should.equal(focusableContainer); }); - it('Should not focus Modal when child has focus', function (done) { + it('Should not focus on the Modal when autoFocus is false', function () { document.activeElement.should.equal(focusableContainer); - let Container = React.createClass({ - getInitialState() { - return {modalOpen: true}; - }, - render() { - if (this.state.modalOpen) { - return ( - {}} container={this}> - - - ); - } else { - return ; - } - } - }); - - let instance = React.render(, focusableContainer); - - setTimeout(function () { - let input = React.findDOMNode( - ReactTestUtils.findRenderedDOMComponentWithTag(instance, 'input')); - - document.activeElement.should.equal(input); - done(); - }, 0); - }); - }); - - - describe('deprecations', function(){ - it('Should render the modal header and title', function() { - let instance = ReactTestUtils.renderIntoDocument( - {}}> + render( + {}} animation={false}> Message - ); - - (()=> { - ReactTestUtils.findRenderedDOMComponentWithTag(instance, 'button'); - ReactTestUtils.findRenderedComponentWithType(instance, Modal.Header); - ReactTestUtils.findRenderedComponentWithType(instance, Modal.Title); - }).should.not.throw(); + , focusableContainer); - shouldWarn( - 'Specifying `closeButton` or `title` Modal props is deprecated'); + document.activeElement.should.equal(focusableContainer); }); - it('Should warn about onRequestHide', function() { - ReactTestUtils.renderIntoDocument( - {}}> - + it('Should not focus Modal when child has focus', function () { + + document.activeElement.should.equal(focusableContainer); + + render( + {}} animation={false}> + - ); + , focusableContainer); - shouldWarn('The Modal prop `onRequestHide` is deprecated'); + let input = document.getElementsByTagName('input')[0]; + + document.activeElement.should.equal(input); }); }); + }); diff --git a/test/ModalTriggerSpec.js b/test/ModalTriggerSpec.js deleted file mode 100644 index 9485f402b8..0000000000 --- a/test/ModalTriggerSpec.js +++ /dev/null @@ -1,123 +0,0 @@ -import React from 'react'; -import ReactTestUtils from 'react/lib/ReactTestUtils'; -import ModalTrigger from '../src/ModalTrigger'; -import { shouldWarn } from './helpers'; - - -describe('ModalTrigger', function() { - - afterEach(()=> { - if ( console.warn.called ) { - shouldWarn('The `ModalTrigger` component is deprecated'); - } - }); - - it('Should warn about deprecated Component', function() { - ReactTestUtils.renderIntoDocument( - test}> - - - ); - - shouldWarn('The `ModalTrigger` component is deprecated'); - }); - - it('Should create ModalTrigger element', function() { - const instance = ReactTestUtils.renderIntoDocument( - test}> - - - ); - const modalTrigger = React.findDOMNode(instance); - assert.equal(modalTrigger.nodeName, 'BUTTON'); - }); - - it('Should pass ModalTrigger onMouseOver prop to child', function() { - const callback = sinon.spy(); - const instance = ReactTestUtils.renderIntoDocument( - test} onMouseOver={callback}> - - - ); - const modalTrigger = React.findDOMNode(instance); - ReactTestUtils.Simulate.mouseOver(modalTrigger); - callback.called.should.be.true; - }); - - it('Should pass ModalTrigger onMouseOut prop to child', function() { - const callback = sinon.spy(); - const instance = ReactTestUtils.renderIntoDocument( - test} onMouseOut={callback}> - - - ); - const modalTrigger = React.findDOMNode(instance); - ReactTestUtils.Simulate.mouseOut(modalTrigger); - callback.called.should.be.true; - }); - - it('Should pass ModalTrigger onFocus prop to child', function() { - const callback = sinon.spy(); - const instance = ReactTestUtils.renderIntoDocument( - test} onFocus={callback}> - - - ); - const modalTrigger = React.findDOMNode(instance); - ReactTestUtils.Simulate.focus(modalTrigger); - callback.called.should.be.true; - }); - - it('Should pass ModalTrigger onBlur prop to child', function() { - const callback = sinon.spy(); - const instance = ReactTestUtils.renderIntoDocument( - test} onBlur={callback}> - - - ); - const modalTrigger = React.findDOMNode(instance); - ReactTestUtils.Simulate.blur(modalTrigger); - callback.called.should.be.true; - }); - - // This is just a copy of the test case for OverlayTrigger. - it('Should forward requested context', function() { - const contextTypes = { - key: React.PropTypes.string - }; - - const contextSpy = sinon.spy(); - class ContextReader extends React.Component { - render() { - contextSpy(this.context.key); - return
; - } - } - ContextReader.contextTypes = contextTypes; - - const TriggerWithContext = ModalTrigger.withContext(contextTypes); - class ContextHolder extends React.Component { - getChildContext() { - return {key: 'value'}; - } - - render() { - return ( - } - > - - - ); - } - } - ContextHolder.childContextTypes = contextTypes; - - const instance = ReactTestUtils.renderIntoDocument(); - const modalTrigger = React.findDOMNode(instance); - ReactTestUtils.Simulate.click(modalTrigger); - - contextSpy.calledWith('value').should.be.true; - }); -}); diff --git a/test/OverlayMixinSpec.js b/test/OverlayMixinSpec.js deleted file mode 100644 index 66818d48f5..0000000000 --- a/test/OverlayMixinSpec.js +++ /dev/null @@ -1,89 +0,0 @@ -import React from 'react'; -import ReactTestUtils from 'react/lib/ReactTestUtils'; -import OverlayMixin from '../src/OverlayMixin'; -import { shouldWarn } from './helpers'; - -describe('OverlayMixin', function () { - let instance; - - let Overlay = React.createClass({ - mixins: [OverlayMixin], - - render() { - return
; - }, - - renderOverlay() { - return this.props.overlay; - } - }); - - - afterEach(function() { - if (instance && ReactTestUtils.isCompositeComponent(instance) && instance.isMounted()) { - React.unmountComponentAtNode(React.findDOMNode(instance)); - } - - if ( console.warn.called ) { - shouldWarn('Overlay mixin is deprecated'); - } - }); - - it('Should render overlay into container (DOMNode)', function() { - let container = document.createElement('div'); - - instance = ReactTestUtils.renderIntoDocument( - } /> - ); - - assert.equal(container.querySelectorAll('#test1').length, 1); - }); - - it('Should render overlay into container (ReactComponent)', function() { - let Container = React.createClass({ - render() { - return } />; - } - }); - - instance = ReactTestUtils.renderIntoDocument( - - ); - - assert.equal(React.findDOMNode(instance).querySelectorAll('#test1').length, 1); - }); - - it('Should not render a null overlay', function() { - let Container = React.createClass({ - render() { - return ; - } - }); - - instance = ReactTestUtils.renderIntoDocument( - - ); - - assert.equal(instance.refs.overlay.getOverlayDOMNode(), null); - }); - - it('Should render only an overlay', function() { - let OnlyOverlay = React.createClass({ - mixins: [OverlayMixin], - - render() { - return null; - }, - - renderOverlay() { - return this.props.overlay; - } - }); - - let overlayInstance = ReactTestUtils.renderIntoDocument( - } /> - ); - - assert.equal(overlayInstance.getOverlayDOMNode().nodeName, 'DIV'); - }); -}); diff --git a/test/helpers.js b/test/helpers.js index 74960b4db4..97fb38d589 100644 --- a/test/helpers.js +++ b/test/helpers.js @@ -1,5 +1,30 @@ +import React from 'react'; +import { cloneElement } from 'react'; + export function shouldWarn(about) { console.warn.called.should.be.true; console.warn.calledWithMatch(about).should.be.true; console.warn.reset(); } + +/** + * Helper for rendering and updating props for plain class Components + * since `setProps` is deprecated. + * @param {ReactElement} element Root element to render + * @param {HTMLElement?} mountPoint Optional mount node, when empty it uses an unattached div like `renderIntoDocument()` + * @return {ComponentInstance} The instance, with a new method `renderWithProps` which will return a new instance wiht updated props + */ +export function render(element, mountPoint){ + let mount = mountPoint || document.createElement('div'); + let instance = React.render(element, mount); + + if (!instance.renderWithProps) { + instance.renderWithProps = function(newProps) { + + return render( + cloneElement(element, newProps), mount); + }; + } + + return instance; +} diff --git a/test/server/ModalSpec.js b/test/server/ModalSpec.js index e78832c956..fae565c4c8 100644 --- a/test/server/ModalSpec.js +++ b/test/server/ModalSpec.js @@ -8,7 +8,7 @@ describe('Modal', () => { assert.doesNotThrow(function renderOnServerSide() { return React.renderToString( - + Message );