@tailor-platform/app-shell 1.15.0 → 1.17.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 (109) hide show
  1. package/dist/app-shell-core.css +1 -1
  2. package/dist/app-shell.d.ts +811 -131
  3. package/dist/app-shell.js +7155 -5797
  4. package/dist/{appshell-DLVBFo5J.js → appshell-CahhZy7v.js} +656 -524
  5. package/dist/semantic-contrast.test.ts +154 -0
  6. package/dist/style-entry.d.ts +147 -92
  7. package/dist/testing.d.ts +152 -95
  8. package/dist/testing.js +1 -1
  9. package/dist/theme-bridge.test.ts +101 -1
  10. package/dist/theme.bridge.css +91 -0
  11. package/dist/theme.css +6 -1
  12. package/dist/themes/_template.css +7 -1
  13. package/dist/themes/bloom.css +0 -6
  14. package/dist/themes/cream.css +0 -6
  15. package/dist/themes/default.css +389 -69
  16. package/dist/vite-plugin.d.ts +147 -92
  17. package/package.json +8 -8
  18. package/skills/app-shell-patterns/SKILL.md +108 -19
  19. package/skills/app-shell-patterns/references/api/create-ai-gateway-client.md +115 -0
  20. package/skills/app-shell-patterns/references/api/create-typed-paths.md +117 -0
  21. package/skills/app-shell-patterns/references/api/define-i18n-labels.md +143 -0
  22. package/skills/app-shell-patterns/references/api/define-module.md +247 -0
  23. package/skills/app-shell-patterns/references/api/define-resource.md +307 -0
  24. package/skills/app-shell-patterns/references/api/guards/hidden.md +202 -0
  25. package/skills/app-shell-patterns/references/api/guards/overview.md +354 -0
  26. package/skills/app-shell-patterns/references/api/guards/pass.md +129 -0
  27. package/skills/app-shell-patterns/references/api/guards/redirect-to.md +307 -0
  28. package/skills/app-shell-patterns/references/api/router/use-location.md +240 -0
  29. package/skills/app-shell-patterns/references/api/router/use-navigate.md +138 -0
  30. package/skills/app-shell-patterns/references/api/router/use-params.md +218 -0
  31. package/skills/app-shell-patterns/references/api/use-ai-chat.md +110 -0
  32. package/skills/app-shell-patterns/references/api/use-app-shell-config.md +132 -0
  33. package/skills/app-shell-patterns/references/api/use-app-shell-data.md +150 -0
  34. package/skills/app-shell-patterns/references/api/use-app-shell.md +150 -0
  35. package/skills/app-shell-patterns/references/api/use-auth.md +145 -0
  36. package/skills/app-shell-patterns/references/api/use-override-breadcrumb.md +74 -0
  37. package/skills/app-shell-patterns/references/api/use-page-meta.md +96 -0
  38. package/skills/app-shell-patterns/references/api/use-register-command-palette-actions.md +90 -0
  39. package/skills/app-shell-patterns/references/api/use-resolved-locale.md +67 -0
  40. package/skills/app-shell-patterns/references/api/use-route-error.md +214 -0
  41. package/skills/app-shell-patterns/references/api/use-theme.md +108 -0
  42. package/skills/app-shell-patterns/references/api/use-time-zone.md +80 -0
  43. package/skills/app-shell-patterns/references/api/use-toast.md +104 -0
  44. package/skills/app-shell-patterns/references/components/action-panel.md +168 -0
  45. package/skills/app-shell-patterns/references/components/activity-card.md +142 -0
  46. package/skills/app-shell-patterns/references/components/ai-chat.md +315 -0
  47. package/skills/app-shell-patterns/references/components/alert.md +202 -0
  48. package/skills/app-shell-patterns/references/components/app-shell.md +530 -0
  49. package/skills/app-shell-patterns/references/components/appearance-switcher.md +87 -0
  50. package/skills/app-shell-patterns/references/components/attachment.md +138 -0
  51. package/skills/app-shell-patterns/references/components/autocomplete.md +278 -0
  52. package/skills/app-shell-patterns/references/components/avatar.md +80 -0
  53. package/skills/app-shell-patterns/references/components/badge.md +309 -0
  54. package/skills/app-shell-patterns/references/components/button.md +164 -0
  55. package/skills/app-shell-patterns/references/components/card.md +181 -0
  56. package/skills/app-shell-patterns/references/components/checkbox.md +169 -0
  57. package/skills/app-shell-patterns/references/components/combobox.md +318 -0
  58. package/skills/app-shell-patterns/references/components/command-palette.md +413 -0
  59. package/skills/app-shell-patterns/references/components/csv-importer.md +214 -0
  60. package/skills/app-shell-patterns/references/components/data-table.md +1168 -0
  61. package/skills/app-shell-patterns/references/components/date-picker.md +259 -0
  62. package/skills/app-shell-patterns/references/components/default-header.md +75 -0
  63. package/skills/app-shell-patterns/references/components/default-sidebar.md +136 -0
  64. package/skills/app-shell-patterns/references/components/description-card.md +569 -0
  65. package/skills/app-shell-patterns/references/components/dialog.md +164 -0
  66. package/skills/app-shell-patterns/references/components/document-progress-card.md +154 -0
  67. package/skills/app-shell-patterns/references/components/filter-rail.md +165 -0
  68. package/skills/app-shell-patterns/references/components/form.md +378 -0
  69. package/skills/app-shell-patterns/references/components/global-header-layout.md +132 -0
  70. package/skills/app-shell-patterns/references/components/grid.md +184 -0
  71. package/skills/app-shell-patterns/references/components/input.md +83 -0
  72. package/skills/app-shell-patterns/references/components/layout.md +539 -0
  73. package/skills/app-shell-patterns/references/components/menu.md +218 -0
  74. package/skills/app-shell-patterns/references/components/metric-card.md +94 -0
  75. package/skills/app-shell-patterns/references/components/select.md +283 -0
  76. package/skills/app-shell-patterns/references/components/sheet.md +237 -0
  77. package/skills/app-shell-patterns/references/components/sidebar-group.md +472 -0
  78. package/skills/app-shell-patterns/references/components/sidebar-item.md +458 -0
  79. package/skills/app-shell-patterns/references/components/sidebar-layout.md +258 -0
  80. package/skills/app-shell-patterns/references/components/sidebar-section.md +114 -0
  81. package/skills/app-shell-patterns/references/components/spinner.md +81 -0
  82. package/skills/app-shell-patterns/references/components/table.md +188 -0
  83. package/skills/app-shell-patterns/references/components/tabs.md +158 -0
  84. package/skills/app-shell-patterns/references/components/textarea.md +122 -0
  85. package/skills/app-shell-patterns/references/components/timeline.md +317 -0
  86. package/skills/app-shell-patterns/references/components/toolbar.md +138 -0
  87. package/skills/app-shell-patterns/references/components/tooltip.md +144 -0
  88. package/skills/app-shell-patterns/references/components/with-guard.md +445 -0
  89. package/skills/app-shell-patterns/references/concepts/authentication.md +301 -0
  90. package/skills/app-shell-patterns/references/concepts/custom-components.md +59 -0
  91. package/skills/app-shell-patterns/references/concepts/file-based-routing.md +356 -0
  92. package/skills/app-shell-patterns/references/{fundamental → concepts}/graphql.md +31 -18
  93. package/skills/app-shell-patterns/references/concepts/modules-and-resources.md +229 -0
  94. package/skills/app-shell-patterns/references/concepts/routing-navigation.md +425 -0
  95. package/skills/app-shell-patterns/references/concepts/sidebar-navigation.md +420 -0
  96. package/skills/app-shell-patterns/references/concepts/styling-theming.md +785 -0
  97. package/skills/app-shell-patterns/references/migrations.md +55 -0
  98. package/skills/app-shell-patterns/references/pages/{detail.md → document-detail.md} +10 -33
  99. package/skills/app-shell-patterns/references/patterns/form-composer.md +43 -45
  100. package/skills/app-shell-patterns/references/patterns/form-modal.md +37 -92
  101. package/skills/app-shell-patterns/references/patterns/form-sectioned.md +12 -38
  102. package/skills/app-shell-patterns/references/patterns/form-single-page.md +11 -32
  103. package/skills/app-shell-patterns/references/patterns/form-wizard.md +12 -53
  104. package/skills/app-shell-patterns/references/patterns/interaction-confirm.md +11 -27
  105. package/skills/app-shell-patterns/references/patterns/interaction-multi-select.md +136 -139
  106. package/skills/app-shell-patterns/references/patterns/interaction-toast.md +10 -34
  107. package/skills/app-shell-patterns/references/patterns/list-dense-scan.md +13 -88
  108. package/skills/app-shell-patterns/references/fundamental/components.md +0 -1009
  109. package/skills/app-shell-patterns/references/fundamental/design-system.md +0 -440
