Skip to content

Commit 6eae964

Browse files
committed
Merge pull request react-bootstrap#848 from react-bootstrap/transitions
Transitions Components
2 parents 5cf1fd4 + c3b41af commit 6eae964

30 files changed

Lines changed: 1418 additions & 152 deletions

docs/examples/.eslintrc

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,8 @@
5252
"TabPane",
5353
"Tooltip",
5454
"Well",
55-
"Thumbnail"
55+
"Thumbnail",
56+
"Collapse",
57+
"Fade"
5658
}
5759
}

docs/examples/Collapse.js

Lines changed: 28 additions & 0 deletions
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);

docs/examples/Fade.js

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

docs/src/ComponentsPage.js

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@ const ComponentsPage = React.createClass({
5656
<div className='row'>
5757
<div className='col-md-9' role='main'>
5858

59+
5960
{/* Buttons */}
6061
<div className='bs-docs-section'>
6162
<h1 id='buttons' className='page-header'>Buttons <small>Button</small></h1>
@@ -843,7 +844,7 @@ const ComponentsPage = React.createClass({
843844
equivalent to jQuery's <code>.appendTo()</code>, which is helpful for components that need to be appended to a DOM node other than
844845
the component's direct parent. The Modal, and Overlay components use the Portal component internally.
845846
</p>
846-
<h3 id='utilities-props'>Props</h3>
847+
<h3 id='utilities-portal-props'>Props</h3>
847848

848849
<PropTable component='Portal'/>
849850

@@ -852,9 +853,25 @@ const ComponentsPage = React.createClass({
852853
A Component that absolutely positions its child to a <code>target</code> component or DOM node. Useful for creating custom
853854
popups or tooltips. Used by the Overlay Components.
854855
</p>
855-
<h3 id='utilities-props'>Props</h3>
856+
<h3 id='utilities-position-props'>Props</h3>
856857

857858
<PropTable component='Position'/>
859+
860+
<h2 id='utilities-transitions'>Transitions</h2>
861+
862+
<h3 id='utilities-collapse'>Collapse</h3>
863+
<p>Add a collapse toggle animation to an element or component.</p>
864+
<ReactPlayground codeText={Samples.Collapse} />
865+
866+
<h4 id='utilities-collapse-props'>Props</h4>
867+
<PropTable component='Collapse'/>
868+
869+
<h3 id='utilities-fade'>Fade</h3>
870+
<p>Add a fade animation to a child element or component.</p>
871+
<ReactPlayground codeText={Samples.Fade} />
872+
873+
<h4 id='utilities-fade-props'>Props</h4>
874+
<PropTable component='Fade'/>
858875
</div>
859876
</div>
860877

docs/src/ReactPlayground.js

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,13 +6,19 @@ import * as modBadge from '../../src/Badge';
66
import * as modButton from '../../src/Button';
77
import * as modButtonGroup from '../../src/ButtonGroup';
88
import * as modButtonInput from '../../src/ButtonInput';
9+
910
import * as modButtonToolbar from '../../src/ButtonToolbar';
11+
import * as modCollapse from '../../src/Collapse';
12+
1013
import * as modCollapsibleNav from '../../src/CollapsibleNav';
1114
import * as modCollapsibleMixin from '../../src/CollapsibleMixin';
1215
import * as modCarousel from '../../src/Carousel';
1316
import * as modCarouselItem from '../../src/CarouselItem';
1417
import * as modCol from '../../src/Col';
1518
import * as modDropdownButton from '../../src/DropdownButton';
19+
20+
import * as modFade from '../../src/Fade';
21+
1622
import * as modFormControls from '../../src/FormControls';
1723
import * as modGlyphicon from '../../src/Glyphicon';
1824
import * as modGrid from '../../src/Grid';
@@ -57,8 +63,13 @@ import CodeExample from './CodeExample';
5763

5864

5965
const classNames = modClassNames.default;
66+
6067
/* eslint-disable */
68+
6169
const Portal = modPortal.default;
70+
const Collapse = modCollapse.default;
71+
const Fade = modFade.default;
72+
6273

6374
const React = modReact.default;
6475
const Accordion = modAccordion.default;

docs/src/Samples.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
/* eslint no-path-concat: 0, no-var: 0 */
22

33
export default {
4+
Collapse: require('fs').readFileSync(__dirname + '/../examples/Collapse.js', 'utf8'),
5+
Fade: require('fs').readFileSync(__dirname + '/../examples/Fade.js', 'utf8'),
6+
47
ButtonTypes: require('fs').readFileSync(__dirname + '/../examples/ButtonTypes.js', 'utf8'),
58
ButtonSizes: require('fs').readFileSync(__dirname + '/../examples/ButtonSizes.js', 'utf8'),
69
ButtonBlock: require('fs').readFileSync(__dirname + '/../examples/ButtonBlock.js', 'utf8'),

src/Collapse.js

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

src/CollapsibleMixin.js

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
11
import React from 'react';
22
import TransitionEvents from './utils/TransitionEvents';
3+
import deprecationWarning from './utils/deprecationWarning';
4+
5+
let warned = false;
36

47
const CollapsibleMixin = {
58

@@ -21,6 +24,13 @@ const CollapsibleMixin = {
2124
};
2225
},
2326

27+
componentWillMount(){
28+
if ( !warned ){
29+
deprecationWarning('CollapsibleMixin', 'Collapse Component');
30+
warned = true;
31+
}
32+
},
33+
2434
componentWillUpdate(nextProps, nextState){
2535
let willExpanded = nextProps.expanded != null ? nextProps.expanded : nextState.expanded;
2636
if (willExpanded === this.isExpanded()) {

0 commit comments

Comments
 (0)