Navigation · Tree

Tree

A tree view component presents a hierarchical list.

Demos

How to import

import { Tree } from '@fibrous/semi-ui-react';

Basic Usage

By default, tree is in single select mode and each item is selectable.

  • 亚洲
  • 中国
  • 北京
  • 上海
  • 北美洲
  • Code
    import { Tree } from '@fibrous/semi-ui-react';
    
    export default function TreeDemo() {
        const treeData = [
            {
                label: '亚洲',
                value: 'Asia',
                key: '0',
                children: [
                    {
                        label: '中国',
                        value: 'China',
                        key: '0-0',
                        children: [
                            {
                                label: '北京',
                                value: 'Beijing',
                                key: '0-0-0',
                            },
                            {
                                label: '上海',
                                value: 'Shanghai',
                                key: '0-0-1',
                            },
                        ],
                    },
                ],
            },
            {
                label: '北美洲',
                value: 'North America',
                key: '1',
            },
        ];
        const style = {
            width: 260,
            height: 420,
            border: '1px solid var(--semi-color-border)',
        };
        return <Tree treeData={treeData} defaultExpandAll style={style} />;
    }

    Multi-choice

    You could use multiple to set mode to multi-choice. When all child items are selected, the parent item will be selected.

  • Asia
  • China
  • Beijing
  • Shanghai
  • Chengdu
  • Japan
  • Osaka
  • North America
  • United States
  • Canada
  • Code
    import { Tree } from '@fibrous/semi-ui-react';
    
    export default function TreeDemo() {
        const treeData = [
            {
                label: 'Asia',
                value: 'Asia',
                key: '0',
                children: [
                    {
                        label: 'China',
                        value: 'China',
                        key: '0-0',
                        children: [
                            {
                                label: 'Beijing',
                                value: 'Beijing',
                                key: '0-0-0',
                            },
                            {
                                label: 'Shanghai',
                                value: 'Shanghai',
                                key: '0-0-1',
                            },
                            {
                                label: 'Chengdu',
                                value: 'Chengdu',
                                key: '0-0-2',
                            },
                        ],
                    },
                    {
                        label: 'Japan',
                        value: 'Japan',
                        key: '0-1',
                        children: [
                            {
                                label: 'Osaka',
                                value: 'Osaka',
                                key: '0-1-0',
                            },
                        ],
                    },
                ],
            },
            {
                label: 'North America',
                value: 'North America',
                key: '1',
                children: [
                    {
                        label: 'United States',
                        value: 'United States',
                        key: '1-0',
                    },
                    {
                        label: 'Canada',
                        value: 'Canada',
                        key: '1-1',
                    },
                ],
            },
        ];
        const style = {
            width: 260,
            height: 420,
            border: '1px solid var(--semi-color-border)',
        };
        return <Tree treeData={treeData} multiple defaultExpandAll style={style} />;
    }

    Searchable

    Use filterTreeNode to support search input. By default it searches the label property of the data. You can use treeNodeFilterProp to set another property to search or pass in a function to filterTreeNode to customize search behavior.

    You could also use showFilteredOnly if you prefer to display filtered results only.

    showFilteredOnly

    Code
    import { useState } from 'react';
    import { Tree, Switch } from '@fibrous/semi-ui-react';
    
    const treeData = [
        {
            label: 'Asia',
            value: 'Asia',
            key: '0',
            children: [
                {
                    label: 'China',
                    value: 'China',
                    key: '0-0',
                    children: [
                        {
                            label: 'Beijing',
                            value: 'Beijing',
                            key: '0-0-0',
                        },
                        {
                            label: 'Shanghai',
                            value: 'Shanghai',
                            key: '0-0-1',
                        },
                    ],
                },
                {
                    label: 'Japan',
                    value: 'Japan',
                    key: '0-1',
                    children: [
                        {
                            label: 'Osaka',
                            value: 'Osaka',
                            key: '0-1-0',
                        },
                    ],
                },
            ],
        },
        {
            label: 'North America',
            value: 'North America',
            key: '1',
            children: [
                {
                    label: 'United States',
                    value: 'United States',
                    key: '1-0',
                },
                {
                    label: 'Canada',
                    value: 'Canada',
                    key: '1-1',
                },
            ],
        },
    ];
    
    const style = {
        width: 260,
        height: 420,
        border: '1px solid var(--semi-color-border)',
    };
    
    export default function TreeDemo() {
        const [showFilteredOnly, setShowFilteredOnly] = useState(false);
    
        return (
            <>
                <span id="tree-searchable-filtered-only">showFilteredOnly</span>
                <Switch aria-labelledby="tree-searchable-filtered-only" checked={showFilteredOnly} onChange={setShowFilteredOnly} size="small" />
                <br />
                <Tree treeData={treeData} multiple filterTreeNode showFilteredOnly={showFilteredOnly} style={style} />
            </>
        );
    }

    After setting the filterTreeNode property to enable search, you can customize the rendering method of the search box by setting searchRender. When set to false, the search box can be hidden.

    Search
    Code
    import { Tree, Input } from '@fibrous/semi-ui-react';
    
    export default function TreeDemo() {
        const treeData = [
            {
                label: 'Asia',
                value: 'Asia',
                key: '0',
                children: [
                    {
                        label: 'China',
                        value: 'China',
                        key: '0-0',
                        children: [
                            {
                                label: 'Beijing',
                                value: 'Beijing',
                                key: '0-0-0',
                            },
                            {
                                label: 'Shanghai',
                                value: 'Shanghai',
                                key: '0-0-1',
                            },
                        ],
                    },
                    {
                        label: 'Japan',
                        value: 'Japan',
                        key: '0-1',
                        children: [
                            {
                                label: 'Osaka',
                                value: 'Osaka',
                                key: '0-1-0',
                            },
                        ],
                    },
                ],
            },
            {
                label: 'North America',
                value: 'North America',
                key: '1',
                children: [
                    {
                        label: 'United States',
                        value: 'United States',
                        key: '1-0',
                    },
                    {
                        label: 'Canada',
                        value: 'Canada',
                        key: '1-1',
                    },
                ],
            },
        ];
    
        return <Tree filterTreeNode searchRender={(searchProps) => <Input {...searchProps} prefix="Search" />} treeData={treeData} />;
    }

    Trigger search manually

    Use ref to get tree instance,you can call search method of tree to trigger search manually. Note that you need to set filterTreeNode to enable search at the same time.If the search box is outside the tree, you can hide the search box inside the tree by setting searchRender=false.

    JSON TreeData

    You could use treeDataSimpleJson to pass in treeNodes data in JSON format. In this case, key will be used as key and label, and value will be used as value correspondingly. Return value includes JSON data in selected nodes.

  • Node2
  • Code
    import { Tree } from '@fibrous/semi-ui-react';
    
    export default function TreeDemo() {
        const json = {
            Node1: {
                'Child Node1': '0-0-1',
                'Child Node2': '0-0-2',
            },
            Node2: '0-1',
        };
        const style = {
            width: 260,
            height: 420,
            border: '1px solid var(--semi-color-border)',
        };
        return <Tree treeDataSimpleJson={json} multiple onChange={(e) => console.log('当前所有选中项: ', e)} onSelect={(e) => console.log('当前选项: ', e)} style={style} />;
    }

    BlockNode

    You could use blockNode to set node to display as a row. In this case, styles for hovering and selected state take effects on the entire row. By default, it is set to true. When it is set to false, only label will be highlighted.

  • Asia
  • China
  • Beijing
  • Shanghai

  • Asia
  • China
  • Beijing
  • Shanghai
  • Code
    import { Tree } from '@fibrous/semi-ui-react';
    
    export default function TreeDemo() {
        const treeData = [
            {
                label: 'Asia',
                value: 'Asia',
                key: '0',
                children: [
                    {
                        label: 'China',
                        value: 'China',
                        key: '0-0',
                        children: [
                            {
                                label: 'Beijing',
                                value: 'Beijing',
                                key: '0-0-0',
                            },
                            {
                                label: 'Shanghai',
                                value: 'Shanghai',
                                key: '0-0-1',
                            },
                        ],
                    },
                    {
                        label: 'Japan',
                        value: 'Japan',
                        key: '0-1',
                        children: [
                            {
                                label: 'Osaka',
                                value: 'Osaka',
                                key: '0-1-0',
                            },
                        ],
                    },
                ],
            },
            {
                label: 'North America',
                value: 'North America',
                key: '1',
                children: [
                    {
                        label: 'United States',
                        value: 'United States',
                        key: '1-0',
                    },
                    {
                        label: 'Canada',
                        value: 'Canada',
                        key: '1-1',
                    },
                ],
            },
        ];
        return (
            <div>
                <Tree treeData={treeData} defaultValue="Shanghai" blockNode={false} />
                <br />
                <Tree treeData={treeData} defaultValue="Shanghai" multiple blockNode={false} />
            </div>
        );
    }

    Custom TreeNode Label

    You could pass in ReactNode for label in TreeNodeData to customize label. Pay attention that by default filterTreeNode searches data by label. When label is a ReactNode, it is advised to pass in customized search function for a searchable tree.

    In v>=1.6.0, you can also use renderLabel to pass in a custom render function. The search value is still the corresponding label property in treeData.

    Node content is best kept for display. Buttons and links in a node can be reached with Tab (see Accessibility), but screen readers read them as part of the node's name and arrow-key browsing skips them. Put actions on a node outside the tree, for example in a menu opened by onContextMenu or in a toolbar next to the tree.

    Code
    import { Tree, Tag } from '@fibrous/semi-ui-react';
    
    export default function TreeDemo() {
        // Node content is display-only: focus stays on the tree, so buttons and links inside are keyboard-unreachable
        const tag = (text: string) => <Tag size="small">{text}</Tag>;
    
        const style = {
            display: 'flex',
            justifyContent: 'space-between',
            alignItems: 'center',
        };
    
        const treeDataWithNode = [
            {
                label: (
                    <div style={style}>
                        <span>亚洲</span>
                        {tag('大洲')}
                    </div>
                ),
                value: 'yazhou',
                key: 'yazhou',
                children: [
                    {
                        label: (
                            <div style={style}>
                                <span>中国</span>
                                {tag('国家')}
                            </div>
                        ),
                        value: 'zhongguo',
                        key: 'zhongguo',
                    },
                    {
                        label: (
                            <div style={style}>
                                <span>日本</span>
                                {tag('国家')}
                            </div>
                        ),
                        value: 'riben',
                        key: 'riben',
                    },
                ],
            },
        ];
        const treeStyle = {
            width: 260,
            height: 420,
            border: '1px solid var(--semi-color-border)',
        };
        return <Tree treeData={treeDataWithNode} style={treeStyle} />;
    }

    Ellipsis for long labels. In v>=1.6.0, you can use renderLabel to truncate overlong text with an ellipsis.

    Code
    import type { ReactNode } from 'react';
    import { Tree, Typography } from '@fibrous/semi-ui-react';
    
    export default function TreeDemo() {
        const renderLabel = (label?: ReactNode) => (
            <Typography.Text ellipsis={{ showTooltip: true }} style={{ width: '100%' }}>
                {label}
            </Typography.Text>
        );
        const treeDataWithNode = [
            {
                label: '亚洲亚洲亚洲亚洲亚洲亚洲亚洲亚洲亚洲亚洲亚洲亚洲',
                value: 'yazhou',
                key: 'yazhou',
                children: [
                    {
                        label: '中国中国中国中国中国中国中国中国中国中国中国中国',
                        value: 'zhongguo',
                        key: 'zhongguo',
                    },
                    {
                        label: '日本日本日本日本日本日本日本日本日本日本日本日本',
                        value: 'riben',
                        key: 'riben',
                    },
                ],
            },
        ];
        const treeStyle = {
            width: 260,
            height: 420,
            border: '1px solid var(--semi-color-border)',
        };
        return <Tree treeData={treeDataWithNode} renderLabel={renderLabel} style={treeStyle} />;
    }

    Custom Icon

    You could use icon to add customized icon.

    Code
    import { IconMapPin } from '@fibrous/semi-icons-react';
    import { Tree } from '@fibrous/semi-ui-react';
    
    export default function TreeDemo() {
        const treeData = [
            {
                label: 'Asia',
                value: 'Asia',
                key: '0',
                icon: <IconMapPin style={{ color: 'var(--semi-color-text-2)' }} />,
                children: [
                    {
                        label: 'China',
                        value: 'China',
                        key: '0-0',
                        icon: <IconMapPin style={{ color: 'var(--semi-color-text-2)' }} />,
                    },
                    {
                        label: 'Japan',
                        value: 'Japan',
                        key: '0-1',
                        icon: <IconMapPin style={{ color: 'var(--semi-color-text-2)' }} />,
                    },
                ],
            },
        ];
        const style = {
            width: 260,
            height: 420,
            border: '1px solid var(--semi-color-border)',
        };
        return <Tree treeData={treeData} style={style} />;
    }

    Directory

    You could use directory to display tree as a directory with default icons. Also, the icon could be overwritten by custom Icon.

    Code
    import { Tree } from '@fibrous/semi-ui-react';
    
    export default function TreeDemo() {
        const treeData = [
            {
                label: 'Asia',
                value: 'Asia',
                key: '0',
                children: [
                    {
                        label: 'China',
                        value: 'China',
                        key: '0-0',
                        children: [
                            {
                                label: 'Beijing',
                                value: 'Beijing',
                                key: '0-0-0',
                            },
                            {
                                label: 'Shanghai',
                                value: 'Shanghai',
                                key: '0-0-1',
                            },
                        ],
                    },
                    {
                        label: 'Japan',
                        value: 'Japan',
                        key: '0-1',
                        children: [
                            {
                                label: 'Osaka',
                                value: 'Osaka',
                                key: '0-1-0',
                            },
                        ],
                    },
                ],
            },
            {
                label: 'North America',
                value: 'North America',
                key: '1',
                children: [
                    {
                        label: 'United States',
                        value: 'United States',
                        key: '1-0',
                    },
                    {
                        label: 'Canada',
                        value: 'Canada',
                        key: '1-1',
                    },
                ],
            },
        ];
        const style = {
            width: 260,
            height: 420,
            border: '1px solid var(--semi-color-border)',
        };
        return <Tree treeData={treeData} directory style={style} />;
    }

    Disabled

    You can use disableStrictly to set whether to enable strict disabling. After enabling strict disabling, when the node is disabled, the selected state cannot be changed through the relationship between the child or the parent.

  • Asia
  • China
  • Beijing
  • Shanghai
  • Code
    import { Tree } from '@fibrous/semi-ui-react';
    
    export default function TreeDemo() {
        const treeData = [
            {
                label: 'Asia',
                value: 'Asia',
                key: '0',
                children: [
                    {
                        label: 'China',
                        value: 'China',
                        key: '0-0',
                        children: [
                            {
                                label: 'Beijing',
                                value: 'Beijing',
                                key: '0-0-0',
                                disabled: true,
                            },
                            {
                                label: 'Shanghai',
                                value: 'Shanghai',
                                key: '0-0-1',
                                disabled: true,
                            },
                        ],
                    },
                    {
                        label: 'Japan',
                        value: 'Japan',
                        key: '0-1',
                        children: [
                            {
                                label: 'Osaka',
                                value: 'Osaka',
                                key: '0-1-0',
                            },
                        ],
                    },
                ],
            },
            {
                label: 'North America',
                value: 'North America',
                key: '1',
                children: [
                    {
                        label: 'United States',
                        value: 'United States',
                        key: '1-0',
                    },
                    {
                        label: 'Canada',
                        value: 'Canada',
                        key: '1-1',
                    },
                ],
            },
        ];
        const style = {
            width: 260,
            height: 420,
            border: '1px solid var(--semi-color-border)',
        };
        return <Tree treeData={treeData} defaultValue="Shanghai" multiple disableStrictly style={style} />;
    }

    Checked RelationShip

    Version: >= 2.5.0

    When multiple selections are made, checkRelation can be used to set the type of node selection relationship, optional: 'related' (default), 'unRelated'. When the selection relationship is 'unRelated', it means that selections between nodes do not affect each other.

  • Asia
  • China
  • Beijing
  • Shanghai
  • Chengdu
  • Japan
  • Osaka
  • North America
  • United States
  • Canada
  • Code
    import { Tree } from '@fibrous/semi-ui-react';
    
    export default function TreeDemo() {
        const treeData = [
            {
                label: 'Asia',
                value: 'Asia',
                key: '0',
                children: [
                    {
                        label: 'China',
                        value: 'China',
                        key: '0-0',
                        children: [
                            {
                                label: 'Beijing',
                                value: 'Beijing',
                                key: '0-0-0',
                            },
                            {
                                label: 'Shanghai',
                                value: 'Shanghai',
                                key: '0-0-1',
                            },
                            {
                                label: 'Chengdu',
                                value: 'Chengdu',
                                key: '0-0-2',
                            },
                        ],
                    },
                    {
                        label: 'Japan',
                        value: 'Japan',
                        key: '0-1',
                        children: [
                            {
                                label: 'Osaka',
                                value: 'Osaka',
                                key: '0-1-0',
                            },
                        ],
                    },
                ],
            },
            {
                label: 'North America',
                value: 'North America',
                key: '1',
                children: [
                    {
                        label: 'United States',
                        value: 'United States',
                        key: '1-0',
                    },
                    {
                        label: 'Canada',
                        value: 'Canada',
                        key: '1-1',
                    },
                ],
            },
        ];
        const style = {
            width: 260,
            height: 420,
            border: '1px solid var(--semi-color-border)',
        };
        return <Tree treeData={treeData} multiple checkRelation="unRelated" defaultExpandAll style={style} />;
    }

    Default Expand All

    Both defaultExpandAll and expandAll can set the default expanded/collapsed state of Tree. The difference between the two is that defaultExpandAll only takes effect at initialization, while expandAll will not only take effect at initialization, but also when the data (treeData/treeDataSimpleJson) is dynamically updated, expandAll will still take effect.

    Among them, expandAll is supported starting from 1.30.0.

    In the demo below, after clicking the button to update TreeData, defaultExpandAll becomes invalid, and expandAll still takes effect.

    defaultExpandAll
  • Node1
  • Child Node1
  • Child Node2
  • Node2
  • expandAll
  • Node1
  • Child Node1
  • Child Node2
  • Node2
  • Code
    import { useState } from 'react';
    import { Tree, Button, type TreeProps } from '@fibrous/semi-ui-react';
    
    export default function TreeDemo() {
        const json = {
            Node1: {
                'Child Node1': '0-0-1',
                'Child Node2': '0-0-2',
            },
            Node2: '0-1',
        };
        const json2 = {
            Node3: {
                'Child Node1': '0-0-1',
                'Child Node2': '0-0-2',
                'Child Node3': '0-0-3',
                'Child Node4': '0-0-4',
            },
            Node2: '0-1',
        };
        const style = {
            marginRight: 20,
            width: 260,
            height: 420,
            border: '1px solid var(--semi-color-border)',
        };
        const [tree, setTree] = useState<TreeProps['treeDataSimpleJson']>(json);
        const handleClick = () => {
            setTree(json2);
        };
        return (
            <>
                <Button onClick={handleClick} style={{ marginBottom: 10 }}>
                    点击更新 TreeData
                </Button>
                <div style={{ display: 'flex' }}>
                    <div>
                        <span>defaultExpandAll</span>
                        <Tree defaultExpandAll treeDataSimpleJson={tree} style={style} />
                    </div>
                    <div>
                        <span>expandAll</span>
                        <Tree expandAll treeDataSimpleJson={tree} style={style} />
                    </div>
                </div>
            </>
        );
    }

    When expandedKeys is passed in, it is the expanded controlled component, which can be used with onExpand. When the expansion is controlled, if you enable filterTreeNode and search, the node will not be automatically expanded. At this time, the expansion of the node is completely controlled by expandedKeys. You can use the input parameter filteredExpandedKeys (version: >= 2.38.0) of onSearch to realize the search expansion effect when the expansion is controlled.

    Controlled Component

    You can use value along with onChange property if you want to use Tree as a controlled component.

  • 亚洲
  • 中国
  • 北京
  • 上海
  • 北美洲
  • Code
    import { useState } from 'react';
    import { Tree } from '@fibrous/semi-ui-react';
    
    const treeData = [
        {
            label: '亚洲',
            value: 'Asia',
            key: '0',
            children: [
                {
                    label: '中国',
                    value: 'China',
                    key: '0-0',
                    children: [
                        {
                            label: '北京',
                            value: 'Beijing',
                            key: '0-0-0',
                        },
                        {
                            label: '上海',
                            value: 'Shanghai',
                            key: '0-0-1',
                        },
                    ],
                },
            ],
        },
        {
            label: '北美洲',
            value: 'North America',
            key: '1',
        },
    ];
    
    const style = {
        width: 260,
        height: 420,
        border: '1px solid var(--semi-color-border)',
    };
    
    export default function TreeDemo() {
        const [value, setValue] = useState('Shanghai');
    
        return (
            <Tree
                treeData={treeData}
                value={value}
                onChange={(next) => {
                    if (typeof next === 'string') {
                        setValue(next);
                    }
                }}
                style={style}
            />
        );
    }

    Auto Expand Parent

    In the case of controlled expansion, when autoExpandParent is turned on, if you want to collapse the parent element, you need to collapse all its child elements. By default, autoExpandParent is false, that is, the collapse of the parent element is not affected by the child element.

    需要先将“中国”节点收起后,才能够收起“亚洲”节点

  • 亚洲
  • 中国
  • 北京
  • 上海
  • 北美洲
  • Code
    import { useState } from 'react';
    import { Tree } from '@fibrous/semi-ui-react';
    
    const treeData = [
        {
            label: '亚洲',
            value: 'Asia',
            key: '0',
            children: [
                {
                    label: '中国',
                    value: 'China',
                    key: '0-0',
                    children: [
                        {
                            label: '北京',
                            value: 'Beijing',
                            key: '0-0-0',
                        },
                        {
                            label: '上海',
                            value: 'Shanghai',
                            key: '0-0-1',
                        },
                    ],
                },
            ],
        },
        {
            label: '北美洲',
            value: 'North America',
            key: '1',
        },
    ];
    
    const style = {
        width: 260,
        height: 420,
        border: '1px solid var(--semi-color-border)',
    };
    
    export default function TreeDemo() {
        const [expandedKeys, setExpandedKeys] = useState(['0', '0-0']);
    
        return (
            <>
                <div>需要先将“中国”节点收起后,才能够收起“亚洲”节点</div>
                <br />
                <Tree autoExpandParent treeData={treeData} onExpand={setExpandedKeys} expandedKeys={expandedKeys} style={style} />
            </>
        );
    }

    Custom expansion icon

    The expansion Icon can be customized through expandIcon. Supports passing in ReactNode or functions. expandIcon is supported since 2.75.0.

    expandIcon: ReactNode | ((props: {
        onClick: (e: MouseEvent) => void;
        className: string;
        expanded: boolean;
    }))

    Examples are as follows:

    expandIcon 是 ReactNode

  • 亚洲
  • 日本
  • 北美洲

  • expandIcon 是函数

  • 亚洲
  • 日本
  • 北美洲
  • Code
    import { useCallback, type MouseEvent } from 'react';
    import { IconMinus, IconPlus, IconChevronDown } from '@fibrous/semi-icons-react';
    import { Tree } from '@fibrous/semi-ui-react';
    
    interface ExpandIconProps {
        expanded: boolean;
        onClick: (e: MouseEvent) => void;
        className: string;
    }
    
    export default function TreeDemo() {
        const treeData = [
            {
                label: '亚洲',
                key: 'yazhou',
                children: [
                    {
                        label: '中国',
                        key: 'zhongguo',
                        children: [
                            {
                                label: '北京',
                                key: 'beijing',
                            },
                            {
                                label: '上海',
                                key: 'shanghai',
                            },
                        ],
                    },
                    {
                        label: '日本',
                        key: 'riben',
                    },
                ],
            },
            {
                label: '北美洲',
                key: 'beimeizhou',
            },
        ];
        const expandIconFunc = useCallback(({ expanded, onClick, className }: ExpandIconProps) => {
            if (expanded) {
                return <IconMinus size="small" className={className} onClick={onClick} />;
            } else {
                return <IconPlus size="small" className={className} onClick={onClick} />;
            }
        }, []);
        const style = {
            width: 260,
            height: 200,
            border: '1px solid var(--semi-color-border)',
        };
    
        return (
            <>
                <p>expandIcon 是 ReactNode</p>
                <Tree expandIcon={<IconChevronDown size="small" className="testCls" />} multiple defaultExpandedKeys={['yazhou']} treeData={treeData} style={style} />
                <br />
                <p>expandIcon 是函数 </p>
                <Tree multiple expandIcon={expandIconFunc} defaultExpandedKeys={['yazhou']} treeData={treeData} style={style} />
            </>
        );
    }

    Tree with line

    Set the line between nodes through showLine, the default is false, supported starting from 2.50.0

    showLine
  • parent-0
  • leaf-0-0
  • leaf-0-0-0
  • leaf-0-0-1
  • leaf-0-0-2
  • leaf-0-1
  • parent-1
  • Code
    import { useState, useCallback, useMemo } from 'react';
    import { Tree, Switch } from '@fibrous/semi-ui-react';
    
    export default function TreeDemo() {
        const [show, setShow] = useState(true);
        const onChange = useCallback((value: boolean) => {
            setShow(value);
        }, []);
        const treeData = useMemo(() => {
            return [
                {
                    label: 'parent-0',
                    key: 'parent-0',
                    children: [
                        {
                            label: 'leaf-0-0',
                            key: 'leaf-0-0',
                            children: [
                                {
                                    label: 'leaf-0-0-0',
                                    key: 'leaf-0-0-0',
                                },
                                {
                                    label: 'leaf-0-0-1',
                                    key: 'leaf-0-0-1',
                                },
                                {
                                    label: 'leaf-0-0-2',
                                    key: 'leaf-0-0-2',
                                },
                            ],
                        },
                        {
                            label: 'leaf-0-1',
                            key: 'leaf-0-1',
                        },
                    ],
                },
                {
                    label: 'parent-1',
                    key: 'parent-1',
                },
            ];
        }, []);
    
        const style = {
            width: 260,
            height: 420,
            border: '1px solid var(--semi-color-border)',
        };
    
        return (
            <>
                <div style={{ display: 'flex', alignItems: 'center', columnGap: 5, marginBottom: 5 }}>
                    <strong id="tree-line-show-line">showLine </strong>
                    <Switch aria-labelledby="tree-line-show-line" checked={show} onChange={onChange} />
                </div>
                <Tree showLine={show} defaultExpandAll treeData={treeData} style={style} />
            </>
        );
    }

    Virtualized Tree

    If you need to render large sets of tree structured data, you could use virtualized tree. In virtualized mode, animation / motion is disabled for better performance.

    The property virtualize is an object consisting of the following values:

    • height: Height of the tree, If passed in as a string, computed height should not be zero for render purpose, in other words, parent node should have offsetHeight
    • width: Width of the tree.
    • itemSize: Height for each line of treeNode, required

    If tree is searchable, you could also turn on showFilteredOnly to reduce time of rendering for results.

    共 0 个节点

    Code
    import { useState } from 'react';
    import { Tree, Button, type TreeNodeData } from '@fibrous/semi-ui-react';
    
    const style = {
        width: 260,
        border: '1px solid var(--semi-color-border)',
    };
    
    function generateData(x = 5, y = 4, z = 3, data: TreeNodeData[] = []) {
        // x: nodes per level. y: nodes with children per level. z: depth (0 is one level)
        function fill(level: number, preKey: string, nodes: TreeNodeData[]) {
            const parents: string[] = [];
            for (let i = 0; i < x; i++) {
                const key = `${preKey}-${i}`;
                nodes.push({ label: `${key}-标签`, key: `${key}-key`, value: `${key}-value` });
                if (i < y) {
                    parents.push(key);
                }
            }
            if (level < 0) {
                return;
            }
            parents.forEach((key, index) => {
                const children: TreeNodeData[] = [];
                nodes[index].children = children;
                fill(level - 1, key, children);
            });
        }
        fill(z, '0', data);
    
        let total = 0;
        for (let n = z + 1; n >= 0; n--) {
            total += x * y ** n;
        }
        return { gData: data, total };
    }
    
    export default function TreeDemo() {
        const [gData, setGData] = useState<TreeNodeData[]>([]);
        const [total, setTotal] = useState(0);
    
        const onGen = () => {
            const data = generateData();
            setGData(data.gData);
            setTotal(data.total);
        };
    
        return (
            <div style={{ padding: '0 20px' }}>
                <Button onClick={onGen}>生成数据: </Button>
                <span>共 {total} 个节点</span>
                <br />
                <br />
                {Boolean(gData.length) && (
                    <Tree
                        treeData={gData}
                        filterTreeNode
                        showFilteredOnly
                        style={style}
                        virtualize={{
                            // A set Tree height fills the remaining space
                            height: 300,
                            itemSize: 28,
                        }}
                    />
                )}
            </div>
        );
    }

    Dynamic Update of Data

  • item-0

  • Code
    import { useState } from 'react';
    import { Tree, Button } from '@fibrous/semi-ui-react';
    
    const style = {
        width: 260,
        height: 420,
        border: '1px solid var(--semi-color-border)',
    };
    
    export default function TreeDemo() {
        const [treeData, setTreeData] = useState([
            {
                key: '0',
                label: 'item-0',
                value: '0',
            },
        ]);
    
        const add = () => {
            const itemLength = Math.floor(Math.random() * 5) + 1;
            const nextTreeData = Array.from({ length: itemLength }).map((_v, i) => {
                const length = Math.floor(Math.random() * 3);
                const children = Array.from({ length }).map((_cv, ci) => {
                    const child = {
                        key: `${i}-${ci}`,
                        label: `Leaf-${i}-${ci}`,
                        value: `${i}-${ci}`,
                    };
                    return child;
                });
                const item = {
                    key: `${i}`,
                    label: `Item-${i}`,
                    value: `${i}`,
                    children,
                };
                return item;
            });
            setTreeData(nextTreeData);
        };
    
        return (
            <div style={style}>
                <Tree treeData={treeData} />
                <br />
                <Button onClick={add} style={{ margin: 20 }}>
                    动态改变数据
                </Button>
            </div>
        );
    }

    Load Data Asynchronously

    You could use loadData to load treeData asynchronously on node expansion. Notice isLeaf is required to mark node as leaf in treeData.

  • Leaf Node
  • Code
    import { useState } from 'react';
    import { Tree, type TreeNodeData } from '@fibrous/semi-ui-react';
    
    export default function TreeDemo() {
        const initialData = [
            {
                label: 'Expand to load',
                value: '0',
                key: '0',
            },
            {
                label: 'Expand to load',
                value: '1',
                key: '1',
            },
            {
                label: 'Leaf Node',
                value: '2',
                key: '2',
                isLeaf: true,
            },
        ];
        const [treeData, setTreeData] = useState<TreeNodeData[]>(initialData);
    
        function updateTreeData(list: TreeNodeData[], key: string | undefined, children: TreeNodeData[]): TreeNodeData[] {
            return list.map((node) => {
                if (node.key === key) {
                    return { ...node, children };
                }
                if (node.children) {
                    return { ...node, children: updateTreeData(node.children, key, children) };
                }
                return node;
            });
        }
    
        function onLoadData(treeNode?: TreeNodeData) {
            const key = treeNode?.key;
            return new Promise<void>((resolve) => {
                if (treeNode?.children) {
                    resolve();
                    return;
                }
                setTimeout(() => {
                    setTreeData((origin) =>
                        updateTreeData(origin, key, [
                            {
                                label: 'Child Node',
                                key: `${key}-0`,
                            },
                            {
                                label: 'Child Node',
                                key: `${key}-1`,
                            },
                        ])
                    );
                    resolve();
                }, 1000);
            });
        }
        return <Tree loadData={onLoadData} treeData={[...treeData]} />;
    }

    Draggable Tree

    You could use draggable along with onDrop to achieve a draggable Tree.

    The callback input parameters of the drag event are as follows:

    - onDragEnd: function({ event, node: DragTreeNode })
    - onDragEnter:function({ event, node: DragTreeNode, expandedKeys: string[] })
    - onDragLeave:function({ event, node: DragTreeNode })
    - onDragOver:function({ event, node: DragTreeNode })
    - onDragStart: function({ event, node: DragTreeNode })
    - onDrop:function({ event, node: DragTreeNode, dragNode: DragTreeNode, dragNodesKeys: string[], dropPosition: number, dropToGap: Boolean })

    The data type DragTreeNode, in addition to all the attributes of TreeNodeData, also includes expanded and pos attributes,

    DragTreeNode {
        expanded: Boolean,
        pos: string
        value?: string | number;
        label?: React.ReactNode;
        disabled?: boolean;
        isLeaf?: boolean;
        [key: string]: any;
    }
    • pos refers to the positional relationship of the current node in the entire treeData, such as the 0th node of the 2nd node of the 1st node of the 0th layer: '0-1-2-0'
    • dropPosition refers to the position where the dragged node is dropped in the current hierarchy. If it is inserted before the 0th node of the same level, it will be -1, and after the 0th node, it will be 1, and it will fall on it. is 0, and so on. With dropToGap, a more complete judgment can be obtained.
    • dropToGap refers to whether the dragged node is dropped between nodes, if false, it is dropped above a node
  • Europe
  • Code
    import { useState } from 'react';
    import { Tree, type TreeNodeData, type TreeOnDragProps } from '@fibrous/semi-ui-react';
    
    type LoopCallback = (item: TreeNodeData, ind: number, arr: TreeNodeData[]) => void;
    
    export default function TreeDemo() {
        const initialData = [
            {
                label: 'Asia',
                value: 'Asia',
                key: '0',
                children: [
                    {
                        label: 'China',
                        value: 'China',
                        key: '0-0',
                        children: [
                            {
                                label: 'Beijing',
                                value: 'Beijing',
                                key: '0-0-0',
                            },
                            {
                                label: 'Shanghai',
                                value: 'Shanghai',
                                key: '0-0-1',
                            },
                        ],
                    },
                    {
                        label: 'Japan',
                        value: 'Japan',
                        key: '0-1',
                        children: [
                            {
                                label: 'Osaka',
                                value: 'Osaka',
                                key: '0-1-0',
                            },
                        ],
                    },
                ],
            },
            {
                label: 'North America',
                value: 'North America',
                key: '1',
                children: [
                    {
                        label: 'United States',
                        value: 'United States',
                        key: '1-0',
                    },
                    {
                        label: 'Canada',
                        value: 'Canada',
                        key: '1-1',
                    },
                ],
            },
            {
                label: 'Europe',
                value: 'Europe',
                key: '2',
            },
        ];
        const [treeData, setTreeData] = useState<TreeNodeData[]>(initialData);
    
        function onDrop(info: TreeOnDragProps) {
            const { dropToGap, node, dragNode } = info;
            const dropKey = node.key;
            const dragKey = dragNode.key;
            const dropPos = node.pos.split('-');
            const dropPosition = info.dropPosition - Number(dropPos[dropPos.length - 1]);
    
            const data = [...treeData];
            const loop = (data: TreeNodeData[], key: string | undefined, callback: LoopCallback) => {
                data.forEach((item, ind, arr) => {
                    if (item.key === key) return callback(item, ind, arr);
                    if (item.children) return loop(item.children, key, callback);
                });
            };
            let dragObj: TreeNodeData = {};
            loop(data, dragKey, (item, ind, arr) => {
                arr.splice(ind, 1);
                dragObj = item;
            });
    
            if (!dropToGap) {
                loop(data, dropKey, (item) => {
                    item.children = item.children || [];
                    item.children.push(dragObj);
                });
            } else if (dropPosition === 1 && node.children && node.expanded) {
                // Expanded with children and dropped in the bottom gap: insert at the top
                loop(data, dropKey, (item) => {
                    item.children = item.children || [];
                    item.children.unshift(dragObj);
                });
            } else {
                let dropNodeInd = 0;
                let dropNodePosArr: TreeNodeData[] = [];
                loop(data, dropKey, (_item, ind, arr) => {
                    dropNodePosArr = arr;
                    dropNodeInd = ind;
                });
                if (dropPosition === -1) {
                    dropNodePosArr.splice(dropNodeInd, 0, dragObj);
                } else {
                    dropNodePosArr.splice(dropNodeInd + 1, 0, dragObj);
                }
            }
            setTreeData(data);
        }
    
        return <Tree treeData={treeData} draggable onDrop={onDrop} />;
    }

    Advanced FullRender

    v>=1.7.0

    You could use renderFullLabel for advanced rendering to render the entire option on you own.

    The parameter types of renderFullLabel are as follows:

    type RenderFullLabelProps = {
        /* The original data of the row */
        data: BasicTreeNodeData;
        /* The level of the line can be used to customize the indentation value */
        level: number;
        /* The style required for virtualization, if virtualization is used, the style must be assigned to the DOM element */
        style: any;
        /* Class name, including built-in styles such as indentation, expand button, filter, disable, select, etc. */
        className: string;
        /* icon of Expand button */
        expandIcon: any;
        /* Selected state */
        checkStatus: {
            /* Whether to select in the multi-select state */
            checked: boolean;
            /* Whether to half-select in the multi-select state */
            halfChecked: boolean;
        };
        /* Expand status */
        expandStatus: {
            /* Has it been expanded */
            expanded: boolean;
            /* Is it unfolding */
            loading: boolean;
        };
        /* Whether the node meets the search conditions */
        filtered: boolean | undefined;
        /* Current search box input */
        searchWord: string | undefined;
        /* Click the callback of the entire row to control the expansion behavior and selection */
        onClick: (e: MouseEvent) => void;
        /* Multi-select click callback */
        onCheck: (e: MouseEvent) => void;
        /* Right-click the callback for the entire row */
        onContextMenu: (e: MouseEvent) => void;
        /* Double-click the entire line of callback */
        onDoubleClick: (e: MouseEvent) => void;
        /* expand callback */
        onExpand: (e: MouseEvent) => void;
    };

    Here are some demos.

    First is to render Parent node as separator and only allow leaf nodes to be selected.
    renderFullLabel only takes care of the UI rendering and won't affect inside data logic. But you could choose info to your needs and use it with controlled mode for advanced usage.

    Code
    import { Tree, Checkbox, type TreeProps } from '@fibrous/semi-ui-react';
    
    export default function TreeDemo() {
        const renderLabel: TreeProps['renderFullLabel'] = ({ className, onExpand, data, onCheck, checkStatus, expandIcon }) => {
            const { label } = data;
            const isLeaf = !(data.children && data.children.length);
            const handleClick = isLeaf ? onCheck : onExpand;
            // Focus stays on the tree (arrows expand, Enter/Space check), so the inline checkbox is display-only
            return (
                <li className={className} role="treeitem" onClick={handleClick}>
                    {!isLeaf && expandIcon}
                    {isLeaf && <Checkbox aria-hidden tabIndex={-1} indeterminate={checkStatus.halfChecked} checked={checkStatus.checked} style={{ marginRight: 8 }} />}
                    <span>{label}</span>
                </li>
            );
        };
    
        const treeData = [
            {
                label: 'Asia',
                value: 'Asia',
                key: '0',
                children: [
                    {
                        label: 'China',
                        value: 'China',
                        key: '0-0',
                        children: [
                            {
                                label: 'Beijing',
                                value: 'Beijing',
                                key: '0-0-0',
                            },
                            {
                                label: 'Shanghai',
                                value: 'Shanghai',
                                key: '0-0-1',
                            },
                        ],
                    },
                    {
                        label: 'Japan',
                        value: 'Japan',
                        key: '0-1',
                        children: [
                            {
                                label: 'Osaka',
                                value: 'Osaka',
                                key: '0-1-0',
                            },
                        ],
                    },
                ],
            },
            {
                label: 'North America',
                value: 'North America',
                key: '1',
                children: [
                    {
                        label: 'United States',
                        value: 'United States',
                        key: '1-0',
                    },
                    {
                        label: 'Canada',
                        value: 'Canada',
                        key: '1-1',
                    },
                ],
            },
        ];
    
        const treeStyle = {
            width: 260,
            height: 420,
            border: '1px solid var(--semi-color-border)',
        };
        return <Tree treeData={treeData} renderFullLabel={renderLabel} multiple leafOnly style={treeStyle} />;
    }

    The second is for the scenario of "I hope that only leaf nodes can be single-selected, and the parent node only plays a role in grouping".

    • You only need to click on the parent node without triggering the selection, click on the leaf node to trigger.
    Code
    import { Tree, type TreeProps } from '@fibrous/semi-ui-react';
    
    export default function TreeDemo() {
        const renderLabel: TreeProps['renderFullLabel'] = ({ className, onExpand, onClick, data, expandIcon }) => {
            const { label } = data;
            const isLeaf = !(data.children && data.children.length);
            const handleClick = isLeaf ? onClick : onExpand;
            return (
                <li className={className} role="treeitem" onClick={handleClick}>
                    {expandIcon}
                    <span>{label}</span>
                </li>
            );
        };
    
        const treeData = [
            {
                label: 'Asia',
                value: 'Asia',
                key: '0',
                children: [
                    {
                        label: 'China',
                        value: 'China',
                        key: '0-0',
                        children: [
                            {
                                label: 'Beijing',
                                value: 'Beijing',
                                key: '0-0-0',
                            },
                            {
                                label: 'Shanghai',
                                value: 'Shanghai',
                                key: '0-0-1',
                            },
                        ],
                    },
                    {
                        label: 'Japan',
                        value: 'Japan',
                        key: '0-1',
                        children: [
                            {
                                label: 'Osaka',
                                value: 'Osaka',
                                key: '0-1-0',
                            },
                        ],
                    },
                ],
            },
            {
                label: 'North America',
                value: 'North America',
                key: '1',
                children: [
                    {
                        label: 'United States',
                        value: 'United States',
                        key: '1-0',
                    },
                    {
                        label: 'Canada',
                        value: 'Canada',
                        key: '1-1',
                    },
                ],
            },
        ];
    
        const treeStyle = {
            width: 260,
            height: 420,
            border: '1px solid var(--semi-color-border)',
        };
        return <Tree treeData={treeData} renderFullLabel={renderLabel} style={treeStyle} onChange={(...args) => console.log('change', ...args)} />;
    }

    Third is for the scenario when selecting Parent node also highlighting child node.

  • 黑色固定按钮
  • 模块
  • 可自由摆放的组件
  • 分栏容器
  • 按钮组件
  • 按钮组件
  • 模块
  • 自定义组件
  • Code
    import { useState } from 'react';
    import { IconFixedStroked, IconSectionStroked, IconAbsoluteStroked, IconInnerSectionStroked, IconComponentStroked } from '@fibrous/semi-icons-react';
    import { Tree, type TreeNodeData, type TreeProps } from '@fibrous/semi-ui-react';
    
    export default function TreeDemo() {
        const [selected, setSelected] = useState(new Set<string | undefined>());
        const [selectedThroughParent, setSelectedThroughParent] = useState(new Set<string | undefined>());
        const treeData = [
            {
                label: '黑色固定按钮',
                icon: <IconFixedStroked style={{ marginRight: 8, color: 'var(--semi-color-text-2)' }} />,
                key: 'fix-btn-0',
            },
            {
                label: '模块',
                key: 'module-0',
                icon: <IconSectionStroked style={{ marginRight: 8, color: 'var(--semi-color-text-2)' }} />,
                children: [
                    {
                        label: '可自由摆放的组件',
                        icon: <IconAbsoluteStroked style={{ marginRight: 8, color: 'var(--semi-color-text-2)' }} />,
                        key: 'free-compo-0',
                    },
                    {
                        label: '分栏容器',
                        icon: <IconInnerSectionStroked style={{ marginRight: 8, color: 'var(--semi-color-text-2)' }} />,
                        key: 'split-col-0',
                        children: [
                            {
                                label: '按钮组件',
                                icon: <IconComponentStroked style={{ marginRight: 8, color: 'var(--semi-color-text-2)' }} />,
                                key: 'btn-0',
                            },
                            {
                                label: '按钮组件',
                                icon: <IconComponentStroked style={{ marginRight: 8, color: 'var(--semi-color-text-2)' }} />,
                                key: 'btn-1',
                            },
                        ],
                    },
                ],
            },
            {
                label: '模块',
                icon: <IconSectionStroked style={{ marginRight: 8, color: 'var(--semi-color-text-2)' }} />,
                key: 'module-1',
                children: [
                    {
                        label: '自定义组件',
                        icon: <IconComponentStroked style={{ marginRight: 8, color: 'var(--semi-color-text-2)' }} />,
                        key: 'cus-0',
                    },
                ],
            },
        ];
        const findDescendantKeys = (node: TreeNodeData) => {
            let res = [node.key];
            const findChild = (item?: TreeNodeData) => {
                if (!item) return;
                const { children } = item;
    
                if (children && children.length) {
                    children.forEach((child) => {
                        res.push(child.key);
                        findChild(child);
                    });
                }
            };
            findChild(node);
            return res;
        };
        const handleSelect: TreeProps['onSelect'] = (key, _bool, node) => {
            setSelected(new Set([key]));
            const descendantKeys = findDescendantKeys(node);
            setSelectedThroughParent(new Set(descendantKeys));
        };
        const renderLabel: TreeProps['renderFullLabel'] = ({ className, data, onClick, expandIcon }) => {
            const { label, icon, key } = data;
            const isLeaf = !(data.children && data.children.length);
            const style = {
                backgroundColor: selected.has(key) ? 'var(--semi-blue-0)' : selectedThroughParent.has(key) ? 'rgb(from var(--semi-blue-0) r g b / .5)' : 'transparent',
            };
            return (
                <li className={className} role="treeitem" onClick={onClick} style={style}>
                    {isLeaf ? <span style={{ width: 24 }} /> : expandIcon}
                    {icon}
                    <span>{label}</span>
                </li>
            );
        };
    
        const treeStyle = {
            width: 260,
            height: 420,
            border: '1px solid var(--semi-color-border)',
        };
    
        return <Tree treeData={treeData} renderFullLabel={renderLabel} onSelect={handleSelect} style={treeStyle} defaultExpandAll />;
    }

    Advanced FullRender with Draggable

    Starting from version 1.27.0, we support the simultaneous use of draggable (draggable) and advanced customization (renderFullLabel). Before this version, the simultaneous use of both was not supported.

  • 黑色固定按钮
  • 模块
  • 可自由摆放的组件
  • 分栏容器
  • 按钮组件
  • 按钮组件
  • 模块
  • 自定义组件
  • Code
    import { useState } from 'react';
    import { IconFixedStroked, IconSectionStroked, IconAbsoluteStroked, IconComponentStroked } from '@fibrous/semi-icons-react';
    import { Tree, type TreeNodeData, type TreeOnDragProps, type TreeProps } from '@fibrous/semi-ui-react';
    
    type LoopCallback = (item: TreeNodeData, ind: number, arr: TreeNodeData[]) => void;
    
    export default function TreeDemo() {
        const [selected, setSelected] = useState(new Set<string | undefined>());
        const [selectedThroughParent, setSelectedThroughParent] = useState(new Set<string | undefined>());
        const defaultTreeData = [
            {
                label: '黑色固定按钮',
                icon: <IconFixedStroked style={{ marginRight: 8, color: 'var(--semi-color-text-2)' }} />,
                key: 'fix-btn-0',
            },
            {
                label: '模块',
                key: 'module-0',
                icon: <IconSectionStroked style={{ marginRight: 8, color: 'var(--semi-color-text-2)' }} />,
                children: [
                    {
                        label: '可自由摆放的组件',
                        icon: <IconAbsoluteStroked style={{ marginRight: 8, color: 'var(--semi-color-text-2)' }} />,
                        key: 'free-compo-0',
                    },
                    {
                        label: '分栏容器',
                        icon: <IconSectionStroked style={{ marginRight: 8, color: 'var(--semi-color-text-2)' }} />,
                        key: 'split-col-0',
                        children: [
                            {
                                label: '按钮组件',
                                icon: <IconComponentStroked style={{ marginRight: 8, color: 'var(--semi-color-text-2)' }} />,
                                key: 'btn-0',
                            },
                            {
                                label: '按钮组件',
                                icon: <IconComponentStroked style={{ marginRight: 8, color: 'var(--semi-color-text-2)' }} />,
                                key: 'btn-1',
                            },
                        ],
                    },
                ],
            },
            {
                label: '模块',
                icon: <IconSectionStroked style={{ marginRight: 8, color: 'var(--semi-color-text-2)' }} />,
                key: 'module-1',
                children: [
                    {
                        label: '自定义组件',
                        icon: <IconComponentStroked style={{ marginRight: 8, color: 'var(--semi-color-text-2)' }} />,
                        key: 'cus-0',
                    },
                ],
            },
        ];
        const [treeData, setTreeData] = useState<TreeNodeData[]>(defaultTreeData);
    
        const onDrop = (info: TreeOnDragProps) => {
            const { dropToGap, node, dragNode } = info;
            const dropKey = node.key;
            const dragKey = dragNode.key;
            const dropPos = node.pos.split('-');
            const dropPosition = info.dropPosition - Number(dropPos[dropPos.length - 1]);
    
            const data = [...treeData];
            const loop = (data: TreeNodeData[], key: string | undefined, callback: LoopCallback) => {
                data.forEach((item, ind, arr) => {
                    if (item.key === key) return callback(item, ind, arr);
                    if (item.children) return loop(item.children, key, callback);
                });
            };
    
            let dragObj: TreeNodeData = {};
            loop(data, dragKey, (item, ind, arr) => {
                arr.splice(ind, 1);
                dragObj = item;
            });
    
            if (!dropToGap) {
                loop(data, dropKey, (item) => {
                    item.children = item.children || [];
                    item.children.push(dragObj);
                });
            } else if (dropPosition === 1 && node.children && node.expanded) {
                loop(data, dropKey, (item) => {
                    item.children = item.children || [];
                    item.children.unshift(dragObj);
                });
            } else {
                let dropNodeInd = 0;
                let dropNodePosArr: TreeNodeData[] = [];
                loop(data, dropKey, (_item, ind, arr) => {
                    dropNodePosArr = arr;
                    dropNodeInd = ind;
                });
                if (dropPosition === -1) {
                    dropNodePosArr.splice(dropNodeInd, 0, dragObj);
                } else {
                    dropNodePosArr.splice(dropNodeInd + 1, 0, dragObj);
                }
            }
            setTreeData(data);
        };
    
        const findDescendantKeys = (node: TreeNodeData) => {
            const res = [node.key];
            const findChild = (item?: TreeNodeData) => {
                if (!item) return;
                const { children } = item;
    
                if (children && children.length) {
                    children.forEach((child) => {
                        res.push(child.key);
                        findChild(child);
                    });
                }
            };
            findChild(node);
            return res;
        };
    
        const handleSelect: TreeProps['onSelect'] = (key, _bool, node) => {
            setSelected(new Set([key]));
            const descendantKeys = findDescendantKeys(node);
            setSelectedThroughParent(new Set(descendantKeys));
        };
    
        const renderLabel: TreeProps['renderFullLabel'] = ({ className, data, onClick, expandIcon }) => {
            const { label, icon, key } = data;
            const isLeaf = !(data.children && data.children.length);
            const style = {
                backgroundColor: selected.has(key) ? 'var(--semi-blue-0)' : selectedThroughParent.has(key) ? 'rgb(from var(--semi-blue-0) r g b / .5)' : 'transparent',
            };
            return (
                <li className={className} role="treeitem" onClick={onClick} style={style}>
                    {isLeaf ? <span style={{ width: 24 }} /> : expandIcon}
                    {icon}
                    <span>{label}</span>
                </li>
            );
        };
    
        const treeStyle = {
            width: 260,
            height: 420,
            border: '1px solid var(--semi-color-border)',
        };
    
        return <Tree treeData={treeData} draggable onDrop={onDrop} renderFullLabel={renderLabel} onSelect={handleSelect} style={treeStyle} defaultExpandAll />;
    }

    API Reference

    Tree

    Props

    PropertyDescriptionTypeDefault
    aria-labelAccessible name of the tree, set on the element with role="tree"; the element is not rendered in the empty state.string-
    autoExpandParentWhether the parents of the nodes in expandedKeys expand automatically when expandedKeys changes; they always do on mount. When on, a controlled tree collapses a parent only after its expanded children are collapsed.booleanfalse
    autoExpandWhenDragEnterWhether dragging over a collapsed node expands it.booleantrue
    autoMergeValueWhen a parent is selected the value leaves out its descendants; applies when leafOnly is false.booleantrue
    blockNodeShows each node as a whole row, highlighting the row on hover and selection; when off only the label is highlighted.booleantrue
    checkRelationHow the checked states of nodes relate in a multiple tree: related links parents and children, unRelated keeps them independent.CheckRelation"related"
    childrenTree does not render children; provide the nodes through treeData.ReactNode-
    classNameClass name of the root element.string-
    defaultExpandAllExpands every node on mount. Later changes of treeData or treeDataSimpleJson do not affect it; use expandAll for that.booleanfalse
    defaultExpandedKeysNodes expanded initially, showing their direct children.string[]-
    defaultValueThe initially selected items, read once on mount.Value-
    directoryDirectory mode, which shows folder and file icons by default; the icon of a node and the icon prop override them.boolean-
    disabledDisables the whole tree. A node with disabled: false does not lift it.booleanfalse
    disableStrictlyStrict disabling: a disabled node cannot change state through the selection of its parent or children.booleanfalse
    draggableWhether nodes can be dragged, used with the drag callbacks such as onDrop.booleanfalse
    emptyContentContent shown when there are no nodes, or when the search finds none with showFilteredOnly on. Defaults to the empty text of the locale; null renders no empty state.ReactNode-
    expandActionHow a node expands: false only through the arrow, click on a click, doubleClick on a double click. The keyboard does not use it.ExpandActionfalse
    expandAllExpands every node, and keeps doing so when the data changes.booleanfalse
    expandedKeysExpanded nodes (controlled). Passing it makes expansion fully controlled, used with onExpand.string[]-
    expandIconCustom expand icon. An element receives the expand click and the switcher class; a function receives onClick, className and expanded and returns the whole icon. Other nodes render as they are, without the expand click.ExpandIcon-
    filterTreeNodeWhether search is on. true matches by treeNodeFilterProp; a function customizes the match, with the node data as its third argument.Typeboolean | ((inputValue: string, treeNodeString: string, data?: TreeNodeData) => boolean)false
    hideDraggingNodeHides the drag image of the node being dragged.boolean-
    iconCustom node icon. An element is used for every node; a function is called once per row with the row's props, including its node data, and returns that row's icon. The icon in node data takes precedence over this prop.TypeReactNode | ((props: TreeNodeProps) => ReactNode)-
    keyMapsCustom field names for key, label, value and children in a node. When label is mapped and search is on, set treeNodeFilterProp to one of the keys in treeData or provide filterTreeNode.KeyMapProps-
    labelEllipsisWhether an overflowing label is truncated; when unset it follows virtualize, truncating when virtualization is on.boolean-
    leafOnlyIn a multiple tree, the value and the argument of onChange hold leaf nodes only.booleanfalse
    loadDataLoads the children of a node and returns a Promise. Mark leaves with isLeaf in the data; a node without isLeaf shows the expand arrow while this is set.Type(treeNode?: TreeNodeData) => Promise<void>-
    loadedKeysLoaded nodes (controlled), used with loadData.string[]-
    motionWhether expanding and collapsing are animated; animation is off for a virtualized tree.booleantrue
    multipleWhether several nodes can be selected. A parent is checked automatically once all its children are.booleanfalse
    onChangeCalled when the selection changes, with the selected value (an array when multiple); with onChangeWithObject set to true the argument is the node object.Type(value: Value) => void-
    onChangeWithObjectWhether the other fields of a selected node go to onChange. When true the argument is the node object with value, label and other fields instead of the value; a controlled value and defaultValue must then be objects with a value.booleanfalse
    onContextMenuCalled on a right click on a node, with the event and the node data.Type(e: MouseEvent, node: TreeNodeData) => void-
    onDoubleClickCalled on a double click on a node, with the event and the node data.Type(e: MouseEvent, node: TreeNodeData) => void-
    onDragEndCalled when a drag ends, with { event, node }.Type(dragProps: DragProps) => void-
    onDragEnterCalled when a drag enters a node, with { event, node, expandedKeys }; expandedKeys is set when the drag expanded a collapsed node.Type(dragEnterProps: DragEnterProps) => void-
    onDragLeaveCalled when a drag leaves a node, with { event, node }.Type(dragProps: DragProps) => void-
    onDragOverCalled while a drag is over a node, with { event, node }.Type(dragProps: DragProps) => void-
    onDragStartCalled when a node starts to be dragged, with { event, node }.Type(dragProps: DragProps) => void-
    onDropCalled when a node is dropped, with { event, node, dragNode, dragNodesKeys, dropPosition, dropToGap }.Type(onDragProps: OnDragProps) => void-
    onExpandCalled when a node expands or collapses, with the expanded keys and { expanded, node }.Type(expandedKeys: string[], expandedOtherProps: ExpandedOtherProps) => void-
    onLoadCalled when a node has loaded, with the set of loaded keys and the node data.Type(loadedKeys?: Set<string>, treeNode?: TreeNodeData) => void-
    onSearchCalled when the search input changes, with the search value and the keys expanded by the search or by value and defaultValue; under controlled expansion it can expand the results.Type(searchValue: string, filteredExpandedKeys: string[]) => void-
    onSelectCalled when a node is selected or deselected, with its key, whether it is selected and its data.Type(selectedKey: string, selected: boolean, selectedNode: TreeNodeData) => void-
    refPoints to the TreeHandle, which holds the search and scrollTo methods; see Methods below.TypeReact.Ref<TreeHandle>-
    renderDraggingNodeThe drag image the browser shows while a node is dragged. It receives the node's HTML element and its data, and returns an HTMLElement.Type(nodeInstance: HTMLElement, node: TreeNodeData) => HTMLElement-
    renderFullLabelRenders the whole row, called once per row with an object holding data, level, expandIcon, className, style, checkStatus, expandStatus, onClick, onCheck and more. The returned element gets role="treeitem", the accessibility state attributes and style from Tree; text, arrays and fragments are wrapped in an li. The element must apply className itself.Type(renderFullLabelProps: RenderFullLabelProps) => ReactNode-
    renderLabelRenders a node's label from the label, the node data and the search word. Search words are no longer highlighted automatically; search still matches the label text in treeData.Type(label?: ReactNode, treeNode?: TreeNodeData, searchWord?: string) => ReactNode-
    searchClassNameClass of the search box container.string-
    searchPlaceholderPlaceholder of the search box; defaults to the text of the locale.string-
    searchRenderRenders a custom search box from an object with className, prefix, value, onChange, showClear and placeholder; onChange receives the input string. false hides the search box.Type((searchRenderProps: SearchRenderProps) => ReactNode) | false-
    searchStyleStyle of the search box container.React.CSSProperties-
    showClearWhether the search box shows a clear button.booleantrue
    showFilteredOnlyShows only the filtered nodes while searching.booleanfalse
    showLineShows connecting lines between nodes.booleanfalse
    styleStyle of the root element, typed as CSSProperties.React.CSSProperties-
    treeDataNode data; keys are unique across the tree. Only a new array updates the tree, changes made in place are not seen.TreeNodeData[]-
    treeDataSimpleJsonNode data as a plain JSON object; the value is a JSON object holding the selected nodes.TreeDataSimpleJson-
    treeNodeFilterPropThe node field matched against the search input.string"label"
    valueValue of the selected node, its key when it has no value (controlled). Passing it makes the tree controlled, used with onChange; an array when multiple.Value-
    virtualizeList virtualization for many nodes: height, width and itemSize (required). Animation is off once enabled.Virtualize-

    Methods

    MethodDescriptionType
    scrollToIn a virtualized tree, scrolls an expanded node into view; takes { key, align }, with align defaulting to center.Type(scrollData: ScrollData) => void
    searchSearches as if the value were typed into the search box; filterTreeNode must be set.Type(value: string) => void

    aria-label, aria-labelledby, aria-describedby and tabIndex land on the element with role="tree"; the empty state has no such element, so they are dropped there. Other native attributes, such as id and data-*, land on the root element .semi-tree-wrapper.

    TreeNodeData

    Key for TreeNodeData is required and must be unique, label can be duplicated. Value is not required. In this case, the value property in onChange, value, defaultValue and onChangeWithObject will point to key property. To ensure everything behave as expected, keep a consistency of whether to have value or not to have value.

    PropertiesInstructionsTypeDefault
    valueValuestring|number-
    labelDisplayed labelstring|ReactNode-
    iconIconReactNode-
    disabledDisabled. When the Tree's disabled is true the whole tree is disabled, and a node's disabled: false does not lift itbooleanfalse
    keyRequired and must be uniquestring-
    isLeafToggle whether node is leaf node, only takes effect in async load data with loadDataboolean-

    Virtualize Object

    itemSize is required to render virtualized tree。

    PropertiesInstructionsTypeDefault
    heightHeight. If passed as a string, computed height should not be 0 for render purpose, in other words, parent node should have offsetHeightnumber|string'100%'
    itemSizeHeight for each line of treeNode, requirednumber-
    widthWidthnumber|string'100%'

    Methods

    Some internal methods provided by Tree can be accessed through ref. The method descriptions are in the Tree table above.

    import { useRef } from 'react';
    import { Tree, type TreeHandle } from '@fibrous/semi-ui-react';
    
    const tree = useRef<TreeHandle>(null);
    const search = (value: string) => tree.current?.search(value);
    
    <Tree ref={tree} filterTreeNode treeData={treeData} />;

    Accessibility

    ARIA

    • Tree supports passing in aria-label, aria-labelledby and aria-describedby to name and describe the Tree; they are all set on the role="tree" element;
    • Tree sets aria-level, aria-setsize, aria-posinset and aria-disabled on each node, and aria-expanded on nodes that can expand; a single-choice tree marks the selected node with aria-selected, a multi-choice tree marks checked nodes with aria-checked (mixed when half checked);
    • Tree will set role to tree and treeitem for corresponding parts.
    • The element returned by renderFullLabel also gets role="treeitem" and the states above; content that cannot carry them, such as text, an array or a Fragment, is wrapped in an li that carries them.
    • The expand arrow and the checkbox in front of a node only picture its state: they are hidden from screen readers and take no focus.

    Keyboard Interaction

    Tree follows the WAI-ARIA Tree View pattern: the whole tree is one Tab stop, focus stays on the tree, aria-activedescendant points at the current node, and the current node shows the focus ring.

    • ArrowDown, ArrowUp: move to the next or previous visible node; Home, End: move to the first or last visible node
    • ArrowRight: expand a collapsed node; on an expanded node, move to its first child (swapped with ArrowLeft in RTL)
    • ArrowLeft: collapse an expanded node; otherwise move to the parent
    • Enter, Space: select the current node, or check and uncheck it in a multi-choice tree; disabled nodes do not respond
    • Typing characters: move to the next node whose text starts with them

    A node that is selected, checked, expanded, right-clicked or double-clicked with the pointer becomes the current node of the keyboard, and the arrow keys continue from it.

    The keyboard does not trigger expandAction; the arrow keys expand and collapse.

    Buttons and links inside a node (for example the control returned by renderLabel) can be reached with Tab. While one of them has focus, keys are handled by that control and the tree does not respond.

    Demo:

        <Tree
            /* other attributes */
            aria-label='example tree'
        />

    Content Guidelines

    • Try to use phrases, capitalize the first letter
    • Keep the same form of terminology between levels, such as all place names or country names

    Design Tokens

    These are the component's internal Sass variables and cannot be overridden. To customize styles, override the --semi-* CSS variables (see the Design Token page) or write unlayered CSS rules.
    Sass variableValueUsage
    $transition_duration-tree_option-bgvar(--semi-transition_duration-none)树选项-背景色-动画持续时间
    $transition_function-tree_option-bgvar(--semi-transition_function-easeIn)树选项-背景色-过渡曲线
    $transition_delay-tree_option-bgvar(--semi-transition_delay-none)树选项-背景色-延迟时间
    $transform_scale-tree-optionvar(--semi-transform_scale-none)树选项-放大
    $color-tree_option-text-defaultvar(--semi-color-text-0)树选项文字颜色
    $color-tree_option-icon-defaultvar(--semi-color-text-2)树选项图标颜色
    $color-tree_option-bg-hovervar(--semi-color-fill-0)树选项背景色 - 悬停
    $color-tree_option_selected-bg-defaultvar(--semi-color-fill-1)树选项背景色 - 按下
    $color-tree_option-bg-activevar(--semi-color-primary-light-default)树选项背景色 - 选中
    $color-tree_option_disabled-text-defaultvar(--semi-color-disabled-text)禁用树选项文字颜色
    $color-tree_option-icon-hovervar(--semi-color-text-0)树选项图标颜色 - 悬浮
    $color-tree_option-icon-activevar(--semi-color-black)树选项图标颜色 - 按下
    $color-tree_option_loading-icon-defaultvar(--semi-color-primary)树选项加载 spin 颜色
    $color-tree_option_draggable_insert-border-defaultvar(--semi-color-primary)树选项加载 spin 颜色
    $font-tree_option_hightlight-fontWeight$font-weight-bold树选项高亮文本字重
    $color-tree_option_hightlight-textvar(--semi-color-primary)树选项高亮文本颜色
    $radius-tree_checkbox_addonvar(--semi-border-radius-small)多选树 checkbox 圆角
    $spacing-tree_search_wrapper-paddingX12px树搜索框水平内边距
    $spacing-tree_search_wrapper-paddingY8px树搜索框垂直内边距
    $spacing-tree_option_level-paddingLeft20px树选项缩进增量
    $spacing-tree_option_level1-paddingLeft8px树选项最上层左侧内边距
    $spacing-tree_optionList-paddingX0树选项列表水平内边距
    $spacing-tree_optionList-paddingY8px树选项列表垂直内边距
    $spacing-tree_option-paddingTop4px树选项顶部内边距
    $spacing-tree_option-paddingBottom4px树选项底部内边距
    $spacing-tree_icon-marginRight8px树选项图标右侧外边距
    $spacing-tree_label_withIcon-marginRight8px树选项图标右侧外边距
    $spacing-tree_option_draggable-paddingY2px可拖拽的树选项垂直内边距
    $spacing-tree_option_draggable-paddingX0可拖拽的树选项水平内边距
    $width-tree_emptyIcon$width-icon-small树选项空图标宽度
    $width-tree_spinIcon$width-icon-small树选项加载 spin 宽度
    $width-tree_option_draggable-border2px可拖拽的树标示线宽度
    $width-tree_option_line1pxshowline展示线宽度