Skip to content

Commit baf5923

Browse files
Update App Shell pages
1 parent 92523ee commit baf5923

4 files changed

Lines changed: 197 additions & 17 deletions

File tree

docs/app-shell/components/badge.md

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ The `Badge` component displays status indicators, labels, and tags with multiple
1010
## Import
1111

1212
```tsx
13-
import { Badge } from "@tailor-platform/app-shell";
13+
import { Badge, type BadgeVariant, type BadgeOptions } from "@tailor-platform/app-shell";
1414
```
1515

1616
## Basic Usage
@@ -83,6 +83,21 @@ type BadgeVariant =
8383
| "outline-neutral";
8484
```
8585

86+
### BadgeOptions
87+
88+
Shared options used by `DataTable` and `DescriptionCard` badge fields:
89+
90+
```typescript
91+
interface BadgeOptions {
92+
/** Maps each value (stringified) to a Badge variant. Unmapped values use `defaultBadgeVariant`. */
93+
badgeVariantMap?: Record<string, BadgeVariant>;
94+
/** Maps each value (stringified) to a display label. Unmapped values render the raw value. */
95+
badgeLabelMap?: Record<string, string>;
96+
/** Variant used when the value is not in `badgeVariantMap`. Default: `"outline-neutral"`. */
97+
defaultBadgeVariant?: BadgeVariant;
98+
}
99+
```
100+
86101
## Examples
87102

88103
### Order Status

docs/app-shell/components/data-table.md

Lines changed: 31 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -225,7 +225,7 @@ A column definition passed to `useDataTable`. `Column<TRow>` is a discriminated
225225
| `width` | `number` | Fixed column width in pixels. Optional. |
226226
| `align` | `"left" \| "right"` | Horizontal alignment. Defaults to `"right"` for `type: "number"` and `type: "money"`; `"left"` otherwise. Pass `"left"` to opt a numeric column out. |
227227
| `truncate` | `boolean` | Truncate overflowing text with an ellipsis. Wires up an app-shell `<Tooltip>` automatically when the resolved cell value is a string or number — resolved via `accessor` first, then `row[col.id]` as a fallback — so hovering the cell reveals the full value. With `inferColumns`, no explicit `accessor` is needed because `id` is pinned to the field name. Requires another column to anchor the row width (`width` on a neighbor, or a fixed-size column like selection / row actions). |
228-
| `accessor` | _(narrowed per `type`)_ | Extracts the raw value. The return type is narrowed per `type` branch — returning an array or a plain object is a compile error on a typed column. Untyped columns (`type` omitted) retain `unknown`. `null` and `undefined` are always allowed. |
228+
| `accessor` | _(narrowed per `type`)_ | Extracts the raw value. The return type is narrowed per `type` branch — returning an array is a compile error on all typed columns except `badge`, and returning a plain object is a compile error on all typed columns. Untyped columns (`type` omitted) retain `unknown`. `null` and `undefined` are always allowed. |
229229
| `sort` | `SortConfig` | Sort configuration. When set, the column header becomes clickable (Asc → Desc → off). |
230230
| `filter` | `FilterConfig` | Filter configuration. When set, the column appears as an option in `DataTable.Filters`. |
231231

@@ -254,14 +254,14 @@ column({
254254
});
255255
```
256256

