@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
@@ -1,22 +1,11 @@
1
1
  ---
2
- slug: pattern/form/wizard
3
- name: Wizard Form
4
- category: pattern
5
- subcategory: form
2
+ title: Wizard Form
6
3
  description: Multi-stage create flow with 3-7 steps and per-step validation gates
7
- requiredImports: [Layout, Card, Badge, Form, Fieldset, Field, Select, Button]
8
- tags: [form, wizard, multi-step, stepper, onboarding]
9
- do:
10
- - Multi-stage Create with 3-7 steps
11
- - Onboarding and request flows where users should focus on one step at a time
12
- - Per-step validation gates progression
13
- dont:
14
- - Single screen of fields — use form/modal or form/single-page
15
- - More than 7 steps — split into separate routed pages or reduce scope
16
- - CSV/spreadsheet import — use the CsvImporter component, not a hand-built wizard
17
4
  ---
18
5
 
19
- # pattern/form/wizard
6
+ <!-- GENERATED by docs-kit from docs-src/patterns/form-wizard.docs.outline.md — do not edit by hand. -->
7
+
8
+ # Wizard Form
20
9
 
21
10
  ## When to Use
22
11
 
@@ -29,45 +18,13 @@ the upload → map → validate → confirm flow. Don't rebuild it here.
29
18
 
30
19
  ## Page Implementation
31
20
 
21
+ <example-preview name="form-wizard"></example-preview>
22
+
32
23
  ```tsx
33
- /* pattern: form/wizard */
34
- import { useState } from "react";
35
- import {
36
- Badge,
37
- Button,
38
- Card,
39
- Field,
40
- Fieldset,
41
- Form,
42
- Layout,
43
- Select,
44
- } from "@tailor-platform/app-shell";
45
-
46
- const OWNERS = ["Tanaka", "Sato", "Suzuki", "Yamada"];
47
- const STEPS = ["Basic info", "Assignment", "Schedule", "Review"] as const;
48
-
49
- type Draft = {
50
- title: string;
51
- description: string;
52
- owner: string;
53
- startDate: string;
54
- estimate: string;
55
- };
56
-
57
- const INITIAL: Draft = {
58
- title: "",
59
- description: "",
60
- owner: "",
61
- startDate: "",
62
- estimate: "",
63
- };
64
-
65
- type Props = {
66
- onComplete: (draft: Draft) => void;
67
- onCancel: () => void;
68
- };
69
-
70
- export default function WizardForm({ onComplete, onCancel }: Props) {
24
+ function FormWizard() {
25
+ const onComplete = (finalDraft: Draft) => window.alert(`Creating ${finalDraft.title}`);
26
+ const onCancel = () => {};
27
+
71
28
  const [step, setStep] = useState(0);
72
29
  // Accumulated values. Each step's Form unmounts on navigation, so the draft
73
30
  // is what makes Back non-destructive — fields re-read it via `defaultValue`.
@@ -187,6 +144,8 @@ export default function WizardForm({ onComplete, onCancel }: Props) {
187
144
  }
188
145
  ```
189
146
 
147
+ <!-- source: wizard-form.tsx -->
148
+
190
149
  ## Constraints
191
150
 
192
151
  - Max 7 steps — more than that causes user abandonment
@@ -1,21 +1,11 @@
1
1
  ---
2
- slug: pattern/interaction/confirm
3
- name: Confirm
4
- category: pattern
5
- subcategory: interaction
2
+ title: Confirm
6
3
  description: Confirmation dialog before destructive or irreversible actions
7
- requiredImports: [Dialog, Button, Input]
8
- tags: [dialog, confirm, destructive, delete, irreversible]
9
- do:
10
- - Before a destructive or irreversible action (delete, cancel, void, archive)
11
- - Before bulk actions that affect many records
12
- - When the action's consequence isn't obvious from the trigger
13
- dont:
14
- - Routine reversible actions — use interaction/toast with optional Undo instead
15
- - Form submission for non-destructive create/edit — submit handlers don't need a confirm
16
4
  ---
