Skip to content

Commit 0503507

Browse files
committed
[added] Collapse Component, replaces CollapsibleMixin
1 parent 3a0b4da commit 0503507

File tree

6 files changed

+513
-64
lines changed

6 files changed

+513
-64
lines changed

Diff for: docs/examples/Collapse.js

+28
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
class Example extends React.Component {
2+
constructor(...args){
3+
super(...args);
4+
5+
this.state = {};
6+
}
7+
8+
render(){
9+
10+
return (
11+
<div>
12+
<Button onClick={ ()=> this.setState({ open: !this.state.open })}>
13+
click
14+
</Button>
15+
<Collapse in={this.state.open}>
16+
<div>
17+
<Well>
18+
Anim pariatur cliche reprehenderit, enim eiusmod high life accusamus terry richardson ad squid.
19+
Nihil anim keffiyeh helvetica, craft beer labore wes anderson cred nesciunt sapiente ea proident.
20+
</Well>
21+
</div>
22+
</Collapse>
23+
</div>
24+
);
25+
}
26+
}
27+
28+
React.render(<Example/>, mountNode);

Diff for: src/Collapse.js

+200
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,200 @@
1+
/*eslint-disable react/prop-types */
2+
'use strict';
3+
import React from 'react';
4+
import Transition from './Transition';
5+
import domUtils from './utils/domUtils';
6+
import createChainedFunction from './utils/createChainedFunction';
7+
8+
let capitalize = str => str[0].toUpperCase() + str.substr(1);
9+
10+
// reading a dimension prop will cause the browser to recalculate,
11+
// which will let our animations work
12+
let triggerBrowserReflow = node => node.offsetHeight; //eslint-disable-line no-unused-expressions
13+
14+
const MARGINS = {
15+
height: ['marginTop', 'marginBottom'],
16+
width: ['marginLeft', 'marginRight']
17+
};
18+
19+
function getDimensionValue(dimension, elem){
20+
let value = elem[`offset${capitalize(dimension)}`];
21+
let computedStyles = domUtils.getComputedStyles(elem);
22+
let margins = MARGINS[dimension];
23+
24+
return (value +
25+
parseInt(computedStyles[margins[0]], 10) +
26+
parseInt(computedStyles[margins[1]], 10)
27+
);
28+
}
29+
30+
class Collapse extends React.Component {
31+
32+
constructor(props, context){
33+
super(props, context);
34+
35+
this.onEnterListener = this.handleEnter.bind(this);
36+
this.onEnteringListener = this.handleEntering.bind(this);
37+
this.onEnteredListener = this.handleEntered.bind(this);
38+
this.onExitListener = this.handleExit.bind(this);
39+
this.onExitingListener = this.handleExiting.bind(this);
40+
}
41+
42+
render() {
43+
let enter = createChainedFunction(this.onEnterListener, this.props.onEnter);
44+
let entering = createChainedFunction(this.onEnteringListener, this.props.onEntering);
45+
let entered = createChainedFunction(this.onEnteredListener, this.props.onEntered);
46+
let exit = createChainedFunction(this.onExitListener, this.props.onExit);
47+
let exiting = createChainedFunction(this.onExitingListener, this.props.onExiting);
48+
49+
return (
50+
<Transition
51+
ref='transition'
52+
{...this.props}
53+
aria-expanded={this.props.in}
54+
className={this._dimension() === 'width' ? 'width' : ''}
55+
exitedClassName='collapse'
56+
exitingClassName='collapsing'
57+
enteredClassName='collapse in'
58+
enteringClassName='collapsing'
59+
onEnter={enter}
60+
onEntering={entering}
61+
onEntered={entered}
62+
onExit={exit}
63+
onExiting={exiting}
64+
onExited={this.props.onExited}
65+
>
66+
{ this.props.children }
67+
</Transition>
68+
);
69+
}
70+
71+
/* -- Expanding -- */
72+
handleEnter(elem){
73+
let dimension = this._dimension();
74+
elem.style[dimension] = '0';
75+
}
76+
77+
handleEntering(elem){
78+
let dimension = this._dimension();
79+
80+
elem.style[dimension] = this._getScrollDimensionValue(elem, dimension);
81+
}
82+
83+
handleEntered(elem){
84+
let dimension = this._dimension();
85+
elem.style[dimension] = null;
86+
}
87+
88+
/* -- Collapsing -- */
89+
handleExit(elem){
90+
let dimension = this._dimension();
91+
92+
elem.style[dimension] = this.props.getDimensionValue(dimension, elem) + 'px';
93+
}
94+
95+
handleExiting(elem){
96+
let dimension = this._dimension();
97+
98+
triggerBrowserReflow(elem);
99+
elem.style[dimension] = '0';
100+
}
101+
102+
_dimension(){
103+
return typeof this.props.dimension === 'function'
104+
? this.props.dimension()
105+
: this.props.dimension;
106+
}
107+
108+
//for testing
109+
_getTransitionInstance(){
110+
return this.refs.transition;
111+
}
112+
113+
_getScrollDimensionValue(elem, dimension){
114+
return elem[`scroll${capitalize(dimension)}`] + 'px';
115+
}
116+
}
117+
118+
Collapse.propTypes = {
119+
/**
120+
* Collapse the Component in or out.
121+
*/
122+
in: React.PropTypes.bool,
123+
124+
/**
125+
* Provide the durration of the animation in milliseconds, used to ensure that finishing callbacks are fired even if the
126+
* original browser transition end events are canceled.
127+
*/
128+
duration: React.PropTypes.number,
129+
130+
/**
131+
* Specifies the dimension used when collapsing.
132+
*
133+
* _Note: Bootstrap only partially supports this!
134+
* You will need to supply your own css animation for the `.width` css class._
135+
*/
136+
dimension: React.PropTypes.oneOfType([
137+
React.PropTypes.oneOf(['height', 'width']),
138+
React.PropTypes.func
139+
]),
140+
141+
/**
142+
* A function that returns the height or width of the animating DOM node. Allows for providing some custom logic how much
143+
* Collapse component should animation in its specified dimension.
144+
*
145+
* `getDimensionValue` is called with the current dimension prop value and the DOM node.
146+
*/
147+
getDimensionValue: React.PropTypes.func,
148+
149+
/**
150+
* A Callback fired before the component starts to expand.
151+
*/
152+
onEnter: React.PropTypes.func,
153+
154+
/**
155+
* A Callback fired immediately after the component starts to expand.
156+
*/
157+
onEntering: React.PropTypes.func,
158+
159+
/**
160+
* A Callback fired after the component has expanded.
161+
*/
162+
onEntered: React.PropTypes.func,
163+
164+
/**
165+
* A Callback fired before the component starts to collapse.
166+
*/
167+
onExit: React.PropTypes.func,
168+
169+
/**
170+
* A Callback fired immediately after the component starts to collapse.
171+
*/
172+
onExiting: React.PropTypes.func,
173+
174+
/**
175+
* A Callback fired after the component has collapsed.
176+
*/
177+
onExited: React.PropTypes.func,
178+
179+
/**
180+
* Specify whether the transitioning component should be unmounted (removed from the DOM) once the exit animation finishes.
181+
*/
182+
unmountOnExit: React.PropTypes.bool,
183+
184+
/**
185+
* Specify whether the component should collapse or expand when it mounts.
186+
*/
187+
transitionAppear: React.PropTypes.bool
188+
};
189+
190+
Collapse.defaultProps = {
191+
in: false,
192+
duration: 300,
193+
dimension: 'height',
194+
transitionAppear: false,
195+
unmountOnExit: false,
196+
getDimensionValue
197+
};
198+
199+
export default Collapse;
200+

