@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
JavaScript
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;