UNPKG

lucid-ui

Version:

A UI component library from Xandr.

150 lines 4.97 kB
import _ from 'lodash'; import React from 'react'; import PropTypes from 'prop-types'; import { Motion, spring } from 'react-motion'; import { QUICK_SLIDE_MOTION } from '../../constants/motion-spring'; import { lucidClassNames } from '../../util/style-helpers'; const cx = lucidClassNames.bind('&-Collapsible'); const { any, bool, node, number, string, func } = PropTypes; /** TODO: Remove this constant when the component is converted to a functional component */ const nonPassthroughs = [ 'children', 'className', 'isExpanded', 'isAnimated', 'isMountControlled', 'mountControlThreshold', 'onRest', 'rootType', ]; 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, style, ...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, { ..._.omit(passThroughs, nonPassthroughs), ref: this.rootRef, className: cx('&', className), style: { height: tween.height !== maxHeight ? tween.height < 0 ? 0 : tween.height : null, overflow: 'hidden', padding: 0, ...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 = { /** Expandable content. */ children: node, /** Appended to the component-specific class names set on the root element. */ className: string, /** Indicates that the component is in the "expanded" state when true and in the "unexpanded" state when false. */ isExpanded: bool, /** Show an animated transition for alternating values of \`isExpanded\`. */ isAnimated: bool, /** If true, do not render children when fully collapsed. */ isMountControlled: bool, /** If \`isMountControlled\` is true, this value sets is the minimum height the container needs to reach to not render any children. */ mountControlThreshold: number, /** Optional. The callback that fires when the animation comes to a rest. */ onRest: func, /** Pass in a custom root element type. */ rootType: any, }; Collapsible.defaultProps = { isExpanded: true, isAnimated: true, isMountControlled: true, mountControlThreshold: 4, rootType: 'div', }; export default Collapsible; //# sourceMappingURL=Collapsible.js.map