输入类

级联选择

用于选择多级分类下的某个选项。

使用场景

与 TreeSelect 组件的区别:

  • TreeSelect: 核心价值在于目标节点,层级结构是为了方便用户快速筛选出目标选项,最终的节点才是用户想要的内容,常见于文件/文件夹选择、组织架构、权限分配等场景。
  • Cascader: 核心价值在于路径,用户选择的不是一个孤立的点,而是一条从根到叶的完整路径,常用于地理位置,商品分类等场景。

代码演示

如何引入

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

基本用法

最简单的用法,默认只可以选叶子节点。

代码
import { Cascader } from '@fibrous/semi-ui-react';

export default function CascaderDemo() {
    const treeData = [
        {
            label: '浙江省',
            value: 'zhejiang',
            children: [
                {
                    label: '杭州市',
                    value: 'hangzhou',
                    children: [
                        {
                            label: '西湖区',
                            value: 'xihu',
                        },
                        {
                            label: '萧山区',
                            value: 'xiaoshan',
                        },
                        {
                            label: '临安区',
                            value: 'linan',
                        },
                    ],
                },
                {
                    label: '宁波市',
                    value: 'ningbo',
                    children: [
                        {
                            label: '海曙区',
                            value: 'haishu',
                        },
                        {
                            label: '江北区',
                            value: 'jiangbei',
                        },
                    ],
                },
            ],
        },
    ];
    return <Cascader style={{ width: 300 }} treeData={treeData} placeholder="请选择所在地区" />;
}

多选

设置 multiple,可以进行多选。

代码
import { Cascader } from '@fibrous/semi-ui-react';

export default function CascaderDemo() {
    const treeData = [
        {
            label: '浙江省',
            value: 'zhejiang',
            children: [
                {
                    label: '杭州市',
                    value: 'hangzhou',
                    children: [
                        {
                            label: '西湖区',
                            value: 'xihu',
                        },
                        {
                            label: '萧山区',
                            value: 'xiaoshan',
                        },
                        {
                            label: '临安区',
                            value: 'linan',
                        },
                    ],
                },
                {
                    label: '宁波市',
                    value: 'ningbo',
                    children: [
                        {
                            label: '海曙区',
                            value: 'haishu',
                        },
                        {
                            label: '江北区',
                            value: 'jiangbei',
                        },
                    ],
                },
            ],
        },
    ];
    return <Cascader defaultValue={['zhejiang', 'ningbo', 'jiangbei']} style={{ width: 300 }} treeData={treeData} placeholder="请选择所在地区" multiple />;
}

可搜索的

通过设置 filterTreeNode 属性可支持搜索功能。

默认对 label 值进行搜索(使用字符串的 includes 方法进行匹配,不区分大小写),可通过 treeNodeFilterProp 指定其他属性值进行搜索。如 label 为 ReactNode,可在 treeData 中使用其他字段存储纯文本,并通过 treeNodeFilterProp 指定该字段进行搜索。

默认搜索结果只会展示叶子结点的路径,想要显示更多的结果,可以设置 filterLeafOnly 为 false。





filterLeafOnly=false:


Label 为自定义节点,指定其他属性进行搜索
代码
import { IconMapPinStroked } from '@fibrous/semi-icons-react';
import { Cascader, Typography } from '@fibrous/semi-ui-react';

// The label inside an option must not be focusable, so the node only adds a decorative icon to the text
const labelNode = (text: string) => (
    <span style={{ display: 'inline-flex', alignItems: 'center', columnGap: 4 }}>
        <IconMapPinStroked />
        {text}
    </span>
);

export default function CascaderDemo() {
    const treeData = [
        {
            label: '浙江省',
            value: 'zhejiang',
            children: [
                {
                    label: '杭州市',
                    value: 'hangzhou',
                    children: [
                        {
                            label: '西湖区',
                            value: 'xihu',
                        },
                        {
                            label: '萧山区',
                            value: 'xiaoshan',
                        },
                        {
                            label: '临安区',
                            value: 'linan',
                        },
                    ],
                },
                {
                    label: '宁波市',
                    value: 'ningbo',
                    children: [
                        {
                            label: '海曙区',
                            value: 'haishu',
                        },
                        {
                            label: '江北区',
                            value: 'jiangbei',
                        },
                    ],
                },
            ],
        },
    ];
    const labelNodeTreeData = [
        {
            label: labelNode('浙江省'),
            labelText: '浙江省',
            value: 'zhejiang',
            children: [
                {
                    label: labelNode('杭州市'),
                    labelText: '杭州市',
                    value: 'hangzhou',
                    children: [
                        {
                            label: labelNode('西湖区'),
                            labelText: '西湖区',
                            value: 'xihu',
                        },
                        {
                            label: labelNode('萧山区'),
                            labelText: '萧山区',
                            value: 'xiaoshan',
                        },
                        {
                            label: labelNode('临安区'),
                            labelText: '临安区',
                            value: 'linan',
                        },
                    ],
                },
                {
                    label: labelNode('宁波市'),
                    labelText: '宁波市',
                    value: 'ningbo',
                    children: [
                        {
                            label: labelNode('海曙区'),
                            labelText: '海曙区',
                            value: 'haishu',
                        },
                        {
                            label: labelNode('江北区'),
                            labelText: '江北区',
                            value: 'jiangbei',
                        },
                    ],
                },
            ],
        },
    ];

    return (
        <div>
            <Cascader style={{ width: 300 }} treeData={treeData} placeholder="默认对label值进行搜索" filterTreeNode />
            <br />
            <br />
            <Cascader style={{ width: 300 }} treeData={treeData} placeholder="对value值进行搜索" filterTreeNode treeNodeFilterProp="value" />
            <br />
            <br />
            <Typography.Title heading={6}>filterLeafOnly=false:</Typography.Title>
            <Cascader style={{ width: 300 }} treeData={treeData} placeholder="filterLeafOnly=false" filterTreeNode filterLeafOnly={false} />
            <br />
            <br />
            <Typography.Title heading={6}>Label 为自定义节点,指定其他属性进行搜索</Typography.Title>
            <Cascader style={{ width: 300 }} treeData={labelNodeTreeData} placeholder="Search for labelText" filterTreeNode treeNodeFilterProp="labelText" displayProp="labelText" />
        </div>
    );
}

可搜索的多选

支持多选和搜索同时使用,在这种场景下,可以通过按下 BackSpace 键来删除对应的已选项目。

海曙区
代码
import { useState } from 'react';
import { Cascader, type CascaderValue } from '@fibrous/semi-ui-react';

export default function CascaderDemo() {
    const [value, setValue] = useState<CascaderValue>(['zhejiang', 'ningbo', 'haishu']);
    const onChange = (newValue: CascaderValue) => {
        setValue(newValue);
    };
    const treeData = [
        {
            label: '浙江省',
            value: 'zhejiang',
            children: [
                {
                    label: '杭州市',
                    value: 'hangzhou',
                    children: [
                        {
                            label: '西湖区',
                            value: 'xihu',
                        },
                        {
                            label: '萧山区',
                            value: 'xiaoshan',
                        },
                        {
                            label: '临安区',
                            value: 'linan',
                        },
                    ],
                },
                {
                    label: '宁波市',
                    value: 'ningbo',
                    children: [
                        {
                            label: '海曙区',
                            value: 'haishu',
                        },
                        {
                            label: '江北区',
                            value: 'jiangbei',
                        },
                    ],
                },
            ],
        },
    ];
    return <Cascader style={{ width: 300 }} treeData={treeData} placeholder="请选择所在地区" value={value} multiple filterTreeNode onChange={onChange} />;
}

可以使用 filterSorter 对筛选后的数据进行排序。

代码
import { Cascader } from '@fibrous/semi-ui-react';

export default function CascaderDemo() {
    const treeData = [
        {
            label: 'Product',
            value: 'Product',
            children: [
                {
                    label: 'Semi-Material',
                    value: 'Semi-Material',
                },
                {
                    label: 'Semi-DSM',
                    value: 'Semi-DSM',
                },
                {
                    label: 'Semi',
                    value: 'Semi',
                },
                {
                    label: 'Semi-C2D',
                    value: 'Semi-C2D',
                },
                {
                    label: 'Semi-D2C',
                    value: 'Semi-D2C',
                },
            ],
        },
    ];
    return (
        <div>
            <Cascader
                style={{ width: 300 }}
                treeData={treeData}
                placeholder="输入 s 查看排序效果"
                filterTreeNode
                filterSorter={(first, second, inputValue) => {
                    const firstData = first[first.length - 1];
                    const lastData = second[second.length - 1];
                    if (firstData.label === inputValue) {
                        return -1;
                    } else if (lastData.label === inputValue) {
                        return 1;
                    } else {
                        return String(firstData.label) < String(lastData.label) ? -1 : 1;
                    }
                }}
            />
        </div>
    );
}

