@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
@@ -1,981 +0,0 @@
1
- # AppShell Components
2
-
3
- > **AppShell version:** `1.7.0` (matches `packages/erp-kit/templates/scaffold/app/*/frontend/package.json` pinned `@tailor-platform/app-shell` semver)
4
- > **Source of truth:** `@tailor-platform/app-shell` exports
5
- > **Update process:** see "Keeping this file in sync" at the bottom
6
-
7
- ## Scope vs design-system.md
8
-
9
- | This file (`components.md`) | `design-system.md` |
10
- | ----------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------- |
11
- | **Imports**, compound structure, hooks, canonical **composition** (`Card` + `Table`, `DataTable`, `Dialog`, etc.) | **Tokens**, theme imports, typography/spacing/radius/elevation **tables**, breakpoints **intent** |
12
- | JSX examples tied to ERP patterns (`patterns/*`) | **`astw:`** rules — only on AppShell `*ClassName` props; plain utilities on **your** elements |
13
- | Prop summaries + links to upstream `docs/components/*.md` | **Visual conformance**: no magic colors/px on custom markup, motion, dark mode |
14
-
15
- **Rule of thumb:** “Which component / prop?” → **here.** “Which token / spacing step / elevation?” → **`design-system.md`** §§4–6.
16
-
17
- This file intentionally does **not** duplicate full token catalogs. “Every heading here ≈ documented export cluster” maintenance lives in **Keeping this file in sync** — upstream npm remains authoritative for completeness.
18
-
19
- Entries follow this shape:
20
-
21
- ```
22
- **Import:** how to import it
23
- **Purpose:** one sentence
24
- **API:** key props or sub-components
25
- **Example:** minimal JSX
26
- **Used in patterns:** which patterns/<type>/<slug>.md cite this component
27
- **Notes:** version-specific quirks (optional)
28
- ```
29
-
30
- For the full upstream API of any component, follow the link to the published reference at the top of its section.
31
-
32
- ---
33
-
34
- ## Layout primitives
35
-
36
- ### `AppShell`
37
-
38
- **Import:** `import { AppShell } from '@tailor-platform/app-shell'`
39
- **Purpose:** Application root — wraps `<App />` with AppShell context, theme, and routing.
40
- **API:** Compound — `AppShell.Root`, plus subcomponents wired through `AppShellProps`. Configured once in `App.tsx`.
41
- **Example:** see `project-setup.md`.
42
- **Used in patterns:** all (root container).
43
-
44
- ### `Layout`
45
-
46
- > Full API: [https://raw.githubusercontent.com/tailor-platform/app-shell/refs/heads/main/docs/components/layout.md](https://raw.githubusercontent.com/tailor-platform/app-shell/refs/heads/main/docs/components/layout.md)
47
-
48
- **Import:** `import { Layout } from '@tailor-platform/app-shell'`
49
- **Purpose:** Standard page container with header + 1–N column body. The most-used component — every page wraps content in `<Layout>`.
50
- **API:** `Layout` (root) + `Layout.Column` + `Layout.Header`. `LayoutProps`: `fill` (boolean), `columns` (number, default auto-detected from children), `gap`, `title`, `actions`, `className`, `style`.
51
-
52
- **`fill`** — stretches the layout to the available height and bounds the column row so children can scroll internally instead of growing the page. Use on table-first pages (`<Layout fill>` + `DataTable`): the title, table toolbar, sticky column headers, and pagination footer stay pinned while only the rows scroll — see **`patterns/list-dense-scan.md`**. Omit on pages that should flow and scroll naturally (forms, dashboards).
53
-
54
- **Column-count → width rules** (column count is auto-detected from `Layout.Column` children):
55
-
56
- | Columns | Breakpoint | Widths | Below breakpoint |
57
- | ------- | ---------- | ------------------------------ | ---------------- |
58
- | 1 | always | full | n/a |
59
- | 2 | ≥ 1024px | flex + 280px | stacks |
60
- | 3 | ≥ 1280px | 320px + flex + 280px | stacks |
61
- | 4+ | ≥ 1280px | equal share — `repeat(N, 1fr)` | stacks |
62
-
63
- Desktop breakpoints and desktop-first rationale: **`design-system.md`** §4 Breakpoints. This table is **`Layout` column mechanics only**.
64
-
65
- **`Layout.Header`** — direct child of `<Layout>`, above any `<Layout.Column>`. Only the first is rendered if multiple are passed.
66
-
67
- | Prop | Type | Description |
68
- | ---------- | ------------------- | ------------------------------------------------ |
69
- | `title` | `string` | Page title — `<h1>` on the left |
70
- | `actions` | `React.ReactNode[]` | Buttons on the right |
71
- | `children` | `React.ReactNode` | Full-width row below title — typical use is tabs |
72
-
73
- **`Layout.Column`** — direct child, accepts `area` (`"left" | "main" | "right"`) for advanced placement override. If any column declares `area`, all columns switch to area-based widths (`left`=320, `main`=flex, `right`=280) and render in source order.
74
-
75
- **Example — list page header with tabs:**
76
-
77
- ```tsx
78
- import { Button, Layout, Tabs } from "@tailor-platform/app-shell";
79
-
80
- <Layout>
81
- <Layout.Header title="Purchase orders" actions={[<Button key="create">Create</Button>]}>
82
- <Tabs.Root value={bucket} onValueChange={setBucket}>
83
- <Tabs.List>
84
- <Tabs.Tab value="all">All</Tabs.Tab>
85
- <Tabs.Tab value="open">Open</Tabs.Tab>
86
- </Tabs.List>
87
- </Tabs.Root>
88
- </Layout.Header>
89
- <Layout.Column>{/* table — see patterns/list/dense-scan.md */}</Layout.Column>
90
- </Layout>;
91
- ```
92
-
93
- **Example — detail page, 2-column with area mode:**
94
-
95
- ```tsx
96
- <Layout>
97
- <Layout.Header title={order.number} actions={[<Button>Edit</Button>]} />
98
- <Layout.Column area="main">
99
- <DescriptionCard ... />
100
- </Layout.Column>
101
- <Layout.Column area="right">
102
- <ActionPanel ... />
103
- <ActivityCard ... />
104
- </Layout.Column>
105
- </Layout>
106
- ```
107
-
108
- **Notes:** Children that aren't `Layout.Header` or `Layout.Column` are filtered out. Column gap overrides (`<Layout className="astw:gap-6" />`) → **`design-system.md`** §5 (`astw:` rules).
109
-
110
- **Used in patterns:** every page pattern (`list/*`, `detail/*`, `form/*`).
111
-
112
- ### `Grid`
113
-
114
- **Import:** `import { Grid } from '@tailor-platform/app-shell'`
115
- **Purpose:** Presentational CSS-Grid container for laying tiles/cards into equal or custom columns with responsive reflow — the canonical wrapper for **metric / KPI strips** and card galleries. Purely layout; makes no data assumptions.
116
- **API:** `GridProps` — `columns` (number | CSS track string like `"280px 1fr"` | responsive object `{ initial, sm, md, lg, xl }`), `gap` (spacing step, default `3`) plus `gapX` / `gapY`, `minChildWidth` (auto-fit: as many ≥Npx columns as fit, no breakpoints needed), `rows`, `flow`, `align`, `justify`. Sub-component **`Grid.Item`** for spanning/placement — `colSpan`, `rowSpan`, `colStart`, `colEnd` (all responsive). Root carries `data-slot="grid"`.
117
- **Example — responsive KPI grid:**
118
-
119
- ```tsx
120
- import { Grid, MetricCard } from "@tailor-platform/app-shell";
121
-
122
- <Grid columns={{ initial: 1, md: 2, xl: 4 }} gap={4}>
123
- <MetricCard title="Net total" value="$1,500" trend={{ direction: "up", value: "+5%" }} />
124
- <MetricCard title="Open orders" value="42" />
125
- <MetricCard title="Overdue" value="7" />
126
- <MetricCard title="Suppliers" value="18" />
127
- </Grid>;
128
- ```
129
-
130
- **Used in patterns:** metric / KPI strips on `detail/*` and dashboards; any equal-column card layout.
131
-
132
- **Notes:** Metric tiles and KPI cards **always** go in a `Grid` — never stacked one-per-row or in a single column. Use a responsive `columns` object (e.g. `{ initial: 1, md: 2, xl: 4 }`) so tiles reflow at smaller widths; reach for `minChildWidth` when the tile count is dynamic.
133
-
134
- ### `SidebarLayout`
135
-
136
- **Import:** `import { SidebarLayout } from '@tailor-platform/app-shell'`
137
- **Purpose:** Top-level layout that mounts the sidebar and renders the page outlet.
138
- **API:** `SidebarLayoutProps` — `sidebar`, `header`, and `children` are full-region slots (each defaults to a built-in). `sidebar` defaults to `SidebarLayout.DefaultSidebar`; `header` defaults to `SidebarLayout.DefaultHeader`. Used in `App.tsx`.
139
- **Used in patterns:** consumed by AppShell init, not directly by page patterns. See `project-setup.md`.
140
-
141
- ### `SidebarLayout.DefaultHeader` (`DefaultHeader`)
142
-
143
- **Import:** `import { SidebarLayout } from '@tailor-platform/app-shell'` → `SidebarLayout.DefaultHeader` (also top-level `DefaultHeader`)
144
- **Purpose:** The built-in top bar (trigger + breadcrumb, plus an `actions` cluster). Drop into `SidebarLayout`'s `header` slot to extend the header without rebuilding it.
145
- **API:** `actions?: ReactNode | ReactNode[]` — the right-hand cluster (opinionated flex row). **Defaults to `[<AppearanceSwitcher />]`, and passing `actions` REPLACES the switcher** — include `<AppearanceSwitcher />` in the array to keep it. `actions={[]}` = empty right side.
146
- **Used in patterns:** project-level header customization (notification bell, user menu). See `project-setup.md`.
147
-
148
- ### `SidebarLayout.DefaultSidebar` (`DefaultSidebar`), `SidebarGroup`, `SidebarItem`, `SidebarSeparator`
149
-
150
- **Import:** `import { SidebarLayout, SidebarGroup, SidebarItem, SidebarSeparator } from '@tailor-platform/app-shell'` → `SidebarLayout.DefaultSidebar` (also top-level `DefaultSidebar`)
151
- **Purpose:** Sidebar composition. `DefaultSidebar` auto-resolves nav items from `appShellPageProps.meta` on each page; the others let you customize manually.
152
- **Used in patterns:** sidebar is a project-level concern. See `project-setup.md`.
153
-
154
- ### `AppearanceSwitcher`
155
-
156
- **Import:** `import { AppearanceSwitcher } from '@tailor-platform/app-shell'`
157
- **Purpose:** Palette-icon dropdown for Light/Dark/System color mode. The default `SidebarLayout.DefaultHeader` action; also usable standalone (e.g. a sidebar footer). Include it in `DefaultHeader`'s `actions` array when customizing the header to keep it visible.
158
- **API:** No props — reads/writes theme via `useTheme`.
159
- **Used in patterns:** project-level. See `project-setup.md`.
160
-
161
- ### `CommandPalette`
162
-
163
- **Import:** `import { CommandPalette } from '@tailor-platform/app-shell'`
164
- **Purpose:** Cmd/Ctrl-K command palette. Auto-discovers searchable resources via `defineResource`.
165
- **API:** Renderless — drop into the layout once.
166
- **Used in patterns:** project-level. Useful for any app with >10 routes.
167
-
168
- ---
169
-
170
- ## Interaction surfaces
171
-
172
- ### `Button`
173
-
174
- > Full API: [https://raw.githubusercontent.com/tailor-platform/app-shell/refs/heads/main/docs/components/button.md](https://raw.githubusercontent.com/tailor-platform/app-shell/refs/heads/main/docs/components/button.md)
175
-
176
- **Import:** `import { Button } from '@tailor-platform/app-shell'`
177
- **Purpose:** All buttons in the app — including polymorphic rendering via the `render` prop.
178
- **API:** `ButtonProps` extends native `<button>` plus `variant` (`default | destructive | outline | secondary | ghost | link`), `size` (`default | sm | lg | icon | xs`), `render` (polymorphic — pass `<Link>` or another element to render-as).
179
- **Example:**
180
-
181
- ```tsx
182
- import { Button, Link } from '@tailor-platform/app-shell';
183
-
184
- <Button render={<Link to="create" />}>Create</Button>
185
- <Button variant="destructive" onClick={handleDelete}>Delete</Button>
186
- ```
187
-
188
- **Used in patterns:** every pattern.
189
-
190
- ### `Link`
191
-
192
- **Import:** `import { Link } from '@tailor-platform/app-shell'`
193
- **Purpose:** Router-aware anchor. Re-exported from `react-router` so the rest of the app stays on the AppShell barrel.
194
- **API:** `to`, `replace`, `state`, etc. — same as `react-router`.
195
- **Used in patterns:** all (navigation).
196
-
197
- ### `NavLink`
198
-
199
- **Import:** `import { NavLink } from '@tailor-platform/app-shell'`
200
- **Purpose:** `Link` that knows when it is active — for nav menus and tab bars.
201
- **API:** `to`, plus `className` / `style` / `children` as render functions receiving `{ isActive, isPending }`.
202
- **Used in patterns:** navigation shells.
203
-
204
- ### `Dialog`
205
-
206
- > Full API: [https://raw.githubusercontent.com/tailor-platform/app-shell/refs/heads/main/docs/components/dialog.md](https://raw.githubusercontent.com/tailor-platform/app-shell/refs/heads/main/docs/components/dialog.md)
207
-
208
- **Import:** `import { Dialog } from '@tailor-platform/app-shell'`
209
- **Purpose:** Modal dialog for confirmations, ≤5-field forms, blocking workflows.
210
- **API:** Compound — `Dialog.Root`, `Dialog.Trigger`, `Dialog.Content`, `Dialog.Header`, `Dialog.Title`, `Dialog.Description`, `Dialog.Footer`, `Dialog.Close`. Controllable via `open` + `onOpenChange`.
211
- **Example:**
212
-
213
- ```tsx
214
- <Dialog.Root>
215
- <Dialog.Trigger render={<Button variant="destructive" />}>Delete</Dialog.Trigger>
216
- <Dialog.Content>
217
- <Dialog.Header>
218
- <Dialog.Title>Delete order #1234?</Dialog.Title>
219
- <Dialog.Description>This cannot be undone.</Dialog.Description>
220
- </Dialog.Header>
221
- <Dialog.Footer>
222
- <Dialog.Close render={<Button variant="outline" />}>Cancel</Dialog.Close>
223
- <Button variant="destructive" onClick={onDelete}>
224
- Delete
225
- </Button>
226
- </Dialog.Footer>
227
- </Dialog.Content>
228
- </Dialog.Root>
229
- ```
230
-
231
- **Used in patterns:** `form/modal`, `interaction/confirm`.
232
-
233
- ### `Sheet`
234
-
235
- > Full API: [https://raw.githubusercontent.com/tailor-platform/app-shell/refs/heads/main/docs/components/sheet.md](https://raw.githubusercontent.com/tailor-platform/app-shell/refs/heads/main/docs/components/sheet.md)
236
-
237
- **Import:** `import { Sheet } from '@tailor-platform/app-shell'`
238
- **Purpose:** Slide-in panel from any edge. Use for filters, side-work without losing context.
239
- **API:** Compound — `Sheet.Root` (with `side: 'left' | 'right' | 'top' | 'bottom'`), `Sheet.Trigger`, `Sheet.Content`, `Sheet.Header`, `Sheet.Title`, `Sheet.Description`, `Sheet.Footer`, `Sheet.Close`.
240
- **Example:**
241
-
242
- ```tsx
243
- <Sheet.Root side="right">
244
- <Sheet.Trigger render={<Button variant="outline" />}>Filters</Sheet.Trigger>
245
- <Sheet.Content>
246
- <Sheet.Header>
247
- <Sheet.Title>Filter orders</Sheet.Title>
248
- </Sheet.Header>
249
- {/* filter inputs */}
250
- <Sheet.Footer>
251
- <Sheet.Close render={<Button variant="outline" />}>Clear</Sheet.Close>
252
- <Button>Apply</Button>
253
- </Sheet.Footer>
254
- </Sheet.Content>
255
- </Sheet.Root>
256
- ```
257
-
258
- **Used in patterns:** `list/*` (filter sheet variant).
259
-
260
- **Notes:** Size the panel with `Sheet.Content`'s **`size`** prop (`"sm" | "md" | "lg" | "xl" | "full"`, default `"sm"`) — not a width utility. `astw:` rules → **`design-system.md`** §5.
261
-
262
- ### `Menu`
263
-
264
- > Full API: [https://raw.githubusercontent.com/tailor-platform/app-shell/refs/heads/main/docs/components/menu.md](https://raw.githubusercontent.com/tailor-platform/app-shell/refs/heads/main/docs/components/menu.md)
265
-
266
- **Import:** `import { Menu } from '@tailor-platform/app-shell'`
267
- **Purpose:** Dropdown menu — row actions, overflow actions, grouped commands.
268
- **API:** Compound — `Menu.Root`, `Menu.Trigger`, `Menu.Content`, `Menu.Item`, `Menu.Separator`, `Menu.Group`, `Menu.GroupLabel`. Supports checkbox/radio items and nested sub-menus.
269
- **Example:**
270
-
271
- ```tsx
272
- <Menu.Root>
273
- <Menu.Trigger>
274
- <Button variant="ghost" size="icon">
275
- <EllipsisVertical />
276
- </Button>
277
- </Menu.Trigger>
278
- <Menu.Content>
279
- <Menu.Item onSelect={() => handleAssign(id)}>Assign</Menu.Item>
280
- <Menu.Item onSelect={() => handleDuplicate(id)}>Duplicate</Menu.Item>
281
- <Menu.Separator />
282
- <Menu.Item onSelect={() => handleDelete(id)}>Delete</Menu.Item>
283
- </Menu.Content>
284
- </Menu.Root>
285
- ```
286
-
287
- **Used in patterns:** `list/*` (row actions), `detail/*` (overflow actions).
288
-
289
- **Notes:** **`list-dense-scan`** uses whole-row / primary-column navigation — keep row `Menu` items **non-navigation** (Assign, Duplicate, Delete). Avoid redundant **View**/**Open**. Detail overflows may include navigation only when not duplicating hero content.
290
-
291
- ### `Tooltip`
292
-
293
- **Import:** `import { Tooltip } from '@tailor-platform/app-shell'`
294
- **Purpose:** Contextual hint on hover/focus. Use sparingly — for icon-only buttons or constrained labels.
295
- **API:** Compound — `Tooltip.Root`, `Tooltip.Trigger`, `Tooltip.Content`.
296
- **Used in patterns:** any pattern with icon-only buttons (must have `aria-label` AND a tooltip).
297
-
298
- ### `Tabs`
299
-
300
- **Import:** `import { Tabs } from '@tailor-platform/app-shell'`
301
- **Purpose:** In-page tab navigation — split one record's sections (Overview / Line items / Activity) or bucket a list (All / Open / …) into switchable panels. Presentational; owns only the active-tab state.
302
- **API:** Compound — `Tabs.Root` (`variant`: `default | line | capsule`; controlled `value` + `onValueChange`, or uncontrolled `defaultValue`), `Tabs.List`, `Tabs.Tab` (`value`), `Tabs.Panel` (`value`). Note: the sub-component is **`Tabs.Tab`**, not `Tabs.Trigger`.
303
- **Example:**
304
-
305
- ```tsx
306
- <Tabs.Root defaultValue="overview" variant="line">
307
- <Tabs.List>
308
- <Tabs.Tab value="overview">Overview</Tabs.Tab>
309
- <Tabs.Tab value="items">Line items</Tabs.Tab>
310
- </Tabs.List>
311
- <Tabs.Panel value="overview">{/* … */}</Tabs.Panel>
312
- <Tabs.Panel value="items">{/* … */}</Tabs.Panel>
313
- </Tabs.Root>
314
- ```
315
-
316
- **Used in patterns:** `list-dense-scan` (bucket tabs composed **above** `DataTable.Root`, synced to `useCollectionVariables` — see the `DataTable` "Bucket tabs" note), `detail/*` (sectioned record content).
317
-
318
- **Notes:** For lists, AppShell's own filtering surface is **toolbar chips** (`DataTable.Filters`), not tabs — reach for `Tabs` only when the business genuinely thinks in a small set of named buckets. Don't render a `Tab` per enum value where a filter chip belongs.
319
-
320
- ---
321
-
322
- ## Display
323
-
324
- ### `Badge`
325
-
326
- > Full API: [https://raw.githubusercontent.com/tailor-platform/app-shell/refs/heads/main/docs/components/badge.md](https://raw.githubusercontent.com/tailor-platform/app-shell/refs/heads/main/docs/components/badge.md)
327
-
328
- **Import:** `import { Badge } from '@tailor-platform/app-shell'`
329
- **Purpose:** Status labels and small categorical chips.
330
- **API:** `BadgeProps` — `variant` (15 total):
331
-
332
- - **Filled** (high emphasis): `default` (primary), `success`, `warning`, `error`, `neutral`, `info`
333
- - **Subtle** (low emphasis, tinted): `subtle-success`, `subtle-warning`, `subtle-error`, `subtle-info`
334
- - **Outline** (renders a status dot — for row/list statuses): `outline-success`, `outline-warning`, `outline-error`, `outline-info`, `outline-neutral`
335
-
336
- Plus `badgeVariants` CVA for custom-styled siblings.
337
- **Example:**
338
-
339
- ```tsx
340
- // Primary / lifecycle status — filled semantic variant
341
- <Badge variant="success">Confirmed</Badge>
342
- // Secondary status (delivery, billing) — outline with status dot
343
- <Badge variant="outline-warning">Partially received</Badge>
344
- // Tag / label — subtle
345
- <Badge variant="subtle-info">New</Badge>
346
- ```
347
-
348
- **Used in patterns:** `list/*` (status column → `outline-*`), `detail/*` (header status).
349
-
350
- **Notes:** Encode status by **semantic color** with a primary/secondary split: a record's **primary/lifecycle status** uses a **filled** semantic variant (one per row / one in a detail header); **secondary statuses** (delivery, billing) use **`outline-*`** (status dot); tags use **`subtle-*`**. Reserve **`default`** (brand) for non-status emphasis. Full rule: **`design-system.md`** → Composition & emphasis rules.
351
-
352
- ### `Table`
353
-
354
- > Full API: [https://raw.githubusercontent.com/tailor-platform/app-shell/refs/heads/main/docs/components/table.md](https://raw.githubusercontent.com/tailor-platform/app-shell/refs/heads/main/docs/components/table.md)
355
-
356
- **Import:** `import { Table } from '@tailor-platform/app-shell'`
357
- **Purpose:** Semantic data table with scrollable container.
358
- **API:** Compound — `Table.Root`, `Table.Header`, `Table.Body`, `Table.Footer`, `Table.Row`, `Table.Head`, `Table.Cell`, `Table.Caption`. `Table.Root` accepts `containerClassName` for the outer wrapper, `className` for the inner `<table>`. `Table.Head` and `Table.Cell` accept **`align`** (`"left" | "center" | "right"`, default `"left"`) — prefer this over ad-hoc `className="astw:text-right"` and pair the same alignment on the head and its column's cells (right for numeric/money, center for compact status/icon columns).
359
- **Example:**
360
-
361
- ```tsx
362
- <Table.Root>
363
- <Table.Header>
364
- <Table.Row>
365
- <Table.Head>Order</Table.Head>
366
- <Table.Head>Status</Table.Head>
367
- <Table.Head align="right">Total</Table.Head>
368
- </Table.Row>
369
- </Table.Header>
370
- <Table.Body>
371
- {orders.map((o) => (
372
- <Table.Row key={o.id}>
373
- <Table.Cell>{o.number}</Table.Cell>
374
- <Table.Cell>
375
- <Badge variant={statusVariant(o.status)}>{o.status}</Badge>
376
- </Table.Cell>
377
- <Table.Cell align="right">{formatMoney(o.total)}</Table.Cell>
378
- </Table.Row>
379
- ))}
380
- </Table.Body>
381
- </Table.Root>
382
- ```
383
-
384
- **Used in patterns:** `list-dense-scan` hand-built subsets / static tables (`DataTable` is preferred for wired lists).
385
-
386
- **Notes:**
387
-
388
- - **Inside a card?** Pass `containerClassName="astw:px-6"` on `Table.Root` for the horizontal inset, and either drop `Card.Content` (bare list form) or pass `Card.Content className="astw:px-0"` (header+content form). Skipping the `containerClassName` lands the first column flush against the card edge. See the `Card` entry for the two canonical forms and a DON'T example. Dense cell typography (**`text-sm`**, **`tabular-nums`** for figures, **`font-mono`** for identifiers) → **`design-system.md`** §4 Typography.
389
- - **Whole row is clickable.** Use `<Table.Row onClick={() => navigate(detailPath)} className="astw:cursor-pointer">`. For keyboard and screen-reader users, also wrap the primary identifier cell content in `<Link>` (so the row is reachable via Tab; `Table.Row` is a `<tr>` and cannot itself be a Link — wrapping a `<tr>` in `<a>` is invalid HTML). **No per-row "View" / "Open" / "→" buttons.** Per-row `Menu` (overflow `…`) is the only allowed per-row action surface and is reserved for non-navigation actions like Archive, Duplicate.
390
-
391
- ### `DataTable`
392
-
393
- > Full API: [https://github.com/tailor-platform/app-shell/blob/main/docs/components/data-table.md](https://github.com/tailor-platform/app-shell/blob/main/docs/components/data-table.md)
394
-
395
- **Import:** compound namespace + helpers from `'@tailor-platform/app-shell'`, e.g. `DataTable`, `useDataTable`, `useCollectionVariables`, `createColumnHelper`, and types such as `Column`, `UseDataTableReturn`.
396
-
397
- **Purpose:** Production list screens over GraphQL **connections**. Owns toolbar filter chips (**`DataTable.Filters`** from column `filter` configs), header sort, **`DataTable.Pagination`** (cursor-first; First/Last when `total` is provided), loading skeleton/error row, **`onClickRow`**, **`rowActions`** (kebab column), **`onSelectionChange`** (checkbox column), **expandable rows** (**`rowExpansion`** — a chevron column plus a full-width detail panel per row; `render` with optional `canExpand` / `getLabel`, and `expandedIds` + `onChange` for controlled mode), **column pinning** (`pin: "left" | "right"`) and built-in **column settings** (`<DataTable.Toolbar columnSettings>` — user show/hide + reorder + pin, persisted per-user via **`tableId`**).
398
-
399
- **Primitives:** Builds on low-level **`Table`**; do not reinvent pagination/filters manually unless the dataset is trivial.
400
-
401
- **Shape:**
402
-
403
- ```tsx
404
- const { variables, control } = useCollectionVariables({
405
- params: { pageSize: 20 },
406
- // tableMetadata: tableMetadata.po, // typed vars when generated
407
- });
408
-
409
- const table = useDataTable({
410
- columns,
411
- data: fetching ? undefined : mappedFromQuery,
412
- loading: fetching,
413
- control,
414
- tableId: "purchase-orders", // persist user column layout to localStorage
415
- onClickRow: (row) => navigate(detailHref(row)),
416
- // onSelectionChange, rowActions, sort: …
417
- });
418
-
419
- <DataTable.Root value={table}>
420
- <DataTable.Toolbar columnSettings>
421
- <DataTable.Filters />
422
- </DataTable.Toolbar>
423
- <DataTable.Table />
424
- <DataTable.Footer>
425
- <DataTable.Pagination pageSizeOptions={[10, 20, 50]} />
426
- </DataTable.Footer>
427
- </DataTable.Root>;
428
- ```
429
-
430
- **Row navigation:** whole row is clickable via **`onClickRow`** → `navigate(detailHref(row))`; wrap the primary identifier cell in `<Link>` for keyboard/SR access. Never add a per-row "View" / "Open" / "→" button. (A first-class row-interaction API is under design — see the row-interaction tracking issue.)
431
-
432
- **Column pinning & settings:** set `pin: "left" | "right"` on a `Column` to freeze it during horizontal scroll — selection auto-pins left, row-actions auto-pins right (a `width` is optional but recommended for stable sizing). Pass **`columnSettings`** to **`DataTable.Toolbar`** to render the built-in "Columns" control (show/hide, reorder, re-pin) at the top-right; pass a stable, **unique** **`tableId`** to persist each user's layout to `localStorage` (a per-user preference — intentionally not in the URL like filters/sort).
433
-
434
- **Column alignment:** each `Column` accepts **`align`** (`"left" | "right"`) applied to both header and body cell. Numeric `type` columns (`"number"`, `"money"`) default to `"right"` automatically so digits align on the decimal place — pass `align="left"` to opt out; everything else defaults to `"left"`.
435
-
436
- **Metadata path:** Prefer `createColumnHelper` + `inferColumns(tableMetadata.order)` (`@tailor-platform/sdk-plugin-app-shell` codegen) when available so enum/datetime/string filters bind to the right editors.
437
-
438
- **Bucket tabs / segmented UX:** AppShell defines **toolbar chips**, not lifecycle tabs. When design places **`Tabs`** (All / Draft / …) inside the card, compose them **above** `DataTable.Root` and synchronize tab-driven bucket state with **`useCollectionVariables`** (`variables.query` / filters)—see **`patterns/list/dense-scan.md`**.
439
-
440
- **Used in patterns:** `list-dense-scan` (preferred for live collections).
441
-
442
- ### `Card`
443
-
444
- **Import:** `import { Card } from '@tailor-platform/app-shell'`
445
- **Purpose:** Generic container with header, content, optional action.
446
- **API:** Compound — `Card.Root`, `Card.Header` (props: `title`, `description`, plus children for actions), `Card.Content`.
447
- **Example:**
448
-
449
- ```tsx
450
- <Card.Root>
451
- <Card.Header title="Recent orders" description="Last 7 days">
452
- <Button size="sm" variant="outline">
453
- View all
454
- </Button>
455
- </Card.Header>
456
- <Card.Content>{/* anything */}</Card.Content>
457
- </Card.Root>
458
- ```
459
-
460
- **Used in patterns:** `detail/*` (related-data sections), `form/wizard` (step container).
461
-
462
- **Notes:**
463
-
464
- - **Tables inside a card need TWO co-requisite geometry changes** (token-backed spacing rationale → **`design-system.md`** §4 Spacing): (a) Card stops imposing horizontal padding — drop `Card.Content` for the bare form, or pass `Card.Content className="astw:px-0"` for the header+content form. (b) `Table.Root` provides the inset itself via `containerClassName="astw:px-6"`. Skipping (b) lands the first column flush against the card edge — `Table.Cell`'s intrinsic `astw:first:pl-6` does NOT render reliably in this composition.
465
-
466
- **Bare table-in-card (list pages, no card-level title):**
467
-
468
- ```tsx
469
- <Card.Root>
470
- <Table.Root containerClassName="astw:px-6">{/* … */}</Table.Root>
471
- </Card.Root>
472
- ```
473
-
474
- **Header + table (detail-page sections like "Line items"):**
475
-
476
- ```tsx
477
- <Card.Root>
478
- <Card.Header title="Line items" />
479
- <Card.Content className="astw:px-0">
480
- <Table.Root containerClassName="astw:px-6">{/* … */}</Table.Root>
481
- </Card.Content>
482
- </Card.Root>
483
- ```
484
-
485
- **DON'T — first column lands flush against the card edge:**
486
-
487
- ```tsx
488
- <Card.Root>
489
- <Card.Content className="astw:px-0">
490
- <Table.Root>{/* missing containerClassName="astw:px-6" */}</Table.Root>
491
- </Card.Content>
492
- </Card.Root>
493
- ```
494
-
495
- ### `MetricCard`
496
-
497
- **Import:** `import { MetricCard } from '@tailor-platform/app-shell'`
498
- **Purpose:** KPI tile for dashboards or hero metric strip.
499
- **API:** `MetricCardProps` — `title`, `value`, `trend: { direction, value }`, `description`, `icon`.
500
- **Example:** metric tiles are laid out in a `Grid` (never one-per-row):
501
-
502
- ```tsx
503
- <Grid columns={{ initial: 1, md: 2, xl: 4 }} gap={4}>
504
- <MetricCard
505
- title="Net total"
506
- value="$1,500"
507
- trend={{ direction: "up", value: "+5%" }}
508
- description="vs last month"
509
- icon={<DollarSign />}
510
- />
511
- <MetricCard title="Open orders" value="42" />
512
- {/* …more tiles */}
513
- </Grid>
514
- ```
515
-
516
- **Used in patterns:** KPI tiles, dashboards, **`detail/*`** metric strips where specs call for them.
517
-
518
- **Notes:** Always wrap metric tiles in **`Grid`** (see the `Grid` entry) — a column of single-width `MetricCard`s stacked one-per-row is an anti-pattern.
519
-
520
- ### `DocumentProgressCard`
521
-
522
- **Import:** `import { DocumentProgressCard } from '@tailor-platform/app-shell'`
523
- **Purpose:** Presentational card for a document's lifecycle / fulfilment state — an optional headline percentage, a stacked progress bar, and a status legend. Domain-agnostic and view-only: pass an arbitrary set of `segments` (e.g. shipped / returned / pending) plus an explicit `percent`; derive both in the consumer.
524
- **API:** `DocumentProgressCardProps` — `segments` (required; array of `{ label, value, color }`), `percent?` (0–100, headline shown top-right), `title?`, `legend?` (defaults to `segments`; supply only when the legend should differ from the bar, e.g. overlapping buckets), `total?` (bar denominator; defaults to the sum of segment values — a larger value leaves an unfilled remainder), `className?`. Segment `color`: `indigo | pink | green | amber | red | blue | neutral`.
525
- **Example:**
526
-
527
- ```tsx
528
- <DocumentProgressCard
529
- title="Shipment status"
530
- percent={60}
531
- segments={[
532
- { label: "Shipped", value: 30, color: "green" },
533
- { label: "Returned", value: 3, color: "red" },
534
- { label: "Pending", value: 17, color: "neutral" },
535
- ]}
536
- />
537
- ```
538
-
539
- **Used in patterns:** `detail/*` — fulfilment / lifecycle summary cards (e.g. a purchase-order receipt progress) in the main column or right rail.
540
-
541
- ### `Avatar`
542
-
543
- **Import:** `import { Avatar } from '@tailor-platform/app-shell'`
544
- **Purpose:** User/entity avatar with fallback initials.
545
- **API:** Compound — `Avatar.Root`, `Avatar.Image`, `Avatar.Fallback`. Plus `avatarVariants` CVA.
546
- **Used in patterns:** `detail/*` (assignee/owner, comments threads).
547
-
548
- ### `DescriptionCard`
549
-
550
- **Import:** `import { DescriptionCard } from '@tailor-platform/app-shell'`
551
- **Purpose:** Key/value display for a single record's metadata.
552
- **API:** `DescriptionCardProps` — `data`, `title`, `fields` (array of `{ key, label, render? }`), `columns` (1 | 2), `headerAction`.
553
- **Example:**
554
-
555
- ```tsx
556
- <DescriptionCard
557
- title="Order details"
558
- data={order}
559
- columns={2}
560
- fields={[
561
- { key: "number", label: "Order number" },
562
- { key: "status", label: "Status", render: (v) => <Badge>{v}</Badge> },
563
- { key: "createdAt", label: "Created", render: formatDate },
564
- ]}
565
- />
566
- ```
567
-
568
- **Used in patterns:** `detail/hero-with-actions` (body sections).
569
-
570
- ### `ActionPanel`
571
-
572
- **Import:** `import { ActionPanel } from '@tailor-platform/app-shell'`
573
- **Purpose:** Right-rail panel listing workflow actions for a detail page (approve, reject, archive, etc.).
574
- **API:** `ActionPanelProps` — `title`, `actions` (array of `{ label, onSelect, variant?, disabled?, hidden? }`), `className`.
575
- **Example:**
576
-
577
- ```tsx
578
- <ActionPanel
579
- title="Actions"
580
- actions={[
581
- { label: "Approve", variant: "default", onSelect: handleApprove },
582
- { label: "Reject", variant: "destructive", onSelect: handleReject, disabled: !canReject },
583
- ]}
584
- />
585
- ```
586
-
587
- **Used in patterns:** `detail/hero-with-actions`.
588
-
589
- **Notes:**
590
-
591
- - **Workflow only — never navigation.** `ActionPanel` lists state-change actions on the current record (Approve, Reject, Archive, Generate Variants, Manage Categories, Duplicate). It must NEVER contain back-navigation, breadcrumb-replacement, or "Go to X list" / "Back to X" links — those live in `Layout.Header`'s breadcrumb. If an entry would just navigate the user somewhere else, it does not belong here.
592
-
593
- ### `ActivityCard`
594
-
595
- **Import:** `import { ActivityCard } from '@tailor-platform/app-shell'`
596
- **Purpose:** Timeline of events on a record (audit log, status changes, comments).
597
- **API:** Compound — `ActivityCard.Root`, `ActivityCard.Items` (generic over item type), plus `ActivityCardProps`, `ActivityCardItem`, `ActivityCardItemProps`. Items render with timestamp + actor + description.
598
- **Used in patterns:** `detail/hero-with-actions` (right column or bottom section).
599
-
600
- ### `Alert`
601
-
602
- **Import:** `import { Alert } from '@tailor-platform/app-shell'`
603
- **Purpose:** Inline, in-page banner for a persistent status message — a form/record error, a warning, or a success/info notice attached to a section. (Transient notifications use `useToast`; blocking confirmations use `Dialog` — see `interaction/confirm`.)
604
- **API:** Compound — `Alert.Root` (`variant`: `neutral | success | warning | error | info`, default `neutral`; a matching icon is rendered automatically; optional `action?: ReactNode`, `dismissible?: boolean`, `onDismiss?: () => void`), `Alert.Title`, `Alert.Description`.
605
- **Example:**
606
-
607
- ```tsx
608
- <Alert.Root variant="error">
609
- <Alert.Title>Couldn't load line items</Alert.Title>
610
- <Alert.Description>Check your connection and try again.</Alert.Description>
611
- </Alert.Root>
612
- ```
613
-
614
- **Used in patterns:** any data-backed screen's **error** state (inline error + retry) and record-level warnings — the error affordance the composition rules require (**`design-system.md`** → Composition & emphasis rules → States).
615
-
616
- **Notes:** Encode severity by `variant` (semantic color), same discipline as `Badge` — don't use `error` for a routine notice. Pair a retry/next-step control via `action` rather than a separate stray button.
617
-
618
- ---
619
-
620
- ## Forms
621
-
622
- > **Which form stack?** AppShell's `Form` / `Field` / `Fieldset` wrap Base UI primitives and are the
623
- > default — they need no extra dependency. They own **accessibility wiring and visual state only**
624
- > (`htmlFor`, `aria-describedby`, `data-invalid` / `data-dirty` / `data-touched`); they do **not** own
625
- > your values. React Hook Form is **optional** and consumer-installed — reach for it only when a form
626
- > genuinely needs cross-field validation, field arrays, or a Zod resolver. The two compose; they are
627
- > not alternatives. `react-hook-form` stopped being a runtime dependency in 1.4.0; it is a dev-only
628
- > dependency of the package today, used to test the RHF integration contracts — if you use it,
629
- > install it in your own app.
630
-
631
- ### `Form`
632
-
633
- > Full API: [https://raw.githubusercontent.com/tailor-platform/app-shell/refs/heads/main/docs/components/form.md](https://raw.githubusercontent.com/tailor-platform/app-shell/refs/heads/main/docs/components/form.md)
634
-
635
- **Import:** `import { Form } from '@tailor-platform/app-shell'`
636
- **Purpose:** Form root that wraps Base UI's form primitive. Provides shared validation context for
637
- child `Field.Root`s, consolidated error display, and server-error routing by field `name`.
638
- **API:** `FormProps` — `onFormSubmit`, `onSubmit`, `errors`, `actionsRef`, `validationMode`,
639
- `noValidate`, `id`. Generic over `FormValues`.
640
- **Actions outside the form:** give the `Form` an `id` and point a detached submit button at it with
641
- the native `form` attribute. This is how a page-header Save reaches a body form (1.13.0+):
642
-
643
- ```tsx
644
- <Layout.Header
645
- title="Create product"
646
- actions={[<Button key="save" type="submit" form="product-form">Save</Button>]}
647
- />
648
- <Form id="product-form" onFormSubmit={save}>…</Form>
649
- ```
650
-
651
- **Submit:** use `onFormSubmit(values)` — it receives parsed form values and is the default. Use the
652
- low-level `onSubmit` **only** when handing the native event to React Hook Form's `handleSubmit`.
653
- **Never** hand-roll `<form onSubmit={e => new FormData(e.currentTarget)}>` — that skips validation
654
- and error routing.
655
- **Server errors:** pass `errors={{ fieldName: "message" }}`; they route to the matching
656
- `Field.Error` and clear when the user edits that field.
657
- **Example:** see `form/single-page.md`.
658
- **Used in patterns:** all `form/*`.
659
-
660
- ### `Field`
661
-
662
- **Import:** `import { Field } from '@tailor-platform/app-shell'`
663
- **Purpose:** Single form field with label + control + error message wiring.
664
- **API:** Compound — `Field.Root`, `Field.Label`, `Field.Control`, `Field.Description`, `Field.Error`,
665
- `Field.Validity`. `Field.Root` establishes a context boundary; every Base UI-backed AppShell control
666
- placed inside it inherits label association, `aria-describedby`, `disabled`, and invalid state
667
- automatically.
668
- **`Field.Control` is already a styled input** — write `<Field.Control />`, not
669
- `<Field.Control render={<Input />} />`. `Input` and `Field.Control` share the same base classes, so
670
- the `render` form just double-wraps. Reach for `Input` directly when you need a control outside a
671
- `Field.Root`, or when the value is React-controlled.
672
- **RHF interop (optional):** `Field.Root` accepts `isTouched`, `isDirty`, `invalid`, and `error`,
673
- matching RHF's `fieldState` shape — so `<Field.Root {...fieldState}>` works with no adapter.
674
- **Used in patterns:** all `form/*`.
675
-
676
- ### How values reach your submit handler
677
-
678
- There are three separate mechanisms. Picking the wrong mental model is the most common
679
- form mistake, so be explicit about which one a screen uses.
680
-
681
- **1. `Form` + `Field.Root` → `onFormSubmit` (the default).** `onFormSubmit` does **not** read
682
- `FormData`; it collects values from the `Field.Root`s registered inside the `Form`, keyed by each
683
- field's `name`. Every AppShell control works this way once wrapped in a `Field.Root name="…"` —
684
- including `Select`, `Combobox`, and `Autocomplete`. They need **no `name` of their own and no React
685
- state**:
686
-
687
- ```tsx
688
- <Form onFormSubmit={(values) => save(values)}>
689
- <Field.Root name="category">
690
- <Field.Label>Category</Field.Label>
691
- <Select items={CATEGORIES} placeholder="Select category" />
692
- </Field.Root>
693
- </Form>
694
- ```
695
-
696
- **2. Native submission** — a plain `<form>`, `new FormData(form)`, or a server action. This reads the
697
- DOM, so each control needs its own `name`. `Select`, `Combobox`, and `Autocomplete` gained `name`
698
- (plus `form`, `required`, `inputRef`) in 1.13.0; before that they contributed nothing to a native
699
- payload.
700
-
701
- **3. React Hook Form** — optional, consumer-installed. RHF owns the values; drive the control with
702
- `value` / `onValueChange` from a `Controller` and spread `fieldState` onto `Field.Root`. Warranted
703
- for cross-field validation, field arrays, or a Zod resolver — not for ordinary CRUD forms.
704
-
705
- #### Object items
706
-
707
- Under mechanism 1, a non-string item is serialised into the submitted value:
708
-
709
- | Item shape | Value in `onFormSubmit` |
710
- | -------------------------------------- | ------------------------------------ |
711
- | `string` | the string |
712
- | `{ value, label }` | `value`, automatically |
713
- | any other object | **JSON string** — usually not wanted |
714
- | any other object + `itemToStringValue` | whatever that function returns |
715
-
716
- So for arbitrary objects, pass `itemToStringValue` to choose the submitted key. It is distinct from
717
- `mapItem`, which controls what the user sees:
718
-
719
- ```tsx
720
- <Combobox
721
- items={warehouses}
722
- mapItem={(w) => ({ label: w.name, key: String(w.id) })}
723
- itemToStringValue={(w) => String(w.id)}
724
- />
725
- ```
726
-
727
- Multi-select submits an array.
728
-
729
- ### `Fieldset`
730
-
731
- **Import:** `import { Fieldset } from '@tailor-platform/app-shell'`
732
- **Purpose:** Group related fields under a legend (subtle section header).
733
- **API:** Compound — `Fieldset.Root`, `Fieldset.Legend`.
734
- **Used in patterns:** `form/sectioned`, `form/wizard`.
735
-
736
- ### `Input`
737
-
738
- **Import:** `import { Input } from '@tailor-platform/app-shell'`
739
- **Purpose:** Text input. Maps to spec field types: `string`, `email`, `number`, `password`, `tel`, `url`.
740
- **API:** `InputProps` — extends native `<input>` props.
741
- **Used in patterns:** all `form/*`.
742
-
743
- ### `Textarea`
744
-
745
- > Full API: [https://raw.githubusercontent.com/tailor-platform/app-shell/refs/heads/main/docs/components/textarea.md](https://raw.githubusercontent.com/tailor-platform/app-shell/refs/heads/main/docs/components/textarea.md)
746
-
747
- **Import:** `import { Textarea } from '@tailor-platform/app-shell'`
748
- **Purpose:** Multi-line text input. Maps to spec field types: `text`, long-form `string`. Use instead of `Input` whenever the value is prose (notes, descriptions, replies, reasons).
749
- **API:** `TextareaProps` — Base UI's field control (`value`, `defaultValue`, `onChange`, `onValueChange`, `disabled`, `required`, `readOnly`, `name`, aria-\*) rendered as a `<textarea>`, plus `rows`. `cols` / `wrap` are not accepted — the control is `w-full`, so neither can take effect.
750
- **Sizing:** no fixed height — `rows` sets the visible line count and the user can drag it taller. `min-h-16` is a hard floor, so `rows` only takes effect from 3 up. Width is the parent's job: the control is `w-full`, and `cols` is not accepted because it could never win against that. Do **not** reach for `Input` and a height override; `Input` is locked to `h-9`, and an `astw:h-*` override from a consuming app resolves to nothing unless that exact utility was compiled into the package CSS.
751
- **Field integration:** drops straight into `Field.Root` for label/description/error wiring exactly like `Input` and `Checkbox` — no bespoke `error` prop.
752
- **Example:**
753
-
754
- ```tsx
755
- <Field.Root name="reason">
756
- <Field.Label>Reason</Field.Label>
757
- <Textarea rows={4} placeholder="Why is this being rejected?" />
758
- </Field.Root>
759
- ```
760
-
761
- **Used in patterns:** `form/composer`, `form/*` (long-text fields).
762
-
763
- ### `Checkbox`
764
-
765
- **Import:** `import { Checkbox } from '@tailor-platform/app-shell'`
766
- **Purpose:** Boolean form control. Maps to spec field type: `boolean`. Also used for row selection and filter toggles.
767
- **API:** `CheckboxProps` — extends Base UI `Checkbox.Root` (`checked`/`defaultChecked`, `onCheckedChange`, `indeterminate`, `disabled`, `required`, `name`, `inputRef`) and adds an inline `label`. Integrates with `Field` for invalid/disabled/label state exactly like `Input`/`Select` — no bespoke `error` prop.
768
- **Accessible name:** the inline `label` prop (enclosing) or a sibling `Field.Label`; for a bare box (e.g. a table row-select cell) pass `aria-label`.
769
- **Used in patterns:** `form/*` (boolean fields), `list/*` (row selection), DataTable filter/column controls.
770
-
771
- ### `Select`
772
-
773
- **Import:** `import { Select } from '@tailor-platform/app-shell'`
774
- **Purpose:** Dropdown for fixed enumerations.
775
- **API:** Compound. Sync version (small option lists). For async/typeahead, see `Combobox` and `Autocomplete`. Type: `SelectAsyncFetcher<T>` for the async variant.
776
- **Accessible name:** inside a `Form` the field label provides it automatically. When used **standalone** (outside a `Form`), pass `aria-label` (or `aria-labelledby` / `id`) — these now forward to the underlying combobox element, so it isn't left named only by its current value. Same applies to `Combobox` and `Autocomplete`.
777
- **Used in patterns:** all `form/*` (enum fields).
778
-
779
- ### `Combobox`
780
-
781
- **Import:** `import { Combobox } from '@tailor-platform/app-shell'`
782
- **Purpose:** Single-select with search, supports async data fetching.
783
- **API:** Compound. `ComboboxAsyncFetcher<T>` for paginated/queried option sources (e.g. lookups).
784
- **Used in patterns:** `form/*` (foreign-key lookups).
785
-
786
- ### `Autocomplete`
787
-
788
- **Import:** `import { Autocomplete } from '@tailor-platform/app-shell'`
789
- **Purpose:** Free-text input with completion suggestions (multi-select capable).
790
- **API:** Compound. `AutocompleteAsyncFetcher<T>` for async suggestion sources. `ItemGroup<T>` for grouped suggestions.
791
- **Used in patterns:** `form/*` (tags, free-text + suggested values).
792
-
793
- ---
794
-
795
- ## Auth & access
796
-
797
- ### `AuthProvider`
798
-
799
- **Import:** `import { AuthProvider } from '@tailor-platform/app-shell'`
800
- **Purpose:** Wraps the app to expose auth state via `useAuth`. Mount in `App.tsx`.
801
- **API:** `AuthProviderProps` — `client` (from `createAuthClient`), `autoLogin`, `guardComponent`.
802
- **Used in patterns:** project-level. See `project-setup.md`.
803
-
804
- ### `createAuthClient`
805
-
806
- **Import:** `import { createAuthClient, type EnhancedAuthClient } from '@tailor-platform/app-shell'`
807
- **Purpose:** Factory for the auth client, configured with platform URL + client ID.
808
- **API:** `createAuthClient(config: AuthClientConfig): EnhancedAuthClient`.
809
- **Used in patterns:** project-level.
810
-
811
- ### `useAuth`, `useAuthSuspense`
812
-
813
- **Import:** `import { useAuth, useAuthSuspense } from '@tailor-platform/app-shell'`
814
- **Purpose:** Read auth state in components. `useAuth` returns nullable state; `useAuthSuspense` suspends until ready (use inside route loaders / suspense boundaries).
815
- **API:** Returns `AuthState` (user, status, login/logout actions).
816
- **Used in patterns:** any pattern needing user identity.
817
-
818
- ### `AuthClient` (type)
819
-
820
- **Import:** `import { type AuthClient } from '@tailor-platform/app-shell'`
821
- **Purpose:** Public auth client interface (re-exported from `@tailor-platform/auth-public-client`).
822
-
823
- ### `WithGuard`
824
-
825
- **Import:** `import { WithGuard } from '@tailor-platform/app-shell'`
826
- **Purpose:** Permission gate around a UI subtree. Hides, denies, or shows a loading state based on the guard result.
827
- **API:** `WithGuardProps` — `guard: Guard`, `fallback`, `mode: 'hidden' | 'deny'`.
828
- **Used in patterns:** `detail/*` (action gating), `list/*` (row-level action visibility).
829
-
830
- ### Guard helpers — `pass`, `hidden`, `redirectTo`
831
-
832
- **Import:** `import { pass, hidden, redirectTo } from '@tailor-platform/app-shell'`
833
- **Purpose:** Return values for guard functions. `pass()` allows, `hidden()` hides, `redirectTo(path)` navigates.
834
- **Used in patterns:** any `Guard` definition.
835
-
836
- ### `Guard`, `GuardContext`, `GuardResult` (types)
837
-
838
- Types for authoring guard functions used by `WithGuard` and `appShellPageProps.guards`.
839
-
840
- ---
841
-
842
- ## Routing helpers
843
-
844
- ### `useNavigate`, `useParams`, `useSearchParams`, `useLocation`, `useRouteError`
845
-
846
- **Import:** `import { useNavigate, useParams, useSearchParams, useLocation, useRouteError } from '@tailor-platform/app-shell'`
847
- **Purpose:** Re-exported from `react-router`. Use the AppShell barrel — never import from `react-router` directly.
848
- **Used in patterns:** all (navigation, route params, query state).
849
-
850
- ### `useMatch`, `useResolvedPath`, `useNavigation`
851
-
852
- **Import:** `import { useMatch, useResolvedPath, useNavigation } from '@tailor-platform/app-shell'`
853
- **Purpose:** `useMatch` tests a pattern against the current location (active states); `useResolvedPath` resolves a relative path; `useNavigation` exposes the in-flight navigation for pending UI.
854
- **Used in patterns:** navigation shells, list→detail transitions.
855
-
856
- ### `useBlocker`, `useBeforeUnload`
857
-
858
- **Import:** `import { useBlocker, useBeforeUnload } from '@tailor-platform/app-shell'`
859
- **Purpose:** Guard navigation away from unsaved work — `useBlocker` for in-app navigation, `useBeforeUnload` for closing or reloading the tab.
860
- **Used in patterns:** form-heavy screens.
861
-
862
- ### Types
863
-
864
- **Import:** `import type { Location, NavigateFunction, NavigateOptions, To, Params, PathMatch, LinkProps, NavLinkProps, Navigation, Blocker, BlockerFunction } from '@tailor-platform/app-shell'`
865
- **Purpose:** The React Router types that go with the hooks above, so app code never imports types from `react-router` either.
866
-
867
- ### Not exported
868
-
869
- `createBrowserRouter`, `RouterProvider`, `MemoryRouter`, `Routes`, `Route` — AppShell builds the router; a second one nested inside it breaks navigation. `useLoaderData`, `Form`, `useSubmit`, `useFetcher`, `useActionData` — AppShell does not wire up React Router's data layer. Load data in the page component or the app's data layer.
870
-
871
- ### Testing
872
-
873
- **Import:** `import { AppShell, TestRouter } from '@tailor-platform/app-shell/testing'`
874
- **Purpose:** Test routing without a `react-router` dependency. `AppShell` from this entry also accepts `memory` / `initialEntries`, for mounting a page at a fixed URL; `TestRouter` gives a single component router context, and takes `path` when that component reads the route (`useParams` is `{}` without it).
875
-
876
- ### `createTypedPaths`
877
-
878
- **Import:** `import { createTypedPaths } from '@tailor-platform/app-shell'`
879
- **Purpose:** Type-safe path builder generated from the route tree.
880
- **API:** `createTypedPaths<TRoutes>()` returns a `paths.for(...)` helper.
881
- **Used in patterns:** project-level. See `project-setup.md`.
882
-
883
- ### `RouteParams`, `PageComponent`, `PageMeta`, `AppShellPageProps`, `AppShellRegister`, `ContextData` (types)
884
-
885
- Types for declaring page props (`appShellPageProps = { meta, guards }`), reading typed route params, and registering route types globally. See `project-setup.md` for usage.
886
-
887
- ---
888
-
889
- ## Hooks
890
-
891
- ### `useToast`
892
-
893
- **Import:** `import { useToast } from '@tailor-platform/app-shell'`
894
- **Purpose:** Imperative toast feedback after mutations.
895
- **API:** Returns the `sonner` `toast` function — `toast.success(message)`, `toast.error(message)`, `toast.loading(message)`, plus options for duration, action button, etc.
896
- **Used in patterns:** `interaction/toast`. Called from any mutation handler.
897
-
898
- ### `useTheme`
899
-
900
- **Import:** `import { useTheme } from '@tailor-platform/app-shell'`
901
- **Purpose:** Read/write the active theme (light/dark/system).
902
- **API:** Returns `ThemeProviderState`.
903
- **Used in patterns:** any theme-toggle UI.
904
-
905
- ### `useAppShell`, `useAppShellConfig`, `useAppShellData`
906
-
907
- **Import:** `import { useAppShell, useAppShellConfig, useAppShellData } from '@tailor-platform/app-shell'`
908
- **Purpose:** Access AppShell context — the resolved `AppShellRegister` data, config, and runtime state.
909
- **Used in patterns:** advanced — when a component needs to read app-wide config or registered resources.
910
-
911
- ### `usePageMeta`, `useOverrideBreadcrumb`
912
-
913
- **Import:** `import { usePageMeta, useOverrideBreadcrumb } from '@tailor-platform/app-shell'`
914
- **Purpose:** Read the current page's `meta` (resolved from `appShellPageProps.meta`) or override the breadcrumb title at runtime (e.g. show the order number once data loads).
915
- **Used in patterns:** `detail/*` (dynamic breadcrumbs from loaded entity).
916
-
917
- ---
918
-
919
- ## i18n & resource definitions
920
-
921
- ### `defineI18nLabels`
922
-
923
- **Import:** `import { defineI18nLabels } from '@tailor-platform/app-shell'`
924
- **Purpose:** Declare i18n labels in a typed way. Returns `I18nLabels<Def, L>`.
925
- **Used in patterns:** project-level for multi-locale apps.
926
-
927
- ### `defineModule`, `defineResource`
928
-
929
- **Import:** `import { defineModule, defineResource } from '@tailor-platform/app-shell'`
930
- **Purpose:** Register an app module / a resource (entity with CRUD pages). Wires routes, sidebar, and command palette automatically.
931
- **API:** `defineModule(props: DefineModuleProps): Module`, `defineResource(props: DefineResourceProps): Resource`.
932
- **Used in patterns:** project-level. See `project-setup.md`.
933
-
934
- ### `MappedItem`, `Module`, `Resource`, `ResourceComponentProps` (types)
935
-
936
- Types returned/consumed by the define-\* helpers above.
937
-
938
- ---
939
-
940
- ## Style helpers
941
-
942
- ### `avatarVariants`, `badgeVariants`, `buttonVariants`
943
-
944
- **Import:** `import { avatarVariants, badgeVariants, buttonVariants } from '@tailor-platform/app-shell'`
945
- **Purpose:** CVA (`class-variance-authority`) variant functions backing `Avatar`, `Badge`, `Button`. Use when authoring a custom component that should match an AppShell variant inline.
946
- **Used in patterns:** custom-component fallbacks (see `design-system.md` § "When AppShell doesn't have a component you need").
947
-
948
- ### `ErrorBoundaryComponent` (type)
949
-
950
- **Import:** `import { type ErrorBoundaryComponent } from '@tailor-platform/app-shell'`
951
- **Purpose:** Type for an error boundary component slot in routing.
952
-
953
- ---
954
-
955
- ## Keeping this file in sync
956
-
957
- When `@tailor-platform/app-shell` publishes a new version:
958
-
959
- 1. Bump `package.json` in the scaffold templates (`templates/scaffold/app/*/frontend/`) to the new version.
960
- 2. Update the AppShell version header at the top of this file.
961
- 3. Diff exports between old and new:
962
-
963
- ```bash
964
- # Run in a scratch dir:
965
- mkdir -p /tmp/appshell-diff && cd /tmp/appshell-diff
966
- npm init -y >/dev/null && npm install --no-save --silent @tailor-platform/app-shell@<NEW_VERSION>
967
- node -e "console.log(Object.keys(require('@tailor-platform/app-shell')).sort().join('\n'))" \
968
- > new-exports.txt
969
- # Compare against the headings in this file:
970
- grep -E '^### ' \
971
- packages/erp-kit/skills/erp-kit-app-6-impl-frontend/references/components.md \
972
- | sed 's/^### //; s/ ,.*//' | sort > current-headings.txt
973
- diff current-headings.txt new-exports.txt
974
- ```
975
-
976
- 4. For each new export — add a section using the fixed shape above.
977
- 5. For each removed export — delete its section, then grep `references/patterns/` for the component name and update or retire the patterns that cited it.
978
- 6. For each changed export (new prop, behavior change) — update the section and add a one-line note under "Notes:".
979
- 7. Run `pnpm erp-kit update skills` to regenerate `.agents/skills/`.
980
-
981
- The pattern-citation review check (`erp-kit-app-7-impl-review` → `design-parity.md`) catches drift indirectly: if a pattern lists a component that no longer exists, generated pages will fail the review.