Diff for: src/CollapsibleNav.js

+30-24
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,13 @@
11
import React, { cloneElement } from 'react';
22
import BootstrapMixin from './BootstrapMixin';
3-
import CollapsibleMixin from './CollapsibleMixin';
3+
import Collapse from './Collapse';
44
import classNames from 'classnames';
5-
import domUtils from './utils/domUtils';
65

76
import ValidComponentChildren from './utils/ValidComponentChildren';
87
import createChainedFunction from './utils/createChainedFunction';
98

109
const CollapsibleNav = React.createClass({
11-
mixins: [BootstrapMixin, CollapsibleMixin],
10+
mixins: [BootstrapMixin],
1211

1312
propTypes: {
1413
onSelect: React.PropTypes.func,
@@ -19,41 +18,48 @@ const CollapsibleNav = React.createClass({
1918
eventKey: React.PropTypes.any
2019
},
2120

22-
getCollapsibleDOMNode() {
23-
return React.findDOMNode(this);
24-
},
2521

26-
getCollapsibleDimensionValue() {
27-
let height = 0;
28-
let nodes = this.refs;
29-
for (let key in nodes) {
30-
if (nodes.hasOwnProperty(key)) {
22+
// getCollapsibleDimensionValue() {
23+
// let height = 0;
24+
// let nodes = this.refs;
25+
// for (let key in nodes) {
26+
// if (nodes.hasOwnProperty(key)) {
3127

32-
let n = React.findDOMNode(nodes[key]);
33-
let h = n.offsetHeight;
34-
let computedStyles = domUtils.getComputedStyles(n);
28+
// let n = React.findDOMNode(nodes[key]);
29+
// let h = n.offsetHeight;
30+
// let computedStyles = domUtils.getComputedStyles(n);
3531

36-
height += (h +
37-
parseInt(computedStyles.marginTop, 10) +
38-
parseInt(computedStyles.marginBottom, 10)
39-
);
40-
}
41-
}
42-
return height;
43-
},
32+
// height += (h +
33+
// parseInt(computedStyles.marginTop, 10) +
34+
// parseInt(computedStyles.marginBottom, 10)
35+
// );
36+
// }
37+
// }
38+
// return height;
39+
// },
4440

4541
render() {
4642
/*
4743
* this.props.collapsible is set in NavBar when an eventKey is supplied.
4844
*/
49-
const classes = this.props.collapsible ? this.getCollapsibleClassSet('navbar-collapse') : null;
45+
const classes = this.props.collapsible ? 'navbar-collapse' : null;
5046
const renderChildren = this.props.collapsible ? this.renderCollapsibleNavChildren : this.renderChildren;
5147

52-
return (
48+
let nav = (
5349
<div eventKey={this.props.eventKey} className={classNames(this.props.className, classes)} >
5450
{ValidComponentChildren.map(this.props.children, renderChildren)}
5551
</div>
5652
);
53+
54+
if ( this.props.collapsible ){
55+
return (
56+
<Collapse in={this.props.expanded}>
57+
{ nav }
58+
</Collapse>
59+
);
60+
} else {
61+
return nav;
62+
}
5763
},
5864

5965
getChildActiveProp(child) {

Diff for: src/Nav.js

+10-20
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,13 @@
11
import React, { cloneElement } from 'react';
22
import BootstrapMixin from './BootstrapMixin';
3-
import CollapsibleMixin from './CollapsibleMixin';
3+
import Collapse from './Collapse';
44
import classNames from 'classnames';
5-
import domUtils from './utils/domUtils';
65

76
import ValidComponentChildren from './utils/ValidComponentChildren';
87
import createChainedFunction from './utils/createChainedFunction';
98

109
const Nav = React.createClass({
11-
mixins: [BootstrapMixin, CollapsibleMixin],
10+
mixins: [BootstrapMixin],
1211

1312
propTypes: {
1413
activeHref: React.PropTypes.string,
@@ -27,33 +26,24 @@ const Nav = React.createClass({
2726

2827
getDefaultProps() {
2928
return {
30-
bsClass: 'nav'
29+
bsClass: 'nav',
30+
expanded: true
3131
};
3232
},
3333

34-
getCollapsibleDOMNode() {
35-
return React.findDOMNode(this);
36-
},
37-
38-
getCollapsibleDimensionValue() {
39-
let node = React.findDOMNode(this.refs.ul);
40-
let height = node.offsetHeight;
41-
let computedStyles = domUtils.getComputedStyles(node);
42-
43-
return height + parseInt(computedStyles.marginTop, 10) + parseInt(computedStyles.marginBottom, 10);
44-
},
45-
4634
render() {
47-
const classes = this.props.collapsible ? this.getCollapsibleClassSet('navbar-collapse') : null;
35+
const classes = this.props.collapsible ? 'navbar-collapse' : null;
4836

4937
if (this.props.navbar && !this.props.collapsible) {
5038
return (this.renderUl());
5139
}
5240

5341
return (
54-
<nav {...this.props} className={classNames(this.props.className, classes)}>
55-
{ this.renderUl() }
56-
</nav>
42+
<Collapse in={this.props.expanded}>
43+
<nav {...this.props} className={classNames(this.props.className, classes)}>
44+
{this.renderUl()}
45+
</nav>
46+
</Collapse>
5747
);
5848
},
5949

0 commit comments

Comments
 (0)