@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.
Files changed (101) hide show
  1. package/LICENSE.md +21 -0
  2. package/dist/app-shell-core.css +1 -1
  3. package/dist/app-shell.d.ts +1203 -316
  4. package/dist/app-shell.js +4562 -3307
  5. package/dist/{appshell-CtS8Y3LW.js → appshell-B0wh5Lq0.js} +850 -549
  6. package/dist/style-entry.d.ts +118 -98
  7. package/dist/testing.d.ts +118 -98
  8. package/dist/testing.js +1 -1
  9. package/dist/themes/_template.css +1 -1
  10. package/dist/vite-plugin.d.ts +118 -98
  11. package/package.json +13 -13
  12. package/skills/app-shell-patterns/SKILL.md +108 -24
  13. package/skills/app-shell-patterns/references/api/create-ai-gateway-client.md +115 -0
  14. package/skills/app-shell-patterns/references/api/create-typed-paths.md +117 -0
  15. package/skills/app-shell-patterns/references/api/define-i18n-labels.md +143 -0
  16. package/skills/app-shell-patterns/references/api/define-module.md +247 -0
  17. package/skills/app-shell-patterns/references/api/define-resource.md +307 -0
  18. package/skills/app-shell-patterns/references/api/guards/hidden.md +202 -0
  19. package/skills/app-shell-patterns/references/api/guards/overview.md +354 -0
  20. package/skills/app-shell-patterns/references/api/guards/pass.md +129 -0
  21. package/skills/app-shell-patterns/references/api/guards/redirect-to.md +307 -0
  22. package/skills/app-shell-patterns/references/api/router/use-location.md +240 -0
  23. package/skills/app-shell-patterns/references/api/router/use-navigate.md +138 -0
  24. package/skills/app-shell-patterns/references/api/router/use-params.md +218 -0
  25. package/skills/app-shell-patterns/references/api/use-ai-chat.md +110 -0
  26. package/skills/app-shell-patterns/references/api/use-app-shell-config.md +126 -0
  27. package/skills/app-shell-patterns/references/api/use-app-shell-data.md +146 -0
  28. package/skills/app-shell-patterns/references/api/use-app-shell.md +143 -0
  29. package/skills/app-shell-patterns/references/api/use-auth.md +145 -0
  30. package/skills/app-shell-patterns/references/api/use-override-breadcrumb.md +74 -0
  31. package/skills/app-shell-patterns/references/api/use-page-meta.md +96 -0
  32. package/skills/app-shell-patterns/references/api/use-register-command-palette-actions.md +90 -0
  33. package/skills/app-shell-patterns/references/api/use-resolved-locale.md +67 -0
  34. package/skills/app-shell-patterns/references/api/use-route-error.md +214 -0
  35. package/skills/app-shell-patterns/references/api/use-theme.md +108 -0
  36. package/skills/app-shell-patterns/references/api/use-time-zone.md +80 -0
  37. package/skills/app-shell-patterns/references/api/use-toast.md +107 -0
  38. package/skills/app-shell-patterns/references/components/action-panel.md +168 -0
  39. package/skills/app-shell-patterns/references/components/activity-card.md +142 -0
  40. package/skills/app-shell-patterns/references/components/ai-chat.md +315 -0
  41. package/skills/app-shell-patterns/references/components/alert.md +202 -0
  42. package/skills/app-shell-patterns/references/components/app-shell.md +530 -0
  43. package/skills/app-shell-patterns/references/components/appearance-switcher.md +87 -0
  44. package/skills/app-shell-patterns/references/components/attachment.md +138 -0
  45. package/skills/app-shell-patterns/references/components/autocomplete.md +278 -0
  46. package/skills/app-shell-patterns/references/components/avatar.md +80 -0
  47. package/skills/app-shell-patterns/references/components/badge.md +309 -0
  48. package/skills/app-shell-patterns/references/components/button.md +164 -0
  49. package/skills/app-shell-patterns/references/components/card.md +109 -0
  50. package/skills/app-shell-patterns/references/components/checkbox.md +169 -0
  51. package/skills/app-shell-patterns/references/components/combobox.md +318 -0
  52. package/skills/app-shell-patterns/references/components/command-palette.md +413 -0
  53. package/skills/app-shell-patterns/references/components/csv-importer.md +214 -0
  54. package/skills/app-shell-patterns/references/components/data-table.md +953 -0
  55. package/skills/app-shell-patterns/references/components/date-picker.md +248 -0
  56. package/skills/app-shell-patterns/references/components/default-header.md +75 -0
  57. package/skills/app-shell-patterns/references/components/default-sidebar.md +136 -0
  58. package/skills/app-shell-patterns/references/components/description-card.md +569 -0
  59. package/skills/app-shell-patterns/references/components/dialog.md +164 -0
  60. package/skills/app-shell-patterns/references/components/document-progress-card.md +154 -0
  61. package/skills/app-shell-patterns/references/components/form.md +378 -0
  62. package/skills/app-shell-patterns/references/components/global-header-layout.md +132 -0
  63. package/skills/app-shell-patterns/references/components/grid.md +184 -0
  64. package/skills/app-shell-patterns/references/components/input.md +83 -0
  65. package/skills/app-shell-patterns/references/components/layout.md +539 -0
  66. package/skills/app-shell-patterns/references/components/menu.md +218 -0
  67. package/skills/app-shell-patterns/references/components/metric-card.md +94 -0
  68. package/skills/app-shell-patterns/references/components/select.md +283 -0
  69. package/skills/app-shell-patterns/references/components/sheet.md +237 -0
  70. package/skills/app-shell-patterns/references/components/sidebar-group.md +466 -0
  71. package/skills/app-shell-patterns/references/components/sidebar-item.md +458 -0
  72. package/skills/app-shell-patterns/references/components/sidebar-layout.md +258 -0
  73. package/skills/app-shell-patterns/references/components/spinner.md +81 -0
  74. package/skills/app-shell-patterns/references/components/table.md +188 -0
  75. package/skills/app-shell-patterns/references/components/tabs.md +158 -0
  76. package/skills/app-shell-patterns/references/components/textarea.md +122 -0
  77. package/skills/app-shell-patterns/references/components/timeline.md +317 -0
  78. package/skills/app-shell-patterns/references/components/toolbar.md +138 -0
  79. package/skills/app-shell-patterns/references/components/tooltip.md +144 -0
  80. package/skills/app-shell-patterns/references/components/with-guard.md +445 -0
  81. package/skills/app-shell-patterns/references/concepts/authentication.md +301 -0
  82. package/skills/app-shell-patterns/references/concepts/custom-components.md +59 -0
  83. package/skills/app-shell-patterns/references/concepts/file-based-routing.md +350 -0
  84. package/skills/app-shell-patterns/references/{fundamental → concepts}/graphql.md +11 -4
  85. package/skills/app-shell-patterns/references/concepts/modules-and-resources.md +229 -0
  86. package/skills/app-shell-patterns/references/concepts/routing-navigation.md +387 -0
  87. package/skills/app-shell-patterns/references/concepts/sidebar-navigation.md +388 -0
  88. package/skills/app-shell-patterns/references/{fundamental/design-system.md → concepts/styling-theming.md} +260 -105
  89. package/skills/app-shell-patterns/references/migrations.md +34 -0
  90. package/skills/app-shell-patterns/references/pages/document-detail.md +1125 -0
  91. package/skills/app-shell-patterns/references/patterns/form-composer.md +44 -46
  92. package/skills/app-shell-patterns/references/patterns/form-modal.md +39 -100
  93. package/skills/app-shell-patterns/references/patterns/form-sectioned.md +12 -38
  94. package/skills/app-shell-patterns/references/patterns/form-single-page.md +11 -32
  95. package/skills/app-shell-patterns/references/patterns/form-wizard.md +12 -53
  96. package/skills/app-shell-patterns/references/patterns/interaction-confirm.md +11 -27
  97. package/skills/app-shell-patterns/references/patterns/interaction-multi-select.md +14 -28
  98. package/skills/app-shell-patterns/references/patterns/interaction-toast.md +10 -34
  99. package/skills/app-shell-patterns/references/patterns/list-dense-scan.md +11 -86
  100. package/skills/app-shell-patterns/references/fundamental/components.md +0 -981
  101. package/skills/app-shell-patterns/references/patterns/detail-hero-with-actions.md +0 -201
