@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
|
@@ -1,22 +1,11 @@
|
|
|
1
1
|
---
|
|
2
|
-
|
|
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
|
-
|
|
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
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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"
|
|
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
|
-
|
|
3
|
-
|
|
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
|
-
|
|
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
|
-
-
|
|
25
|
-
-
|
|
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
|
-
|
|
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
|
|
34
|
-
|
|
35
|
-
| Layout.Column
|
|
36
|
-
|
|
|
37
|
-
| [
|
|
38
|
-
| [x] |
|
|
39
|
-
| [
|
|
40
|
-
| [
|
|
41
|
-
|
|
|
42
|
-
|
|
|
43
|
-
|
|
|
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
|
-
|
|
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
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
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
|
|
83
|
-
|
|
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
|
-
<
|
|
88
|
-
<Table
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
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'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
|
-
-
|
|
161
|
-
-
|
|
162
|
-
-
|
|
163
|
-
-
|
|
164
|
-
-
|
|
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
|
-
-
|
|
169
|
-
-
|
|
170
|
-
-
|
|
171
|
-
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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".
|