@tailor-platform/app-shell 1.18.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 +1108 -190
- package/dist/app-shell.js +13090 -10783
- package/dist/{appshell-Y1AYCuVJ.js → appshell-B4QkQvXF.js} +519 -392
- package/dist/fonts/inter.css +7 -7
- package/dist/fonts/noto-sans-jp.css +116 -116
- package/dist/style-entry.d.ts +1 -656
- package/dist/testing.d.ts +132 -97
- package/dist/testing.js +1 -1
- package/dist/vite-plugin.d.ts +132 -97
- package/package.json +15 -10
- package/skills/app-shell-patterns/SKILL.md +38 -27
- package/skills/app-shell-patterns/references/api/aggregations.md +131 -0
- 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 +57 -23
- 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/badge.md +7 -3
- package/skills/app-shell-patterns/references/components/card.md +2 -2
- package/skills/app-shell-patterns/references/components/data-table.md +514 -77
- 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 +19 -9
- package/skills/app-shell-patterns/references/components/global-header-layout.md +31 -8
- package/skills/app-shell-patterns/references/components/item-list.md +191 -0
- package/skills/app-shell-patterns/references/components/layout.md +24 -17
- package/skills/app-shell-patterns/references/components/select.md +18 -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/text.md +75 -38
- 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/sidebar-navigation.md +16 -14
- package/skills/app-shell-patterns/references/concepts/styling-theming.md +28 -20
- package/skills/app-shell-patterns/references/migrations.md +22 -2
- package/skills/app-shell-patterns/references/pages/document-detail.md +32 -25
- package/skills/app-shell-patterns/references/patterns/form-modal.md +4 -1
- package/skills/app-shell-patterns/references/patterns/interaction-multi-select.md +1 -1
- package/dist/semantic-contrast.test.ts +0 -227
- package/dist/theme-bridge.test.ts +0 -182
|
@@ -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>
|
|
@@ -100,14 +100,14 @@ A compound component that groups all parts of a form field and manages its valid
|
|
|
100
100
|
|
|
101
101
|
### Sub-components
|
|
102
102
|
|
|
103
|
-
| Sub-component | Description
|
|
104
|
-
| ------------------- |
|
|
105
|
-
| `Field.Root` | Context boundary for a single field. Manages validation state.
|
|
106
|
-
| `Field.Label` | Accessible label; `htmlFor` is resolved automatically via context.
|
|
107
|
-
| `Field.Control` | Styled `<input>`.
|
|
108
|
-
| `Field.Description` | Supplementary help text; automatically linked via `aria-describedby`.
|
|
109
|
-
| `Field.Error` | Validation error message. Use `match` to target specific validity states.
|
|
110
|
-
| `Field.Validity` | Render-prop access to the field's `ValidityState` for fully custom validation UI.
|
|
103
|
+
| Sub-component | Description |
|
|
104
|
+
| ------------------- | ---------------------------------------------------------------------------------------------------------------------- |
|
|
105
|
+
| `Field.Root` | Context boundary for a single field. Manages validation state. |
|
|
106
|
+
| `Field.Label` | Accessible label; `htmlFor` is resolved automatically via context. |
|
|
107
|
+
| `Field.Control` | Styled text `<input>`. `Select`, `Combobox`, and the other self-wired controls go directly under `Field.Root` instead. |
|
|
108
|
+
| `Field.Description` | Supplementary help text; automatically linked via `aria-describedby`. |
|
|
109
|
+
| `Field.Error` | Validation error message. Use `match` to target specific validity states. |
|
|
110
|
+
| `Field.Validity` | Render-prop access to the field's `ValidityState` for fully custom validation UI. |
|
|
111
111
|
|
|
112
112
|
### Field.Root Props
|
|
113
113
|
|
|
@@ -134,7 +134,17 @@ A compound component that groups all parts of a form field and manages its valid
|
|
|
134
134
|
|
|
135
135
|
### Using Another AppShell Component as the Control
|
|
136
136
|
|
|
137
|
-
`Field.Control`
|
|
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
|
+
|
|
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
|
+
|
|
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
|
+
|
|
147
|
+
Do not pass the self-wired controls to `Field.Control`'s `render` prop. They draw their own frame, so `<Field.Control render={<Select />} />` renders a second border around the select. `Field.Control` also passes them `<input>` props they don't take. `Field.Control` logs a console warning when it is given one of these controls directly as an element. A wrapper component or the function form of `render` is not detected.
|
|
138
148
|
|
|
139
149
|
```tsx
|
|
140
150
|
const [country, setCountry] = React.useState<string | null>(null);
|
|
@@ -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:
|
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: ItemList
|
|
3
|
+
description: Compact icon-and-label lists for consistent navigation inside and outside the Sidebar
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
<!-- GENERATED by docs-kit from docs-src/components/item-list.docs.outline.md — do not edit by hand. -->
|
|
7
|
+
|
|
8
|
+
# ItemList
|
|
9
|
+
|
|
10
|
+
`ItemList` presents compact icon-and-label rows for navigation and actions. Use it for in-content navigation, tables of contents, or other lists that should share the Sidebar's list presentation without depending on its layout or providers.
|
|
11
|
+
|
|
12
|
+
It does not require `AppShell`, a router, or a Sidebar provider. Resource metadata, translations, permissions, and active-route matching remain the caller's responsibility.
|
|
13
|
+
|
|
14
|
+
## Import
|
|
15
|
+
|
|
16
|
+
```tsx
|
|
17
|
+
import { ItemList } from "@tailor-platform/app-shell";
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
## Basic Usage
|
|
21
|
+
|
|
22
|
+
<example-preview name="basic-usage"></example-preview>
|
|
23
|
+
|
|
24
|
+
```tsx
|
|
25
|
+
function BasicUsage() {
|
|
26
|
+
const [view, setView] = useState("Overview");
|
|
27
|
+
return (
|
|
28
|
+
<>
|
|
29
|
+
<ItemList.Root aria-label="Workspace">
|
|
30
|
+
<ItemList.Item
|
|
31
|
+
icon={<Home />}
|
|
32
|
+
isActive={view === "Overview"}
|
|
33
|
+
onClick={() => setView("Overview")}
|
|
34
|
+
>
|
|
35
|
+
Overview
|
|
36
|
+
</ItemList.Item>
|
|
37
|
+
<ItemList.Separator />
|
|
38
|
+
<ItemList.Section title="Business">
|
|
39
|
+
<ItemList.Item icon={<ShoppingCart />} onClick={() => setView("Orders")}>
|
|
40
|
+
Orders
|
|
41
|
+
</ItemList.Item>
|
|
42
|
+
<ItemList.Group title="Products" icon={<Package />}>
|
|
43
|
+
<ItemList.Item onClick={() => setView("All products")}>All products</ItemList.Item>
|
|
44
|
+
<ItemList.Item onClick={() => setView("Categories")}>Categories</ItemList.Item>
|
|
45
|
+
</ItemList.Group>
|
|
46
|
+
</ItemList.Section>
|
|
47
|
+
</ItemList.Root>
|
|
48
|
+
<output>Current view: {view}</output>
|
|
49
|
+
</>
|
|
50
|
+
);
|
|
51
|
+
}
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
## Sub-components
|
|
55
|
+
|
|
56
|
+
| Sub-component | Description |
|
|
57
|
+
| -------------------- | -------------------------------------------------------------------------------- |
|
|
58
|
+
| `ItemList.Root` | A semantic `ul`; accepts standard list props |
|
|
59
|
+
| `ItemList.Item` | A compact icon-and-label row containing a button or supplied interactive element |
|
|
60
|
+
| `ItemList.Group` | A collapsible, indented child list; open by default |
|
|
61
|
+
| `ItemList.Section` | A labeled, non-collapsible list whose items retain their normal indentation |
|
|
62
|
+
| `ItemList.Separator` | A decorative divider that remains a valid list child |
|
|
63
|
+
|
|
64
|
+
## Items, Links, and Actions
|
|
65
|
+
|
|
66
|
+
`ItemList.Item` is for links and actions, not static rows. It requires `onClick`, `render`, or both; omitting both is a type error, including for a disabled Item. With `onClick`, it renders a `button` with `type="button"` by default. Supply an anchor or application link through `render` for navigation.
|
|
67
|
+
|
|
68
|
+
`render` replaces the interactive element inside the row, not the containing `li`. `children` supplies its label, and `icon` supplies an optional decorative leading icon. Standard button props, classes, attributes, and refs apply to the inner operation. A disabled Item is skipped by Tab and cancels clicks, including navigation through a supplied link.
|
|
69
|
+
|
|
70
|
+
<example-preview name="links-and-actions"></example-preview>
|
|
71
|
+
|
|
72
|
+
```tsx
|
|
73
|
+
function LinksAndActions() {
|
|
74
|
+
const [checks, setChecks] = useState(0);
|
|
75
|
+
const handleCheckNotifications = () => {
|
|
76
|
+
setChecks((count) => count + 1);
|
|
77
|
+
};
|
|
78
|
+
return (
|
|
79
|
+
<>
|
|
80
|
+
<ItemList.Root aria-label="Quick access">
|
|
81
|
+
<ItemList.Item icon={<Home />} render={<a href="https://example.com/help" />}>
|
|
82
|
+
Help center
|
|
83
|
+
</ItemList.Item>
|
|
84
|
+
<ItemList.Item icon={<Bell />} onClick={handleCheckNotifications}>
|
|
85
|
+
Check notifications
|
|
86
|
+
</ItemList.Item>
|
|
87
|
+
</ItemList.Root>
|
|
88
|
+
<output>Notification checks: {checks}</output>
|
|
89
|
+
</>
|
|
90
|
+
);
|
|
91
|
+
}
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
For application navigation, use `Link` or `NavLink` from `@tailor-platform/app-shell` inside an AppShell-managed page. Do not import routing APIs directly from `react-router` or create a separate router around the list. The routes in this example belong to the documentation app; use your application's route paths in your own pages.
|
|
95
|
+
|
|
96
|
+
<example-preview name="app-shell-routing"></example-preview>
|
|
97
|
+
|
|
98
|
+
```tsx
|
|
99
|
+
function AppShellRouting() {
|
|
100
|
+
return (
|
|
101
|
+
<ItemList.Root aria-label="Documentation pages">
|
|
102
|
+
<ItemList.Item render={<Link to="/components/button" />}>Button documentation</ItemList.Item>
|
|
103
|
+
<ItemList.Item render={<NavLink to="/components/item-list" end />}>
|
|
104
|
+
ItemList documentation
|
|
105
|
+
</ItemList.Item>
|
|
106
|
+
</ItemList.Root>
|
|
107
|
+
);
|
|
108
|
+
}
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
`NavLink` owns route matching and supplies `aria-current="page"` when matched; ItemList styles that attribute with the same emphasis as `isActive`. Use `end` when only the exact destination should be current. With a plain link, the caller supplies `aria-current="page"` when appropriate.
|
|
112
|
+
|
|
113
|
+
`isActive` remains a boolean for explicit visual emphasis, including local actions. It does not imply `aria-current`, selection state, a `listbox` role, or `aria-selected`. ItemList does not perform route matching.
|
|
114
|
+
|
|
115
|
+
### Item Props
|
|
116
|
+
|
|
117
|
+
| Prop | Type | Default | Description |
|
|
118
|
+
| ----------- | -------------------------------------- | ------- | ------------------------------------------------------------------ |
|
|
119
|
+
| `icon` | `ReactNode` | — | Optional decorative leading icon |
|
|
120
|
+
| `children` | `ReactNode` | — | Compact row label |
|
|
121
|
+
| `render` | `ReactElement` | — | Replaces the inner button; required unless `onClick` is supplied |
|
|
122
|
+
| `onClick` | `MouseEventHandler<HTMLButtonElement>` | — | Action handler; required unless `render` is supplied |
|
|
123
|
+
| `isActive` | `boolean` | `false` | Explicit visual emphasis without route matching or selection state |
|
|
124
|
+
| `disabled` | `boolean` | — | Disables clicks and removes the operation from the tab order |
|
|
125
|
+
| `className` | `string` | — | Classes applied to the inner operation |
|
|
126
|
+
|
|
127
|
+
Other standard button props, such as `title` and ARIA attributes, are forwarded to the inner operation. Put link-specific props such as `href`, `target`, and `rel` on the element passed to `render`.
|
|
128
|
+
|
|
129
|
+
Rows without an `icon` do not reserve icon space. To align a mixed list, explicitly pass `icon={<span />}` on a row that needs an empty icon slot. Lists with no icons keep their compact indentation.
|
|
130
|
+
|
|
131
|
+
## Groups and Heading Hierarchies
|
|
132
|
+
|
|
133
|
+
`expandOnClick` defaults to `true`: clicking a group's heading toggles its child list, whether or not `render` is supplied. `render` only replaces the heading's main operation; it does not determine the expansion behavior. `defaultOpen` defaults to `true`; set it to `false` to start collapsed. Groups can be nested.
|
|
134
|
+
|
|
135
|
+
Set `expandOnClick={false}` to separate the heading's main operation from expansion. This requires `onClick` or `render` so the heading does not become an inert button. A separate button, named by the group title, expands and collapses the group. Use this for linked headings so navigation does not toggle the list.
|
|
136
|
+
|
|
137
|
+
`onClick` applies only to the heading's main operation. Clicking a child Item or the separate expand button does not invoke it. `isActive` and `aria-current="page"` control emphasis on the main heading operation, not the separate expand button.
|
|
138
|
+
|
|
139
|
+
For a table of contents, use a linked Group for a parent heading and Items for its child headings. Keep the list data aligned with the actual heading hierarchy and anchor IDs in the document.
|
|
140
|
+
|
|
141
|
+
<example-preview name="linked-group"></example-preview>
|
|
142
|
+
|
|
143
|
+
```tsx
|
|
144
|
+
function LinkedGroup() {
|
|
145
|
+
return (
|
|
146
|
+
<ItemList.Root aria-label="Contents">
|
|
147
|
+
<ItemList.Group
|
|
148
|
+
title="Installing"
|
|
149
|
+
render={<a href="#installing" aria-label="Installing" />}
|
|
150
|
+
expandOnClick={false}
|
|
151
|
+
>
|
|
152
|
+
<ItemList.Item render={<a href="#configuration" aria-label="Configuration" />}>
|
|
153
|
+
Configuration
|
|
154
|
+
</ItemList.Item>
|
|
155
|
+
</ItemList.Group>
|
|
156
|
+
</ItemList.Root>
|
|
157
|
+
);
|
|
158
|
+
}
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
### Group Props
|
|
162
|
+
|
|
163
|
+
| Prop | Type | Default | Description |
|
|
164
|
+
| --------------- | -------------------------------------- | ------- | ------------------------------------------------------------------------------------------------- |
|
|
165
|
+
| `title` | `ReactNode` | — | Accessible group label, including text and optional decorations |
|
|
166
|
+
| `icon` | `ReactNode` | — | Optional decorative leading icon |
|
|
167
|
+
| `render` | `ReactElement` | — | Replaces the main heading operation without changing expansion behavior |
|
|
168
|
+
| `onClick` | `MouseEventHandler<HTMLButtonElement>` | — | Handler for the main heading operation only |
|
|
169
|
+
| `expandOnClick` | `boolean` | `true` | Toggle from the heading; `false` requires `onClick` or `render` and adds a separate expand button |
|
|
170
|
+
| `isActive` | `boolean` | `false` | Explicit visual emphasis for the heading operation |
|
|
171
|
+
| `defaultOpen` | `boolean` | `true` | Initial uncontrolled expanded state |
|
|
172
|
+
| `children` | `ReactNode` | — | Nested Items, Groups, Sections, or Separators |
|
|
173
|
+
|
|
174
|
+
Other standard `li` props, including `className`, apply to the containing group item, not its heading operation. `onClick` is the exception described above.
|
|
175
|
+
|
|
176
|
+
A Group's rendered label names its heading operation, separate expand button, and child list. Keep meaningful text in a rich `title`; ReactNode content is not converted into a string. Meaningful details such as counts can be read with the label, while purely decorative elements should use `aria-hidden="true"`. An icon-only title needs screen-reader text. Do not put links or buttons inside the title, because the heading itself is interactive.
|
|
177
|
+
|
|
178
|
+
## Sections and Separators
|
|
179
|
+
|
|
180
|
+
Use `Group` for hierarchy and `Section` for flat classification. A Section accepts a resolved string `title` and `children`, keeps child rows at their normal indentation, and does not add an interactive heading. Other standard `li` props apply to its outer item.
|
|
181
|
+
|
|
182
|
+
Neither Group nor Section requires wrapping its children in another `ItemList.Root`. Use `ItemList.Separator` between logical runs of items; it is decorative and renders a list item rather than an invalid direct `div` child of `ul`.
|
|
183
|
+
|
|
184
|
+
## Styling and Accessibility
|
|
185
|
+
|
|
186
|
+
- Keep labels concise; they truncate in narrow containers.
|
|
187
|
+
- Use `aria-label` or `aria-labelledby` on Root when a list needs an accessible name.
|
|
188
|
+
- Native links and buttons retain their keyboard interactions. Group buttons announce their expanded state and support Enter and Space.
|
|
189
|
+
- Resolve translations before passing labels; ItemList does not translate them. Group titles can contain ReactNode content, while Section titles remain strings.
|
|
190
|
+
- ItemList is an ordinary list, not a menu or selection widget. It does not provide arrow-key selection, search, reordering, or Sidebar icon-rail behavior.
|
|
191
|
+
- Existing Sidebar components retain route metadata resolution, rail tooltips and flyouts, and drawer dismissal while sharing the underlying list presentation.
|
|
@@ -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:
|
|
@@ -171,6 +179,16 @@ const { Root, Trigger, Value, Content, Item, Group, GroupLabel, Separator } = Se
|
|
|
171
179
|
|
|
172
180
|
## Form submission
|
|
173
181
|
|
|
182
|
+
Inside a form, place `Select` directly under `Field.Root`, not inside `Field.Control`. `Select` draws
|
|
183
|
+
its own trigger and registers with the field itself.
|
|
184
|
+
|
|
185
|
+
```tsx
|
|
186
|
+
<Field.Root name="country">
|
|
187
|
+
<Field.Label>Country</Field.Label>
|
|
188
|
+
<Select items={["Japan", "United States"]} placeholder="Select a country" />
|
|
189
|
+
</Field.Root>
|
|
190
|
+
```
|
|
191
|
+
|
|
174
192
|
Inside a `Field.Root`, the field's `name` identifies the value and these props are unnecessary —
|
|
175
193
|
see [Form](./form.md). They matter for **native** submission: a plain `<form>`, `new FormData(form)`,
|
|
176
194
|
or a server action.
|
|
@@ -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
|