如果想要自定义渲染搜索后的选项,可以使用 filterRender 实现整行的自定义渲染,函数参数如下:

interface FilterRenderProps {
    className: string;
    inputValue: string; // 搜索栏搜索内容
    disabled: boolean; // 是否禁用
    data: CascaderData[]; // 搜索结果数据
    selected: boolean; // 单选时的选中状态
    checkStatus: {
        // 多选时的选中状态
        checked: boolean;
        halfChecked: boolean;
    };
    onClick: (e: React.MouseEvent) => void; // 单选点击选中回调
    onCheck: (e: CheckboxEvent) => void; // 多选点击选中回调,Checkbox 的 onChange 签名
}

使用示例如下

鼠标 hover 到选项可查看被省略文本完整内容



代码
import { Cascader, Typography, Checkbox, type CascaderFilterRenderProps } from '@fibrous/semi-ui-react';

export default function CascaderDemo() {
    const treeData = [
        {
            label: 'Semi',
            value: 'Semi',
            children: [
                {
                    label: 'Semi-Material Semi-Material Semi-Material Semi-Material',
                    value: 'Semi-Material',
                },
                {
                    label: 'Semi-DSM Semi-DSM Semi-DSM Semi-DSM',
                    value: 'Semi-DSM',
                },
                {
                    label: 'Semi Design Semi Design Semi Design Semi Design',
                    value: 'Semi',
                },
                {
                    label: 'Semi-C2D Semi-C2D Semi-C2D Semi-C2D Semi-C2D',
                    value: 'Semi-C2D',
                },
                {
                    label: 'Semi-D2C Semi-D2C Semi-D2C Semi-D2C Semi-D2C ',
                    value: 'Semi-D2C',
                },
            ],
        },
    ];
    const { Text } = Typography;

    const renderSearchOptionSingle = ({ className, data, onClick, selected }: CascaderFilterRenderProps) => {
        return (
            <li className={className} style={{ justifyContent: 'flex-start' }} role="option" aria-selected={selected} onClick={onClick}>
                <Text ellipsis={{ showTooltip: { opts: { style: { wordBreak: 'break-all' } } } }} style={{ width: 270, color: selected ? 'var(--semi-color-primary)' : undefined }}>
                    {data.map((item) => item.label).join(' / ')}
                </Text>
            </li>
        );
    };

    const renderSearchOptionMultiple = ({ className, data, checkStatus, onCheck }: CascaderFilterRenderProps) => {
        const label = data.map((item) => item.label).join(' / ');
        return (
            <li className={className} style={{ justifyContent: 'flex-start' }} role="option" aria-label={label} aria-selected={checkStatus.checked}>
                <Checkbox tabIndex={-1} onChange={onCheck} indeterminate={checkStatus.halfChecked} checked={checkStatus.checked}>
                    <Text ellipsis={{ showTooltip: { opts: { style: { wordBreak: 'break-all' } } } }} style={{ width: 250 }}>
                        {label}
                    </Text>
                </Checkbox>
            </li>
        );
    };

    return (
        <div>
            <p>鼠标 hover 到选项可查看被省略文本完整内容</p>
            <br />
            <Cascader style={{ width: 320 }} treeData={treeData} placeholder="单选,输入 s 自定义搜索选项渲染结果" filterTreeNode filterRender={renderSearchOptionSingle} />
            <br />
            <Cascader multiple style={{ width: 320, marginTop: 20 }} treeData={treeData} placeholder="多选,输入 s 自定义搜索选项渲染结果" filterTreeNode filterRender={renderSearchOptionMultiple} />
        </div>
    );
}

如果搜索结果中存在大量 Option,可以通过设置 virtualizeInSearch 开启搜索结果面板的虚拟化来优化性能,virtualizeInSearch 是一个包含下列值的对象:

  • height: Option 列表高度值(number,单位 px),缺省 180,与下拉框高度一致
  • width: Option 列表宽度值
  • itemSize: 每行 Option 的高度

限制标签展示数量

在多选的场景中,利用 maxTagCount 可以限制展示的标签数量,超出部分将以 +N 的方式展示。

使用 showRestTagsPopover 可以设置在超出 maxTagCount 后,hover +N 是否显示 Popover,默认为 false。并且,还可以在 restTagsPopoverProps 属性中配置 Popover。

代码
import { Cascader } from '@fibrous/semi-ui-react';

export default function CascaderDemo() {
    const treeData = [
        {
            label: '浙江省',
            value: 'zhejiang',
            children: [
                {
                    label: '杭州市',
                    value: 'hangzhou',
                    children: [
                        {
                            label: '西湖区',
                            value: 'xihu',
                        },
                        {
                            label: '萧山区',
                            value: 'xiaoshan',
                        },
                        {
                            label: '临安区',
                            value: 'linan',
                        },
                    ],
                },
                {
                    label: '宁波市',
                    value: 'ningbo',
                    children: [
                        {
                            label: '海曙区',
                            value: 'haishu',
                        },
                        {
                            label: '江北区',
                            value: 'jiangbei',
                        },
                    ],
                },
            ],
        },
    ];
    return (
        <Cascader
            style={{ width: 300 }}
            treeData={treeData}
            placeholder="请选择所在地区"
            multiple
            showRestTagsPopover
            restTagsPopoverProps={{ position: 'top' }}
            maxTagCount={1}
            defaultValue={[
                ['zhejiang', 'ningbo', 'haishu'],
                ['zhejiang', 'hangzhou', 'xihu'],
            ]}
        />
    );
}

限制选中数量

在多选的场景中,利用 max 可以限制多选选中的数量。超出 max 后将触发 onExceed 回调。

代码
import { Cascader, Toast } from '@fibrous/semi-ui-react';

export default function CascaderDemo() {
    const treeData = [
        {
            label: '浙江省',
            value: 'zhejiang',
            children: [
                {
                    label: '杭州市',
                    value: 'hangzhou',
                    children: [
                        {
                            label: '西湖区',
                            value: 'xihu',
                        },
                        {
                            label: '萧山区',
                            value: 'xiaoshan',
                        },
                        {
                            label: '临安区',
                            value: 'linan',
                        },
                    ],
                },
                {
                    label: '宁波市',
                    value: 'ningbo',
                    children: [
                        {
                            label: '海曙区',
                            value: 'haishu',
                        },
                        {
                            label: '江北区',
                            value: 'jiangbei',
                        },
                    ],
                },
            ],
        },
    ];
    return (
        <Cascader
            style={{ width: 300 }}
            treeData={treeData}
            placeholder="请选择所在地区"
            multiple
            max={1}
            onExceed={(v) => {
                Toast.warning('exceed max');
                console.log(v);
            }}
            defaultValue={[['zhejiang', 'ningbo', 'haishu']]}
        />
    );
}

选择即改变

在单选的情况下,还可以通过设置 changeOnSelect,允许选中父级选项。



代码
import { Cascader } from '@fibrous/semi-ui-react';

export default function CascaderDemo() {
    const treeData = [
        {
            label: '浙江省',
            value: 'zhejiang',
            children: [
                {
                    label: '杭州市',
                    value: 'hangzhou',
                    children: [
                        {
                            label: '西湖区',
                            value: 'xihu',
                        },
                        {
                            label: '萧山区',
                            value: 'xiaoshan',
                        },
                        {
                            label: '临安区',
                            value: 'linan',
                        },
                    ],
                },
                {
                    label: '宁波市',
                    value: 'ningbo',
                    children: [
                        {
                            label: '海曙区',
                            value: 'haishu',
                        },
                        {
                            label: '江北区',
                            value: 'jiangbei',
                        },
                    ],
                },
            ],
        },
    ];
    return (
        <div>
            <Cascader style={{ width: 300 }} treeData={treeData} changeOnSelect placeholder="选择即改变" />
            <br />
            <br />
            <Cascader style={{ width: 300 }} treeData={treeData} changeOnSelect placeholder="可搜索的选择即改变" filterTreeNode />
        </div>
    );
}

