import type { ReactNode, ComponentProps } from 'react'; import React, { useEffect, useState, createRef } from 'react'; import type { TreeProps } from 'rc-tree'; import Tree from 'rc-tree'; import type { EventDataNode } from 'rc-tree/lib/interface'; import type { CSSMotionProps, MotionEventHandler, MotionEndEventHandler } from 'rc-motion'; import './Tree.less'; const FileOutlined = ; const FolderOpenOutlined = ; const FolderOutlined = ; const MinusSquareOutlined = ; const PlusSquareOutlined = ; function getTreeFromList(nodes: ReactNode, prefix = '') { const data: TreeProps['treeData'] = []; [].concat(nodes).forEach((node, i) => { const key = `${prefix ? `${prefix}-` : ''}${i}`; switch (node.type) { case 'ul': const parent = data[data.length - 1]?.children || data; const ulLeafs = getTreeFromList(node.props.children || [], key); parent.push(...ulLeafs); break; case 'li': const liLeafs = getTreeFromList(node.props.children, key); data.push({ title: [].concat(node.props.children).filter(child => child.type !== 'ul'), key, children: liLeafs, isLeaf: !liLeafs.length, }); break; default: } }); return data; } const useListToTree = (nodes: ReactNode) => { const [tree, setTree] = useState(getTreeFromList(nodes)); useEffect(() => { setTree(getTreeFromList(nodes)); }, [nodes]); return tree; }; const getIcon = (props) => { const { isLeaf, expanded } = props; if (isLeaf) { return FileOutlined; } return expanded ? FolderOpenOutlined : FolderOutlined; } const renderSwitcherIcon = (props) => { const { isLeaf, expanded } = props; if (isLeaf) { return ; } return expanded ? ( {MinusSquareOutlined} ) : ( {PlusSquareOutlined} ); } // ================== Collapse Motion ================== const getCollapsedHeight: MotionEventHandler = () => ({ height: 0, opacity: 0 }); const getRealHeight: MotionEventHandler = node => ({ height: node.scrollHeight, opacity: 1 }); const getCurrentHeight: MotionEventHandler = node => ({ height: node.offsetHeight }); const skipOpacityTransition: MotionEndEventHandler = (_, event) => (event as TransitionEvent).propertyName === 'height'; const collapseMotion: CSSMotionProps = { motionName: 'ant-motion-collapse', onAppearStart: getCollapsedHeight, onEnterStart: getCollapsedHeight, onAppearActive: getRealHeight, onEnterActive: getRealHeight, onLeaveStart: getCurrentHeight, onLeaveActive: getCollapsedHeight, onAppearEnd: skipOpacityTransition, onEnterEnd: skipOpacityTransition, onLeaveEnd: skipOpacityTransition, motionDeadline: 500, }; export default (props: ComponentProps<'div'>) => { const data = useListToTree(props.children); const treeRef = createRef(); const onClick = (event: React.MouseEvent, node: EventDataNode) =>{ const { isLeaf } = node; if (isLeaf || event.shiftKey || event.metaKey || event.ctrlKey) { return; } treeRef.current!.onNodeExpand(event as any, node); }; return ( ', children: data }]} defaultExpandAll switcherIcon={renderSwitcherIcon} /> ); };