@tailor-platform/app-shell 1.18.0 → 1.19.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.
@@ -1,6 +1,8 @@
1
1
  ---
2
2
  name: app-shell-patterns
3
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 — when choosing the right AppShell component, layout, or design token for a UI — or when upgrading @tailor-platform/app-shell, or diagnosing styling, theming, or dark-mode breakage that appeared after a version bump."
4
+ metadata:
5
+ app-shell-version: "1.19.0"
4
6
  ---
5
7
 
6
8
  # App-Shell Patterns
@@ -9,6 +11,10 @@ description: "Best-practice UI patterns and correct component usage for building
9
11
 
10
12
  Select and implement the correct UI pattern using @tailor-platform/app-shell components.
11
13
 
14
+ ## Version
15
+
16
+ This skill was generated from `@tailor-platform/app-shell` **1.19.0**. Before relying on it, compare that with the version installed in the app you're working on (resolve `@tailor-platform/app-shell/package.json` from that app, not the monorepo root). If they differ, this copy is stale: tell the user and suggest re-syncing it from the installed package's `skills/app-shell-patterns/` directory.
17
+
12
18
  ## Concepts
13
19
 
14
20
  Cross-cutting guides that underpin every screen — styling & theming and the token system, GraphQL data access, building custom components, plus auth and routing. Patterns and components build on these.
@@ -64,6 +70,7 @@ Every AppShell component, its API surface, and usage guidance. Read the entry fo
64
70
  | [GlobalHeaderLayout](references/components/global-header-layout.md) | Opinionated layout with an app-wide header above the whole shell and a sidebar that collapses to a persistent icon rail |
65
71
  | [Grid](references/components/grid.md) | Generic CSS-Grid layout primitive for equal or custom-width columns with responsive reflow, auto-fit, and spanning |
66
72
  | [Input](references/components/input.md) | Styled text input with consistent theming |
73
+ | [ItemList](references/components/item-list.md) | Compact icon-and-label lists for consistent navigation inside and outside the Sidebar |
67
74
  | [Layout](references/components/layout.md) | Responsive column layout component for organizing page content in 1, 2, or 3 columns |
68
75
  | [Menu](references/components/menu.md) | Dropdown menu with a compound component API, supporting checkbox/radio items, groups, separators, and nested sub-menus |
69
76
  | [MetricCard](references/components/metric-card.md) | Compact card for dashboard KPI summaries with title, value, optional trend and description |
@@ -87,33 +94,34 @@ Every AppShell component, its API surface, and usage guidance. Read the entry fo
87
94
 
88
95
  Hooks, factory functions, guards, and router utilities exported by AppShell.
89
96
 
