lucid-ui
Version:
A UI component library from AppNexus.
133 lines (132 loc) • 4.64 kB
JavaScript
import _ from 'lodash';
import React from 'react';
import PropTypes from 'react-peek/prop-types';
import { Motion, spring } from 'react-motion';
import { QUICK_SLIDE_MOTION } from '../../constants/motion-spring';
import { lucidClassNames } from '../../util/style-helpers';
import { omitProps } from '../../util/component-types';
const cx = lucidClassNames.bind('&-Collapsible');
const { any, bool, node, number, string, func } = PropTypes;
class Collapsible extends React.Component {
constructor() {
super(...arguments);
this.rootRef = React.createRef();
this.isAnimated = false;
this.delayTimer = null;
this._isMounted = false;
this.state = {
maxHeight: 0,
};
}
UNSAFE_componentWillMount() {
this._isMounted = false;
this.isAnimated = false;
this.delayTimer = null;
}
componentDidMount() {
this._isMounted = true;
_.delay(() => {
// const maxHeight = _.get(this, 'rootRef.current.scrollHeight');
if (this._isMounted) {
this.setState({
maxHeight: _.get(this, 'rootRef.current.scrollHeight'),
});
}
this.isAnimated = this.props.isAnimated;
}, 32);
}
componentDidUpdate() {
this.isAnimated = false;
this.delayTimer = _.delay(() => {
if (this.props.isExpanded) {
const maxHeight = _.get(this, 'rootRef.current.scrollHeight');
if (maxHeight !== this.state.maxHeight) {
if (this._isMounted) {
this.setState({
maxHeight,
});
}
}
}
this.isAnimated = this.props.isAnimated;
}, 32);
}
componentWillUnmount() {
this.delayTimer && clearTimeout(this.delayTimer);
}
render() {
const { children, className, isExpanded, isMountControlled, mountControlThreshold, rootType, onRest, ...passThroughs } = this.props;
const { maxHeight } = this.state;
return (React.createElement(Motion, { style: this.isAnimated
? {
height: isExpanded
? spring(maxHeight, QUICK_SLIDE_MOTION)
: spring(0, QUICK_SLIDE_MOTION),
}
: { height: isExpanded ? maxHeight : 0 }, onRest: onRest }, (tween) => React.createElement(rootType, {
...omitProps(passThroughs, undefined, Object.keys(Collapsible.propTypes)),
ref: this.rootRef,
className: cx('&', className),
style: {
height: tween.height !== maxHeight
? tween.height < 0
? 0
: tween.height
: null,
overflow: 'hidden',
padding: 0,
...passThroughs.style,
},
}, [
React.createElement("div", { key: 'content', className: cx('&-content'), style: { margin: 0 } }, isMountControlled && !isExpanded
? _.isNull(maxHeight) ||
Math.abs(tween.height) > mountControlThreshold
? children
: null
: children),
])));
}
}
Collapsible.displayName = 'Collapsible';
Collapsible.peek = {
description: `
This is a simple container that can render content as expanded or
collapsed.
`,
categories: ['utility'],
};
// static _isPrivate = true;
Collapsible.propTypes = {
children: node `
Expandable content.
`,
className: string `
Appended to the component-specific class names set on the root element.
`,
isExpanded: bool `
Indicates that the component is in the "expanded" state when true and in
the "unexpanded" state when false.
`,
isAnimated: bool `
Show an animated transition for alternating values of \`isExpanded\`.
`,
isMountControlled: bool `
If true, do not render children when fully collapsed.
`,
mountControlThreshold: number `
If \`isMountControlled\` is true, this value sets is the minimum height
the container needs to reach to not render any children.
`,
onRest: func `Optional. The callback that fires when the animation comes to a rest.`,
rootType: any `
Pass in a custom root element type.
`,
};
Collapsible.defaultProps = {
isExpanded: true,
isAnimated: true,
isMountControlled: true,
mountControlThreshold: 4,
rootType: 'div',
};
export default Collapsible;