123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165 |
- // @ts-ignore currently no type definition for @douyinfe/semi-animation-react
- import { Transition } from '@douyinfe/semi-animation-react';
- import PropTypes from 'prop-types';
- import cls from 'classnames';
- import React, { useRef, useState, useCallback, useMemo } from 'react';
- import { cssClasses } from '@douyinfe/semi-foundation/collapsible/constants';
- import { Motion } from '../_base/base';
- import getMotionObjFromProps from '@douyinfe/semi-foundation/utils/getMotionObjFromProps';
- const ease = 'cubicBezier(.25,.1,.25,1)';
- export interface CollapsibleProps {
- motion?: Motion;
- children?: React.ReactNode;
- isOpen?: boolean;
- duration?: number;
- keepDOM?: boolean;
- className?: string;
- style?: React.CSSProperties;
- collapseHeight?: number;
- reCalcKey?: number | string;
- id?:string,
- }
- const Collapsible = (props: CollapsibleProps) => {
- const {
- motion,
- children,
- isOpen,
- duration,
- keepDOM,
- collapseHeight,
- style,
- className,
- reCalcKey,
- id
- } = props;
- const ref = useRef(null);
- const [maxHeight, setMaxHeight] = useState(0);
- const [open, setOpen] = useState(props.isOpen);
- const [isFirst, setIsFirst] = useState(true);
- const [transitionImmediate, setTransitionImmediate] = useState(open && isFirst);
- const [left, setLeft] = useState(!props.isOpen);
- if (isOpen !== open) {
- setOpen(isOpen);
- if (isFirst) {
- setIsFirst(false);
- setTransitionImmediate(false);
- }
- isOpen && setLeft(!isOpen);
- }
- const setHeight = useCallback(node => {
- const currHeight = node && node.scrollHeight;
- if (currHeight && maxHeight !== currHeight) {
- setMaxHeight(currHeight);
- }
- // eslint-disable-next-line react-hooks/exhaustive-deps
- }, [left, reCalcKey, maxHeight]);
- const resetHeight = () => {
- ref.current.style.maxHeight = 'none';
- };
- const formatStyle = ({ maxHeight: maxHeightInTransitionStyle }: any) => ({ maxHeight: maxHeightInTransitionStyle });
- const shouldKeepDOM = () => keepDOM || collapseHeight !== 0;
- const defaultMaxHeight = useMemo(() => {
- return isOpen || !shouldKeepDOM() && !motion ? 'none' : collapseHeight;
- }, [collapseHeight, motion, isOpen, shouldKeepDOM]);
- const renderChildren = (transitionStyle: Record<string, any> | null) => {
- const transition =
- transitionStyle && typeof transitionStyle === 'object' ?
- formatStyle(transitionStyle) :
- {};
- const wrapperstyle = {
- overflow: 'hidden',
- maxHeight: defaultMaxHeight,
- ...style,
- ...transition,
- };
- if (isFirst) {
- wrapperstyle.maxHeight = defaultMaxHeight;
- }
- const wrapperCls = cls(`${cssClasses.PREFIX}-wrapper`, className);
- return (
- <div style={wrapperstyle} className={wrapperCls} ref={ref}>
- <div
- ref={setHeight}
- style={{ overflow: 'hidden' }}
- id={id}
- x-semi-prop="children"
- >
- {children}
- </div>
- </div>
- );
- };
- const didLeave = () => {
- setLeft(true);
- !shouldKeepDOM() && setMaxHeight(collapseHeight);
- };
- const renderContent = () => {
- if (left && !shouldKeepDOM()) {
- return null;
- }
- const mergedMotion = getMotionObjFromProps({
- didEnter: resetHeight,
- didLeave,
- motion,
- });
- return (
- <Transition
- state={isOpen ? 'enter' : 'leave'}
- immediate={transitionImmediate}
- from={{ maxHeight: 0 }}
- enter={{ maxHeight: { val: maxHeight, easing: ease, duration } }}
- leave={{ maxHeight: { val: collapseHeight, easing: ease, duration } }}
- {...mergedMotion}
- >
- {(transitionStyle: Record<string, any>) =>
- renderChildren(motion ? transitionStyle : null)
- }
- </Transition>
- );
- };
- return renderContent();
- };
- Collapsible.propType = {
- motion: PropTypes.oneOfType([PropTypes.bool, PropTypes.func, PropTypes.object]),
- 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
- ]),
- };
- Collapsible.defaultProps = {
- isOpen: false,
- duration: 250,
- motion: true,
- keepDOM: false,
- collapseHeight: 0
- };
- export default Collapsible;
|