90
- | Name | Description |
91
- | ------------------------------------------------------------------------------------------ | ---------------------------------------------------------------------------------- |
92
- | [createAIGatewayClient](references/api/create-ai-gateway-client.md) | Create a low-level AI Gateway client that reuses AppShell authentication |
93
- | [createTypedPaths](references/api/create-typed-paths.md) | Generate type-safe route paths from file-based routing |
94
- | [defineI18nLabels](references/api/define-i18n-labels.md) | Type-safe locale-first labels for component text and localized navigation metadata |
95
- | [defineModule](references/api/define-module.md) | Define a top-level module that appears in the main navigation |
96
- | [defineResource](references/api/define-resource.md) | Define a nested resource (page) within a module |
97
- | [hidden](references/api/guards/hidden.md) | Guard function that denies access and returns 404 Not Found |
98
- | [Guards Overview](references/api/guards/overview.md) | Access control system for routes and components using guard functions |
99
- | [pass](references/api/guards/pass.md) | Guard function that allows access and continues to the next guard |
100
- | [redirectTo](references/api/guards/redirect-to.md) | Guard function that redirects to another page |
101
- | [useLocation](references/api/router/use-location.md) | Hook to access current location object with pathname, search, and state |
102
- | [useNavigate](references/api/router/use-navigate.md) | Hook for programmatic navigation between routes |
103
- | [useParams](references/api/router/use-params.md) | Hook to access dynamic route parameters |
104
- | [useAIChat](references/api/use-ai-chat.md) | Simple text-only chat hook for AI Gateway |
105
- | [useAppShell](references/api/use-app-shell.md) | Hook to access AppShell context data and configuration |
106
- | [useAppShellConfig](references/api/use-app-shell-config.md) | Hook to access AppShell configuration data |
107
- | [useAppShellData](references/api/use-app-shell-data.md) | Hook to access custom context data passed to AppShell |
108
- | [useAuth](references/api/use-auth.md) | Hook for accessing authentication state and methods |
109
- | [useOverrideBreadcrumb](references/api/use-override-breadcrumb.md) | Hook for dynamically overriding breadcrumb titles from within page components |
110
- | [usePageMeta](references/api/use-page-meta.md) | Hook to resolve page metadata from route path |
111
- | [useRegisterCommandPaletteActions](references/api/use-register-command-palette-actions.md) | Hook for registering contextual page actions into the CommandPalette |
112
- | [useResolvedLocale](references/api/use-resolved-locale.md) | Hook to access the full BCP-47 locale and language code from AppShell context |
113
- | [useRouteError](references/api/use-route-error.md) | Hook to access error details in custom error boundaries |
114
- | [useTheme](references/api/use-theme.md) | Hook for accessing and controlling theme (light/dark mode) |
115
- | [useTimeZone](references/api/use-time-zone.md) | Hook to access the configured IANA timezone from AppShell context |
116
- | [useToast](references/api/use-toast.md) | Hook for displaying toast notifications |
97
+ | Name | Description |
98
+ | ------------------------------------------------------------------------------------------ | ----------------------------------------------------------------------------------------------------------------------------------------- |
99
+ | [Value aggregations](references/api/aggregations.md) | Reusable selector-based sum, average, minimum, maximum, and non-null count over row collections, with optional result-only transformation |
100
+ | [createAIGatewayClient](references/api/create-ai-gateway-client.md) | Create a low-level AI Gateway client that reuses AppShell authentication |
101
+ | [createTypedPaths](references/api/create-typed-paths.md) | Generate type-safe route paths from file-based routing |
102
+ | [defineI18nLabels](references/api/define-i18n-labels.md) | Type-safe locale-first labels for component text and localized navigation metadata |
103
+ | [defineModule](references/api/define-module.md) | Define a top-level module that appears in the main navigation |
104
+ | [defineResource](references/api/define-resource.md) | Define a nested resource (page) within a module |
105
+ | [hidden](references/api/guards/hidden.md) | Guard function that denies access and returns 404 Not Found |
106
+ | [Guards Overview](references/api/guards/overview.md) | Access control system for routes and components using guard functions |
107
+ | [pass](references/api/guards/pass.md) | Guard function that allows access and continues to the next guard |
108
+ | [redirectTo](references/api/guards/redirect-to.md) | Guard function that redirects to another page |
109
+ | [useLocation](references/api/router/use-location.md) | Hook to access current location object with pathname, search, and state |
110
+ | [useNavigate](references/api/router/use-navigate.md) | Hook for programmatic navigation between routes |
111
+ | [useParams](references/api/router/use-params.md) | Hook to access dynamic route parameters |
112
+ | [useAIChat](references/api/use-ai-chat.md) | Simple text-only chat hook for AI Gateway |
113
+ | [useAppShell](references/api/use-app-shell.md) | Hook to access AppShell context data and configuration |
114
+ | [useAppShellConfig](references/api/use-app-shell-config.md) | Hook to access AppShell configuration data |
115
+ | [useAppShellData](references/api/use-app-shell-data.md) | Hook to access custom context data passed to AppShell |
116
+ | [useAuth](references/api/use-auth.md) | Hook for accessing authentication state and methods |
117
+ | [useOverrideBreadcrumb](references/api/use-override-breadcrumb.md) | Hook for dynamically overriding breadcrumb titles from within page components |
118
+ | [usePageMeta](references/api/use-page-meta.md) | Hook to resolve page metadata from route path |
119
+ | [useRegisterCommandPaletteActions](references/api/use-register-command-palette-actions.md) | Hook for registering contextual page actions into the CommandPalette |
120
+ | [useResolvedLocale](references/api/use-resolved-locale.md) | Hook to access the full BCP-47 locale and language code from AppShell context |
121
+ | [useRouteError](references/api/use-route-error.md) | Hook to access error details in custom error boundaries |
122
+ | [useTheme](references/api/use-theme.md) | Hook for accessing and controlling theme (light/dark mode) |
123
+ | [useTimeZone](references/api/use-time-zone.md) | Hook to access the configured IANA timezone from AppShell context |
124
+ | [useToast](references/api/use-toast.md) | Hook for displaying toast notifications |
117
125
 
118
126
  ## Available Pages
119
127
 