自定义显示

可以通过 displayProp 设置回填选项显示的属性值,默认为 label。

单选


多选
代码
import { Cascader, Typography } from '@fibrous/semi-ui-react';

export default function CascaderDemo() {
    const treeData = [
        {
            label: '浙江省',
            value: 'zhejiang',
            children: [
                {
                    label: '杭州市',
                    value: 'hangzhou',
                    children: [
                        {
                            label: '西湖区',
                            value: 'xihu',
                        },
                        {
                            label: '萧山区',
                            value: 'xiaoshan',
                        },
                        {
                            label: '临安区',
                            value: 'linan',
                        },
                    ],
                },
                {
                    label: '宁波市',
                    value: 'ningbo',
                    children: [
                        {
                            label: '海曙区',
                            value: 'haishu',
                        },
                        {
                            label: '江北区',
                            value: 'jiangbei',
                        },
                    ],
                },
            ],
        },
    ];
    return (
        <>
            <Typography.Title heading={6}>单选</Typography.Title>
            <Cascader style={{ width: 300 }} treeData={treeData} placeholder="回填时显示数据的value值" displayProp="value" defaultValue={['zhejiang', 'ningbo', 'jiangbei']} />
            <br />
            <br />
            <Typography.Title heading={6}>多选</Typography.Title>
            <Cascader multiple style={{ width: 300 }} treeData={treeData} defaultValue={['zhejiang', 'ningbo', 'jiangbei']} placeholder="回填时显示数据的value值" displayProp="value" />
        </>
    );
}

可以通过设置 displayRender 可以设定返回格式。

单选 (multiple=false) 时, displayRender((labelPath: ReactNode[]) => ReactNode), 其中 labelPath 是由 label 构成的 path 数组。

多选 (multiple=true) 时, displayRender((item: Entity, index: number) => ReactNode), 其中 item 为节点的相关数据,Entity 如下:

interface Entity {
    children?: Entity[]; // 子节点列表
    data: CascaderData; // treedata
    ind: number; // index
    key: string; // key
    level: number; // 节点层级
    parent?: Entity; // 父节点数据
    parentKey?: string; // 父节点 key
    path: string[]; // key 路径
    pos: string; // 节点在 treeData 中的层级位置,如 '0-0-1'
    valuePath: string[]; // value 路径
}
单选


多选
代码
import { Cascader, Tag, Typography } from '@fibrous/semi-ui-react';

export default function CascaderDemo() {
    const treeData = [
        {
            label: '浙江省',
            value: 'zhejiang',
            children: [
                {
                    label: '杭州市',
                    value: 'hangzhou',
                    children: [
                        {
                            label: '西湖区',
                            value: 'xihu',
                        },
                        {
                            label: '萧山区',
                            value: 'xiaoshan',
                        },
                        {
                            label: '临安区',
                            value: 'linan',
                        },
                    ],
                },
                {
                    label: '宁波市',
                    value: 'ningbo',
                    children: [
                        {
                            label: '海曙区',
                            value: 'haishu',
                        },
                        {
                            label: '江北区',
                            value: 'jiangbei',
                        },
                    ],
                },
            ],
        },
    ];
    return (
        <>
            <Typography.Title heading={6}>单选</Typography.Title>
            <Cascader style={{ width: 300 }} treeData={treeData} placeholder="自定义回填时显示数据的格式" displayRender={(list) => '已选择:' + list.join(' -> ')} defaultValue={['zhejiang', 'ningbo', 'jiangbei']} />
            <br />
            <br />
            <Typography.Title heading={6}>多选</Typography.Title>
            <Cascader
                multiple
                style={{ width: 300 }}
                treeData={treeData}
                defaultValue={['zhejiang', 'ningbo', 'jiangbei']}
                placeholder="自定义回填时显示数据的格式"
                displayRender={(item, idx) => (
                    <Tag style={{ marginRight: 4 }} color="white" key={`${idx}-${item.data.label}`}>
                        {item.data.label}
                    </Tag>
                )}
            />
        </>
    );
}

自定义分隔符

可以使用 separator 设置分隔符, 包括:搜索时显示在下拉框的内容以及单选时回显到 Trigger 的内容的分隔符。

代码
import { Cascader } from '@fibrous/semi-ui-react';

export default function CascaderDemo() {
    const treeData = [
        {
            label: '浙江省',
            value: 'zhejiang',
            children: [
                {
                    label: '杭州市',
                    value: 'hangzhou',
                    children: [
                        {
                            label: '西湖区',
                            value: 'xihu',
                        },
                        {
                            label: '萧山区',
                            value: 'xiaoshan',
                        },
                        {
                            label: '临安区',
                            value: 'linan',
                        },
                    ],
                },
                {
                    label: '宁波市',
                    value: 'ningbo',
                    children: [
                        {
                            label: '海曙区',
                            value: 'haishu',
                        },
                        {
                            label: '江北区',
                            value: 'jiangbei',
                        },
                    ],
                },
            ],
        },
    ];
    return <Cascader style={{ width: 300 }} treeData={treeData} defaultValue={['zhejiang', 'ningbo', 'jiangbei']} filterTreeNode separator=" > " />;
}

禁用



代码
import { Cascader } from '@fibrous/semi-ui-react';

export default function CascaderDemo() {
    const treeData = [
        {
            label: '浙江省',
            value: 'zhejiang',
            children: [
                {
                    label: '杭州市',
                    value: 'hangzhou',
                    children: [
                        {
                            label: '西湖区',
                            value: 'xihu',
                        },
                        {
                            label: '萧山区',
                            value: 'xiaoshan',
                        },
                        {
                            label: '临安区',
                            value: 'linan',
                        },
                    ],
                },
            ],
        },
    ];
    return (
        <div>
            <Cascader style={{ width: 300 }} treeData={treeData} placeholder="请选择所在地区" disabled />
            <br />
            <br />
            <Cascader style={{ width: 300 }} treeData={treeData} placeholder="请选择所在地区" defaultValue={['zhejiang', 'hangzhou', 'xihu']} filterTreeNode disabled />
        </div>
    );
}

严格禁用

可以使用 disableStrictly 来开启严格禁用。开启严格禁用后,当节点是 disabled 的时候,则不能通过子级或者父级的关系改变选中状态。

以下面的 demo 为例,节点"宁波"开启了严格禁用,因此,当我们改变其父节点"浙江省"的选中状态时,也不会影响到节点"宁波"的选中状态。

代码
import { Cascader } from '@fibrous/semi-ui-react';

export default function CascaderDemo() {
    const treeData = [
        {
            label: '浙江省',
            value: 'zhejiang',
            children: [
                {
                    label: '杭州市',
                    value: 'hangzhou',
                    children: [
                        {
                            label: '西湖区',
                            value: 'xihu',
                        },
                        {
                            label: '萧山区',
                            value: 'xiaoshan',
                        },
                        {
                            label: '临安区',
                            value: 'linan',
                        },
                    ],
                },
                {
                    label: '宁波市',
                    value: 'ningbo',
                    disabled: true,
                    children: [
                        {
                            label: '海曙区',
                            value: 'haishu',
                        },
                        {
                            label: '江北区',
                            value: 'jiangbei',
                        },
                    ],
                },
            ],
        },
    ];
    return <Cascader style={{ width: 300 }} treeData={treeData} multiple placeholder="请选择所在地区" disableStrictly />;
}

展示子菜单的时机

可以使用 showNext 设置展开 Dropdown 子菜单的触发时机,可选: click(默认)、hover。

代码
import { Cascader } from '@fibrous/semi-ui-react';

export default function CascaderDemo() {
    const treeData = [
        {
            label: '浙江省',
            value: 'zhejiang',
            children: [
                {
                    label: '杭州市',
                    value: 'hangzhou',
                    children: [
                        {
                            label: '西湖区',
                            value: 'xihu',
                        },
                        {
                            label: '萧山区',
                            value: 'xiaoshan',
                        },
                        {
                            label: '临安区',
                            value: 'linan',
                        },
                    ],
                },
            ],
        },
    ];
    return <Cascader style={{ width: 300 }} treeData={treeData} placeholder="请选择所在地区" showNext="hover" />;
}

点击选中

在多选模式下,默认情况下点击非叶子节点不会触发选中。你可以通过 clickToSelect 开启点击任意节点即选中的功能。

