@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.
- package/dist/app-shell-core.css +1 -1
- package/dist/app-shell.d.ts +811 -131
- package/dist/app-shell.js +7155 -5797
- package/dist/{appshell-DLVBFo5J.js → appshell-CahhZy7v.js} +656 -524
- package/dist/semantic-contrast.test.ts +154 -0
- package/dist/style-entry.d.ts +147 -92
- package/dist/testing.d.ts +152 -95
- package/dist/testing.js +1 -1
- package/dist/theme-bridge.test.ts +101 -1
- package/dist/theme.bridge.css +91 -0
- package/dist/theme.css +6 -1
- package/dist/themes/_template.css +7 -1
- package/dist/themes/bloom.css +0 -6
- package/dist/themes/cream.css +0 -6
- package/dist/themes/default.css +389 -69
- package/dist/vite-plugin.d.ts +147 -92
- package/package.json +8 -8
- package/skills/app-shell-patterns/SKILL.md +108 -19
- package/skills/app-shell-patterns/references/api/create-ai-gateway-client.md +115 -0
- package/skills/app-shell-patterns/references/api/create-typed-paths.md +117 -0
- package/skills/app-shell-patterns/references/api/define-i18n-labels.md +143 -0
- package/skills/app-shell-patterns/references/api/define-module.md +247 -0
- package/skills/app-shell-patterns/references/api/define-resource.md +307 -0
- package/skills/app-shell-patterns/references/api/guards/hidden.md +202 -0
- package/skills/app-shell-patterns/references/api/guards/overview.md +354 -0
- package/skills/app-shell-patterns/references/api/guards/pass.md +129 -0
- package/skills/app-shell-patterns/references/api/guards/redirect-to.md +307 -0
- package/skills/app-shell-patterns/references/api/router/use-location.md +240 -0
- package/skills/app-shell-patterns/references/api/router/use-navigate.md +138 -0
- package/skills/app-shell-patterns/references/api/router/use-params.md +218 -0
- package/skills/app-shell-patterns/references/api/use-ai-chat.md +110 -0
- package/skills/app-shell-patterns/references/api/use-app-shell-config.md +132 -0
- package/skills/app-shell-patterns/references/api/use-app-shell-data.md +150 -0
- package/skills/app-shell-patterns/references/api/use-app-shell.md +150 -0
- package/skills/app-shell-patterns/references/api/use-auth.md +145 -0
- package/skills/app-shell-patterns/references/api/use-override-breadcrumb.md +74 -0
- package/skills/app-shell-patterns/references/api/use-page-meta.md +96 -0
- package/skills/app-shell-patterns/references/api/use-register-command-palette-actions.md +90 -0
- package/skills/app-shell-patterns/references/api/use-resolved-locale.md +67 -0
- package/skills/app-shell-patterns/references/api/use-route-error.md +214 -0
- package/skills/app-shell-patterns/references/api/use-theme.md +108 -0
- package/skills/app-shell-patterns/references/api/use-time-zone.md +80 -0
- package/skills/app-shell-patterns/references/api/use-toast.md +104 -0
- package/skills/app-shell-patterns/references/components/action-panel.md +168 -0
- package/skills/app-shell-patterns/references/components/activity-card.md +142 -0
- package/skills/app-shell-patterns/references/components/ai-chat.md +315 -0
- package/skills/app-shell-patterns/references/components/alert.md +202 -0
- package/skills/app-shell-patterns/references/components/app-shell.md +530 -0
- package/skills/app-shell-patterns/references/components/appearance-switcher.md +87 -0
- package/skills/app-shell-patterns/references/components/attachment.md +138 -0
- package/skills/app-shell-patterns/references/components/autocomplete.md +278 -0
- package/skills/app-shell-patterns/references/components/avatar.md +80 -0
- package/skills/app-shell-patterns/references/components/badge.md +309 -0
- package/skills/app-shell-patterns/references/components/button.md +164 -0
- package/skills/app-shell-patterns/references/components/card.md +181 -0
- package/skills/app-shell-patterns/references/components/checkbox.md +169 -0
- package/skills/app-shell-patterns/references/components/combobox.md +318 -0
- package/skills/app-shell-patterns/references/components/command-palette.md +413 -0
- package/skills/app-shell-patterns/references/components/csv-importer.md +214 -0
- package/skills/app-shell-patterns/references/components/data-table.md +1168 -0
- package/skills/app-shell-patterns/references/components/date-picker.md +259 -0
- package/skills/app-shell-patterns/references/components/default-header.md +75 -0
- package/skills/app-shell-patterns/references/components/default-sidebar.md +136 -0
- package/skills/app-shell-patterns/references/components/description-card.md +569 -0
- package/skills/app-shell-patterns/references/components/dialog.md +164 -0
- package/skills/app-shell-patterns/references/components/document-progress-card.md +154 -0
- package/skills/app-shell-patterns/references/components/filter-rail.md +165 -0
- package/skills/app-shell-patterns/references/components/form.md +378 -0
- package/skills/app-shell-patterns/references/components/global-header-layout.md +132 -0
- package/skills/app-shell-patterns/references/components/grid.md +184 -0
- package/skills/app-shell-patterns/references/components/input.md +83 -0
- package/skills/app-shell-patterns/references/components/layout.md +539 -0
- package/skills/app-shell-patterns/references/components/menu.md +218 -0
- package/skills/app-shell-patterns/references/components/metric-card.md +94 -0
- package/skills/app-shell-patterns/references/components/select.md +283 -0
- package/skills/app-shell-patterns/references/components/sheet.md +237 -0
- package/skills/app-shell-patterns/references/components/sidebar-group.md +472 -0
- package/skills/app-shell-patterns/references/components/sidebar-item.md +458 -0
- package/skills/app-shell-patterns/references/components/sidebar-layout.md +258 -0
- package/skills/app-shell-patterns/references/components/sidebar-section.md +114 -0
- package/skills/app-shell-patterns/references/components/spinner.md +81 -0
- package/skills/app-shell-patterns/references/components/table.md +188 -0
- package/skills/app-shell-patterns/references/components/tabs.md +158 -0
- package/skills/app-shell-patterns/references/components/textarea.md +122 -0
- package/skills/app-shell-patterns/references/components/timeline.md +317 -0
- package/skills/app-shell-patterns/references/components/toolbar.md +138 -0
- package/skills/app-shell-patterns/references/components/tooltip.md +144 -0
- package/skills/app-shell-patterns/references/components/with-guard.md +445 -0
- package/skills/app-shell-patterns/references/concepts/authentication.md +301 -0
- package/skills/app-shell-patterns/references/concepts/custom-components.md +59 -0
- package/skills/app-shell-patterns/references/concepts/file-based-routing.md +356 -0
- package/skills/app-shell-patterns/references/{fundamental → concepts}/graphql.md +31 -18
- package/skills/app-shell-patterns/references/concepts/modules-and-resources.md +229 -0
- package/skills/app-shell-patterns/references/concepts/routing-navigation.md +425 -0
- package/skills/app-shell-patterns/references/concepts/sidebar-navigation.md +420 -0
- package/skills/app-shell-patterns/references/concepts/styling-theming.md +785 -0
- package/skills/app-shell-patterns/references/migrations.md +55 -0
- package/skills/app-shell-patterns/references/pages/{detail.md → document-detail.md} +10 -33
- package/skills/app-shell-patterns/references/patterns/form-composer.md +43 -45
- package/skills/app-shell-patterns/references/patterns/form-modal.md +37 -92
- package/skills/app-shell-patterns/references/patterns/form-sectioned.md +12 -38
- package/skills/app-shell-patterns/references/patterns/form-single-page.md +11 -32
- package/skills/app-shell-patterns/references/patterns/form-wizard.md +12 -53
- package/skills/app-shell-patterns/references/patterns/interaction-confirm.md +11 -27
- package/skills/app-shell-patterns/references/patterns/interaction-multi-select.md +136 -139
- package/skills/app-shell-patterns/references/patterns/interaction-toast.md +10 -34
- package/skills/app-shell-patterns/references/patterns/list-dense-scan.md +13 -88
- package/skills/app-shell-patterns/references/fundamental/components.md +0 -1009
- 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
|
+
```
|