#Cascader 级联选择
用于逐级选择地区、分类等有层级关系的选项。
#基础选择
children 定义子选项。点击父项展开下一列,点击末级选项确认完整路径;disabled 可以禁用一个分支。
basic.tsx
import { Cascader, Form, FormField } from '@ifhover/leaf-ui';
const options = [
{
value: 'design',
label: '设计',
children: [
{ value: 'interface', label: '界面设计' },
{ value: 'brand', label: '品牌设计' },
],
},
{
value: 'engineering',
label: '工程',
children: [
{ value: 'frontend', label: '前端开发' },
{ value: 'backend', label: '后端开发' },
],
},
{ value: 'archived', label: '归档分类', disabled: true },
];
export function CascaderBasic() {
return (
<Form layout="vertical">
<FormField label="项目分类">
<Cascader aria-label="项目分类" options={options} placeholder="选择项目分类" />
</FormField>
<FormField label="默认分类">
<Cascader aria-label="默认分类" options={options} defaultValue={['design', 'interface']} />
</FormField>
<FormField label="禁用分类">
<Cascader aria-label="禁用分类" options={options} disabled />
</FormField>
</Form>
);
}
#受控路径
value 是从根节点到末级节点的 value 数组,onChange 同时提供完整的选项对象路径。切换父项后会清除旧分支的后续选择。
controlled.tsx
import { Cascader, Form, FormField, Text } from '@ifhover/leaf-ui';
import { useState } from 'react';
import { regions } from './options';
export function CascaderControlled() {
const [value, setValue] = useState<string[]>(['zhejiang', 'hangzhou', 'xihu']);
return (
<Form layout="vertical">
<FormField label="收货地区">
<Cascader aria-label="收货地区" options={regions} value={value} onChange={setValue} />
</FormField>
<Text tone="muted">选中路径:{value.join(' → ') || '未选择'}</Text>
</Form>
);
}
示例使用的地区选项:
options.ts
import type { CascaderOption } from '@ifhover/leaf-ui';
export const regions: CascaderOption[] = [
{
value: 'zhejiang',
label: '浙江',
children: [
{
value: 'hangzhou',
label: '杭州',
children: [
{ value: 'xihu', label: '西湖区' },
{ value: 'yuhang', label: '余杭区' },
],
},
{ value: 'ningbo', label: '宁波', children: [{ value: 'haishu', label: '海曙区' }] },
],
},
{
value: 'jiangsu',
label: '江苏',
children: [
{ value: 'nanjing', label: '南京', children: [{ value: 'gulou', label: '鼓楼区' }] },
{ value: 'suzhou', label: '苏州', children: [{ value: 'gusu', label: '姑苏区' }] },
],
},
{ value: 'archived', label: '归档地区', disabled: true },
];
#搜索、多选与异步加载
搜索、多选与选择中间层
懒加载与刷新缓存
capabilities.tsx
import type { CascaderOption } from '@ifhover/leaf-ui';
import { Button, Cascader, Flex, Space, Text } from '@ifhover/leaf-ui';
import { useCallback, useState } from 'react';
const options = [
{
value: 'product',
label: 'Product',
children: [
{ value: 'ui', label: 'UI' },
{ value: 'web', label: 'Web' },
],
},
{ value: 'team', label: 'Team', children: [{ value: 'design', label: 'Design' }] },
];
const lazy: CascaderOption[] = [{ value: 'remote', label: 'Remote', isLeaf: false }];
export function Capabilities() {
const [version, setVersion] = useState(0);
const load = useCallback(async (option: CascaderOption, signal: AbortSignal) => {
await new Promise<void>((resolve, reject) => {
const timer = setTimeout(resolve, 350);
signal.addEventListener(
'abort',
() => {
clearTimeout(timer);
reject(signal.reason);
},
{ once: true },
);
});
return [{ value: `${option.value}-child`, label: 'Loaded item', isLeaf: true }];
}, []);
return (
<Space direction="vertical" align="stretch">
<Flex direction="column" gap="sm" align="center">
<Text>
{'搜索、多选与选择中间层'}
</Text>
<Cascader options={options} showSearch multiple changeOnSelect />
</Flex>
<Flex direction="column" gap="sm" align="center">
<Text>{'懒加载与刷新缓存'}</Text>
<Cascader options={lazy} loadData={load} cacheKey={version} allowClear />
</Flex>
<Button variant="outline" onClick={() => setVersion(version + 1)}>
{'刷新数据'}
</Button>
</Space>
);
}
#单行多选
设置 tagWrap={false} 后,已选项始终展示在一行内。长文字自动省略,放不下的选项合并为 +N;空间很窄时只显示数量,打开浮层可查看和修改选择。maxTagCount 可进一步限制标签数量,maxTagCount="responsive" 也会启用单行自动折叠。
single-line.tsx
import { Cascader } from '@ifhover/leaf-ui';
const options = [
{
value: 'product',
label: '产品团队',
children: [
{ value: 'design', label: '产品体验设计' },
{ value: 'web', label: '前端开发' },
{ value: 'mobile', label: '移动端开发' },
],
},
];
export function SingleLine() {
return (
<Cascader
aria-label="产品团队"
multiple
showSearch
tagWrap={false}
options={options}
defaultValue={[
['product', 'design'],
['product', 'web'],
['product', 'mobile'],
]}
/>
);
}
#Cascader API
| 属性 | 类型 | 默认值 | 说明 |
|---|---|---|---|
popupPlacement | 'top' | 'top-start' | 'top-end' | 'bottom' | 'bottom-start' | 'bottom-end' | 'left' | 'left-start' | 'left-end' | 'right' | 'right-start' | 'right-end' | — | 浮层相对触发控件的位置。 |
popupClassName | string | — | 浮层的自定义类名。 |
popupStyle | CSSProperties | — | 浮层的自定义样式。 |
popupRender | (content: React.ReactNode) => React.ReactNode | — | 包装或补充面板内容,保留传入的内容以维持交互。 |
getPopupContainer | () => Element | DocumentFragment | — | 返回浮层的挂载容器,也可返回 ShadowRoot。 |
options | readonly CascaderOption[] | 必填 | 必需。层级选项列表 |
size | 'sm' | 'md' | 'lg' | 'md' | 选择器尺寸 |
status | 'error' | 'warning' | — | 校验状态 |
placeholder | string | '请选择' | 未选择时的提示文字 |
name | string | — | 字段名 / 关联的表单 ID |
required | boolean | false | 要求整体值非空,复合控件只校验一次。 |
allowClear | boolean | true | 清除后返回两个空数组 |
onOpenChange | (open: boolean) => void | — | 面板展开或收起 |
open | boolean | — | 控制面板是否展开。 |
defaultOpen | boolean | — | 面板初始展开状态。 |
readOnly | boolean | — | 锁定业务值,禁止输入、选择、清除和删除。 |
showSearch | boolean | — | 启用搜索输入。 |
changeOnSelect | boolean | — | 允许选择中间层级。 |
displayRender | (labels: ReactNode[], selectedOptions: CascaderOption[]) => ReactNode | — | 自定义路径的显示内容。 |
optionRender | (option: CascaderOption) => ReactNode | — | 自定义选项内容。 |
loadData | (option: CascaderOption, signal: AbortSignal) => Promise<readonly CascaderOption[]> | — | 异步加载子节点;应响应 AbortSignal。 |
onLoadError | (error: unknown, option: CascaderOption) => void | — | 加载失败时调用,可展示重试提示。 |
cacheKey | string | number | — | 数据版本改变时清空懒加载缓存,并取消旧请求。 |
tagWrap | boolean | true | 允许多选标签换行;false 单行显示并自动折叠溢出项。 |
maxTagCount | number | 'responsive' | — | 最多展示的标签数;responsive 根据可用宽度自动折叠。 |
multiple | false | true | false | 启用多选;value 和回调的类型随之变化。 |
value | readonly string[] | readonly (readonly string[])[] | [] | 受控路径 / 非受控初始路径 |
defaultValue | readonly string[] | readonly (readonly string[])[] | [] | 受控路径 / 非受控初始路径 |
onChange | ((value: string[], selectedOptions: CascaderOption[]) => void) | ((value: string[][], selectedOptions: CascaderOption[][]) => void) | — | 末级选择完成或清除时触发 |
disabled | boolean | false | 禁用交互。 |
form | string | — | 关联的原生表单 ID。 |
ref | Ref<HTMLButtonElement> | — | 触发按钮引用 |
同一层内的 value 应唯一,传入路径应完整且存在于 options 中。其余原生 button 属性和 aria-* 透传至触发按钮;className / style 作用于外层容器。
#类型
CascaderProps 用于定义组件属性。单独组装层级选项时,可以使用 CascaderOption:
import type { CascaderProps, CascaderOption, ControlSize, ControlStatus } from '@ifhover/leaf-ui';
const options: CascaderOption[] = [
{
label: '浙江',
value: 'zhejiang',
children: [{ label: '杭州', value: 'hangzhou' }],
},
];#CascaderOption
| 属性 | 类型 | 默认值 | 说明 |
|---|---|---|---|
value | string | 必填 | 选项值,同一层内应唯一 |
label | string | 必填 | 选项文字 |
disabled | boolean | false | 禁用该选项及其子级 |
children | readonly CascaderOption[] | — | 子选项;没有子选项时可确认选择 |
#表单与键盘
设置 name 后,FormData 的值为 JSON 数组字符串,例如 ["zhejiang","hangzhou","xihu"],清除后为空字符串。可用 JSON.parse 读取路径。非受控组件支持表单 reset,受控组件需同步重置自己的状态。
Enter、空格或 ↓ 打开面板,↑ / ↓ 在列内移动,Home / End 移至首尾,→ 展开子级,← 返回上一级,Enter 选择。Tab 在各列之间移动,离开面板时聚焦表单中的后续控件;Escape 收起并聚焦触发按钮。关闭时未确认的路径不会提交。
面板沿用局部主题,较深层级可以横向滚动查看。为控件提供 label 或 aria-label。
#CascaderOption
| 属性 | 类型 | 默认值 | 说明 |
|---|---|---|---|
value | string | — | 必需。受控值。 |
label | ReactNode | — | 必需。展示文字或内容。 |
searchLabel | string | — | 搜索时使用的纯文本。 |
isLeaf | boolean | — | 明确指定是否为叶子节点,配合异步加载使用。 |
disabled | boolean | — | 禁用交互。 |
children | readonly CascaderOption[] | — | 子内容;嵌套数据类型表示子节点。 |
#CascaderFieldNames
| 属性 | 类型 | 默认值 | 说明 |
|---|---|---|---|
value | string | — | 受控值。 |
label | string | — | 展示文字或内容。 |
children | string | — | 子内容;嵌套数据类型表示子节点。 |
disabled | string | — | 禁用交互。 |
isLeaf | string | — | 明确指定是否为叶子节点,配合异步加载使用。 |