这个 API 在配合 showNext="hover" 使用时特别有用:鼠标悬浮展开子菜单,点击则选中当前节点。

代码
import { Cascader } from '@fibrous/semi-ui-react';

export default function CascaderDemo() {
    const treeData = [
        {
            label: '浙江省',
            value: 'zhejiang',
            children: [
                {
                    label: '杭州市',
                    value: 'hangzhou',
                    children: [
                        {
                            label: '西湖区',
                            value: 'xihu',
                        },
                        {
                            label: '萧山区',
                            value: 'xiaoshan',
                        },
                    ],
                },
                {
                    label: '宁波市',
                    value: 'ningbo',
                    children: [
                        {
                            label: '海曙区',
                            value: 'haishu',
                        },
                    ],
                },
            ],
        },
    ];
    return <Cascader style={{ width: 300 }} treeData={treeData} multiple placeholder="请选择所在地区" showNext="hover" clickToSelect />;
}

在顶部/底部渲染附加项

我们在级联选择器的顶部、底部分别预留了插槽,你可以通过 topSlot 或 bottomSlot 来设置。

代码
import { Cascader, Typography } from '@fibrous/semi-ui-react';

export default function CascaderDemo() {
    const { Text } = Typography;
    const slotStyle = {
        height: '36px',
        display: 'flex',
        padding: '0 32px',
        alignItems: 'center',
        cursor: 'pointer',
        borderTop: '1px solid var(--semi-color-border)',
    };
    const treeData = [
        {
            label: '浙江省',
            value: 'zhejiang',
            children: [
                {
                    label: '杭州市',
                    value: 'hangzhou',
                    children: [
                        {
                            label: '西湖区',
                            value: 'xihu',
                        },
                        {
                            label: '萧山区',
                            value: 'xiaoshan',
                        },
                        {
                            label: '临安区',
                            value: 'linan',
                        },
                    ],
                },
                {
                    label: '宁波市',
                    value: 'ningbo',
                    children: [
                        {
                            label: '海曙区',
                            value: 'haishu',
                        },
                        {
                            label: '江北区',
                            value: 'jiangbei',
                        },
                    ],
                },
            ],
        },
    ];
    return (
        <Cascader
            style={{ width: 300 }}
            treeData={treeData}
            placeholder="请选择所在地区"
            bottomSlot={
                <div style={slotStyle}>
                    <Text>找不到相关选项?</Text>
                    <Text link>去新建</Text>
                </div>
            }
        />
    );
}

受控

传入 value 时即为受控组件,可以配合 onChange 使用。

代码
import { useState } from 'react';
import { Cascader, type CascaderSimpleValue } from '@fibrous/semi-ui-react';

export default function CascaderDemo() {
    const [value, setValue] = useState<CascaderSimpleValue[]>([]);
    const onChange = (newValue: CascaderSimpleValue[]) => {
        setValue(newValue);
    };
    const treeData = [
        {
            label: '浙江省',
            value: 'zhejiang',
            children: [
                {
                    label: '杭州市',
                    value: 'hangzhou',
                    children: [
                        {
                            label: '西湖区',
                            value: 'xihu',
                        },
                        {
                            label: '萧山区',
                            value: 'xiaoshan',
                        },
                        {
                            label: '临安区',
                            value: 'linan',
                        },
                    ],
                },
                {
                    label: '宁波市',
                    value: 'ningbo',
                    children: [
                        {
                            label: '海曙区',
                            value: 'haishu',
                        },
                        {
                            label: '江北区',
                            value: 'jiangbei',
                        },
                    ],
                },
            ],
        },
    ];
    return <Cascader style={{ width: 300 }} treeData={treeData} placeholder="请选择所在地区" value={value} onChange={onChange} />;
}

自动合并 value

在多选(multiple=true)场景中,当我们选中祖先节点时,如果希望 value 不包含它对应的子孙节点,则可以通过 autoMergeValue 来设置,默认为 true。当 autoMergeValue 和 leafOnly 同时开启时,后者优先级更高。

代码
import { useState } from 'react';
import { Cascader, type CascaderValue } from '@fibrous/semi-ui-react';

export default function CascaderDemo() {
    const [value, setValue] = useState<CascaderValue>([]);
    const onChange = (value: CascaderValue) => {
        console.log(value);
        setValue(value);
    };
    const treeData = [
        {
            label: '浙江省',
            value: 'zhejiang',
            children: [
                {
                    label: '杭州市',
                    value: 'hangzhou',
                    children: [
                        {
                            label: '西湖区',
                            value: 'xihu',
                        },
                        {
                            label: '萧山区',
                            value: 'xiaoshan',
                        },
                        {
                            label: '临安区',
                            value: 'linan',
                        },
                    ],
                },
                {
                    label: '宁波市',
                    value: 'ningbo',
                    children: [
                        {
                            label: '海曙区',
                            value: 'haishu',
                        },
                        {
                            label: '江北区',
                            value: 'jiangbei',
                        },
                    ],
                },
            ],
        },
    ];
    return <Cascader style={{ width: 300 }} treeData={treeData} placeholder="autoMergeValue 为 false" value={value} multiple autoMergeValue={false} onChange={(e) => onChange(e)} />;
}

仅叶子节点

在多选时,可以通过开启 leafOnly 来设置 value 只包含叶子节点,即显示的 Tag 和 onChange 的参数 value 只包含 value。

代码
import { useState } from 'react';
import { Cascader, type CascaderValue } from '@fibrous/semi-ui-react';

export default function CascaderDemo() {
    const [value, setValue] = useState<CascaderValue>([]);
    const onChange = (value: CascaderValue) => {
        console.log(value);
        setValue(value);
    };
    const treeData = [
        {
            label: '浙江省',
            value: 'zhejiang',
            children: [
                {
                    label: '杭州市',
                    value: 'hangzhou',
                    children: [
                        {
                            label: '西湖区',
                            value: 'xihu',
                        },
                        {
                            label: '萧山区',
                            value: 'xiaoshan',
                        },
                        {
                            label: '临安区',
                            value: 'linan',
                        },
                    ],
                },
                {
                    label: '宁波市',
                    value: 'ningbo',
                    children: [
                        {
                            label: '海曙区',
                            value: 'haishu',
                        },
                        {
                            label: '江北区',
                            value: 'jiangbei',
                        },
                    ],
                },
            ],
        },
    ];
    return <Cascader style={{ width: 300 }} treeData={treeData} placeholder="开启 leafOnly" value={value} multiple leafOnly onChange={(e) => onChange(e)} />;
}

节点选中关系

多选时,可以使用 checkRelation 来设置节点之间选中关系的类型,可选:'related'(默认)、'unRelated'。当选中关系为 'unRelated' 时,意味着节点之间的选中互不影响。

代码
import { Cascader } from '@fibrous/semi-ui-react';
export default function CascaderDemo() {
    const treeData = [
        {
            label: '亚洲',
            value: 'Asia',
            children: [
                {
                    label: '中国',
                    value: 'China',
                    children: [
                        {
                            label: '北京',
                            value: 'Beijing',
                        },
                        {
                            label: '上海',
                            value: 'Shanghai',
                        },
                    ],
                },
            ],
        },
        {
            label: '北美洲',
            value: 'North America',
        },
    ];
    return <Cascader multiple defaultValue={[['Asia'], ['Asia', 'China', 'Beijing']]} checkRelation="unRelated" style={{ width: 300 }} treeData={treeData} />;
}

动态更新数据



代码
import { useState } from 'react';
import { Cascader, Button, type CascaderData } from '@fibrous/semi-ui-react';

export default function CascaderDemo() {
    const [treeData, setTreeData] = useState<CascaderData[]>([]);

    const add = () => {
        let itemLength = Math.floor(Math.random() * 3) + 1;
        let newTreeData = Array.from({ length: itemLength }, (_v, i) => {
            let length = Math.floor(Math.random() * 3);
            let children = Array.from({ length }, (_cv, ci) => {
                let child = {
                    key: `${i}-${ci}`,
                    label: `Item-${i}-${ci}`,
                    value: `${i}-${ci}`,
                };
                return child;
            });
            let item = {
                key: `${i}`,
                label: `Item-${i}`,
                value: `${i}`,
                children,
            };
            return item;
        });
        setTreeData(newTreeData);
    };

    return (
        <>
            <Cascader style={{ width: 300 }} treeData={treeData} placeholder="请选择" />
            <br />
            <br />
            <Button onClick={add}>动态改变数据</Button>
        </>
    );
}

