@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.
- package/dist/app-shell-core.css +2 -2
- package/dist/app-shell.d.ts +807 -130
- package/dist/app-shell.js +12419 -10930
- package/dist/{appshell-DQTz-QwK.js → appshell-B4QkQvXF.js} +377 -360
- package/dist/fonts/inter.css +7 -7
- package/dist/fonts/noto-sans-jp.css +116 -116
- package/dist/testing.d.ts +119 -89
- package/dist/testing.js +1 -1
- package/dist/vite-plugin.d.ts +119 -89
- package/package.json +10 -8
- package/skills/app-shell-patterns/SKILL.md +4 -3
- package/skills/app-shell-patterns/references/api/create-ai-gateway-client.md +32 -2
- package/skills/app-shell-patterns/references/api/define-module.md +5 -5
- package/skills/app-shell-patterns/references/api/router/use-params.md +2 -2
- package/skills/app-shell-patterns/references/api/use-ai-chat.md +46 -8
- package/skills/app-shell-patterns/references/api/use-app-shell-config.md +4 -4
- package/skills/app-shell-patterns/references/api/use-app-shell-data.md +5 -5
- package/skills/app-shell-patterns/references/api/use-app-shell.md +2 -2
- package/skills/app-shell-patterns/references/api/use-auth.md +3 -3
- package/skills/app-shell-patterns/references/api/use-route-error.md +12 -12
- package/skills/app-shell-patterns/references/components/action-panel.md +4 -2
- package/skills/app-shell-patterns/references/components/activity-card.md +3 -3
- package/skills/app-shell-patterns/references/components/ai-chat.md +37 -3
- package/skills/app-shell-patterns/references/components/app-shell.md +6 -5
- package/skills/app-shell-patterns/references/components/card.md +2 -2
- package/skills/app-shell-patterns/references/components/data-table.md +87 -31
- package/skills/app-shell-patterns/references/components/date-picker.md +2 -2
- package/skills/app-shell-patterns/references/components/default-sidebar.md +2 -2
- package/skills/app-shell-patterns/references/components/dialog.md +8 -0
- package/skills/app-shell-patterns/references/components/filter-rail.md +10 -3
- package/skills/app-shell-patterns/references/components/form.md +5 -5
- package/skills/app-shell-patterns/references/components/global-header-layout.md +31 -8
- package/skills/app-shell-patterns/references/components/layout.md +24 -17
- package/skills/app-shell-patterns/references/components/select.md +8 -0
- package/skills/app-shell-patterns/references/components/sidebar-group.md +9 -7
- package/skills/app-shell-patterns/references/components/sidebar-layout.md +74 -11
- package/skills/app-shell-patterns/references/components/sidebar-section.md +8 -8
- package/skills/app-shell-patterns/references/components/tabs.md +32 -2
- package/skills/app-shell-patterns/references/components/tree.md +342 -0
- package/skills/app-shell-patterns/references/components/with-guard.md +5 -5
- package/skills/app-shell-patterns/references/concepts/authentication.md +1 -1
- package/skills/app-shell-patterns/references/concepts/routing-navigation.md +3 -3
- package/skills/app-shell-patterns/references/concepts/styling-theming.md +13 -13
- package/skills/app-shell-patterns/references/migrations.md +20 -0
- package/skills/app-shell-patterns/references/pages/document-detail.md +8 -12
- 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
|
-
<
|
|
143
|
-
<
|
|
144
|
-
|
|
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">
|
|
1031
|
-
|
|
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
|
|
1062
|
-
| -------------------- |
|
|
1063
|
-
| `selectedIds` | `string[]`
|
|
1064
|
-
| `defaultSelectedIds` | `string[]`
|
|
1065
|
-
| `onChange` | `(ids: string[]) => void`
|
|
1066
|
-
| `actions` | `SelectionAction[]`
|
|
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
|
-
| `
|
|
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
|
|
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
|
-
<
|
|
158
|
-
<
|
|
159
|
-
|
|
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
|
|
140
|
-
|
|
|
141
|
-
| Plain text, email, number, password, … input
|
|
142
|
-
| Native element (`<textarea>`, `<input type="…">`)
|
|
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
|
|
62
|
-
|
|
|
63
|
-
| `header`
|
|
64
|
-
| `sidebar`
|
|
65
|
-
| `defaultOpen`
|
|
66
|
-
| `
|
|
67
|
-
| `
|
|
68
|
-
| `
|
|
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
|
-
<
|
|
250
|
-
<
|
|
251
|
-
|
|
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
|