使用场景
用于对输入框提供输入建议,进行自动补全的操作
与可搜索的 Select 组件的区别:
- AutoComplete 本质上是一个增强型的提供了输入建议的 Input 组件,而 Select 是一个选择器
- 点击展开时,Select 会将输入框的值全部清空,而 AutoComplete 会保留上次选中的值
- Select 的已选项渲染(renderSelectedItem)可定制化程度更高,可以为任意类型的 ReactNode,而 AutoComplete 只允许为字符串
代码演示
如何引入
import { AutoComplete } from '@fibrous/semi-ui-react';
基本用法
通过 onSearch 监听用户输入,将输入建议通过更新 props.data 传入。通过 onChange 保持受控,当输入框变化/选中输入项时会触发 onChange
代码
import { useState } from 'react';
import { IconSearch } from '@fibrous/semi-icons-react';
import { AutoComplete } from '@fibrous/semi-ui-react';
export default function AutoCompleteDemo() {
const [stringData, setStringData] = useState<string[]>([]);
const [value, setValue] = useState<string | number>('');
const handleStringSearch = (value: string) => {
let result: string[];
if (value) {
result = ['gmail.com', '163.com', 'qq.com'].map((domain) => `${value}@${domain}`);
} else {
result = [];
}
setStringData(result);
};
const handleChange = (value: string | number) => {
console.log('onChange', value);
setValue(value);
};
return <AutoComplete data={stringData} value={value} showClear prefix={<IconSearch />} placeholder="搜索... " onSearch={handleStringSearch} onChange={handleChange} style={{ width: 200 }} />;
}自定义候选项渲染
需要自定义候选项渲染时,data 可以传入一个对象数组(每个 Object 必须含有 label、value 两个 key,value 为候选项选中的值,label 为候选项展示的内容)
通过 renderItem 可以自定义候选项的渲染
代码
import { useState } from 'react';
import { IconSearch } from '@fibrous/semi-icons-react';
import { AutoComplete, Avatar } from '@fibrous/semi-ui-react';
const list: ListItem[] = [
{ name: '夏可漫', email: 'xiakeman@example.com', abbr: 'XK', color: 'amber' },
{ name: '申悦', email: 'shenyue@example.com', abbr: 'SY', color: 'indigo' },
{ name: '曲晨一', email: 'quchenyi@example.com', abbr: 'CY', color: 'blue' },
{ name: '文嘉茂', email: 'wenjiamao@example.com', abbr: 'JM', color: 'cyan' },
];
interface ListItem {
name: string;
email: string;
abbr: string;
color: 'amber' | 'indigo' | 'blue' | 'cyan';
value?: string;
label?: string;
}
export default function AutoCompleteDemo() {
const [data, setData] = useState<ListItem[]>([]);
const handleStringSearch = (value: string) => {
let result: ListItem[];
if (value) {
result = list.map((item) => {
return { ...item, value: item.name, label: item.email };
});
} else {
result = [];
}
setData(result);
};
const renderOption = (item: ListItem) => {
return (
<>
<Avatar color={item.color} size="small">
{item.abbr}
</Avatar>
<div style={{ marginLeft: 4 }}>
<div style={{ fontSize: 14, marginLeft: 4 }}>{item.name}</div>
<div style={{ marginLeft: 4 }}>{item.email}</div>
</div>
</>
);
};
return <AutoComplete aria-label="搜索邮箱" data={data} showClear prefix={<IconSearch />} onSearch={handleStringSearch} renderItem={renderOption} renderSelectedItem={(option) => option.email} style={{ width: 280 }} />;
}远程搜索
从 onSearch 中获取用户输入值,动态更新 data 值
代码
import { useMemo, useState } from 'react';
import { debounce } from 'es-toolkit';
import { IconSearch } from '@fibrous/semi-icons-react';
import { IconSelect, IconForm, IconTable, IconInput, IconButton } from '@fibrous/semi-icons-react/lab';
import { AutoComplete, type AutoCompleteDataItem } from '@fibrous/semi-ui-react';
const initList = [
{ value: 'select', label: '选择器', icon: <IconSelect /> },
{ value: 'input', label: '输入框', icon: <IconInput /> },
{ value: 'form', label: '表单', icon: <IconForm /> },
{ value: 'button', label: '按钮', icon: <IconButton /> },
{ value: 'table', label: '表格', icon: <IconTable /> },
];
type ListItem = (typeof initList)[number];
export default function AutoCompleteDemo() {
const [loading, setLoading] = useState(false);
const [list, setList] = useState(initList);
const search = useMemo(
() =>
debounce((inputValue: string) => {
setLoading(true);
let newList = initList;
if (inputValue) {
newList = initList.filter((item) => item.value.includes(inputValue));
}
setTimeout(() => {
setList(newList);
setLoading(false);
}, 1000);
}, 200),
[]
);
const handleSelect = (value: string | number | AutoCompleteDataItem) => {
console.log(value);
};
const renderItem = (item: ListItem) => {
return (
<div style={{ display: 'flex', alignItems: 'center' }}>
<div style={{ fontSize: 32 }}>{item.icon}</div>
<div style={{ marginLeft: 12 }}>
<p>{item.value}</p>
<p>{item.label}</p>
</div>
</div>
);
};
const renderSelectedItem = (item: ListItem) => {
// Unlike Select, this can return only a string, not a ReactNode
return item.value;
};
return <AutoComplete aria-label="搜索" data={list} style={{ width: 250 }} prefix={<IconSearch />} onSearch={search} loading={loading} renderItem={renderItem} renderSelectedItem={renderSelectedItem} onSelect={handleSelect} />;
}尺寸
通过设置 size 可设置输入框尺寸,可选small,default(默认),large
代码
import { AutoComplete } from '@fibrous/semi-ui-react';
export default function AutoCompleteDemo() {
return (
<div>
<AutoComplete data={[1, 2, 3, 4]} size="small" placeholder="small" style={{ width: 200 }} />
<br />
<br />
<AutoComplete data={[1, 2, 3, 4]} size="default" placeholder="default" style={{ width: 200 }} />
<br />
<br />
<AutoComplete data={[1, 2, 3, 4]} size="large" placeholder="large" style={{ width: 200 }} />
</div>
);
}下拉菜单的位置
通过设置 position 可设置下拉菜单位置,可选值参考 Tooltip position
代码
import { useState } from 'react';
import { AutoComplete } from '@fibrous/semi-ui-react';
export default function AutoCompleteDemo() {
const [data, setData] = useState<string[]>([]);
const change = (input: string) => {
let newData = ['gmail.com', '163.com', 'qq.com'].map((domain) => `${input}@${domain}`);
if (!input) {
newData = [];
}
setData(newData);
};
return (
<div>
<AutoComplete data={data} position="top" onSearch={change} placeholder="选项菜单在上方显示" style={{ width: 200, margin: 10 }} />
<AutoComplete data={data} position="rightTop" onSearch={change} placeholder="选项菜单在右侧显示" style={{ width: 200, margin: 10 }} />
</div>
);
}禁用
代码
import { AutoComplete } from '@fibrous/semi-ui-react';
export default function AutoCompleteDemo() {
return <AutoComplete data={[1, 2, 3, 4]} placeholder="禁用下拉菜单" disabled style={{ width: 200 }} />;
}校验状态
可设置不同校验状态,展示不同样式
代码
import { AutoComplete } from '@fibrous/semi-ui-react';
export default function AutoCompleteDemo() {
return (
<>
<AutoComplete aria-label="Warning status" defaultValue="ies" validateStatus="warning" />
<br />
<br />
<AutoComplete aria-label="Error status" defaultValue="ies" validateStatus="error" />
<br />
<br />
<AutoComplete aria-label="Default status" defaultValue="ies" />
</>
);
}自定义空内容
可设置自定义展示空内容
代码
import { useState } from 'react';
import { IllustrationNoContent } from '@fibrous/semi-illustrations-react';
import { AutoComplete, Empty } from '@fibrous/semi-ui-react';
export default function AutoCompleteDemo() {
let [data, setData] = useState<number[]>([]);
const [loading, setLoading] = useState(false);
const fetchData = (v: string) => {
setLoading(true);
setTimeout(() => {
if (!v) {
setData([]);
setLoading(false);
return;
}
setData(() => {
const res = Array.from(Array(5)).map(() => Math.random());
return res;
});
setLoading(false);
}, 1000);
};
return <AutoComplete aria-label="搜索" loading={loading} data={data} emptyContent={<Empty style={{ padding: 12, width: 300 }} image={<IllustrationNoContent style={{ width: 150, height: 150 }} />} description="暂无内容" />} onSearch={fetchData} />;
}API 参考
Props
| 属性 | 说明 | 类型 | 默认值 |
|---|---|---|---|
| autoAdjustOverflow | 下拉面板被遮挡时是否自动调整方向。未设置时取 Popover 的默认值 true。 | boolean | - |
| autoFocus | 挂载后是否自动聚焦输入框。 | boolean | false |
| className | 根 div 的类名。 | string | - |
| clearIcon | 自定义清除图标,showClear 为 true 时有效。 | ReactNode | - |
| data | 候选项的数据源,可以是字符串、数字或对象的数组。对象项的 value 是选中值,label 是候选行的内容,不传时候选行为空。其余字段原样保留,出现在 renderItem 的参数里。需替换为新数组才会更新列表。 | T[] | [] |
| defaultActiveFirstOption | 面板打开后是否默认高亮第一个候选项,此时按 Enter 可直接选中。 | boolean | false |
| defaultOpen | 挂载时是否默认展开下拉面板。 | boolean | - |
| defaultValue | 非受控时输入框的初始值,只在挂载时读取。 | string | number | - |
| disabled | 是否禁用。 | boolean | - |
| dropdownClassName | 下拉列表的类名,落在 role="listbox" 元素上。 | string | - |
| dropdownMatchSelectWidth | 下拉面板的最小宽度是否等于输入框宽度。 | boolean | true |
| dropdownStyle | 下拉列表的内联样式,与 maxHeight 合并后落在 role="listbox" 元素上。 | React.CSSProperties | - |
| emptyContent | data 为空时在下拉面板中显示的内容;加载中时代替它显示加载动画。 | React.ReactNode | null |
| id | 输入框的 id,供 <label for> 关联;使用 triggerRender 时落在根 div 上。 | string | - |
| insetLabel | 输入框内嵌标签,渲染在前缀位置;设置 prefix 或 triggerRender 时不渲染,也不作为可访问名称。 | React.ReactNode | - |
| insetLabelId | insetLabel 元素的 id,combobox 与 listbox 的名称引用它;不传时组件在渲染 insetLabel 时生成。 | string | - |
| loading | 下拉列表是否显示加载动画;为 true 时代替候选项和空内容。 | boolean | false |
| maxHeight | 下拉列表的最大高度,数字按 px 处理。 | string | number | 300 |
| motion | 下拉面板显示、隐藏时是否有动画。 | boolean | true |
| onBlur | 焦点离开输入框所在区域时的回调,参数为 FocusEvent;焦点在区域内移动(例如移到清除按钮)时不触发。 | Type(e: React.FocusEvent) => void | - |
| onChange | 输入框内容变化或选中候选项时的回调,参数为当前值。 | Type(value: string | number) => void | - |
| onChangeWithObject | 不生效:组件不读取该属性。 | boolean | - |
| onClear | 用清除按钮清空内容后的回调。 | Type() => void | - |
| onDropdownVisibleChange | 下拉面板显示或隐藏时的回调,参数为是否可见。 | Type(visible: boolean) => void | - |
| onFocus | 焦点进入输入框所在区域时的回调,参数为 FocusEvent;焦点在区域内移动(例如移到清除按钮)时不触发。 | Type(e: React.FocusEvent) => void | - |
| onKeyDown | 键盘按下时的回调,在根 div 上触发,参数为 React.KeyboardEvent<HTMLDivElement>。 | Type(e: React.KeyboardEvent<HTMLDivElement>) => void | - |
| onSearch | 用户输入时的回调,参数为输入的文本。 | Type(inputValue: string) => void | - |
| onSelect | 候选项被选中时的回调,参数为选中值;设置 onSelectWithObject 时为去掉内部字段的数据项对象。 | Type(value: string | number | BaseDataItem) => void | - |
| onSelectWithObject | 为 true 时 onSelect 收到去掉内部字段的数据项对象,而不是选中值。 | boolean | false |
| placeholder | 输入框的提示文案。 | string | - |
| position | 下拉面板的位置,可选值同 Tooltip 的 position。未设置时为 bottomLeft,ConfigProvider 的 direction 为 rtl 时为 bottomRight。 | Position | - |
| prefix | 输入框的前缀内容;设置后 insetLabel 不渲染。 | React.ReactNode | - |
| ref | 根 div 的引用,包裹输入框或 triggerRender 的输出。得到的是原生 div 元素,其 focus() 不会聚焦输入框。 | TypeReact.Ref<HTMLDivElement> | - |
| renderItem | 自定义候选项的渲染,参数为数据项本身,返回 ReactNode;未设置时显示 label。 | Type(option: T) => React.ReactNode | - |
| renderSelectedItem | 选中候选项后输入框显示的文本,必须返回字符串;字符串或数字项收到 { value, label },对象项收到数据项本身。 | Type(option: T extends string | number ? BaseDataItem : T) => string | - |
| showClear | 有内容时是否显示清除按钮。 | boolean | false |
| size | 输入框尺寸,可选 small、default 或 large。 | Size | "default" |
| stopPropagation | 是否阻止下拉面板上的 click、mousedown 与焦点进出事件冒泡。 | boolean | string | true |
| style | 根 div 的内联样式。 | React.CSSProperties | - |
| suffix | 输入框的后缀内容。 | React.ReactNode | - |
| triggerRender | 自定义触发器,替换输入框。参数含 value(已选项数组)、inputValue、placeholder、disabled、autoFocus、size、validateStatus、showClear、prefix、suffix、clearIcon、aria-label、aria-labelledby、aria-describedby、aria-errormessage、aria-invalid、aria-required(其他 aria-* 不作为直接参数传入,可从 componentProps 取得)、onChange(更新输入文本,触发 onSearch 与 onChange 并展开列表)、onClear、onFocus、onBlur 与 componentProps。使用时 id 落在根 div 上。 | Type(props?: any) => React.ReactNode | - |
| validateStatus | 校验状态,可选 default、error 或 warning,只影响展示样式。 | ValidateStatus | "default" |
| value | 当前值。传入后为受控;之后改回 undefined 时输入框清空,组件转为非受控。 | string | number | - |
className、style、data-* 与其他原生属性和事件(例如 onClick)落在根 div 上,根 div 的 tabIndex 固定为 -1。id、name、aria-describedby、aria-errormessage、aria-invalid、aria-label、aria-labelledby 与 aria-required 落在输入框(role="combobox")上,因此 <label for> 能关联到输入框。使用 triggerRender 时没有输入框:id 落在根 div 上,name 不落到任何元素,上面列出的 aria-* 通过参数交给自定义触发器的可聚焦元素,其他 aria-* 不作为直接参数传到触发器上。
Accessibility
键盘和焦点
- AutoComplete 的 input 框带有
role="combobox",并通过aria-expanded、aria-controls、aria-autocomplete="list"和aria-activedescendant与选项面板关联 - combobox 的名称按优先级取:
aria-labelledby、aria-label、实际渲染出来的 insetLabel(combobox 的aria-labelledby指向它,未传insetLabelId时组件生成 id;有 prefix 时 insetLabel 不渲染,也不作名称)。listbox 使用同一个名称来源。AutoComplete 没有默认名称:以上都没有时,请用aria-label或aria-labelledby为输入框命名;<label for>只命名输入框,listbox 仍没有名称 - AutoComplete 的 input 框可被聚焦,聚焦后,键盘用户可以通过
上箭头或下箭头打开选项面板;data 为空时面板里显示 emptyContent(没有提供时为空),加载中显示加载动画 - AutoComplete 也支持通过
Enter键打开和收起面板 - 若用户将 defaultActiveFirstOption 属性设置为 true 时,选项面板打开后默认高亮第一个选项
- 若下拉菜单打开时:
- 使用
Esc可以关闭菜单 - 使用
Tab可以关闭菜单,焦点照常移到下一个可聚焦元素 - 使用
上箭头或下箭头可以切换选项,被高亮的选项(aria-activedescendant指向的项)aria-selected为true - 被聚焦的选项可以通过
Enter键选中,并收起面板 - 用鼠标选中选项后,焦点留在输入框中
- 使用
showClear开启且有内容时,清除按钮与 Input 相同:原生button,名称为语言包中的「清空输入」,从输入框按Tab移到它,Enter或空格键清空并把焦点放回输入框
文案规范
- 需要清晰地展示内容,让用户显而易见地感知到可用的各个选项
- 限制一次性展示的选项数量
设计变量
下表是组件内部的 Sass 变量,不能覆写。定制样式请覆写 --semi-* CSS 变量(见 Design Token 页),或写不分层的 CSS 规则。
| Sass 变量 | 值 | 用法 |
|---|---|---|
| $transition_duration-autoComplete_option-bg | var(--semi-transition_duration-none) | 选择器-选项-动画持续时间 |
| $transition_function-autoComplete_option-bg | var(--semi-transition_function-easeIn) | 选择器-选项-过渡曲线 |
| $transition_delay-autoComplete_option-bg | var(--semi-transition_delay-none) | 选择器-选项-延迟时间 |
| $spacing-autoComplete_loading_wrapper-paddingTop | $spacing-tight | 加载搜索结果时的顶部内边距 |
| $spacing-autoComplete_loading_wrapper-paddingBottom | $spacing-tight | 加载搜索结果时的底部内边距 |
| $color-autoComplete_option-icon-default | transparent | 自动完成菜单选项图标颜色 - 默认态 |
| $color-autoComplete_option-bg-hover | var(--semi-color-fill-0) | 自动完成菜单选项背景颜色 - 悬停态 |
| $color-autoComplete_option-bg-active | var(--semi-color-fill-1) | 自动完成菜单选项背景颜色 - 按下态 |
| $color-autoComplete_option_disabled-text | var(--semi-color-disabled-text) | 禁用自动完成菜单选项文字颜色 |
| $width-autoComplete_option_tick | $width-icon-small | 自动完成菜单项选中对勾图标大小 |
| $spacing-autoComplete_option_tick-marginRight | $spacing-tight | 自动完成菜单选中对勾右侧外边距 |
| $spacing-autoComplete_option-paddingLeft | $spacing-base-tight | 自动完成菜单项左侧内边距 |
| $spacing-autoComplete_option-paddingRight | $spacing-base-tight | 自动完成菜单项右侧内边距 |
| $spacing-autoComplete_option-paddingTop | $spacing-tight | 自动完成菜单项顶部内边距 |
| $spacing-autoComplete_option-paddingBottom | $spacing-tight | 自动完成菜单项底部内边距 |
| $spacing-autoComplete_option_first-marginTop | $spacing-extra-tight | 自动完成第一个菜单项顶部外边距 |
| $spacing-autoComplete_option_last-marginBottom | $spacing-extra-tight | 自动完成最后一个菜单项顶部外边距 |
| $radius-autoComplete_option | 0px | 自动完成待选项圆角 |
| $color-autoComplete_option_main-text | var(--semi-color-text-0) | 自动完成菜单选项文本颜色 |