#TreeSelect 树选择器
从层级数据中选择一个或多个值。
#选择与搜索
basic.tsx
import { Space, Text, TreeSelect } from '@ifhover/leaf-ui';
import { useState } from 'react';
import { treeOptions } from './options';
export function TreeSelectBasic() {
const [value, setValue] = useState<string | null>(null);
return (
<Space direction="vertical" align="stretch">
<TreeSelect
options={treeOptions}
value={value}
onChange={setValue}
defaultExpandAll
aria-label="所属团队"
placeholder="选择所属团队"
/>
<output>
<Text tone="muted">当前值: {typeof value === 'string' ? value : '未选择'}</Text>
</output>
</Space>
);
}
#多选与勾选
multiple.tsx
import { Form, FormField, TreeSelect } from '@ifhover/leaf-ui';
import { treeOptions } from './options';
export function TreeSelectMultiple() {
return (
<Form layout="vertical">
<FormField label="多选 · 逐项选择">
<TreeSelect options={treeOptions} multiple defaultValue={['visual']} defaultExpandAll />
</FormField>
<FormField label="勾选联动 · 提交子节点">
<TreeSelect
options={treeOptions}
treeCheckable
defaultValue={['visual']}
defaultExpandAll
/>
</FormField>
<FormField label="勾选联动 · 合并显示父节点">
<TreeSelect
options={treeOptions}
treeCheckable
showCheckedStrategy="parent"
defaultValue={['design']}
defaultExpandAll
/>
</FormField>
</Form>
);
}
#状态与浮层
states.tsx
import { Form, FormField, TreeSelect } from '@ifhover/leaf-ui';
import { treeOptions } from './options';
export function TreeSelectStates() {
return (
<Form layout="vertical">
<FormField label="禁用">
<TreeSelect options={treeOptions} disabled defaultValue="web" />
</FormField>
<FormField label="校验反馈">
<TreeSelect options={treeOptions} status="error" placeholder="请选择一个团队" />
</FormField>
<FormField label="自定义浮层宽度">
<TreeSelect options={treeOptions} popupWidth={320} popupMaxWidth={360} defaultExpandAll />
</FormField>
</Form>
);
}
#扩展用法
extensions.tsx
import { TreeSelect } from '@ifhover/leaf-ui';
export function ExtensionDemo() {
return (
<TreeSelect
placeholder="展开团队加载成员"
options={[{ value: 'team', label: '团队', isLeaf: false }]}
virtual
loadData={async (option) => {
await new Promise((resolve) => setTimeout(resolve, 500));
return [
{ value: `${option.value}-design`, label: '设计组', isLeaf: true },
{ value: `${option.value}-dev`, label: '开发组', isLeaf: true },
];
}}
/>
);
}
#选择限制与节点展示
浮层默认与控件等宽(popupWidth="trigger")。设置 popupWidth="auto" 仅按内容定宽,也可以比控件窄;popupWidth={300} 固定为 300px,也支持 CSS 长度。popupMaxWidth 默认未设置,下面的示例按内容定宽并显式设置最大宽度。浮层仍会在视口边缘避让。
最多选择两人,只展示一个标签
capabilities.tsx
import { Flex, Space, Text, TreeSelect } from '@ifhover/leaf-ui';
import { useState } from 'react';
const options = [
{
value: 'team',
label: 'Team',
children: [
{ value: 'alice', label: 'Alice' },
{ value: 'bob', label: 'Bob' },
{ value: 'carol', label: 'Carol' },
],
},
];
export function Capabilities() {
const [expanded, setExpanded] = useState<readonly string[]>(['team']);
return (
<Space direction="vertical" align="stretch">
<Flex direction="column" gap="sm" align="center">
<Text>
{'最多选择两人,只展示一个标签'}
</Text>
<TreeSelect
aria-label={'团队成员'}
popupWidth="auto"
popupMaxWidth={320}
treeCheckable
options={options}
maxCount={2}
maxTagCount={1}
treeExpandedKeys={expanded}
onExpand={setExpanded}
nodeRender={(node) => <strong>{node.label}</strong>}
filterTreeNode={(node, query) =>
String(node.label).toLowerCase().includes(query.toLowerCase())
}
showSearch
/>
</Flex>
</Space>
);
}
#单行多选
设置 tagWrap={false} 后,已选项始终展示在一行内。长文字自动省略,放不下的选项合并为 +N;空间很窄时只显示数量,打开浮层可查看和修改选择。maxTagCount 可进一步限制标签数量,maxTagCount="responsive" 也会启用单行自动折叠。
再次打开会保留上次展开的分支。不可选分类可点击整行展开;勾选模式可点击整行或按 Enter / 空格勾选,箭头只负责展开或收起。搜索临时展开匹配分支,不改变原有展开状态。
single-line.tsx
import { Form, FormField, TreeSelect } from '@ifhover/leaf-ui';
import { treeOptions } from './options';
export function SingleLine() {
return (
<Form layout="vertical">
<FormField label="单行多选">
<TreeSelect
treeCheckable
tagWrap={false}
options={treeOptions}
defaultValue={['visual', 'product', 'web', 'server']}
treeDefaultExpandedKeys={['design']}
/>
</FormField>
</Form>
);
}
#TreeSelect 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 TreeSelectOption[] | 必填 | 必需。层级选项 |
checkStrictly | boolean | false | 父子节点独立勾选 |
showCheckedStrategy | 'child' | 'parent' | 'all' | 'child' | 勾选后显示与输出子节点、合并父节点或全部节点 |
showSearch | boolean | true | 通过输入过滤节点 |
onSearch | (query: string) => void | — | 搜索文字变化 |
allowClear | boolean | true | 允许清除可用选项 |
defaultExpandAll | boolean | false | 初始展开全部 |
treeDefaultExpandedKeys | readonly string[] | [] | 初始展开的 value |
size | 'sm' | 'md' | 'lg' | 'md' | 控件尺寸 |
status | 'error' | 'warning' | — | 校验反馈 |
popupWidth | number | string | 'trigger' | 浮层宽度;trigger 与控件等宽,auto 按内容定宽(可比控件窄),数字为固定像素值,也支持 CSS 长度 |
popupMaxWidth | number | string | — | 可选的最大宽度;默认不限制,仍受视口限制 |
notFoundContent | ReactNode | 暂无选项 | 无匹配内容 |
onOpenChange | (open: boolean) => void | — | 展开状态变化 |
loadData | ( option: TreeSelectOption, signal: AbortSignal, ) => Promise<readonly TreeSelectOption[]> | — | 展开时加载子项,signal 用于取消 |
onLoadError | (error: unknown, option: TreeSelectOption) => void | — | 加载失败,再次展开可重试 |
virtual | boolean | false | 仅渲染可见项目 |
listHeight | number | 256 | 列表高度 |
itemHeight | number | 38 | 跟随区域控件高度和密度的预估行高,包含行间距;实际行会被测量。 |
inputReadOnly | boolean | — | 只禁止手动输入,仍可在面板中选择。 |
open | boolean | — | 控制面板是否展开。 |
defaultOpen | boolean | — | 面板初始展开状态。 |
maxTagCount | number | 'responsive' | — | 最多展示的标签数;responsive 根据可用宽度自动折叠。 |
tagWrap | boolean | true | 允许多选标签换行;false 单行显示并自动折叠溢出项。 |
maxCount | number | — | 最多可选的数量。 |
treeExpandedKeys | readonly string[] | — | 受控的展开节点。 |
onExpand | (keys: string[]) => void | — | 展开节点发生变化时调用。 |
filterTreeNode | (option: TreeSelectOption, query: string) => boolean | — | 自定义节点的搜索匹配。 |
nodeRender | (option: TreeSelectOption) => ReactNode | — | 自定义节点展示内容。 |
cacheKey | string | number | — | 数据版本改变时清空懒加载缓存,并取消旧请求。 |
multiple | false | true | boolean | false | 多选 / 复选框模式,后者自动开启多选 |
treeCheckable | false | boolean | true | false | 多选 / 复选框模式,后者自动开启多选 |
value | string | null | readonly string[] | readonly string[] | null | 受控 / 初始值;多选使用数组 |
defaultValue | string | null | readonly string[] | readonly string[] | null | 受控 / 初始值;多选使用数组 |
onChange | (((value: string | null) => void) | ((value: readonly string[]) => void)) | ((value: readonly string[]) => void) | — | 选择变化,清除单选返回 null,多选返回 [] |
disabled | boolean | false | 禁用交互。 |
required | boolean | false | 要求整体值非空,复合控件只校验一次。 |
name | string | — | 原生 FormData 中的字段名。 |
form | string | — | 关联的原生表单 ID。 |
#TreeSelectOption
| 属性 | 类型 | 默认值 | 说明 |
|---|---|---|---|
value | string | — | 必需。唯一值,必填 |
label | ReactNode | — | 必需。展示内容,必填 |
children | readonly TreeSelectOption[] | — | 子选项,结构相同 |
disabled | boolean | — | 禁用交互。 |
selectable | boolean | — | 设为 false 可让分类节点仅用于浏览 |
disableCheckbox | boolean | — | 禁止勾选并阻断联动 |
icon | ReactNode | — | 节点图标 |
searchLabel | string | — | label 为节点时提供搜索和读屏文字 |
isLeaf | boolean | — | 明确指定是否为叶子节点,配合异步加载使用。 |
#类型
可导入以下类型定义属性、组装数据或处理回调。
import type { TreeSelectProps, TreeSelectOption, TreeSelectValue } from '@ifhover/leaf-ui';#使用说明
选项的 value 应唯一且稳定。示例数据:
import type { TreeSelectOption } from '@ifhover/leaf-ui';
export const treeOptions: readonly TreeSelectOption[] = [
{
value: 'design',
label: '设计团队',
selectable: false,
children: [
{ value: 'visual', label: '视觉设计' },
{ value: 'product', label: '产品设计' },
{ value: 'archive', label: '归档团队', disabled: true },
],
},
{
value: 'engineering',
label: '研发团队',
selectable: false,
children: [
{ value: 'web', label: '前端开发' },
{ value: 'server', label: '服务端开发' },
],
},
];
TreeSelectValue 是 string | readonly string[] | null,可用于组装受控值。复选框默认提交最终子节点;showCheckedStrategy 同时决定显示和回调值。禁用的已有选项不会被清除或父子联动修改。
方向键下进入树,Enter 选择,空格勾选,Escape 关闭。多选时,搜索为空按 Backspace 可移除最后一个可用选项。FormData 在同一字段名下提交多个值,使用 getAll(name) 读取;非受控字段响应表单 reset。