Autocomplete 自动完成
结合选择与过滤,让用户从选项列表中搜索并选择
用法
import { Autocomplete, useFilter } from "@lenso/ui";此预览复用英文版适配,不代表中文源示例已完成本地实现。
"use client";/** HeroUI v3.2.6 adaptation. SPDX-License-Identifier: Apache-2.0 */import { NativeAutocomplete } from "./_native";
export const states = [ { id: "florida", name: "Florida" }, { id: "delaware", name: "Delaware" }, { id: "california", name: "California" }, { id: "texas", name: "Texas" }, { id: "new-york", name: "New York" }, { id: "washington", name: "Washington" },];export default function Default() { return ( <NativeAutocomplete items={states} label="States to Visit" placeholder="Select states" multiple chips hideClear /> );}Local adaptation source above. Derived from HeroUI v3.2.6 source.
组件结构
import {Autocomplete, Label, Description, SearchField, ListBox} from "@lenso/ui";
export default () => ( <Autocomplete> <Label /> <Autocomplete.Trigger> <Autocomplete.Value /> <Autocomplete.ClearButton /> <Autocomplete.Indicator /> </Autocomplete.Trigger> <Description /> <Autocomplete.Popover> <Autocomplete.Filter> <SearchField> <SearchField.Group> <SearchField.SearchIcon /> <SearchField.Input /> </SearchField.Group> </SearchField> <ListBox> <ListBox.Item> <Label /> <ListBox.ItemIndicator /> </ListBox.Item> </ListBox> </Autocomplete.Filter> </Autocomplete.Popover> </Autocomplete>);示例
变体
Autocomplete 组件支持两种视觉变体:
primary(默认)- 标准样式带阴影,适用于大多数场景secondary- 低强调变体无阴影,适用于 Surface 组件内
此预览复用英文版适配,不代表中文源示例已完成本地实现。
"use client";/** HeroUI v3.2.6 adaptation. SPDX-License-Identifier: Apache-2.0 */import * as stylex from "@stylexjs/stylex";import { NativeAutocomplete, styles } from "./_native";const items = [1, 2, 3, 4].map((number) => ({ id: `option${number}`, name: `Option ${number}` }));export function Variants() { return ( <div {...stylex.props(styles.section)}> <section {...stylex.props(styles.stack)}> <h3>Single Select Variants</h3> <NativeAutocomplete items={items} label="Primary variant" variant="primary" /> <NativeAutocomplete items={items} label="Secondary variant" variant="secondary" /> </section> <section {...stylex.props(styles.stack)}> <h3>Multiple Select Variants</h3> <NativeAutocomplete items={items} label="Primary variant" variant="primary" placeholder="Select multiple" multiple chips /> <NativeAutocomplete items={items} label="Secondary variant" variant="secondary" placeholder="Select multiple" multiple chips /> </section> </div> );}Local adaptation source above. Derived from HeroUI v3.2.6 source.
宽度充满
此预览复用英文版适配,不代表中文源示例已完成本地实现。
"use client";/** HeroUI v3.2.6 adaptation. SPDX-License-Identifier: Apache-2.0 */import { Surface } from "@lenso/ui";import { NativeAutocomplete, styles } from "./_native";import { states } from "./default";export function FullWidth() { return ( <Surface xstyle={[styles.surface, styles.fullSurface]}> <NativeAutocomplete items={states} label="State" variant="secondary" fullWidth searchLabel="Search states" /> </Surface> );}Local adaptation source above. Derived from HeroUI v3.2.6 source.
带描述
此预览复用英文版适配,不代表中文源示例已完成本地实现。
"use client";/** HeroUI v3.2.6 adaptation. SPDX-License-Identifier: Apache-2.0 */import { NativeAutocomplete } from "./_native";import { states } from "./default";export function WithDescription() { return ( <NativeAutocomplete items={states} label="State" searchLabel="Search states" searchPlaceholder="Search states..." description="Select your state of residence" /> );}Local adaptation source above. Derived from HeroUI v3.2.6 source.
必填
此预览复用英文版适配,不代表中文源示例已完成本地实现。
"use client";/** HeroUI v3.2.6 adaptation. SPDX-License-Identifier: Apache-2.0 */import { Button } from "@lenso/ui";import { useState } from "react";import * as stylex from "@stylexjs/stylex";import { NativeAutocomplete, styles, type Option } from "./_native";import { states } from "./default";
const countries = [ { id: "usa", name: "United States" }, { id: "canada", name: "Canada" }, { id: "mexico", name: "Mexico" }, { id: "uk", name: "United Kingdom" }, { id: "france", name: "France" }, { id: "germany", name: "Germany" },];export function Required() { const [state, setState] = useState<Option | Option[] | null>(null); const [country, setCountry] = useState<Option | Option[] | null>(null); const [submitted, setSubmitted] = useState(false); return ( <form noValidate {...stylex.props(styles.stack)} onSubmit={(event) => { event.preventDefault(); setSubmitted(true); const data = new FormData(event.currentTarget); if (data.get("state") && data.get("country")) { alert("Form submitted successfully!"); return; } const fields = event.currentTarget.querySelectorAll<HTMLButtonElement>('button[role="combobox"]'); const firstInvalid = fields[data.get("state") ? 1 : 0]; requestAnimationFrame(() => { if (firstInvalid?.isConnected) firstInvalid.focus(); }); }} > <NativeAutocomplete items={states} label="State" name="state" required value={state} onValueChange={setState} invalid={submitted && !state} error={submitted && !state ? "Please select a state." : undefined} searchLabel="Search states" /> <NativeAutocomplete items={countries} label="Country" name="country" required value={country} onValueChange={setCountry} invalid={submitted && !country} error={submitted && !country ? "Please select a country." : undefined} placeholder="Select a country" searchLabel="Search countries" /> <Button type="submit">Submit</Button> </form> );}Local adaptation source above. Derived from HeroUI v3.2.6 source.
禁用
此预览复用英文版适配,不代表中文源示例已完成本地实现。
"use client";/** HeroUI v3.2.6 adaptation. SPDX-License-Identifier: Apache-2.0 */import * as stylex from "@stylexjs/stylex";import { NativeAutocomplete, styles } from "./_native";import { states } from "./default";const countries = [ { id: "argentina", name: "Argentina" }, { id: "venezuela", name: "Venezuela" }, { id: "japan", name: "Japan" }, { id: "france", name: "France" }, { id: "italy", name: "Italy" }, { id: "spain", name: "Spain" },];export function Disabled() { return ( <div {...stylex.props(styles.stack)}> <NativeAutocomplete items={states} label="State" disabled defaultValue={states.find((item) => item.id === "california")} /> <NativeAutocomplete items={countries} label="Countries to Visit" placeholder="Select countries" disabled multiple defaultValue={countries.filter((item) => ["argentina", "japan", "france"].includes(item.id), )} /> </div> );}Local adaptation source above. Derived from HeroUI v3.2.6 source.
含禁用选项
此预览复用英文版适配,不代表中文源示例已完成本地实现。
"use client";/** HeroUI v3.2.6 adaptation. SPDX-License-Identifier: Apache-2.0 */import { NativeAutocomplete } from "./_native";const animals = [ { id: "dog", name: "Dog" }, { id: "cat", name: "Cat", disabled: true }, { id: "bird", name: "Bird" }, { id: "kangaroo", name: "Kangaroo", disabled: true }, { id: "elephant", name: "Elephant" }, { id: "tiger", name: "Tiger" },];export function WithDisabledOptions() { return ( <NativeAutocomplete items={animals} label="Animal" placeholder="Select an animal" searchLabel="Search animals" searchPlaceholder="Search animals..." /> );}Local adaptation source above. Derived from HeroUI v3.2.6 source.
允许空选项
allowsEmptyCollection 属性允许集合为空时仍可使用自动完成,适用于列表初始为空或全部被过滤掉的场景。
此预览复用英文版适配,不代表中文源示例已完成本地实现。
"use client";/** HeroUI v3.2.6 adaptation. SPDX-License-Identifier: Apache-2.0 */import { NativeAutocomplete } from "./_native";export function AllowsEmptyCollection() { return ( <NativeAutocomplete items={[]} label="State" searchLabel="Search states" searchPlaceholder="Search states..." /> );}Local adaptation source above. Derived from HeroUI v3.2.6 source.
分组选项
此预览复用英文版适配,不代表中文源示例已完成本地实现。
"use client";/** HeroUI v3.2.6 adaptation. SPDX-License-Identifier: Apache-2.0 */import { Autocomplete } from "@lenso/ui";import { useId, useState } from "react";import { NativeAutocomplete } from "./_native";
const groups = [ { name: "North America", items: [ { id: "usa", name: "United States" }, { id: "canada", name: "Canada" }, { id: "mexico", name: "Mexico" }, ], }, { name: "Europe", items: [ { id: "uk", name: "United Kingdom" }, { id: "france", name: "France" }, { id: "germany", name: "Germany" }, { id: "spain", name: "Spain" }, { id: "italy", name: "Italy" }, ], }, { name: "Asia", items: [ { id: "japan", name: "Japan" }, { id: "china", name: "China" }, { id: "india", name: "India" }, { id: "south-korea", name: "South Korea" }, ], },];export function WithSections() { const id = useId(); const { contains } = Autocomplete.useFilter({ sensitivity: "base" }); const [query, setQuery] = useState(""); const visibleGroups = groups .map((group) => ({ ...group, items: group.items.filter((item) => contains(item.name, query)), })) .filter((group) => group.items.length); return ( <NativeAutocomplete items={groups.flatMap((group) => group.items)} filteredItems={visibleGroups.flatMap((group) => group.items)} label="Country" placeholder="Select a country" searchLabel="Search countries" searchPlaceholder="Search countries..." inputValue={query} onInputValueChange={setQuery} list={ <Autocomplete.List> {visibleGroups.map((group, index) => ( <Autocomplete.Group key={group.name} aria-labelledby={`${id}-${index}`}> {index > 0 && <Autocomplete.Separator />} <Autocomplete.GroupLabel id={`${id}-${index}`}>{group.name}</Autocomplete.GroupLabel> {group.items.map((item) => ( <Autocomplete.Item key={item.id} value={item}> {item.name} <Autocomplete.ItemIndicator /> </Autocomplete.Item> ))} </Autocomplete.Group> ))} </Autocomplete.List> } /> );}Local adaptation source above. Derived from HeroUI v3.2.6 source.
多选
此预览复用英文版适配,不代表中文源示例已完成本地实现。
"use client";/** HeroUI v3.2.6 adaptation. SPDX-License-Identifier: Apache-2.0 */import { NativeAutocomplete } from "./_native";import { controlledStates } from "./controlled";export function MultipleSelect() { return ( <NativeAutocomplete items={controlledStates} label="States" placeholder="Select states" multiple chips /> );}Local adaptation source above. Derived from HeroUI v3.2.6 source.
在多选模式下,直接使用 <Autocomplete.Value /> 会将每个选中项在 ListBox.Item 中的 children
复制到触发器中,并以符合当前语言环境的分隔符连接。仅包含文本的选项会显示为纯文本,但复选框、头像等可见内容会随每个选中项各出现一次;函数形式的
children 也始终以 isSelected: false 调用。
如需以纯文本显示选中值,请使用 selectedText 渲染属性,它会将每个选中项的 textValue 连接为一个字符串:
<Autocomplete.Value> {({isPlaceholder, defaultChildren, selectedText}) => isPlaceholder ? defaultChildren : selectedText }</Autocomplete.Value>如需可移除标签等更丰富的布局,请参考上方示例,基于 state.selectedItems 自行渲染内容。
受控组件
此预览复用英文版适配,不代表中文源示例已完成本地实现。
"use client";/** HeroUI v3.2.6 adaptation. SPDX-License-Identifier: Apache-2.0 */import { useState } from "react";import * as stylex from "@stylexjs/stylex";import { NativeAutocomplete, styles, type Option } from "./_native";export const controlledStates = [ { id: "california", name: "California" }, { id: "texas", name: "Texas" }, { id: "florida", name: "Florida" }, { id: "new-york", name: "New York" }, { id: "illinois", name: "Illinois" }, { id: "pennsylvania", name: "Pennsylvania" },];export function Controlled() { const [state, setState] = useState<Option | Option[] | null>(controlledStates[0] ?? null); return ( <div {...stylex.props(styles.stack)}> <NativeAutocomplete items={controlledStates} label="State (controlled)" placeholder="Select a state" searchLabel="Search states" value={state} onValueChange={setState} /> <p {...stylex.props(styles.muted)}> Selected: {state && !Array.isArray(state) ? state.name : "None"} </p> </div> );}Local adaptation source above. Derived from HeroUI v3.2.6 source.
受控多选
此预览复用英文版适配,不代表中文源示例已完成本地实现。
"use client";/** HeroUI v3.2.6 adaptation. SPDX-License-Identifier: Apache-2.0 */import { useState } from "react";import * as stylex from "@stylexjs/stylex";import { NativeAutocomplete, styles, type Option } from "./_native";import { controlledStates } from "./controlled";export function ControlledMultiple() { const [selected, setSelected] = useState<Option | Option[] | null>(() => controlledStates.slice(0, 2), ); return ( <div {...stylex.props(styles.stack)}> <NativeAutocomplete items={controlledStates} label="States" placeholder="Select states" multiple value={selected} onValueChange={setSelected} searchLabel="Search states" /> <p {...stylex.props(styles.muted)}> Selected:{" "} {Array.isArray(selected) && selected.length ? selected.map((item) => item.id).join(", ") : "None"} </p> </div> );}Local adaptation source above. Derived from HeroUI v3.2.6 source.
受控展开状态
此预览复用英文版适配,不代表中文源示例已完成本地实现。
"use client";/** HeroUI v3.2.6 adaptation. SPDX-License-Identifier: Apache-2.0 */import { Button } from "@lenso/ui";import { useState } from "react";import * as stylex from "@stylexjs/stylex";import { NativeAutocomplete, styles } from "./_native";import { states } from "./default";export function ControlledOpenState() { const [open, setOpen] = useState(false); return ( <div {...stylex.props(styles.stack)}> <NativeAutocomplete items={states} label="State" searchLabel="Search states" open={open} onOpenChange={setOpen} /> <Button onClick={() => setOpen(!open)}>{open ? "Close" : "Open"} Autocomplete</Button> <p {...stylex.props(styles.muted)}>Autocomplete is {open ? "open" : "closed"}</p> </div> );}Local adaptation source above. Derived from HeroUI v3.2.6 source.
异步搜索
此预览复用英文版适配,不代表中文源示例已完成本地实现。
"use client";/** HeroUI v3.2.6 adaptation. SPDX-License-Identifier: Apache-2.0 */import { useEffect, useState } from "react";import { NativeAutocomplete, type Option } from "./_native";
interface ResponseData { results: { name: string }[];}export function AsynchronousFiltering() { const [query, setQuery] = useState(""); const [result, setResult] = useState<{ query: string; items: Option[]; error?: string } | null>( null, ); useEffect(() => { const controller = new AbortController(); const load = async () => { try { const response = await fetch( `https://swapi.py4e.com/api/people/?search=${encodeURIComponent(query)}`, { signal: controller.signal }, ); if (!response.ok) throw new Error("Could not load characters"); const data = (await response.json()) as ResponseData; if (!controller.signal.aborted) setResult({ query, items: data.results.map((item) => ({ id: item.name, name: item.name })), }); } catch (error) { if (!controller.signal.aborted) setResult({ query, items: [], error: error instanceof Error ? error.message : "Could not load characters", }); } }; void load(); return () => controller.abort(); }, [query]); const loading = result?.query !== query; return ( <NativeAutocomplete items={result?.items ?? []} filteredItems={loading ? [] : (result?.items ?? [])} label="Search a Star Wars characters" placeholder="Search..." searchLabel="Search characters" searchPlaceholder="Search characters..." inputValue={query} onInputValueChange={setQuery} loading={loading} emptyText={result?.error || "No results found"} /> );}Local adaptation source above. Derived from HeroUI v3.2.6 source.
自定义指示器
此预览复用英文版适配,不代表中文源示例已完成本地实现。
"use client";/** HeroUI v3.2.6 adaptation. SPDX-License-Identifier: Apache-2.0 */import { ChevronsExpandVertical } from "@gravity-ui/icons";import { NativeAutocomplete } from "./_native";import { states } from "./default";export function CustomIndicator() { return ( <NativeAutocomplete items={states} label="State" searchLabel="Search states" indicator={<ChevronsExpandVertical width={12} height={12} />} /> );}Local adaptation source above. Derived from HeroUI v3.2.6 source.
自定义展示值
可使用 render props 自定义显示值:
此预览复用英文版适配,不代表中文源示例已完成本地实现。
"use client";/** HeroUI v3.2.6 adaptation. SPDX-License-Identifier: Apache-2.0 */import * as stylex from "@stylexjs/stylex";import { NativeAutocomplete, styles } from "./_native";
const currencies = [ { code: "USD", id: "usd", name: "US Dollar", symbol: "$" }, { code: "EUR", id: "eur", name: "Euro", symbol: "€" }, { code: "GBP", id: "gbp", name: "British Pound", symbol: "£" }, { code: "JPY", id: "jpy", name: "Japanese Yen", symbol: "¥" }, { code: "CHF", id: "chf", name: "Swiss Franc", symbol: "₣" },].map((currency) => ({ ...currency, searchText: `${currency.code} ${currency.name}` }));export function CustomValue() { return ( <NativeAutocomplete items={currencies} label="Currency" placeholder="Select a currency" hideClear defaultValue={currencies[0]} searchLabel="Search currencies" searchPlaceholder="Search currencies..." renderItem={(item) => { const currency = currencies.find((entry) => entry.id === item.id); return ( <span {...stylex.props(styles.details)}> <span>{currency?.code}</span> <span {...stylex.props(styles.muted)}>{item.name}</span> </span> ); }} renderValue={(item) => { const currency = currencies.find((entry) => entry.id === item.id); return currency ? ( <span {...stylex.props(styles.row)}> <strong>{currency.symbol}</strong> <span>{currency.code}</span> <span {...stylex.props(styles.muted)}>{currency.name}</span> </span> ) : ( item.name ); }} /> );}Local adaptation source above. Derived from HeroUI v3.2.6 source.
表面样式
在 Surface 组件内使用时,请使用 variant="secondary" 以应用适合 Surface 背景的低强调变体。
此预览复用英文版适配,不代表中文源示例已完成本地实现。
"use client";/** HeroUI v3.2.6 adaptation. SPDX-License-Identifier: Apache-2.0 */import { Surface } from "@lenso/ui";import { NativeAutocomplete, styles } from "./_native";import { states } from "./default";export function OnSurface() { return ( <Surface xstyle={styles.surface}> <NativeAutocomplete items={states} label="State" variant="secondary" fullWidth searchLabel="Search states" /> </Surface> );}Local adaptation source above. Derived from HeroUI v3.2.6 source.
虚拟滚动
Autocomplete 通过 Virtualizer 支持虚拟化,仅渲染视口内可见行以高效处理大数据集。
此预览复用英文版适配,不代表中文源示例已完成本地实现。
"use client";/** HeroUI v3.2.6 adaptation. SPDX-License-Identifier: Apache-2.0 */import { Autocomplete } from "@lenso/ui";import { useRef, useState } from "react";import * as stylex from "@stylexjs/stylex";import { NativeAutocomplete, OptionContent, type Option } from "./_native";
const firstNames = [ "Emma", "Liam", "Olivia", "Noah", "Ava", "James", "Sophia", "Oliver", "Isabella", "Lucas", "Mia", "Ethan", "Charlotte", "Mason", "Amelia", "Logan", "Harper", "Alexander", "Ella", "Benjamin",];const lastNames = [ "Smith", "Johnson", "Williams", "Brown", "Jones", "Garcia", "Miller", "Davis", "Rodriguez", "Martinez", "Anderson", "Taylor", "Thomas", "Jackson", "White", "Harris", "Clark", "Lewis", "Robinson", "Walker",];const allUsers: Option[] = Array.from({ length: 1000 }, (_, index) => { const first = firstNames[index % firstNames.length] ?? ""; const last = lastNames[Math.floor(index / firstNames.length) % lastNames.length] ?? ""; return { id: String(index + 1), name: `${first} ${last}`, email: `${first.toLowerCase()}.${last.toLowerCase()}@acme.com`, };});const virtualStyles = stylex.create({ field: { width: 300 }, list: { height: 300, overflowY: "auto", position: "relative", padding: 0 }, row: { height: 50, boxSizing: "border-box" }, spacer: (height: number) => ({ height }),});export function Virtualization() { const { contains } = Autocomplete.useFilter({ sensitivity: "base" }); const [query, setQuery] = useState(""); const [scrollTop, setScrollTop] = useState(0); const listRef = useRef<HTMLDivElement>(null); const filtered = allUsers.filter( (user) => contains(user.name, query) || contains(user.email ?? "", query), ); const start = Math.max(0, Math.floor(scrollTop / 50) - 3); const end = Math.min(filtered.length, start + 14); const onHighlight = (item: Option | undefined) => { const index = filtered.findIndex((user) => user.id === item?.id); const element = listRef.current; if (index < 0 || !element) return; const top = index * 50; if (top < element.scrollTop) element.scrollTop = top; else if (top + 50 > element.scrollTop + 300) element.scrollTop = top - 250; setScrollTop(element.scrollTop); }; return ( <NativeAutocomplete items={allUsers} xstyle={virtualStyles.field} filteredItems={filtered} virtualized onItemHighlighted={onHighlight} label="User" placeholder="Select a user" searchLabel="Search users" searchPlaceholder="Search users..." inputValue={query} onInputValueChange={(next) => { setQuery(next); setScrollTop(0); if (listRef.current) listRef.current.scrollTop = 0; }} list={ <Autocomplete.List ref={listRef} xstyle={virtualStyles.list} onScroll={(event) => setScrollTop(event.currentTarget.scrollTop)} > <div aria-hidden="true" {...stylex.props(virtualStyles.spacer(start * 50))} /> {filtered.slice(start, end).map((item, offset) => ( <Autocomplete.Item key={item.id} value={item} index={start + offset} aria-setsize={filtered.length} aria-posinset={start + offset + 1} xstyle={virtualStyles.row} > <OptionContent item={item} /> <Autocomplete.ItemIndicator /> </Autocomplete.Item> ))} <div aria-hidden="true" {...stylex.props(virtualStyles.spacer((filtered.length - end) * 50))} /> </Autocomplete.List> } /> );}Local adaptation source above. Derived from HeroUI v3.2.6 source.
高级示例
用户选择
此预览复用英文版适配,不代表中文源示例已完成本地实现。
"use client";/** HeroUI v3.2.6 adaptation. SPDX-License-Identifier: Apache-2.0 */import * as stylex from "@stylexjs/stylex";import { NativeAutocomplete, OptionAvatar, styles } from "./_native";export const users = [ { avatarUrl: "https://heroui-assets.nyc3.cdn.digitaloceanspaces.com/avatars/blue.jpg", email: "[email protected]", fallback: "B", id: "1", name: "Bob", }, { avatarUrl: "https://heroui-assets.nyc3.cdn.digitaloceanspaces.com/avatars/green.jpg", email: "[email protected]", fallback: "F", id: "2", name: "Fred", }, { avatarUrl: "https://heroui-assets.nyc3.cdn.digitaloceanspaces.com/avatars/purple.jpg", email: "[email protected]", fallback: "M", id: "3", name: "Martha", }, { avatarUrl: "https://heroui-assets.nyc3.cdn.digitaloceanspaces.com/avatars/red.jpg", email: "[email protected]", fallback: "J", id: "4", name: "John", }, { avatarUrl: "https://heroui-assets.nyc3.cdn.digitaloceanspaces.com/avatars/orange.jpg", email: "[email protected]", fallback: "J", id: "5", name: "Jane", },];export function UserSelection() { return ( <NativeAutocomplete items={users} label="User" placeholder="Select a user" searchLabel="Search users" searchPlaceholder="Search users..." renderValue={(item) => ( <span {...stylex.props(styles.row)}> <OptionAvatar item={item} small /> {item.name} </span> )} /> );}Local adaptation source above. Derived from HeroUI v3.2.6 source.
用户多选
此预览复用英文版适配,不代表中文源示例已完成本地实现。
"use client";/** HeroUI v3.2.6 adaptation. SPDX-License-Identifier: Apache-2.0 */import { NativeAutocomplete } from "./_native";import { users } from "./user-selection";export function UserSelectionMultiple() { return ( <NativeAutocomplete items={users} label="Users" placeholder="Select your teammates" multiple chips searchLabel="Search users" searchPlaceholder="Search users..." /> );}Local adaptation source above. Derived from HeroUI v3.2.6 source.
地点搜索
此预览复用英文版适配,不代表中文源示例已完成本地实现。
"use client";/** HeroUI v3.2.6 adaptation. SPDX-License-Identifier: Apache-2.0 */import { Autocomplete } from "@lenso/ui";import { useEffect, useState } from "react";import { NativeAutocomplete } from "./_native";
const cities = [ { country: "USA", name: "New York" }, { country: "USA", name: "Los Angeles" }, { country: "USA", name: "Chicago" }, { country: "UK", name: "London" }, { country: "France", name: "Paris" }, { country: "Japan", name: "Tokyo" }, { country: "Australia", name: "Sydney" }, { country: "Canada", name: "Toronto" }, { country: "Germany", name: "Berlin" }, { country: "Spain", name: "Madrid" },].map((city) => ({ ...city, id: city.name }));
export function LocationSearch() { const { contains } = Autocomplete.useFilter({ sensitivity: "base" }); const [query, setQuery] = useState(""); const [settledQuery, setSettledQuery] = useState(""); useEffect(() => { const timeout = setTimeout(() => setSettledQuery(query), 300); return () => clearTimeout(timeout); }, [query]); const loading = query !== settledQuery; const results = cities.filter((city) => contains(city.name, settledQuery)); return ( <NativeAutocomplete items={cities} label="City" placeholder="Search for a city" searchLabel="Search cities" searchPlaceholder="Search cities..." inputValue={query} onInputValueChange={setQuery} filteredItems={loading ? [] : results} loading={loading} emptyText="No cities found" /> );}Local adaptation source above. Derived from HeroUI v3.2.6 source.
标签组选择
此预览复用英文版适配,不代表中文源示例已完成本地实现。
"use client";/** HeroUI v3.2.6 adaptation. SPDX-License-Identifier: Apache-2.0 */import { NativeAutocomplete } from "./_native";const tags = [ { id: "react", name: "React" }, { id: "typescript", name: "TypeScript" }, { id: "javascript", name: "JavaScript" }, { id: "nodejs", name: "Node.js" }, { id: "python", name: "Python" }, { id: "vue", name: "Vue" }, { id: "angular", name: "Angular" }, { id: "nextjs", name: "Next.js" },];export function TagGroupSelection() { return ( <NativeAutocomplete items={tags} label="Tags" placeholder="Select tags" multiple chips searchLabel="Search tags" searchPlaceholder="Search tags..." emptyText="No tags found" /> );}Local adaptation source above. Derived from HeroUI v3.2.6 source.
邮件收件人
此预览复用英文版适配,不代表中文源示例已完成本地实现。
"use client";/** HeroUI v3.2.6 adaptation. SPDX-License-Identifier: Apache-2.0 */import { NativeAutocomplete } from "./_native";const emails = [ { email: "[email protected]", id: "[email protected]", name: "Alice Johnson" }, { email: "[email protected]", id: "[email protected]", name: "Bob Smith" }, { email: "[email protected]", id: "[email protected]", name: "Charlie Brown" }, { email: "[email protected]", id: "[email protected]", name: "Diana Prince" }, { email: "[email protected]", id: "[email protected]", name: "Eve Wilson" },];export function EmailRecipients() { return ( <NativeAutocomplete items={emails} label="To" placeholder="Add recipients" multiple chips chipText={(item) => item.email} searchLabel="Search emails" searchPlaceholder="Search emails..." emptyText="No recipients found" /> );}Local adaptation source above. Derived from HeroUI v3.2.6 source.
自定义样式
Tailwind CSS
此预览复用英文版适配,不代表中文源示例已完成本地实现。
"use client";/** HeroUI v3.2.6 adaptation. SPDX-License-Identifier: Apache-2.0 */import { NativeAutocomplete } from "./_native";const teammates = [ { id: "sarah", name: "Sarah Chen", role: "Product Design" }, { id: "marcus", name: "Marcus Lee", role: "Engineering" }, { id: "priya", name: "Priya Patel", role: "Data Science" }, { id: "jordan", name: "Jordan Kim", role: "Customer Success" }, { id: "alex", name: "Alex Rivera", role: "Marketing" },];export function CustomStyles() { return ( <NativeAutocomplete items={teammates} label="Assignees" description="People who will be notified when this task updates." placeholder="Search teammates..." searchLabel="Search by name or role" searchPlaceholder="Search by name or role..." multiple chips custom emptyText="No teammates found" /> );}Local adaptation source above. Derived from HeroUI v3.2.6 source.
全局 CSS
可使用 @layer components 指令自定义 Autocomplete 组件类。
了解更多。
@layer components { .autocomplete { @apply flex flex-col gap-1; }
.autocomplete__trigger { @apply rounded-lg border border-border bg-surface p-2; }
.autocomplete__value { @apply text-current; }
.autocomplete__clear-button { @apply text-muted hover:text-foreground; }
.autocomplete__indicator { @apply text-muted; }
.autocomplete__popover { @apply rounded-lg border border-border bg-surface p-2; }}样式参考
HeroUI 遵循 BEM 方法论,确保组件变体与状态可复用且易于自定义。
CSS 类
Autocomplete 组件使用以下 CSS 类(查看源码样式):
基础类 [!toc]
.autocomplete- 自动完成根容器.autocomplete__trigger- 触发自动完成的按钮.autocomplete__value- 显示的值或占位符.autocomplete__clear-button- 清除已选值的按钮.autocomplete__indicator- 下拉指示图标.autocomplete__popover- 弹出层容器.autocomplete__filter- 过滤包装器
变体类 [!toc]
.autocomplete--primary- 带阴影的主变体(默认).autocomplete--secondary- 无阴影的次变体,适用于 Surface 内
状态类 [!toc]
.autocomplete[data-invalid="true"]- 无效状态.autocomplete__trigger[data-focus-visible="true"]- 触发器聚焦状态.autocomplete__trigger[data-disabled="true"]- 触发器禁用状态.autocomplete__value[data-placeholder="true"]- 占位符状态.autocomplete__clear-button[data-empty="true"]- 无选择时隐藏清除按钮.autocomplete__indicator[data-open="true"]- 打开时的指示器状态
交互状态
组件同时支持 CSS 伪类与 data 属性:
- Hover:触发器上的
:hover或[data-hovered="true"] - Focus:触发器上的
:focus-visible或[data-focus-visible="true"] - Disabled:Autocomplete 上的
:disabled或[data-disabled="true"] - Open:指示器上的
[data-open="true"]
API 参考
Autocomplete
| Prop | 类型 | 默认值 | 描述 |
|---|---|---|---|
placeholder | string | 'Select an item' | 为空时显示的占位文本 |
selectionMode | "single" | "multiple" | "single" | 启用单选或多选 |
allowsEmptyCollection | boolean | false | 是否允许空集合;为 true 时无选项也可使用 |
isOpen | boolean | - | 弹出层打开状态(受控) |
defaultOpen | boolean | - | 弹出层默认打开状态(非受控) |
onOpenChange | (isOpen: boolean) => void | - | 打开状态变化时的回调 |
disabledKeys | Iterable<Key> | - | 禁用项的 key |
isDisabled | boolean | - | 是否禁用 |
value | Key | Key[] | null | - | 当前值(受控) |
defaultValue | Key | Key[] | null | - | 默认值(非受控) |
onChange | (value: Key | Key[] | null) => void | - | 值变化时的回调 |
isRequired | boolean | - | 是否必填 |
isInvalid | boolean | - | 值是否无效 |
name | string | - | 提交 HTML 表单时使用的名称 |
fullWidth | boolean | false | 是否占满容器宽度 |
variant | "primary" | "secondary" | "primary" | 视觉变体。primary 为默认带阴影样式;secondary 为低强调无阴影,适用于 Surface 内 |
className | string | - | 附加 CSS 类 |
children | ReactNode | RenderFunction | - | 内容或 render 函数 |
Autocomplete.Trigger
| Prop | 类型 | 默认值 | 描述 |
|---|---|---|---|
className | string | - | 附加 CSS 类 |
children | ReactNode | RenderFunction | - | 触发器内容或 render 函数 |
Autocomplete.Value
| Prop | 类型 | 默认值 | 描述 |
|---|---|---|---|
className | string | - | 附加 CSS 类 |
children | ReactNode | RenderFunction | - | 值内容或 render 函数 |
Autocomplete.Indicator
| Prop | 类型 | 默认值 | 描述 |
|---|---|---|---|
className | string | - | 附加 CSS 类 |
children | ReactNode | - | 自定义指示器内容 |
Autocomplete.ClearButton
| Prop | 类型 | 默认值 | 描述 |
|---|---|---|---|
className | string | - | 附加 CSS 类 |
onClick | (e: MouseEvent) => void | - | 点击按钮时的回调 |
ref | RefObject<HTMLButtonElement> | - | 清除按钮元素的 ref |
Autocomplete.Popover
| Prop | 类型 | 默认值 | 描述 |
|---|---|---|---|
placement | "bottom" | "bottom left" | "bottom right" | "bottom start" | "bottom end" | "top" | "top left" | "top right" | "top start" | "top end" | "left" | "left top" | "left bottom" | "start" | "start top" | "start bottom" | "right" | "right top" | "right bottom" | "end" | "end top" | "end bottom" | "bottom" | 弹出层相对触发器的位置 |
className | string | - | 附加 CSS 类 |
children | ReactNode | - | 子内容 |
Autocomplete.Filter
| Prop | 类型 | 默认值 | 描述 |
|---|---|---|---|
filter | (text: string, input: string) => boolean | - | 自定义过滤函数 |
inputValue | string | - | 受控输入值 |
onInputChange | (value: string) => void | - | 输入值变化时的回调 |
children | ReactNode | - | 过滤内容(SearchField 与 ListBox) |
useFilter Hook
React Aria 的 useFilter hook 提供自动完成过滤函数。
import {useFilter} from "@lenso/ui";
const {contains} = useFilter({sensitivity: "base"});
<Autocomplete.Filter filter={contains}> <SearchField>...</SearchField> <ListBox>...</ListBox></Autocomplete.Filter>Options:
| Option | 类型 | 默认值 | 描述 |
|---|---|---|---|
sensitivity | "base" | "accent" | "case" | "variant" | "base" | 匹配的 locale 敏感度 |
Returns:
| Function | 类型 | 描述 |
|---|---|---|
contains | (string: string, substring: string) => boolean | 判断字符串是否包含子串 |
startsWith | (string: string, substring: string) => boolean | 判断字符串是否以子串开头 |
endsWith | (string: string, substring: string) => boolean | 判断字符串是否以子串结尾 |
Render Props
使用 Autocomplete.Value 的 render 函数时,提供以下值:
| Prop | 类型 | 描述 |
|---|---|---|
defaultChildren | ReactNode | 默认渲染的值。多选模式下为每个选中项 children 的副本,以符合当前语言环境的分隔符连接 |
isPlaceholder | boolean | 是否为占位符 |
state | SelectState | 自动完成的状态。如需获取选中的集合节点,请使用 state.selectedItems |
selectedItems | (T | null)[] | 当前选中项的值 |
selectedText | string | 选中项的 textValue,以符合当前语言环境的分隔符连接 |
无障碍
Autocomplete 组件实现带过滤的 ARIA select 模式,提供:
- 完整键盘导航支持
- 选择变化的屏幕阅读器播报
- 正确的焦点管理
- 禁用状态支持
- 带过滤的搜索功能
- HTML 表单集成
更多信息请参阅 React Aria Select 文档。