异步加载数据

可以使用 loadData 实现异步加载数据

不能与搜索同时使用

代码
import { useState } from 'react';
import { Cascader, type CascaderData } from '@fibrous/semi-ui-react';

export default function CascaderDemo() {
    const initialData: CascaderData[] = [
        {
            label: 'Node1',
            value: '0-0',
        },
        {
            label: 'Node2',
            value: '0-1',
        },
        {
            label: 'Node3',
            value: '0-2',
            isLeaf: true,
        },
    ];
    const [data, setData] = useState(initialData);

    const updateTreeData = (list: CascaderData[], value: CascaderData['value'], children: CascaderData[]): CascaderData[] => {
        return list.map((node) => {
            if (node.value === value) {
                return { ...node, children };
            }
            if (node.children) {
                return { ...node, children: updateTreeData(node.children, value, children) };
            }
            return node;
        });
    };

    const onLoadData = (selectedOpt: CascaderData[]) => {
        const targetOpt = selectedOpt[selectedOpt.length - 1];
        const { label, value } = targetOpt;
        return new Promise<void>((resolve) => {
            if (targetOpt.children) {
                resolve();
                return;
            }

            setTimeout(() => {
                setData((origin) =>
                    updateTreeData(origin, value, [
                        {
                            label: `${label} - 1`,
                            value: `${label}-1`,
                            isLeaf: selectedOpt.length > 1,
                        },
                        {
                            label: `${label} - 2`,
                            value: `${label}-2`,
                            isLeaf: selectedOpt.length > 1,
                        },
                    ])
                );
                resolve();
            }, 1000);
        });
    };

    return <Cascader style={{ width: 300 }} treeData={data} loadData={onLoadData} placeholder="Please select" />;
}

远程搜索

Cascader 支持远程搜索。设置 remote 后,搜索输入不再走本地匹配,而是仅触发 onSearch 回调,由你根据输入异步拉取 treeData。这与 Select 的 remote 行为一致。

超长列表

当你的数据结构层级特别深时,Cascader下拉菜单可能会超出屏幕,此时我们建议为下拉菜单设置 overflow-x: auto 以及一个合适的 width 宽度( 建议以N+0.5列的宽度为准,最右侧显示半列,以给用户一种右侧尚有待展开项,可以水平方向滚动的视觉暗示)

代码
import { Cascader } from '@fibrous/semi-ui-react';

export default function CascaderDemo() {
    const treeData = [
        {
            label: 'A',
            value: 'A',
            children: [
                {
                    label: 'B',
                    value: 'B',
                    children: [
                        {
                            label: 'C',
                            value: 'C',
                            children: [
                                {
                                    label: 'D',
                                    value: 'D',
                                    children: [
                                        {
                                            label: 'E',
                                            value: 'E',
                                            children: [
                                                {
                                                    label: 'F',
                                                    value: 'F',
                                                },
                                            ],
                                        },
                                    ],
                                },
                            ],
                        },
                    ],
                },
            ],
        },
    ];
    return <Cascader dropdownClassName="components-cascader-demo" style={{ width: 300 }} treeData={treeData} placeholder="请选择所在地区" />;
}
.components-cascader-demo {
    .semi-cascader-option-lists {
        max-width: 510px;
        overflow-x: auto;
    }
}

自定义 Trigger

如果默认的触发器样式满足不了你的需求,可以用triggerRender自定义选择框的展示

triggerRender 入参如下

interface TriggerRenderProps {
    /* Cascader 的 props */
    componentProps: CascaderProps;
    /* 是否禁用 Cascader */
    disabled: boolean;
    /**
     * 已选中的 node 在 treeData 中的层级位置,如下例子,
     * 当选中浙江省-杭州市-萧山区时,此处 value 为 '0-0-1'
     */
    value?: string | Set<string>;
    /* 展开时为搜索词;可搜索的单选收起时为已选路径的展示文本,设置 displayRender 时为其返回值 */
    inputValue: ReactNode;
    /**
     * 用于更新 input 框值的函数,当你在 triggerRender 自定义的
     * Input 组件值更新时,你应该调用该函数,用于向 Cascader 内部
     * 同步状态, 使用时需要设置 filterTreeNode 参数非 false
     */
    onSearch: (inputValue: string) => void;
    /* 用于清空值的函数,入参为点击事件 */
    onClear: (e: React.MouseEvent) => void;
    /* Placeholder */
    placeholder?: string;
    /* 用于删除单个 item,入参为该节点在 treeData 中的层级位置(与 value 的格式相同) */
    onRemove: (pos: string) => void;
}

代码
import { useCallback } from 'react';
import { IconClose, IconChevronDown } from '@fibrous/semi-icons-react';
import { Cascader, Button, ButtonGroup, Tag, TagInput, type CascaderData, type CascaderTriggerRenderProps, type TagProps } from '@fibrous/semi-ui-react';

const treeData: CascaderData[] = [
    {
        label: '浙江省',
        value: 'zhejiang',
        children: [
            {
                label: '杭州市',
                value: 'hangzhou',
                children: [
                    {
                        label: '西湖区',
                        value: 'xihu',
                    },
                    {
                        label: '萧山区',
                        value: 'xiaoshan',
                    },
                    {
                        label: '临安区',
                        value: 'linan',
                    },
                ],
            },
            {
                label: '宁波市',
                value: 'ningbo',
                children: [
                    {
                        label: '海曙区',
                        value: 'haishu',
                    },
                    {
                        label: '江北区',
                        value: 'jiangbei',
                    },
                ],
            },
        ],
    },
];

const getLabelFromValue = (value: string) => {
    const [first, ...rest] = value.split('-').map((item) => Number(item));
    let node: CascaderData | undefined = treeData[first];
    rest.forEach((item) => {
        node = node?.children?.[item];
    });
    return node?.label;
};

export default function CascaderDemo() {
    const triggerRenderSingle = ({ value, placeholder, onClear }: CascaderTriggerRenderProps) => {
        // The clear button is a sibling of the trigger button: buttons cannot nest
        return (
            <ButtonGroup theme="light">
                <Button icon={<IconChevronDown />} iconPosition="right">
                    {value && value.length > 0 ? getLabelFromValue(value) : placeholder}
                </Button>
                {Boolean(value) && (
                    <Button
                        icon={<IconClose />}
                        aria-label="清除"
                        onClick={(e) => {
                            e.stopPropagation();
                            onClear(e);
                        }}
                    />
                )}
            </ButtonGroup>
        );
    };

    const triggerRenderMultiple = useCallback(({ value, onSearch, onRemove }: CascaderTriggerRenderProps<true>) => {
        const onCloseTag: TagProps['onClose'] = (_value, _e, tagKey) => {
            onRemove(String(tagKey));
        };

        const renderTagItem = (value: string) => {
            const label = getLabelFromValue(value);
            return (
                <Tag tagKey={value} key={value} closable onClose={onCloseTag} style={{ marginLeft: 2 }}>
                    {label}
                </Tag>
            );
        };

        return <TagInput value={Array.from(value)} onInputChange={onSearch} renderTagItem={renderTagItem} />;
    }, []);

    return (
        <>
            <Cascader treeData={treeData} placeholder="Custom Trigger" triggerRender={triggerRenderSingle} />
            <br />
            <Cascader triggerRender={triggerRenderMultiple} multiple filterTreeNode treeData={treeData} style={{ width: 300 }} placeholder="Custom Trigger" />
        </>
    );
}

API 参考

Cascader

Props

