الإكمال التلقائي: المجموعات والفواصل
هذه الصفحة غير مترجمة بعد، وهي معروضة بالإنجليزية.
The Autocomplete groups its options when you pass getOptionGroup, a function that returns an option’s group name or undefined. The name is also the group’s label. Options stay one flat list, and a hairline separates the sections.
Nothing selected.
import { useMemo, useState } from "react";
import { Autocomplete, type OptionValue } from "slotsmith/autocomplete";
import { countries, type Country } from "../shared/options";
/** Each country's region, the name of the group it joins. */
const regions: Record<string, string> = {
ar: "Americas", br: "Americas", ca: "Americas", mx: "Americas", us: "Americas",
eg: "Africa", ke: "Africa", ma: "Africa", za: "Africa",
in: "Asia", jp: "Asia", jo: "Asia", sa: "Asia", ae: "Asia", tr: "Asia",
fr: "Europe", de: "Europe", nl: "Europe", no: "Europe", pt: "Europe", es: "Europe", se: "Europe", gb: "Europe",
};
export default function Groups() {
const [byRegion, setByRegion] = useState(true);
const [country, setCountry] = useState<OptionValue | null>(null);
// Groups appear in the order of their first option, so sorting the options sets the group order.
const options = useMemo(
() => (byRegion ? [...countries].sort((a, b) => (regions[a.code] ?? "").localeCompare(regions[b.code] ?? "")) : countries),
[byRegion],
);
return (
<div style={{ display: "grid", gap: "1rem", maxWidth: 360 }}>
<label>
<input type="checkbox" checked={byRegion} onChange={(event) => setByRegion(event.target.checked)} /> Sort by region
</label>
<Autocomplete<Country>
options={options}
getOptionValue={(entry) => entry.code}
getOptionLabel={(entry) => entry.name}
// The group's name is also its label. Australia has none, so it sits in an unlabelled section.
getOptionGroup={(entry) => regions[entry.code]}
value={country}
onChange={setCountry}
aria-label="Country"
/>
<p>{country === null ? "Nothing selected." : `Selected: ${country}`}</p>
</div>
);
}export type Country = { code: string; name: string };
/** Sorted by name, as a picker shows them. */
export const countries: Country[] = [
{ code: "ar", name: "Argentina" },
{ code: "au", name: "Australia" },
{ code: "br", name: "Brazil" },
{ code: "ca", name: "Canada" },
{ code: "eg", name: "Egypt" },
{ code: "fr", name: "France" },
{ code: "de", name: "Germany" },
{ code: "in", name: "India" },
{ code: "jp", name: "Japan" },
{ code: "jo", name: "Jordan" },
{ code: "ke", name: "Kenya" },
{ code: "mx", name: "Mexico" },
{ code: "ma", name: "Morocco" },
{ code: "nl", name: "Netherlands" },
{ code: "no", name: "Norway" },
{ code: "pt", name: "Portugal" },
{ code: "sa", name: "Saudi Arabia" },
{ code: "za", name: "South Africa" },
{ code: "es", name: "Spain" },
{ code: "se", name: "Sweden" },
{ code: "tr", name: "Türkiye" },
{ code: "ae", name: "United Arab Emirates" },
{ code: "gb", name: "United Kingdom" },
{ code: "us", name: "United States" },
];
/** `id` and `label` are the names the autocomplete reads by default, so topics need no getters. */
export type Topic = { id: string; label: string; archived?: boolean };
export const topics: Topic[] = [
{ id: "accessibility", label: "Accessibility" },
{ id: "animation", label: "Animation" },
{ id: "caching", label: "Caching" },
{ id: "design-systems", label: "Design systems" },
{ id: "forms", label: "Forms" },
{ id: "i18n", label: "Internationalisation" },
{ id: "performance", label: "Performance" },
{ id: "rtl", label: "Right-to-left" },
{ id: "server-components", label: "Server components" },
{ id: "state", label: "State" },
{ id: "testing", label: "Testing" },
{ id: "typescript", label: "TypeScript" },
{ id: "legacy", label: "Legacy browsers", archived: true },
];
/** A country with the region it is listed under; a `disabled` one stays in the list but cannot be picked. */
export type RegionCountry = Country & { region: string; disabled?: boolean };
/** A short list grouped by region. Groups show in the order of their first option, so the regions come in this order. */
export const countriesByRegion: RegionCountry[] = [
{ code: "de", name: "Germany", region: "Europe" },
{ code: "fr", name: "France", region: "Europe" },
{ code: "es", name: "Spain", region: "Europe" },
{ code: "ge", name: "Georgia", region: "Europe" },
{ code: "dz", name: "Algeria", region: "Africa" },
{ code: "eg", name: "Egypt", region: "Africa" },
{ code: "ng", name: "Nigeria", region: "Africa" },
{ code: "ne", name: "Niger", region: "Africa", disabled: true },
{ code: "jp", name: "Japan", region: "Asia" },
{ code: "sg", name: "Singapore", region: "Asia" },
{ code: "sa", name: "Saudi Arabia", region: "Asia" },
{ code: "sy", name: "Syria", region: "Asia" },
];Open the picker, then untick “Sort by region” and open it again: the groups change order, but each region’s countries stay together.
Order
getOptionGroup does not sort anything. The order comes from options:
- Groups appear in the order of their first option. Sort the options by group first, as the demo does, to choose the group order.
- An option joins its group wherever it sits in the list, so a group’s options need not be next to each other.
- Options with no group form one unlabelled section, placed where the first of them appears. In the demo that is Australia.
- A group the search empties disappears, with its label and its separator.
The keyboard follows the order on screen. The arrow keys, Home, End, PgUp, PgDn, typeahead and autoHighlight move through the options only. Labels and separators are never highlighted.
Remote pages and virtual lists
With remote options, each new page joins the groups already on screen. A page that brings more countries from Europe adds them under the existing Europe label, not under a second one.
VirtualAutocomplete renders labels and separators as rows of their own. Their heights come from virtual.groupLabelSize (28 by default) and virtual.separatorSize (9). Change them if your GroupLabel or Separator slot is taller. When a label scrolls out of view, the list keeps a hidden copy of it, so the group still has a name.
Changing the parts
Three slots draw the groups:
Groupwraps one group’s options.GroupLabelis the label. It receiveshiddenfor the virtual list’s hidden copy, so pass that on.Separatoris the line between sections.
In your own layout, the compound parts Autocomplete.Group, Autocomplete.GroupLabel and Autocomplete.Separator render the same parts; see compound parts. The headless useAutocomplete returns sections and the matching prop getters. The adapters draw groups with each library’s own label and divider.
Accessibility
Each group has role="group" and is named by its label through aria-labelledby. The label itself has role="presentation", so it is not read as an extra item. The separator is hidden from assistive technology, because a listbox may only contain options and groups.