Skip to content

Commit 6c5621f

Browse files
j-franciscoeddywashere
authored andcommitted
feat(Collapse): Add onOpened and onClosed events (#277) (#301)
* feat(Collapse): Add onOpened and onClosed events Allow passing in onOpened and onClosed props as functions that are triggered after the Collapse component has finished opening or closing. Useful for performing an action that relies on the new height of the page after a Collapse component has opened or closed (such as scrolling). * Fix typo in test * fix(Collapse): default onOpened and onClosed to noop Remove the need for the onOpened and onClosed helper functions and call the props directly. * Add info about onOpened and onClosed to Collapse example
1 parent fef0cbf commit 6c5621f

4 files changed

Lines changed: 124 additions & 3 deletions

File tree

docs/lib/Components/CollapsePage.js

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,9 @@ import Helmet from 'react-helmet';
66
import CollapseExample from '../examples/Collapse';
77
const CollapseExampleSource = require('!!raw!../examples/Collapse');
88

9+
import CollapseEventsExample from '../examples/CollapseEvents';
10+
const CollapseEventsExampleSource = require('!!raw!../examples/CollapseEvents');
11+
912
export default class CollapsePage extends React.Component {
1013
render() {
1114
return (
@@ -32,11 +35,23 @@ export default class CollapsePage extends React.Component {
3235
delay: PropTypes.oneOfType([
3336
PropTypes.shape({ show: PropTypes.number, hide: PropTypes.number }),
3437
PropTypes.number
35-
]),
36-
// optionally override show/hide delays - default { show: 350, hide: 350 }
38+
]), // optionally override show/hide delays - default { show: 350, hide: 350 }
39+
onOpened: PropTypes.func,
40+
onClosed: PropTypes.func,
3741
}`}
3842
</PrismCode>
3943
</pre>
44+
45+
<h3>Events</h3>
46+
<p>Use the onOpened and onClosed props for callbacks when the Collapse has finished opening or closing.</p>
47+
<div className="docs-example">
48+
<CollapseEventsExample />
49+
</div>
50+
<pre>
51+
<PrismCode className="language-jsx">
52+
{CollapseEventsExampleSource}
53+
</PrismCode>
54+
</pre>
4055
</div>
4156
);
4257
}
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
import React, { Component } from 'react';
2+
import { Collapse, Button, CardBlock, Card } from 'reactstrap';
3+
4+
class Example extends Component {
5+
constructor(props) {
6+
super(props);
7+
this.onOpened = this.onOpened.bind(this);
8+
this.onClosed = this.onClosed.bind(this);
9+
this.toggle = this.toggle.bind(this);
10+
this.state = { collapse: false, status: 'Closed' };
11+
}
12+
13+
onOpened() {
14+
this.setState({ ...this.state, status: 'Opened' });
15+
}
16+
17+
onClosed() {
18+
this.setState({ ...this.state, status: 'Closed' });
19+
}
20+
21+
toggle() {
22+
const status = !this.state.collapse ? 'Opening...' : 'Closing...';
23+
this.setState({ collapse: !this.state.collapse, status });
24+
}
25+
26+
render() {
27+
return (
28+
<div>
29+
<Button color="primary" onClick={this.toggle} style={{ marginBottom: '1rem' }}>Toggle</Button>
30+
<h5>Current state: {this.state.status}</h5>
31+
<Collapse isOpen={this.state.collapse} onOpened={this.onOpened} onClosed={this.onClosed}>
32+
<Card>
33+
<CardBlock>
34+
Anim pariatur cliche reprehenderit,
35+
enim eiusmod high life accusamus terry richardson ad squid. Nihil
36+
anim keffiyeh helvetica, craft beer labore wes anderson cred
37+
nesciunt sapiente ea proident.
38+
</CardBlock>
39+
</Card>
40+
</Collapse>
41+
</div>
42+
);
43+
}
44+
}
45+
46+
export default Example;

src/Collapse.js

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@ const propTypes = {
1818
PropTypes.shape({ show: PropTypes.number, hide: PropTypes.number }),
1919
PropTypes.number,
2020
]),
21+
onOpened: PropTypes.func,
22+
onClosed: PropTypes.func,
2123
};
2224

2325
const DEFAULT_DELAYS = {
@@ -29,11 +31,14 @@ const defaultProps = {
2931
isOpen: false,
3032
tag: 'div',
3133
delay: DEFAULT_DELAYS,
34+
onOpened: () => {},
35+
onClosed: () => {},
3236
};
3337

3438
class Collapse extends Component {
3539
constructor(props) {
3640
super(props);
41+
3742
this.state = {
3843
collapse: props.isOpen ? SHOWN : HIDDEN,
3944
height: null
@@ -78,6 +83,20 @@ class Collapse extends Component {
7883
// else: do nothing.
7984
}
8085

86+
componentDidUpdate(prevProps, prevState) {
87+
if (this.state.collapse === SHOWN &&
88+
prevState &&
89+
prevState.collapse !== SHOWN) {
90+
this.props.onOpened();
91+
}
92+
93+
if (this.state.collapse === HIDDEN &&
94+
prevState &&
95+
prevState.collapse !== HIDDEN) {
96+
this.props.onClosed();
97+
}
98+
}
99+
81100
componentWillUnmount() {
82101
clearTimeout(this.transitionTag);
83102
}
@@ -101,7 +120,7 @@ class Collapse extends Component {
101120
cssModule,
102121
tag: Tag,
103122
...attributes
104-
} = omit(this.props, ['isOpen', 'delay']);
123+
} = omit(this.props, ['isOpen', 'delay', 'onOpened', 'onClosed']);
105124
const { collapse, height } = this.state;
106125
let collapseClass;
107126
switch (collapse) {

src/__tests__/Collapse.spec.js

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -160,4 +160,45 @@ describe('Collapse', () => {
160160
wrapper.unmount();
161161
expect(Collapse.prototype.componentWillUnmount).toHaveBeenCalled();
162162
});
163+
164+
it('should call onOpened after opening', () => {
165+
const onOpened = jasmine.createSpy('onOpenedSpy');
166+
const onClosed = jasmine.createSpy('onClosedSpy');
167+
const wrapper = mount(<Collapse isOpen={isOpen} onOpened={onOpened} onClosed={onClosed} />);
168+
169+
jasmine.clock().tick(300);
170+
expect(isOpen).toBe(false);
171+
expect(onOpened).not.toHaveBeenCalled();
172+
expect(onClosed).not.toHaveBeenCalled();
173+
174+
toggle();
175+
wrapper.setProps({ isOpen });
176+
jasmine.clock().tick(380);
177+
expect(isOpen).toBe(true);
178+
expect(onOpened).toHaveBeenCalled();
179+
expect(onClosed).not.toHaveBeenCalled();
180+
181+
wrapper.unmount();
182+
});
183+
184+
it('should call onClosed after closing', () => {
185+
const onOpened = jasmine.createSpy('onOpenedSpy');
186+
const onClosed = jasmine.createSpy('onClosedSpy');
187+
toggle();
188+
const wrapper = mount(<Collapse isOpen={isOpen} onOpened={onOpened} onClosed={onClosed} />);
189+
190+
jasmine.clock().tick(380);
191+
expect(isOpen).toBe(true);
192+
expect(onOpened).not.toHaveBeenCalled();
193+
expect(onClosed).not.toHaveBeenCalled();
194+
195+
toggle();
196+
wrapper.setProps({ isOpen });
197+
jasmine.clock().tick(380);
198+
expect(isOpen).toBe(false);
199+
expect(onOpened).not.toHaveBeenCalled();
200+
expect(onClosed).toHaveBeenCalled();
201+
202+
wrapper.unmount();
203+
});
163204
});

0 commit comments

Comments
 (0)