#Select 选择器
用于从下拉列表中选择一项或多项。
#基础选择
通过 options 传入选项;placeholder 在空值时提示选择,单个选项可以禁用。
import { Form, FormField, Select } from '@ifhover/leaf-ui';
const options = [
{ label: '设计工作室', value: 'design' },
{ label: '产品团队', value: 'product' },
{ label: '工程团队', value: 'engineering' },
{ label: '归档项目', value: 'archived', disabled: true },
];
export function SelectBasic() {
return (
<Form layout="vertical">
<FormField label="选择团队">
<Select aria-label="选择团队" options={options} placeholder="请选择团队" />
</FormField>
<FormField label="默认团队">
<Select aria-label="默认团队" options={options} defaultValue="design" />
</FormField>
<FormField label="禁用团队">
<Select aria-label="禁用团队" options={options} disabled defaultValue="product" />
</FormField>
</Form>
);
}
#受控与校验
value / onChange 管理选择结果,回调直接返回字符串值和选项对象。allowClear 显示清除按钮;status="error" 同时提供错误外观与 aria-invalid。
import { Form, FormField, Select, Text } from '@ifhover/leaf-ui';
import { useState } from 'react';
export function SelectControlled() {
const [value, setValue] = useState('');
return (
<Form layout="vertical">
<FormField label="项目可见性">
<Select
aria-label="项目可见性"
value={value}
placeholder="选择可见性"
allowClear
onChange={setValue}
options={[
{ label: '公开', value: 'public' },
{ label: '团队', value: 'team' },
{ label: '私密', value: 'private' },
]}
/>
</FormField>
<Text tone="muted">当前值:{value || '尚未选择'}</Text>
<FormField label="未设置团队 · 错误">
<Select
aria-label="未设置团队"
status="error"
placeholder="请选择所属团队"
options={[{ label: '设计团队', value: 'design' }]}
/>
</FormField>
</Form>
);
}
#尺寸对齐
import type { ControlSize } from '@ifhover/leaf-ui';
import { Button, Flex, Input, Select, Text } from '@ifhover/leaf-ui';
export function ControlSizes() {
return (
<Flex direction="column" gap="md">
{(['sm', 'md', 'lg'] as const).map((size: ControlSize) => (
<Flex key={size} align="center" gap="sm" wrap>
<Text tone="muted">{size}</Text>
<Input size={size} aria-label={`${size} 搜索`} placeholder="搜索项目" />
<Select
size={size}
aria-label={`${size} 状态`}
defaultValue="all"
options={[
{ label: '全部状态', value: 'all' },
{ label: '进行中', value: 'active' },
]}
/>
<Button size={size}>查询</Button>
</Flex>
))}
</Flex>
);
}
#搜索与多选
showSearch 只筛选已有选项;multiple 使用数组值并在选择后保持展开,可移除标签。
import type { SelectOption } from '@ifhover/leaf-ui';
import { Form, FormField, Select, Text } from '@ifhover/leaf-ui';
import { useState } from 'react';
const options: SelectOption[] = [
{ value: 'design', label: '设计' },
{ value: 'engineering', label: '研发' },
{ value: 'marketing', label: '市场' },
];
export function SelectSearch() {
const [teams, setTeams] = useState<string[]>(['design']);
return (
<Form layout="vertical">
<FormField label="搜索团队">
<Select
aria-label="搜索团队"
showSearch
allowClear
options={options}
placeholder="输入搜索团队"
/>
</FormField>
<FormField label="多选团队">
<Select
aria-label="多选团队"
multiple
showSearch
allowClear
options={options}
value={teams}
onChange={setTeams}
/>
</FormField>
<output>
<Text tone="muted">{teams.join(', ') || '—'}</Text>
</output>
</Form>
);
}
#浮层宽度与内容
import type { SelectOption } from '@ifhover/leaf-ui';
import { Form, FormField, Select } from '@ifhover/leaf-ui';
const options: SelectOption[] = [
{ value: 'garden', label: 'Leaf Garden Design System' },
{ value: 'studio', label: 'Leaf Studio' },
{ value: 'notes', label: 'Leaf Notes' },
];
export function SelectPopup() {
return (
<Form layout="vertical">
<FormField label="自动浮层宽度">
<Select
aria-label="自动浮层宽度"
style={{ width: 180 }}
options={options}
defaultValue="studio"
/>
</FormField>
<FormField label="固定浮层宽度">
<Select
aria-label="固定浮层宽度"
popupWidth={300}
popupMaxWidth={360}
style={{ width: 180 }}
options={options}
optionRender={(option) => (
<div>
<strong>{option.label}</strong>
<div style={{ fontSize: 12, opacity: 0.65 }}>{option.value}</div>
</div>
)}
/>
</FormField>
<FormField label="多选数量限制">
<Select
aria-label="多选数量限制"
multiple
options={options}
defaultValue={['studio', 'notes']}
maxCount={2}
maxTagCount={1}
allowClear
/>
</FormField>
<FormField label="加载选项">
<Select aria-label="加载选项" options={[]} loading placeholder="加载中" />
</FormField>
<FormField label="空选项">
<Select aria-label="空选项" options={[]} notFoundContent="还没有项目" />
</FormField>
</Form>
);
}
#扩展用法
import { Select, Space } from '@ifhover/leaf-ui';
const many = Array.from({ length: 5000 }, (_, i) => ({
value: String(i),
label: `项目 ${i + 1}`,
}));
export function ExtensionDemo() {
return (
<Space direction="vertical" align="stretch">
<label htmlFor="select-extension-1">
分组与自由创建
<Select
id="select-extension-1"
multiple
showSearch
allowCreate
options={[
{
label: '设计',
options: [
{ value: 'ui', label: '界面设计' },
{ value: 'ux', label: '体验设计' },
],
},
{ label: '工程', options: [{ value: 'web', label: '前端开发' }] },
]}
placeholder="选择或创建标签"
/>
</label>
<label htmlFor="select-extension-2">
5000 个选项
<Select
id="select-extension-2"
showSearch
virtual
options={many}
placeholder="搜索并选择"
/>
</label>
</Space>
);
}
#选中内容与受控浮层
import { Button, Flex, Select, Space, Tag, Text } from '@ifhover/leaf-ui';
import { useState } from 'react';
const options = ['Design', 'Engineering', 'Marketing', 'Operations'].map((label) => ({
value: label,
label,
}));
export function Capabilities() {
const [open, setOpen] = useState(false);
return (
<Space direction="vertical" align="stretch">
<Flex direction="column" gap="sm" align="center">
<Text>
{'根据宽度自动折叠标签'}
</Text>
<Select
multiple
showSearch
defaultValue={['Design', 'Engineering', 'Marketing']}
options={options}
maxTagCount="responsive"
tagRender={(option, remove) => (
<Tag closable onClose={remove}>
{option.label}
</Tag>
)}
/>
</Flex>
<Flex direction="column" gap="sm" align="center">
<Text>{'受控浮层与附加操作'}</Text>
<Select
options={options}
open={open}
onOpenChange={setOpen}
labelRender={(option) => <strong>{option.label}</strong>}
popupRender={(content) => (
<>
{content}
<Button size="sm" variant="ghost" onClick={() => setOpen(false)}>
{'完成'}
</Button>
</>
)}
/>
</Flex>
</Space>
);
}
#单行多选
设置 tagWrap={false} 后,已选项始终展示在一行内。长文字自动省略,放不下的选项合并为 +N;空间很窄时只显示数量,打开浮层可查看和修改选择。maxTagCount 可进一步限制标签数量,maxTagCount="responsive" 也会启用单行自动折叠。
import { Form, FormField, Select } from '@ifhover/leaf-ui';
const options = [
{ value: '0', label: '一个名称比较长的产品设计团队' },
{ value: '1', label: '前端开发' },
{ value: '2', label: '服务端开发' },
{ value: '3', label: '日常运营' },
];
const selected = options.map((option) => option.value);
export function SingleLine() {
return (
<Form layout="vertical">
<FormField label="单行显示 · 长标签自动省略">
<Select
multiple
showSearch
allowClear
tagWrap={false}
options={options}
defaultValue={selected}
/>
</FormField>
<FormField label="默认换行">
<Select multiple options={options} defaultValue={selected} />
</FormField>
</Form>
);
}
#Select 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: ReactNode) => ReactNode | — | 包装或补充面板内容,保留传入的内容以维持交互。 |
getPopupContainer | () => Element | DocumentFragment | — | 返回浮层的挂载容器,也可返回 ShadowRoot。 |
options | readonly (SelectOption|SelectOptionGroup)[] | 必填 | 必需。选项列表 |
size | 'sm' | 'md' | 'lg' | 'md' | 选择器尺寸 |
status | 'error' | 'warning' | — | 校验状态 |
allowClear | boolean | false | 展示清除按钮 |
showSearch | boolean | false | 允许输入搜索选项 |
filterOption | boolean | ((query: string, option: SelectOption) => boolean) | true | 自定义筛选,false 用于外部筛选 |
onSearch | (query: string) => void | — | 搜索文字变化 |
onOpenChange | (open: boolean) => void | — | 展开或收起选项面板 |
open | boolean | — | 控制面板是否展开。 |
defaultOpen | boolean | — | 面板初始展开状态。 |
inputReadOnly | boolean | — | 只禁止手动输入,仍可在面板中选择。 |
labelRender | (option: SelectOption) => ReactNode | — | 自定义选中值的展示。 |
tagRender | (option: SelectOption, remove: () => void) => ReactNode | — | 自定义选中标签;通过 remove 请求移除。 |
onPopupScroll | (event: React.UIEvent<HTMLDivElement>) => void | — | 浮层滚动事件,可用来加载下一页。 |
popupWidth | number | string | 'auto' | 浮层宽度;auto 按内容扩宽,trigger 与控件等宽,也可指定像素或 CSS 长度 |
popupMaxWidth | number | string | 420 | 最大宽度,仍受视口限制 |
loading | boolean | false | 加载提示 |
notFoundContent | ReactNode | — | 没有选项时的内容 |
optionRender | (option: SelectOption) => ReactNode | — | 自定义选项内容 |
maxCount | number | — | 多选数量上限,已选项可继续取消 |
maxTagCount | number | 'responsive' | — | 最多展示的标签数;responsive 根据可用宽度自动折叠。 |
tagWrap | boolean | true | 允许多选标签换行;false 单行显示并自动折叠溢出项。 |
allowCreate | boolean | false | 允许创建未匹配的选项 |
onCreate | (option: SelectOption) => void | — | 新选项创建回调 |
virtual | boolean | 选项超过 100 时启用 | 仅渲染可见项目 |
listHeight | number | 248 | 列表高度 |
optionHeight | number | 32 | 跟随区域控件高度和密度的预估行高;实际行会被测量。 |
multiple | false | true | false | 多选时值为数组,默认 [] |
value | string | readonly string[] | '' | 受控值 / 非受控初始值 |
defaultValue | string | readonly string[] | '' | 受控值 / 非受控初始值 |
onChange | ((value: string, option?: SelectOption) => void) | ((value: string[], options: SelectOption[]) => void) | — | 返回值与选项;清除时返回空字符串和 undefined |
disabled | boolean | false | 禁用交互。 |
name | string | — | 原生 FormData 中的字段名。 |
form | string | — | 关联的原生表单 ID。 |
required | boolean | false | 要求整体值非空,复合控件只校验一次。 |
ref | Ref<HTMLInputElement> | — | 输入框引用 |
支持 name、required、form 和非受控表单 reset;其余原生 input 属性及 aria-* 透传至输入框。className 与 style 作用于外层容器。选项 value 应唯一,不要将空字符串用作业务选项值。
#类型
SelectProps 用于定义组件属性。单独组装选项列表时,可以使用 SelectOption:
import type { SelectProps, SelectOption, ControlSize, ControlStatus } from '@ifhover/leaf-ui';
const options: SelectOption[] = [
{ label: '设计团队', value: 'design' },
{ label: '研发团队', value: 'engineering' },
];#SelectOption
| 属性 | 类型 | 默认值 | 说明 |
|---|---|---|---|
value | string | 必填 | 唯一选项值,不能使用空字符串 |
searchLabel | string | — | 搜索文字;默认使用文字 label,复杂节点使用 value |
label | ReactNode | 必填 | 选项内容,可以是文字或 React 节点 |
disabled | boolean | false | 禁用该选项 |
#使用说明
为选择器提供 label 或 aria-label。FormData 提交选项的字符串 value,清除后为空字符串;受控组件的表单重置需同步重置自己的状态。
Enter、空格或方向键打开面板,↑ / ↓ 移动高亮并跳过禁用选项,Home / End 跳至首尾,Enter 或空格确认。Escape 收起,Tab 继续访问后续控件。点击外部或移走焦点会收起。
浮层随页面滚动定位,并在视口边缘避让;长列表可滚动。支持单选、多选及搜索,适用于选项数量适中的场景。
多选清除返回 [];FormData 在同一个 name 下提交多个值,通过 formData.getAll(name) 读取。搜索文字为空时,Backspace 移除最后一个可用标签。离开控件会清空搜索文字,默认不创建选项;设置 allowCreate 可创建未匹配的选项。label 为 React 节点时,用 searchLabel 指定搜索文字。ref 与原生 input 属性作用于输入框。
控件保持页面中的布局宽度,浮层默认至少与控件等宽,并按选项内容扩宽,最多达到 popupMaxWidth。使用 popupWidth="trigger" 保持等宽,或通过 popupWidth={300} 指定宽度。内容超过最大宽度时才换行。
#扩展类型
import type { SelectOptionGroup } from '@ifhover/leaf-ui';#SelectOptionGroup API
| 属性 | 类型 | 默认值 | 说明 |
|---|---|---|---|
label | ReactNode | 必填 | 必需。显示内容 |
options | readonly SelectOption[] | 必填 | 必需。可用选项 |
#SelectOption
| 属性 | 类型 | 默认值 | 说明 |
|---|---|---|---|
value | string | — | 必需。受控值。 |
label | ReactNode | — | 必需。展示文字或内容。 |
disabled | boolean | — | 禁用交互。 |
searchLabel | string | — | 搜索时使用的纯文本。 |