257-
| `type` | Accessor return type | Value handling | Options interface |
258-
| -------- | ------------------------------------------------------------ | ------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------- |
259-
| `text` | `string \| number \| boolean \| bigint \| null \| undefined` | `String(value)` — falls back to `` when nullish. | _(no options)_ |
260-
| `number` | `number \| null \| undefined` | `Intl.NumberFormat`. `` for nullish / NaN. | `NumberCellOptions`: `minDecimals`, `maxDecimals`, `locale` |
261-
| `money` | `number \| null \| undefined` | `Intl.NumberFormat` currency. `` for nullish. | `MoneyCellOptions<TRow>`: `currency` (string or `(row) => string`), `maxDecimals`, `locale` |
262-
| `date` | `Date \| string \| number \| null \| undefined` | `Intl.DateTimeFormat`. Accepts `Date`/ISO/epoch. | `DateCellOptions`: `dateFormat` (`"short"` \| `"long"` \| `"datetime"`), `locale` |
263-
| `badge` | `string \| number \| boolean \| null \| undefined` | `<Badge>` keyed off the stringified value. | `BadgeCellOptions`: `badgeVariantMap`, `badgeLabelMap`, `defaultBadgeVariant` (defaults to `"neutral"`) |
264-
| `link` | `string \| number \| boolean \| null \| undefined` | app-shell `<Link>` to `typeOptions.href(row)`. | `LinkCellOptions<TRow>`: `href: (row) => string \| null \| undefined` (returning nullish renders plain text; **required**) |
257+
| `type` | Accessor return type | Value handling | Options interface |
258+
| -------- | ---------------------------------------------------------------------------------------- | ------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------- |
259+
| `text` | `string \| number \| boolean \| bigint \| null \| undefined` | `String(value)` — falls back to `` when nullish. | _(no options)_ |
260+
| `number` | `number \| null \| undefined` | `Intl.NumberFormat`. `` for nullish / NaN. | `NumberCellOptions`: `minDecimals`, `maxDecimals`, `locale` |
261+
| `money` | `number \| null \| undefined` | `Intl.NumberFormat` currency. `` for nullish. | `MoneyCellOptions<TRow>`: `currency` (string or `(row) => string`), `maxDecimals`, `locale` |
262+
| `date` | `Date \| string \| number \| null \| undefined` | `Intl.DateTimeFormat`. Accepts `Date`/ISO/epoch. | `DateCellOptions`: `dateFormat` (`"short"` \| `"long"` \| `"datetime"`), `locale` |
263+
| `badge` | `string \| number \| boolean \| null \| undefined \| Array<string \| number \| boolean>` | `<Badge>` keyed off the stringified value. Arrays render multiple badges. | `BadgeCellOptions`: `badgeVariantMap`, `badgeLabelMap`, `defaultBadgeVariant` (defaults to `"outline-neutral"`), `maxVisible` |
264+
| `link` | `string \| number \| boolean \| null \| undefined` | app-shell `<Link>` to `typeOptions.href(row)`. | `LinkCellOptions<TRow>`: `href: (row) => string \| null \| undefined` (returning nullish renders plain text; **required**) |
265265

266266
Empty values (`null`, `undefined`, `""`) render a muted `` placeholder for every type. Use `render` for custom empty-state handling.
267267

@@ -277,7 +277,7 @@ column({ type: "link", accessor: (r) => r.title });
277277
// ❌ Compile error — text columns reject typeOptions entirely
278278
column({ type: "text", accessor: (r) => r.title, typeOptions: { locale: "en-US" } });
279279

280-
// ❌ Compile error — text/number/money/badge/link accessor cannot return an array or object
280+
// ❌ Compile error — text/number/money/link accessor cannot return an array or object
281281
column({ type: "text", accessor: (row) => row.tags }); // row.tags is string[]
282282
column({ type: "number", accessor: (row) => row.meta }); // row.meta is an object
283283
```
@@ -382,13 +382,29 @@ column({
382382
processing: "Processing",
383383
cancelled: "Cancelled",
384384
},
385-
defaultBadgeVariant: "neutral", // unmapped values fall back here
385+
defaultBadgeVariant: "outline-neutral", // unmapped values fall back here
386386
},
387387
});
388388
```
389389

390390
- The cell value is stringified before lookup, so `accessor` can return strings, numbers, or booleans.
391-
- Unmapped values render with `defaultBadgeVariant` (or `"neutral"`) and the raw stringified value as the label.
391+
- `accessor` may also return an **array** of values — each item is rendered as a separate badge.
392+
- Unmapped values render with `defaultBadgeVariant` (or `"outline-neutral"`) and the raw stringified value as the label.
393+
394+
#### Array badges with overflow
395+
396+
Use `maxVisible` to cap the number of badges shown. Extra values are hidden behind a hover popover:
397+
398+
```tsx
399+
column({
400+
...infer("tags"),
401+
type: "badge",
402+
typeOptions: {
403+
badgeVariantMap: { Premium: "warning", Office: "outline-info" },
404+
maxVisible: 2,
405+
},
406+
});
407+
```
392408