@@ -179,6 +187,8 @@ Full rationale in [`styling-theming`](references/concepts/styling-theming.md)
179
187
 
180
188
  - **One primary action per view:** at most one primary/filled `Button`; everything else is `outline`/`secondary`/`ghost`.
181
189
  - **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.
190
+ - **Text uses `Text` and typography roles:** render headings, body copy, labels, and captions with [`Text`](references/components/text.md) and a `variant` (`heading-md`, `body-md`, `body-sm`, `label-md`, …) — never a bare `<p>`, `<h*>`, or `<span>` styled with size and weight utilities such as `text-lg font-semibold` or `text-sm`. Set the heading level with `Text.h1` to `Text.h6` (`<Text.h3 variant="heading-md">`), not by its size, and the color with a token in `className`. For other plain elements use `Text.p`, `Text.span` or `Text.div`; use `render` only to compose with another component. On an element you style yourself, such as a `th`, use the role utility (`text-label-md`). Pick the size with `variant`: a size class such as `text-sm` on `Text` has no effect. `Layout.Header` already renders the page title, and a form field label is [`Field.Label`](references/components/form.md), which already has the label role. Role table and color pairings: [`styling-theming`](references/concepts/styling-theming.md) → Typography.
191
+ - **Status and feedback color by semantic role:** use a component variant first (`Badge`, `Alert`, `Button variant="destructive"`). On your own element, use the roles `{info|success|warning|danger|neutral}-{surface|border|solid|text|contrast|indicator}`, for example `bg-warning-surface text-warning-text border-warning-border`. Error text is `text-danger-text`. Never write the deprecated `--status-*`, `--alert-*` or `--destructive` tokens or their utilities (`bg-status-*`, `text-alert-*`, `bg-destructive`, `text-destructive`) in new code; the next major removes them. When you edit a screen that still uses them, replace them: `text-destructive` → `text-danger-text`, `bg-status-completed` → `bg-success-solid`, `bg-alert-warning-background` → `bg-warning-surface`. Full mapping: [`styling-theming`](references/concepts/styling-theming.md) → Semantic color roles.
182
192
  - **No duplicate actions:** an action lives in exactly one place — never repeat the same action in both `Layout.Header` and `ActionPanel`.
183
193
  - **Action placement:** primary CTA + status in `Layout.Header`; workflow actions in `ActionPanel`; back/navigation in the breadcrumb — never in `ActionPanel`.
184
194
  - **Metric tiles always go in a `Grid`** (`columns={{ initial: 1, md: 2, xl: 4 }}`) — never one-per-row.