@@ -0,0 +1,164 @@
1
+ ---
2
+ title: Dialog
3
+ description: Modal dialog with a compound component API
4
+ ---
5
+
6
+ <!-- GENERATED by docs-kit from docs-src/components/dialog.docs.outline.md — do not edit by hand. -->
7
+
8
+ # Dialog
9
+
10
+ The `Dialog` component provides a modal dialog with a compound component API. It is backed by Base UI's Dialog primitive.
11
+
12
+ ## Import
13
+
14
+ ```tsx
15
+ import { Dialog } from "@tailor-platform/app-shell";
16
+ ```
17
+
18
+ ## Basic Usage
19
+
20
+ <example-preview name="basic-usage"></example-preview>
21
+
22
+ ```tsx
23
+ function BasicUsage() {
24
+ return (
25
+ <Dialog.Root>
26
+ <Dialog.Trigger render={<Button />}>Open</Dialog.Trigger>
27
+ <Dialog.Content>
28
+ <Dialog.Header>
29
+ <Dialog.Title>Confirm Action</Dialog.Title>
30
+ <Dialog.Description>Are you sure you want to proceed?</Dialog.Description>
31
+ </Dialog.Header>
32
+ <Dialog.Footer>
33
+ <Dialog.Close render={<Button variant="outline" />}>Cancel</Dialog.Close>
34
+ <Button>Confirm</Button>
35
+ </Dialog.Footer>
36
+ </Dialog.Content>
37
+ </Dialog.Root>
38
+ );
39
+ }
40
+ ```
41
+
42
+ ## Sub-components
43
+
44
+ | Sub-component | Description |
45
+ | -------------------- | ----------------------------------------------------------------------- |
46
+ | `Dialog.Root` | Manages open/close state |
47
+ | `Dialog.Trigger` | Element that opens the dialog when clicked |
48
+ | `Dialog.Content` | The main dialog panel (includes overlay and close button automatically) |
49
+ | `Dialog.Header` | Layout wrapper for title and description |
50
+ | `Dialog.Footer` | Layout wrapper for action buttons |
51
+ | `Dialog.Title` | Dialog title (announced by screen readers) |
52
+ | `Dialog.Description` | Additional context below the title |
53
+ | `Dialog.Close` | Button that closes the dialog |
54
+
55
+ ## Props
56
+
57
+ ### Dialog.Root Props
58
+
59
+ | Prop | Type | Default | Description |
60
+ | -------------- | ------------------------- | ------- | --------------------------------- |
61
+ | `open` | `boolean` | - | Controlled open state |
62
+ | `defaultOpen` | `boolean` | `false` | Initial open state (uncontrolled) |
63
+ | `onOpenChange` | `(open: boolean) => void` | - | Callback when open state changes |
64
+ | `modal` | `boolean` | `true` | Whether the dialog is modal |
65
+ | `children` | `React.ReactNode` | - | Dialog sub-components |
66
+
67
+ ### Dialog.Content Props
68
+
69
+ Accepts `className` and all standard HTML `<div>` props.
70
+
71
+ ### Dialog.Trigger / Dialog.Close Props
72
+
73
+ Accept a `render` prop for custom element rendering (Base UI render pattern), plus standard button props.
74
+
75
+ ## Controlled Usage
76
+
77
+ <example-preview name="controlled"></example-preview>
78
+
79
+ ```tsx
80
+ function Controlled() {
81
+ const [open, setOpen] = useState(false);
82
+ return (
83
+ <>
84
+ <Button variant="outline" onClick={() => setOpen(true)}>
85
+ Open Dialog
86
+ </Button>
87
+ <Dialog.Root open={open} onOpenChange={setOpen}>
88
+ <Dialog.Content>
89
+ <Dialog.Header>
90
+ <Dialog.Title>Settings</Dialog.Title>
91
+ </Dialog.Header>
92
+ <Dialog.Footer>
93
+ <Button onClick={() => setOpen(false)}>Done</Button>
94
+ </Dialog.Footer>
95
+ </Dialog.Content>
96
+ </Dialog.Root>
97
+ </>
98
+ );
99
+ }
100
+ ```
101
+
102
+ ## Examples
103
+
104
+ ### Delete Confirmation
105
+
106
+ ```tsx
107
+ function DeleteConfirmation({ onDelete }: { onDelete: () => void }) {
108
+ return (
109
+ <Dialog.Root>
110
+ <Dialog.Trigger render={<Button variant="destructive" />}>Delete</Dialog.Trigger>
111
+ <Dialog.Content>
112
+ <Dialog.Header>
113
+ <Dialog.Title>Delete Order</Dialog.Title>
114
+ <Dialog.Description>
115
+ This action cannot be undone. The order will be permanently deleted.
116
+ </Dialog.Description>
117
+ </Dialog.Header>
118
+ <Dialog.Footer>
119
+ <Dialog.Close render={<Button variant="outline" />}>Cancel</Dialog.Close>
120
+ <Button variant="destructive" onClick={onDelete}>
121
+ Delete
122
+ </Button>
123
+ </Dialog.Footer>
124
+ </Dialog.Content>
125
+ </Dialog.Root>
126
+ );
127
+ }
128
+ ```
129
+
130
+ ### Form Dialog
131
+
132
+ ```tsx
133
+ function EditDialog({ item }: { item: Item }) {
134
+ const [name, setName] = useState(item.name);
135
+
136
+ return (
137
+ <Dialog.Root>
138
+ <Dialog.Trigger render={<Button variant="outline" />}>Edit</Dialog.Trigger>
139
+ <Dialog.Content>
140
+ <Dialog.Header>
141
+ <Dialog.Title>Edit Item</Dialog.Title>
142
+ </Dialog.Header>
143
+ <Input value={name} onChange={(e) => setName(e.target.value)} />
144
+ <Dialog.Footer>
145
+ <Dialog.Close render={<Button variant="outline" />}>Cancel</Dialog.Close>
146
+ <Button onClick={() => handleSave(name)}>Save</Button>
147
+ </Dialog.Footer>
148
+ </Dialog.Content>
149
+ </Dialog.Root>
150
+ );
151
+ }
152
+ ```
153
+
154
+ ## Accessibility
155
+
156
+ - Dialog title is announced by screen readers via `Dialog.Title`
157
+ - Focus is trapped inside the dialog while open
158
+ - Pressing `Escape` closes the dialog
159
+ - The close button is always present inside `Dialog.Content`
160
+
161
+ ## Related Components
162
+
163
+ - [Sheet](./sheet.md) - Slide-in panel alternative for non-modal workflows
164
+ - [Button](./button.md) - Use as trigger and action buttons
@@ -0,0 +1,154 @@
1
+ ---
2
+ title: DocumentProgressCard
3
+ description: Generic card visualising a document's lifecycle state — optional percentage, stacked progress bar, and status legend
4
+ ---
5
+
6
+ <!-- GENERATED by docs-kit from docs-src/components/document-progress-card.docs.outline.md — do not edit by hand. -->
7
+
8
+ # DocumentProgressCard
9
+
10
+ `DocumentProgressCard` is a generic, presentational card for communicating the lifecycle/fulfilment state of a document in a record detail right rail. It shows an optional completion percentage, a stacked progress bar, and a legend — driven by an arbitrary set of status `segments`.
11
+
12
+ It is view-only and domain-agnostic: pass the segments and an explicit `percent`. Any domain-specific math — deriving the percentage, or decomposing overlapping buckets into bar segments — lives in the consumer. See [Example: purchase-order fulfilment](#example-purchase-order-fulfilment) for a complete, copyable pattern.
13
+
14
+ ## Import
15
+
16
+ ```tsx
17
+ import { DocumentProgressCard } 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
+ return (
27
+ <DocumentProgressCard
28
+ title="Shipment status"
29
+ percent={60}
30
+ segments={[
31
+ { label: "Shipped", value: 30, color: "green" },
32
+ { label: "Returned", value: 3, color: "red" },
33
+ { label: "Pending", value: 17, color: "neutral" },
34
+ ]}
35
+ />
36
+ );
37
+ }
38
+ ```
39
+
40
+ ## Props
41
+
42
+ _Generated from the type surface — first-party props only._
43
+
44
+ ### DocumentProgressCardProps
45
+
46
+ | Prop | Type | Default | Description |
47
+ | ------------ | --------------------------- | ------- | ---------------------------------------------------------------------------------------------------------------------------------------------------- |
48
+ | `className?` | `string` | - | Additional CSS classes for the card root. |
49
+ | `legend?` | `DocumentProgressSegment[]` | - | Legend rows. Defaults to `segments`. Provide this only when the legend should differ from the bar (e.g. overlapping buckets shown distinctly). |
50
+ | `percent?` | `number` | - | Headline percentage shown top-right (0–100). Optional and explicit — the generic card has no notion of "complete", so the consumer supplies it. |
51
+ | `segments` | `DocumentProgressSegment[]` | - | Status segments rendered as a single stacked bar (and, by default, the legend). |
52
+ | `title?` | `ReactNode` | - | Optional card title shown top-left. |
53
+ | `total?` | `number` | - | Denominator used to size the bar. Defaults to the sum of segment values (the bar is fully tiled). A larger value leaves an unfilled track remainder. |
54
+
55
+ ## Exports
56
+
57
+ - `DocumentProgressCard` — function
58
+ - `DocumentProgressCardProps` — interface
59
+
60
+ ### `DocumentProgressSegment`
61
+
62
+ | Field | Type | Description |
63
+ | ------- | ----------------------- | ------------------------------------------------------ |
64
+ | `label` | `string` | Legend label. |
65
+ | `value` | `number` | Amount — shown in the legend and used to size the bar. |
66
+ | `color` | `DocumentProgressColor` | Bar / marker color (required). |
67
+
68
+ `DocumentProgressColor` is one of: `"indigo"`, `"pink"`, `"green"`, `"amber"`, `"red"`, `"blue"`, `"neutral"`.
69
+
70
+ ## Progress Bar
71
+
72
+ The bar tiles `segments` left-to-right, each sized as `value / (total ?? sum of values)`. When `total` exceeds the segment sum, the shortfall renders as an empty `bg-muted` track — useful for a "remaining" portion you don't want as a colored segment. A `"neutral"` segment reads as a muted/track-like fill.
73
+
74
+ ## Legend
75
+
76
+ By default the legend mirrors `segments`. Pass `legend` to render different rows — for example when buckets overlap and the bar shows a decomposition while the legend shows the raw figures:
77
+
78
+ ```tsx
79
+ <DocumentProgressCard
80
+ percent={30}
81
+ total={40}
82
+ segments={[
83
+ { label: "Net received", value: 10, color: "indigo" },
84
+ { label: "Returned", value: 2, color: "pink" },
85
+ ]}
86
+ legend={[
87
+ { label: "Received items", value: 12, color: "indigo" },
88
+ { label: "Returned items", value: 2, color: "pink" },
89
+ { label: "Yet to receive", value: 28, color: "neutral" },
90
+ ]}
91
+ />
92
+ ```
93
+
94
+ ## Example: purchase-order fulfilment
95
+
96
+ A common use is communicating a purchase order's receiving state — **received**, **returned**, and **yet to receive**. `DocumentProgressCard` stays generic, so derive the percentage and the bar breakdown in your component and pass them in. This recipe reproduces the recommended look (labels, indigo/pink/neutral colors, and a bar that decomposes received into "kept" + "returned" against the ordered total):
97
+
98
+ ```tsx
99
+ function PurchaseOrderFulfilment({
100
+ received,
101
+ returned,
102
+ yetToReceive,
103
+ // Whether returned items still count as fulfilled. Set false to subtract them.
104
+ returnedCountsAsComplete = true,
105
+ }: {
106
+ received: number;
107
+ returned: number;
108
+ yetToReceive: number;
109
+ returnedCountsAsComplete?: boolean;
110
+ }) {
111
+ // Returned is a subset of received; clamp so the breakdown can't exceed it.
112
+ const effectiveReturned = Math.min(Math.max(returned, 0), Math.max(received, 0));
113
+ const total = Math.max(received, 0) + Math.max(yetToReceive, 0); // the ordered quantity
114
+ const complete = returnedCountsAsComplete ? received : received - effectiveReturned;
115
+ const percent = total > 0 ? Math.round((complete / total) * 100) : 0;
116
+
117
+ return (
118
+ <DocumentProgressCard
119
+ title="Fulfilment rate"
120
+ percent={percent}
121
+ total={total} // ordered quantity — the shortfall renders as the "yet to receive" track
122
+ // Bar: net received (kept) + returned. Yet-to-receive is the unfilled remainder.
123
+ segments={[
124
+ { label: "Received items", value: received - effectiveReturned, color: "indigo" },
125
+ { label: "Returned items", value: effectiveReturned, color: "pink" },
126
+ ]}
127
+ // Legend: the three buckets as-is, so "Received items" shows the full amount.
128
+ legend={[
129
+ { label: "Received items", value: received, color: "indigo" },
130
+ { label: "Returned items", value: returned, color: "pink" },
131
+ { label: "Yet to receive", value: yetToReceive, color: "neutral" },
132
+ ]}
133
+ />
134
+ );
135
+ }
136
+
137
+ // <PurchaseOrderFulfilment received={12} returned={2} yetToReceive={28} /> → 30%
138
+ ```
139
+
140
+ Notes for adapting it:
141
+
142
+ - **Colors:** `indigo` (received), `pink` (returned), `neutral` (yet to receive) is the recommended set; swap any of the seven palette colors to fit your domain.
143
+ - **Bar vs. legend:** the bar uses `received − returned` so the two colored segments don't double-count the returned items, while the `legend` override keeps "Received items" showing the full received figure.
144
+ - **`total`:** pass the ordered quantity so the unfilled portion of the bar represents "yet to receive". Omit it (or use the segment sum) if you want a fully-tiled bar instead.
145
+ - **Percentage policy:** flip `returnedCountsAsComplete` to decide whether returned items count toward completion.
146
+
147
+ ## Input handling
148
+
149
+ Segment values are expected to be non-negative numbers. Non-finite or negative values are coerced to `0`, and `percent` is rounded and clamped to `[0, 100]`.
150
+
151
+ ## Related
152
+
153
+ - [MetricCard](./metric-card.md) — Compact KPI summary card.
154
+ - [Card](./card.md) — The underlying card primitive.
@@ -0,0 +1,165 @@
1
+ ---
2
+ title: FilterRail
3
+ description: An always-visible faceted filter rail with user layout settings, driven by the same CollectionControl as DataTable
4
+ ---
5
+
6
+ <!-- GENERATED by docs-kit from docs-src/components/filter-rail.docs.outline.md — do not edit by hand. -->
7
+
8
+ # FilterRail
9
+
10
+ `FilterRail` shows every filterable axis beside the results instead of hiding it behind a filter dropdown. Each section shows its options up front, so users see what they can filter by without opening anything.
11
+
12
+ It writes the same `CollectionControl` a `DataTable` reads, so there is no second filter engine: a checkbox section **is** the `in` operator, a radio section is `eq`, a range is `between`.
13
+
14
+ ```tsx
15
+ import { FilterRail, type FilterRailSection } from "@tailor-platform/app-shell";
16
+
17
+ const sections: FilterRailSection[] = [
18
+ {
19
+ id: "category",
20
+ label: "Category",
21
+ control: "checkbox",
22
+ field: "category",
23
+ options: categories,
24
+ },
25
+ { id: "status", label: "Status", control: "radio", field: "status", options: statuses },
26
+ ];
27
+
28
+ <FilterRail.Root control={control} sections={sections}>
29
+ <FilterRail.Sections />
30
+ </FilterRail.Root>;
31
+ ```
32
+
33
+ ## Sections and parts
34
+
35
+ What the rail shows is **config**: `sections`, one entry per axis, in screen order. How the rail is assembled is **parts**: leave a part out and that feature is not rendered.
36
+
37
+ | Part | Renders |
38
+ | --------------------- | ----------------------------------------------------------------------------- |
39
+ | `FilterRail.Root` | The rail. Takes `control`, `sections`, `layout`. |
40
+ | `FilterRail.Header` | A title row with a slot for actions. |
41
+ | `FilterRail.ClearAll` | Clears the rail's own filters only. Hidden while none are active. |
42
+ | `FilterRail.Settings` | Show / hide, reorder and sort sections. Needs `layout`. |
43
+ | `FilterRail.Sections` | The sections, in the user's layout. Scrolls while the header stays put. |
44
+ | `FilterRail.Provider` | Holds the config when a `Trigger` sits outside the rail (see Narrow screens). |
45
+ | `FilterRail.Trigger` | The narrow-screen "Filters" button, placed anywhere inside `Provider`. |
46
+
47
+ ```tsx
48
+ <FilterRail.Root control={control} sections={sections} layout={layout}>
49
+ <FilterRail.Header>
50
+ <FilterRail.ClearAll />
51
+ <FilterRail.Settings />
52
+ </FilterRail.Header>
53
+ <FilterRail.Sections />
54
+ </FilterRail.Root>
55
+ ```
56
+
57
+ ### Section types
58
+
59
+ Every section has an `id`, a `label` and an optional `hint`. The operator is fixed per section — the user picks values, never operators.
60
+
61
+ | `control` | Commits | Notes |
62
+ | ------------- | ------------------------------------------ | ----------------------------------------------------------- |
63
+ | `checkbox` | `in` (or `nin`) with the ticked values | `sort`, `truncate` (default 8), `pinned`, `searchThreshold` |
64
+ | `radio` | `eq` (or `ne`) with one value | An "Any" row removes the filter; `anyLabel: null` hides it |
65
+ | `boolean` | `eq` with `true` / `false` | Three states: Any, `trueLabel`, `falseLabel` |
66
+ | `text` | `contains`, `hasPrefix` or `eq` | Debounced (`debounceMs`, default 250) |
67
+ | `numberRange` | `between` `{ min, max }`, or `gte` / `lte` | Commits on blur / Enter; one end alone is half-open |
68
+ | `date` | `eq`, `gte` or `lte` a `YYYY-MM-DD` | Uses `DatePicker` |
69
+ | `dateRange` | `between` `{ min, max }` | Uses `DateRangePicker`; optional `presets` |
70
+ | `custom` | Whatever `render` writes | Declares the `fields` it owns so Clear all includes them |
71
+
72
+ A checkbox list long enough to be truncated also gets a search box, so a value can be found without scrolling (`searchThreshold` overrides when it appears).
73
+
74
+ A field holds one filter, so two sections must not share a field; the rail warns in the console when they do.
75
+
76
+ #### Typing fields
77
+
78
+ Give `FilterRailSection` the row's keys so a `field` that is not on the table is a type error, the same way `createColumnHelper<Row>()` types columns. This works with a typed control or a plain one:
79
+
80
+ ```tsx
81
+ const sections: FilterRailSection<keyof Product & string>[] = [
82
+ { id: "category", label: "Category", control: "checkbox", field: "category", options },
83
+ // field: "categroy" → type error
84
+ ];
85
+ ```
86
+
87
+ With a typed control (from `useCollectionVariables({ tableMetadata })`), a section whose field the control does not have is also rejected.
88
+
89
+ ```tsx
90
+ const stockLevel: FilterRailSection = {
91
+ id: "stockLevel",
92
+ label: "Stock level",
93
+ control: "custom",
94
+ fields: ["stock"],
95
+ render: ({ getFilter, setFilter }) => (
96
+ <Button
97
+ variant="outline"
98
+ size="xs"
99
+ onClick={() => setFilter("stock", { operator: "between", value: { min: 1, max: 20 } })}
100
+ >
101
+ Low stock
102
+ </Button>
103
+ ),
104
+ };
105
+ ```
106
+
107
+ The rail never hides state: a selected option is never truncated away, never disabled and never hidden by a section's search box.
108
+
109
+ ### Collapsing sections
110
+
111
+ Each section collapses and expands from the chevron by its title. Sections start expanded; set `defaultCollapsed: true` to start one collapsed (for a rarely used axis). While a section is collapsed, its header still shows how many values are selected and keeps its **Clear**, so a collapsed section never hides an applied filter. The open / closed state is kept while the page is open, including across the narrow-screen sheet opening and closing.
112
+
113
+ ```tsx
114
+ { id: "warehouse", label: "Warehouse", control: "checkbox", field: "warehouse", options, defaultCollapsed: true }
115
+ ```
116
+
117
+ ## Choosing a control
118
+
119
+ Checkbox and radio sections list every option, so they are only for **small, fixed sets** whose values you know up front — status, category, size, a handful of warehouses. Their `options` are a static array; the rail does not fetch them.
120
+
121
+ Do **not** use a checkbox or radio list for a field whose values **keep growing** — product names, SKUs, customers, suppliers, order numbers. The list gets longer with every new record, takes over the rail, and becomes a scroll hunt. Give those fields a search instead:
122
+
123
+ | The field's values are… | Use |
124
+ | ------------------------------------------------ | --------------------------------------------------------------------------- |
125
+ | A small fixed set (≈ up to 20), pick any | `checkbox` |
126
+ | A small fixed set, pick one | `radio` (or `boolean` for yes / no) |
127
+ | Open-ended and growing (names, codes, customers) | `text` — a search that commits `contains`, `hasPrefix` or `eq` |
128
+ | Open-ended, but users must pick exact records | a `custom` section rendering a searchable picker (e.g. an async `Combobox`) |
129
+ | Numbers or dates | `numberRange`, `date`, `dateRange` |
130
+
131
+ In development the rail warns in the console when a checkbox or radio section lists more than 50 options, as a nudge towards a search.
132
+
133
+ ## User layout
134
+
135
+ `useFilterRailLayout(storageKey)` persists the user's section order, hidden sections and option sort to localStorage. Pass it as `layout` and add `FilterRail.Settings`. Hiding a section that carries a filter also clears that filter. To share a layout across a team, pass your own `{ value, onChange, onReset }` binding instead.
136
+
137
+ ## Beside a DataTable
138
+
139
+ The rail sits in its own `Layout.Column`, outside `DataTable.Root`, which is why `control` is a prop. Do not also render `DataTable.Filters` for the same fields — one fact in two places can drift.
140
+
141
+ ## Narrow screens
142
+
143
+ Below the `lg` breakpoint there is no room for a side column, so the rail turns itself into a left sheet behind a **Filters** button carrying the active count — the same way the Sidebar handles small screens. Apps write no breakpoint code.
144
+
145
+ By default the button appears where the rail would have been. To put it somewhere else — a table toolbar, a page header — wrap the page in `FilterRail.Provider`, move the config there, and place `FilterRail.Trigger` wherever you want it. The Trigger renders nothing on wide screens, and while one is mounted the rail skips its own button.
146
+
147
+ ```tsx
148
+ <FilterRail.Provider control={control} sections={sections}>
149
+ <Layout fill>
150
+ <Layout.Column area="left">
151
+ <FilterRail.Root>
152
+ <FilterRail.Sections />
153
+ </FilterRail.Root>
154
+ </Layout.Column>
155
+ <Layout.Column area="main">
156
+ <DataTable.Root value={table}>
157
+ <DataTable.Toolbar>
158
+ <FilterRail.Trigger />
159
+ </DataTable.Toolbar>
160
+ <DataTable.Table />
161
+ </DataTable.Root>
162
+ </Layout.Column>
163
+ </Layout>
164
+ </FilterRail.Provider>
165
+ ```