laif-ds
Version:
Design System di Laif con componenti React basati su principi di Atomic Design
119 lines (97 loc) • 6.07 kB
Markdown
# AsyncSelect
## Overview
Generic async select with search input, popover list, server-side filtering, and optional multiple selection. Fully render-prop driven for option content and value extraction.
---
## Props
| Prop | Type | Default | Description |
| --------------------- | ------------------------------------- | ------------- | ----------------------------------------------------------------------------------- |
| `fetcher` | `(query?: string) => Promise<T[]>` | **required** | Async loader for options. Called on open and on search. |
| `renderOptionItem` | `(option: T) => React.ReactNode` | **required** | Custom renderer for each menu row. |
| `resolveOptionValue` | `(option: T) => string` | **required** | Returns the unique ID stored in `value`. |
| `renderSelectedValue` | `(option: T) => React.ReactNode` | **required** | Content shown in the trigger when selected. |
| `multiple` | `boolean` | `false` | Enables multi-selection and switches `value` to `string[]`. |
| `value` | `string \| string[] \| undefined` | `undefined` | Controlled value. |
| `onChange` | `(value: string \| string[]) => void` | `undefined` | Emits the next value when the selection changes. |
| `label` | `string \| React.ReactNode` | `undefined` | Optional label rendered above the trigger. |
| `placeholder` | `string` | `"Select..."` | Placeholder when no value is selected. |
| `disabled` | `boolean` | `false` | Disables trigger and input interactions. |
| `className` | `string` | `undefined` | Extra classes for the trigger button. |
| `wrpClassName` | `string` | `undefined` | Classes applied to the outer wrapper (use this to control width, e.g. `w-[200px]`). |
| `noResultsMessage` | `string` | `undefined` | Message shown inside the default empty state. |
| `clearable` | `boolean` | `true` | Shows the clear icon when a value is present. |
| `size` | `"sm" \| "default" \| "lg"` | `"default"` | Trigger size variant. |
| `notFound` | `React.ReactNode` | `undefined` | Custom fallback when there are no options. |
| `initialOptions` | `T[]` | `undefined` | Prefills cache/selection with a known options list. |
| `debounce` | `number` | `300` | Debounce delay (ms) before calling `fetcher`. |
---
## Behavior
- **Search**: Debounced input (300ms) that always hits the provided `fetcher`.
- **Caching**: Results cached by query; reused when re-typing.
- **Multiple**: Renders selected count or first label; supports checkbox UI per item.
- **Clear**: Built-in clear icon (uses DS `Icon`), respects `clearable`.
- **A11y**: Trigger focus ring and keyboard navigation via `cmdk`.
---
## Examples
### Multiple with Steps
```tsx
import * as React from "react";
import { AsyncSelect } from "laif-ds";
type Tag = { id: string; label: string };
const tags: Tag[] = [
{ id: "react", label: "React" },
{ id: "nextjs", label: "Next.js" },
{ id: "tailwind", label: "Tailwind" },
];
export function MultipleTags() {
const [value, setValue] = React.useState<string[]>([]);
const fetcher = async (q?: string) =>
!q ? tags : tags.filter((t) => t.label.toLowerCase().includes(q!.toLowerCase()));
return (
<AsyncSelect<Tag>
multiple
fetcher={fetcher}
value={value}
onChange={setValue}
renderOptionItem={(t) => t.label}
resolveOptionValue={(t) => t.id}
renderSelectedValue={(t) => t.label}
placeholder="Select tags..."
wrpClassName="w-[280px]"
/>
);
}
---
## Notes
- **Trigger width**: Use `width="auto"` to match trigger width; or a fixed `number|string`.
- **Filtering**: Always handled server-side via `fetcher`, with cached responses per query.
- **Theming**: Uses DS token classes across trigger, list, and states.
type Tag = { id: string; label: string };
const tags: Tag[] = [
{ id: "react", label: "React" },
{ id: "nextjs", label: "Next.js" },
{ id: "tailwind", label: "Tailwind" },
];
export function MultipleTags() {
const [value, setValue] = React.useState<string[]>([]);
const fetcher = async (q?: string) =>
!q ? tags : tags.filter((t) => t.label.toLowerCase().includes(q!.toLowerCase()));
return (
<AsyncSelect<Tag>
multiple
fetcher={fetcher}
value={value}
onChange={setValue}
renderOption={(t) => t.label}
getOptionValue={(t) => t.id}
getDisplayValue={(t) => t.label}
placeholder="Select tags..."
width="auto"
/>
);
}
```
---
## Notes
- **Trigger width**: Use `width="auto"` to match trigger width; or a fixed `number|string`.
- **Filtering**: Always handled server-side via `fetcher`, with cached responses per query.
- **Theming**: Uses DS token classes across trigger, list, and states.