Skip to content

Commit

Permalink
feat(collapse): add horizontal collapse
Browse files Browse the repository at this point in the history
  • Loading branch information
bestguy authored and phwebi committed Oct 27, 2021
1 parent a6050f5 commit f780187
Show file tree
Hide file tree
Showing 5 changed files with 62 additions and 17 deletions.
11 changes: 11 additions & 0 deletions docs/lib/Components/CollapsePage.js
Expand Up @@ -5,11 +5,13 @@ import PageTitle from '../UI/PageTitle';
import SectionTitle from '../UI/SectionTitle';

import CollapseExample from '../examples/Collapse';
import CollapseHorizontalExample from '../examples/CollapseHorizontal';
import UncontrolledCollapseExample from '../examples/CollapseUncontrolled';

import CollapseEventsExample from '../examples/CollapseEvents';

const CollapseExampleSource = require('!!raw-loader!../examples/Collapse');
const CollapseHorizontalExampleSource = require('!!raw-loader!../examples/CollapseHorizontal');
const CollapseEventsExampleSource = require('!!raw-loader!../examples/CollapseEvents');

const UncontrolledCollapseExampleSource = require('!!raw-loader!../examples/CollapseUncontrolled');
Expand All @@ -31,6 +33,7 @@ export default class CollapsePage extends React.Component {
<PrismCode className="language-jsx">
{`Collapse.propTypes = {
...Transition.propTypes, // see note below
horizontal: PropTypes.bool,
isOpen: PropTypes.bool,
children: PropTypes.oneOfType([
PropTypes.arrayOf(PropTypes.node),
Expand All @@ -51,6 +54,14 @@ export default class CollapsePage extends React.Component {
http://reactcommunity.org/react-transition-group/transition/</a>.
</p>

<SectionTitle>Horizontal</SectionTitle>
<div className="docs-example">
<CollapseHorizontalExample />
</div>
<pre>
<PrismCode className="language-jsx">{CollapseHorizontalExampleSource}</PrismCode>
</pre>

<SectionTitle>Events</SectionTitle>
<p>
Use the <code>onEnter</code>, onEntering, onEntered, onExiting and onExited props for
Expand Down
24 changes: 24 additions & 0 deletions docs/lib/examples/CollapseHorizontal.js
@@ -0,0 +1,24 @@
import React, { useState } from 'react';
import { Alert, Collapse, Button } from 'reactstrap';

const Example = () => {
const [isOpen, setIsOpen] = useState(false);

const toggle = () => setIsOpen(!isOpen);

return (
<div>
<Button color="primary" onClick={toggle} style={{ marginBottom: '1rem' }}>Toggle</Button>
<Collapse isOpen={isOpen} horizontal>
<Alert style={{ width: '500px' }}>
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.
</Alert>
</Collapse>
</div>
);
}

export default Example;
30 changes: 17 additions & 13 deletions src/Collapse.js
Expand Up @@ -6,6 +6,7 @@ import { mapToCssModules, omit, pick, TransitionTimeouts, TransitionPropTypeKeys

const propTypes = {
...Transition.propTypes,
horizontal: PropTypes.bool,
isOpen: PropTypes.bool,
children: PropTypes.oneOfType([
PropTypes.arrayOf(PropTypes.node),
Expand All @@ -24,6 +25,7 @@ const propTypes = {

const defaultProps = {
...Transition.defaultProps,
horizontal: false,
isOpen: false,
appear: false,
enter: true,
Expand All @@ -43,53 +45,54 @@ function getTransitionClass(status) {
return transitionStatusToClassHash[status] || 'collapse';
}

function getHeight(node) {
return node.scrollHeight;
}

class Collapse extends Component {
constructor(props) {
super(props);

this.state = {
height: null
dimension: null
};

['onEntering', 'onEntered', 'onExit', 'onExiting', 'onExited'].forEach((name) => {
this[name] = this[name].bind(this);
});
}

getDimension(node) {
return this.props.horizontal ? node.scrollWidth : node.scrollHeight;
}

onEntering(node, isAppearing) {
this.setState({ height: getHeight(node) });
this.setState({ dimension: this.getDimension(node) });
this.props.onEntering(node, isAppearing);
}

onEntered(node, isAppearing) {
this.setState({ height: null });
this.setState({ dimension: null });
this.props.onEntered(node, isAppearing);
}

onExit(node) {
this.setState({ height: getHeight(node) });
this.setState({ dimension: this.getDimension(node) });
this.props.onExit(node);
}

onExiting(node) {
// getting this variable triggers a reflow
const _unused = node.offsetHeight; // eslint-disable-line no-unused-vars
this.setState({ height: 0 });
const _unused = this.getDimension(node); // eslint-disable-line no-unused-vars
this.setState({ dimension: 0 });
this.props.onExiting(node);
}

onExited(node) {
this.setState({ height: null });
this.setState({ dimension: null });
this.props.onExited(node);
}

render() {
const {
tag: Tag,
horizontal,
isOpen,
className,
navbar,
Expand All @@ -99,7 +102,7 @@ class Collapse extends Component {
...otherProps
} = this.props;

const { height } = this.state;
const { dimension } = this.state;

const transitionProps = pick(otherProps, TransitionPropTypeKeys);
const childProps = omit(otherProps, TransitionPropTypeKeys);
Expand All @@ -117,10 +120,11 @@ class Collapse extends Component {
let collapseClass = getTransitionClass(status);
const classes = mapToCssModules(classNames(
className,
horizontal && 'collapse-horizontal',
collapseClass,
navbar && 'navbar-collapse'
), cssModule);
const style = height === null ? null : { height };
const style = dimension === null ? null : { [horizontal ? 'width' : 'height']: dimension };
return (
<Tag
{...childProps}
Expand Down
13 changes: 9 additions & 4 deletions src/__tests__/Collapse.spec.js
Expand Up @@ -38,6 +38,11 @@ describe('Collapse', () => {
expect(wrapper.find('div').hasClass('collapse')).toEqual(true);
});

it('should render with class "collapse-horizontal" if it has prop horizontal', () => {
wrapper = mount(<Collapse horizontal />);
expect(wrapper.find('div').hasClass('collapse-horizontal')).toEqual(true);
});

it('should render with class "navbar-collapse" if it has prop navbar', () => {
wrapper = mount(<Collapse navbar />);
expect(wrapper.find('div').hasClass('navbar-collapse')).toEqual(true);
Expand All @@ -50,12 +55,12 @@ describe('Collapse', () => {

it('should set height to null when isOpen is true', () => {
wrapper = shallow(<Collapse isOpen />);
expect(wrapper.state('height')).toBe(null);
expect(wrapper.state('dimension')).toBe(null);
});

it('should not set height when isOpen is false', () => {
wrapper = shallow(<Collapse isOpen={false} />);
expect(wrapper.state('height')).toBe(null);
expect(wrapper.state('dimension')).toBe(null);
});

it('should forward all styles', () => {
Expand Down Expand Up @@ -111,15 +116,15 @@ describe('Collapse', () => {
isOpen = true;
wrapper = mount(<Collapse isOpen={isOpen} />);
toggle();
expect(wrapper.state('height')).toBe(0);
expect(wrapper.state('dimension')).toBe(0);
wrapper.unmount();
});

it('should remove inline style when isOpen change to true after transition', () => {
wrapper = mount(<Collapse isOpen={isOpen} />);
toggle();
jest.runTimersToTime(380);
expect(wrapper.state('height')).toBe(null);
expect(wrapper.state('dimension')).toBe(null);
wrapper.unmount();
});
});
1 change: 1 addition & 0 deletions types/lib/Collapse.d.ts
Expand Up @@ -6,6 +6,7 @@ export interface CollapseProps extends React.HTMLAttributes<HTMLElement> {
isOpen?: boolean;
cssModule?: CSSModule;
tag?: React.ElementType;
horizontal?: boolean;
navbar?: boolean;
delay?: {
show: number;
Expand Down

0 comments on commit f780187

Please sign in to comment.