# 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.
