UNPKG

@hackplan/polaris

Version:

Shopify’s product component library

95 lines (94 loc) 3.51 kB
import React from 'react'; import { addEventListener, removeEventListener, } from '@shopify/javascript-utilities/events'; import { read } from '@shopify/javascript-utilities/fastdom'; import { classNames } from '../../utilities/css'; import { withAppProvider } from '../AppProvider'; import styles from './Collapsible.scss'; export class Collapsible extends React.Component { constructor() { super(...arguments); this.state = { height: null, animationState: 'idle', }; this.node = null; this.heightNode = null; this.bindNode = (node) => { this.node = node; }; this.bindHeightNode = (node) => { this.heightNode = node; }; this.handleTransitionEnd = (event) => { const { target } = event; if (target === this.node) { this.setState({ animationState: 'idle', height: null }); } }; } componentWillReceiveProps({ open: willOpen }) { const { open } = this.props; if (open !== willOpen) { this.setState({ animationState: 'measuring' }); } } componentDidUpdate({ open: wasOpen }) { const { animationState } = this.state; read(() => { switch (animationState) { case 'idle': break; case 'measuring': this.setState({ animationState: wasOpen ? 'closingStart' : 'openingStart', height: wasOpen && this.heightNode ? this.heightNode.scrollHeight : 0, }); break; case 'closingStart': this.setState({ animationState: 'closing', height: 0, }); break; case 'openingStart': this.setState({ animationState: 'opening', height: this.heightNode ? this.heightNode.scrollHeight : 0, }); } }); } componentDidMount() { if (this.node == null) { return; } addEventListener(this.node, 'transitionend', this.handleTransitionEnd); } componentWillUnmount() { if (this.node == null) { return; } removeEventListener(this.node, 'transitionend', this.handleTransitionEnd); } render() { const { id, open, children } = this.props; const { animationState, height } = this.state; const animating = animationState !== 'idle'; const wrapperClassName = classNames(styles.Collapsible, open && styles.open, animating && styles.animating, !animating && open && styles.fullyOpen); const displayHeight = collapsibleHeight(open, animationState, height); const content = animating || open ? children : null; return (<div id={id} aria-hidden={!open} style={{ height: displayHeight }} className={wrapperClassName} ref={this.bindNode}> <div ref={this.bindHeightNode}>{content}</div> </div>); } } function collapsibleHeight(open, animationState, height) { if (animationState === 'idle' && open) { return open ? 'auto' : undefined; } if (animationState === 'measuring') { return open ? undefined : 'auto'; } return `${height || 0}px`; } export default withAppProvider()(Collapsible);