属性说明类型默认值
arrowIcon自定义右侧下拉箭头,传入 null 时不显示箭头。showClear 开启且有选中值时,悬停优先显示清除按钮。ReactNodeType<IconChevronDown />
autoAdjustOverflow下拉被边缘遮挡时是否自动调整展开方向,默认为 true。boolean-
autoMergeValue多选时,选中父节点后 value 不再包含它的子孙节点。不支持动态切换。leafOnly 也开启时,leafOnly 优先。booleantrue
borderless是否使用无边框模式。booleanfalse
bottomSlot下拉底部的插槽内容。ReactNode-
changeOnSelect单选时是否允许选择非叶子节点。booleanfalse
checkRelation多选时节点之间的选中关系:related 为父子联动,unRelated 为互不影响。'related' | 'unRelated'"related"
children类型上保留的 children,组件不渲染它。React.ReactNode-
className选择框根元素(触发器)的类名。string-
clearIcon自定义清除按钮的图标,showClear 为 true 时有效。ReactNode-
clickToSelect多选时点击任意节点即选中,常与 showNext="hover" 配合:悬停展开子菜单,点击选中当前节点。booleanfalse
defaultOpen是否在挂载时默认展开下拉。booleanfalse
defaultValue默认选中的条目,只在挂载时读取:单选为节点或路径,多选为路径数组。TypeModeValue<Multiple>-
disabled是否禁用。boolean-
disableStrictly是否开启严格禁用。开启后,禁用的节点不会因父级或子级的选中关系改变选中状态。booleanfalse
displayProp回填到触发器与标签中的节点属性。string"label"
displayRender自定义回填内容。单选时参数为路径上各级节点 label 的数组,返回回填内容;多选时参数为节点的 Entity 与索引,返回该标签的内容。未设置时,单选以 separator 连接各级 label。TypeByMultiple<Multiple, (selected: Array<ReactNode>) => ReactNode, (selected: Entity, idx: number) => ReactNode>-
dropdownClassName下拉菜单的类名。string-
dropdownStyle下拉菜单的内联样式。CSSProperties-
emptyContent没有可选数据或搜索无结果时展示的内容。未设置时使用语言包中的「暂无数据」,传入 null 则不显示。ReactNode-
enableLeafClick多选时,点击叶子节点的选项是否触发勾选。booleanfalse
expandIcon自定义子菜单的展开图标。ReactNode-
filterLeafOnly搜索结果是否只展示叶子节点的路径。booleantrue
filterRender自定义渲染每一条搜索结果,参数为 FilterRenderProps。返回单个元素时,该元素获得选项的 id、role="option" 和选中状态;返回文本或多个节点时,组件包一层 li。Type(props: FilterRenderProps) => ReactNode-
filterSorter对搜索结果排序。参数为两条结果路径(从根节点到匹配节点的节点数组)与输入内容,返回比较值。Type(first: CascaderData[], second: CascaderData[], inputValue: string) => number-
filterTreeNode是否开启搜索。为 true 时按 treeNodeFilterProp 的值匹配输入,不区分大小写;也可以传入匹配函数,参数为输入内容、节点的属性字符串与节点数据。Type((inputValue: string, treeNodeString: string, data?: CascaderData) => boolean) | booleanfalse
id选择框根元素(触发器)的 id。string-
insetLabel嵌入触发器内的标签,作为组合框的名称来源;有 prefix 时不渲染。ReactNode-
insetLabelIdinsetLabel 内容的 id,用于关联组合框的名称;未设置时由组件生成。string-
keyMaps自定义节点中 value、label、children、disabled、isLeaf 的字段名。KeyMapProps-
leafOnly多选时 value 与标签只包含叶子节点。不支持动态切换。booleanfalse
loadData异步加载数据,参数为从根到当前节点的路径,需要返回 Promise。不能与搜索同时使用。Type(selectOptions: CascaderData[]) => Promise<void>-
max多选时选中数量的上限,超出后触发 onExceed。number-
maxTagCount多选时标签的最大展示数量,超出部分以 +N 展示。number-
motion下拉框弹出时是否有动画。booleantrue
multiple是否多选。它改变 value、defaultValue、onChange、onSelect、displayRender 与 triggerRender 的类型。Multiplefalse
onBlur失焦或关闭下拉时的回调,不是 DOM 的 blur 事件。点击外部时参数为原生 MouseEvent,Esc 关闭时为 KeyboardEvent,通过 ref 的 close() 关闭时为 undefined。Type(e?: MouseEvent | React.KeyboardEvent | globalThis.MouseEvent) => void-
onChange选中项变化时的回调。参数为选中路径的 value 数组,多选时为路径数组;onChangeWithObject 为 true 时参数为节点数据。Type(value: ByMultiple<Multiple, Array<SimpleValueType>, Array<Array<SimpleValueType>>>) => void-
onChangeWithObject为 true 时 onChange 的参数为节点数据(含 value 键),而不是 value;受控的 value 与 defaultValue 也要传节点数据。boolean-
onClear点击清除按钮时的回调。Type() => void-
onDropdownVisibleChange下拉展开或收起时的回调,参数为是否可见。Type(visible: boolean) => void-
onExceed多选时超出 max 后的回调,参数为已选中的 Entity 数组。Type(checkedItem: Entity[]) => void-
onFocus组合框获得焦点,或下拉被鼠标、键盘打开时的回调,不是 DOM 的 focus 事件。Type(e: MouseEvent | React.KeyboardEvent) => void-
onListScroll列视图中某一列滚动时的回调,参数为事件与 { panelIndex, activeNode };搜索结果列表的滚动不触发。Type(e: React.UIEvent<HTMLUListElement>, panel: ScrollPanelProps) => void-
onLoad节点加载完毕时的回调,参数为新加载的 key 集合与该节点的数据。Type(newLoadedKeys: Set<string>, data: CascaderData) => void-
onSearch搜索文本变化时的回调,参数为输入的内容。Type(value: string) => void-
onSelect节点被选中时的回调,参数为选中项的 value;多选时只选中一个节点为单个值,否则为数组。Type(value: ByMultiple<Multiple, string | number, string | number | Array<string | number>>) => void-
placeholder选择框的占位文字。string-
position下拉的展开方向,默认 bottomLeft(RTL 下为 bottomRight)。Position-
prefix前缀标签,渲染在触发器内左侧。ReactNode-
preventScroll组件内的 focus 是否阻止浏览器滚动文档以显示获得焦点的元素。boolean-
ref接收 CascaderHandle 句柄,下方 Methods 列出的 open、close、focus、blur 与 search 方法挂在其上。TypeReact.Ref<CascaderHandle>-
remote是否开启远程搜索。开启后输入不再做本地过滤,只触发 onSearch,由使用方异步更新 treeData,行为与 Select 的 remote 一致。booleanfalse
restTagsPopoverProps剩余标签 Popover 的属性,如 position、trigger,类型同 PopoverProps。PopoverProps{}
searchPlaceholder搜索框的占位文字,未设置时使用 placeholder。string-
searchPosition搜索框的位置:trigger 在触发器内,custom 由使用方自定义,此时用 ref 的 search 方法传入搜索词。'trigger' | 'custom'"trigger"
separator路径的分隔符,用于搜索结果,以及单选时回填到触发器的内容。string" / "
showClear是否展示清除按钮。booleanfalse
showNext展开子菜单的时机:click 或 hover。ShowNextType"click"
showRestTagsPopover超出 maxTagCount 后,悬停 +N 时是否用 Popover 展示剩余标签。booleanfalse
size选择框的尺寸,可选 small、default、large。CascaderType"default"
stopPropagation是否阻止下拉框上的点击事件冒泡。boolean | stringtrue
style选择框根元素(触发器)的样式。CSSProperties-
suffix后缀标签,渲染在触发器内右侧。ReactNode-
topSlot下拉顶部的插槽内容。ReactNode-
treeData节点数据,结构见 CascaderData。TypeArray<CascaderData>[]
treeNodeFilterProp搜索时用于匹配输入的节点属性。string"label"
triggerRender自定义触发器的渲染,参数为 TriggerRenderProps,返回的元素替换默认的触发器内容。Type(props: TriggerRenderProps<Multiple>) => ReactNode-
validateStatus触发器的校验状态,可选 default、error、warning,只影响样式;未设置时等同 default。ValidateStatus-
value受控的选中条目,类型同 defaultValue。传入后即为受控组件,配合 onChange 更新。TypeModeValue<Multiple>-
virtualizeInSearch搜索结果的虚拟列表配置,由 height、width、itemSize 组成,用于结果很多的场景;height 缺省为 180。Virtualize-

Methods

方法说明类型
blur使组合框失焦。Type() => void
close关闭下拉列表。Type() => void
focus聚焦组合框(搜索框或触发器),不展开下拉。Type() => void
open展开下拉列表。Type() => void
search按传入的文本搜索,需同时设置 filterTreeNode;searchPosition 为 custom 时由使用方自定义展示搜索框。Type(value: string) => void

aria-label、aria-labelledby、aria-describedby、aria-errormessage、aria-invalid、aria-required 落在当前的 combobox 上(见 ARIA),其余未声明的原生属性(如 data-*、onClick)落在根元素(触发器)上。onFocus、onBlur 在打开、关闭下拉时触发,不是 DOM 焦点事件。

