- Accordion
- Alert
- Alert Dialog
- Autocomplete
- Auth Surface
- Avatar
- Badge
- Browse Catalog Dialog
- Button
- Card
- Checkbox
- Checkbox Group
- Collapsible
- Combobox
- Command
- Connector Setup Dialog
- Cookie Banner
- Dialog
- Directory Card
- Directory Detail
- Directory Skeleton
- DrawerНовое
- Token Parts Input
- Empty
- Field
- Fieldset
- File Preview Modal
- File Preview Skeleton
- Form
- Frame
- Group
- Icon
- Input
- Input Group
- Kbd
- Label
- Legal Shell
- Menu
- Mermaid Diagram
- Mind Map Diagram
- Not Found Screen
- Onboarding Frame
- Popover
- PDF Thumbnail
- Personalization Landing
- Preview Card
- Pricing Page
- Progress
- Radio Group
- Ring Spinner
- Scroll Area
- Select
- Separator
- Settings Page
- Settings Skills
- Settings Connectors
- Settings Capabilities
- Settings Usage
- Settings Account
- Settings Billing
- Sheet
- Sidebar
- Skeleton
- Skill Create Dialog
- Slider
- Spinner
- Stat
- Switch
- Table
- Tabs
- Textarea
- Toast
- Toggle
- Tooltip
- Компоненты AI
- Chat Conversation
- Chat Message
- Chat Response
- Chat Suggestion
- Chat Prompt Input
- Slash Highlighted Textarea
- Chat Search Dialog
- Chat Skill Doc
- Chat Connector Detail
- Chat Attachments
- Chat File Card
- Chat Token Chip
- Chat Code Block
- Chat Image
- Chat Inline Citation
- Chat Sources
- Chat Web Search
- Chat Research
- Chat Source
- Chat Actions
- Chat Context
- Chat Loader
- Chat Compaction
- Chat Timeline
- Chat Snippet
- Chat Terminal
- Chat Stack Trace
- Chat Test Results
- Chat File Tree
- Chat Environment Variables
- Chat Audio Player
- Chat Transcription
- Chat Speech Input
- Chat Mic Selector
- Chat Voice Selector
- Chat Agent
- Chat Persona
- Chat Connection
- Chat Connector Suggestion
- Chat Queue
- Chat Checkpoint
- Chat Confirmation
- Chat Artifact
- Chat JSX Preview
- Chat Schema Display
- Chat Package Info
- Chat Commit
- Chat Plan
- Chat Open In Chat
- Chat Sandbox
- Chat Model Selector
- Chat Canvas
- Chat Node
- Chat Edge
Autocomplete
Поле ввода, предлагающее варианты по мере набора текста.
"use client";
import {
Autocomplete,
AutocompleteEmpty,
AutocompleteInput,
AutocompleteItem,
AutocompleteList,
AutocompletePopup,
} from "@/components/ui/autocomplete";
const items = [
{ label: "Яблоко", value: "apple" },
{ label: "Банан", value: "banana" },
{ label: "Апельсин", value: "orange" },
{ label: "Виноград", value: "grape" },
{ label: "Клубника", value: "strawberry" },
{ label: "Манго", value: "mango" },
{ label: "Ананас", value: "pineapple" },
{ label: "Киви", value: "kiwi" },
{ label: "Персик", value: "peach" },
{ label: "Груша", value: "pear" },
];
export default function Particle() {
return (
<Autocomplete items={items}>
<AutocompleteInput
aria-label="Поиск по списку"
placeholder="Поиск по списку…"
/>
<AutocompletePopup>
<AutocompleteEmpty>Ничего не найдено.</AutocompleteEmpty>
<AutocompleteList>
{(item) => (
<AutocompleteItem key={item.value} value={item}>
{item.label}
</AutocompleteItem>
)}
</AutocompleteList>
</AutocompletePopup>
</Autocomplete>
);
}
Установка
pnpm dlx shadcn@latest add @oracul/autocomplete
Использование
import {
Autocomplete,
AutocompleteEmpty,
AutocompleteInput,
AutocompleteItem,
AutocompleteList,
AutocompletePopup,
} from "@/components/ui/autocomplete"const items = [
{ value: "apple", label: "Apple" },
{ value: "banana", label: "Banana" },
{ value: "orange", label: "Orange" },
{ value: "grape", label: "Grape" },
]
<Autocomplete items={items}>
<AutocompleteInput placeholder="Search..." />
<AutocompletePopup>
<AutocompleteEmpty>No results found.</AutocompleteEmpty>
<AutocompleteList>
{(item) => <AutocompleteItem key={item.value} value={item}>{item.label}</AutocompleteItem>}
</AutocompleteList>
</AutocompletePopup>
</Autocomplete>API
Autocomplete
Корневой компонент автодополнения. Управляет состоянием автодополнения и предоставляет контекст дочерним компонентам.
| Свойство | Тип | Описание |
|---|---|---|
items | readonly unknown[] | Массив элементов для отображения в автодополнении |
open | boolean | Управляет тем, открыт ли всплывающий список |
...props | React.ComponentProps<typeof Autocomplete> | Поддерживаются все свойства Autocomplete из Base UI |
AutocompleteInput
Компонент поля ввода с расширенными возможностями для вариантов размера и поддержки дополнений.
| Свойство | Тип | По умолчанию | Описание |
|---|---|---|---|
size | "sm" | "default" | "lg" | "default" | Вариант размера поля ввода |
startAddon | React.ReactNode | - | Элемент для отображения в начале (слева) поля ввода, например значок |
showTrigger | boolean | false | Отображать ли кнопку-триггер (значок шеврона) справа от поля ввода |
showClear | boolean | false | Отображать ли кнопку очистки (значок X) справа от поля ввода, когда в нём есть значение |
triggerProps | Autocomplete.Trigger.Props | - | Свойства, передаваемые внутренней кнопке-триггеру; удобно для переопределения aria-label |
clearProps | Autocomplete.Clear.Props | - | Свойства, передаваемые внутренней кнопке очистки; удобно для переопределения aria-label |
className | string | - | Дополнительные CSS-классы для применения к компоненту |
...props | Свойства Autocomplete Input из Base UI | - | Поддерживаются все стандартные атрибуты поля ввода автодополнения |
AutocompletePopup
Всплывающий контейнер, отображающий предложения автодополнения.
| Свойство | Тип | Описание |
|---|---|---|
className | string | Дополнительные CSS-классы для применения к компоненту |
portalProps | Autocomplete.Portal.Props | Свойства, передаваемые внутреннему порталу (keepMounted, container и т. д.); см. API портала Combobox в Base UI (автодополнение использует его повторно) |
...props | Свойства Autocomplete Popup из Base UI | Поддерживаются все стандартные атрибуты всплывающего списка автодополнения |
AutocompleteList
Прокручиваемый контейнер для элементов автодополнения.
| Свойство | Тип | Описание |
|---|---|---|
className | string | Дополнительные CSS-классы для применения к компоненту |
...props | Свойства Autocomplete List из Base UI | Поддерживаются все стандартные атрибуты списка автодополнения |
AutocompleteItem
Отдельный выбираемый элемент автодополнения.
| Свойство | Тип | Описание |
|---|---|---|
value | unknown | Значение элемента |
className | string | Дополнительные CSS-классы для применения к компоненту |
...props | Свойства Autocomplete Item из Base UI | Поддерживаются все стандартные атрибуты элемента автодополнения |
AutocompleteEmpty
Отображает сообщение, когда результаты не найдены.
| Свойство | Тип | Описание |
|---|---|---|
className | string | Дополнительные CSS-классы для применения к компоненту |
...props | Свойства Autocomplete Empty из Base UI | Поддерживаются все стандартные атрибуты пустого состояния автодополнения |
AutocompleteGroup
Объединяет связанные элементы автодополнения в группу.
| Свойство | Тип | Описание |
|---|---|---|
items | readonly unknown[] | Массив элементов в этой группе |
className | string | Дополнительные CSS-классы для применения к компоненту |
...props | Свойства Autocomplete Group из Base UI | Поддерживаются все стандартные атрибуты группы автодополнения |
AutocompleteGroupLabel
Отображает метку для группы автодополнения.
| Свойство | Тип | Описание |
|---|---|---|
className | string | Дополнительные CSS-классы для применения к компоненту |
...props | Свойства Autocomplete Group Label из Base UI | Поддерживаются все стандартные атрибуты метки группы автодополнения |
AutocompleteCollection
Используется для обёртывания элементов внутри группы при рендеринге.
| Свойство | Тип | Описание |
|---|---|---|
...props | Свойства Autocomplete Collection из Base UI | Поддерживаются все стандартные атрибуты коллекции автодополнения |
Примеры
Отключённый
"use client";
import {
Autocomplete,
AutocompleteEmpty,
AutocompleteInput,
AutocompleteItem,
AutocompleteList,
AutocompletePopup,
} from "@/components/ui/autocomplete";
const items = [
{ label: "Яблоко", value: "apple" },
{ label: "Банан", value: "banana" },
{ label: "Апельсин", value: "orange" },
{ label: "Виноград", value: "grape" },
{ label: "Клубника", value: "strawberry" },
{ label: "Манго", value: "mango" },
{ label: "Ананас", value: "pineapple" },
{ label: "Киви", value: "kiwi" },
{ label: "Персик", value: "peach" },
{ label: "Груша", value: "pear" },
];
export default function Particle() {
return (
<Autocomplete disabled items={items}>
<AutocompleteInput
aria-label="Поиск по списку"
placeholder="Поиск по списку…"
/>
<AutocompletePopup>
<AutocompleteEmpty>Ничего не найдено.</AutocompleteEmpty>
<AutocompleteList>
{(item) => (
<AutocompleteItem key={item.value} value={item}>
{item.label}
</AutocompleteItem>
)}
</AutocompleteList>
</AutocompletePopup>
</Autocomplete>
);
}
Маленький размер
"use client";
import {
Autocomplete,
AutocompleteEmpty,
AutocompleteInput,
AutocompleteItem,
AutocompleteList,
AutocompletePopup,
} from "@/components/ui/autocomplete";
const items = [
{ label: "Яблоко", value: "apple" },
{ label: "Банан", value: "banana" },
{ label: "Апельсин", value: "orange" },
{ label: "Виноград", value: "grape" },
{ label: "Клубника", value: "strawberry" },
{ label: "Манго", value: "mango" },
{ label: "Ананас", value: "pineapple" },
{ label: "Киви", value: "kiwi" },
{ label: "Персик", value: "peach" },
{ label: "Груша", value: "pear" },
];
export default function Particle() {
return (
<Autocomplete items={items}>
<AutocompleteInput
aria-label="Поиск по списку"
placeholder="Поиск по списку…"
size="sm"
/>
<AutocompletePopup>
<AutocompleteEmpty>Ничего не найдено.</AutocompleteEmpty>
<AutocompleteList>
{(item) => (
<AutocompleteItem key={item.value} value={item}>
{item.label}
</AutocompleteItem>
)}
</AutocompleteList>
</AutocompletePopup>
</Autocomplete>
);
}
Большой размер
"use client";
import {
Autocomplete,
AutocompleteEmpty,
AutocompleteInput,
AutocompleteItem,
AutocompleteList,
AutocompletePopup,
} from "@/components/ui/autocomplete";
const items = [
{ label: "Яблоко", value: "apple" },
{ label: "Банан", value: "banana" },
{ label: "Апельсин", value: "orange" },
{ label: "Виноград", value: "grape" },
{ label: "Клубника", value: "strawberry" },
{ label: "Манго", value: "mango" },
{ label: "Ананас", value: "pineapple" },
{ label: "Киви", value: "kiwi" },
{ label: "Персик", value: "peach" },
{ label: "Груша", value: "pear" },
];
export default function Particle() {
return (
<Autocomplete items={items}>
<AutocompleteInput
aria-label="Поиск по списку"
placeholder="Поиск по списку…"
size="lg"
/>
<AutocompletePopup>
<AutocompleteEmpty>Ничего не найдено.</AutocompleteEmpty>
<AutocompleteList>
{(item) => (
<AutocompleteItem key={item.value} value={item}>
{item.label}
</AutocompleteItem>
)}
</AutocompleteList>
</AutocompletePopup>
</Autocomplete>
);
}
С меткой
"use client";
import { useId } from "react";
import {
Autocomplete,
AutocompleteEmpty,
AutocompleteInput,
AutocompleteItem,
AutocompleteList,
AutocompletePopup,
} from "@/components/ui/autocomplete";
import { Label } from "@/components/ui/label";
const items = [
{ label: "Яблоко", value: "apple" },
{ label: "Банан", value: "banana" },
{ label: "Апельсин", value: "orange" },
{ label: "Виноград", value: "grape" },
{ label: "Клубника", value: "strawberry" },
{ label: "Манго", value: "mango" },
{ label: "Ананас", value: "pineapple" },
{ label: "Киви", value: "kiwi" },
{ label: "Персик", value: "peach" },
{ label: "Груша", value: "pear" },
];
export default function Particle() {
const id = useId();
return (
<Autocomplete items={items}>
<div className="flex flex-col items-start gap-2">
<Label htmlFor={id}>Фрукты</Label>
<AutocompleteInput
aria-label="Поиск по списку"
id={id}
placeholder="Поиск по списку…"
/>
</div>
<AutocompletePopup>
<AutocompleteEmpty>Ничего не найдено.</AutocompleteEmpty>
<AutocompleteList>
{(item) => (
<AutocompleteItem key={item.value} value={item}>
{item.label}
</AutocompleteItem>
)}
</AutocompleteList>
</AutocompletePopup>
</Autocomplete>
);
}
Встроенное автодополнение
Автозаполнение поля ввода подсвеченным элементом при навигации стрелками.
"use client";
import {
Autocomplete,
AutocompleteEmpty,
AutocompleteInput,
AutocompleteItem,
AutocompleteList,
AutocompletePopup,
} from "@/components/ui/autocomplete";
const items = [
{ label: "Яблоко", value: "apple" },
{ label: "Банан", value: "banana" },
{ label: "Апельсин", value: "orange" },
{ label: "Виноград", value: "grape" },
{ label: "Клубника", value: "strawberry" },
{ label: "Манго", value: "mango" },
{ label: "Ананас", value: "pineapple" },
{ label: "Киви", value: "kiwi" },
{ label: "Персик", value: "peach" },
{ label: "Груша", value: "pear" },
];
export default function Particle() {
return (
<Autocomplete items={items} mode="both">
<AutocompleteInput
aria-label="Поиск по списку"
placeholder="Поиск по списку…"
/>
<AutocompletePopup>
<AutocompleteEmpty>Ничего не найдено.</AutocompleteEmpty>
<AutocompleteList>
{(item) => (
<AutocompleteItem key={item.value} value={item}>
{item.label}
</AutocompleteItem>
)}
</AutocompleteList>
</AutocompletePopup>
</Autocomplete>
);
}
Автоподсветка
Автоматически подсвечивать первый подходящий вариант.
"use client";
import {
Autocomplete,
AutocompleteEmpty,
AutocompleteInput,
AutocompleteItem,
AutocompleteList,
AutocompletePopup,
} from "@/components/ui/autocomplete";
const items = [
{ label: "Яблоко", value: "apple" },
{ label: "Банан", value: "banana" },
{ label: "Апельсин", value: "orange" },
{ label: "Виноград", value: "grape" },
{ label: "Клубника", value: "strawberry" },
{ label: "Манго", value: "mango" },
{ label: "Ананас", value: "pineapple" },
{ label: "Киви", value: "kiwi" },
{ label: "Персик", value: "peach" },
{ label: "Груша", value: "pear" },
];
export default function Particle() {
return (
<Autocomplete autoHighlight items={items}>
<AutocompleteInput
aria-label="Поиск по списку"
placeholder="Поиск по списку…"
/>
<AutocompletePopup>
<AutocompleteEmpty>Ничего не найдено.</AutocompleteEmpty>
<AutocompleteList>
{(item) => (
<AutocompleteItem key={item.value} value={item}>
{item.label}
</AutocompleteItem>
)}
</AutocompleteList>
</AutocompletePopup>
</Autocomplete>
);
}
С кнопкой очистки
"use client";
import {
Autocomplete,
AutocompleteEmpty,
AutocompleteInput,
AutocompleteItem,
AutocompleteList,
AutocompletePopup,
} from "@/components/ui/autocomplete";
const items = [
{ label: "Яблоко", value: "apple" },
{ label: "Банан", value: "banana" },
{ label: "Апельсин", value: "orange" },
{ label: "Виноград", value: "grape" },
{ label: "Клубника", value: "strawberry" },
{ label: "Манго", value: "mango" },
{ label: "Ананас", value: "pineapple" },
{ label: "Киви", value: "kiwi" },
{ label: "Персик", value: "peach" },
{ label: "Груша", value: "pear" },
];
export default function Particle() {
return (
<Autocomplete items={items}>
<AutocompleteInput
aria-label="Поиск по списку"
placeholder="Поиск по списку…"
showClear
/>
<AutocompletePopup>
<AutocompleteEmpty>Ничего не найдено.</AutocompleteEmpty>
<AutocompleteList>
{(item) => (
<AutocompleteItem key={item.value} value={item}>
{item.label}
</AutocompleteItem>
)}
</AutocompleteList>
</AutocompletePopup>
</Autocomplete>
);
}
С кнопками триггера и очистки
"use client";
import {
Autocomplete,
AutocompleteEmpty,
AutocompleteInput,
AutocompleteItem,
AutocompleteList,
AutocompletePopup,
} from "@/components/ui/autocomplete";
const items = [
{ label: "Яблоко", value: "apple" },
{ label: "Банан", value: "banana" },
{ label: "Апельсин", value: "orange" },
{ label: "Виноград", value: "grape" },
{ label: "Клубника", value: "strawberry" },
{ label: "Манго", value: "mango" },
{ label: "Ананас", value: "pineapple" },
{ label: "Киви", value: "kiwi" },
{ label: "Персик", value: "peach" },
{ label: "Груша", value: "pear" },
];
export default function Particle() {
return (
<Autocomplete items={items}>
<AutocompleteInput
aria-label="Поиск по списку"
placeholder="Поиск по списку…"
showClear
showTrigger
/>
<AutocompletePopup>
<AutocompleteEmpty>Ничего не найдено.</AutocompleteEmpty>
<AutocompleteList>
{(item) => (
<AutocompleteItem key={item.value} value={item}>
{item.label}
</AutocompleteItem>
)}
</AutocompleteList>
</AutocompletePopup>
</Autocomplete>
);
}
С дополнением в начале
Отобразите значок или другой элемент в начале поля ввода с помощью свойства startAddon.
"use client";
import { IconSearch } from "@tabler/icons-react";
import {
Autocomplete,
AutocompleteEmpty,
AutocompleteInput,
AutocompleteItem,
AutocompleteList,
AutocompletePopup,
} from "@/components/ui/autocomplete";
const items = [
{ label: "Яблоко", value: "apple" },
{ label: "Банан", value: "banana" },
{ label: "Апельсин", value: "orange" },
{ label: "Виноград", value: "grape" },
{ label: "Клубника", value: "strawberry" },
{ label: "Манго", value: "mango" },
{ label: "Ананас", value: "pineapple" },
{ label: "Киви", value: "kiwi" },
{ label: "Персик", value: "peach" },
{ label: "Груша", value: "pear" },
];
export default function Particle() {
return (
<Autocomplete items={items}>
<AutocompleteInput
aria-label="Поиск продуктов"
placeholder="Поиск продуктов…"
startAddon={<IconSearch stroke={1.5} />}
/>
<AutocompletePopup>
<AutocompleteEmpty>Ничего не найдено.</AutocompleteEmpty>
<AutocompleteList>
{(item) => (
<AutocompleteItem key={item.value} value={item}>
{item.label}
</AutocompleteItem>
)}
</AutocompleteList>
</AutocompletePopup>
</Autocomplete>
);
}
С группами
"use client";
import { Fragment } from "react";
import {
Autocomplete,
AutocompleteCollection,
AutocompleteEmpty,
AutocompleteGroup,
AutocompleteGroupLabel,
AutocompleteInput,
AutocompleteItem,
AutocompleteList,
AutocompletePopup,
AutocompleteSeparator,
} from "@/components/ui/autocomplete";
// Grouped items demo
type Tag = { id: string; label: string; group: "Status" | "Priority" | "Team" };
type TagGroup = { value: string; items: Tag[] };
const tagsData: Tag[] = [
// Status
{ group: "Status", id: "s-open", label: "Открыта" },
{ group: "Status", id: "s-in-progress", label: "В работе" },
{ group: "Status", id: "s-blocked", label: "Заблокирована" },
{ group: "Status", id: "s-resolved", label: "Решена" },
{ group: "Status", id: "s-closed", label: "Закрыта" },
// Priority
{ group: "Priority", id: "p-low", label: "Низкий" },
{ group: "Priority", id: "p-medium", label: "Средний" },
{ group: "Priority", id: "p-high", label: "Высокий" },
{ group: "Priority", id: "p-urgent", label: "Срочный" },
// Team
{ group: "Team", id: "t-design", label: "Дизайн" },
{ group: "Team", id: "t-frontend", label: "Фронтенд" },
{ group: "Team", id: "t-backend", label: "Бэкенд" },
{ group: "Team", id: "t-devops", label: "DevOps" },
{ group: "Team", id: "t-qa", label: "Тестирование" },
{ group: "Team", id: "t-mobile", label: "Мобильная разработка" },
{ group: "Team", id: "t-data", label: "Данные" },
{ group: "Team", id: "t-security", label: "Безопасность" },
{ group: "Team", id: "t-platform", label: "Платформа" },
{ group: "Team", id: "t-infra", label: "Инфраструктура" },
{ group: "Team", id: "t-product", label: "Продукт" },
{ group: "Team", id: "t-marketing", label: "Маркетинг" },
{ group: "Team", id: "t-sales", label: "Продажи" },
{ group: "Team", id: "t-support", label: "Поддержка" },
{ group: "Team", id: "t-research", label: "Исследования" },
{ group: "Team", id: "t-content", label: "Контент" },
{ group: "Team", id: "t-analytics", label: "Аналитика" },
{ group: "Team", id: "t-operations", label: "Операции" },
{ group: "Team", id: "t-finance", label: "Финансы" },
{ group: "Team", id: "t-hr", label: "HR" },
{ group: "Team", id: "t-legal", label: "Юристы" },
{ group: "Team", id: "t-growth", label: "Рост" },
{ group: "Team", id: "t-partner", label: "Партнёры" },
{ group: "Team", id: "t-community", label: "Сообщество" },
{ group: "Team", id: "t-docs", label: "Документация" },
{ group: "Team", id: "t-l10n", label: "Локализация" },
{ group: "Team", id: "t-a11y", label: "Доступность" },
{ group: "Team", id: "t-sre", label: "SRE" },
{ group: "Team", id: "t-release", label: "Релизы" },
{ group: "Team", id: "t-architecture", label: "Архитектура" },
{ group: "Team", id: "t-ux", label: "UX" },
{ group: "Team", id: "t-ui", label: "UI" },
{ group: "Team", id: "t-management", label: "Менеджмент" },
];
function groupTags(tags: Tag[]): TagGroup[] {
const groups: Record<string, Tag[]> = {};
for (const tag of tags) {
if (!groups[tag.group]) {
groups[tag.group] = [];
}
groups[tag.group]?.push(tag);
}
const order: Array<TagGroup["value"]> = ["Status", "Priority", "Team"];
return order.map((value) => ({ items: groups[value] ?? [], value }));
}
const groupedTags: TagGroup[] = groupTags(tagsData);
export default function Particle() {
return (
<Autocomplete items={groupedTags}>
<div className="flex flex-col items-start gap-2">
<AutocompleteInput
aria-label="Поиск по тегам"
placeholder="например, фича"
/>
</div>
<AutocompletePopup>
<AutocompleteEmpty>Теги не найдены.</AutocompleteEmpty>
<AutocompleteList>
{(group: TagGroup) => (
<Fragment key={group.value}>
<AutocompleteGroup items={group.items}>
<AutocompleteGroupLabel>{group.value}</AutocompleteGroupLabel>
<AutocompleteCollection>
{(tag: Tag) => (
<AutocompleteItem key={tag.id} value={tag}>
{tag.label}
</AutocompleteItem>
)}
</AutocompleteCollection>
</AutocompleteGroup>
{group.value !== "Team" && <AutocompleteSeparator />}
</Fragment>
)}
</AutocompleteList>
</AutocompletePopup>
</Autocomplete>
);
}
С ограничением результатов
"use client";
import { useMemo, useState } from "react";
import {
Autocomplete,
AutocompleteEmpty,
AutocompleteInput,
AutocompleteItem,
AutocompleteList,
AutocompletePopup,
AutocompleteStatus,
useAutocompleteFilter,
} from "@/components/ui/autocomplete";
// Limit results demo
const limit = 7;
type SimpleTag = { id: string; value: string };
const manyTags: SimpleTag[] = [
{ id: "lang-js", value: "JavaScript" },
{ id: "lang-ts", value: "TypeScript" },
{ id: "lang-py", value: "Python" },
{ id: "lang-java", value: "Java" },
{ id: "lang-csharp", value: "C#" },
{ id: "lang-cpp", value: "C++" },
{ id: "lang-c", value: "C" },
{ id: "lang-go", value: "Go" },
{ id: "lang-rust", value: "Rust" },
{ id: "lang-rb", value: "Ruby" },
{ id: "lang-php", value: "PHP" },
{ id: "lang-swift", value: "Swift" },
{ id: "lang-kotlin", value: "Kotlin" },
{ id: "lang-scala", value: "Scala" },
{ id: "lang-elixir", value: "Elixir" },
{ id: "lang-hs", value: "Haskell" },
{ id: "lang-dart", value: "Dart" },
{ id: "lang-objc", value: "Objective-C" },
{ id: "lang-julia", value: "Julia" },
{ id: "lang-r", value: "R" },
{ id: "lang-perl", value: "Perl" },
{ id: "lang-lua", value: "Lua" },
{ id: "lang-ocaml", value: "OCaml" },
{ id: "lang-fsharp", value: "F#" },
];
export default function Particle() {
const [value, setValue] = useState("");
const { contains } = useAutocompleteFilter({ sensitivity: "base" });
const totalMatches = useMemo(() => {
const trimmed = value.trim();
if (!trimmed) return manyTags.length;
return manyTags.filter((t) => contains(t.value, trimmed)).length;
}, [value, contains]);
const moreCount = Math.max(0, totalMatches - limit);
return (
<Autocomplete
items={manyTags}
limit={limit}
onValueChange={setValue}
value={value}
>
<AutocompleteInput placeholder="например, feature" />
<AutocompletePopup>
<AutocompleteEmpty>Теги не найдены.</AutocompleteEmpty>
<AutocompleteList>
{(tag: SimpleTag) => (
<AutocompleteItem key={tag.id} value={tag}>
{tag.value}
</AutocompleteItem>
)}
</AutocompleteList>
{moreCount > 0 && (
<AutocompleteStatus>
ещё +{moreCount} (продолжайте вводить, чтобы сузить поиск)
</AutocompleteStatus>
)}
</AutocompletePopup>
</Autocomplete>
);
}
Асинхронный поиск
"use client";
import type { ReactNode } from "react";
import { useEffect, useState } from "react";
import {
Autocomplete,
AutocompleteInput,
AutocompleteItem,
AutocompleteList,
AutocompletePopup,
AutocompleteStatus,
useAutocompleteFilter,
} from "@/components/ui/autocomplete";
import { Spinner } from "@/components/ui/spinner";
type Movie = { id: string; title: string; year: number };
const top100Movies: Movie[] = [
{ id: "1", title: "The Shawshank Redemption", year: 1994 },
{ id: "2", title: "The Godfather", year: 1972 },
{ id: "3", title: "The Dark Knight", year: 2008 },
{ id: "4", title: "The Godfather Part II", year: 1974 },
{ id: "5", title: "12 Angry Men", year: 1957 },
{ id: "8", title: "Pulp Fiction", year: 1994 },
{ id: "11", title: "Forrest Gump", year: 1994 },
{ id: "14", title: "Inception", year: 2010 },
];
async function searchMovies(
query: string,
filter: (item: string, query: string) => boolean,
): Promise<Movie[]> {
await new Promise((resolve) =>
setTimeout(resolve, Math.random() * 500 + 100),
);
if (Math.random() < 0.01 || query === "will_error") {
throw new Error("Network error");
}
return top100Movies.filter(
(movie) =>
filter(movie.title, query) || filter(movie.year.toString(), query),
);
}
export default function Particle() {
const [searchValue, setSearchValue] = useState("");
const [isLoading, setIsLoading] = useState(false);
const [searchResults, setSearchResults] = useState<Movie[]>([]);
const [error, setError] = useState<string | null>(null);
const { contains } = useAutocompleteFilter({ sensitivity: "base" });
useEffect(() => {
if (!searchValue) {
setSearchResults([]);
setIsLoading(false);
return;
}
setIsLoading(true);
setError(null);
let ignore = false;
const timeoutId = setTimeout(async () => {
try {
const results = await searchMovies(searchValue, contains);
if (!ignore) setSearchResults(results);
} catch {
if (!ignore) {
setError("Не удалось загрузить фильмы. Попробуйте ещё раз.");
setSearchResults([]);
}
} finally {
if (!ignore) setIsLoading(false);
}
}, 300);
return () => {
clearTimeout(timeoutId);
ignore = true;
};
}, [searchValue, contains]);
let status: ReactNode = `Найдено результатов: ${searchResults.length}`;
if (isLoading) {
status = (
<span className="flex items-center justify-between gap-2 text-muted-foreground">
Поиск…
<Spinner className="size-4.5 sm:size-4" />
</span>
);
} else if (error) {
status = (
<span className="font-normal text-destructive text-sm">{error}</span>
);
} else if (searchResults.length === 0 && searchValue) {
status = (
<span className="font-normal text-muted-foreground text-sm">
Фильм или год «{searchValue}» отсутствует в Топ-100 фильмов IMDb
</span>
);
}
const shouldRenderPopup = searchValue !== "";
return (
<Autocomplete
filter={null}
items={searchResults}
itemToStringValue={(item: unknown) => (item as Movie).title}
onValueChange={setSearchValue}
value={searchValue}
>
<AutocompleteInput placeholder="например, Pulp Fiction или 1994" />
{shouldRenderPopup && (
<AutocompletePopup aria-busy={isLoading || undefined}>
<AutocompleteStatus className="text-muted-foreground">
{status}
</AutocompleteStatus>
<AutocompleteList>
{(movie: Movie) => (
<AutocompleteItem key={movie.id} value={movie}>
<div className="flex w-full flex-col gap-1">
<div className="font-medium">{movie.title}</div>
<div className="text-muted-foreground text-xs">
{movie.year}
</div>
</div>
</AutocompleteItem>
)}
</AutocompleteList>
</AutocompletePopup>
)}
</Autocomplete>
);
}
Интеграция с формой
"use client";
import type { FormEvent } from "react";
import { useState } from "react";
import {
Autocomplete,
AutocompleteEmpty,
AutocompleteInput,
AutocompleteItem,
AutocompleteList,
AutocompletePopup,
} from "@/components/ui/autocomplete";
import { Button } from "@/components/ui/button";
import { Field, FieldError, FieldLabel } from "@/components/ui/field";
import { Form } from "@/components/ui/form";
const items = [
{ label: "Яблоко", value: "apple" },
{ label: "Банан", value: "banana" },
{ label: "Апельсин", value: "orange" },
{ label: "Виноград", value: "grape" },
{ label: "Клубника", value: "strawberry" },
{ label: "Манго", value: "mango" },
{ label: "Ананас", value: "pineapple" },
{ label: "Киви", value: "kiwi" },
{ label: "Персик", value: "peach" },
{ label: "Груша", value: "pear" },
];
export default function Particle() {
const [loading, setLoading] = useState(false);
const onSubmit = async (e: FormEvent<HTMLFormElement>) => {
e.preventDefault();
const formData = new FormData(e.currentTarget);
const selectedItem = formData.get("item");
// Base UI extracts the 'label' property from objects, so we need to find the corresponding value
const itemValue =
items.find((item) => item.label === selectedItem)?.value || selectedItem;
setLoading(true);
await new Promise((r) => setTimeout(r, 800));
setLoading(false);
alert(`Любимый продукт: ${itemValue || ""}`);
};
return (
<Form className="flex w-full max-w-64 flex-col gap-4" onSubmit={onSubmit}>
<Field name="item">
<FieldLabel>Любимый продукт</FieldLabel>
<Autocomplete items={items} required>
<AutocompleteInput placeholder="Поиск продуктов…" />
<AutocompletePopup>
<AutocompleteEmpty>Ничего не найдено.</AutocompleteEmpty>
<AutocompleteList>
{(item) => (
<AutocompleteItem key={item.value} value={item}>
{item.label}
</AutocompleteItem>
)}
</AutocompleteList>
</AutocompletePopup>
</Autocomplete>
<FieldError>Выберите продукт.</FieldError>
</Field>
<Button loading={loading} type="submit">
Отправить
</Button>
</Form>
);
}
На этой странице