393409
### `link` — clickable text
394410

@@ -428,13 +444,13 @@ column({
428444

429445
### Combining `type` with `inferColumns`
430446

431-
`inferColumns` (from `@tailor-platform/app-shell-sdk-plugin`) derives `label`, `sort`, `filter`, and a default `render` from TailorDB metadata. You can layer a `type` on top to swap the rendering without losing the inferred sort/filter config:
447+
`inferColumns` (from `@tailor-platform/app-shell-sdk-plugin`) derives `label`, `sort`, `filter`, and `id` from TailorDB metadata. You can layer a `type` on top to get a built-in renderer without losing the inferred sort/filter config:
432448

433449
```tsx
434450
const infer = inferColumns(tableMetadata.order);
435451

436452
const columns = [
437-
// Inferred string column — keeps the default render
453+
// Inferred column — displays row[id] as plain text
438454
column(infer("reference")),
439455

440456
// Inferred datetime column, swapped to a `date` cell with long format
@@ -457,7 +473,7 @@ const columns = [
457473
];
458474
```
459475

460-
When you spread `...infer("field")`, drop in `accessor` if the inferred render doesn't already match what your `type` expects — built-in renderers read from `accessor` (or `row[id]`), not from the inferred `render`.
476+
When you spread `...infer("field")`, add `accessor` when you want a typed renderer to read a specific value — built-in renderers read from `accessor` (or `row[id]`).
461477

462478
## `FilterConfig`
463479

docs/app-shell/components/description-card.md

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,7 @@ Plain text display with optional truncation and copy button.
6767

6868
### badge
6969

70-
Displays value as a Badge component with variant mapping.
70+
Displays value as a Badge component with variant mapping. Accepts a single value or an array of values.
7171

7272
```tsx
7373
{
@@ -89,6 +89,27 @@ Displays value as a Badge component with variant mapping.
8989

9090
Badge values are rendered in sentence case by default. Set `meta.sentenceCaseBadges` to `false` when you need to display the original value from your data.
9191

92+
#### Array badges with overflow
93+
94+
When the field value is an array, each item is rendered as a separate badge. Use `maxVisible` to cap the number shown — extra items appear in a hover popover:
95+
96+
```tsx
97+
<DescriptionCard
98+
data={{ tags: ["urgent", "fragile", "international"] }}
99+
fields={[
100+
{
101+
key: "tags",
102+
label: "Tags",
103+
type: "badge",
104+
meta: {
105+
badgeVariantMap: { urgent: "error", fragile: "warning", international: "outline-info" },
106+
maxVisible: 2,
107+
},
108+
},
109+
]}
110+
/>
111+
```
112+
92113
### money
93114

94115
Formats currency values with proper locale formatting.

docs/app-shell/components/tabs.md

Lines changed: 128 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,128 @@
1+
---
2+
title: Tabs
3+
description: Tab navigation with a compound component API
4+
---
5+
6+
# Tabs
7+
8+
The `Tabs` component provides tab-based navigation for toggling between related panels on the same page. It is backed by Base UI's Tabs primitive.
9+
10+
## Import
11+
12+
```tsx
13+
import { Tabs } from "@tailor-platform/app-shell";
14+
```
15+
16+
## Basic Usage
17+
18+
```tsx
19+
<Tabs.Root defaultValue="overview">
20+
<Tabs.List>
21+
<Tabs.Tab value="overview">Overview</Tabs.Tab>
22+
<Tabs.Tab value="projects">Projects</Tabs.Tab>
23+
<Tabs.Tab value="account">Account</Tabs.Tab>
24+
</Tabs.List>
25+
<Tabs.Panel value="overview">Overview content</Tabs.Panel>
26+
<Tabs.Panel value="projects">Projects content</Tabs.Panel>
27+
<Tabs.Panel value="account">Account content</Tabs.Panel>
28+
</Tabs.Root>
29+
```
30+
31+
## Sub-components
32+
33+
| Sub-component | Description |
34+
| ------------- | -------------------------------------------------------------- |
35+
| `Tabs.Root` | Manages tab selection state |
36+
| `Tabs.List` | Groups the individual tab buttons |
37+
| `Tabs.Tab` | An interactive tab button that toggles the corresponding panel |
38+
| `Tabs.Panel` | A panel displayed when the corresponding tab is active |
39+
40+
## Props
41+
42+
### Tabs.Root Props
43+
44+
| Prop | Type | Default | Description |
45+
| --------------- | ---------------------------------- | ----------- | --------------------------------------- |
46+
| `defaultValue` | `Tabs.Tab.Value` | `0` | Initial active tab value (uncontrolled) |
47+
| `value` | `Tabs.Tab.Value` | - | Controlled active tab value |
48+
| `onValueChange` | `(value: any) => void` | - | Callback when the active tab changes |
49+
| `variant` | `'default' \| 'line' \| 'capsule'` | `'default'` | Visual style of the tabs |
50+
| `className` | `string` | - | Additional CSS classes for root |
51+
| `children` | `React.ReactNode` | - | Tabs sub-components |
52+
53+
### Tabs.List Props
54+
55+
Accepts `className` and all standard HTML `<div>` props.
56+
57+
### Tabs.Tab Props
58+
59+
| Prop | Type | Default | Description |
60+
| ---------- | ---------------- | ------- | ---------------------------------- |
61+
| `value` | `Tabs.Tab.Value` | - | **Required.** The value of the tab |
62+
| `disabled` | `boolean` | - | Whether the tab is disabled |
63+
64+
Also accepts `className` and all standard HTML `<button>` props.
65+
66+
### Tabs.Panel Props
67+
68+
| Prop | Type | Default | Description |
69+
| ------------- | ---------------- | ------- | ------------------------------------------------------ |
70+
| `value` | `Tabs.Tab.Value` | - | **Required.** The value matching the corresponding tab |
71+
| `keepMounted` | `boolean` | `false` | Whether to keep the panel in the DOM when hidden |
72+
73+
Also accepts `className` and all standard HTML `<div>` props.
74+
75+
## Controlled Usage
76+
77+
```tsx
78+
const [activeTab, setActiveTab] = useState("overview");
79+
80+
<Tabs.Root value={activeTab} onValueChange={setActiveTab}>
81+
<Tabs.List>
82+
<Tabs.Tab value="overview">Overview</Tabs.Tab>
83+
<Tabs.Tab value="details">Details</Tabs.Tab>
84+
</Tabs.List>
85+
<Tabs.Panel value="overview">Overview content</Tabs.Panel>
86+
<Tabs.Panel value="details">Details content</Tabs.Panel>
87+
</Tabs.Root>;
88+
```
89+
90+
## Examples
91+
92+
### With Disabled Tab
93+
94+
```tsx
95+
<Tabs.Root defaultValue="active">
96+
<Tabs.List>
97+
<Tabs.Tab value="active">Active</Tabs.Tab>
98+
<Tabs.Tab value="pending">Pending</Tabs.Tab>
99+
<Tabs.Tab value="archived" disabled>
100+
Archived
101+
</Tabs.Tab>
102+
</Tabs.List>
103+
<Tabs.Panel value="active">Active items</Tabs.Panel>
104+
<Tabs.Panel value="pending">Pending items</Tabs.Panel>
105+
<Tabs.Panel value="archived">Archived items</Tabs.Panel>
106+
</Tabs.Root>
107+
```
108+
109+
### Use Icons in Tab
110+
111+
```tsx
112+
<Tabs.Root defaultValue="overview" variant="capsule">
113+
<Tabs.List>
114+
<Tabs.Tab value="overview">
115+
<LayoutDashboardIcon />
116+
</Tabs.Tab>
117+
<Tabs.Tab value="projects">
118+
<FolderKanbanIcon />
119+
</Tabs.Tab>
120+
<Tabs.Tab value="settings">
121+
<SettingsIcon />
122+
</Tabs.Tab>
123+
</Tabs.List>
124+
<Tabs.Panel value="overview">Overview content</Tabs.Panel>
125+
<Tabs.Panel value="projects">Projects content</Tabs.Panel>
126+
<Tabs.Panel value="settings">Settings content</Tabs.Panel>
127+
</Tabs.Root>
128+
```

0 commit comments

Comments
 (0)