@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.
@@ -52,11 +52,13 @@ export type Order = {
52
52
  createdAt: string;
53
53
  };
54
54
 
55
+ // Primary status column — filled semantic variants (one per row).
56
+ // Secondary status columns (e.g. delivery, billing) use outline-* instead.
55
57
  const statusVariant = {
56
58
  draft: "neutral",
57
- confirmed: "outline-info",
58
- shipped: "outline-warning",
59
- delivered: "outline-success",
59
+ confirmed: "info",
60
+ shipped: "warning",
61
+ delivered: "success",
60
62
  } as const;
61
63
 
62
64
  export const columns: Column<Order>[] = [
@@ -67,7 +69,10 @@ export const columns: Column<Order>[] = [
67
69
  render: (row) => <Badge variant={statusVariant[row.status]}>{row.status}</Badge>,
68
70
  },
69
71
  {
72
+ // Numeric columns right-align so digits line up. `type: "money" | "number"`
73
+ // auto-right; with a custom `render` set `align` explicitly.
70
74
  label: "Amount",
75
+ align: "right",
71
76
  render: (row) => `${row.amount.toLocaleString()}`,
72
77
  },
73
78
  { label: "Created", accessor: (row) => row.createdAt },
@@ -78,7 +83,7 @@ export const columns: Column<Order>[] = [
78
83
 
79
84
  ```tsx
80
85
  /* pattern: list/dense-scan */
81
- import { DataTable, useDataTable, Button, Input } from "@tailor-platform/app-shell";
86
+ import { DataTable, Layout, useDataTable, Button, Input } from "@tailor-platform/app-shell";
82
87
  import type { Order } from "./columns";
83
88
  import { columns } from "./columns";
84
89
  import type { DataTableData } from "@tailor-platform/app-shell";
@@ -92,19 +97,46 @@ export default function DenseScanList({ data, onCreateClick }: Props) {
92
97
  const table = useDataTable({ data, columns });
93
98
 
94
99
  return (
95
- <div className="space-y-4">
96
- <div className="flex items-center justify-between">
97
- <Input placeholder="Search orders..." className="max-w-sm" />
98
- <Button onClick={onCreateClick}>Create Order</Button>
99
- </div>
100
- <DataTable.Root value={table}>
101
- <DataTable.Table />
102
- </DataTable.Root>
103
- </div>
100
+ // `fill` pins the page chrome for table-first pages: the title, toolbar,
101
+ // column header row, and pagination footer stay visible at every viewport
102
+ // height — only the table's rows region scrolls. Omit `fill` on pages
103
+ // that should flow and scroll naturally (forms, dashboards, articles).
104
+ <Layout fill>
105
+ <Layout.Header
106
+ title="Orders"
107
+ actions={[
108
+ <Button key="create" onClick={onCreateClick}>
109
+ Create Order
110
+ </Button>,
111
+ ]}
112
+ />
113
+ <Layout.Column>
114
+ <DataTable.Root value={table}>
115
+ <DataTable.Toolbar>
116
+ <Input placeholder="Search orders..." className="max-w-sm" />
117
+ </DataTable.Toolbar>
118
+ <DataTable.Table />
119
+ <DataTable.Footer>
120
+ <DataTable.Pagination />
121
+ </DataTable.Footer>
122
+ </DataTable.Root>
123
+ </Layout.Column>
124
+ </Layout>
104
125
  );
105
126
  }
106
127
  ```
107
128
 
129
+ ## Page Layout & Internal Scrolling
130
+
131
+ Table-first pages should pin their chrome and scroll only the rows region. Wrap the page in `<Layout fill>`:
132
+
133
+ - `fill` stretches the layout to the available height and bounds the column row, so the `DataTable` shrinks to fit instead of growing past the viewport
134
+ - The `Layout.Header` (title/actions), `DataTable.Toolbar`, the column header row (sticky), and `DataTable.Footer` (pagination) stay visible at every viewport height — only the rows scroll vertically
135
+ - When the current page of rows fits, nothing stretches and no scrollbar appears — short tables render identically with or without `fill`
136
+ - Requires no extra styling on the page: the height chain (`AppShell` content area → `Layout fill` → `Layout.Column` → `DataTable.Root`) is wired by the components
137
+
138
+ Omit `fill` on pages that should flow and scroll naturally (forms, dashboards, articles) — the AppShell content area scrolls those.
139
+
108
140
  ## Variants
109
141
 
110
142
  - **Toolbar chips only (`DataTable.Filters`)** — best when filters map cleanly to typed column metadata / enum facets
@@ -117,7 +149,9 @@ export default function DenseScanList({ data, onCreateClick }: Props) {
117
149
 
118
150
  - Column count: 4-8 recommended
119
151
  - Must include pagination — never render unbounded lists
120
- - Status Badge colors must use design system tokens (variant prop)
152
+ - Table-first pages use `<Layout fill>` so title/toolbar/header/footer stay pinned and only rows scroll
153
+ - Handle every state: `DataTable` renders the loading skeleton and error row; always provide a **labelled empty state** (what the list is + how to add the first record) rather than a bare empty table
154
+ - Status Badge colors must use design system tokens (variant prop): the **primary** status column uses **filled** semantic variants; **secondary** status columns (delivery, billing) use **`outline-*`** (see `design-system.md` → Composition & emphasis rules)
121
155
  - Bulk actions toolbar appears only when ≥1 row is selected
122
156
  - Whole row is clickable via `onClickRow`; no per-row "View" / "Open" buttons
123
157
  - Per-row `Menu` (overflow `…`) is reserved for non-navigation actions (Archive, Duplicate, Delete)
@@ -125,6 +159,7 @@ export default function DenseScanList({ data, onCreateClick }: Props) {
125
159
  ## Anti-patterns
126
160
 
127
161
  - Building a bespoke table + custom pagination instead of `DataTable` + `useCollectionVariables`
162
+ - Hand-rolled `max-height`/`overflow` wrappers around `DataTable` to contain scrolling — use `<Layout fill>` instead
128
163
  - Tabs that mutate only local UI state while pagination/filters assume the full server set
129
164
  - Using `<table>` directly instead of `<DataTable>` for live collections
130
165
  - Client-side filtering on 1000+ records without server-side support