CascaderData

属性说明类型默认值
children子节点CascaderData[]-
disabled不可选状态boolean-
isLeaf叶子节点boolean-
label展示的文本(必填)ReactNode-
loading正在加载boolean-
value属性值(必填)string|number-

Methods

通过 ref 拿到的 CascaderHandle 上挂有 open、close、focus、blur 与 search 方法,可以用来实现某些特殊交互,各方法的参数与说明见上方 API 参考。

Accessibility

ARIA

  • Cascader 支持传入 aria-label、aria-describedby、aria-errormessage、aria-invalid、aria-labelledby、aria-required 来表示该 Cascader 的相关信息;
  • combobox 的名称按优先级取:aria-labelledby、aria-label、实际渲染出来的 insetLabel(combobox 的 aria-labelledby 指向它,未传 insetLabelId 时组件生成 id;有 prefix 时 insetLabel 不渲染,也不作名称),都没有时为语言包中的「级联选择」。下拉中的 tree 或搜索结果 listbox 使用同一个名称来源;
  • 按 ARIA 1.2 的 combobox 写法,任一时刻只有一个元素是 combobox,aria-controls 指向下拉中的列表,下拉与 combobox 同名。不可搜索时触发器是 select-only combobox,已选路径是它的值;可搜索时搜索框本身是 combobox,带 aria-autocomplete="list",触发器不再有角色和 Tab 停靠点。单选的搜索框只在搜索时出现,未搜索时触发器仍是 combobox;搜索框在输入前为空,已选路径以 aria-describedby 关联为它的描述;搜索中选中结果后焦点回到触发器;
  • 各列合起来是一棵 role="tree":选项为 treeitem,带 aria-level(所在列)、aria-setsize、aria-posinset、aria-disabled,可展开的选项带 aria-expanded;单选时用 aria-selected,多选时用 aria-checked(半选为 mixed);
  • 搜索结果是 role="listbox",选项为 option,用 aria-selected 表示选中,多选时表示勾选。filterRender 返回的元素会被加上 id、role="option" 和上述状态;
  • 选项前的勾选图标和多选复选框只是状态的图示,对读屏软件隐藏,也不接收焦点。

键盘交互

Cascader 按 WAI-ARIA Combobox 处理键盘:焦点始终留在触发器或搜索框上,aria-activedescendant 指向当前选项,当前选项显示焦点环。下拉刚打开时没有当前选项。

  • 下拉关闭时:ArrowDown、ArrowUp、Enter 打开下拉;不可搜索时,Space 也打开下拉,Home、End 打开下拉并移到第一列的第一个、最后一个选项。可搜索时,Space、Home、End 用于编辑文字
  • Enter:有当前选项时等同点击它,多选时勾选或取消勾选;禁用选项不响应
  • ArrowDown、ArrowUp:在当前选项所在的列(或搜索结果)中移到下一个、上一个选项,第一次按 ArrowDown 移到第一列的第一个选项;Home、End:移到这一列的第一个、最后一个选项
  • ArrowRight:展开当前选项,显示它的下一列;已展开时移到下一列的第一个选项(RTL 下与 ArrowLeft 互换)
  • ArrowLeft:收起展开的当前选项;否则移到上一列的父选项
  • Space:有当前选项时与 Enter 相同(焦点在搜索框里时用于输入)
  • 输入字符:下拉打开时,从当前选项往后,移到已显示的各列中第一个以输入内容开头的选项,各列保持不变;快速连续输入的字符合成一个搜索词。焦点在搜索框里时字符用于输入;下拉关闭时不响应
  • Esc:关闭下拉

焦点在搜索框里且已输入文字时,只有 ArrowDown、ArrowUp、Enter 用于移动和选择,其余按键用于编辑文字。方向键只移动当前选项,不像悬停那样自动展开下一列,也不触发 loadData;ArrowRight 展开时才加载。

showClear 开启、有已选值或搜索文本且未禁用时,触发器内渲染清除按钮。它是原生 button,名称为语言包中的「清空已选值」,在 hover、下拉打开或焦点位于 Cascader 内时显示。从触发器或搜索框按 Tab 移到清除按钮,Enter 或 Space 清空,效果与鼠标点击相同,清空后焦点回到触发器或搜索框。多选时也可在搜索框中用 Backspace 删除标签。

文案规范

  • 选择器选项
    • 如果没有默认选项,就使用 "Select" 做占位文案
    • 选项要按首字母顺序或者其他有逻辑的排列顺序,使用户更好地找到选项
    • 使用语句书写规范(首字母大写,其余小写),避免在句尾使用逗号和分号
    • 清晰表达出选项所表示的选择目的

设计变量

