@tailor-platform/app-shell 1.7.0 → 1.9.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 +787 -9
- package/dist/app-shell.js +5913 -3812
- package/package.json +4 -2
- package/skills/app-shell-patterns/SKILL.md +32 -20
- package/skills/app-shell-patterns/references/fundamental/components.md +125 -25
- package/skills/app-shell-patterns/references/fundamental/design-system.md +24 -0
- package/skills/app-shell-patterns/references/fundamental/graphql.md +1 -1
- package/skills/app-shell-patterns/references/patterns/detail-hero-with-actions.md +51 -7
- package/skills/app-shell-patterns/references/patterns/list-dense-scan.md +49 -14
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tailor-platform/app-shell",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.9.0",
|
|
4
4
|
"description": "An opinionated React application framework for building ERP applications on Tailor Platform",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"app-shell",
|
|
@@ -44,13 +44,15 @@
|
|
|
44
44
|
},
|
|
45
45
|
"dependencies": {
|
|
46
46
|
"@base-ui/react": "^1.5.0",
|
|
47
|
+
"@internationalized/date": "3.12.2",
|
|
47
48
|
"@standard-schema/spec": "^1.1.0",
|
|
48
|
-
"@tailor-platform/auth-public-client": "^0.5.
|
|
49
|
+
"@tailor-platform/auth-public-client": "^0.5.1",
|
|
49
50
|
"change-case": "^5.4.4",
|
|
50
51
|
"class-variance-authority": "^0.7.1",
|
|
51
52
|
"clsx": "^2.1.1",
|
|
52
53
|
"encoding-japanese": "^2.2.0",
|
|
53
54
|
"lucide-react": "^1.8.0",
|
|
55
|
+
"openai": "6.45.0",
|
|
54
56
|
"papaparse": "^5.5.3",
|
|
55
57
|
"react-router": "^7.14.1",
|
|
56
58
|
"sonner": "^2.0.7",
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: app-shell-patterns
|
|
3
|
-
description: UI
|
|
3
|
+
description: "Best-practice UI patterns and correct component usage for building pages in apps that use @tailor-platform/app-shell. Use when: building or editing any screen, page, list, table, detail view, form, modal, dialog, wizard, or bulk/confirm/toast interaction in an app with @tailor-platform/app-shell installed — or when choosing the right AppShell component, layout, or design token for a UI."
|
|
4
4
|
---
|
|
5
5
|
|
|
6
6
|
# App-Shell Patterns
|
|
@@ -13,41 +13,41 @@ Select and implement the correct UI pattern using @tailor-platform/app-shell com
|
|
|
13
13
|
|
|
14
14
|
These are the foundational rules that underpin all patterns. All patterns build on top of these references.
|
|
15
15
|
|
|
16
|
-
| File
|
|
17
|
-
|
|
|
18
|
-
| [components.md](references/fundamental/components.md)
|
|
16
|
+
| File | Description |
|
|
17
|
+
| ----------------------------------------------------------- | ----------------------- |
|
|
18
|
+
| [components.md](references/fundamental/components.md) | components reference |
|
|
19
19
|
| [design-system.md](references/fundamental/design-system.md) | design-system reference |
|
|
20
|
-
| [graphql.md](references/fundamental/graphql.md)
|
|
20
|
+
| [graphql.md](references/fundamental/graphql.md) | graphql reference |
|
|
21
21
|
|
|
22
22
|
## Available Patterns
|
|
23
23
|
|
|
24
24
|
### detail
|
|
25
25
|
|
|
26
|
-
| Slug
|
|
27
|
-
|
|
|
26
|
+
| Slug | Name | Description |
|
|
27
|
+
| ----------------------------------------------------------------------------- | ------------------------ | --------------------------------------------------------------------- |
|
|
28
28
|
| [`detail/hero-with-actions`](references/patterns/detail-hero-with-actions.md) | Hero With Actions Detail | Single-record detail view with workflow actions and activity timeline |
|
|
29
29
|
|
|
30
30
|
### form
|
|
31
31
|
|
|
32
|
-
| Slug
|
|
33
|
-
|
|
|
34
|
-
| [`form/modal`](references/patterns/form-modal.md)
|
|
35
|
-
| [`form/sectioned`](references/patterns/form-sectioned.md)
|
|
36
|
-
| [`form/single-page`](references/patterns/form-single-page.md) | Single Page Form | Routed full-page form for moderate field count (6-15) without natural sectioning
|
|
37
|
-
| [`form/wizard`](references/patterns/form-wizard.md)
|
|
32
|
+
| Slug | Name | Description |
|
|
33
|
+
| ------------------------------------------------------------- | ---------------- | --------------------------------------------------------------------------------- |
|
|
34
|
+
| [`form/modal`](references/patterns/form-modal.md) | Modal Form | Default form pattern for Create/Edit — keeps user in context on the parent screen |
|
|
35
|
+
| [`form/sectioned`](references/patterns/form-sectioned.md) | Sectioned Form | Complex form with 15+ fields organized into named fieldset sections |
|
|
36
|
+
| [`form/single-page`](references/patterns/form-single-page.md) | Single Page Form | Routed full-page form for moderate field count (6-15) without natural sectioning |
|
|
37
|
+
| [`form/wizard`](references/patterns/form-wizard.md) | Wizard Form | Multi-stage create flow with 3-7 steps and per-step validation gates |
|
|
38
38
|
|
|
39
39
|
### interaction
|
|
40
40
|
|
|
41
|
-
| Slug
|
|
42
|
-
|
|
|
43
|
-
| [`interaction/confirm`](references/patterns/interaction-confirm.md)
|
|
44
|
-
| [`interaction/multi-select`](references/patterns/interaction-multi-select.md) | Multi Select | Floating bottom action bar for bulk operations on selected list rows
|
|
45
|
-
| [`interaction/toast`](references/patterns/interaction-toast.md)
|
|
41
|
+
| Slug | Name | Description |
|
|
42
|
+
| ----------------------------------------------------------------------------- | ------------ | --------------------------------------------------------------------- |
|
|
43
|
+
| [`interaction/confirm`](references/patterns/interaction-confirm.md) | Confirm | Confirmation dialog before destructive or irreversible actions |
|
|
44
|
+
| [`interaction/multi-select`](references/patterns/interaction-multi-select.md) | Multi Select | Floating bottom action bar for bulk operations on selected list rows |
|
|
45
|
+
| [`interaction/toast`](references/patterns/interaction-toast.md) | Toast | Lightweight feedback after mutations — success or error notifications |
|
|
46
46
|
|
|
47
47
|
### list
|
|
48
48
|
|
|
49
|
-
| Slug
|
|
50
|
-
|
|
|
49
|
+
| Slug | Name | Description |
|
|
50
|
+
| ----------------------------------------------------------- | --------------- | ------------------------------------------------------------------------------------------------------- |
|
|
51
51
|
| [`list/dense-scan`](references/patterns/list-dense-scan.md) | Dense Scan List | High-density scannable list backed by GraphQL connections with DataTable, sort, filters, and pagination |
|
|
52
52
|
|
|
53
53
|
## How to Use
|
|
@@ -65,3 +65,15 @@ These are the foundational rules that underpin all patterns. All patterns build
|
|
|
65
65
|
- NEVER mix patterns in a single page component
|
|
66
66
|
- ALWAYS use AppShell components — do NOT use raw HTML or third-party UI libraries
|
|
67
67
|
- If no entry matches, compose directly from fundamental references
|
|
68
|
+
|
|
69
|
+
### Cross-cutting UX rules (apply to every screen)
|
|
70
|
+
|
|
71
|
+
Full rationale in [`design-system.md`](references/fundamental/design-system.md) → Composition & emphasis rules.
|
|
72
|
+
|
|
73
|
+
- **One primary action per view:** at most one primary/filled `Button`; everything else is `outline`/`secondary`/`ghost`.
|
|
74
|
+
- **Status badges by semantic color:** a **filled** semantic variant for the record's primary/lifecycle status, **`outline-*`** for secondary statuses, **`subtle-*`** for tags; reserve brand `default` for non-status emphasis.
|
|
75
|
+
- **No duplicate actions:** an action lives in exactly one place — never repeat the same action in both `Layout.Header` and `ActionPanel`.
|
|
76
|
+
- **Action placement:** primary CTA + status in `Layout.Header`; workflow actions in `ActionPanel`; back/navigation in the breadcrumb — never in `ActionPanel`.
|
|
77
|
+
- **Metric tiles always go in a `Grid`** (`columns={{ initial: 1, md: 2, xl: 4 }}`) — never one-per-row.
|
|
78
|
+
- **Forms default to `form/modal`** — only build a routed full-page form when the design explicitly calls for one.
|
|
79
|
+
- **Handle every state:** loading (skeleton), empty (labelled empty state), and error (inline + retry) — never ship only the happy path.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# AppShell Components
|
|
2
2
|
|
|
3
|
-
> **AppShell version:** `
|
|
3
|
+
> **AppShell version:** `1.7.0` (matches `packages/erp-kit/templates/scaffold/app/*/frontend/package.json` pinned `@tailor-platform/app-shell` semver)
|
|
4
4
|
> **Source of truth:** `@tailor-platform/app-shell` exports
|
|
5
5
|
> **Update process:** see "Keeping this file in sync" at the bottom
|
|
6
6
|
|
|
@@ -47,7 +47,9 @@ For the full upstream API of any component, follow the link to the published ref
|
|
|
47
47
|
|
|
48
48
|
**Import:** `import { Layout } from '@tailor-platform/app-shell'`
|
|
49
49
|
**Purpose:** Standard page container with header + 1–N column body. The most-used component — every page wraps content in `<Layout>`.
|
|
50
|
-
**API:** `Layout` (root) + `Layout.Column` + `Layout.Header`. `LayoutProps`: `columns` (number, default auto-detected from children), `gap`, `title`, `actions`, `className`, `style`.
|
|
50
|
+
**API:** `Layout` (root) + `Layout.Column` + `Layout.Header`. `LayoutProps`: `fill` (boolean), `columns` (number, default auto-detected from children), `gap`, `title`, `actions`, `className`, `style`.
|
|
51
|
+
|
|
52
|
+
**`fill`** — stretches the layout to the available height and bounds the column row so children can scroll internally instead of growing the page. Use on table-first pages (`<Layout fill>` + `DataTable`): the title, table toolbar, sticky column headers, and pagination footer stay pinned while only the rows scroll — see **`patterns/list-dense-scan.md`**. Omit on pages that should flow and scroll naturally (forms, dashboards).
|
|
51
53
|
|
|
52
54
|
**Column-count → width rules** (column count is auto-detected from `Layout.Column` children):
|
|
53
55
|
|
|
@@ -79,8 +81,8 @@ import { Button, Layout, Tabs } from "@tailor-platform/app-shell";
|
|
|
79
81
|
<Layout.Header title="Purchase orders" actions={[<Button key="create">Create</Button>]}>
|
|
80
82
|
<Tabs.Root value={bucket} onValueChange={setBucket}>
|
|
81
83
|
<Tabs.List>
|
|
82
|
-
<Tabs.
|
|
83
|
-
<Tabs.
|
|
84
|
+
<Tabs.Tab value="all">All</Tabs.Tab>
|
|
85
|
+
<Tabs.Tab value="open">Open</Tabs.Tab>
|
|
84
86
|
</Tabs.List>
|
|
85
87
|
</Tabs.Root>
|
|
86
88
|
</Layout.Header>
|
|
@@ -107,19 +109,55 @@ import { Button, Layout, Tabs } from "@tailor-platform/app-shell";
|
|
|
107
109
|
|
|
108
110
|
**Used in patterns:** every page pattern (`list/*`, `detail/*`, `form/*`).
|
|
109
111
|
|
|
112
|
+
### `Grid`
|
|
113
|
+
|
|
114
|
+
**Import:** `import { Grid } from '@tailor-platform/app-shell'`
|
|
115
|
+
**Purpose:** Presentational CSS-Grid container for laying tiles/cards into equal or custom columns with responsive reflow — the canonical wrapper for **metric / KPI strips** and card galleries. Purely layout; makes no data assumptions.
|
|
116
|
+
**API:** `GridProps` — `columns` (number | CSS track string like `"280px 1fr"` | responsive object `{ initial, sm, md, lg, xl }`), `gap` (spacing step, default `3`) plus `gapX` / `gapY`, `minChildWidth` (auto-fit: as many ≥Npx columns as fit, no breakpoints needed), `rows`, `flow`, `align`, `justify`. Sub-component **`Grid.Item`** for spanning/placement — `colSpan`, `rowSpan`, `colStart`, `colEnd` (all responsive). Root carries `data-slot="grid"`.
|
|
117
|
+
**Example — responsive KPI grid:**
|
|
118
|
+
|
|
119
|
+
```tsx
|
|
120
|
+
import { Grid, MetricCard } from "@tailor-platform/app-shell";
|
|
121
|
+
|
|
122
|
+
<Grid columns={{ initial: 1, md: 2, xl: 4 }} gap={4}>
|
|
123
|
+
<MetricCard title="Net total" value="$1,500" trend={{ direction: "up", value: "+5%" }} />
|
|
124
|
+
<MetricCard title="Open orders" value="42" />
|
|
125
|
+
<MetricCard title="Overdue" value="7" />
|
|
126
|
+
<MetricCard title="Suppliers" value="18" />
|
|
127
|
+
</Grid>;
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
**Used in patterns:** metric / KPI strips on `detail/*` and dashboards; any equal-column card layout.
|
|
131
|
+
|
|
132
|
+
**Notes:** Metric tiles and KPI cards **always** go in a `Grid` — never stacked one-per-row or in a single column. Use a responsive `columns` object (e.g. `{ initial: 1, md: 2, xl: 4 }`) so tiles reflow at smaller widths; reach for `minChildWidth` when the tile count is dynamic.
|
|
133
|
+
|
|
110
134
|
### `SidebarLayout`
|
|
111
135
|
|
|
112
136
|
**Import:** `import { SidebarLayout } from '@tailor-platform/app-shell'`
|
|
113
137
|
**Purpose:** Top-level layout that mounts the sidebar and renders the page outlet.
|
|
114
|
-
**API:** `SidebarLayoutProps` — sidebar
|
|
138
|
+
**API:** `SidebarLayoutProps` — `sidebar`, `header`, and `children` are full-region slots (each defaults to a built-in). `sidebar` defaults to `SidebarLayout.DefaultSidebar`; `header` defaults to `SidebarLayout.DefaultHeader`. Used in `App.tsx`.
|
|
115
139
|
**Used in patterns:** consumed by AppShell init, not directly by page patterns. See `project-setup.md`.
|
|
116
140
|
|
|
117
|
-
### `
|
|
141
|
+
### `SidebarLayout.DefaultHeader` (`DefaultHeader`)
|
|
142
|
+
|
|
143
|
+
**Import:** `import { SidebarLayout } from '@tailor-platform/app-shell'` → `SidebarLayout.DefaultHeader` (also top-level `DefaultHeader`)
|
|
144
|
+
**Purpose:** The built-in top bar (trigger + breadcrumb, plus an `actions` cluster). Drop into `SidebarLayout`'s `header` slot to extend the header without rebuilding it.
|
|
145
|
+
**API:** `actions?: ReactNode | ReactNode[]` — the right-hand cluster (opinionated flex row). **Defaults to `[<AppearanceSwitcher />]`, and passing `actions` REPLACES the switcher** — include `<AppearanceSwitcher />` in the array to keep it. `actions={[]}` = empty right side.
|
|
146
|
+
**Used in patterns:** project-level header customization (notification bell, user menu). See `project-setup.md`.
|
|
118
147
|
|
|
119
|
-
|
|
148
|
+
### `SidebarLayout.DefaultSidebar` (`DefaultSidebar`), `SidebarGroup`, `SidebarItem`, `SidebarSeparator`
|
|
149
|
+
|
|
150
|
+
**Import:** `import { SidebarLayout, SidebarGroup, SidebarItem, SidebarSeparator } from '@tailor-platform/app-shell'` → `SidebarLayout.DefaultSidebar` (also top-level `DefaultSidebar`)
|
|
120
151
|
**Purpose:** Sidebar composition. `DefaultSidebar` auto-resolves nav items from `appShellPageProps.meta` on each page; the others let you customize manually.
|
|
121
152
|
**Used in patterns:** sidebar is a project-level concern. See `project-setup.md`.
|
|
122
153
|
|
|
154
|
+
### `AppearanceSwitcher`
|
|
155
|
+
|
|
156
|
+
**Import:** `import { AppearanceSwitcher } from '@tailor-platform/app-shell'`
|
|
157
|
+
**Purpose:** Palette-icon dropdown for Light/Dark/System color mode. The default `SidebarLayout.DefaultHeader` action; also usable standalone (e.g. a sidebar footer). Include it in `DefaultHeader`'s `actions` array when customizing the header to keep it visible.
|
|
158
|
+
**API:** No props — reads/writes theme via `useTheme`.
|
|
159
|
+
**Used in patterns:** project-level. See `project-setup.md`.
|
|
160
|
+
|
|
123
161
|
### `CommandPalette`
|
|
124
162
|
|
|
125
163
|
**Import:** `import { CommandPalette } from '@tailor-platform/app-shell'`
|
|
@@ -250,6 +288,28 @@ import { Button, Link } from '@tailor-platform/app-shell';
|
|
|
250
288
|
**API:** Compound — `Tooltip.Root`, `Tooltip.Trigger`, `Tooltip.Content`.
|
|
251
289
|
**Used in patterns:** any pattern with icon-only buttons (must have `aria-label` AND a tooltip).
|
|
252
290
|
|
|
291
|
+
### `Tabs`
|
|
292
|
+
|
|
293
|
+
**Import:** `import { Tabs } from '@tailor-platform/app-shell'`
|
|
294
|
+
**Purpose:** In-page tab navigation — split one record's sections (Overview / Line items / Activity) or bucket a list (All / Open / …) into switchable panels. Presentational; owns only the active-tab state.
|
|
295
|
+
**API:** Compound — `Tabs.Root` (`variant`: `default | line | capsule`; controlled `value` + `onValueChange`, or uncontrolled `defaultValue`), `Tabs.List`, `Tabs.Tab` (`value`), `Tabs.Panel` (`value`). Note: the sub-component is **`Tabs.Tab`**, not `Tabs.Trigger`.
|
|
296
|
+
**Example:**
|
|
297
|
+
|
|
298
|
+
```tsx
|
|
299
|
+
<Tabs.Root defaultValue="overview" variant="line">
|
|
300
|
+
<Tabs.List>
|
|
301
|
+
<Tabs.Tab value="overview">Overview</Tabs.Tab>
|
|
302
|
+
<Tabs.Tab value="items">Line items</Tabs.Tab>
|
|
303
|
+
</Tabs.List>
|
|
304
|
+
<Tabs.Panel value="overview">{/* … */}</Tabs.Panel>
|
|
305
|
+
<Tabs.Panel value="items">{/* … */}</Tabs.Panel>
|
|
306
|
+
</Tabs.Root>
|
|
307
|
+
```
|
|
308
|
+
|
|
309
|
+
**Used in patterns:** `list-dense-scan` (bucket tabs composed **above** `DataTable.Root`, synced to `useCollectionVariables` — see the `DataTable` "Bucket tabs" note), `detail/*` (sectioned record content).
|
|
310
|
+
|
|
311
|
+
**Notes:** For lists, AppShell's own filtering surface is **toolbar chips** (`DataTable.Filters`), not tabs — reach for `Tabs` only when the business genuinely thinks in a small set of named buckets. Don't render a `Tab` per enum value where a filter chip belongs.
|
|
312
|
+
|
|
253
313
|
---
|
|
254
314
|
|
|
255
315
|
## Display
|
|
@@ -260,14 +320,27 @@ import { Button, Link } from '@tailor-platform/app-shell';
|
|
|
260
320
|
|
|
261
321
|
**Import:** `import { Badge } from '@tailor-platform/app-shell'`
|
|
262
322
|
**Purpose:** Status labels and small categorical chips.
|
|
263
|
-
**API:** `BadgeProps` — `variant
|
|
323
|
+
**API:** `BadgeProps` — `variant` (15 total):
|
|
324
|
+
|
|
325
|
+
- **Filled** (high emphasis): `default` (primary), `success`, `warning`, `error`, `neutral`, `info`
|
|
326
|
+
- **Subtle** (low emphasis, tinted): `subtle-success`, `subtle-warning`, `subtle-error`, `subtle-info`
|
|
327
|
+
- **Outline** (renders a status dot — for row/list statuses): `outline-success`, `outline-warning`, `outline-error`, `outline-info`, `outline-neutral`
|
|
328
|
+
|
|
329
|
+
Plus `badgeVariants` CVA for custom-styled siblings.
|
|
264
330
|
**Example:**
|
|
265
331
|
|
|
266
332
|
```tsx
|
|
267
|
-
|
|
333
|
+
// Primary / lifecycle status — filled semantic variant
|
|
334
|
+
<Badge variant="success">Confirmed</Badge>
|
|
335
|
+
// Secondary status (delivery, billing) — outline with status dot
|
|
336
|
+
<Badge variant="outline-warning">Partially received</Badge>
|
|
337
|
+
// Tag / label — subtle
|
|
338
|
+
<Badge variant="subtle-info">New</Badge>
|
|
268
339
|
```
|
|
269
340
|
|
|
270
|
-
**Used in patterns:** `list/*` (status column), `detail/*` (header status).
|
|
341
|
+
**Used in patterns:** `list/*` (status column → `outline-*`), `detail/*` (header status).
|
|
342
|
+
|
|
343
|
+
**Notes:** Encode status by **semantic color** with a primary/secondary split: a record's **primary/lifecycle status** uses a **filled** semantic variant (one per row / one in a detail header); **secondary statuses** (delivery, billing) use **`outline-*`** (status dot); tags use **`subtle-*`**. Reserve **`default`** (brand) for non-status emphasis. Full rule: **`design-system.md`** → Composition & emphasis rules.
|
|
271
344
|
|
|
272
345
|
### `Table`
|
|
273
346
|
|
|
@@ -275,7 +348,7 @@ import { Button, Link } from '@tailor-platform/app-shell';
|
|
|
275
348
|
|
|
276
349
|
**Import:** `import { Table } from '@tailor-platform/app-shell'`
|
|
277
350
|
**Purpose:** Semantic data table with scrollable container.
|
|
278
|
-
**API:** Compound — `Table.Root`, `Table.Header`, `Table.Body`, `Table.Footer`, `Table.Row`, `Table.Head`, `Table.Cell`, `Table.Caption`. `Table.Root` accepts `containerClassName` for the outer wrapper, `className` for the inner `<table>`.
|
|
351
|
+
**API:** Compound — `Table.Root`, `Table.Header`, `Table.Body`, `Table.Footer`, `Table.Row`, `Table.Head`, `Table.Cell`, `Table.Caption`. `Table.Root` accepts `containerClassName` for the outer wrapper, `className` for the inner `<table>`. `Table.Head` and `Table.Cell` accept **`align`** (`"left" | "center" | "right"`, default `"left"`) — prefer this over ad-hoc `className="astw:text-right"` and pair the same alignment on the head and its column's cells (right for numeric/money, center for compact status/icon columns).
|
|
279
352
|
**Example:**
|
|
280
353
|
|
|
281
354
|
```tsx
|
|
@@ -284,7 +357,7 @@ import { Button, Link } from '@tailor-platform/app-shell';
|
|
|
284
357
|
<Table.Row>
|
|
285
358
|
<Table.Head>Order</Table.Head>
|
|
286
359
|
<Table.Head>Status</Table.Head>
|
|
287
|
-
<Table.Head
|
|
360
|
+
<Table.Head align="right">Total</Table.Head>
|
|
288
361
|
</Table.Row>
|
|
289
362
|
</Table.Header>
|
|
290
363
|
<Table.Body>
|
|
@@ -294,7 +367,7 @@ import { Button, Link } from '@tailor-platform/app-shell';
|
|
|
294
367
|
<Table.Cell>
|
|
295
368
|
<Badge variant={statusVariant(o.status)}>{o.status}</Badge>
|
|
296
369
|
</Table.Cell>
|
|
297
|
-
<Table.Cell
|
|
370
|
+
<Table.Cell align="right">{formatMoney(o.total)}</Table.Cell>
|
|
298
371
|
</Table.Row>
|
|
299
372
|
))}
|
|
300
373
|
</Table.Body>
|
|
@@ -346,6 +419,8 @@ const table = useDataTable({
|
|
|
346
419
|
</DataTable.Root>;
|
|
347
420
|
```
|
|
348
421
|
|
|
422
|
+
**Column alignment:** each `Column` accepts **`align`** (`"left" | "right"`) applied to both header and body cell. Numeric `type` columns (`"number"`, `"money"`) default to `"right"` automatically so digits align on the decimal place — pass `align="left"` to opt out; everything else defaults to `"left"`.
|
|
423
|
+
|
|
349
424
|
**Metadata path:** Prefer `createColumnHelper` + `inferColumns(tableMetadata.order)` (`@tailor-platform/app-shell-sdk-plugin` codegen) when available so enum/datetime/string filters bind to the right editors.
|
|
350
425
|
|
|
351
426
|
**Bucket tabs / segmented UX:** AppShell defines **toolbar chips**, not lifecycle tabs. When design places **`Tabs`** (All / Draft / …) inside the card, compose them **above** `DataTable.Root` and synchronize tab-driven bucket state with **`useCollectionVariables`** (`variables.query` / filters)—see **`patterns/list/dense-scan.md`**.
|
|
@@ -410,25 +485,31 @@ const table = useDataTable({
|
|
|
410
485
|
**Import:** `import { MetricCard } from '@tailor-platform/app-shell'`
|
|
411
486
|
**Purpose:** KPI tile for dashboards or hero metric strip.
|
|
412
487
|
**API:** `MetricCardProps` — `title`, `value`, `trend: { direction, value }`, `description`, `icon`.
|
|
413
|
-
**Example:**
|
|
488
|
+
**Example:** metric tiles are laid out in a `Grid` (never one-per-row):
|
|
414
489
|
|
|
415
490
|
```tsx
|
|
416
|
-
<
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
/>
|
|
491
|
+
<Grid columns={{ initial: 1, md: 2, xl: 4 }} gap={4}>
|
|
492
|
+
<MetricCard
|
|
493
|
+
title="Net total"
|
|
494
|
+
value="$1,500"
|
|
495
|
+
trend={{ direction: "up", value: "+5%" }}
|
|
496
|
+
description="vs last month"
|
|
497
|
+
icon={<DollarSign />}
|
|
498
|
+
/>
|
|
499
|
+
<MetricCard title="Open orders" value="42" />
|
|
500
|
+
{/* …more tiles */}
|
|
501
|
+
</Grid>
|
|
423
502
|
```
|
|
424
503
|
|
|
425
504
|
**Used in patterns:** KPI tiles, dashboards, **`detail/*`** metric strips where specs call for them.
|
|
426
505
|
|
|
506
|
+
**Notes:** Always wrap metric tiles in **`Grid`** (see the `Grid` entry) — a column of single-width `MetricCard`s stacked one-per-row is an anti-pattern.
|
|
507
|
+
|
|
427
508
|
### `DocumentProgressCard`
|
|
428
509
|
|
|
429
510
|
**Import:** `import { DocumentProgressCard } from '@tailor-platform/app-shell'`
|
|
430
|
-
**Purpose:**
|
|
431
|
-
**API:** `DocumentProgressCardProps` — `segments` (`{ label, value, color
|
|
511
|
+
**Purpose:** Presentational card for a document's lifecycle / fulfilment state — an optional headline percentage, a stacked progress bar, and a status legend. Domain-agnostic and view-only: pass an arbitrary set of `segments` (e.g. shipped / returned / pending) plus an explicit `percent`; derive both in the consumer.
|
|
512
|
+
**API:** `DocumentProgressCardProps` — `segments` (required; array of `{ label, value, color }`), `percent?` (0–100, headline shown top-right), `title?`, `legend?` (defaults to `segments`; supply only when the legend should differ from the bar, e.g. overlapping buckets), `total?` (bar denominator; defaults to the sum of segment values — a larger value leaves an unfilled remainder), `className?`. Segment `color`: `indigo | pink | green | amber | red | blue | neutral`.
|
|
432
513
|
**Example:**
|
|
433
514
|
|
|
434
515
|
```tsx
|
|
@@ -443,7 +524,7 @@ const table = useDataTable({
|
|
|
443
524
|
/>
|
|
444
525
|
```
|
|
445
526
|
|
|
446
|
-
**Used in patterns:**
|
|
527
|
+
**Used in patterns:** `detail/*` — fulfilment / lifecycle summary cards (e.g. a purchase-order receipt progress) in the main column or right rail.
|
|
447
528
|
|
|
448
529
|
### `Avatar`
|
|
449
530
|
|
|
@@ -504,6 +585,24 @@ const table = useDataTable({
|
|
|
504
585
|
**API:** Compound — `ActivityCard.Root`, `ActivityCard.Items` (generic over item type), plus `ActivityCardProps`, `ActivityCardItem`, `ActivityCardItemProps`. Items render with timestamp + actor + description.
|
|
505
586
|
**Used in patterns:** `detail/hero-with-actions` (right column or bottom section).
|
|
506
587
|
|
|
588
|
+
### `Alert`
|
|
589
|
+
|
|
590
|
+
**Import:** `import { Alert } from '@tailor-platform/app-shell'`
|
|
591
|
+
**Purpose:** Inline, in-page banner for a persistent status message — a form/record error, a warning, or a success/info notice attached to a section. (Transient notifications use `useToast`; blocking confirmations use `Dialog` — see `interaction/confirm`.)
|
|
592
|
+
**API:** Compound — `Alert.Root` (`variant`: `neutral | success | warning | error | info`, default `neutral`; a matching icon is rendered automatically; optional `action?: ReactNode`, `dismissible?: boolean`, `onDismiss?: () => void`), `Alert.Title`, `Alert.Description`.
|
|
593
|
+
**Example:**
|
|
594
|
+
|
|
595
|
+
```tsx
|
|
596
|
+
<Alert.Root variant="error">
|
|
597
|
+
<Alert.Title>Couldn't load line items</Alert.Title>
|
|
598
|
+
<Alert.Description>Check your connection and try again.</Alert.Description>
|
|
599
|
+
</Alert.Root>
|
|
600
|
+
```
|
|
601
|
+
|
|
602
|
+
**Used in patterns:** any data-backed screen's **error** state (inline error + retry) and record-level warnings — the error affordance the composition rules require (**`design-system.md`** → Composition & emphasis rules → States).
|
|
603
|
+
|
|
604
|
+
**Notes:** Encode severity by `variant` (semantic color), same discipline as `Badge` — don't use `error` for a routine notice. Pair a retry/next-step control via `action` rather than a separate stray button.
|
|
605
|
+
|
|
507
606
|
---
|
|
508
607
|
|
|
509
608
|
## Forms
|
|
@@ -544,6 +643,7 @@ const table = useDataTable({
|
|
|
544
643
|
**Import:** `import { Select } from '@tailor-platform/app-shell'`
|
|
545
644
|
**Purpose:** Dropdown for fixed enumerations.
|
|
546
645
|
**API:** Compound. Sync version (small option lists). For async/typeahead, see `Combobox` and `Autocomplete`. Type: `SelectAsyncFetcher<T>` for the async variant.
|
|
646
|
+
**Accessible name:** inside a `Form` the field label provides it automatically. When used **standalone** (outside a `Form`), pass `aria-label` (or `aria-labelledby` / `id`) — these now forward to the underlying combobox element, so it isn't left named only by its current value. Same applies to `Combobox` and `Autocomplete`.
|
|
547
647
|
**Used in patterns:** all `form/*` (enum fields).
|
|
548
648
|
|
|
549
649
|
### `Combobox`
|
|
@@ -626,7 +726,7 @@ Types for authoring guard functions used by `WithGuard` and `appShellPageProps.g
|
|
|
626
726
|
|
|
627
727
|
### `RouteParams`, `PageComponent`, `PageMeta`, `AppShellPageProps`, `AppShellRegister`, `ContextData` (types)
|
|
628
728
|
|
|
629
|
-
Types for declaring page props (`appShellPageProps = { meta, guards
|
|
729
|
+
Types for declaring page props (`appShellPageProps = { meta, guards }`), reading typed route params, and registering route types globally. See `project-setup.md` for usage.
|
|
630
730
|
|
|
631
731
|
---
|
|
632
732
|
|
|
@@ -353,3 +353,27 @@ When a custom component proves reusable across 2+ apps, promote it upstream into
|
|
|
353
353
|
| Two-column detail at <1024 | right column collapses below main — do not override |
|
|
354
354
|
| Inline ID, code, table number | `text-mono` |
|
|
355
355
|
| Timestamp, label, subtle metadata | `text-caption text-fg-subtle` |
|
|
356
|
+
|
|
357
|
+
### Composition & emphasis rules
|
|
358
|
+
|
|
359
|
+
These are visual-composition rules every screen must follow, regardless of pattern. They exist because emphasis only works when it is scarce.
|
|
360
|
+
|
|
361
|
+
**Emphasis budget.** Attention is a budget you spend once per scan region.
|
|
362
|
+
|
|
363
|
+
- **One primary action per view.** A screen (or a card/section) has at most one filled/primary `Button`; everything else is `outline`, `secondary`, or `ghost`. If two things look equally important, neither reads as important.
|
|
364
|
+
- **Badges** encode status by semantic color, with a clear primary/secondary split:
|
|
365
|
+
- A record's **primary / lifecycle status** (PO status, SO status) → a **filled semantic** variant (`success` / `warning` / `error` / `info` / `neutral`) — one per row in a list, one in a detail header.
|
|
366
|
+
- **Secondary statuses** (delivery, billing, fulfilment) and dense supporting columns → **`outline-*`** (with status dot).
|
|
367
|
+
- **Tags / labels** ("New", "Returned") → **`subtle-*`**.
|
|
368
|
+
- Reserve **`default`** (brand fill) for non-status emphasis — never the brand color as a routine status. The defect to avoid: making _every_ chip a loud fill, or giving secondary statuses the same weight as the primary one. (Variants: **`components.md`** → `Badge`.)
|
|
369
|
+
- **Color:** status colors (`success`/`warning`/`error`/`info`) signal meaning, not decoration — don't tint neutral content.
|
|
370
|
+
|
|
371
|
+
**Hierarchy.** One `h1` per page (the `Layout.Header` title). Section headings step down (`h2` → `h3`); never skip levels for size — pick the role token (`design-system.md` §4 Typography), not the pixel size.
|
|
372
|
+
|
|
373
|
+
**States — never ship only the happy path.** Every data-backed screen handles:
|
|
374
|
+
|
|
375
|
+
- **Loading** — skeleton/placeholder, not a blank flash.
|
|
376
|
+
- **Empty** — a labelled empty state (what it is, how to add the first record), not a bare empty table.
|
|
377
|
+
- **Error** — an inline error with a retry affordance, not a silent failure.
|
|
378
|
+
|
|
379
|
+
**Spacing rhythm.** Use the spacing scale (§4) consistently — equal gaps between sibling sections, consistent card padding. A one-off `gap` or `padding` that doesn't match its siblings reads as a mistake.
|
|
@@ -191,7 +191,7 @@ Every page component:
|
|
|
191
191
|
};
|
|
192
192
|
```
|
|
193
193
|
|
|
194
|
-
3. Uses `appShellPageProps.guards` for permission gates
|
|
194
|
+
3. Uses `appShellPageProps.guards` for permission gates. See [project-setup.md](project-setup.md).
|
|
195
195
|
|
|
196
196
|
## Quick reference
|
|
197
197
|
|
|
@@ -5,7 +5,21 @@ category: pattern
|
|
|
5
5
|
subcategory: detail
|
|
6
6
|
description: Single-record detail view with workflow actions and activity timeline
|
|
7
7
|
requiredImports:
|
|
8
|
-
[
|
|
8
|
+
[
|
|
9
|
+
Layout,
|
|
10
|
+
Badge,
|
|
11
|
+
Button,
|
|
12
|
+
Menu,
|
|
13
|
+
DescriptionCard,
|
|
14
|
+
Card,
|
|
15
|
+
Table,
|
|
16
|
+
ActionPanel,
|
|
17
|
+
ActivityCard,
|
|
18
|
+
Grid,
|
|
19
|
+
MetricCard,
|
|
20
|
+
DocumentProgressCard,
|
|
21
|
+
Alert,
|
|
22
|
+
]
|
|
9
23
|
tags: [detail, actions, timeline, workflow, two-column]
|
|
10
24
|
do:
|
|
11
25
|
- Single-record detail view (Order #1234, Supplier ABC, Product SKU-42)
|
|
@@ -51,6 +65,33 @@ Composition rules:
|
|
|
51
65
|
|
|
52
66
|
Responsive: <1024 collapses to single column with right column rendered below main; 1024–1280 two columns with narrow side; >1280 two columns at full width.
|
|
53
67
|
|
|
68
|
+
## Layout variants
|
|
69
|
+
|
|
70
|
+
Choose the column structure from the **record** — the pattern is "hero + workflow + (optional) history," and the layout flexes. The skill never mandates a fixed column count.
|
|
71
|
+
|
|
72
|
+
- **Main + right rail** (most common) — the record has workflow actions and/or activity/related summaries → right `Layout.Column area="right"` holds `ActionPanel`, `ActivityCard`, integration cards.
|
|
73
|
+
- **Single main column** — the record is mostly descriptive with few/no workflow actions and no meaningful history → one `Layout.Column`. Don't invent a rail just to fill space.
|
|
74
|
+
- **Main + left rail** (`area="left"`, wider 320px) — when a persistent section index / summary should lead the reading order.
|
|
75
|
+
- Either column holds **N cards** as the record requires.
|
|
76
|
+
|
|
77
|
+
## Optional cards
|
|
78
|
+
|
|
79
|
+
Add these only when the record calls for them — none is mandatory. Each is a card like any other in the columns above.
|
|
80
|
+
|
|
81
|
+
- **Metric strip** — when the record has headline KPIs (totals, counts), lead the main column with `MetricCard`s wrapped in a `Grid` (never one per row). See the `Grid` and `MetricCard` entries in `components.md`.
|
|
82
|
+
|
|
83
|
+
```tsx
|
|
84
|
+
<Grid columns={{ initial: 1, md: 2, xl: 4 }} gap={4}>
|
|
85
|
+
<MetricCard title="Net total" value="$12,480" trend={{ direction: "up", value: "+4%" }} />
|
|
86
|
+
<MetricCard title="Lines" value="18" />
|
|
87
|
+
<MetricCard title="Received" value="11" />
|
|
88
|
+
<MetricCard title="Outstanding" value="7" />
|
|
89
|
+
</Grid>
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
- **`DocumentProgressCard`** — when the record has a fulfilment / lifecycle breakdown (received vs returned vs pending, shipment status). Sits in the main column or the right rail; derive `percent` and `segments` in the consumer.
|
|
93
|
+
- **`Alert`** — a record-level inline banner (blocking error, on-hold warning) above the columns; use the **error** variant for the pattern's required error state. Transient feedback uses `useToast`, not `Alert`.
|
|
94
|
+
|
|
54
95
|
## Page Implementation
|
|
55
96
|
|
|
56
97
|
```tsx
|
|
@@ -109,16 +150,16 @@ export default function HeroWithActionsDetail({ order, onApprove, onCancel }: Pr
|
|
|
109
150
|
<Table.Header>
|
|
110
151
|
<Table.Row>
|
|
111
152
|
<Table.Head>SKU</Table.Head>
|
|
112
|
-
<Table.Head>Qty</Table.Head>
|
|
113
|
-
<Table.Head>Total</Table.Head>
|
|
153
|
+
<Table.Head align="right">Qty</Table.Head>
|
|
154
|
+
<Table.Head align="right">Total</Table.Head>
|
|
114
155
|
</Table.Row>
|
|
115
156
|
</Table.Header>
|
|
116
157
|
<Table.Body>
|
|
117
158
|
{order.lineItems.map((item) => (
|
|
118
159
|
<Table.Row key={item.id}>
|
|
119
160
|
<Table.Cell>{item.sku}</Table.Cell>
|
|
120
|
-
<Table.Cell>{item.qty}</Table.Cell>
|
|
121
|
-
<Table.Cell>${item.total.toLocaleString()}</Table.Cell>
|
|
161
|
+
<Table.Cell align="right">{item.qty}</Table.Cell>
|
|
162
|
+
<Table.Cell align="right">${item.total.toLocaleString()}</Table.Cell>
|
|
122
163
|
</Table.Row>
|
|
123
164
|
))}
|
|
124
165
|
</Table.Body>
|
|
@@ -143,9 +184,10 @@ export default function HeroWithActionsDetail({ order, onApprove, onCancel }: Pr
|
|
|
143
184
|
|
|
144
185
|
## Constraints
|
|
145
186
|
|
|
146
|
-
-
|
|
187
|
+
- **Header carries the single primary CTA + the status `Badge`** — not workflow actions. Workflow actions (Approve, Reject, Archive, …) live in the `ActionPanel`. If the header needs more than ~2 actions, move the overflow into a `Menu`.
|
|
188
|
+
- **Never duplicate an action** across `Layout.Header` and `ActionPanel` — each action has exactly one home.
|
|
147
189
|
- Every content section MUST sit inside `Card.Root` (or `DescriptionCard`, which already self-contains). Raw divs are not allowed.
|
|
148
|
-
- `ActionPanel` is workflow-only — never back-navigation.
|
|
190
|
+
- `ActionPanel` is workflow-only — never back-navigation (that lives in the breadcrumb).
|
|
149
191
|
- `Table.Root` inside a Card requires `containerClassName="astw:px-6"`.
|
|
150
192
|
|
|
151
193
|
## Anti-patterns
|
|
@@ -155,3 +197,5 @@ export default function HeroWithActionsDetail({ order, onApprove, onCancel }: Pr
|
|
|
155
197
|
- Bare `<div>` sections in the main column — every content section MUST sit inside `Card.Root`.
|
|
156
198
|
- `ActionPanel` containing back-navigation (e.g. "Back to Product List") — back navigation lives in `Layout.Header`'s breadcrumb.
|
|
157
199
|
- A `Table.Root` inside a Card without `containerClassName="astw:px-6"` — the first column lands flush against the card edge.
|
|
200
|
+
- The same action in both `Layout.Header` and the `ActionPanel` — duplicating it makes neither read as canonical. Pick one home.
|
|
201
|
+
- Giving every status the same loud weight — the record's primary/lifecycle status is a **filled** semantic badge; secondary statuses (fulfilment, billing) use **`outline-*`** (see `design-system.md` → Composition & emphasis rules).
|