@tailor-platform/app-shell 1.19.0 → 1.20.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 (46) hide show
  1. package/dist/app-shell-core.css +2 -2
  2. package/dist/app-shell.d.ts +807 -130
  3. package/dist/app-shell.js +12419 -10930
  4. package/dist/{appshell-DQTz-QwK.js → appshell-B4QkQvXF.js} +377 -360
  5. package/dist/fonts/inter.css +7 -7
  6. package/dist/fonts/noto-sans-jp.css +116 -116
  7. package/dist/testing.d.ts +119 -89
  8. package/dist/testing.js +1 -1
  9. package/dist/vite-plugin.d.ts +119 -89
  10. package/package.json +10 -8
  11. package/skills/app-shell-patterns/SKILL.md +4 -3
  12. package/skills/app-shell-patterns/references/api/create-ai-gateway-client.md +32 -2
  13. package/skills/app-shell-patterns/references/api/define-module.md +5 -5
  14. package/skills/app-shell-patterns/references/api/router/use-params.md +2 -2
  15. package/skills/app-shell-patterns/references/api/use-ai-chat.md +46 -8
  16. package/skills/app-shell-patterns/references/api/use-app-shell-config.md +4 -4
  17. package/skills/app-shell-patterns/references/api/use-app-shell-data.md +5 -5
  18. package/skills/app-shell-patterns/references/api/use-app-shell.md +2 -2
  19. package/skills/app-shell-patterns/references/api/use-auth.md +3 -3
  20. package/skills/app-shell-patterns/references/api/use-route-error.md +12 -12
  21. package/skills/app-shell-patterns/references/components/action-panel.md +4 -2
  22. package/skills/app-shell-patterns/references/components/activity-card.md +3 -3
  23. package/skills/app-shell-patterns/references/components/ai-chat.md +37 -3
  24. package/skills/app-shell-patterns/references/components/app-shell.md +6 -5
  25. package/skills/app-shell-patterns/references/components/card.md +2 -2
  26. package/skills/app-shell-patterns/references/components/data-table.md +87 -31
  27. package/skills/app-shell-patterns/references/components/date-picker.md +2 -2
  28. package/skills/app-shell-patterns/references/components/default-sidebar.md +2 -2
  29. package/skills/app-shell-patterns/references/components/dialog.md +8 -0
  30. package/skills/app-shell-patterns/references/components/filter-rail.md +10 -3
  31. package/skills/app-shell-patterns/references/components/form.md +5 -5
  32. package/skills/app-shell-patterns/references/components/global-header-layout.md +31 -8
  33. package/skills/app-shell-patterns/references/components/layout.md +24 -17
  34. package/skills/app-shell-patterns/references/components/select.md +8 -0
  35. package/skills/app-shell-patterns/references/components/sidebar-group.md +9 -7
  36. package/skills/app-shell-patterns/references/components/sidebar-layout.md +74 -11
  37. package/skills/app-shell-patterns/references/components/sidebar-section.md +8 -8
  38. package/skills/app-shell-patterns/references/components/tabs.md +32 -2
  39. package/skills/app-shell-patterns/references/components/tree.md +342 -0
  40. package/skills/app-shell-patterns/references/components/with-guard.md +5 -5
  41. package/skills/app-shell-patterns/references/concepts/authentication.md +1 -1
  42. package/skills/app-shell-patterns/references/concepts/routing-navigation.md +3 -3
  43. package/skills/app-shell-patterns/references/concepts/styling-theming.md +13 -13
  44. package/skills/app-shell-patterns/references/migrations.md +20 -0
  45. package/skills/app-shell-patterns/references/pages/document-detail.md +8 -12
  46. package/skills/app-shell-patterns/references/patterns/interaction-multi-select.md +1 -1