下表是组件内部的 Sass 变量,不能覆写。定制样式请覆写 --semi-* CSS 变量(见 Design Token 页),或写不分层的 CSS 规则。
Sass 变量值用法
$transition_duration-cascader_option-bgvar(--semi-transition_duration-none)
$transition_function-cascader_option-bgvar(--semi-transition_function-easeIn)
$transition_delay-cascader_option-bgvar(--semi-transition_delay-none)
$radius-cascadervar(--semi-border-radius-small)级联选择触发器圆角
$radius-cascader_option_emptyvar(--semi-border-radius-medium)级联选择菜单空状态圆角
$color-cascader_default-border-defaulttransparent级联选择触发器描边颜色 - 默认
$color-cascader_default-border-hovertransparent级联选择触发器描边颜色 - 悬浮
$color-cascader_default-border-focusvar(--semi-color-focus-border)级联选择触发器描边颜色 - 选中
$color-cascader_default-bg-defaultvar(--semi-color-fill-0)级联选择触发器背景颜色 - 默认
$color-cascader_default-bg-hovervar(--semi-color-fill-1)级联选择触发器背景颜色 - 悬浮
$color-cascader_default-bg-activevar(--semi-color-fill-2)级联选择触发器背景颜色 - 按下
$color-cascader_option_list-border-defaultvar(--semi-color-fill-0)级联选择各级菜单分割线颜色
$color-cascader_option_main-text-defaultvar(--semi-color-text-0)级联选择菜单项文字颜色
$color-cascader_option-bg-hovervar(--semi-color-fill-0)级联选择菜单项背景颜色 - 悬浮
$color-cascader_option-bg-activevar(--semi-color-fill-1)级联选择菜单项背景颜色 - 按下
$color-cascader_option-bg-selectedvar(--semi-color-primary-light-default)级联选择菜单项背景颜色 - 命中
$color-cascader_option_disabled-text-defaultvar(--semi-color-disabled-text)级联选择菜单项文字颜色 - 禁用
$color-cascader_option_disabled-bg-hovertransparent级联选择菜禁用单项背景颜色 - 悬浮
$color-cascader_option_disabled-bg-activetransparent级联选择菜禁用单项背景颜色 - 按下
$color-cascader_tag_disabled-bg-defaulttransparent级联选择菜禁用Tag背景颜色
$color-cascader_warning-bg-defaultvar(--semi-color-warning-light-default)级联选择触发器警告背景颜色
$color-cascader_warning-border-defaultvar(--semi-color-warning-light-default)级联选择触发器警告描边颜色
$color-cascader_warning-bg-hovervar(--semi-color-warning-light-hover)级联选择触发器警告背景颜色 - 悬浮
$color-cascader_warning-border-hovervar(--semi-color-warning-light-hover)级联选择触发器警告描边颜色 - 悬浮
$color-cascader_warning-bg-focusvar(--semi-color-warning-light-default)级联选择触发器警告背景颜色 - 选中
$color-cascader_warning-border-focusvar(--semi-color-warning)级联选择触发器警告描边颜色 - 选中
$color-cascader_warning-bg-activevar(--semi-color-warning-light-active)级联选择触发器警告背景颜色 - 按下
$color-cascader_warning-border-activevar(--semi-color-warning-light-active)级联选择触发器警告描边颜色 - 按下
$color-cascader_danger-bg-defaultvar(--semi-color-danger-light-default)级联选择触发器危险背景颜色
$color-cascader_danger-border-defaultvar(--semi-color-danger-light-default)级联选择触发器危险描边颜色
$color-cascader_danger-bg-hovervar(--semi-color-danger-light-hover)级联选择触发器危险背景颜色 - 悬浮
$color-cascader_danger-border-hovervar(--semi-color-danger-light-hover)级联选择触发器危险描边颜色 - 悬浮
$color-cascader_danger-bg-focusvar(--semi-color-danger-light-default)级联选择触发器危险背景颜色 - 选中
$color-cascader_danger-border-focusvar(--semi-color-danger)级联选择触发器危险描边颜色 - 选中
$color-cascader_danger-bg-activevar(--semi-color-danger-light-active)级联选择触发器危险背景颜色 - 按下
$color-cascader_danger-border-activevar(--semi-color-danger-light-active)级联选择触发器危险描边颜色 - 按下
$color-cascader_prefix_suffix_text-defaultvar(--semi-color-text-2)级联选择前后缀文字颜色
$spacing-cascader_selection-paddingLeft12px级联选择触发器左侧内边距 - 默认
$spacing-cascader_selection-paddingRight12px级联选择触发器右侧内边距 - 默认
$spacing-cascader_small_selection-paddingLeft12px级联选择触发器左侧内边距 - 小尺寸
$spacing-cascader_small_selection-paddingRight12px级联选择触发器右侧内边距 - 小尺寸
$spacing-cascader_large_selection-paddingLeft12px级联选择触发器左侧内边距 - 大尺寸
$spacing-cascader_large_selection-paddingRight12px级联选择触发器右侧内边距 - 大尺寸
$spacing-cascader_selection_multiple-paddingLeft$spacing-extra-tight级联选择触发器多选时左侧内边距 - 默认
$spacing-cascader_selection_multiple-paddingRight$spacing-extra-tight级联选择触发器多选时右侧内边距 - 默认
$spacing-cascader_small_selection_multiple-paddingLeft$spacing-extra-tight级联选择触发器多选时左侧内边距 - 小尺寸
$spacing-cascader_small_selection_multiple-paddingRight$spacing-extra-tight级联选择触发器多选时左侧内边距 - 小尺寸
$spacing-cascader_large_selection_multiple-paddingLeft$spacing-extra-tight级联选择触发器多选时左侧内边距 - 大尺寸
$spacing-cascader_large_selection_multiple-paddingRight$spacing-extra-tight级联选择触发器多选时左侧内边距 - 大尺寸
$spacing-cascader_selection_tag-marginLeft$spacing-none级联选择触发器多选时标签的水平左外边距
$spacing-cascader_selection_tag-marginRight$spacing-super-tight级联选择触发器多选时标签的水平右外边距
$spacing-cascader_selection_tag-marginY1px级联选择触发器多选时标签的垂直外边距
$spacing-cascader_selection_tagInput-marginLeft-$spacing-extra-tight级联选择触发器多选搜索时 TagInput 的左外边距
$spacing-cascader_selection_input-marginLeft$spacing-extra-tight级联选择触发器多选搜索时输入框的左外边距
$spacing-cascader_selection_n-marginRight$spacing-extra-tight超出 maxTagCount 后,+n 的右外边距
$spacing-cascader_option-icon-marginLeft8px级联选择菜单项图标左侧外边距
$color-cascader_selection_n-text-defaultvar(--semi-color-text-0)超出 maxTagCount 后,+n 的文字默认颜色
$color-cascader_selection_n-text-disabledvar(--semi-color-disabled-text)超出 maxTagCount 后,+n 的文字disabled颜色
$color-cascader_selection_text_inactivevar(--semi-color-text-2)级联选择单选inpu输入框和text并存时,text颜色
$color-cascader_selection-text-defaultvar(--semi-color-text-0)级联选择选中项文字颜色
$color-cascader_placeholder-text-defaultvar(--semi-color-text-2)级联选择未选中项文字颜色
$color-cascader-icon-defaultvar(--semi-color-text-2)级联选择触发器图标颜色 - 默认
$color-cascader-icon-hovervar(--semi-color-primary-hover)级联选择触发器图标颜色 - 悬浮
$color-cascader-icon-activevar(--semi-color-primary-active)级联选择触发器图标颜色 - 按下
$color-cascader_option-icon-default$color-cascader-icon-default级联选择菜单项图标颜色
$color-cascader_input_disabled-bg-defaultvar(--semi-color-disabled-fill)级联选择禁用态背景颜色
$color-cascader_input_disabled-bg-hovervar(--semi-color-disabled-fill)级联选择禁用态背景颜色 - 悬浮
$color-cascader_input_disabled-text-defaultvar(--semi-color-disabled-text)级联选择禁用态文字颜色
$color-cascader_input_disabled-icon-defaultvar(--semi-color-disabled-text)级联选择禁用态图标颜色
$color-cascader_search-border-defaultvar(--semi-color-fill-0)级联选择搜索描边颜色
$color-cascader_option_empty-text-defaultvar(--semi-color-disabled-text)级联选择空状态文字颜色
$color-cascader_input-bg-defaulttransparent级联选择输入框背景颜色
$color-cascader_input-border-defaultnone级联选择输入框描边颜色
$color-cascader_select_highlightvar(--semi-color-primary)级联选择器搜索命中后文字高亮的颜色
$font-cascader_select-fontWeight$font-weight-bold级联选择菜单项字重 - 选中
$font-cascader-fontWeight$font-weight-regular级联选择菜单项字重 - 未选中
$font-cascader_selection_n-fontSize$font-size-small超出 maxTagCount 后,+n 的文字尺寸
$font-cascader_prefix_suffix_fontWeight$font-weight-bold级联选择 prefix/suffix 文字字重
$width-cascader-border1px级联选择触发器描边宽度
$width-cascader_option_list-border1px级联选择各级菜单分割线宽度
$width-cascader80px级联选择触发器最小宽度
$height-cascader_default$height-control-default级联选择触发器高度 - 默认
$height-cascader_small$height-control-small级联选择触发器高度 - 小尺寸
$height-cascader_large$height-control-large级联选择触发器高度 - 大尺寸
$width-cascader_hover-border$width-cascader-border级联选择触发器描边宽度 - 悬浮
$width-cascader_focus-border$border-thickness-control-focus级联选择触发器描边宽度 - 选中态
$width-cascader_search-border1px级联选择触搜索描边宽度
$width-cascader-icon32px级联选择触发器图标尺寸
$width-cascader-option-icon16px级联选择菜单项图标尺寸
$width-cascader_option150px级联选择各级菜单宽度
$height-cascader_option_list180px级联选择菜单高度
$height-cascader_selection_tagInput_wrapper_small22px级联选择多选搜索时搜索框最小高度 - 小尺寸
$height-cascader_selection_tagInput_wrapper_default30px级联选择多选搜索时搜索框最小高度 - 默认尺寸
$height-cascader_selection_tagInput_wrapper_large38px级联选择多选搜索时搜索框最小高度 - 大尺寸
$height-cascader_selection_wrapper_small22px级联选择单选搜索时搜索框高度 - 小尺寸
$height-cascader_selection_wrapper30px级联选择单选搜索时搜索框高度 - 默认尺寸
$height-cascader_selection_wrapper_large38px级联选择单选搜索时搜索框高度 - 大尺寸
$spacing-cascader_text-marginX$spacing-base-tight级联选择 prefix/suffix 文字水平内间距
$spacing-cascader_icon-marginX$spacing-tight级联选择 prefix/suffix 图标水平内间距
$spacing-cascader_label_checkbox-marginRight$spacing-tight级联选择 checkbox 的右间距
$spacing-cascader_selection_n-paddingX$spacing-tight级联选择 +N 的水平间距
$spacing-cascader_flatten_list-paddingRight64px级联选择搜索结果菜单右侧内边距
$spacing-cascader_empty_icon-marginRight8px级联选择搜索结果菜单
$spacing-cacader_option_list-paddingY4px级联选择下拉菜单列表垂直内边距
$spacing-cacader_option_list-paddingX0px级联选择下拉菜单列表水平内边距
$spacing-cascader_search-paddingX12px级联选择搜索水平内边距
$spacing-cascader_search-paddingY8px级联选择搜索垂直内边距
$spacing-cascader_option_empty-paddingY8px级联选择空结果垂直内边距
$spacing-cascader_option_empty-paddingX12px级联选择空结果水平内边距
$spacing-cascader_option-paddingTop8px级联选择菜单项顶部内边距
$spacing-cascader_option-paddingBottom8px级联选择菜单项底部内边距
$spacing-cascader_option-paddingLeft12px级联选择菜单项左侧内边距
$spacing-cascader_option-paddingRight16px级联选择菜单项右侧内边距