diff --git a/docs/examples/Fade.js b/docs/examples/Fade.js new file mode 100644 index 0000000000..2583412829 --- /dev/null +++ b/docs/examples/Fade.js @@ -0,0 +1,29 @@ + +class Example extends React.Component { + + constructor(...args){ + super(...args); + this.state = {}; + } + + render(){ + + return ( +
+ + +
+ + Anim pariatur cliche reprehenderit, enim eiusmod high life accusamus terry richardson ad squid. + Nihil anim keffiyeh helvetica, craft beer labore wes anderson cred nesciunt sapiente ea proident. + +
+
+
+ ); + } +} + +React.render(, mountNode); diff --git a/src/Fade.js b/src/Fade.js new file mode 100644 index 0000000000..b91cb32b51 --- /dev/null +++ b/src/Fade.js @@ -0,0 +1,90 @@ +'use strict'; +import React from 'react'; +import Transition from './Transition'; + +class Fade extends React.Component { + + constructor(props, context){ + super(props, context); + } + + render() { + return ( + + { this.props.children } + + ); + } +} + +Fade.propTypes = { + /** + * Fade the Component in or out. + */ + in: React.PropTypes.bool, + + /** + * Provide the durration of the animation in milliseconds, used to ensure that finishing callbacks are fired even if the + * original browser transition end events are canceled. + */ + duration: React.PropTypes.number, + + /** + * A Callback fired before the component starts to fade in. + */ + onEnter: React.PropTypes.func, + + /** + * A Callback fired immediately after the component has started to faded in. + */ + onEntering: React.PropTypes.func, + + /** + * A Callback fired after the component has faded in. + */ + onEntered: React.PropTypes.func, + + /** + * A Callback fired before the component starts to fade out. + */ + onExit: React.PropTypes.func, + + /** + * A Callback fired immediately after the component has started to faded out. + */ + onExiting: React.PropTypes.func, + + /** + * A Callback fired after the component has faded out. + */ + onExited: React.PropTypes.func, + + + /** + * Specify whether the transitioning component should be unmounted (removed from the DOM) once the exit animation finishes. + */ + unmountOnExit: React.PropTypes.bool, + + /** + * Specify whether the component should fade in or out when it mounts. + */ + transitionAppear: React.PropTypes.bool + +}; + +Fade.defaultProps = { + in: false, + duration: 300, + dimension: 'height', + transitionAppear: false, + unmountOnExit: false +}; + +export default Fade; + diff --git a/src/Modal.js b/src/Modal.js index 3816318c51..55df65f433 100644 --- a/src/Modal.js +++ b/src/Modal.js @@ -4,11 +4,11 @@ import React, { cloneElement } from 'react'; import classNames from 'classnames'; import createChainedFunction from './utils/createChainedFunction'; import BootstrapMixin from './BootstrapMixin'; -import FadeMixin from './FadeMixin'; import domUtils from './utils/domUtils'; import EventListener from './utils/EventListener'; import Portal from './Portal'; +import Fade from './Fade'; import Body from './ModalBody'; import Header from './ModalHeader'; @@ -90,12 +90,17 @@ function getScrollbarSize(){ document.body.removeChild(scrollDiv); scrollDiv = null; + return scrollbarSize; } const ModalMarkup = React.createClass({ +<<<<<<< HEAD mixins: [ BootstrapMixin, FadeMixin ], +======= + mixins: [ BootstrapMixin ], +>>>>>>> [added] Fade Component, replaces FadeMixin propTypes: { @@ -166,8 +171,7 @@ const ModalMarkup = React.createClass({ let classes = { modal: true, - fade: this.props.animation, - 'in': !this.props.animation + in: this.props.show && !this.props.animation }; let modal = ( @@ -206,18 +210,22 @@ const ModalMarkup = React.createClass({ }, renderBackdrop(modal) { - let classes = { - 'modal-backdrop': true, - fade: this.props.animation, - 'in': !this.props.animation - }; - - let onClick = this.props.backdrop === true ? - this.handleBackdropClick : null; + let { animation } = this.props; + let duration = Modal.BACKDROP_TRANSITION_DURATION; //eslint-disable-line no-use-before-define + + let backdrop = ( +
+ ); return (
-
+ { animation + ? {backdrop} + : backdrop + } {modal}
); @@ -381,16 +389,40 @@ const Modal = React.createClass({ ...ModalMarkup.propTypes }, + getDefaultProps(){ + return { + show: false, + animation: true + }; + }, + render() { - let { show, ...props } = this.props; + let { children, ...props } = this.props; + + let show = !!props.show; let modal = ( - {this.props.children} + + { children } + ); return ( - - { show && modal } + + { props.animation + ? ( + + { modal } + + ) + : show && modal + } + ); } @@ -401,4 +433,7 @@ Modal.Header = Header; Modal.Title = Title; Modal.Footer = Footer; +Modal.TRANSITION_DURATION = 300; +Modal.BACKDROP_TRANSITION_DURATION = 150; + export default Modal; diff --git a/src/Overlay.js b/src/Overlay.js index d425fe91a4..a5b27d923f 100644 --- a/src/Overlay.js +++ b/src/Overlay.js @@ -1,13 +1,26 @@ /*eslint-disable object-shorthand, react/prop-types */ -import React from 'react'; +import React, { cloneElement } from 'react'; import Portal from './Portal'; import Position from './Position'; import RootCloseWrapper from './RootCloseWrapper'; +import CustomPropTypes from './utils/CustomPropTypes'; +import Fade from './Fade'; +import classNames from 'classnames'; + class Overlay extends React.Component { constructor(props, context){ super(props, context); + + this.state = { exited: false }; + this.onHiddenListener = this.handleHidden.bind(this); + } + + componentWillReceiveProps(nextProps) { + if (this.props.show){ + this.setState({ exited: false }); + } } render(){ @@ -17,30 +30,60 @@ class Overlay extends React.Component { , target , placement , rootClose + , children + , animation: Transition , ...props } = this.props; - let positionedChild = ( - - { this.props.children } - - ); + let child = null; - if (rootClose) { - positionedChild = ( - - { positionedChild } - + if ( Transition === true ){ + Transition = Fade; + } + + if (props.show || (Transition && !this.state.exited)) { + + child = children; + + // Position the child before the animation to avoid `null` DOM nodes + child = ( + + { child } + ); + + child = Transition + ? ( + + { child } + + ) + : cloneElement(child, { className: classNames('in', child.className) }); + + + if (rootClose) { + child = ( + + { child } + + ); + } } return ( - - { props.show && - positionedChild - } + + { child } ); } + + handleHidden(){ + this.setState({ exited: true }); + } } Overlay.propTypes = { @@ -57,7 +100,19 @@ Overlay.propTypes = { /** * A Callback fired by the Overlay when it wishes to be hidden. */ - onHide: React.PropTypes.func + onHide: React.PropTypes.func, + + /** + * Use animation + */ + animation: React.PropTypes.oneOfType([ + React.PropTypes.bool, + CustomPropTypes.elementType + ]) +}; + +Overlay.defaultProps = { + animation: Fade }; export default Overlay; diff --git a/src/Popover.js b/src/Popover.js index e24b0c51a6..1133703238 100644 --- a/src/Popover.js +++ b/src/Popover.js @@ -2,12 +2,11 @@ import React from 'react'; import classNames from 'classnames'; import BootstrapMixin from './BootstrapMixin'; -import FadeMixin from './FadeMixin'; import CustomPropTypes from './utils/CustomPropTypes'; const Popover = React.createClass({ - mixins: [BootstrapMixin, FadeMixin], + mixins: [ BootstrapMixin ], propTypes: { /** @@ -42,15 +41,11 @@ const Popover = React.createClass({ arrowOffsetTop: React.PropTypes.oneOfType([ React.PropTypes.number, React.PropTypes.string ]), + /** * Title text */ - title: React.PropTypes.node, - /** - * Specify whether the Popover should be use show and hide animations. - */ - animation: React.PropTypes.bool - + title: React.PropTypes.node }, @@ -64,10 +59,7 @@ const Popover = React.createClass({ render() { const classes = { 'popover': true, - [this.props.placement]: true, - // in class will be added by the FadeMixin when the animation property is true - 'in': !this.props.animation && (this.props.positionLeft != null || this.props.positionTop != null), - 'fade': this.props.animation + [this.props.placement]: true }; const style = { diff --git a/src/Position.js b/src/Position.js index b92c256965..36f9d817ba 100644 --- a/src/Position.js +++ b/src/Position.js @@ -31,13 +31,13 @@ class Position extends React.Component { } render() { - let { placement, children } = this.props; + let { children, ...props } = this.props; let { positionLeft, positionTop, ...arrows } = this.props.target ? this.state : {}; return cloneElement( React.Children.only(children), { + ...props, ...arrows, - placement, positionTop, positionLeft, style: { @@ -61,13 +61,18 @@ class Position extends React.Component { return; } + let overlay = React.findDOMNode(this); let target = React.findDOMNode(this.props.target(this.props)); let container = React.findDOMNode(this.props.container) || domUtils.ownerDocument(this).body; + // if ( !overlay || !target || !container ){ + // return; + // } + this.setState( calcOverlayPosition( this.props.placement - , React.findDOMNode(this) + , overlay , target , container , this.props.containerPadding)); diff --git a/src/Tooltip.js b/src/Tooltip.js index 640267829e..e21c57c845 100644 --- a/src/Tooltip.js +++ b/src/Tooltip.js @@ -2,11 +2,10 @@ import React from 'react'; import classNames from 'classnames'; import BootstrapMixin from './BootstrapMixin'; -import FadeMixin from './FadeMixin'; import CustomPropTypes from './utils/CustomPropTypes'; const Tooltip = React.createClass({ - mixins: [BootstrapMixin, FadeMixin], + mixins: [BootstrapMixin], propTypes: { /** @@ -44,11 +43,7 @@ const Tooltip = React.createClass({ /** * Title text */ - title: React.PropTypes.node, - /** - * Specify whether the Tooltip should be use show and hide animations. - */ - animation: React.PropTypes.bool + title: React.PropTypes.node }, getDefaultProps() { @@ -61,10 +56,7 @@ const Tooltip = React.createClass({ render() { const classes = { 'tooltip': true, - [this.props.placement]: true, - // in class will be added by the FadeMixin when the animation property is true - 'in': !this.props.animation && (this.props.positionLeft != null || this.props.positionTop != null), - 'fade': this.props.animation + [this.props.placement]: true }; const style = { diff --git a/test/FadeSpec.js b/test/FadeSpec.js new file mode 100644 index 0000000000..1aa710fcd6 --- /dev/null +++ b/test/FadeSpec.js @@ -0,0 +1,82 @@ +import React from 'react'; +import ReactTestUtils from 'react/lib/ReactTestUtils'; +import Fade from '../src/Fade'; +//import classNames from 'classnames'; + +describe('Fade', function () { + + let Component, instance; + + beforeEach(function(){ + + Component = React.createClass({ + render(){ + let { children, ...props } = this.props; + + return ( + this.fade = r} + {...props} + > +
+ {children} +
+
+ ); + } + }); + }); + + it('Should default to hidden', function () { + instance = ReactTestUtils.renderIntoDocument( + Panel content + ); + + assert.ok( + instance.fade.props.in === false); + }); + + it('Should always have the "fade" class', () => { + instance = ReactTestUtils.renderIntoDocument( + Panel content + ); + + assert.ok( + instance.fade.props.in === false); + + assert.equal( + React.findDOMNode(instance).className, 'fade'); + + }); + + it('Should add "in" class when entering', done => { + instance = ReactTestUtils.renderIntoDocument( + Panel content + ); + + function onEntering(){ + assert.equal(React.findDOMNode(instance).className, 'fade in'); + done(); + } + + assert.ok( + instance.fade.props.in === false); + + instance.setProps({ in: true, onEntering }); + }); + + it('Should remove "in" class when exiting', done => { + instance = ReactTestUtils.renderIntoDocument( + Panel content + ); + + function onExiting(){ + assert.equal(React.findDOMNode(instance).className, 'fade'); + done(); + } + + assert.equal( + React.findDOMNode(instance).className, 'fade in'); + + instance.setProps({ in: false, onExiting }); + }); +}); diff --git a/test/ModalSpec.js b/test/ModalSpec.js index 6065c0bbc3..d45636cd1d 100644 --- a/test/ModalSpec.js +++ b/test/ModalSpec.js @@ -3,6 +3,7 @@ import ReactTestUtils from 'react/lib/ReactTestUtils'; import Modal from '../src/Modal'; import { render } from './helpers'; + describe('Modal', function () { let mountPoint; @@ -172,10 +173,8 @@ describe('Modal', function () { document.activeElement.should.equal(focusableContainer); }); - it('Should not focus on the Modal when autoFocus is false', function () { - - document.activeElement.should.equal(focusableContainer); + it('Should not focus on the Modal when autoFocus is false', function () { render( {}} animation={false}> Message diff --git a/test/PopoverSpec.js b/test/PopoverSpec.js index 45b6f00757..30509376e4 100644 --- a/test/PopoverSpec.js +++ b/test/PopoverSpec.js @@ -11,16 +11,16 @@ describe('Popover', function () { ); assert.ok(ReactTestUtils.findRenderedDOMComponentWithClass(instance, 'popover-title')); assert.ok(ReactTestUtils.findRenderedDOMComponentWithClass(instance, 'popover-content')); - assert.ok(ReactTestUtils.findRenderedDOMComponentWithClass(instance, 'fade')); + assert.ok(ReactTestUtils.findRenderedDOMComponentWithTag(instance, 'strong')); }); - it('Should not have the fade class if animation is false', function () { - let instance = ReactTestUtils.renderIntoDocument( - - Popover Content - - ); - assert.equal(React.findDOMNode(instance).className.match(/\bfade\b/), null, 'The fade class should not be present'); - }); + // it('Should not have the fade class if animation is false', function () { + // let instance = ReactTestUtils.renderIntoDocument( + // + // Popover Content + // + // ); + // assert.equal(React.findDOMNode(instance).className.match(/\bfade\b/), null, 'The fade class should not be present'); + // }); }); diff --git a/test/TooltipSpec.js b/test/TooltipSpec.js index a07bb06073..a21c6317bf 100644 --- a/test/TooltipSpec.js +++ b/test/TooltipSpec.js @@ -10,15 +10,15 @@ describe('Tooltip', function () { ); assert.ok(ReactTestUtils.findRenderedDOMComponentWithTag(instance, 'strong')); - assert.ok(ReactTestUtils.findRenderedDOMComponentWithClass(instance, 'fade')); - }); - it('Should not have the fade class if animation is false', function () { - let instance = ReactTestUtils.renderIntoDocument( - - Tooltip Content - - ); - assert.equal(React.findDOMNode(instance).className.match(/\bfade\b/), null, 'The fade class should not be present'); }); + + // it('Should not have the fade class if animation is false', function () { + // let instance = ReactTestUtils.renderIntoDocument( + // + // Tooltip Content + // + // ); + // assert.equal(React.findDOMNode(instance).className.match(/\bfade\b/), null, 'The fade class should not be present'); + // }); });