UNPKG

@douyinfe/semi-ui

Version:

A modern, comprehensive, flexible design system and UI library. Connect DesignOps & DevOps. Quickly build beautiful React apps. Maintained by Douyin-fe team.

178 lines 6.35 kB
import _pick from "lodash/pick"; import _isEqual from "lodash/isEqual"; import React from 'react'; import CollapsibleFoundation from '@douyinfe/semi-foundation/lib/es/collapsible/foundation'; import BaseComponent from "../_base/baseComponent"; import PropTypes from "prop-types"; import cls from "classnames"; import { cssClasses } from '@douyinfe/semi-foundation/lib/es/collapsible/constants'; import '@douyinfe/semi-foundation/lib/es/collapsible/collapsible.css'; import { getDefaultPropsFromGlobalConfig } from "../_utils"; class Collapsible extends BaseComponent { constructor(props) { super(props); this.domRef = /*#__PURE__*/React.createRef(); this.hasBeenRendered = false; this.handleResize = entryList => { const entry = entryList[0]; if (entry) { const entryInfo = Collapsible.getEntryInfo(entry); this.foundation.updateDOMHeight(entryInfo.height); this.foundation.updateDOMInRenderTree(entryInfo.isShown); } }; this.isChildrenInRenderTree = () => { if (this.domRef.current) { return this.domRef.current.offsetHeight > 0; } return false; }; this.state = { domInRenderTree: false, domHeight: 0, visible: this.props.isOpen, isTransitioning: false }; this.foundation = new CollapsibleFoundation(this.adapter); } get adapter() { return Object.assign(Object.assign({}, super.adapter), { setDOMInRenderTree: domInRenderTree => { if (this.state.domInRenderTree !== domInRenderTree) { this.setState({ domInRenderTree }); } }, setDOMHeight: domHeight => { if (this.state.domHeight !== domHeight) { this.setState({ domHeight }); } }, setVisible: visible => { if (this.state.visible !== visible) { this.setState({ visible }); } }, setIsTransitioning: isTransitioning => { if (this.state.isTransitioning !== isTransitioning) { this.setState({ isTransitioning }); } } }); } componentDidMount() { super.componentDidMount(); this.resizeObserver = new ResizeObserver(this.handleResize); this.resizeObserver.observe(this.domRef.current); const domInRenderTree = this.isChildrenInRenderTree(); this.foundation.updateDOMInRenderTree(domInRenderTree); if (domInRenderTree) { this.foundation.updateDOMHeight(this.domRef.current.scrollHeight); } } componentDidUpdate(prevProps, prevState, snapshot) { const changedPropKeys = Object.keys(_pick(this.props, ['reCalcKey', "isOpen"])).filter(key => !_isEqual(this.props[key], prevProps[key])); const changedStateKeys = Object.keys(_pick(this.state, ['domInRenderTree'])).filter(key => !_isEqual(this.state[key], prevState[key])); if (changedPropKeys.includes("reCalcKey")) { this.foundation.updateDOMHeight(this.domRef.current.scrollHeight); } if (changedStateKeys.includes("domInRenderTree") && this.state.domInRenderTree) { this.foundation.updateDOMHeight(this.domRef.current.scrollHeight); } if (changedPropKeys.includes("isOpen")) { if (this.props.isOpen || !this.props.motion) { this.foundation.updateVisible(this.props.isOpen); } } if (this.props.motion && prevProps.isOpen !== this.props.isOpen) { this.foundation.updateIsTransitioning(true); } } componentWillUnmount() { super.componentWillUnmount(); this.resizeObserver.disconnect(); } render() { const wrapperStyle = Object.assign({ overflow: 'hidden', height: this.props.isOpen ? this.state.domHeight : this.props.collapseHeight, opacity: this.props.isOpen || !this.props.fade || this.props.collapseHeight !== 0 ? 1 : 0, transitionDuration: `${this.props.motion && this.state.isTransitioning ? this.props.duration : 0}ms` }, this.props.style); const wrapperCls = cls(`${cssClasses.PREFIX}-wrapper`, { [`${cssClasses.PREFIX}-transition`]: this.props.motion && this.state.isTransitioning }, this.props.className); const shouldRender = this.props.keepDOM && (this.props.lazyRender ? this.hasBeenRendered : true) || this.props.collapseHeight !== 0 || this.state.visible || this.props.isOpen; if (shouldRender && !this.hasBeenRendered) { this.hasBeenRendered = true; } return /*#__PURE__*/React.createElement("div", Object.assign({ className: wrapperCls, style: wrapperStyle, onTransitionEnd: () => { var _a, _b; if (!this.props.isOpen) { this.foundation.updateVisible(false); } this.foundation.updateIsTransitioning(false); (_b = (_a = this.props).onMotionEnd) === null || _b === void 0 ? void 0 : _b.call(_a); } }, this.getDataAttr(this.props)), /*#__PURE__*/React.createElement("div", { "x-semi-prop": "children", ref: this.domRef, style: { overflow: 'hidden' }, id: this.props.id }, shouldRender && this.props.children)); } } Collapsible.__SemiComponentName__ = "Collapsible"; Collapsible.defaultProps = getDefaultPropsFromGlobalConfig(Collapsible.__SemiComponentName__, { isOpen: false, duration: 250, motion: true, keepDOM: false, lazyRender: false, collapseHeight: 0, fade: false }); Collapsible.getEntryInfo = entry => { //judge whether parent or self display none let inRenderTree; if (entry.borderBoxSize) { inRenderTree = !(entry.borderBoxSize[0].blockSize === 0 && entry.borderBoxSize[0].inlineSize === 0); } else { inRenderTree = !(entry.contentRect.height === 0 && entry.contentRect.width === 0); } let height = 0; if (entry.borderBoxSize) { height = Math.ceil(entry.borderBoxSize[0].blockSize); } else { const target = entry.target; height = target.clientHeight; } return { isShown: inRenderTree, height }; }; Collapsible.propTypes = { motion: PropTypes.bool, children: PropTypes.node, isOpen: PropTypes.bool, duration: PropTypes.number, keepDOM: PropTypes.bool, collapseHeight: PropTypes.number, style: PropTypes.object, className: PropTypes.string, reCalcKey: PropTypes.oneOfType([PropTypes.string, PropTypes.number]) }; export default Collapsible;