@@ -0,0 +1,131 @@
1
+ ---
2
+ title: Value aggregations
3
+ description: Reusable selector-based sum, average, minimum, maximum, and non-null count over row collections, with optional result-only transformation
4
+ ---
5
+
6
+ <!-- GENERATED by docs-kit from docs-src/hooks/aggregations.docs.outline.md — do not edit by hand. -->
7
+
8
+ # Value aggregations
9
+
10
+ `aggregations` creates reusable callbacks that select values from a row collection and aggregate them. Each callback accepts `{ rows: readonly TRow[] }`; the module has no dependency on React, DataTable, or column definitions.
11
+
12
+ ```tsx
13
+ import { aggregations, type Aggregation } from "@tailor-platform/app-shell";
14
+
15
+ type Line = { quantity: number | null };
16
+
17
+ const sum = aggregations.sum((row: Line) => row.quantity);
18
+ sum({ rows: [{ quantity: 1 }, { quantity: 2 }, { quantity: null }] }); // 3
19
+ sum({ rows: [{ quantity: 4 }, { quantity: 5 }] }); // 9: no previous aggregate state
20
+
21
+ const summary: Aggregation<Line, string> = aggregations.sum(
22
+ (row: Line) => row.quantity,
23
+ (value) => (value === null ? "Unavailable" : `${value} units`),
24
+ );
25
+ summary({ rows: [{ quantity: 1 }, { quantity: 2 }] }); // "3 units"
26
+ ```
27
+
28
+ ## Operations
29
+
30
+ | Factory | Result | Empty or all-nullish selected values |
31
+ | ------------------------------ | --------------------------------------- | ------------------------------------ |
32
+ | `aggregations.sum(selector)` | Sum of finite numbers | `0` |
33
+ | `aggregations.avg(selector)` | Average of finite numbers | `null` |
34
+ | `aggregations.min(selector)` | Minimum finite number | `null` |
35
+ | `aggregations.max(selector)` | Maximum finite number | `null` |
36
+ | `aggregations.count(selector)` | Count of non-nullish values of any type | `0` |
37
+
38
+ A selector is required. It receives one row at a time and determines the values to aggregate. Numeric selectors must return `number | null | undefined`; count selectors may return any type. There is no automatic field lookup or connection to a column's `id`, `accessor`, or rendered content.
39
+
40
+ Each factory returns an `Aggregation<TRow, TResult>` callback. Pass a read-only or mutable row array through `{ rows }`; aggregation does not mutate it or retain state between invocations. Selectors and transformers must be pure and must not mutate rows. Exceptions from either callback propagate to the caller.
41
+
42
+ All operations skip selected `null` and `undefined` values. Numeric operations also validate selected values at runtime and return `null` for the entire result if any other value is not a finite JavaScript number, including numeric strings, booleans, dates, `NaN`, and infinities. They do not coerce values, compare dates or strings, or return a partial result. Average divides by the number of non-nullish numeric values, not the row count.
43
+
44
+ Calculations use ordinary JavaScript numbers. A non-finite calculated result returns `null`; for average, this includes an intermediate sum overflow even when the mathematical average could be finite. These functions do not provide arbitrary-precision arithmetic.
45
+
46
+ `count(selector)` counts selected `0`, `false`, and `""` as present values. It counts non-nullish values, not truthy values; use `rows.length` when every row should count.
47
+
48
+ ## Optional transformer
49
+
50
+ Pass a second function to transform the final result:
51
+
52
+ ```tsx
53
+ const averageText = aggregations.avg(
54
+ (row: Line) => row.quantity,
55
+ (value) => value?.toFixed(2) ?? "–",
56
+ );
57
+ averageText({ rows: [{ quantity: 1 }, { quantity: null }, { quantity: 2 }] }); // "1.50"
58
+
59
+ const detailed = aggregations.sum(
60
+ (row: Line) => row.quantity,
61
+ (total) => ({ total, valid: total !== null }),
62
+ );
63
+ detailed({ rows: [{ quantity: 1 }, { quantity: 2 }] }); // { total: 3, valid: true }
64
+ ```
65
+
66
+ The transformer is called exactly once per aggregation invocation, including for empty or invalid input. Numeric transformers receive `number | null`; count transformers receive `number`. Its return type becomes the callback's result type, and may be text, JSX, an object, or another value. The aggregation library does not interpret the transformed result.
67
+
68
+ A transformer receives only the result, not rows, selected values, columns, or UI context. Filter `rows` before invoking the callback when the calculation needs a different input set.
69
+
70
+ For detached callbacks, annotate the selector's row parameter as above so the row and transformed result types can both be inferred. Row types are not restricted to objects; for example, `aggregations.sum<number>((value) => value)({ rows: [1, 2] })` returns `3`.
71
+
72
+ ## Composing with business validation
73
+
74
+ An aggregation is an ordinary callback, so a wrapper can validate the supplied rows before calling it. Keep business rules in the wrapper, numeric calculation in the aggregation, and result formatting in the transformer. The transformer is not an error handler: it receives `null` for invalid numeric results but has no row context or business-error details; selector exceptions still propagate.
75
+
76
+ For example, reject mixed currencies before adding monetary amounts:
77
+
78
+ ```tsx
79
+ type MoneyLine = { amount: number | null; currency: string };
80
+
81
+ const sumAmount = aggregations.sum(
82
+ (row: MoneyLine) => row.amount,
83
+ (total) => (total === null ? "Invalid amount" : total.toLocaleString("en-US")),
84
+ );
85
+
86
+ const moneyFooter = (ctx: { rows: readonly MoneyLine[] }) => {
87
+ const currencies = new Set(
88
+ ctx.rows.filter((row) => row.amount != null).map((row) => row.currency),
89
+ );
90
+ if (currencies.size > 1) {
91
+ return `Mixed currencies: ${[...currencies].join(", ")}`;
92
+ }
93
+ return sumAmount(ctx);
94
+ };
95
+
96
+ moneyFooter({
97
+ rows: [
98
+ { amount: 1000, currency: "JPY" },
99
+ { amount: 500, currency: "JPY" },
100
+ ],
101
+ }); // "1,500"
102
+ moneyFooter({
103
+ rows: [
104
+ { amount: 1000, currency: "JPY" },
105
+ { amount: 500, currency: "USD" },
106
+ ],
107
+ }); // "Mixed currencies: JPY, USD"
108
+ ```
109
+
110
+ The guard ignores nullish amounts because they do not contribute to the sum. A rejected currency combination returns directly without evaluating the selector or transformer. Neither the wrapper nor the helper converts currencies or provides arbitrary-precision arithmetic.
111
+
112
+ Pass `moneyFooter` directly as a column's `footer`. See the [runnable DataTable example](../components/data-table.md#composing-with-business-validation) to toggle mixed currencies and observe the composed behavior.
113
+
114
+ ## DataTable integration
115
+
116
+ Pass an aggregation callback directly to a column footer. It is an ordinary footer renderer that reads the current `ctx.rows`; DataTable needs no aggregation-specific handling:
117
+
118
+ ```tsx
119
+ import { aggregations, createColumnHelper } from "@tailor-platform/app-shell";
120
+
121
+ const { column } = createColumnHelper<{ quantity: number | null }>();
122
+ column({
123
+ id: "quantity",
124
+ type: "number",
125
+ footer: aggregations.sum((row) => row.quantity),
126
+ });
127
+ ```
128
+
129
+ Inline selectors infer the row type from the surrounding footer definition. The selector is the source of truth even when the column has an `accessor`; write the same calculation in the selector if the footer should aggregate a computed cell value.
130
+
131
+ Use a transformer for simple footer text or JSX. DataTable only accepts callback results it can render; object-producing aggregations remain available for non-UI use. For the current-page scope, loading behavior, and custom renderers, see [DataTable column footers](../components/data-table.md#column-footers).
@@ -17,24 +17,30 @@ import { ActionPanel } from "@tailor-platform/app-shell";
17
17
 
18
18
  ## Basic Usage
19
19
 
20
+ <example-preview name="basic-usage"></example-preview>
21
+
20
22
  ```tsx
21
- <ActionPanel
22
- title="Actions"
23
- actions={[
24
- {
25
- key: "create-invoice",
26
- label: "Create new sales invoice",
27
- icon: <ReceiptIcon />,
28
- onClick: () => openCreateInvoiceModal(),
29
- },
30
- {
31
- key: "view-docs",
32
- label: "View documentation",
33
- icon: <ExternalLinkIcon />,
34
- onClick: () => window.open("/docs", "_blank", "noopener,noreferrer"),
35
- },
36
- ]}
37
- />
23
+ function BasicUsage() {
24
+ return (
25
+ <ActionPanel
26
+ title="Actions"
27
+ actions={[
28
+ {
29
+ key: "create-invoice",
30
+ label: "Create new sales invoice",
31
+ icon: <Receipt />,
32
+ onClick: () => {},
33
+ },
34
+ {
35
+ key: "view-docs",
36
+ label: "View documentation",
37
+ icon: <ExternalLink />,
38
+ onClick: () => {},
39
+ },
40
+ ]}
41
+ />
42
+ );
43
+ }
38
44
  ```
39
45
 
40
46
  ## Props
@@ -43,11 +49,12 @@ _Generated from the type surface — first-party props only._
43
49
 
44
50
  ### ActionPanelProps
45
51
 
46
- | Prop | Type | Default | Description |
47
- | ------------ | -------------- | ------- | --------------------------------------------- |
48
- | `actions` | `ActionItem[]` | - | List of actions to render |
49
- | `className?` | `string` | - | Additional CSS classes for the card container |
50
- | `title` | `string` | - | Card title |
52
+ | Prop | Type | Default | Description |
53
+ | --------------- | -------------- | ------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
54
+ | `actions` | `ActionItem[]` | - | List of actions to render |
55
+ | `className?` | `string` | - | Additional CSS classes for the card container |
56
+ | `emptyContent?` | `ReactNode` | - | Content shown with consistent padding when actions is empty. Defaults to a localized "No actions available" message. Strings use the default body text styling; other React nodes render unchanged. Pass null to hide the empty content and its additional vertical spacing while keeping the card and title. |
57
+ | `title` | `string` | - | Card title |
51
58
 
52
59
  ## Exports
53
60
 
@@ -71,7 +78,32 @@ _Generated from the type surface — first-party props only._
71
78
  - Rows are rendered as `<button type="button">` elements.
72
79
  - `disabled` or `loading` makes the row non-interactive.
73
80
  - When `loading` is `true`, the icon slot renders a spinner without shifting layout.
74
- - Empty actions show a built-in fallback message: `No actions available`.
81
+ - Empty actions show `emptyContent`, which defaults to a styled message localized using the AppShell locale: `No actions available` in English or `利用可能なアクションはありません` in Japanese.
82
+
83
+ ## Custom Empty Content
84
+
85
+ Pass a string or any `ReactNode` to `emptyContent` to replace the default content when `actions` is empty. Strings use the same muted `Text` body styling as the default message. Other React nodes render directly inside `Card.Content` without a paragraph wrapper and control their own typography. `Card.Content` owns the spacing, and the title, empty content, and action icons share the same 32px horizontal inset. Action rows retain horizontal padding around their hover backgrounds and focus rings. Custom empty content is ignored when actions are present.
86
+
87
+ <example-preview name="custom-empty-content"></example-preview>
88
+
89
+ ```tsx
90
+ function CustomEmptyContent() {
91
+ return (
92
+ <ActionPanel
93
+ title="操作"
94
+ actions={[]}
95
+ emptyContent={
96
+ <div>
97
+ <p>利用可能な操作はありません</p>
98
+ <p>ステータスが変わると、操作が表示されます。</p>
99
+ </div>
100
+ }
101
+ />
102
+ );
103
+ }
104
+ ```
105
+
106
+ Pass `null` to hide the empty content and its additional vertical spacing while keeping the card and its title. To hide the entire panel when no actions are available, render it conditionally with `actions.length > 0`.
75
107
 
76
108
  ## With Loading State
77
109
 
@@ -32,6 +32,7 @@ function BasicUsage() {
32
32
  <Badge variant="subtle-warning">Subtle Warning</Badge>
33
33
  <Badge variant="subtle-error">Subtle Error</Badge>
34
34
  <Badge variant="subtle-info">Subtle Info</Badge>
35
+ <Badge variant="subtle-neutral">Subtle Neutral</Badge>
35
36
  </div>
36
37
  );
37
38
  }
