UNPKG

lucid-ui

Version:

A UI component library from AppNexus.

133 lines (132 loc) 4.64 kB
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;