diff --git a/docs/examples/.eslintrc b/docs/examples/.eslintrc index 2db814d319..ed5fd65183 100644 --- a/docs/examples/.eslintrc +++ b/docs/examples/.eslintrc @@ -13,8 +13,8 @@ "Button", "ButtonGroup", "ButtonToolbar", - "CollapsableNav", - "CollapsableMixin", + "CollapsibleNav", + "CollapsibleMixin", "Carousel", "CarouselItem", "Col", diff --git a/docs/examples/CollapsableNav.js b/docs/examples/CollapsibleNav.js similarity index 90% rename from docs/examples/CollapsableNav.js rename to docs/examples/CollapsibleNav.js index 87824bb989..2d04d65fc8 100644 --- a/docs/examples/CollapsableNav.js +++ b/docs/examples/CollapsibleNav.js @@ -1,6 +1,6 @@ const navbarInstance = ( - {/* This is the eventKey referenced */} + {/* This is the eventKey referenced */} - + ); diff --git a/docs/examples/CollapsableParagraph.js b/docs/examples/CollapsibleParagraph.js similarity index 82% rename from docs/examples/CollapsableParagraph.js rename to docs/examples/CollapsibleParagraph.js index 9e45532e3b..d917d63f24 100644 --- a/docs/examples/CollapsableParagraph.js +++ b/docs/examples/CollapsibleParagraph.js @@ -1,11 +1,11 @@ -const CollapsableParagraph = React.createClass({ - mixins: [CollapsableMixin], +const CollapsibleParagraph = React.createClass({ + mixins: [CollapsibleMixin], - getCollapsableDOMNode: function(){ + getCollapsibleDOMNode: function(){ return this.refs.panel.getDOMNode(); }, - getCollapsableDimensionValue: function(){ + getCollapsibleDimensionValue: function(){ return this.refs.panel.getDOMNode().scrollHeight; }, @@ -15,7 +15,7 @@ const CollapsableParagraph = React.createClass({ }, render: function(){ - let styles = this.getCollapsableClassSet(); + let styles = this.getCollapsibleClassSet(); let text = this.isExpanded() ? 'Hide' : 'Show'; return (
@@ -29,9 +29,9 @@ const CollapsableParagraph = React.createClass({ }); const panelInstance = ( - + Anim pariatur cliche reprehenderit, enim eiusmod high life accusamus terry richardson ad squid. 3 wolf moon officia aute, non cupidatat skateboard dolor brunch. Food truck quinoa nesciunt laborum eiusmod. Brunch 3 wolf moon tempor, sunt aliqua put a bird on it squid single-origin coffee nulla assumenda shoreditch et. Nihil anim keffiyeh helvetica, craft beer labore wes anderson cred nesciunt sapiente ea proident. Ad vegan excepteur butcher vice lomo. Leggings occaecat craft beer farm-to-table, raw denim aesthetic synth nesciunt you probably haven't heard of them accusamus labore sustainable VHS. - + ); React.render(panelInstance, mountNode); diff --git a/docs/examples/NavbarCollapsable.js b/docs/examples/NavbarCollapsible.js similarity index 100% rename from docs/examples/NavbarCollapsable.js rename to docs/examples/NavbarCollapsible.js diff --git a/docs/src/ComponentsPage.js b/docs/src/ComponentsPage.js index 50be067373..ba2dbde570 100644 --- a/docs/src/ComponentsPage.js +++ b/docs/src/ComponentsPage.js @@ -208,9 +208,9 @@ const ComponentsPage = React.createClass({

<Accordion /> aliases <PanelGroup accordion />.

-

Collapsable Mixin

-

CollapsableMixin can be used to create your own components with collapse functionality.

- +

Collapsible Mixin

+

CollapsibleMixin can be used to create your own components with collapse functionality.

+
@@ -351,7 +351,7 @@ const ComponentsPage = React.createClass({

By setting the property {React.DOM.code(null, 'defaultNavExpanded={true}')} the Navbar will start expanded by default.

Scrollbar overflow

-

The height of the collapsable is slightly smaller than the real height. To hide the scroll bar, add the following css to your style files.

+

The height of the collapsible is slightly smaller than the real height. To hide the scroll bar, add the following css to your style files.

                       {React.DOM.code(null,
                         '.navbar-collapse {\n' +
@@ -360,17 +360,17 @@ const ComponentsPage = React.createClass({
                       )}
                     
- +

Mobile Friendly (Multiple Nav Components)

-

To have a mobile friendly Navbar that handles multiple Nav components use CollapsableNav. The toggleNavKey must still be set, however, the corresponding eventKey must now be on the CollapsableNav component.

+

To have a mobile friendly Navbar that handles multiple Nav components use CollapsibleNav. The toggleNavKey must still be set, however, the corresponding eventKey must now be on the CollapsibleNav component.

Div collapse

-

The navbar-collapse div gets created as the collapsable element which follows the bootstrap collapsable navbar documentation.

+

The navbar-collapse div gets created as the collapsible element which follows the bootstrap collapsible navbar documentation.

<div class="collapse navbar-collapse"></div>
- +
{/* Tabbed Areas */} diff --git a/docs/src/ReactPlayground.js b/docs/src/ReactPlayground.js index 4a0c64cbaa..2aa45b8406 100644 --- a/docs/src/ReactPlayground.js +++ b/docs/src/ReactPlayground.js @@ -6,8 +6,8 @@ import * as modBadge from '../../src/Badge'; import * as modmodButton from '../../src/Button'; import * as modButtonGroup from '../../src/ButtonGroup'; import * as modmodButtonToolbar from '../../src/ButtonToolbar'; -import * as modCollapsableNav from '../../src/CollapsableNav'; -import * as modCollapsableMixin from '../../src/CollapsableMixin'; +import * as modCollapsibleNav from '../../src/CollapsibleNav'; +import * as modCollapsibleMixin from '../../src/CollapsibleMixin'; import * as modCarousel from '../../src/Carousel'; import * as modCarouselItem from '../../src/CarouselItem'; import * as modCol from '../../src/Col'; @@ -53,8 +53,8 @@ const Badge = modBadge.default; const Button = modmodButton.default; const ButtonGroup = modButtonGroup.default; const ButtonToolbar = modmodButtonToolbar.default; -const CollapsableNav = modCollapsableNav.default; -const CollapsableMixin = modCollapsableMixin.default; +const CollapsibleNav = modCollapsibleNav.default; +const CollapsibleMixin = modCollapsibleMixin.default; const Carousel = modCarousel.default; const CarouselItem = modCarouselItem.default; const Col = modCol.default; diff --git a/docs/src/Samples.js b/docs/src/Samples.js index 459943615e..004fe581d7 100644 --- a/docs/src/Samples.js +++ b/docs/src/Samples.js @@ -28,7 +28,7 @@ export default { PanelGroupControlled: require('fs').readFileSync(__dirname + '/../examples/PanelGroupControlled.js', 'utf8'), PanelGroupUncontrolled: require('fs').readFileSync(__dirname + '/../examples/PanelGroupUncontrolled.js', 'utf8'), PanelGroupAccordion: require('fs').readFileSync(__dirname + '/../examples/PanelGroupAccordion.js', 'utf8'), - CollapsableParagraph: require('fs').readFileSync(__dirname + '/../examples/CollapsableParagraph.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'), ModalOverlayMixin: require('fs').readFileSync(__dirname + '/../examples/ModalOverlayMixin.js', 'utf8'), @@ -52,8 +52,8 @@ export default { NavJustified: require('fs').readFileSync(__dirname + '/../examples/NavJustified.js', 'utf8'), NavbarBasic: require('fs').readFileSync(__dirname + '/../examples/NavbarBasic.js', 'utf8'), NavbarBrand: require('fs').readFileSync(__dirname + '/../examples/NavbarBrand.js', 'utf8'), - NavbarCollapsable: require('fs').readFileSync(__dirname + '/../examples/NavbarCollapsable.js', 'utf8'), - CollapsableNav: require('fs').readFileSync(__dirname + '/../examples/CollapsableNav.js', 'utf8'), + NavbarCollapsible: require('fs').readFileSync(__dirname + '/../examples/NavbarCollapsible.js', 'utf8'), + CollapsibleNav: require('fs').readFileSync(__dirname + '/../examples/CollapsibleNav.js', 'utf8'), TabbedAreaUncontrolled: require('fs').readFileSync(__dirname + '/../examples/TabbedAreaUncontrolled.js', 'utf8'), TabbedAreaControlled: require('fs').readFileSync(__dirname + '/../examples/TabbedAreaControlled.js', 'utf8'), TabbedAreaNoAnimation: require('fs').readFileSync(__dirname + '/../examples/TabbedAreaNoAnimation.js', 'utf8'), diff --git a/src/CollapsableMixin.js b/src/CollapsableMixin.js index 182843c34a..1aae543000 100644 --- a/src/CollapsableMixin.js +++ b/src/CollapsableMixin.js @@ -1,168 +1,38 @@ -import React from 'react'; -import TransitionEvents from 'react/lib/ReactTransitionEvents'; +import assign from './utils/Object.assign'; +import deprecationWarning from './utils/deprecationWarning'; +import CollapsibleMixin from './CollapsibleMixin'; -const CollapsableMixin = { - - propTypes: { - defaultExpanded: React.PropTypes.bool, - expanded: React.PropTypes.bool - }, - - getInitialState(){ - let defaultExpanded = this.props.defaultExpanded != null ? - this.props.defaultExpanded : - this.props.expanded != null ? - this.props.expanded : - false; - - return { - expanded: defaultExpanded, - collapsing: false - }; - }, - - componentWillUpdate(nextProps, nextState){ - let willExpanded = nextProps.expanded != null ? nextProps.expanded : nextState.expanded; - if (willExpanded === this.isExpanded()) { - return; - } - - // if the expanded state is being toggled, ensure node has a dimension value - // this is needed for the animation to work and needs to be set before - // the collapsing class is applied (after collapsing is applied the in class - // is removed and the node's dimension will be wrong) - - let node = this.getCollapsableDOMNode(); - let dimension = this.dimension(); - let value = '0'; - - if(!willExpanded){ - value = this.getCollapsableDimensionValue(); - } - - node.style[dimension] = value + 'px'; - - this._afterWillUpdate(); - }, - - componentDidUpdate(prevProps, prevState){ - // check if expanded is being toggled; if so, set collapsing - this._checkToggleCollapsing(prevProps, prevState); - - // check if collapsing was turned on; if so, start animation - this._checkStartAnimation(); - }, - - // helps enable test stubs - _afterWillUpdate(){ - }, - - _checkStartAnimation(){ - if(!this.state.collapsing) { - return; - } - - let node = this.getCollapsableDOMNode(); - let dimension = this.dimension(); - let value = this.getCollapsableDimensionValue(); - - // setting the dimension here starts the transition animation - let result; - if(this.isExpanded()) { - result = value + 'px'; - } else { - result = '0px'; - } - node.style[dimension] = result; - }, - - _checkToggleCollapsing(prevProps, prevState){ - let wasExpanded = prevProps.expanded != null ? prevProps.expanded : prevState.expanded; - let isExpanded = this.isExpanded(); - if(wasExpanded !== isExpanded){ - if(wasExpanded) { - this._handleCollapse(); - } else { - this._handleExpand(); - } - } - }, - - _handleExpand(){ - let node = this.getCollapsableDOMNode(); - let dimension = this.dimension(); - - let complete = () => { - this._removeEndEventListener(node, complete); - // remove dimension value - this ensures the collapsable item can grow - // in dimension after initial display (such as an image loading) - node.style[dimension] = ''; - this.setState({ - collapsing:false - }); - }; - - this._addEndEventListener(node, complete); - - this.setState({ - collapsing: true - }); - }, - - _handleCollapse(){ - let node = this.getCollapsableDOMNode(); - - let complete = () => { - this._removeEndEventListener(node, complete); - this.setState({ - collapsing: false - }); - }; - - this._addEndEventListener(node, complete); - - this.setState({ - collapsing: true - }); - }, - - // helps enable test stubs - _addEndEventListener(node, complete){ - TransitionEvents.addEndEventListener(node, complete); - }, - - // helps enable test stubs - _removeEndEventListener(node, complete){ - TransitionEvents.removeEndEventListener(node, complete); - }, - - dimension(){ - return (typeof this.getCollapsableDimension === 'function') ? - this.getCollapsableDimension() : - 'height'; - }, - - isExpanded(){ - return this.props.expanded != null ? this.props.expanded : this.state.expanded; - }, +let link = 'https://github.com/react-bootstrap/react-bootstrap/issues/425#issuecomment-97110963'; +const CollapsableMixin = assign({}, CollapsibleMixin, { getCollapsableClassSet(className) { - let classes = {}; - - if (typeof className === 'string') { - className.split(' ').forEach(subClasses => { - if (subClasses) { - classes[subClasses] = true; - } - }); - } - - classes.collapsing = this.state.collapsing; - classes.collapse = !this.state.collapsing; - classes.in = this.isExpanded() && !this.state.collapsing; - - return classes; + deprecationWarning( + 'CollapsableMixin.getCollapsableClassSet()', + 'CollapsibleMixin.getCollapsibleClassSet()', + link + ); + return CollapsibleMixin.getCollapsibleClassSet.call(this, className); + }, + + getCollapsibleDOMNode() { + deprecationWarning( + 'CollapsableMixin.getCollapsableDOMNode()', + 'CollapsibleMixin.getCollapsibleDOMNode()', + link + ); + return this.getCollapsableDOMNode(); + }, + + getCollapsibleDimensionValue() { + deprecationWarning( + 'CollapsableMixin.getCollapsableDimensionValue()', + 'CollapsibleMixin.getCollapsibleDimensionValue()', + link + ); + return this.getCollapsableDimensionValue(); } -}; +}); + +deprecationWarning('CollapsableMixin', 'CollapsibleMixin', link); export default CollapsableMixin; diff --git a/src/CollapsableNav.js b/src/CollapsableNav.js index e0ca719dc1..cc211684f3 100644 --- a/src/CollapsableNav.js +++ b/src/CollapsableNav.js @@ -1,111 +1,12 @@ -import React, { cloneElement } from 'react'; -import BootstrapMixin from './BootstrapMixin'; -import CollapsableMixin from './CollapsableMixin'; -import classNames from 'classnames'; -import domUtils from './utils/domUtils'; +import deprecationWarning from './utils/deprecationWarning'; +import CollapsibleNav from './CollapsibleNav'; -import ValidComponentChildren from './utils/ValidComponentChildren'; -import createChainedFunction from './utils/createChainedFunction'; +let CollapsableNav = CollapsibleNav; -const CollapsableNav = React.createClass({ - mixins: [BootstrapMixin, CollapsableMixin], - - propTypes: { - onSelect: React.PropTypes.func, - activeHref: React.PropTypes.string, - activeKey: React.PropTypes.any, - collapsable: React.PropTypes.bool, - expanded: React.PropTypes.bool, - eventKey: React.PropTypes.any - }, - - getCollapsableDOMNode() { - return this.getDOMNode(); - }, - - getCollapsableDimensionValue() { - let height = 0; - let nodes = this.refs; - for (let key in nodes) { - if (nodes.hasOwnProperty(key)) { - - let n = nodes[key].getDOMNode() - , h = n.offsetHeight - , computedStyles = domUtils.getComputedStyles(n); - - height += (h + parseInt(computedStyles.marginTop, 10) + parseInt(computedStyles.marginBottom, 10)); - } - } - return height; - }, - - render() { - /* - * this.props.collapsable is set in NavBar when a eventKey is supplied. - */ - let classes = this.props.collapsable ? this.getCollapsableClassSet() : {}; - /* - * prevent duplicating navbar-collapse call if passed as prop. kind of overkill... good cadidate to have check implemented as a util that can - * also be used elsewhere. - */ - if (this.props.className === undefined || this.props.className.split(' ').indexOf('navbar-collapse') === -2) { - classes['navbar-collapse'] = this.props.collapsable; - } - - return ( -
- {ValidComponentChildren.map(this.props.children, this.props.collapsable ? this.renderCollapsableNavChildren : this.renderChildren )} -
- ); - }, - - getChildActiveProp(child) { - if (child.props.active) { - return true; - } - if (this.props.activeKey != null) { - if (child.props.eventKey === this.props.activeKey) { - return true; - } - } - if (this.props.activeHref != null) { - if (child.props.href === this.props.activeHref) { - return true; - } - } - - return child.props.active; - }, - - renderChildren(child, index) { - let key = child.key ? child.key : index; - return cloneElement( - child, - { - activeKey: this.props.activeKey, - activeHref: this.props.activeHref, - ref: 'nocollapse_' + key, - key: key, - navItem: true - } - ); - }, - - renderCollapsableNavChildren(child, index) { - let key = child.key ? child.key : index; - return cloneElement( - child, - { - active: this.getChildActiveProp(child), - activeKey: this.props.activeKey, - activeHref: this.props.activeHref, - onSelect: createChainedFunction(child.props.onSelect, this.props.onSelect), - ref: 'collapsable_' + key, - key: key, - navItem: true - } - ); - } -}); +deprecationWarning( + 'CollapsableNav', + 'CollapsibleNav', + 'https://github.com/react-bootstrap/react-bootstrap/issues/425#issuecomment-97110963' +); export default CollapsableNav; diff --git a/src/CollapsibleMixin.js b/src/CollapsibleMixin.js new file mode 100644 index 0000000000..505b62909d --- /dev/null +++ b/src/CollapsibleMixin.js @@ -0,0 +1,178 @@ +import React from 'react'; +import TransitionEvents from 'react/lib/ReactTransitionEvents'; +import deprecationWarning from './utils/deprecationWarning'; + +const CollapsibleMixin = { + + propTypes: { + defaultExpanded: React.PropTypes.bool, + expanded: React.PropTypes.bool + }, + + getInitialState(){ + let defaultExpanded = this.props.defaultExpanded != null ? + this.props.defaultExpanded : + this.props.expanded != null ? + this.props.expanded : + false; + + return { + expanded: defaultExpanded, + collapsing: false + }; + }, + + componentWillUpdate(nextProps, nextState){ + let willExpanded = nextProps.expanded != null ? nextProps.expanded : nextState.expanded; + if (willExpanded === this.isExpanded()) { + return; + } + + // if the expanded state is being toggled, ensure node has a dimension value + // this is needed for the animation to work and needs to be set before + // the collapsing class is applied (after collapsing is applied the in class + // is removed and the node's dimension will be wrong) + + let node = this.getCollapsibleDOMNode(); + let dimension = this.dimension(); + let value = '0'; + + if(!willExpanded){ + value = this.getCollapsibleDimensionValue(); + } + + node.style[dimension] = value + 'px'; + + this._afterWillUpdate(); + }, + + componentDidUpdate(prevProps, prevState){ + // check if expanded is being toggled; if so, set collapsing + this._checkToggleCollapsing(prevProps, prevState); + + // check if collapsing was turned on; if so, start animation + this._checkStartAnimation(); + }, + + // helps enable test stubs + _afterWillUpdate(){ + }, + + _checkStartAnimation(){ + if(!this.state.collapsing) { + return; + } + + let node = this.getCollapsibleDOMNode(); + let dimension = this.dimension(); + let value = this.getCollapsibleDimensionValue(); + + // setting the dimension here starts the transition animation + let result; + if(this.isExpanded()) { + result = value + 'px'; + } else { + result = '0px'; + } + node.style[dimension] = result; + }, + + _checkToggleCollapsing(prevProps, prevState){ + let wasExpanded = prevProps.expanded != null ? prevProps.expanded : prevState.expanded; + let isExpanded = this.isExpanded(); + if(wasExpanded !== isExpanded){ + if(wasExpanded) { + this._handleCollapse(); + } else { + this._handleExpand(); + } + } + }, + + _handleExpand(){ + let node = this.getCollapsibleDOMNode(); + let dimension = this.dimension(); + + let complete = () => { + this._removeEndEventListener(node, complete); + // remove dimension value - this ensures the collapsible item can grow + // in dimension after initial display (such as an image loading) + node.style[dimension] = ''; + this.setState({ + collapsing:false + }); + }; + + this._addEndEventListener(node, complete); + + this.setState({ + collapsing: true + }); + }, + + _handleCollapse(){ + let node = this.getCollapsibleDOMNode(); + + let complete = () => { + this._removeEndEventListener(node, complete); + this.setState({ + collapsing: false + }); + }; + + this._addEndEventListener(node, complete); + + this.setState({ + collapsing: true + }); + }, + + // helps enable test stubs + _addEndEventListener(node, complete){ + TransitionEvents.addEndEventListener(node, complete); + }, + + // helps enable test stubs + _removeEndEventListener(node, complete){ + TransitionEvents.removeEndEventListener(node, complete); + }, + + dimension(){ + if (typeof this.getCollapsableDimension === 'function') { + deprecationWarning( + 'CollapsableMixin.getCollapsableDimension()', + 'CollapsibleMixin.getCollapsibleDimension()', + 'https://github.com/react-bootstrap/react-bootstrap/issues/425#issuecomment-97110963' + ); + return this.getCollapsableDimension(); + } + + return (typeof this.getCollapsibleDimension === 'function') ? + this.getCollapsibleDimension() : + 'height'; + }, + + isExpanded(){ + return this.props.expanded != null ? this.props.expanded : this.state.expanded; + }, + + getCollapsibleClassSet(className) { + let classes = {}; + + if (typeof className === 'string') { + className.split(' ').forEach(subClasses => { + if (subClasses) { + classes[subClasses] = true; + } + }); + } + + classes.collapsing = this.state.collapsing; + classes.collapse = !this.state.collapsing; + classes.in = this.isExpanded() && !this.state.collapsing; + + return classes; + } +}; + +export default CollapsibleMixin; diff --git a/src/CollapsibleNav.js b/src/CollapsibleNav.js new file mode 100644 index 0000000000..e8af629387 --- /dev/null +++ b/src/CollapsibleNav.js @@ -0,0 +1,121 @@ +import React, { cloneElement } from 'react'; +import BootstrapMixin from './BootstrapMixin'; +import CollapsibleMixin from './CollapsibleMixin'; +import classNames from 'classnames'; +import domUtils from './utils/domUtils'; + +import ValidComponentChildren from './utils/ValidComponentChildren'; +import createChainedFunction from './utils/createChainedFunction'; + +const CollapsibleNav = React.createClass({ + mixins: [BootstrapMixin, CollapsibleMixin], + + propTypes: { + onSelect: React.PropTypes.func, + activeHref: React.PropTypes.string, + activeKey: React.PropTypes.any, + collapsable: React.PropTypes.bool, + expanded: React.PropTypes.bool, + eventKey: React.PropTypes.any + }, + + getCollapsibleDOMNode() { + return this.getDOMNode(); + }, + + getCollapsibleDimensionValue() { + let height = 0; + let nodes = this.refs; + for (let key in nodes) { + if (nodes.hasOwnProperty(key)) { + + let n = nodes[key].getDOMNode() + , h = n.offsetHeight + , computedStyles = domUtils.getComputedStyles(n); + + height += (h + + parseInt(computedStyles.marginTop, 10) + + parseInt(computedStyles.marginBottom, 10) + ); + } + } + return height; + }, + + render() { + /* + * this.props.collapsable is set in NavBar when a eventKey is supplied. + */ + let classes = this.props.collapsable ? this.getCollapsibleClassSet() : {}; + /* + * prevent duplicating navbar-collapse call if passed as prop. + * kind of overkill... + * good cadidate to have check implemented as an util that can + * also be used elsewhere. + */ + if (this.props.className === undefined || + this.props.className.split(' ').indexOf('navbar-collapse') === -2) { + classes['navbar-collapse'] = this.props.collapsable; + } + + return ( +
+ {ValidComponentChildren.map(this.props.children, + this.props.collapsable ? + this.renderCollapsibleNavChildren : + this.renderChildren + )} +
+ ); + }, + + getChildActiveProp(child) { + if (child.props.active) { + return true; + } + if (this.props.activeKey != null) { + if (child.props.eventKey === this.props.activeKey) { + return true; + } + } + if (this.props.activeHref != null) { + if (child.props.href === this.props.activeHref) { + return true; + } + } + + return child.props.active; + }, + + renderChildren(child, index) { + let key = child.key ? child.key : index; + return cloneElement( + child, + { + activeKey: this.props.activeKey, + activeHref: this.props.activeHref, + ref: 'nocollapse_' + key, + key: key, + navItem: true + } + ); + }, + + renderCollapsibleNavChildren(child, index) { + let key = child.key ? child.key : index; + return cloneElement( + child, + { + active: this.getChildActiveProp(child), + activeKey: this.props.activeKey, + activeHref: this.props.activeHref, + onSelect: createChainedFunction(child.props.onSelect, this.props.onSelect), + ref: 'collapsible_' + key, + key: key, + navItem: true + } + ); + } +}); + +export default CollapsibleNav; diff --git a/src/Nav.js b/src/Nav.js index 7828f06cb9..8633769269 100644 --- a/src/Nav.js +++ b/src/Nav.js @@ -1,6 +1,6 @@ import React, { cloneElement } from 'react'; import BootstrapMixin from './BootstrapMixin'; -import CollapsableMixin from './CollapsableMixin'; +import CollapsibleMixin from './CollapsibleMixin'; import classNames from 'classnames'; import domUtils from './utils/domUtils'; @@ -9,7 +9,7 @@ import ValidComponentChildren from './utils/ValidComponentChildren'; import createChainedFunction from './utils/createChainedFunction'; const Nav = React.createClass({ - mixins: [BootstrapMixin, CollapsableMixin], + mixins: [BootstrapMixin, CollapsibleMixin], propTypes: { activeHref: React.PropTypes.string, @@ -32,11 +32,11 @@ const Nav = React.createClass({ }; }, - getCollapsableDOMNode() { + getCollapsibleDOMNode() { return React.findDOMNode(this); }, - getCollapsableDimensionValue: function () { + getCollapsibleDimensionValue: function () { let node = React.findDOMNode(this.refs.ul), height = node.offsetHeight, computedStyles = domUtils.getComputedStyles(node); @@ -45,7 +45,7 @@ const Nav = React.createClass({ }, render() { - let classes = this.props.collapsable ? this.getCollapsableClassSet() : {}; + let classes = this.props.collapsable ? this.getCollapsibleClassSet() : {}; classes['navbar-collapse'] = this.props.collapsable; diff --git a/src/Panel.js b/src/Panel.js index f56af7ae3d..66a9b0620d 100644 --- a/src/Panel.js +++ b/src/Panel.js @@ -2,10 +2,10 @@ import React, { cloneElement } from 'react'; import classNames from 'classnames'; import BootstrapMixin from './BootstrapMixin'; -import CollapsableMixin from './CollapsableMixin'; +import CollapsibleMixin from './CollapsibleMixin'; const Panel = React.createClass({ - mixins: [BootstrapMixin, CollapsableMixin], + mixins: [BootstrapMixin, CollapsibleMixin], propTypes: { collapsable: React.PropTypes.bool, @@ -41,11 +41,11 @@ const Panel = React.createClass({ this.setState({expanded:!this.state.expanded}); }, - getCollapsableDimensionValue() { + getCollapsibleDimensionValue() { return React.findDOMNode(this.refs.panel).scrollHeight; }, - getCollapsableDOMNode() { + getCollapsibleDOMNode() { if (!this.isMounted() || !this.refs || !this.refs.panel) { return null; } @@ -72,7 +72,7 @@ const Panel = React.createClass({ return (
diff --git a/src/index.js b/src/index.js index ab8c3f01c2..f4b296d878 100644 --- a/src/index.js +++ b/src/index.js @@ -8,10 +8,12 @@ import Button from './Button'; import ButtonGroup from './ButtonGroup'; import ButtonToolbar from './ButtonToolbar'; import CollapsableNav from './CollapsableNav'; +import CollapsibleNav from './CollapsibleNav'; import Carousel from './Carousel'; import CarouselItem from './CarouselItem'; import Col from './Col'; import CollapsableMixin from './CollapsableMixin'; +import CollapsibleMixin from './CollapsibleMixin'; import DropdownButton from './DropdownButton'; import DropdownMenu from './DropdownMenu'; import DropdownStateMixin from './DropdownStateMixin'; @@ -60,10 +62,12 @@ export default { ButtonGroup, ButtonToolbar, CollapsableNav, + CollapsibleNav, Carousel, CarouselItem, Col, CollapsableMixin, + CollapsibleMixin, DropdownButton, DropdownMenu, DropdownStateMixin, diff --git a/src/utils/deprecationWarning.js b/src/utils/deprecationWarning.js new file mode 100644 index 0000000000..2395cb4729 --- /dev/null +++ b/src/utils/deprecationWarning.js @@ -0,0 +1,10 @@ +export default function deprecationWarning(oldname, newname, link) { + if (process.env.NODE_ENV !== 'production') { + let message = `${oldname} is deprecated. Use ${newname} instead.`; + console.warn(message); + + if (link) { + console.warn(`You can read more about it here ${link}`); + } + } +} diff --git a/test/CollapsableMixinSpec.js b/test/CollapsableMixinSpec.js index 3323bdcbe7..4e1dd76f77 100644 --- a/test/CollapsableMixinSpec.js +++ b/test/CollapsableMixinSpec.js @@ -2,6 +2,7 @@ import React from 'react'; import ReactTestUtils from 'react/lib/ReactTestUtils'; import CollapsableMixin from '../src/CollapsableMixin'; import classNames from 'classnames'; +import {shouldWarn} from './helpers'; describe('CollapsableMixin', function () { @@ -39,6 +40,7 @@ describe('CollapsableMixin', function () { ); let state = instance.getInitialState(); assert.ok(state.expanded === true); + shouldWarn('deprecated'); }); it('Should default collapsing to false', function () { @@ -47,6 +49,7 @@ describe('CollapsableMixin', function () { ); let state = instance.getInitialState(); assert.ok(state.collapsing === false); + shouldWarn('deprecated'); }); }); @@ -56,6 +59,7 @@ describe('CollapsableMixin', function () { Panel content ); assert.ok(ReactTestUtils.findRenderedDOMComponentWithClass(instance, 'collapse')); + shouldWarn('deprecated'); }); }); @@ -70,6 +74,7 @@ describe('CollapsableMixin', function () { instance.setProps({expanded:true}); let node = instance.getCollapsableDOMNode(); assert.equal(node.className, 'collapsing'); + shouldWarn('deprecated'); }); it('Should set initial 0px height', function () { @@ -81,6 +86,7 @@ describe('CollapsableMixin', function () { }; instance.setProps({expanded:true}); + shouldWarn('deprecated'); }); it('Should set transition to height', function () { @@ -89,6 +95,7 @@ describe('CollapsableMixin', function () { instance.setProps({expanded:true}); assert.equal(node.style.height, '15px'); + shouldWarn('deprecated'); }); it('Should transition from collapsing to not collapsing', function (done) { @@ -96,6 +103,7 @@ describe('CollapsableMixin', function () { setTimeout(function(){ complete(); assert.ok(!instance.state.collapsing); + shouldWarn('deprecated'); done(); }, 100); }; @@ -110,6 +118,7 @@ describe('CollapsableMixin', function () { setTimeout(function(){ complete(); assert.equal(nodeInner.style.height, ''); + shouldWarn('deprecated'); done(); }, 100); }; @@ -131,6 +140,7 @@ describe('CollapsableMixin', function () { instance.setProps({expanded:false}); let node = instance.getCollapsableDOMNode(); assert.equal(node.className, 'collapsing'); + shouldWarn('deprecated'); }); it('Should set initial height', function () { @@ -142,6 +152,7 @@ describe('CollapsableMixin', function () { assert.equal(node.style.height, ''); instance.setProps({expanded:false}); + shouldWarn('deprecated'); }); it('Should set transition to height', function () { @@ -150,6 +161,7 @@ describe('CollapsableMixin', function () { instance.setProps({expanded:false}); assert.equal(node.style.height, '0px'); + shouldWarn('deprecated'); }); it('Should transition from collapsing to not collapsing', function (done) { @@ -157,6 +169,7 @@ describe('CollapsableMixin', function () { setTimeout(function(){ complete(); assert.ok(!instance.state.collapsing); + shouldWarn('deprecated'); done(); }, 100); }; @@ -171,6 +184,7 @@ describe('CollapsableMixin', function () { setTimeout(function(){ complete(); assert.ok(nodeInner.style.height === '0px'); + shouldWarn('deprecated'); done(); }, 100); }; @@ -187,6 +201,7 @@ describe('CollapsableMixin', function () { Panel content ); assert.ok(ReactTestUtils.findRenderedDOMComponentWithClass(instance, 'collapse in')); + shouldWarn('deprecated'); }); it('Should have collapse and in class with defaultExpanded', function () { @@ -194,6 +209,7 @@ describe('CollapsableMixin', function () { Panel content ); assert.ok(ReactTestUtils.findRenderedDOMComponentWithClass(instance, 'collapse in')); + shouldWarn('deprecated'); }); }); @@ -206,6 +222,7 @@ describe('CollapsableMixin', function () { it('Defaults to height', function(){ assert.equal(instance.dimension(), 'height'); + shouldWarn('deprecated'); }); it('Uses getCollapsableDimension if exists', function(){ @@ -213,6 +230,7 @@ describe('CollapsableMixin', function () { return 'whatevs'; }; assert.equal(instance.dimension(), 'whatevs'); + shouldWarn('deprecated'); }); }); }); diff --git a/test/CollapsibleMixinSpec.js b/test/CollapsibleMixinSpec.js new file mode 100644 index 0000000000..c4e0599c64 --- /dev/null +++ b/test/CollapsibleMixinSpec.js @@ -0,0 +1,259 @@ +import React from 'react'; +import ReactTestUtils from 'react/lib/ReactTestUtils'; +import CollapsibleMixin from '../src/CollapsibleMixin'; +import classNames from 'classnames'; +import {shouldWarn} from './helpers'; + +describe('CollapsibleMixin', function () { + + let Component, instance; + + beforeEach(function(){ + Component = React.createClass({ + mixins: [CollapsibleMixin], + + getCollapsibleDOMNode: function(){ + return this.refs.panel.getDOMNode(); + }, + + getCollapsibleDimensionValue: function(){ + return 15; + }, + + render: function(){ + let styles = this.getCollapsibleClassSet(); + return ( +
+
+ {this.props.children} +
+
+ ); + } + }); + }); + + describe('getInitialState', function(){ + it('Should check defaultExpanded', function () { + instance = ReactTestUtils.renderIntoDocument( + Panel content + ); + let state = instance.getInitialState(); + assert.ok(state.expanded === true); + }); + + it('Should default collapsing to false', function () { + instance = ReactTestUtils.renderIntoDocument( + Panel content + ); + let state = instance.getInitialState(); + assert.ok(state.collapsing === false); + }); + }); + + describe('collapsed', function(){ + it('Should have collapse class', function () { + instance = ReactTestUtils.renderIntoDocument( + Panel content + ); + assert.ok(ReactTestUtils.findRenderedDOMComponentWithClass(instance, 'collapse')); + }); + }); + + describe('from collapsed to expanded', function(){ + beforeEach(function(){ + instance = ReactTestUtils.renderIntoDocument( + Panel content + ); + }); + + it('Should have collapsing class', function () { + instance.setProps({expanded:true}); + let node = instance.getCollapsibleDOMNode(); + assert.equal(node.className, 'collapsing'); + }); + + it('Should set initial 0px height', function () { + let node = instance.getCollapsibleDOMNode(); + assert.equal(node.style.height, ''); + + instance._afterWillUpdate = function(){ + assert.equal(node.style.height, '0px'); + }; + + instance.setProps({expanded:true}); + }); + + it('Should set transition to height', function () { + let node = instance.getCollapsibleDOMNode(); + assert.equal(node.styled, undefined); + + instance.setProps({expanded:true}); + assert.equal(node.style.height, '15px'); + }); + + it('Should transition from collapsing to not collapsing', function (done) { + instance._addEndEventListener = function(node, complete){ + setTimeout(function(){ + complete(); + assert.ok(!instance.state.collapsing); + done(); + }, 100); + }; + instance.setProps({expanded:true}); + assert.ok(instance.state.collapsing); + }); + + it('Should clear height after transition complete', function (done) { + let node = instance.getCollapsibleDOMNode(); + + instance._addEndEventListener = function(nodeInner, complete){ + setTimeout(function(){ + complete(); + assert.equal(nodeInner.style.height, ''); + done(); + }, 100); + }; + + assert.equal(node.style.height, ''); + instance.setProps({expanded:true}); + assert.equal(node.style.height, '15px'); + }); + }); + + describe('from expanded to collapsed', function(){ + beforeEach(function(){ + instance = ReactTestUtils.renderIntoDocument( + Panel content + ); + }); + + it('Should have collapsing class', function () { + instance.setProps({expanded:false}); + let node = instance.getCollapsibleDOMNode(); + assert.equal(node.className, 'collapsing'); + }); + + it('Should set initial height', function () { + let node = instance.getCollapsibleDOMNode(); + + instance._afterWillUpdate = function(){ + assert.equal(node.style.height, '15px'); + }; + + assert.equal(node.style.height, ''); + instance.setProps({expanded:false}); + }); + + it('Should set transition to height', function () { + let node = instance.getCollapsibleDOMNode(); + assert.equal(node.style.height, ''); + + instance.setProps({expanded:false}); + assert.equal(node.style.height, '0px'); + }); + + it('Should transition from collapsing to not collapsing', function (done) { + instance._addEndEventListener = function(node, complete){ + setTimeout(function(){ + complete(); + assert.ok(!instance.state.collapsing); + done(); + }, 100); + }; + instance.setProps({expanded:false}); + assert.ok(instance.state.collapsing); + }); + + it('Should have 0px height after transition complete', function (done) { + let node = instance.getCollapsibleDOMNode(); + + instance._addEndEventListener = function(nodeInner, complete){ + setTimeout(function(){ + complete(); + assert.ok(nodeInner.style.height === '0px'); + done(); + }, 100); + }; + + assert.equal(node.style.height, ''); + instance.setProps({expanded:false}); + assert.equal(node.style.height, '0px'); + }); + }); + + describe('expanded', function(){ + it('Should have collapse and in class', function () { + instance = ReactTestUtils.renderIntoDocument( + Panel content + ); + assert.ok(ReactTestUtils.findRenderedDOMComponentWithClass(instance, 'collapse in')); + }); + + it('Should have collapse and in class with defaultExpanded', function () { + instance = ReactTestUtils.renderIntoDocument( + Panel content + ); + assert.ok(ReactTestUtils.findRenderedDOMComponentWithClass(instance, 'collapse in')); + }); + }); + + describe('dimension', function(){ + beforeEach(function(){ + instance = ReactTestUtils.renderIntoDocument( + Panel content + ); + }); + + it('Defaults to height', function(){ + assert.equal(instance.dimension(), 'height'); + }); + + it('Uses getCollapsibleDimension if exists', function(){ + instance.getCollapsibleDimension = function(){ + return 'whatevs'; + }; + assert.equal(instance.dimension(), 'whatevs'); + }); + }); + + describe('deprecations', function() { + it('warns about getCollaps_a_bleDimension() deprecation', function () { + Component = React.createClass({ + mixins: [CollapsibleMixin], + + getCollapsableDimension: function(){}, + + render: function(){ + return (
); + } + }); + + instance = ReactTestUtils.renderIntoDocument( + + ); + + instance.dimension(); + shouldWarn('deprecated'); + }); + + it('does not warn about getCollaps_i_bleDimension() use', function () { + Component = React.createClass({ + mixins: [CollapsibleMixin], + + getCollapsibleDimension: function(){}, + + render: function(){ + return (
); + } + }); + + instance = ReactTestUtils.renderIntoDocument( + + ); + + instance.dimension(); + console.warn.called.should.be.false; + }); + }); +}); diff --git a/test/CollapsableNavSpec.js b/test/CollapsibleNavSpec.js similarity index 77% rename from test/CollapsableNavSpec.js rename to test/CollapsibleNavSpec.js index ee4a3c206f..c7e2c46610 100644 --- a/test/CollapsableNavSpec.js +++ b/test/CollapsibleNavSpec.js @@ -1,22 +1,22 @@ import React from 'react'; import ReactTestUtils from 'react/lib/ReactTestUtils'; import Navbar from '../src/Navbar'; -import CollapsableNav from '../src/CollapsableNav'; +import CollapsibleNav from '../src/CollapsibleNav'; import Nav from '../src/Nav'; import NavItem from '../src/NavItem'; -describe('CollapsableNav', function () { +describe('CollapsibleNav', function () { it('Should create div and add collapse class', function () { let Parent = React.createClass({ render: function() { return ( - + - + ); } @@ -30,7 +30,7 @@ describe('CollapsableNav', function () { render: function() { return ( - + - + ); } }); let instance = ReactTestUtils.renderIntoDocument(); - assert.ok(ReactTestUtils.findRenderedComponentWithType(instance.refs.collapsable_object.refs.collapsable_0, Nav)); - assert.ok(ReactTestUtils.findRenderedComponentWithType(instance.refs.collapsable_object.refs.collapsable_1, Nav)); + assert.ok(ReactTestUtils.findRenderedComponentWithType(instance.refs.collapsible_object.refs.collapsible_0, Nav)); + assert.ok(ReactTestUtils.findRenderedComponentWithType(instance.refs.collapsible_object.refs.collapsible_1, Nav)); }); it('Should just render children and move along if not in ', function () { let Parent = React.createClass({ render: function() { return ( - + - + ); } }); let instance = ReactTestUtils.renderIntoDocument(); - let collapsableNav = ReactTestUtils.findRenderedComponentWithType(instance, CollapsableNav); - assert.notOk(collapsableNav.getDOMNode().className.match(/\navbar-collapse\b/)); - let nav = ReactTestUtils.findRenderedComponentWithType(collapsableNav.refs.nocollapse_0, Nav); + let collapsibleNav = ReactTestUtils.findRenderedComponentWithType(instance, CollapsibleNav); + assert.notOk(collapsibleNav.getDOMNode().className.match(/\navbar-collapse\b/)); + let nav = ReactTestUtils.findRenderedComponentWithType(collapsibleNav.refs.nocollapse_0, Nav); assert.ok(nav); }); @@ -74,20 +74,20 @@ describe('CollapsableNav', function () { render: function() { return ( - + - + ); } }); let instance = ReactTestUtils.renderIntoDocument(); - assert.ok(ReactTestUtils.findRenderedDOMComponentWithClass(instance.refs.collapsable_object.refs.collapsable_0, 'foo')); - assert.ok(ReactTestUtils.findRenderedDOMComponentWithClass(instance.refs.collapsable_object.refs.collapsable_0, 'bar')); - assert.ok(ReactTestUtils.findRenderedDOMComponentWithClass(instance.refs.collapsable_object.refs.collapsable_0, 'baz')); + assert.ok(ReactTestUtils.findRenderedDOMComponentWithClass(instance.refs.collapsible_object.refs.collapsible_0, 'foo')); + assert.ok(ReactTestUtils.findRenderedDOMComponentWithClass(instance.refs.collapsible_object.refs.collapsible_0, 'bar')); + assert.ok(ReactTestUtils.findRenderedDOMComponentWithClass(instance.refs.collapsible_object.refs.collapsible_0, 'baz')); }); it('Should should not duplicate classes', function () { @@ -95,18 +95,18 @@ describe('CollapsableNav', function () { render: function() { return ( - + - + ); } }); let instance = ReactTestUtils.renderIntoDocument(); - let classDOM = ReactTestUtils.findRenderedDOMComponentWithTag(instance.refs.collapsable_object, 'DIV').props.className + let classDOM = ReactTestUtils.findRenderedDOMComponentWithTag(instance.refs.collapsible_object, 'DIV').props.className , classArray = classDOM.split(' ') , idx = classArray.indexOf('navbar-collapse'); assert.equal(classArray.indexOf('navbar-collapse', idx+1), -1); diff --git a/test/InputSpec.js b/test/InputSpec.js index 36369968a5..7074404355 100644 --- a/test/InputSpec.js +++ b/test/InputSpec.js @@ -4,6 +4,7 @@ import Input from '../src/Input'; import Button from '../src/Button'; import DropdownButton from '../src/DropdownButton'; import MenuItem from '../src/MenuItem'; +import {shouldWarn} from './helpers'; describe('Input', function () { it('renders children when type is not set', function () { @@ -63,9 +64,7 @@ describe('Input', function () { ); - console.warn.called.should.be.true; - console.warn.calledWithMatch('propType: Invalid').should.be.true; - console.warn.reset(); // reset state for afterEach() + shouldWarn('propType: Invalid'); }); it('renders a p element when type=static', function () { diff --git a/test/helpers.js b/test/helpers.js new file mode 100644 index 0000000000..8065ae7ff8 --- /dev/null +++ b/test/helpers.js @@ -0,0 +1,9 @@ +function shouldWarn(about) { + console.warn.called.should.be.true; + console.warn.calledWithMatch(about).should.be.true; + console.warn.reset(); +} + +export default { + shouldWarn +};