@@ -0,0 +1,218 @@
1
+ ---
2
+ title: Menu
3
+ description: Dropdown menu with a compound component API, supporting checkbox/radio items, groups, separators, and nested sub-menus
4
+ ---
5
+
6
+ <!-- GENERATED by docs-kit from docs-src/components/menu.docs.outline.md — do not edit by hand. -->
7
+
8
+ # Menu
9
+
10
+ The `Menu` component provides a dropdown menu with a compound component API. It is backed by Base UI's Menu primitive.
11
+
12
+ ## Import
13
+
14
+ ```tsx
15
+ import { Menu } 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
+ <Menu.Root>
26
+ <Menu.Trigger>Open menu</Menu.Trigger>
27
+ <Menu.Content>
28
+ <Menu.Item>Edit</Menu.Item>
29
+ <Menu.Item>Duplicate</Menu.Item>
30
+ <Menu.Separator />
31
+ <Menu.Item>Delete</Menu.Item>
32
+ </Menu.Content>
33
+ </Menu.Root>
34
+ );
35
+ }
36
+ ```
37
+
38
+ ## Sub-components
39
+
40
+ | Sub-component | Description |
41
+ | ---------------------------- | --------------------------------------------------------- |
42
+ | `Menu.Root` | Manages menu open/close state |
43
+ | `Menu.Trigger` | Element that opens the menu when clicked |
44
+ | `Menu.Content` | The menu popup containing menu items |
45
+ | `Menu.Item` | An individual interactive item |
46
+ | `Menu.LinkItem` | A link item for navigation |
47
+ | `Menu.CheckboxItem` | A menu item that toggles a setting on or off |
48
+ | `Menu.CheckboxItemIndicator` | Indicates whether the checkbox item is ticked |
49
+ | `Menu.RadioItem` | A menu item that works like a radio button within a group |
50
+ | `Menu.RadioItemIndicator` | Indicates whether the radio item is selected |
51
+ | `Menu.RadioGroup` | Groups related radio items |
52
+ | `Menu.Group` | Groups related menu items |
53
+ | `Menu.GroupLabel` | An accessible label for a `Menu.Group` |
54
+ | `Menu.Separator` | A visual separator between menu items |
55
+ | `Menu.SubmenuRoot` | Groups all parts of a nested sub-menu |
56
+ | `Menu.SubmenuTrigger` | A menu item that opens a sub-menu |
57
+
58
+ ## Props
59
+
60
+ ### Menu.Root Props
61
+
62
+ | Prop | Type | Default | Description |
63
+ | -------------- | ------------------------- | ------- | --------------------------------- |
64
+ | `open` | `boolean` | - | Controlled open state |
65
+ | `defaultOpen` | `boolean` | `false` | Initial open state (uncontrolled) |
66
+ | `onOpenChange` | `(open: boolean) => void` | - | Callback when open state changes |
67
+ | `modal` | `boolean` | `true` | Whether the menu is modal |
68
+ | `disabled` | `boolean` | `false` | Disables the entire menu |
69
+ | `children` | `React.ReactNode` | - | Menu sub-components |
70
+
71
+ ### Menu.Content Props
72
+
73
+ | Prop | Type | Default | Description |
74
+ | ----------- | -------------------------------------------------------------------------------------------------------------- | --------------------------------------------------- | ----------------------------------- |
75
+ | `position` | `{ side?: "top" \| "right" \| "bottom" \| "left"; align?: "start" \| "center" \| "end"; sideOffset?: number }` | `{ side: "bottom", align: "start", sideOffset: 4 }` | Placement of the popup |
76
+ | `className` | `string` | - | Additional CSS classes |
77
+ | `children` | `React.ReactNode` | - | Menu items and other sub-components |
78
+
79
+ ### Menu.Item Props
80
+
81
+ Accepts `className`, `disabled`, and all standard Base UI `Menu.Item` props.
82
+
83
+ ### Menu.SubmenuRoot Props
84
+
85
+ | Prop | Type | Default | Description |
86
+ | -------------- | ------------------------- | ------- | --------------------------------- |
87
+ | `open` | `boolean` | - | Controlled open state |
88
+ | `defaultOpen` | `boolean` | `false` | Initial open state (uncontrolled) |
89
+ | `onOpenChange` | `(open: boolean) => void` | - | Callback when open state changes |
90
+ | `disabled` | `boolean` | `false` | Disables the entire sub-menu |
91
+ | `children` | `React.ReactNode` | - | Sub-menu sub-components |
92
+
93
+ ## Grouped Items
94
+
95
+ Use `Menu.Group` and `Menu.GroupLabel` to visually group related items:
96
+
97
+ ```tsx
98
+ <Menu.Root>
99
+ <Menu.Trigger>Actions</Menu.Trigger>
100
+ <Menu.Content>
101
+ <Menu.Group>
102
+ <Menu.GroupLabel>File</Menu.GroupLabel>
103
+ <Menu.Item>New</Menu.Item>
104
+ <Menu.Item>Open</Menu.Item>
105
+ </Menu.Group>
106
+ <Menu.Separator />
107
+ <Menu.Group>
108
+ <Menu.GroupLabel>Edit</Menu.GroupLabel>
109
+ <Menu.Item>Cut</Menu.Item>
110
+ <Menu.Item>Copy</Menu.Item>
111
+ <Menu.Item>Paste</Menu.Item>
112
+ </Menu.Group>
113
+ </Menu.Content>
114
+ </Menu.Root>
115
+ ```
116
+
117
+ ## Checkbox Items
118
+
119
+ ```tsx
120
+ const [bold, setBold] = useState(false);
121
+ const [italic, setItalic] = useState(false);
122
+
123
+ <Menu.Root>
124
+ <Menu.Trigger>Format</Menu.Trigger>
125
+ <Menu.Content>
126
+ <Menu.CheckboxItem checked={bold} onCheckedChange={setBold}>
127
+ <Menu.CheckboxItemIndicator>✓</Menu.CheckboxItemIndicator>
128
+ Bold
129
+ </Menu.CheckboxItem>
130
+ <Menu.CheckboxItem checked={italic} onCheckedChange={setItalic}>
131
+ <Menu.CheckboxItemIndicator>✓</Menu.CheckboxItemIndicator>
132
+ Italic
133
+ </Menu.CheckboxItem>
134
+ </Menu.Content>
135
+ </Menu.Root>;
136
+ ```
137
+
138
+ ## Radio Items
139
+
140
+ ```tsx
141
+ const [align, setAlign] = useState("left");
142
+
143
+ <Menu.Root>
144
+ <Menu.Trigger>Align</Menu.Trigger>
145
+ <Menu.Content>
146
+ <Menu.RadioGroup value={align} onValueChange={setAlign}>
147
+ <Menu.RadioItem value="left">
148
+ <Menu.RadioItemIndicator>●</Menu.RadioItemIndicator>
149
+ Left
150
+ </Menu.RadioItem>
151
+ <Menu.RadioItem value="center">
152
+ <Menu.RadioItemIndicator>●</Menu.RadioItemIndicator>
153
+ Center
154
+ </Menu.RadioItem>
155
+ <Menu.RadioItem value="right">
156
+ <Menu.RadioItemIndicator>●</Menu.RadioItemIndicator>
157
+ Right
158
+ </Menu.RadioItem>
159
+ </Menu.RadioGroup>
160
+ </Menu.Content>
161
+ </Menu.Root>;
162
+ ```
163
+
164
+ ## Nested Sub-menus
165
+
166
+ ```tsx
167
+ <Menu.Root>
168
+ <Menu.Trigger>Edit</Menu.Trigger>
169
+ <Menu.Content>
170
+ <Menu.Item>Cut</Menu.Item>
171
+ <Menu.Item>Copy</Menu.Item>
172
+ <Menu.SubmenuRoot>
173
+ <Menu.SubmenuTrigger>Paste Special ›</Menu.SubmenuTrigger>
174
+ <Menu.Content>
175
+ <Menu.Item>Paste as Plain Text</Menu.Item>
176
+ <Menu.Item>Paste and Match Style</Menu.Item>
177
+ </Menu.Content>
178
+ </Menu.SubmenuRoot>
179
+ </Menu.Content>
180
+ </Menu.Root>
181
+ ```
182
+
183
+ ## Examples
184
+
185
+ ### Context Menu
186
+
187
+ ```tsx
188
+ function OrderActions({ order }: { order: Order }) {
189
+ return (
190
+ <Menu.Root>
191
+ <Menu.Trigger>
192
+ <Button variant="ghost" size="icon">
193
+ <EllipsisVerticalIcon />
194
+ </Button>
195
+ </Menu.Trigger>
196
+ <Menu.Content>
197
+ <Menu.Item onSelect={() => handleEdit(order)}>Edit</Menu.Item>
198
+ <Menu.Item onSelect={() => handleDuplicate(order)}>Duplicate</Menu.Item>
199
+ <Menu.Separator />
200
+ <Menu.Item onSelect={() => handleDelete(order)}>Delete</Menu.Item>
201
+ </Menu.Content>
202
+ </Menu.Root>
203
+ );
204
+ }
205
+ ```
206
+
207
+ ## Accessibility
208
+
209
+ - Menu items are keyboard navigable with arrow keys
210
+ - Pressing `Escape` closes the menu
211
+ - Sub-menus open on arrow-right and close on arrow-left
212
+ - `Menu.GroupLabel` is automatically associated with its parent group
213
+
214
+ ## Related Components
215
+
216
+ - [Dialog](./dialog.md) - For actions that require confirmation
217
+ - [Sheet](./sheet.md) - Slide-in panel for more complex workflows
218
+ - [Button](./button.md) - Common trigger element for menus
@@ -0,0 +1,94 @@
1
+ ---
2
+ title: MetricCard
3
+ description: Compact card for dashboard KPI summaries with title, value, optional trend and description
4
+ ---
5
+
6
+ <!-- GENERATED by docs-kit from docs-src/components/metric-card.docs.outline.md — do not edit by hand. -->
7
+
8
+ # MetricCard
9
+
10
+ `MetricCard` is a presentational card for displaying a single KPI (key performance indicator) on dashboards. It shows a small title, a prominent value, and optionally a trend indicator and supplementary description text. In v1 the component is static (no click handler or internal actions).
11
+
12
+ ## Import
13
+
14
+ ```tsx
15
+ import { MetricCard } 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
+ <MetricCard
26
+ title="Net total payment"
27
+ value="$1,500.00"
28
+ trend={{ direction: "up", value: "+5%" }}
29
+ description="vs last month"
30
+ />
31
+ );
32
+ }
33
+ ```
34
+
35
+ ## Props
36
+
37
+ _Generated from the type surface — first-party props only._
38
+
39
+ ### MetricCardProps
40
+
41
+ | Prop | Type | Default | Description |
42
+ | -------------- | ----------------- | ------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------- |
43
+ | `className?` | `string` | - | Additional CSS classes for the card container |
44
+ | `description?` | `string` | - | Optional supplementary description (e.g. "vs last period", "this week"). Empty strings are treated as absent and the description is not rendered in the meta row. |
45
+ | `icon?` | `ReactNode` | - | Optional leading icon |
46
+ | `title` | `string` | - | Metric title / header (e.g. "Net total payment") |
47
+ | `trend?` | `MetricCardTrend` | - | Optional trend indicator |
48
+ | `value` | `ReactNode` | - | Primary value (e.g. formatted currency or number) |
49
+
50
+ ## Exports
51
+
52
+ - `MetricCard` — function
53
+ - `MetricCardProps` — interface
54
+
55
+ ## Trend Directions
56
+
57
+ - **up** — Positive change (success styling, e.g. green).
58
+ - **down** — Negative change (destructive styling, e.g. red).
59
+ - **neutral** — No change or neutral (muted styling).
60
+
61
+ ```tsx
62
+ <MetricCard
63
+ title="Revenue"
64
+ value="$2,400"
65
+ trend={{ direction: "up", value: "+12%" }}
66
+ description="vs last month"
67
+ />
68
+
69
+ <MetricCard
70
+ title="Costs"
71
+ value="$800"
72
+ trend={{ direction: "down", value: "-5%" }}
73
+ description="vs last quarter"
74
+ />
75
+
76
+ <MetricCard
77
+ title="Balance"
78
+ value="$0"
79
+ trend={{ direction: "neutral", value: "0%" }}
80
+ />
81
+ ```
82
+
83
+ ## With Icon
84
+
85
+ ```tsx
86
+ <MetricCard title="Total orders" value="142" icon={<OrderIcon />} description="this week" />
87
+ ```
88
+
89
+ ## Related
90
+
91
+ - [Grid](./grid.md) — Arrange MetricCards in responsive columns (e.g. KPI grids).
92
+ - [Layout](./layout.md) — Page layout for placing MetricCards in a grid.
93
+ - [DescriptionCard](./description-card.md) — Structured key-value cards for detail views.
94
+ - [Badge](./badge.md) — Status badges that can complement metric displays.
@@ -0,0 +1,283 @@
1
+ ---
2
+ title: Select
3
+ description: Single or multi-select dropdown with optional async data fetching
4
+ ---
5
+
6
+ <!-- GENERATED by docs-kit from docs-src/components/select.docs.outline.md — do not edit by hand. -->
7
+
8
+ # Select
9
+
10
+ The `Select` component provides a fully assembled single or multi-select dropdown. Pass `items` and get a ready-to-use select out of the box. For async data fetching use `Select.Async`. For custom compositions use `Select.Parts`.
11
+
12
+ ## Import
13
+
14
+ ```tsx
15
+ import { Select } 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
+ <Select
26
+ items={["Apple", "Banana", "Cherry"]}
27
+ placeholder="Pick a fruit"
28
+ onValueChange={(value) => console.log(value)}
29
+ />
30
+ );
31
+ }
32
+ ```
33
+
34
+ ## Props
35
+
36
+ ### Select 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 shown when no value is selected |
42
+ | `multiple` | `true \| false \| undefined` | `false` | Enables multi-select mode |
43
+ | `value` | `T \| null` (single) or `T[]` (multiple) | - | Controlled value |
44
+ | `defaultValue` | `T \| null` (single) or `T[]` (multiple) | - | Initial value (uncontrolled) |
45
+ | `onValueChange` | `(value: T \| null) => void` (single) or `(value: T[]) => void` (multiple) | - | Called when the selected value changes |
46
+ | `renderValue` | `(value: T \| null \| T[]) => React.ReactNode` | - | Custom render function for the selected value display |
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 select |
50
+ | `aria-label` | `string` | - | Accessible name for the trigger. Use when there is no visible label |
51
+ | `aria-labelledby` | `string` | - | ID of the element(s) that label the trigger |
52
+ | `id` | `string` | - | ID applied to the combobox trigger element |
53
+
54
+ ### MappedItem
55
+
56
+ ```ts
57
+ interface MappedItem {
58
+ label: string; // Display text, used for filtering and a11y
59
+ key?: string; // React key. Defaults to label
60
+ render?: React.ReactNode; // Custom JSX to render in the dropdown
61
+ }
62
+ ```
63
+
64
+ ### ItemGroup
65
+
66
+ Pass grouped items by wrapping them in `ItemGroup<T>` objects:
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
+ <Select items={fruits} placeholder="Pick a fruit" />;
84
+ ```
85
+
86
+ ## Object Items with mapItem
87
+
88
+ When items are objects, use `mapItem` to tell the component how to display them:
89
+
90
+ ```tsx
91
+ type Fruit = { id: number; name: string };
92
+
93
+ const fruits: Fruit[] = [
94
+ { id: 1, name: "Apple" },
95
+ { id: 2, name: "Banana" },
96
+ ];
97
+
98
+ <Select
99
+ items={fruits}
100
+ mapItem={(fruit) => ({ label: fruit.name, key: String(fruit.id) })}
101
+ onValueChange={(fruit) => console.log(fruit?.id)}
102
+ />;
103
+ ```
104
+
105
+ ## Multi-select
106
+
107
+ ```tsx
108
+ <Select
109
+ items={["Red", "Green", "Blue"]}
110
+ multiple
111
+ placeholder="Pick colors"
112
+ onValueChange={(colors) => console.log(colors)}
113
+ />
114
+ ```
115
+
116
+ ## Async Loading
117
+
118
+ Use `Select.Async` to load items from an API. The fetcher is called each time the dropdown is opened.
119
+
120
+ ```tsx
121
+ import { type SelectAsyncFetcher } from "@tailor-platform/app-shell";
122
+
123
+ const fetcher: SelectAsyncFetcher<Fruit> = async ({ signal }) => {
124
+ const res = await fetch("/api/fruits", { signal });
125
+ return res.json();
126
+ };
127
+
128
+ <Select.Async
129
+ fetcher={fetcher}
130
+ mapItem={(fruit) => ({ label: fruit.name, key: String(fruit.id) })}
131
+ placeholder="Pick a fruit"
132
+ onValueChange={(fruit) => console.log(fruit)}
133
+ />;
134
+ ```
135
+
136
+ ### Select.Async Props
137
+
138
+ Accepts all the same props as `Select` except `items`, plus:
139
+
140
+ | Prop | Type | Default | Description |
141
+ | ---------------------- | -------------------------- | -------------------------- | ---------------------------------------------------------------------------------------------------------------------------------- |
142
+ | `fetcher` | `SelectAsyncFetcher<T>` | - | Fetcher called each time the dropdown is opened |
143
+ | `loadingText` | `string` | `"Loading..."` | Text shown while loading |
144
+ | `errorText` | `string` | `"Couldn't load results."` | Message shown in the dropdown when the fetcher fails (with Retry) |
145
+ | `retryText` | `string` | `"Retry"` | Label for the retry button in the error state |
146
+ | `onFetchError` | `(error: unknown) => void` | - | Called once per outage when a fetch fails (logging/error tracking) |
147
+ | `modal` | `boolean` | `false` | Whether the select traps focus (modal behavior). Set to `true` when rendering inside a `Dialog` or `Sheet` to preserve focus-trap. |
148
+ | `alignItemWithTrigger` | `boolean` | `false` | Whether to align the selected item with the trigger when the dropdown opens. |
149
+
150
+ > **Note:** `Select.Async` does not support `ItemGroup<T>[]` — the fetcher must return a flat array.
151
+
152
+ > **Limitation:** `Select.Async` defaults to `modal={false}` and `alignItemWithTrigger={false}` to work around a Base UI scroll-lock bug with dynamically loaded items. As a result, it may not function correctly inside focus-trapping containers such as `Dialog` or `Sheet` — the focus trap can block interaction with the dropdown, or the portal may render outside the modal's visible area. If you need an async dropdown inside a `Dialog` or `Sheet`, prefer [`Combobox.Async`](./combobox.md) which uses Popover-based positioning and does not have this constraint.
153
+
154
+ ### SelectAsyncFetcher
155
+
156
+ ```ts
157
+ type SelectAsyncFetcher<T> = (options: { signal: AbortSignal }) => Promise<T[]>;
158
+ ```
159
+
160
+ ### Error handling
161
+
162
+ If the fetcher throws or rejects, `Select.Async` renders a built-in inline error state in the dropdown — the `errorText` message plus a **Retry** button that re-runs the fetch — instead of the misleading empty state. Aborted requests (the dropdown closed before the fetch settled) are ignored, and announcements are de-duped per outage. Pass `onFetchError` to run a side effect (logging, toast) — it fires once per outage and re-arms after the next successful fetch.
163
+
164
+ ## Low-level Primitives
165
+
166
+ `Select.Parts` exposes the styled sub-components for fully custom compositions:
167
+
168
+ ```tsx
169
+ const { Root, Trigger, Value, Content, Item, Group, GroupLabel, Separator } = Select.Parts;
170
+ ```
171
+
172
+ ## Form submission
173
+
174
+ Inside a `Field.Root`, the field's `name` identifies the value and these props are unnecessary —
175
+ see [Form](./form.md). They matter for **native** submission: a plain `<form>`, `new FormData(form)`,
176
+ or a server action.
177
+
178
+ | Prop | Type | Default | Description |
179
+ | ------------------- | ----------------------------- | ------- | -------------------------------------------------------------------------------------------------------- |
180
+ | `name` | `string` | - | Names the hidden input for native submission. **Ignored inside a `Field.Root`** — the field's name wins. |
181
+ | `form` | `string` | - | `id` of the owning form, when the control is rendered outside it. |
182
+ | `required` | `boolean` | `false` | Blocks submission until a value is chosen, surfacing the matching `Field.Error`. |
183
+ | `inputRef` | `React.Ref<HTMLInputElement>` | - | Ref to the hidden input (use for React Hook Form's `field.ref`). |
184
+ | `itemToStringValue` | `(item: T) => string` | - | Serialises a non-string item for submission. Items shaped `{ value, label }` use `value` automatically. |
185
+
186
+ ## Examples
187
+
188
+ ### Controlled Select
189
+
190
+ ```tsx
191
+ const [selected, setSelected] = useState<string | null>(null);
192
+
193
+ <Select
194
+ items={["Draft", "Pending", "Approved", "Rejected"]}
195
+ value={selected}
196
+ onValueChange={setSelected}
197
+ placeholder="Select status"
198
+ />;
199
+ ```
200
+
201
+ ### Custom Render
202
+
203
+ ```tsx
204
+ <Select
205
+ items={statuses}
206
+ mapItem={(s) => ({
207
+ label: s.name,
208
+ key: s.id,
209
+ render: (
210
+ <span className="flex items-center gap-2">
211
+ <span className={`size-2 rounded-full bg-${s.color}`} />
212
+ {s.name}
213
+ </span>
214
+ ),
215
+ })}
216
+ placeholder="Select status"
217
+ />
218
+ ```
219
+
220
+ ### Async with Parts (custom composition)
221
+
222
+ Combine `Select.useAsync` with `Select.Parts` for full control over layout and rendering:
223
+
224
+ `Select.useAsync` accepts the same options as `Select.Async` (including `onFetchError`) and returns:
225
+
226
+ | Property | Type | Description |
227
+ | --------- | ------------ | ------------------------------------------------------- |
228
+ | `items` | `T[]` | Currently loaded items |
229
+ | `loading` | `boolean` | Whether a fetch is in progress |
230
+ | `error` | `unknown` | The error thrown by the last fetch, if any |
231
+ | `retry` | `() => void` | Re-runs the last fetch (use to build a custom retry UI) |
232
+
233
+ ```tsx
234
+ type Fruit = { id: number; name: string };
235
+
236
+ const fruits = Select.useAsync({
237
+ fetcher: async ({ signal }) => {
238
+ const res = await fetch("/api/fruits", { signal });
239
+ return res.json() as Promise<Fruit[]>;
240
+ },
241
+ onFetchError: (error) => reportError(error),
242
+ });
243
+
244
+ <Select.Parts.Root {...fruits} itemToStringLabel={(f) => f.name}>
245
+ <Select.Parts.Trigger>
246
+ <Select.Parts.Value placeholder="Pick a fruit" />
247
+ </Select.Parts.Trigger>
248
+ <Select.Parts.Content>
249
+ {fruits.error ? (
250
+ <div className="px-4 py-2 text-center text-sm">
251
+ Couldn't load. <button onClick={fruits.retry}>Retry</button>
252
+ </div>
253
+ ) : fruits.loading ? (
254
+ <div className="px-4 py-2 text-center text-sm text-muted-foreground">Loading...</div>
255
+ ) : (
256
+ fruits.items.map((f) => (
257
+ <Select.Parts.Item key={f.id} value={f}>
258
+ {f.name}
259
+ </Select.Parts.Item>
260
+ ))
261
+ )}
262
+ </Select.Parts.Content>
263
+ </Select.Parts.Root>;
264
+ ```
265
+
266
+ ## Accessibility
267
+
268
+ - The trigger exposes the `combobox` role. When used inside a `Form`/`FormControl` it is labeled automatically.
269
+ - When used standalone (e.g. a table toolbar or list filter) there is no visible `<label>`, so give the trigger an accessible name with `aria-label` or `aria-labelledby`. Otherwise screen readers announce only the current value.
270
+
271
+ ```tsx
272
+ // Announced as "Direction filter, combobox" instead of just the value
273
+ <Select items={items} value={value} onValueChange={setValue} aria-label="Direction filter" />
274
+
275
+ // Or point at a visible label
276
+ <span id="dir-label">From</span>
277
+ <Select items={items} aria-labelledby="dir-label" />
278
+ ```
279
+
280
+ ## Related Components
281
+
282
+ - [Combobox](./combobox.md) - Searchable combobox with filtering
283
+ - [Autocomplete](./autocomplete.md) - Free-text input with suggestions