123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215 |
- import React from 'react';
- import type {
- CollapsibleAdapter,
- CollapsibleFoundationProps,
- CollapsibleFoundationState
- } from "@douyinfe/semi-foundation/collapsible/foundation";
- import BaseComponent from "../_base/baseComponent";
- import PropTypes from "prop-types";
- import cls from "classnames";
- import {cssClasses} from "@douyinfe/semi-foundation/collapsible/constants";
- import {isEqual} from "lodash";
- import CollapsibleFoundation from "@douyinfe/semi-foundation/collapsible/foundation";
- import "@douyinfe/semi-foundation/collapsible/collapsible.scss";
- interface CollapsibleProps extends CollapsibleFoundationProps {
- motion?: boolean;
- children?: React.ReactNode;
- isOpen?: boolean;
- duration?: number;
- keepDOM?: boolean;
- className?: string;
- style?: React.CSSProperties;
- collapseHeight?: number;
- reCalcKey?: number | string;
- id?: string,
- }
- interface CollapsibleState extends CollapsibleFoundationState {
- domInRenderTree: boolean
- domHeight: number
- visible: boolean
- isTransitioning: boolean
- }
- class Collapsible extends BaseComponent<CollapsibleProps, CollapsibleState> {
- static defaultProps = {
- isOpen: false,
- duration: 250,
- motion: true,
- keepDOM: false,
- collapseHeight: 0,
- fade:false
- };
- private domRef = React.createRef<HTMLDivElement>();
- private resizeObserver: ResizeObserver | null;
- public foundation: CollapsibleFoundation;
- constructor(props: CollapsibleProps) {
- super(props);
- this.state = {
- domInRenderTree: false,
- domHeight: 0,
- visible: this.props.isOpen,
- isTransitioning:false
- }
- this.foundation = new CollapsibleFoundation(this.adapter);
- }
- get adapter(): CollapsibleAdapter<CollapsibleProps, CollapsibleState> {
- return {
- ...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})
- }
- },
- getState(key: string): any {
- return this.state[key];
- },
- getProp(key: string): any {
- return this.props[key];
- },
- getStates(): CollapsibleState {
- return this.state;
- },
- getProps(): CollapsibleProps {
- return this.props;
- }
- };
- }
- 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: Readonly<CollapsibleProps>, prevState: Readonly<CollapsibleState>, snapshot?: any) {
- const changedPropKeys = Object.keys(this.props).filter(key => !isEqual(this.props[key], prevProps[key]));
- const changedStateKeys = Object.keys(this.state).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.setState({isTransitioning:true})
- }
- }
- componentWillUnmount() {
- super.componentWillUnmount()
- this.resizeObserver.disconnect();
- }
- handleResize = (entryList: ResizeObserverEntry[]) => {
- const entry = entryList[0];
- if (entry) {
- const entryInfo = Collapsible.getEntryInfo(entry);
- this.foundation.updateDOMHeight(entryInfo.height);
- this.foundation.updateDOMInRenderTree(entryInfo.isShown);
- }
- }
- static getEntryInfo = (entry: ResizeObserverEntry) => {
- //judge whether parent or self display none
- let inRenderTree: boolean;
- 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 as HTMLElement;
- height = target.clientHeight;
- }
- return {
- isShown: inRenderTree, height
- }
- }
- isChildrenInRenderTree = () => {
- if (this.domRef.current) {
- return this.domRef.current.offsetHeight > 0
- } else {
- return false
- }
- }
- render() {
- const wrapperStyle: React.CSSProperties = {
- overflow: 'hidden',
- height: this.props.isOpen ? (this.props.collapseHeight || this.state.domHeight) : 0,
- opacity: (this.props.isOpen || !this.props.fade) ? 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);
- return <div className={wrapperCls} style={wrapperStyle} onTransitionEnd={() => {
- if (!this.props.isOpen) {
- this.foundation.updateVisible(false)
- }
- this.setState({isTransitioning:false})
- }}>
- <div
- x-semi-prop="children"
- ref={this.domRef}
- style={{overflow: 'hidden'}}
- id={this.props.id}
- >
- {
- (this.props.keepDOM || this.state.visible || this.props.isOpen) && this.props.children
- }
- </div>
- </div>
- }
- }
- 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;
|