@tailor-platform/app-shell 1.14.0 → 1.16.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE.md +21 -0
- package/dist/app-shell-core.css +1 -1
- package/dist/app-shell.d.ts +1203 -316
- package/dist/app-shell.js +4562 -3307
- package/dist/{appshell-CtS8Y3LW.js → appshell-B0wh5Lq0.js} +850 -549
- package/dist/style-entry.d.ts +118 -98
- package/dist/testing.d.ts +118 -98
- package/dist/testing.js +1 -1
- package/dist/themes/_template.css +1 -1
- package/dist/vite-plugin.d.ts +118 -98
- package/package.json +13 -13
- package/skills/app-shell-patterns/SKILL.md +108 -24
- package/skills/app-shell-patterns/references/api/create-ai-gateway-client.md +115 -0
- package/skills/app-shell-patterns/references/api/create-typed-paths.md +117 -0
- package/skills/app-shell-patterns/references/api/define-i18n-labels.md +143 -0
- package/skills/app-shell-patterns/references/api/define-module.md +247 -0
- package/skills/app-shell-patterns/references/api/define-resource.md +307 -0
- package/skills/app-shell-patterns/references/api/guards/hidden.md +202 -0
- package/skills/app-shell-patterns/references/api/guards/overview.md +354 -0
- package/skills/app-shell-patterns/references/api/guards/pass.md +129 -0
- package/skills/app-shell-patterns/references/api/guards/redirect-to.md +307 -0
- package/skills/app-shell-patterns/references/api/router/use-location.md +240 -0
- package/skills/app-shell-patterns/references/api/router/use-navigate.md +138 -0
- package/skills/app-shell-patterns/references/api/router/use-params.md +218 -0
- package/skills/app-shell-patterns/references/api/use-ai-chat.md +110 -0
- package/skills/app-shell-patterns/references/api/use-app-shell-config.md +126 -0
- package/skills/app-shell-patterns/references/api/use-app-shell-data.md +146 -0
- package/skills/app-shell-patterns/references/api/use-app-shell.md +143 -0
- package/skills/app-shell-patterns/references/api/use-auth.md +145 -0
- package/skills/app-shell-patterns/references/api/use-override-breadcrumb.md +74 -0
- package/skills/app-shell-patterns/references/api/use-page-meta.md +96 -0
- package/skills/app-shell-patterns/references/api/use-register-command-palette-actions.md +90 -0
- package/skills/app-shell-patterns/references/api/use-resolved-locale.md +67 -0
- package/skills/app-shell-patterns/references/api/use-route-error.md +214 -0
- package/skills/app-shell-patterns/references/api/use-theme.md +108 -0
- package/skills/app-shell-patterns/references/api/use-time-zone.md +80 -0
- package/skills/app-shell-patterns/references/api/use-toast.md +107 -0
- package/skills/app-shell-patterns/references/components/action-panel.md +168 -0
- package/skills/app-shell-patterns/references/components/activity-card.md +142 -0
- package/skills/app-shell-patterns/references/components/ai-chat.md +315 -0
- package/skills/app-shell-patterns/references/components/alert.md +202 -0
- package/skills/app-shell-patterns/references/components/app-shell.md +530 -0
- package/skills/app-shell-patterns/references/components/appearance-switcher.md +87 -0
- package/skills/app-shell-patterns/references/components/attachment.md +138 -0
- package/skills/app-shell-patterns/references/components/autocomplete.md +278 -0
- package/skills/app-shell-patterns/references/components/avatar.md +80 -0
- package/skills/app-shell-patterns/references/components/badge.md +309 -0
- package/skills/app-shell-patterns/references/components/button.md +164 -0
- package/skills/app-shell-patterns/references/components/card.md +109 -0
- package/skills/app-shell-patterns/references/components/checkbox.md +169 -0
- package/skills/app-shell-patterns/references/components/combobox.md +318 -0
- package/skills/app-shell-patterns/references/components/command-palette.md +413 -0
- package/skills/app-shell-patterns/references/components/csv-importer.md +214 -0
- package/skills/app-shell-patterns/references/components/data-table.md +953 -0
- package/skills/app-shell-patterns/references/components/date-picker.md +248 -0
- package/skills/app-shell-patterns/references/components/default-header.md +75 -0
- package/skills/app-shell-patterns/references/components/default-sidebar.md +136 -0
- package/skills/app-shell-patterns/references/components/description-card.md +569 -0
- package/skills/app-shell-patterns/references/components/dialog.md +164 -0
- package/skills/app-shell-patterns/references/components/document-progress-card.md +154 -0
- package/skills/app-shell-patterns/references/components/form.md +378 -0
- package/skills/app-shell-patterns/references/components/global-header-layout.md +132 -0
- package/skills/app-shell-patterns/references/components/grid.md +184 -0
- package/skills/app-shell-patterns/references/components/input.md +83 -0
- package/skills/app-shell-patterns/references/components/layout.md +539 -0
- package/skills/app-shell-patterns/references/components/menu.md +218 -0
- package/skills/app-shell-patterns/references/components/metric-card.md +94 -0
- package/skills/app-shell-patterns/references/components/select.md +283 -0
- package/skills/app-shell-patterns/references/components/sheet.md +237 -0
- package/skills/app-shell-patterns/references/components/sidebar-group.md +466 -0
- package/skills/app-shell-patterns/references/components/sidebar-item.md +458 -0
- package/skills/app-shell-patterns/references/components/sidebar-layout.md +258 -0
- package/skills/app-shell-patterns/references/components/spinner.md +81 -0
- package/skills/app-shell-patterns/references/components/table.md +188 -0
- package/skills/app-shell-patterns/references/components/tabs.md +158 -0
- package/skills/app-shell-patterns/references/components/textarea.md +122 -0
- package/skills/app-shell-patterns/references/components/timeline.md +317 -0
- package/skills/app-shell-patterns/references/components/toolbar.md +138 -0
- package/skills/app-shell-patterns/references/components/tooltip.md +144 -0
- package/skills/app-shell-patterns/references/components/with-guard.md +445 -0
- package/skills/app-shell-patterns/references/concepts/authentication.md +301 -0
- package/skills/app-shell-patterns/references/concepts/custom-components.md +59 -0
- package/skills/app-shell-patterns/references/concepts/file-based-routing.md +350 -0
- package/skills/app-shell-patterns/references/{fundamental → concepts}/graphql.md +11 -4
- package/skills/app-shell-patterns/references/concepts/modules-and-resources.md +229 -0
- package/skills/app-shell-patterns/references/concepts/routing-navigation.md +387 -0
- package/skills/app-shell-patterns/references/concepts/sidebar-navigation.md +388 -0
- package/skills/app-shell-patterns/references/{fundamental/design-system.md → concepts/styling-theming.md} +260 -105
- package/skills/app-shell-patterns/references/migrations.md +34 -0
- package/skills/app-shell-patterns/references/pages/document-detail.md +1125 -0
- package/skills/app-shell-patterns/references/patterns/form-composer.md +44 -46
- package/skills/app-shell-patterns/references/patterns/form-modal.md +39 -100
- package/skills/app-shell-patterns/references/patterns/form-sectioned.md +12 -38
- package/skills/app-shell-patterns/references/patterns/form-single-page.md +11 -32
- package/skills/app-shell-patterns/references/patterns/form-wizard.md +12 -53
- package/skills/app-shell-patterns/references/patterns/interaction-confirm.md +11 -27
- package/skills/app-shell-patterns/references/patterns/interaction-multi-select.md +14 -28
- package/skills/app-shell-patterns/references/patterns/interaction-toast.md +10 -34
- package/skills/app-shell-patterns/references/patterns/list-dense-scan.md +11 -86
- package/skills/app-shell-patterns/references/fundamental/components.md +0 -981
- package/skills/app-shell-patterns/references/patterns/detail-hero-with-actions.md +0 -201
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Checkbox
|
|
3
|
+
description: Styled checkbox for boolean form fields, with indeterminate state and automatic Field / React Hook Form integration
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
<!-- GENERATED by docs-kit from docs-src/components/checkbox.docs.outline.md — do not edit by hand. -->
|
|
7
|
+
|
|
8
|
+
# Checkbox
|
|
9
|
+
|
|
10
|
+
The `Checkbox` component is a styled control for boolean fields. It wraps Base UI's checkbox, so it works with [`Field`](./form.md) and React Hook Form the same way `Input`, `Select`, and `Combobox` do — dropped inside a `Field.Root` it inherits label association, `aria-describedby`, `disabled`, and the invalid (error) state automatically.
|
|
11
|
+
|
|
12
|
+
## Import
|
|
13
|
+
|
|
14
|
+
```tsx
|
|
15
|
+
import { Checkbox } from "@tailor-platform/app-shell";
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
## Basic Usage
|
|
19
|
+
|
|
20
|
+
<example-preview name="basic-usage"></example-preview>
|
|
21
|
+
|
|
22
|
+
```tsx
|
|
23
|
+
function BasicUsage() {
|
|
24
|
+
return <Checkbox label="Subscribe to updates" />;
|
|
25
|
+
}
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
Omit `label` to render just the box — useful for a table row-select cell (pass `aria-label`) or when a `Field.Label` sibling supplies the text.
|
|
29
|
+
|
|
30
|
+
```tsx
|
|
31
|
+
<Checkbox aria-label="Select row" />
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
## Props
|
|
35
|
+
|
|
36
|
+
Accepts all Base UI `Checkbox.Root` props, plus `label`. The most common:
|
|
37
|
+
|
|
38
|
+
| Prop | Type | Default | Description |
|
|
39
|
+
| ----------------- | ------------------------------------- | ------- | ------------------------------------------------------------------------------------- |
|
|
40
|
+
| `label` | `React.ReactNode` | - | Inline label rendered next to the box (wraps both in a `<label>`). Omit for box-only. |
|
|
41
|
+
| `checked` | `boolean` | - | Controlled checked state. |
|
|
42
|
+
| `defaultChecked` | `boolean` | `false` | Initial checked state when uncontrolled. |
|
|
43
|
+
| `onCheckedChange` | `(checked: boolean, details) => void` | - | Called with the next boolean state. |
|
|
44
|
+
| `indeterminate` | `boolean` | `false` | Renders the "partially selected" (mixed) state. |
|
|
45
|
+
| `disabled` | `boolean` | `false` | Disables the control. |
|
|
46
|
+
| `required` | `boolean` | `false` | Marks the control as required for native/Field validation. |
|
|
47
|
+
| `name` | `string` | - | Field name for native form submission. |
|
|
48
|
+
| `value` | `string` | - | Submitted value when checked (native forms). |
|
|
49
|
+
| `inputRef` | `React.Ref<HTMLInputElement>` | - | Ref to the hidden native input (use for React Hook Form's `field.ref`). |
|
|
50
|
+
| `className` | `string` | - | Applied to the outer element — the `<label>` when `label` is set, else the box. |
|
|
51
|
+
|
|
52
|
+
## Examples
|
|
53
|
+
|
|
54
|
+
### Controlled
|
|
55
|
+
|
|
56
|
+
```tsx
|
|
57
|
+
const [checked, setChecked] = useState(false);
|
|
58
|
+
|
|
59
|
+
<Checkbox label="Enable notifications" checked={checked} onCheckedChange={setChecked} />;
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
### Indeterminate ("select all")
|
|
63
|
+
|
|
64
|
+
```tsx
|
|
65
|
+
<Checkbox
|
|
66
|
+
aria-label="Select all rows"
|
|
67
|
+
checked={allSelected}
|
|
68
|
+
indeterminate={someSelected && !allSelected}
|
|
69
|
+
onCheckedChange={(checked) => (checked ? selectAll() : clearAll())}
|
|
70
|
+
/>
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
### Disabled
|
|
74
|
+
|
|
75
|
+
```tsx
|
|
76
|
+
<Checkbox label="Unavailable option" disabled />
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
## Use With Field
|
|
80
|
+
|
|
81
|
+
Placed inside a `Field.Root`, `Checkbox` picks up the field's invalid and disabled state automatically — no `error` prop of its own. Use the inline `label` for the checkbox text and `Field.Error` for the message below it.
|
|
82
|
+
|
|
83
|
+
```tsx
|
|
84
|
+
<Field.Root name="acceptTerms" error={{ message: "You must accept the terms." }}>
|
|
85
|
+
<Checkbox label="I accept the terms and conditions" />
|
|
86
|
+
<Field.Error>You must accept the terms.</Field.Error>
|
|
87
|
+
</Field.Root>
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
## React Hook Form
|
|
91
|
+
|
|
92
|
+
Drive the checkbox with a `Controller`. `onCheckedChange` hands React Hook Form a boolean directly, and spreading `fieldState` onto `Field.Root` wires the error/invalid state.
|
|
93
|
+
|
|
94
|
+
```tsx
|
|
95
|
+
import { useForm, Controller } from "react-hook-form";
|
|
96
|
+
|
|
97
|
+
function TermsForm() {
|
|
98
|
+
const { control, handleSubmit } = useForm({ defaultValues: { acceptTerms: false } });
|
|
99
|
+
|
|
100
|
+
return (
|
|
101
|
+
<Form onSubmit={handleSubmit((data) => save(data))}>
|
|
102
|
+
<Controller
|
|
103
|
+
name="acceptTerms"
|
|
104
|
+
control={control}
|
|
105
|
+
rules={{ required: "You must accept the terms." }}
|
|
106
|
+
render={({ field, fieldState }) => (
|
|
107
|
+
<Field.Root name={field.name} {...fieldState}>
|
|
108
|
+
<Checkbox
|
|
109
|
+
label="I accept the terms and conditions"
|
|
110
|
+
checked={field.value}
|
|
111
|
+
onCheckedChange={field.onChange}
|
|
112
|
+
onBlur={field.onBlur}
|
|
113
|
+
inputRef={field.ref}
|
|
114
|
+
/>
|
|
115
|
+
<Field.Error>{fieldState.error?.message}</Field.Error>
|
|
116
|
+
</Field.Root>
|
|
117
|
+
)}
|
|
118
|
+
/>
|
|
119
|
+
<button type="submit">Save</button>
|
|
120
|
+
</Form>
|
|
121
|
+
);
|
|
122
|
+
}
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
## With a shadcn-style `FormField`
|
|
126
|
+
|
|
127
|
+
`Checkbox` also drops into the common shadcn `FormField` / `FormControl` / `FormMessage`
|
|
128
|
+
pattern — no AppShell `Field` needed. `FormControl` injects `id`, `aria-describedby`, and
|
|
129
|
+
`aria-invalid` onto the control; `Checkbox` forwards them (and `ref`, in React 19) to the
|
|
130
|
+
underlying element and styles its error state off `aria-invalid`, so the invalid highlight
|
|
131
|
+
works here too.
|
|
132
|
+
|
|
133
|
+
```tsx
|
|
134
|
+
<FormField
|
|
135
|
+
control={control}
|
|
136
|
+
name="acceptTerms"
|
|
137
|
+
render={({ field }) => (
|
|
138
|
+
<FormItem>
|
|
139
|
+
<FormControl>
|
|
140
|
+
<Checkbox
|
|
141
|
+
label="I accept the terms and conditions"
|
|
142
|
+
checked={field.value}
|
|
143
|
+
onCheckedChange={field.onChange}
|
|
144
|
+
onBlur={field.onBlur}
|
|
145
|
+
inputRef={field.ref}
|
|
146
|
+
/>
|
|
147
|
+
</FormControl>
|
|
148
|
+
<FormMessage />
|
|
149
|
+
</FormItem>
|
|
150
|
+
)}
|
|
151
|
+
/>
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
> Use the inline `label` prop for the checkbox text (it wraps the box in a `<label>`, so
|
|
155
|
+
> clicking the text toggles it). A separate `FormLabel` with `htmlFor` is descriptive only —
|
|
156
|
+
> the control renders a `<span role="checkbox">`, which isn't a natively labelable element,
|
|
157
|
+
> so a sibling label won't toggle it on click.
|
|
158
|
+
|
|
159
|
+
## TypeScript
|
|
160
|
+
|
|
161
|
+
```typescript
|
|
162
|
+
import { type CheckboxProps } from "@tailor-platform/app-shell";
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
## Related Components
|
|
166
|
+
|
|
167
|
+
- [Form, Field, and Fieldset](./form.md) — Checkbox integrates with `Field` for labels, errors, and validation.
|
|
168
|
+
- [Input](./input.md) — Text input counterpart.
|
|
169
|
+
- [Textarea](./textarea.md) — Multi-line text counterpart.
|
|
@@ -0,0 +1,318 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Combobox
|
|
3
|
+
description: Searchable combobox with single/multi selection, built-in filtering, async data fetching, and user-creatable items
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
<!-- GENERATED by docs-kit from docs-src/components/combobox.docs.outline.md — do not edit by hand. -->
|
|
7
|
+
|
|
8
|
+
# Combobox
|
|
9
|
+
|
|
10
|
+
The `Combobox` component provides a searchable combobox with built-in filtering. Pass `items` and get a ready-to-use combobox out of the box. For async data fetching use `Combobox.Async`. For user-created items add an `onCreateItem` prop. For custom compositions use `Combobox.Parts`.
|
|
11
|
+
|
|
12
|
+
## Import
|
|
13
|
+
|
|
14
|
+
```tsx
|
|
15
|
+
import { Combobox } from "@tailor-platform/app-shell";
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
## Basic Usage
|
|
19
|
+
|
|
20
|
+
<example-preview name="basic-usage"></example-preview>
|
|
21
|
+
|
|
22
|
+
```tsx
|
|
23
|
+
function BasicUsage() {
|
|
24
|
+
return (
|
|
25
|
+
<Combobox
|
|
26
|
+
items={["Apple", "Banana", "Cherry"]}
|
|
27
|
+
placeholder="Search fruits..."
|
|
28
|
+
onValueChange={(value) => console.log(value)}
|
|
29
|
+
/>
|
|
30
|
+
);
|
|
31
|
+
}
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
## Props
|
|
35
|
+
|
|
36
|
+
### Combobox Props
|
|
37
|
+
|
|
38
|
+
| Prop | Type | Default | Description |
|
|
39
|
+
| ------------------- | -------------------------------------------------------------------------- | ---------------------------- | -------------------------------------------------------------------------- |
|
|
40
|
+
| `items` | `I[]` | - | Items to display. May be a flat array or an array of `ItemGroup<T>` |
|
|
41
|
+
| `placeholder` | `string` | - | Placeholder text for the input |
|
|
42
|
+
| `emptyText` | `string` | `"No results."` | Text shown when no items match |
|
|
43
|
+
| `multiple` | `true \| false \| undefined` | `false` | Enables multi-select mode |
|
|
44
|
+
| `value` | `T \| null` (single) or `T[]` (multiple) | - | Controlled value |
|
|
45
|
+
| `defaultValue` | `T \| null` (single) or `T[]` (multiple) | - | Initial value (uncontrolled) |
|
|
46
|
+
| `onValueChange` | `(value: T \| null) => void` (single) or `(value: T[]) => void` (multiple) | - | Called when the selected value changes |
|
|
47
|
+
| `mapItem` | `(item: T) => MappedItem` | - | Map each item to its label, key, and optional custom render |
|
|
48
|
+
| `className` | `string` | - | Additional CSS classes for the root container |
|
|
49
|
+
| `disabled` | `boolean` | `false` | Disables the combobox |
|
|
50
|
+
| `aria-label` | `string` | - | Accessible name for the input. Use when there is no visible label |
|
|
51
|
+
| `aria-labelledby` | `string` | - | ID of the element(s) that label the input |
|
|
52
|
+
| `id` | `string` | - | ID applied to the combobox input element |
|
|
53
|
+
| `onCreateItem` | `(value: string) => T \| false \| Promise<T \| false>` | - | Enable user-created items (requires `mapItem`; `T` must be an object type) |
|
|
54
|
+
| `formatCreateLabel` | `(value: string) => string` | `` (v) => `Create "${v}"` `` | Format the label for the "create" option |
|
|
55
|
+
|
|
56
|
+
### MappedItem
|
|
57
|
+
|
|
58
|
+
```ts
|
|
59
|
+
interface MappedItem {
|
|
60
|
+
label: string; // Display text, used for filtering and a11y
|
|
61
|
+
key?: string; // React key. Defaults to label
|
|
62
|
+
render?: React.ReactNode; // Custom JSX to render in the dropdown
|
|
63
|
+
}
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
### ItemGroup
|
|
67
|
+
|
|
68
|
+
```ts
|
|
69
|
+
interface ItemGroup<T> {
|
|
70
|
+
label: string;
|
|
71
|
+
items: T[];
|
|
72
|
+
}
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
## Grouped Items
|
|
76
|
+
|
|
77
|
+
```tsx
|
|
78
|
+
const fruits = [
|
|
79
|
+
{ label: "Citrus", items: ["Orange", "Lemon", "Lime"] },
|
|
80
|
+
{ label: "Berries", items: ["Strawberry", "Blueberry"] },
|
|
81
|
+
];
|
|
82
|
+
|
|
83
|
+
<Combobox items={fruits} placeholder="Search fruits..." />;
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
## Multi-select
|
|
87
|
+
|
|
88
|
+
In multi-select mode, selected items are displayed as chips inside the input:
|
|
89
|
+
|
|
90
|
+
```tsx
|
|
91
|
+
<Combobox
|
|
92
|
+
items={["Red", "Green", "Blue"]}
|
|
93
|
+
multiple
|
|
94
|
+
placeholder="Pick colors"
|
|
95
|
+
onValueChange={(colors) => console.log(colors)}
|
|
96
|
+
/>
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
## Creatable Items
|
|
100
|
+
|
|
101
|
+
Add `onCreateItem` to let users create new items on-the-fly. `T` must be an object type:
|
|
102
|
+
|
|
103
|
+
```tsx
|
|
104
|
+
type Tag = { id: string; name: string };
|
|
105
|
+
|
|
106
|
+
const [tags, setTags] = useState<Tag[]>([
|
|
107
|
+
{ id: "1", name: "Bug" },
|
|
108
|
+
{ id: "2", name: "Feature" },
|
|
109
|
+
]);
|
|
110
|
+
|
|
111
|
+
<Combobox
|
|
112
|
+
items={tags}
|
|
113
|
+
mapItem={(tag) => ({ label: tag.name, key: tag.id })}
|
|
114
|
+
onCreateItem={(value) => {
|
|
115
|
+
const newTag: Tag = { id: crypto.randomUUID(), name: value };
|
|
116
|
+
setTags((prev) => [...prev, newTag]);
|
|
117
|
+
return newTag; // return the new item to add it to the selection
|
|
118
|
+
}}
|
|
119
|
+
placeholder="Search or create a tag..."
|
|
120
|
+
/>;
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
`onCreateItem` may return:
|
|
124
|
+
|
|
125
|
+
- `T` — accept the item and add it to the selection
|
|
126
|
+
- `false` — cancel the creation
|
|
127
|
+
- `Promise<T | false>` — for async workflows
|
|
128
|
+
|
|
129
|
+
## Async Loading
|
|
130
|
+
|
|
131
|
+
Use `Combobox.Async` to load items from an API. The fetcher is called on each keystroke (debounced). When the dropdown first opens or the input is cleared, the fetcher receives `null` as the query — return initial/default items for `null`, or return an empty array to show nothing until the user starts typing.
|
|
132
|
+
|
|
133
|
+
```tsx
|
|
134
|
+
import { type ComboboxAsyncFetcher } from "@tailor-platform/app-shell";
|
|
135
|
+
|
|
136
|
+
const fetcher: ComboboxAsyncFetcher<User> = async (query, { signal }) => {
|
|
137
|
+
const res = await fetch(`/api/users?q=${query ?? ""}`, { signal });
|
|
138
|
+
return res.json();
|
|
139
|
+
};
|
|
140
|
+
|
|
141
|
+
<Combobox.Async
|
|
142
|
+
fetcher={fetcher}
|
|
143
|
+
mapItem={(user) => ({ label: user.name, key: user.id })}
|
|
144
|
+
placeholder="Search users..."
|
|
145
|
+
onValueChange={(user) => console.log(user)}
|
|
146
|
+
/>;
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
`Combobox.Async` also supports `onCreateItem` for creatable async comboboxes.
|
|
150
|
+
|
|
151
|
+
### Combobox.Async Props
|
|
152
|
+
|
|
153
|
+
Accepts all the same props as `Combobox` except `items`, plus:
|
|
154
|
+
|
|
155
|
+
| Prop | Type | Default | Description |
|
|
156
|
+
| -------------- | -------------------------- | -------------------------- | ------------------------------------------------------------------ |
|
|
157
|
+
| `fetcher` | `ComboboxAsyncFetcher<T>` | - | Fetcher called on each keystroke (debounced by default) |
|
|
158
|
+
| `loadingText` | `string` | `"Loading..."` | Text shown while loading |
|
|
159
|
+
| `errorText` | `string` | `"Couldn't load results."` | Message shown in the popover when the fetcher fails (with Retry) |
|
|
160
|
+
| `retryText` | `string` | `"Retry"` | Label for the retry button in the error state |
|
|
161
|
+
| `onFetchError` | `(error: unknown) => void` | - | Called once per outage when a fetch fails (logging/error tracking) |
|
|
162
|
+
|
|
163
|
+
### ComboboxAsyncFetcher
|
|
164
|
+
|
|
165
|
+
```ts
|
|
166
|
+
type ComboboxAsyncFetcher<T> =
|
|
167
|
+
| ((query: string | null, options: { signal: AbortSignal }) => Promise<T[]>)
|
|
168
|
+
| {
|
|
169
|
+
fn: (query: string | null, options: { signal: AbortSignal }) => Promise<T[]>;
|
|
170
|
+
debounceMs: number;
|
|
171
|
+
};
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
`query` is `null` when the user has not typed anything (e.g. the dropdown was just opened or the input was cleared). Pass `{ fn, debounceMs }` to customize the debounce delay.
|
|
175
|
+
|
|
176
|
+
### Error handling
|
|
177
|
+
|
|
178
|
+
If the fetcher throws or rejects, `Combobox.Async` renders a built-in inline error state in the popover — the `errorText` message plus a **Retry** button that re-runs the last fetch — instead of the misleading "No results." empty state. Aborted/superseded requests (a keystroke replaced by a newer one) are ignored, and the component de-dupes per outage so typing during an outage doesn't stack repeated announcements.
|
|
179
|
+
|
|
180
|
+
To run a side effect on failure (log to error tracking, show a toast), pass `onFetchError` — it fires **once per outage** (on the transition into the error state) and re-arms after the next successful fetch. The inline error state is shown regardless.
|
|
181
|
+
|
|
182
|
+
```tsx
|
|
183
|
+
<Combobox.Async
|
|
184
|
+
fetcher={fetcher}
|
|
185
|
+
errorText="Couldn't load users."
|
|
186
|
+
retryText="Try again"
|
|
187
|
+
onFetchError={(error) => reportError(error)}
|
|
188
|
+
/>
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
## Low-level Primitives
|
|
192
|
+
|
|
193
|
+
`Combobox.Parts` exposes styled sub-components and hooks for fully custom compositions:
|
|
194
|
+
|
|
195
|
+
```tsx
|
|
196
|
+
const {
|
|
197
|
+
Root,
|
|
198
|
+
InputGroup,
|
|
199
|
+
Input,
|
|
200
|
+
Trigger,
|
|
201
|
+
Content,
|
|
202
|
+
List,
|
|
203
|
+
Item,
|
|
204
|
+
Empty,
|
|
205
|
+
Group,
|
|
206
|
+
GroupLabel,
|
|
207
|
+
Clear,
|
|
208
|
+
Chips,
|
|
209
|
+
Chip,
|
|
210
|
+
ChipRemove,
|
|
211
|
+
Value,
|
|
212
|
+
Collection,
|
|
213
|
+
Status,
|
|
214
|
+
} = Combobox.Parts;
|
|
215
|
+
```
|
|
216
|
+
|
|
217
|
+
## Form submission
|
|
218
|
+
|
|
219
|
+
Inside a `Field.Root`, the field's `name` identifies the value and these props are unnecessary —
|
|
220
|
+
see [Form](./form.md). They matter for **native** submission: a plain `<form>`, `new FormData(form)`,
|
|
221
|
+
or a server action.
|
|
222
|
+
|
|
223
|
+
| Prop | Type | Default | Description |
|
|
224
|
+
| ------------------- | ----------------------------- | ------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
225
|
+
| `name` | `string` | - | Names the hidden input for native submission. **Ignored inside a `Field.Root`** — the field's name wins. |
|
|
226
|
+
| `form` | `string` | - | `id` of the owning form, when the control is rendered outside it. |
|
|
227
|
+
| `required` | `boolean` | `false` | Blocks submission until a value is chosen, surfacing the matching `Field.Error`. |
|
|
228
|
+
| `inputRef` | `React.Ref<HTMLInputElement>` | - | Ref to the hidden input (use for React Hook Form's `field.ref`). |
|
|
229
|
+
| `itemToStringValue` | `(item: T) => string` | - | Serialises a non-string item for submission. Items shaped `{ value, label }` use `value` automatically. Not available on the creatable variant, which derives its own. |
|
|
230
|
+
|
|
231
|
+
`itemToStringValue` is not accepted on the creatable variant (`onCreateItem`): `Combobox` derives
|
|
232
|
+
its own there so the pending-item sentinel serialises correctly. A creatable combobox over object
|
|
233
|
+
items therefore cannot customise the submitted value — use the non-creatable variant, or map the
|
|
234
|
+
value in your submit handler.
|
|
235
|
+
|
|
236
|
+
## Examples
|
|
237
|
+
|
|
238
|
+
### Controlled Combobox
|
|
239
|
+
|
|
240
|
+
```tsx
|
|
241
|
+
const [selected, setSelected] = useState<User | null>(null);
|
|
242
|
+
|
|
243
|
+
<Combobox
|
|
244
|
+
items={users}
|
|
245
|
+
mapItem={(u) => ({ label: u.name, key: u.id })}
|
|
246
|
+
value={selected}
|
|
247
|
+
onValueChange={setSelected}
|
|
248
|
+
placeholder="Select a user"
|
|
249
|
+
/>;
|
|
250
|
+
```
|
|
251
|
+
|
|
252
|
+
### Async with Parts (custom composition)
|
|
253
|
+
|
|
254
|
+
Combine `Combobox.useAsync` with `Combobox.Parts` for full control over layout and rendering:
|
|
255
|
+
|
|
256
|
+
`Combobox.useAsync` accepts the same options as `Combobox.Async` (including `onFetchError`) and returns:
|
|
257
|
+
|
|
258
|
+
| Property | Type | Description |
|
|
259
|
+
| --------- | ------------ | ------------------------------------------------------- |
|
|
260
|
+
| `items` | `T[]` | Currently loaded items |
|
|
261
|
+
| `loading` | `boolean` | Whether a fetch is in progress |
|
|
262
|
+
| `error` | `unknown` | The error thrown by the last fetch, if any |
|
|
263
|
+
| `retry` | `() => void` | Re-runs the last fetch (use to build a custom retry UI) |
|
|
264
|
+
|
|
265
|
+
```tsx
|
|
266
|
+
type Country = { code: string; name: string };
|
|
267
|
+
|
|
268
|
+
const countries = Combobox.useAsync({
|
|
269
|
+
fetcher: async (query, { signal }) => {
|
|
270
|
+
const res = await fetch(`/api/countries?q=${query ?? ""}`, { signal });
|
|
271
|
+
if (!res.ok) return [];
|
|
272
|
+
return res.json() as Promise<Country[]>;
|
|
273
|
+
},
|
|
274
|
+
onFetchError: (error) => reportError(error),
|
|
275
|
+
});
|
|
276
|
+
|
|
277
|
+
<Combobox.Parts.Root {...countries} filter={null} itemToStringLabel={(c) => c.name}>
|
|
278
|
+
<Combobox.Parts.InputGroup>
|
|
279
|
+
<Combobox.Parts.Input placeholder="Search countries..." />
|
|
280
|
+
<Combobox.Parts.Clear />
|
|
281
|
+
<Combobox.Parts.Trigger />
|
|
282
|
+
</Combobox.Parts.InputGroup>
|
|
283
|
+
<Combobox.Parts.Content>
|
|
284
|
+
<Combobox.Parts.List>
|
|
285
|
+
{countries.items.map((c) => (
|
|
286
|
+
<Combobox.Parts.Item key={c.code} value={c}>
|
|
287
|
+
{c.name}
|
|
288
|
+
</Combobox.Parts.Item>
|
|
289
|
+
))}
|
|
290
|
+
<Combobox.Parts.Empty>
|
|
291
|
+
{countries.error ? (
|
|
292
|
+
<button onClick={countries.retry}>Couldn't load. Retry</button>
|
|
293
|
+
) : countries.loading ? (
|
|
294
|
+
"Loading..."
|
|
295
|
+
) : (
|
|
296
|
+
"No results."
|
|
297
|
+
)}
|
|
298
|
+
</Combobox.Parts.Empty>
|
|
299
|
+
</Combobox.Parts.List>
|
|
300
|
+
</Combobox.Parts.Content>
|
|
301
|
+
</Combobox.Parts.Root>;
|
|
302
|
+
```
|
|
303
|
+
|
|
304
|
+
## Accessibility
|
|
305
|
+
|
|
306
|
+
- Input is keyboard accessible with arrow key navigation
|
|
307
|
+
- Pressing `Escape` closes the dropdown
|
|
308
|
+
- Multi-select chips have `aria-label` set from the item label
|
|
309
|
+
- When used standalone (no visible `<label>`), give the input an accessible name with `aria-label` or `aria-labelledby` — otherwise screen readers announce only the current value:
|
|
310
|
+
|
|
311
|
+
```tsx
|
|
312
|
+
<Combobox items={items} aria-label="Fruit filter" />
|
|
313
|
+
```
|
|
314
|
+
|
|
315
|
+
## Related Components
|
|
316
|
+
|
|
317
|
+
- [Select](./select.md) - Non-searchable dropdown
|
|
318
|
+
- [Autocomplete](./autocomplete.md) - Free-text input with suggestions
|