@@ -54,6 +55,7 @@ Filled variants for high emphasis, plus subtle variants for lower-emphasis statu
54
55
  <Badge variant="subtle-warning">Needs Attention</Badge>
55
56
  <Badge variant="subtle-error">Needs Review</Badge>
56
57
  <Badge variant="subtle-info">In Progress</Badge>
58
+ <Badge variant="subtle-neutral">Archived</Badge>
57
59
  ```
58
60
 
59
61
  ### Outline Variants with Status Dots
@@ -74,9 +76,9 @@ _Generated from the type surface — first-party props only._
74
76
 
75
77
  ### BadgeProps
76
78
 
77
- | Prop | Type | Default | Description |
78
- | ---------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------- | -------------------- |
79
- | `variant?` | `"error" \| "neutral" \| "success" \| "warning" \| "info" \| "default" \| "subtle-success" \| "subtle-warning" \| "subtle-error" \| "subtle-info" \| "outline-success" \| "outline-warning" \| "outline-error" \| "outline-info" \| "outline-neutral" \| null` | `"default"` | Visual style variant |
79
+ | Prop | Type | Default | Description |
80
+ | ---------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------- | -------------------- |
81
+ | `variant?` | `"error" \| "neutral" \| "success" \| "warning" \| "info" \| "default" \| "subtle-success" \| "subtle-warning" \| "subtle-error" \| "subtle-info" \| "subtle-neutral" \| "outline-success" \| "outline-warning" \| "outline-error" \| "outline-info" \| "outline-neutral" \| null` | `"default"` | Visual style variant |
80
82
 
81
83
  ## Exports
82
84
 
@@ -99,6 +101,7 @@ type BadgeVariant =
99
101
  | "subtle-warning" // Low-emphasis yellow
100
102
  | "subtle-error" // Low-emphasis red/destructive
101
103
  | "subtle-info" // Low-emphasis blue
104
+ | "subtle-neutral" // Low-emphasis gray
102
105
  // Outline variants with status dots
103
106
  | "outline-success"
104
107
  | "outline-warning"
@@ -225,6 +228,7 @@ function ProductBadge({ product }: { product: Product }) {
225
228
  | `subtle-warning` | ![Subtle yellow badge] | Low-emphasis pending or attention states |
226
229
  | `subtle-error` | ![Subtle red badge] | Low-emphasis failures or exceptions |
227
230
  | `subtle-info` | ![Subtle blue badge] | Low-emphasis informational or in-progress |
231
+ | `subtle-neutral` | ![Subtle gray badge] | Low-emphasis tags with no status |
228
232
 
229
233
  ### Outline Variants
230
234