@@ -47,6 +47,7 @@ import {
47
47
  import { gql, useQuery } from "urql";
48
48
  import {
49
49
  DataTable,
50
+ Toolbar,
50
51
  useDataTable,
51
52
  useCollectionVariables,
52
53
  createColumnHelper,
@@ -139,9 +140,16 @@ function JournalsPage() {
139
140
 
140
141
  return (
141
142
  <DataTable.Root value={table}>
142
- <DataTable.Toolbar>
143
- <DataTable.Filters />
144
- </DataTable.Toolbar>
143
+ <Toolbar.Root>
144
+ <Toolbar.Row justify="between" aria-label="Journal table controls">
145
+ <Toolbar.Group>
146
+ <DataTable.Filters />
147
+ </Toolbar.Group>
148
+ <Toolbar.Group>
149
+ <DataTable.ColumnSettings />
150
+ </Toolbar.Group>
151
+ </Toolbar.Row>
152
+ </Toolbar.Root>
145
153
  <DataTable.Table />
146
154
  <DataTable.Footer>
147
155
  <DataTable.Pagination pageSizeOptions={[10, 20, 50]} />
@@ -333,7 +341,7 @@ function PerCellStyling() {
333
341
 
334
342
  return (
335
343
  <div className="space-y-3">
336
- <p>Overtime: below 40 hours is safe, 40–59 is caution, 60 or more is danger.</p>
344
+ <Text.p>Overtime: below 40 hours is safe, 40–59 is caution, 60 or more is danger.</Text.p>
337
345
  <DataTable.Root value={table}>
338
346
  <DataTable.Table />
339
347
  </DataTable.Root>
@@ -702,6 +710,50 @@ The trigger is a native `<button>`, so Enter/Space activation and the focus ring
702
710
 
703
711
  **Known limitation — row count.** Detail rows are real `<tr>` elements, so a screen reader counts them: ten records with two expanded announces as twelve rows. Fixing this needs `aria-rowcount` plus explicit `aria-rowindex` on every row (with detail rows sharing their parent's index) and correct interaction with pagination; `role="presentation"` on the detail row would fix the count but remove the panel from screen-reader table navigation. Neither is implemented.
704
712
 
713
+ ## Sub-rows
714
+
715
+ Pass `subRows` to show nested rows under each row, like a chart of accounts or a bill of materials. The page's rows are the top level: pagination counts them, and each parent's sub-rows stay on its page, so a family is never split across pages.
716
+
717
+ ```tsx
718
+ const table = useDataTable<Account>({
719
+ columns, // the other columns: code, balance…
720
+ data, // the page's top-level accounts
721
+ subRows: {
722
+ getSubRows: (account) => account.children,
723
+ hierarchyColumn: {
724
+ label: "Account",
725
+ accessor: (account) => account.name,
726
+ width: 280,
727
+ truncate: true,
728
+ },
729
+ },
730
+ rowSelection: { actions: [exportAccounts] },
731
+ });
732
+ ```
733
+
734
+ - **`subRows` brings its own column.** `hierarchyColumn` defines the column that carries the indent, the open / close caret, the checkbox (except under `"independent"`) and the row's label. Like the row-actions column, the feature owns its place: it's always first and pinned left, it isn't in column settings or saved layout, and it leaves your other columns exactly as you defined them.
735
+ - **It's a column for display.** It takes `label` and `accessor` (both required), `render`, `type` and `typeOptions`, `width` and `truncate`, `header` and `footer`, and `headerProps`, `footerProps` and `cellProps`, typed as on any column. `truncate` cuts the label only, never the indent or caret. Its `footer` gets the page's top-level rows. It has no `id`, `pin`, `align`, `edit`, `sort` or `filter`.
736
+ - **Rows need an `id`, unique across the tree.** Expansion and selection are keyed by it. A row without one can't open or be selected.
737
+ - **Sub-rows come with the data.** `getSubRows` reads them synchronously from each row. Loading children on demand isn't supported yet.
738
+ - **Not combined with `rowExpansion`.** A row shows either its sub-rows or a detail panel, so passing both is a type error. Untyped callers that pass both get `subRows` and a console warning.
739
+ - **Open rows** can be controlled with `expandedIds` + `onExpandedChange`, or started with `defaultExpandedIds`. `useDataTable` returns `expandedSubRowIds` and `toggleSubRows(row)`.
740
+
741
+ The caret is a native `<button>` with `aria-expanded`, named from the hierarchy column's `accessor` ("Expand row Assets"), so it's reached with Tab and worked with Enter / Space. Full tree-grid arrow-key navigation isn't implemented yet.
742
+
743
+ ### Selecting sub-rows
744
+
745
+ `rowSelection` takes the same `selectionStrategy` as [`Tree`](./tree.md), and works the same way, so a tree and a tree-shaped table behave alike. Take a parent row P with two rows beneath it, A and B:
746
+
747
+ | `selectionStrategy` | Tick P | Tick A and B | P ticked on its own? | Checkboxes sit |
748
+ | -------------------- | ----------- | ------------ | -------------------- | ---------------------------------------------- |
749
+ | `"leaves"` (default) | `[A, B]` | `[A, B]` | No | In the hierarchy column, indented with the row |
750
+ | `"topmost"` | `[P]` | `[P]` | No | In the hierarchy column, indented with the row |
751
+ | `"all"` | `[P, A, B]` | `[P, A, B]` | No | In the hierarchy column, indented with the row |
752
+ | `"cascade"` | `[P, A, B]` | `[A, B]` | Yes | In the hierarchy column, indented with the row |
753
+ | `"independent"` | `[P]` | `[A, B]` | Yes | In the usual left-hand selection column |
754
+
755
+ With `"leaves"`, `"topmost"` and `"all"`, a parent is a container: its checkbox comes from the rows beneath it. With `"cascade"`, ticking a parent ticks its rows too, but unticking one leaves the parent ticked. Ids from other pages pass through untouched, and the header checkbox ticks the current page's rows, sitting in the hierarchy column's header when the checkboxes do. Selection actions receive the ids `selectedIds` lists.
756
+
705
757
  ## Inline editing
706
758
 
707
759
  Give a typed column an `edit` config to use its built-in editor. This standardizes the editing UI — draft input, IME, keyboard movement, confirmation, and callback-error presentation. Validation rules and persistence remain consumer-owned; by default, the editor retains a draft until its callbacks complete successfully. Columns without `edit` keep their existing behavior.
@@ -812,8 +864,8 @@ To customize the body, provide `error.render`. Its `error` is the original throw
812
864
  error: {
813
865
  render: ({ row, error }) => (
814
866
  <div>
815
- <p>Could not update row {row.id}</p>
816
- <p>{error instanceof CellEditError ? error.message : "Please try again later"}</p>
867
+ <Text.p>Could not update row {row.id}</Text.p>
868
+ <Text.p>{error instanceof CellEditError ? error.message : "Please try again later"}</Text.p>
817
869
  </div>
818
870
  ),
819
871
  }
@@ -944,10 +996,10 @@ function ImmediateSaving() {
944
996
 
945
997
  return (
946
998
  <div className="space-y-3">
947
- <p role="status">
999
+ <Text.p role="status">
948
1000
  {savingCount ? `${savingCount} field save(s) pending` : "No saves pending"}
949
- </p>
950
- {error && <p role="alert">{error}</p>}
1001
+ </Text.p>
1002
+ {error && <Text.p role="alert">{error}</Text.p>}
951
1003
  <DataTable.Root value={table}>
952
1004
  <DataTable.Table />
953
1005
  </DataTable.Root>
@@ -1027,8 +1079,10 @@ function CollectThenSubmit() {
1027
1079
 
1028
1080
  return (
1029
1081
  <div className="space-y-3">
1030
- <p role="status">{saving ? "Saving batch…" : "Confirm edits, then Save or Cancel"}</p>
1031
- {error && <p role="alert">{error}</p>}
1082
+ <Text.p role="status">
1083
+ {saving ? "Saving batch…" : "Confirm edits, then Save or Cancel"}
1084
+ </Text.p>
1085
+ {error && <Text.p role="alert">{error}</Text.p>}
1032
1086
  <DataTable.Root value={table}>
1033
1087
  <DataTable.Table />
1034
1088
  </DataTable.Root>
@@ -1058,12 +1112,13 @@ Pass `rowSelection` to `useDataTable` to enable the checkbox column. Rows must h
1058
1112
 
1059
1113
  ### `RowSelectionOptions`
1060
1114
 
1061
- | Property | Type | Description |
1062
- | -------------------- | ------------------------- | -------------------------------------------------------------------------------------------------------------- |
1063
- | `selectedIds` | `string[]` | Controlled selected ids. Requires `onChange` and cannot be combined with `defaultSelectedIds`. |
1064
- | `defaultSelectedIds` | `string[]` | Initial selected ids in uncontrolled mode. Later changes are ignored. |
1065
- | `onChange` | `(ids: string[]) => void` | Notifies user-driven selection changes; required in controlled mode and optional in uncontrolled mode. |
1066
- | `actions` | `SelectionAction[]` | Optional bulk actions shown in `DataTable.Footer` while ids are selected. Omitted or empty arrays show no bar. |
1115
+ | Property | Type | Description |
1116
+ | -------------------- | -------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------ |
1117
+ | `selectedIds` | `string[]` | Controlled selected ids. Requires `onChange` and cannot be combined with `defaultSelectedIds`. |
1118
+ | `defaultSelectedIds` | `string[]` | Initial selected ids in uncontrolled mode. Later changes are ignored. |
1119
+ | `onChange` | `(ids: string[]) => void` | Notifies user-driven selection changes; required in controlled mode and optional in uncontrolled mode. |
1120
+ | `actions` | `SelectionAction[]` | Optional bulk actions shown in `DataTable.Footer` while ids are selected. Omitted or empty arrays show no bar. |
1121
+ | `selectionStrategy` | `"leaves" \| "topmost" \| "all" \| "cascade" \| "independent"` | With `subRows`: how ticking spreads and what `selectedIds` lists. Default `"leaves"`. See [Selecting sub-rows](#selecting-sub-rows). |
1067
1122
 
1068
1123
  ### Controlled selection
1069
1124
 
@@ -1177,20 +1232,21 @@ const table = useDataTable({
1177
1232
 
1178
1233
  ### Options
1179
1234
 
1180
- | Option | Type | Description |
1181
- | ------------------- | ---------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
1182
- | `columns` | `Column<TRow>[]` | Column definitions. Required. |
1183
- | `data` | `DataTableData<TRow> \| undefined` | Fetched data. Pass `undefined` while loading. |
1184
- | `loading` | `boolean` | When `true`, renders a loading skeleton. |
1185
- | `error` | `Error \| null` | When set, renders an error message in the table body. |
1186
- | `control` | `CollectionControl` | Collection control from `useCollectionVariables()`. Required for `DataTable.Pagination` and `DataTable.Filters`. |
1187
- | `onClickRow` | `(row: TRow) => void` | Called when the user clicks a row. Adds a pointer cursor to rows. |
1188
- | `tableId` | `string` | Stable id used to persist per-user column layout (visibility, order, pinning) to `localStorage`. When omitted, column layout is in-memory only and resets on reload. |
1189
- | `rowActions` | `RowAction<TRow>[]` | Per-row action items rendered in a kebab-menu column. The column is omitted when empty or not provided. |
1190
- | `rowSelection` | `RowSelectionOptions` | Controlled `selectedIds` + `onChange`, or uncontrolled `defaultSelectedIds` + optional `onChange`, with optional `actions`. Providing even `{}` enables checkboxes. See [Row selection](#row-selection). |
1191
- | `onSelectionChange` | `(ids: string[]) => void` | **Deprecated:** use `rowSelection.onChange`. Still supported for compatibility; enables checkboxes and is used when the new callback is absent. |
1192
- | `rowExpansion` | `RowExpansionOptions<TRow>` | Expandable detail rows: `render`, plus optional `canExpand` / `getLabel`, and `expandedIds` + `onChange` together for controlled mode. See [Expandable rows](#expandable-rows). |
1193
- | `sort` | `false \| { multiple?: boolean }` | Sort behaviour. `false` disables sorting entirely. `{ multiple: true }` enables multi-column sorting. Omit or pass `{}` for single-column sort (default). |
1235
+ | Option | Type | Description |
1236
+ | ------------------- | ---------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
1237
+ | `columns` | `Column<TRow>[]` | Column definitions. Required. |
1238
+ | `data` | `DataTableData<TRow> \| undefined` | Fetched data. Pass `undefined` while loading. |
1239
+ | `loading` | `boolean` | When `true`, renders a loading skeleton. |
1240
+ | `error` | `Error \| null` | When set, renders an error message in the table body. |
1241
+ | `control` | `CollectionControl` | Collection control from `useCollectionVariables()`. Required for `DataTable.Pagination` and `DataTable.Filters`. |
1242
+ | `onClickRow` | `(row: TRow) => void` | Called when the user clicks a row. Adds a pointer cursor to rows. |
1243
+ | `tableId` | `string` | Stable id used to persist per-user column layout (visibility, order, pinning) to `localStorage`. When omitted, column layout is in-memory only and resets on reload. |
1244
+ | `rowActions` | `RowAction<TRow>[]` | Per-row action items rendered in a kebab-menu column. The column is omitted when empty or not provided. |
1245
+ | `rowSelection` | `RowSelectionOptions` | Controlled `selectedIds` + `onChange`, or uncontrolled `defaultSelectedIds` + optional `onChange`, with optional `actions`. Providing even `{}` enables checkboxes. See [Row selection](#row-selection). |
1246
+ | `onSelectionChange` | `(ids: string[]) => void` | **Deprecated:** use `rowSelection.onChange`. Still supported for compatibility; enables checkboxes and is used when the new callback is absent. |
1247
+ | `rowExpansion` | `RowExpansionOptions<TRow>` | Expandable detail rows: `render`, plus optional `canExpand` / `getLabel`, and `expandedIds` + `onChange` together for controlled mode. See [Expandable rows](#expandable-rows). |
1248
+ | `subRows` | `SubRowsOptions<TRow>` | Nested rows: `getSubRows`, `hierarchyColumn` (a `HierarchyColumn<TRow>`), and `expandedIds` + `onExpandedChange` or `defaultExpandedIds`. Can't be combined with `rowExpansion`. See [Sub-rows](#sub-rows). |
1249
+ | `sort` | `false \| { multiple?: boolean }` | Sort behaviour. `false` disables sorting entirely. `{ multiple: true }` enables multi-column sorting. Omit or pass `{}` for single-column sort (default). |
1194
1250
 
1195
1251
  ### `DataTableData`
1196
1252
 
@@ -56,7 +56,7 @@ With a visible label + description:
56
56
  aria-labelledby="invoice-date-label"
57
57
  aria-describedby="invoice-date-help"
58
58
  />
59
- <p id="invoice-date-help">Format follows your locale</p>
59
+ <Text.p id="invoice-date-help">Format follows your locale</Text.p>
60
60
  ```
61
61
 
62
62
  Inside `Field.Root`:
@@ -124,7 +124,7 @@ Use standard HTML + ARIA when rendering the field standalone:
124
124
  value={value}
125
125
  onChange={setValue}
126
126
  />
127
- {error && <p id="delivery-date-error">{error}</p>}
127
+ {error && <Text.p id="delivery-date-error">{error}</Text.p>}
128
128
  ```
129
129
 
130
130
  Or let `Field.Root` wire the label, description, and error elements:
@@ -75,7 +75,7 @@ With `iconRail`, collapsing the sidebar keeps icons visible at every width:
75
75
 
76
76
  - on desktop, the sidebar collapses to a narrow persistent rail
77
77
  - on mobile, the rail stays visible and its toggle opens the full sidebar as a slide-in drawer
78
- - grouped items reveal child pages from the collapsed icon via a flyout popover
78
+ - grouped items reveal child pages from the collapsed icon via a flyout popover: it opens on hover with a mouse, and on tap on a touch device (tap again, tap outside, or press `Escape` to close it). A group with its own page links to it from the flyout heading
79
79
  - items without children show their label in a tooltip
80
80
 
81
81
  [`GlobalHeaderLayout`](./global-header-layout.md) bakes this mode in through `GlobalHeaderLayout.DefaultSidebar`.
@@ -94,7 +94,7 @@ When the app title and primary toggle live in a full-width top bar, hide the sid
94
94
  function UserProfile() {
95
95
  return (
96
96
  <div className="border-t p-4">
97
- <p className="text-sm font-medium">Ada Lovelace</p>
97
+ <Text.p variant="label-md">Ada Lovelace</Text.p>
98
98
  <button type="button" className="mt-2 text-sm text-muted-foreground">
99
99
  Logout
100
100
  </button>
@@ -68,6 +68,14 @@ function BasicUsage() {
68
68
 
69
69
  Accepts `className` and all standard HTML `<div>` props.
70
70
 
71
+ ## Tall Content
72
+
73
+ `Dialog.Content` is never taller than the viewport minus 2rem (16px above and below, the same margin as on the sides). When the content is taller, the whole dialog scrolls, including the title and the close button. `Escape` and a click on the backdrop still close it.
74
+
75
+ On open, focus moves to the first control in the dialog. In a dialog that scrolls, this happens only when that control is in view at the top. When it is below the fold (for example, a footer button after a long body), the dialog itself takes focus instead, so it opens at the top rather than scrolled to that control. `Tab` then moves to the control. Pass `initialFocus` to choose the element yourself.
76
+
77
+ To keep a header or footer in view while only the body scrolls, make `Dialog.Content` a flex column and give the body `min-h-0 flex-1 overflow-y-auto`. A `className` with its own `max-h-*` or `overflow-*` replaces the defaults.
78
+
71
79
  ### Dialog.Trigger / Dialog.Close Props
72
80
 
73
81
  Accept a `render` prop for custom element rendering (Base UI render pattern), plus standard button props.
@@ -154,9 +154,16 @@ By default the button appears where the rail would have been. To put it somewher
154
154
  </Layout.Column>
155
155
  <Layout.Column area="main">
156
156
  <DataTable.Root value={table}>
157
- <DataTable.Toolbar>
158
- <FilterRail.Trigger />
159
- </DataTable.Toolbar>
157
+ <Toolbar.Root>
158
+ <Toolbar.Row justify="between" aria-label="Table controls">
159
+ <Toolbar.Group>
160
+ <FilterRail.Trigger />
161
+ </Toolbar.Group>
162
+ <Toolbar.Group>
163
+ <DataTable.ColumnSettings />
164
+ </Toolbar.Group>
165
+ </Toolbar.Row>
166
+ </Toolbar.Root>
160
167
  <DataTable.Table />
161
168
  </DataTable.Root>
162
169
  </Layout.Column>
@@ -136,11 +136,11 @@ A compound component that groups all parts of a form field and manages its valid
136
136
 
137
137
  `Field.Control` is a text input. It is the control for plain text-like fields, and you can pass it a native element such as `render={<textarea />}`. The controls in the last row of the table below go **directly under `Field.Root`**, as a sibling of `Field.Label`. Each one registers itself with the `Field` context, so it gets label association, `aria-describedby`, `disabled`, and validation state without `Field.Control`.
138
138
 
139
- | Control | Where it goes |
140
- | ------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------- |
141
- | Plain text, email, number, password, … input | `<Field.Control type="…" />` |
142
- | Native element (`<textarea>`, `<input type="…">`) | `<Field.Control render={<textarea />} />` |
143
- | `Select`, `Combobox`, `Autocomplete`, `Checkbox`, `Textarea`, `DateField`, `DatePicker`, `DateRangePicker` (and their `.Async`) | Directly under `Field.Root` — not `Field.Control` |
139
+ | Control | Where it goes |
140
+ | ------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------- |
141
+ | Plain text, email, number, password, … input | `<Field.Control type="…" />` |
142
+ | Native element (`<textarea>`, `<input type="…">`) | `<Field.Control render={<textarea />} />` |
143
+ | `Select`, `Combobox`, `Autocomplete`, `Checkbox`, `Textarea`, `DateField`, `DatePicker`, `DateRangePicker` (and their `.Async`), `Tree` (checkbox mode) | Directly under `Field.Root` — not `Field.Control` |
144
144
 
145
145
  `Input` is the exception. It is a plain `<input>` that does not connect to `Field.Root`, so inside a field use `<Field.Control />` in its place.
146
146
 
@@ -58,17 +58,40 @@ function App() {
58
58
 
59
59
  ## Props
60
60
 
61
- | Prop | Type | Default | Description |
62
- | ------------- | --------------------------------------------------------------- | --------------------------------------- | ---------------------------------------------------------------- |
63
- | `header` | `React.ReactNode` | `<GlobalHeaderLayout.DefaultHeader />` | Replaces the full-width app header. |
64
- | `sidebar` | `React.ReactNode` | `<GlobalHeaderLayout.DefaultSidebar />` | Replaces the primary sidebar. |
65
- | `defaultOpen` | `boolean` | `true` | Whether the sidebar starts expanded on desktop. |
66
- | `collapsible` | `boolean` | `true` | Whether the sidebar can be collapsed. |
67
- | `children` | `(props: { Outlet: () => React.ReactNode }) => React.ReactNode` | current route outlet | Custom content renderer for the stock content container. |
68
- | `body` | `React.ReactNode` | - | Escape hatch that replaces the entire region beside the sidebar. |
61
+ | Prop | Type | Default | Description |
62
+ | ------------------ | --------------------------------------------------------------- | --------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------- |
63
+ | `header` | `React.ReactNode` | `<GlobalHeaderLayout.DefaultHeader />` | Replaces the full-width app header. |
64
+ | `sidebar` | `React.ReactNode` | `<GlobalHeaderLayout.DefaultSidebar />` | Replaces the primary sidebar. |
65
+ | `defaultOpen` | `boolean` | `true` | Whether the sidebar starts expanded on desktop. Ignored in `"fixed"` and `"resize"` modes. |
66
+ | `sidebarBehaviour` | `"fixed" \| "collapse" \| "resize" \| "collapse-and-resize"` | `"collapse-and-resize"` | Selects the permitted desktop operations without restricting mobile/tablet drawer access. |
67
+ | `collapsible` | `boolean` | - | Deprecated compatibility prop: explicit `false` maps to `"fixed"` and explicit `true` to `"collapse"`. Cannot be combined with `sidebarBehaviour`. |
68
+ | `children` | `(props: { Outlet: () => React.ReactNode }) => React.ReactNode` | current route outlet | Custom content renderer for the stock content container. |
69
+ | `body` | `React.ReactNode` | - | Escape hatch that replaces the entire region beside the sidebar. |
69
70
 
70
71
  `children` and `body` are mutually exclusive. Use `body` when you need to add your own side columns around the main content.
71
72
 
73
+ ## Resizing the sidebar
74
+
75
+ `sidebarBehaviour` selects the desktop operations and defaults to `"collapse-and-resize"`. The four modes are shared with [`SidebarLayout`](./sidebar-layout.md#resizing-the-sidebar):
76
+
77
+ ```tsx
78
+ <GlobalHeaderLayout />; // Both operations are enabled by default
79
+ <GlobalHeaderLayout sidebarBehaviour="fixed" />; // Neither operation
80
+ <GlobalHeaderLayout sidebarBehaviour="collapse" />; // Collapse only
81
+ <GlobalHeaderLayout sidebarBehaviour="resize" />; // Resize only
82
+ <GlobalHeaderLayout sidebarBehaviour="collapse-and-resize" />; // Both
83
+ ```
84
+
85
+ `"fixed"` and `"resize"` keep desktop expanded, ignore `defaultOpen`, and make the hook's `setOpen()` action inert. `"fixed"` has no desktop interaction controls; `"resize"` has a resize handle but no collapse controls. `"collapse"` has collapse controls but no resize handle. Desktop toggle shortcuts are not intercepted when collapsing is disabled.
86
+
87
+ Every mode keeps the layout's normal responsive icon rail and mobile/tablet overlay, so the trigger, `toggle()`, and shortcut can still open the full menu. Desktop expansion and responsive drawer visibility are independent.
88
+
89
+ `collapsible` remains supported but deprecated. Explicit `false` maps to `"fixed"`, explicit `true` maps to `"collapse"`, and omitting both props uses the new default. TypeScript rejects specifying both props. Replace legacy `collapsible={false}` with `sidebarBehaviour="fixed"`; it keeps desktop fixed but intentionally no longer forces a fixed sidebar on mobile/tablet.
90
+
91
+ The edge resize handle and keyboard resizing match [`SidebarLayout`](./sidebar-layout.md#resizing-the-sidebar). Resize-only mode clamps pointer and keyboard widths to 192–480px without collapse resistance, and double-click only resets to 256px. With `"collapse-and-resize"`, dragging uses resistance near the rail and can open, resize, and collapse in one gesture; double-clicking at 256px collapses, and double-click or keyboard expansion restores 256px. Width is not saved across reloads. Responsive overlay widths and the collapsed rail width are unchanged.
92
+
93
+ Custom sidebars can read the effective, normalized `sidebarBehaviour` through [`useAppShellSidebar()`](./sidebar-layout.md#reading-and-controlling-the-sidebar). Its existing state and actions remain available; arbitrary custom markup still owns its presentation and sizing.
94
+
72
95
  ## Built-in parts
73
96
 
74
97
  `GlobalHeaderLayout` exposes the same composition pieces as `SidebarLayout`, plus the opinionated header and sidebar defaults:
@@ -26,12 +26,12 @@ function BasicUsage() {
26
26
  return (
27
27
  <Layout>
28
28
  <Layout.Column>
29
- <h2>Main Content</h2>
30
- <p>Your main content goes here...</p>
29
+ <Text.h2>Main Content</Text.h2>
30
+ <Text.p>Your main content goes here...</Text.p>
31
31
  </Layout.Column>
32
32
  <Layout.Column>
33
- <h2>Sidebar</h2>
34
- <p>Additional info or actions...</p>
33
+ <Text.h2>Sidebar</Text.h2>
34
+ <Text.p>Additional info or actions...</Text.p>
35
35
  </Layout.Column>
36
36
  </Layout>
37
37
  );
@@ -230,7 +230,7 @@ If any `Layout.Column` has an `area` prop, all columns switch to area-based widt
230
230
  <nav>Sidebar</nav>
231
231
  </Layout.Column>
232
232
  <Layout.Column area="main">
233
- <h2>Content</h2>
233
+ <Text.h2>Content</Text.h2>
234
234
  </Layout.Column>
235
235
  </Layout>
236
236
  ```
@@ -246,9 +246,16 @@ By default, `<Layout>` grows with its content and the AppShell content area scro
246
246
  <Layout.Header title="Products" />
247
247
  <Layout.Column>
248
248
  <DataTable.Root value={table}>
249
- <DataTable.Toolbar>
250
- <DataTable.Filters />
251
- </DataTable.Toolbar>
249
+ <Toolbar.Root>
250
+ <Toolbar.Row justify="between" aria-label="Product table controls">
251
+ <Toolbar.Group>
252
+ <DataTable.Filters />
253
+ </Toolbar.Group>
254
+ <Toolbar.Group>
255
+ <DataTable.ColumnSettings />
256
+ </Toolbar.Group>
257
+ </Toolbar.Row>
258
+ </Toolbar.Root>
252
259
  <DataTable.Table />
253
260
  <DataTable.Footer>
254
261
  <DataTable.Pagination />
@@ -310,12 +317,12 @@ function OrderDetailPage() {
310
317
  </Layout.Column>
311
318
  <Layout.Column>
312
319
  <Card>
313
- <h3>Quick Actions</h3>
320
+ <Text.h3>Quick Actions</Text.h3>
314
321
  <Button fullWidth>Mark as Shipped</Button>
315
322
  <Button fullWidth>Send Invoice</Button>
316
323
  </Card>
317
324
  <Card>
318
- <h3>Order History</h3>
325
+ <Text.h3>Order History</Text.h3>
319
326
  <Timeline events={order.history} />
320
327
  </Card>
321
328
  </Layout.Column>
@@ -333,7 +340,7 @@ function Dashboard() {
333
340
  <Layout.Header title="Dashboard" />
334
341
  <Layout.Column>
335
342
  <Card>
336
- <h3>Navigation</h3>
343
+ <Text.h3>Navigation</Text.h3>
337
344
  <nav>
338
345
  <a href="#overview">Overview</a>
339
346
  <a href="#sales">Sales</a>
@@ -343,17 +350,17 @@ function Dashboard() {
343
350
  </Layout.Column>
344
351
  <Layout.Column>
345
352
  <Card>
346
- <h2>Sales Overview</h2>
353
+ <Text.h2>Sales Overview</Text.h2>
347
354
  <Chart data={salesData} />
348
355
  </Card>
349
356
  <Card>
350
- <h2>Recent Orders</h2>
357
+ <Text.h2>Recent Orders</Text.h2>
351
358
  <OrdersTable orders={recentOrders} />
352
359
  </Card>
353
360
  </Layout.Column>
354
361
  <Layout.Column>
355
362
  <Card>
356
- <h3>Activity Feed</h3>
363
+ <Text.h3>Activity Feed</Text.h3>
357
364
  <ActivityList activities={activities} />
358
365
  </Card>
359
366
  </Layout.Column>
@@ -381,7 +388,7 @@ function ProductEditor() {
381
388
  />
382
389
  <Layout.Column>
383
390
  <Card>
384
- <h3>Product Details</h3>
391
+ <Text.h3>Product Details</Text.h3>
385
392
  <Input
386
393
  label="Name"
387
394
  value={product.name}
@@ -402,7 +409,7 @@ function ProductEditor() {
402
409
  </Layout.Column>
403
410
  <Layout.Column>
404
411
  <Card>
405
- <h3>Preview</h3>
412
+ <Text.h3>Preview</Text.h3>
406
413
  <ProductCard product={product} />
407
414
  </Card>
408
415
  </Layout.Column>
@@ -439,7 +446,7 @@ function EditorPage() {
439
446
  <Layout.Header title="Settings" actions={[<Button key="save">Save Changes</Button>]} />
440
447
  <Layout.Column>
441
448
  <Card>
442
- <h3>Account Settings</h3>
449
+ <Text.h3>Account Settings</Text.h3>
443
450
  <SettingsForm />
444
451
  </Card>
445
452
  </Layout.Column>
@@ -161,6 +161,14 @@ type SelectAsyncFetcher<T> = (options: { signal: AbortSignal }) => Promise<T[]>;
161
161
 
162
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
163
 
164
+ ## Long Lists
165
+
166
+ The list is at most 15rem tall and scrolls when there are more options. To change the height, compose with `Select.Parts` and pass a plain class with a trailing `!` to `Content`:
167
+
168
+ ```tsx
169
+ <Select.Parts.Content className="max-h-96!">{/* items */}</Select.Parts.Content>
170
+ ```
171
+
164
172
  ## Low-level Primitives
165
173
 
166
174
  `Select.Parts` exposes the styled sub-components for fully custom compositions:
@@ -32,13 +32,13 @@ import { Package } from "lucide-react";
32
32
 
33
33
  ## Props
34
34
 
35
- | Prop | Type | Default | Description |
36
- | ------------- | --------------------------- | ------------ | ---------------------------------------------- |
37
- | `title` | `string \| LocalizedString` | **Required** | Group title (supports i18n) |
38
- | `icon` | `React.ReactNode` | - | Icon displayed next to title |
39
- | `to` | `string` | - | Makes title clickable (navigates to URL) |
40
- | `defaultOpen` | `boolean` | `true` | Initial expanded/collapsed state |
41
- | `children` | `React.ReactNode` | **Required** | Child items (SidebarItem, nested groups, etc.) |
35
+ | Prop | Type | Default | Description |
36
+ | ------------- | --------------------------- | ------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
37
+ | `title` | `string \| LocalizedString` | **Required** | Group title (supports i18n) |
38
+ | `icon` | `React.ReactNode` | - | Icon displayed next to title |
39
+ | `to` | `string` | - | Makes title clickable (navigates to URL). In the icon rail on a touch device, tapping the group opens its flyout instead; the page is linked from the flyout heading |
40
+ | `defaultOpen` | `boolean` | `true` | Initial expanded/collapsed state |
41
+ | `children` | `React.ReactNode` | **Required** | Child items (SidebarItem, nested groups, etc.) |
42
42
 
43
43
  ## Features
44
44
 
@@ -168,6 +168,8 @@ import { Settings } from "lucide-react";
168
168
 
169
169
  Clicking "Settings" navigates to `/settings`, while child items navigate to their specific pages.
170
170
 
171
+ In the collapsed icon rail, the group icon is the link on a mouse device, and its flyout opens on hover. On a touch device the icon opens the flyout on tap, and "Settings" is the link at the top of the flyout.
172
+
171
173
  ### Multiple Groups
172
174
 
173
175
  ```tsx