17
5
 
18
- # pattern/interaction/confirm
6
+ <!-- GENERATED by docs-kit from docs-src/patterns/interaction-confirm.docs.outline.md — do not edit by hand. -->
7
+
8
+ # Confirm
19
9
 
20
10
  ## When to Use
21
11
 
@@ -25,31 +15,23 @@ dont:
25
15
 
26
16
  ## Page Implementation
27
17
 
28
- ```tsx
29
- /* pattern: interaction/confirm */
30
- import { Button, Dialog } from "@tailor-platform/app-shell";
31
-
32
- type Props = {
33
- orderId: string;
34
- onDelete: () => void;
35
- };
18
+ <example-preview name="interaction-confirm"></example-preview>
36
19
 
37
- export default function ConfirmDialog({ orderId, onDelete }: Props) {
20
+ ```tsx
21
+ function InteractionConfirm() {
38
22
  return (
39
23
  <Dialog.Root>
40
24
  <Dialog.Trigger render={<Button variant="destructive" />}>Delete</Dialog.Trigger>
41
25
  <Dialog.Content>
42
26
  <Dialog.Header>
43
- <Dialog.Title>Delete order {orderId}?</Dialog.Title>
27
+ <Dialog.Title>Delete order ORD-1024?</Dialog.Title>
44
28
  <Dialog.Description>
45
29
  This will remove the order and all its line items. This action cannot be undone.
46
30
  </Dialog.Description>
47
31
  </Dialog.Header>
48
32
  <Dialog.Footer>
49
33
  <Dialog.Close render={<Button variant="outline" />}>Cancel</Dialog.Close>
50
- <Button variant="destructive" onClick={onDelete}>
51
- Delete
52
- </Button>
34
+ <Dialog.Close render={<Button variant="destructive" />}>Delete</Dialog.Close>
53
35
  </Dialog.Footer>
54
36
  </Dialog.Content>
55
37
  </Dialog.Root>
@@ -57,6 +39,8 @@ export default function ConfirmDialog({ orderId, onDelete }: Props) {
57
39
  }
58
40
  ```
59
41
 
42
+ <!-- source: confirm.tsx -->
43
+
60
44
  ## Copy Rules
61
45
 
62
46
  - Title is a question, names the object: "Delete order ORD-1234?", "Cancel invoice INV-001?"
@@ -1,155 +1,145 @@
1
1
  ---
2
- slug: pattern/interaction/multi-select
3
- name: Multi Select
4
- category: pattern
5
- subcategory: interaction
6
- description: Floating bottom action bar for bulk operations on selected list rows
7
- requiredImports: [Table, Checkbox, Button, Menu]
8
- tags: [bulk, selection, toolbar, floating-bar, multi-select, batch]
9
- do:
10
- - ANY list page where rows can be acted on in bulk (archive, assign, export, approve, delete)
11
- - Selection is initiated by clicking a leading-column checkbox on rows
12
- - Selection state needs to persist across pagination and filter changes
13
- dont:
14
- - A list where bulk action is genuinely impossible (single-select only)
15
- - A pure picker/selector inside a Dialog whose footer already gates the action
16
- - Destructive bulk action triggered without confirmation — pair with interaction/confirm
2
+ title: Multi Select
3
+ description: Bulk operations on selected DataTable rows, from a bar that takes over the table footer
17
4
  ---
18
5
 
19
- # pattern/interaction/multi-select
6
+ <!-- GENERATED by docs-kit from docs-src/patterns/interaction-multi-select.docs.outline.md — do not edit by hand. -->
7
+
8
+ # Multi Select
20
9
 
21
10
  ## When to Use
22
11
 
23
12
  - ANY list page where rows can be acted on in bulk (archive, assign, export, approve, delete)
24
- - Selection is initiated by clicking a leading-column checkbox on rows
25
- - Selection state needs to persist across pagination and filter changes
13
+ - The list is a `DataTable` — selection and the bulk-action bar are built in; you only declare the actions
14
+ - Some actions apply to only part of a selection (approve only pending rows, delete only archived ones)
26
15
 
27
16
  ## Layout
28
17
 
29
- Floating action bar appears the moment selection count goes from 0 → 1, anchored to the bottom of the viewport, centered horizontally, with elevation. It disappears when selection returns to 0.
18
+ Pass `rowSelection.actions` to `useDataTable`. The moment one row is selected, `DataTable.Footer` becomes the bulk-action bar: a tinted strip with the selection count, the actions, and Clear, with pagination kept on the right. It turns back into the normal footer when the selection empties. In `<Layout fill>` the footer is already pinned; on a page that scrolls, the bar sticks to the bottom of the viewport until the end of the table is reached.
30
19
 
31
20
  ```
32
- +---------------------------------------------------------+
33
- | Layout.Header title [Filter] [Create] |
34
- +---------------------------------------------------------+
35
- | Layout.Column |
36
- | Table.Root |
37
- | [x] | Col | Col | Col | Col |
38
- | [x] | row | row | row | row |
39
- | [ ] | row | row | row | row |
40
- | [x] | row | row | row | row |
41
- | |
42
- | +--------------------------------------+ |
43
- | | 3 selected [Archive] [Export] [⋯] [Clear] | |
44
- | +--------------------------------------+ |
45
- +---------------------------------------------------------+
21
+ +--------------------------------------------------------------------+
22
+ | Layout.Header title [Create] |
23
+ +--------------------------------------------------------------------+
24
+ | Layout.Column |
25
+ | DataTable.Root |
26
+ | Toolbar [Filters] [Columns] |
27
+ | [x] | Col | Col | Col | Col |
28
+ | [x] | row | row | row | row |
29
+ | [ ] | row | row | row | row |
30
+ | [x] | row | row | row | row |
31
+ | DataTable.Footer, while rows are selected: |
32
+ | 3 of 240 selected | [Confirm] [Export] [⋯] | Clear ‹ 1/10 › |
33
+ +--------------------------------------------------------------------+
46
34
  ```
47
35
 
48
36
  ## Page Implementation
49
37
 
50
- ```tsx
51
- /* pattern: interaction/multi-select */
52
- import { useState } from "react";
53
- import { Button, Table, Menu } from "@tailor-platform/app-shell";
54
- import type { Order } from "./mock";
55
-
56
- type Props = {
57
- orders: Order[];
58
- onArchive: (ids: string[]) => void;
59
- onExport: (ids: string[]) => void;
60
- };
61
-
62
- export default function MultiSelect({ orders, onArchive, onExport }: Props) {
63
- const [selectedIds, setSelectedIds] = useState<Set<string>>(new Set());
64
-
65
- const toggleRow = (id: string) => {
66
- setSelectedIds((prev) => {
67
- const next = new Set(prev);
68
- if (next.has(id)) next.delete(id);
69
- else next.add(id);
70
- return next;
71
- });
72
- };
38
+ <example-preview name="interaction-multi-select"></example-preview>
73
39
 
74
- const toggleAll = () => {
75
- if (selectedIds.size === orders.length) {
76
- setSelectedIds(new Set());
77
- } else {
78
- setSelectedIds(new Set(orders.map((o) => o.id)));
79
- }
40
+ ```tsx
41
+ function InteractionMultiSelect() {
42
+ const [selectedIds, setSelectedIds] = useState<string[]>([]);
43
+ const [pendingCancel, setPendingCancel] = useState<PendingCancel | null>(null);
44
+ const [cancelling, setCancelling] = useState(false);
45
+ const [cancelError, setCancelError] = useState<string | null>(null);
46
+
47
+ const removeCompletedIds = (ids: string[]) => {
48
+ const completed = new Set(ids);
49
+ setSelectedIds((current) => current.filter((id) => !completed.has(id)));
80
50
  };
81
51
 
82
- const clearSelection = () => setSelectedIds(new Set());
83
- const selectedCount = selectedIds.size;
52
+ const actions: SelectionAction[] = [
53
+ {
54
+ id: "confirm",
55
+ label: "Confirm",
56
+ // The operation returns actual successes; never clear ineligible or newer selections.
57
+ onClick: async (ids) => removeCompletedIds(await confirmOrders(ids)),
58
+ },
59
+ {
60
+ id: "export",
61
+ label: "Export",
62
+ onClick: (ids) => exportOrders(ids),
63
+ },
64
+ {
65
+ id: "assign",
66
+ label: "Assign owner",
67
+ onClick: async (ids) => window.alert(`Assigning ${ids.length} order(s)`),
68
+ },
69
+ {
70
+ // 4th action: lands in the More actions menu.
71
+ id: "cancel",
72
+ label: "Cancel",
73
+ variant: "destructive",
74
+ onClick: (ids, { run }) => {
75
+ setCancelError(null);
76
+ setPendingCancel({ ids: [...ids], run });
77
+ },
78
+ },
79
+ ];
80
+
81
+ // In a real app, fetch eligibility for these snapshot ids before showing this explanation.
82
+ const cancellableCount = mockOrders.filter(
83
+ (order) => pendingCancel?.ids.includes(order.id) && order.status !== "Shipped",
84
+ ).length;
85
+
86
+ const table = useDataTable({
87
+ columns,
88
+ data: { rows: mockOrders, total: mockOrders.length },
89
+ rowSelection: { selectedIds, onChange: setSelectedIds, actions },
90
+ });
84
91
 
85
92
  return (
86
93
  <>
87
- <Table.Root>
88
- <Table.Header>
89
- <Table.Row>
90
- <Table.Head className="w-10">
91
- <input
92
- type="checkbox"
93
- checked={selectedIds.size === orders.length && orders.length > 0}
94
- onChange={toggleAll}
95
- aria-label="Select all on page"
96
- />
97
- </Table.Head>
98
- <Table.Head>Order #</Table.Head>
99
- <Table.Head>Status</Table.Head>
100
- <Table.Head>Total</Table.Head>
101
- </Table.Row>
102
- </Table.Header>
103
- <Table.Body>
104
- {orders.map((order) => (
105
- <Table.Row key={order.id}>
106
- <Table.Cell>
107
- <input
108
- type="checkbox"
109
- checked={selectedIds.has(order.id)}
110
- onChange={() => toggleRow(order.id)}
111
- aria-label={`Select ${order.number}`}
112
- />
113
- </Table.Cell>
114
- <Table.Cell>{order.number}</Table.Cell>
115
- <Table.Cell>{order.status}</Table.Cell>
116
- <Table.Cell>${order.total.toLocaleString()}</Table.Cell>
117
- </Table.Row>
118
- ))}
119
- </Table.Body>
120
- </Table.Root>
121
-
122
- {selectedCount > 0 && (
123
- <div
124
- role="toolbar"
125
- aria-label="Bulk actions"
126
- className="fixed bottom-4 left-1/2 z-30 flex -translate-x-1/2 items-center gap-2 rounded-md border bg-popover px-3 py-2 shadow-lg"
127
- >
128
- <span className="text-sm font-medium">{selectedCount} selected</span>
129
- <Button size="sm" onClick={() => onArchive([...selectedIds])}>
130
- Archive
131
- </Button>
132
- <Button size="sm" variant="outline" onClick={() => onExport([...selectedIds])}>
133
- Export
134
- </Button>
135
- <Menu.Root>
136
- <Menu.Trigger>
137
- <Button size="sm" variant="outline" aria-label="More actions">
138
- ⋯
139
- </Button>
140
- </Menu.Trigger>
141
- <Menu.Content>
142
- <Menu.Item>Assign owner</Menu.Item>
143
- <Menu.Item>Tag</Menu.Item>
144
- <Menu.Separator />
145
- <Menu.Item>Delete</Menu.Item>
146
- </Menu.Content>
147
- </Menu.Root>
148
- <Button size="sm" variant="ghost" onClick={clearSelection}>
149
- Clear
150
- </Button>
151
- </div>
152
- )}
94
+ <DataTable.Root value={table}>
95
+ <DataTable.Table />
96
+ <DataTable.Footer>
97
+ <DataTable.Pagination />
98
+ </DataTable.Footer>
99
+ </DataTable.Root>
100
+
101
+ <Dialog.Root
102
+ open={pendingCancel !== null}
103
+ onOpenChange={(open) => {
104
+ if (!open && !cancelling) setPendingCancel(null);
105
+ }}
106
+ >
107
+ <Dialog.Content>
108
+ <Dialog.Header>
109
+ <Dialog.Title>Cancel selected orders?</Dialog.Title>
110
+ <Dialog.Description>
111
+ {cancellableCount} of {pendingCancel?.ids.length ?? 0} selected orders can be
112
+ cancelled; shipped orders are excluded. Cancelled orders can&apos;t be reopened.
113
+ </Dialog.Description>
114
+ </Dialog.Header>
115
+ {cancelError && <p role="alert">{cancelError}</p>}
116
+ <Dialog.Footer>
117
+ <Dialog.Close render={<Button variant="outline" disabled={cancelling} />}>
118
+ Keep orders
119
+ </Dialog.Close>
120
+ <Button
121
+ variant="destructive"
122
+ disabled={cancelling || cancellableCount === 0}
123
+ onClick={async () => {
124
+ if (!pendingCancel || cancelling) return;
125
+ setCancelling(true);
126
+ setCancelError(null);
127
+ try {
128
+ const cancelledIds = await pendingCancel.run(cancelOrders(pendingCancel.ids));
129
+ removeCompletedIds(cancelledIds);
130
+ setPendingCancel(null);
131
+ } catch {
132
+ setCancelError("Cancellation failed. Please try again.");
133
+ } finally {
134
+ setCancelling(false);
135
+ }
136
+ }}
137
+ >
138
+ {cancelling ? "Cancelling…" : "Cancel orders"}
139
+ </Button>
140
+ </Dialog.Footer>
141
+ </Dialog.Content>
142
+ </Dialog.Root>
153
143
  </>
154
144
  );
155
145
  }
@@ -157,17 +147,24 @@ export default function MultiSelect({ orders, onArchive, onExport }: Props) {
157
147
 
158
148
  ## Constraints
159
149
 
160
- - Count label + Clear button are always present in the bar
161
- - Max 3 inline action buttons — 4th onward collapse behind an overflow `Menu`
162
- - Destructive bulk actions MUST open an `interaction/confirm` dialog
163
- - Filter or sort change must NOT silently clear the selection
164
- - Pagination MUST preserve selection across pages
150
+ - Declare bulk actions as the non-generic `SelectionAction[]` in `useDataTable`'s `rowSelection.actions` — do NOT hand-build the bar, a floating panel, or checkboxes; the footer renders the selection count, actions, overflow menu, and Clear. Providing `rowSelection` enables checkboxes even when `actions` is omitted or empty; the bar requires non-empty actions and selected ids
151
+ - Keep `DataTable.Footer` in the tree — it is where the bar renders
152
+ - Selection stores IDs only, not row objects. `onClick` receives a click-time copy of all selected ids (`string[]`), including unloaded ids; fetch any records required for the operation in the consumer's ID-based workflow
153
+ - The consumer owns eligibility checks, required fetching, confirmation UI, and execution; backend validation remains authoritative. Selection actions have no row-based `canApply`, automatic eligible count, or eligibility-based disabled state
154
+ - Return the promise from `onClick` for async work: the bar disables its actions and shows a spinner while it runs. Success and rejection both leave selection unchanged; there is no `keepSelection` option. Rejections are logged; show errors in the consumer UI
155
+ - Use controlled `rowSelection: { selectedIds, onChange }` to remove only ids actually changed, with a functional update against the current selection. Preserve ineligible ids and ids selected after the operation started; cancellation or failure does not clear selection
156
+ - At most 3 actions render inline, in array order; the 4th onward move to the More actions menu — order by frequency and put a destructive action last
157
+ - Destructive bulk actions MUST open an `interaction/confirm` dialog from `onClick`. Optionally keep the `run` helper and pass it the later request (`await run(deleteRows(ids))`) for pending tracking only. `clearSelection` explicitly clears every page when that is intentional
158
+ - Share only presentation fields (`id`, `label`, `icon`, `variant`) as a non-generic `DataTableAction`. `RowAction<TRow>` retains `canApply` for its concrete row; bulk eligibility belongs in the ID-based consumer workflow
159
+ - Selection persists across pagination, filter, and sort changes — do NOT clear it on those
165
160
 
166
161
  ## Anti-patterns
167
162
 
168
- - Placing bulk-action buttons in the page header — bulk actions belong only in the floating bar
169
- - Hiding the bar behind row hover or right-click — bar must be visible when selection > 0
170
- - Omitting the count or the Clear affordance — both are mandatory
171
- - Letting filter/sort changes silently drop selection
163
+ - Hand-rolled selection (`useState<Set>`, raw `<input type="checkbox">`, `Table.Root`) when the list is a DataTable
164
+ - A floating or fixed-position action bar over the table instead of the footer bar
165
+ - Placing bulk-action buttons in the page header or the table toolbar
166
+ - Treating the currently loaded rows as the full selection or deriving bulk eligibility from incomplete row data
167
+ - Clearing the whole selection after partial success, or overwriting it with the click-time snapshot and losing newer selections
168
+ - Fire-and-forget async `onClick` that doesn't return its promise or use `run` — users can fire it twice
172
169
  - Firing destructive bulk actions without an interaction/confirm step
173
170
  - Per-row `Menu` actions as a substitute for bulk actions when selection > 0
@@ -1,21 +1,11 @@
1
1
  ---
2
- slug: pattern/interaction/toast
3
- name: Toast
4
- category: pattern
5
- subcategory: interaction
2
+ title: Toast
6
3
  description: Lightweight feedback after mutations — success or error notifications
7
- requiredImports: [Button]
8
- tags: [toast, feedback, notification, mutation, success, error]
9
- do:
10
- - Feedback after a mutation (create, update, delete) — success or error
11
- - Lightweight async signal that doesn't need to block the UI
12
- - Confirming work the user just initiated
13
- dont:
14
- - Destructive action that has not yet executed — use interaction/confirm
15
- - Long-running blocking operation — use an inline progress UI, not a toast
16
4
  ---
17
5
 
18
- # pattern/interaction/toast
6
+ <!-- GENERATED by docs-kit from docs-src/patterns/interaction-toast.docs.outline.md — do not edit by hand. -->
7
+
8
+ # Toast
19
9
 
20
10
  ## When to Use
21
11
 
@@ -25,31 +15,17 @@ dont:
25
15
 
26
16
  ## Page Implementation
27
17
 
28
- ```tsx
29
- /* pattern: interaction/toast */
30
- import { Button, useToast } from "@tailor-platform/app-shell";
31
-
32
- type Props = {
33
- orderId: string;
34
- onApprove: () => Promise<void>;
35
- };
18
+ <example-preview name="interaction-toast"></example-preview>
36
19
 
37
- export default function ToastExample({ orderId, onApprove }: Props) {
20
+ ```tsx
21
+ function InteractionToast() {
38
22
  const toast = useToast();
39
-
40
- const handleApprove = async () => {
41
- try {
42
- await onApprove();
43
- toast.success(`Order ${orderId} approved`);
44
- } catch {
45
- toast.error("Failed to approve order. Try again.");
46
- }
47
- };
48
-
49
- return <Button onClick={handleApprove}>Approve</Button>;
23
+ return <Button onClick={() => toast.success("Order ORD-1024 approved")}>Approve</Button>;
50
24
  }
51
25
  ```
52
26
 
27
+ <!-- source: toast-example.tsx -->
28
+
53
29
  ## Copy Rules
54
30
 
55
31
  - Success: name what happened, including the object identifier. "Order #1234 created", "Product archived".