@godxjp/ui-mcp 0.8.0 → 0.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/index.js CHANGED
@@ -71,7 +71,7 @@ var COMPONENTS = [
71
71
  "A master list page (e.g. invoices, journal entries, customers) where the header holds the page title, a 'New Invoice' button in `extra`, a breadcrumb trail, and a full-bleed DataTable as the body \u2014 use `variant='flush'` + `<PageInset>` for the FilterBar above the table.",
72
72
  "A detail / edit form page where the footer holds Save and Cancel buttons \u2014 use `footer={<Inline><Button>\u4FDD\u5B58</Button><Button variant='outline'>\u30AD\u30E3\u30F3\u30BB\u30EB</Button></Inline>}` with `stickyFooter` so the actions remain reachable as the form scrolls.",
73
73
  "A settings or narrow-form page (e.g. account profile, entity configuration) where `variant='narrow'` constrains content to a readable column width and `stickyFooter` pins the submit bar.",
74
- "A dashboard page with KPI cards and chart sections \u2014 use `variant='default'` with `children={<Stack gap='lg'>\u2026</Stack>}` to vertically stack multiple Card/CardStat sections beneath the page title.",
74
+ "A dashboard page with KPI cards and chart sections \u2014 use `variant='default'` with `children={<Stack gap='lg'>\u2026</Stack>}` to vertically stack multiple Card/StatCard sections beneath the page title.",
75
75
  "Any deep-nav page in a multi-level admin (e.g. Accounting > Ledger > Journal Entry #42) where a 3-segment breadcrumb trail provides back-navigation without browser history dependence.",
76
76
  "A high-density data reconciliation page where an analyst needs to see maximum rows \u2014 use `density='compact'` to tighten all spacing across the DataTable, FilterBar, and controls in a single prop."
77
77
  ],
@@ -123,15 +123,15 @@ export default function OrdersPage() {
123
123
  ],
124
124
  useCases: [
125
125
  'Top-level page body: wrap KPI `ResponsiveGrid`, standalone `FilterBar`, and a table `Card` in a `<Stack gap="lg">` \u2014 this is the canonical inertia-list-page structure (rule 38 + rule 40).',
126
- "Detail/form pages: stack a `PageHeader` (or `PageContainer` header), a facts `Card` with `KeyValueGrid`, and an action `Card` with `FormField` rows, each separated by a semantically meaningful gap level.",
126
+ "Detail/form pages: stack a `PageHeader` (or `PageContainer` header), a facts `Card` with `Descriptions`, and an action `Card` with `FormField` rows, each separated by a semantically meaningful gap level.",
127
127
  'Card body with multiple related fields: use `<Stack gap="sm">` inside `<CardContent>` to separate labelled input rows without resorting to `space-y-2` utilities.',
128
- 'Dashboard shells: `<Stack gap="xl">` as the page root, containing a `<ResponsiveGrid columns={4}>` of `CardStat` items followed by chart cards and a table card \u2014 each row is a Stack child.',
128
+ 'Dashboard shells: `<Stack gap="xl">` as the page root, containing a `<ResponsiveGrid columns={4}>` of `StatCard` items followed by chart cards and a table card \u2014 each row is a Stack child.',
129
129
  'Modal/Sheet interiors: `<Stack gap="md">` inside `<Dialog>` or `<Sheet>` content area to separate sections (description, form, preview) with consistent token spacing.',
130
130
  'Empty/loading placeholder layout: wrap `<SkeletonTable>` or `<DataState>` in a `<Stack gap="md">` alongside a header block so the skeleton occupies the same vertical rhythm as the loaded page.'
131
131
  ],
132
132
  related: [
133
133
  "Inline \u2014 horizontal counterpart; use for button groups, icon+label rows, badge clusters. When children should sit side-by-side, use Inline. When they should stack top-to-bottom, use Stack. Never use Stack with a flex-row className override.",
134
- "ResponsiveGrid \u2014 use when children are card-shaped items that should tile into 2/3/4 columns on desktop and collapse to 1 column on mobile (e.g., CardStat KPI rows). Stack does not do multi-column layouts.",
134
+ "ResponsiveGrid \u2014 use when children are card-shaped items that should tile into 2/3/4 columns on desktop and collapse to 1 column on mobile (e.g., StatCard KPI rows). Stack does not do multi-column layouts.",
135
135
  "PageContainer \u2014 outer page wrapper that provides title, breadcrumb, and padding context. Stack is the layout primitive used INSIDE PageContainer's children area, not a replacement for it.",
136
136
  "CardContent \u2014 for spacing inside a Card, always use CardContent (which adds consistent padding); don't wrap Card body in a bare Stack with padding classes. A Stack inside CardContent is correct when you need multiple vertically spaced sections within the card body."
137
137
  ],
@@ -170,7 +170,7 @@ export default function OrdersPage() {
170
170
  useCases: [
171
171
  "Action toolbar: grouping a primary Button and a secondary outline Button side-by-side at the bottom of a form or dialog (the catalog example shows exactly this).",
172
172
  "Table/list toolbar: placing a `SearchInput` next to a `FilterBar` trigger or a `DateRangePicker` at the top of a DataTable page, so controls sit in a wrapping row with consistent gap.",
173
- "Status chip row: rendering a cluster of `Badge` or `StatusBadge` components inline (e.g. invoice tags: Paid + Overdue + Draft) without writing ad-hoc flex classes.",
173
+ "Status chip row: rendering a cluster of `Badge` or `Badge` components inline (e.g. invoice tags: Paid + Overdue + Draft) without writing ad-hoc flex classes.",
174
174
  'Icon + text label pair: wrapping a flag icon and country name in `CountrySelect`, or a lucide icon and a span, keeping them vertically centered with a tight `gap="xs"`.',
175
175
  'Card header actions: grouping a `Button variant="ghost"` edit action and a `DropdownMenu` trigger on the right side of a `CardHeader`, using `className="justify-end"` on the Inline.',
176
176
  'Alert action row: pairing two action links/buttons inside an `Alert` body (the godx-ui Alert component itself uses `<Inline gap="xs">` internally for its action cluster).'
@@ -206,39 +206,39 @@ import { Button } from "@godxjp/ui/general";
206
206
  name: "children",
207
207
  type: "ReactNode",
208
208
  required: true,
209
- description: "Grid items \u2014 typically Card or CardStat."
209
+ description: "Grid items \u2014 typically Card or StatCard."
210
210
  }
211
211
  ],
212
212
  usage: [
213
- "DO place CardStat tiles directly as immediate children \u2014 CardStat IS already a bordered card; never wrap it in an extra <Card><CardContent>. The canonical pattern is <ResponsiveGrid columns={4}><CardStat .../><CardStat .../></ResponsiveGrid>.",
213
+ "DO place StatCard tiles directly as immediate children \u2014 StatCard IS already a bordered card; never wrap it in an extra <Card><CardContent>. The canonical pattern is <ResponsiveGrid columns={4}><StatCard .../><StatCard .../></ResponsiveGrid>.",
214
214
  "DO use columns={2|3|4} to declare the target desktop column count \u2014 the grid collapses automatically to 1 column on narrow containers (mobile-first via CSS container queries), via 2-column intermediate at \u2265640px, then full target count at \u22651024px. There is no 'columns={1}' \u2014 omit the grid for single-column flows.",
215
215
  "DO NOT place a DataTable inside a ResponsiveGrid column beside a card or chart. DataTable must occupy its own full-width row in a Card with CardContent flush. Nesting a multi-column table in a grid column squeezes CJK text to one character per line (see rule 37).",
216
216
  "DO use ResponsiveGrid for page-level spacing \u2014 it applies the correct gap token (--space-stack-md) automatically. Never add raw gap-* / p-* / space-* utilities to the page layout around tiles; compose spacing through this component instead (rule 40).",
217
- "DO render SkeletonCard children in place of CardStat tiles while KPIs are loading \u2014 same columns prop, same count as the real tiles. Switch to real CardStat once data resolves.",
217
+ "DO render SkeletonCard children in place of StatCard tiles while KPIs are loading \u2014 same columns prop, same count as the real tiles. Switch to real StatCard once data resolves.",
218
218
  "The grid uses CSS container queries, not viewport media queries \u2014 it responds to its containing block width, not the window. Ensure the container is not artificially constrained (e.g. inside a narrow SplitPane column) or column expansion will never trigger."
219
219
  ],
220
220
  useCases: [
221
- "Dashboard KPI row: rendering 3\u20134 CardStat tiles (revenue, member count, active invoices, overdue amount) that reflow to a 2-column stacked grid on tablet and a single column on mobile.",
222
- "Summary header above a list page: a 2-column grid of two CardStat totals (e.g. total payable vs total paid) sitting above a FilterBar and DataTable.",
223
- "Accounting period overview: 4 CardStat tiles (opening balance, total debits, total credits, closing balance) that collapse gracefully on narrow viewports without any custom CSS.",
224
- "Loading state for a KPI row: identical <ResponsiveGrid columns={4}> wrapping four <SkeletonCard /> placeholders rendered while async data is in flight, swapped for real CardStat tiles once resolved.",
225
- "Settings or profile summary cards: 2- or 3-column grid of Card+CardContent blocks (not CardStat) showing categorized read-only data groups before a detail form below.",
221
+ "Dashboard KPI row: rendering 3\u20134 StatCard tiles (revenue, member count, active invoices, overdue amount) that reflow to a 2-column stacked grid on tablet and a single column on mobile.",
222
+ "Summary header above a list page: a 2-column grid of two StatCard totals (e.g. total payable vs total paid) sitting above a FilterBar and DataTable.",
223
+ "Accounting period overview: 4 StatCard tiles (opening balance, total debits, total credits, closing balance) that collapse gracefully on narrow viewports without any custom CSS.",
224
+ "Loading state for a KPI row: identical <ResponsiveGrid columns={4}> wrapping four <SkeletonCard /> placeholders rendered while async data is in flight, swapped for real StatCard tiles once resolved.",
225
+ "Settings or profile summary cards: 2- or 3-column grid of Card+CardContent blocks (not StatCard) showing categorized read-only data groups before a detail form below.",
226
226
  "Entity comparison panel: a columns={3} grid comparing three legal entities side-by-side with a Card+CardContent per entity, which collapses to 2-up on tablet and stacks on mobile."
227
227
  ],
228
228
  related: [
229
229
  "Stack \u2014 use Stack (vertical) or Inline (horizontal) for sequential blocks of mixed-width content (forms, description lists, button rows). Use ResponsiveGrid only when you want equal-width, auto-reflowing tile columns.",
230
230
  "SplitPane \u2014 use SplitPane for a fixed two-panel side-by-side layout with a defined primary/secondary ratio that does NOT collapse to stacked tiles. Use ResponsiveGrid when you want automatic column count collapse on narrow screens.",
231
- "CardStat \u2014 the canonical direct child of ResponsiveGrid for KPI tiles. CardStat is self-contained (draws its own bordered card); never wrap it in Card/CardContent when placing it inside ResponsiveGrid.",
232
- "SkeletonCard \u2014 the loading-state sibling of CardStat, used as a drop-in placeholder child of ResponsiveGrid with the same columns count while KPI data is in flight."
231
+ "StatCard \u2014 the canonical direct child of ResponsiveGrid for KPI tiles. StatCard is self-contained (draws its own bordered card); never wrap it in Card/CardContent when placing it inside ResponsiveGrid.",
232
+ "SkeletonCard \u2014 the loading-state sibling of StatCard, used as a drop-in placeholder child of ResponsiveGrid with the same columns count while KPI data is in flight."
233
233
  ],
234
234
  example: `import { ResponsiveGrid } from "@godxjp/ui/layout";
235
- import { CardStat } from "@godxjp/ui/data-display";
235
+ import { StatCard } from "@godxjp/ui/data-display";
236
236
 
237
237
  <ResponsiveGrid columns={4}>
238
- <CardStat label="\u7DCF\u4F1A\u54E1\u6570" value="12,400" />
239
- <CardStat label="\u516C\u958B\u4E2D\u30AF\u30FC\u30DD\u30F3" value="8" />
240
- <CardStat label="\u6708\u9593\u5229\u7528\u6570" value="3,210" />
241
- <CardStat label="\u5272\u5F15\u7DCF\u984D" value="\xA5480,000" />
238
+ <StatCard label="\u7DCF\u4F1A\u54E1\u6570" value="12,400" />
239
+ <StatCard label="\u516C\u958B\u4E2D\u30AF\u30FC\u30DD\u30F3" value="8" />
240
+ <StatCard label="\u6708\u9593\u5229\u7528\u6570" value="3,210" />
241
+ <StatCard label="\u5272\u5F15\u7DCF\u984D" value="\xA5480,000" />
242
242
  </ResponsiveGrid>`,
243
243
  storyPath: "layout/ResponsiveGrid.stories.tsx",
244
244
  rules: [24, 40]
@@ -297,7 +297,7 @@ import { CardStat } from "@godxjp/ui/data-display";
297
297
  "DO use the auto-built topbar rail (logo / topbarLeft / topbarRight) for simple shells. Pass a fully configured <Topbar> to the `topbar` prop only when you need live handlers (entity switcher via productMenu, search, notifications, user avatar) \u2014 when `topbar` is provided, logo/topbarLeft/topbarRight are ignored entirely.",
298
298
  "DO wire a single `sidebarCollapsed` boolean between AppShell's `sidebarCollapsed` prop and Sidebar's `collapsed` prop \u2014 AppShell sets `data-collapsed='true'` on the root div (which CSS reads for width transitions) but does NOT own the collapsed state itself; lift the state and pass it down to both.",
299
299
  "DO place breadcrumb content in AppShell's `breadcrumb` prop (renders in the `app-breadcrumb` div inside `<main>` ABOVE children) \u2014 do NOT hand-roll a breadcrumb bar as the first child of children, and do NOT put breadcrumbs inside <Sidebar>.",
300
- "DO NOT nest a second AppShell or ShellApp inside AppShell's children \u2014 AppShell renders the root `app-root` div; nesting shells breaks the CSS grid layout.",
300
+ "DO NOT nest a second AppShell or AppShell inside AppShell's children \u2014 AppShell renders the root `app-root` div; nesting shells breaks the CSS grid layout.",
301
301
  "DO NOT add padding directly to children expecting it to reach the viewport edge \u2014 AppShell's `<main>` is a scroll container; use <PageContainer> (or <PageInset> inside a flush PageContainer) inside children to get standard page padding."
302
302
  ],
303
303
  useCases: [
@@ -309,7 +309,7 @@ import { CardStat } from "@godxjp/ui/data-display";
309
309
  "Breadcrumb-aware shell: pass a <Breadcrumb items={\u2026}> node to AppShell's `breadcrumb` prop so the breadcrumb strip appears above all page content without each page having to render it separately."
310
310
  ],
311
311
  related: [
312
- "ShellApp \u2014 opinionated wrapper that composes AppShell + a frozen default Topbar in three props (menu, children, breadcrumb). Use ShellApp for quick scaffolding when the default GodX product chip and no-op search/notification handlers are acceptable; switch to AppShell directly the moment you need a custom entity switcher, real onSearchOpen, user slot, or any topbar configuration.",
312
+ "AppShell \u2014 opinionated wrapper that composes AppShell + a frozen default Topbar in three props (menu, children, breadcrumb). Use AppShell for quick scaffolding when the default GodX product chip and no-op search/notification handlers are acceptable; switch to AppShell directly the moment you need a custom entity switcher, real onSearchOpen, user slot, or any topbar configuration.",
313
313
  "Sidebar \u2014 the canonical node to pass as AppShell's `sidebar` prop; owns activeId, collapsible submenu groups, collapsed icon-only mode, and section labels. Never hand-roll a nav list inside the sidebar slot.",
314
314
  "Topbar \u2014 the structured topbar component to pass to AppShell's `topbar` prop when you need live product/project chip switchers, search, notifications, sidebar toggle, user avatar, or rightSlot extras. When `topbar` is provided, AppShell's logo/topbarLeft/topbarRight props are ignored.",
315
315
  "PageContainer \u2014 the mandatory direct child inside AppShell's `children` for every page; provides title, subtitle, extra actions, breadcrumb, footer, variant (flush/narrow/ghost), and density. Never render raw content directly as AppShell's child without a PageContainer wrapper."
@@ -330,7 +330,7 @@ const sidebar = (
330
330
  />
331
331
  );
332
332
 
333
- export function CrmLayout({ children }: { children: React.ReactNode }) {
333
+ export function CrmLayout({ children }: { content: React.ReactNode }) {
334
334
  return <AppShell sidebar={sidebar}>{children}</AppShell>;
335
335
  }`,
336
336
  storyPath: "layout/AppShell.stories.tsx",
@@ -387,7 +387,7 @@ export function CrmLayout({ children }: { children: React.ReactNode }) {
387
387
  ],
388
388
  usage: [
389
389
  "DO: Define all nav items as a SidebarSectionProp[] data structure and pass it to sections \u2014 never hand-roll nav buttons alongside or instead of the Sidebar.",
390
- "DO: Add children: SidebarItemProp[] to any SidebarItemProp to create a collapsible submenu group. The parent item's icon is required even for groups. The group auto-opens and highlights when activeId matches any descendant.",
390
+ "DO: Add content: SidebarItemProp[] to any SidebarItemProp to create a collapsible submenu group. The parent item's icon is required even for groups. The group auto-opens and highlights when activeId matches any descendant.",
391
391
  "DO: Mirror the collapsed boolean between AppShell's sidebarCollapsed prop and Sidebar's collapsed prop \u2014 they must stay in sync so the shell layout grid adjusts correctly.",
392
392
  "DO: Use the footer prop for user info or status \u2014 it is pinned below the scroll area and does not scroll away.",
393
393
  "DON'T: Manage collapse state inside the Sidebar \u2014 it is stateless. Hoist the boolean to your shell/page state and pass it down via both AppShell.sidebarCollapsed and Sidebar.collapsed.",
@@ -421,7 +421,7 @@ const sections: SidebarSection[] = [
421
421
  id: "ledger",
422
422
  label: "Ledger",
423
423
  icon: BookOpen,
424
- children: [
424
+ content: [
425
425
  { id: "journal", label: "Journal", icon: FileText },
426
426
  { id: "chart-of-accounts", label: "Chart of Accounts", icon: CreditCard },
427
427
  ],
@@ -480,7 +480,7 @@ export default function Shell() {
480
480
  {
481
481
  name: "Topbar",
482
482
  group: "layout",
483
- tagline: "App-shell top bar with product/project chip switchers, search, notifications, and sidebar toggle \u2014 pass DropdownMenuContent to productMenu/projectMenu to turn any chip into a real dropdown switcher; the project chip is hidden entirely when neither project nor projectMenu is set.",
483
+ tagline: "App-shell top bar with product/project chip switchers, search, notifications, and sidebar toggle \u2014 pass DropdownMenuContent to productMenu/projectMenu to turn any chip into a real dropdown switcher; the project chip is hidden entirely when neither project nor projectSidebar is set.",
484
484
  props: [
485
485
  {
486
486
  name: "product",
@@ -587,7 +587,7 @@ export default function Shell() {
587
587
  "AppShell \u2014 the parent shell component that places Topbar inside its `app-topbar` header region via the `topbar` prop. Always use Topbar inside AppShell, not standalone.",
588
588
  "Sidebar \u2014 the companion left-rail nav; pair with Topbar's `collapsed`/`onToggleCollapsed` to keep sidebar and topbar toggle state in sync.",
589
589
  "DropdownMenu / DropdownMenuContent \u2014 pass a `DropdownMenuContent` as `productMenu` or `projectMenu` to turn a chip into an inline switcher. Topbar handles the `DropdownMenuTrigger` wrapping internally; only the Content node is needed.",
590
- "ShellApp \u2014 a higher-level opinionated shell that already composes AppShell + Topbar with hardcoded product/project chips; use it for prototypes but use AppShell + Topbar directly for production apps that need real switcher props."
590
+ "AppShell \u2014 a higher-level opinionated shell that already composes AppShell + Topbar with hardcoded product/project chips; use it for prototypes but use AppShell + Topbar directly for production apps that need real switcher props."
591
591
  ],
592
592
  example: `import { Topbar } from "@godxjp/ui/layout";
593
593
  import { AppShell } from "@godxjp/ui/layout";
@@ -598,7 +598,7 @@ import {
598
598
 
599
599
  // Entity-switcher example: product chip opens an entity dropdown,
600
600
  // project chip is hidden (no project concept in this app).
601
- function MyShell({ children }: { children: React.ReactNode }) {
601
+ function MyShell({ children }: { content: React.ReactNode }) {
602
602
  const [collapsed, setCollapsed] = React.useState(false);
603
603
  const [unread, setUnread] = React.useState(true);
604
604
 
@@ -661,7 +661,7 @@ function MyShell({ children }: { children: React.ReactNode }) {
661
661
  "Dashboard section inside a flush PageContainer that shows a brief intro paragraph or status summary strip before a full-width chart or DataTable.",
662
662
  "Multi-section flush page where two or more padded action bars (bulk-action toolbar, pagination row) appear between full-bleed tables \u2014 each strip gets its own PageInset.",
663
663
  'Settings or form pages using variant="flush" where a prominent alert or MutationFeedback banner must align with the form fields rendered in a padded section below.',
664
- "Accounting detail pages (e.g., journal entry list) where a summary KeyValueGrid strip needs the same left-edge as the page header while the entry rows below are full-bleed."
664
+ "Accounting detail pages (e.g., journal entry list) where a summary Descriptions strip needs the same left-edge as the page header while the entry rows below are full-bleed."
665
665
  ],
666
666
  related: [
667
667
  'PageContainer \u2014 the required parent; PageInset only makes sense as a child of PageContainer variant="flush". Use PageContainer for all other padding needs (default variant already provides horizontal padding everywhere).',
@@ -706,7 +706,7 @@ function MyShell({ children }: { children: React.ReactNode }) {
706
706
  "DON'T: hand-roll a two-column div layout with flexbox or CSS Grid when SplitPane already ships \u2014 that duplicates the responsive breakpoint logic and the semantic `<aside>` element."
707
707
  ],
708
708
  useCases: [
709
- "Invoice / transaction detail page: list of records in `children` (DataTable), selected-record detail panel in `aside` (KeyValueGrid + Timeline).",
709
+ "Invoice / transaction detail page: list of records in `children` (DataTable), selected-record detail panel in `aside` (Descriptions + Timeline).",
710
710
  'Accounting ledger drill-down: account list on the left, chart-of-accounts metadata or running balance breakdown on the right using `asideWidth="sm"`.',
711
711
  "Document review workflow: PDF or rich-text viewer in `children`, approval form or annotation panel in `aside`.",
712
712
  "Settings page with a category list or Steps navigator in `children` and a live preview or summary card in `aside`.",
@@ -741,14 +741,14 @@ function MyShell({ children }: { children: React.ReactNode }) {
741
741
  usage: [
742
742
  "DO import from `@godxjp/ui/layout` (not from a navigation or general sub-path) and pass a single `items` prop \u2014 an ordered array of `{ label, to? }` objects. No children, no sub-components, no render-prop API.",
743
743
  'DO omit `to` on the last (current-page) segment \u2014 the component automatically renders it as a `<span aria-current="page">` instead of a router `<Link>`. Passing `to` on the last item does NOT make it a link; drop it intentionally.',
744
- "DO pass the Breadcrumb node as a ReactNode to the `breadcrumb` prop of `ShellApp` (or `AppShell`) for shell-level breadcrumbs, or to `PageContainer`'s `breadcrumb` prop (which accepts `BreadcrumbItemProp[]` directly \u2014 not a ReactNode). When passing to `PageContainer`, pass the raw array; when passing to `ShellApp`, wrap it: `breadcrumb={<Breadcrumb items={\u2026} />}`.",
744
+ "DO pass the Breadcrumb node as a ReactNode to the `breadcrumb` prop of `AppShell` (or `AppShell`) for shell-level breadcrumbs, or to `PageContainer`'s `breadcrumb` prop (which accepts `BreadcrumbItemProp[]` directly \u2014 not a ReactNode). When passing to `PageContainer`, pass the raw array; when passing to `AppShell`, wrap it: `breadcrumb={<Breadcrumb items={\u2026} />}`.",
745
745
  'DON\'T hand-roll a breadcrumb strip (divs with chevrons, anchors, separators) \u2014 Breadcrumb ships the `<nav aria-label="Breadcrumb">` + `<ol>` + `aria-hidden` chevrons. Any custom trail is a violation of the no-hand-roll rule and will fail `npm run ui:audit`.',
746
746
  "DON'T use Breadcrumb for tab-style or step-style navigation (multi-step forms, wizard progress). Those flows belong to `Steps`. Breadcrumb is strictly a spatial location trail, not a process indicator.",
747
747
  "The component is fully uncontrolled and stateless \u2014 it renders whatever `items` you pass. Dynamic breadcrumbs (route-derived, breadcrumb context, etc.) must be assembled in the parent and passed down as a plain array; there is no internal routing awareness."
748
748
  ],
749
749
  useCases: [
750
750
  "Per-page location trail on any admin page deeper than two levels \u2014 e.g. Home \u2192 Accounting \u2192 Invoices \u2192 Invoice #1042 \u2014 passed to `PageContainer`'s `breadcrumb` prop so it appears above the page `<h1>`.",
751
- "Persistent shell-level breadcrumb in a `ShellApp` or `AppShell` layout that updates as the user navigates between Inertia/React Router pages; constructed from route params and passed as a ReactNode to `ShellApp`'s `breadcrumb` prop.",
751
+ "Persistent shell-level breadcrumb in a `AppShell` or `AppShell` layout that updates as the user navigates between Inertia/React Router pages; constructed from route params and passed as a ReactNode to `AppShell`'s `breadcrumb` prop.",
752
752
  "Master-detail drill-down in an accounting app: the detail page (journal entry, partner, bank account) shows a breadcrumb back to the list and to the domain root, giving the user a one-click escape without using the browser back button.",
753
753
  "Embedded sub-panel breadcrumb inside a `SplitPane` or `Sheet` where a secondary content area has its own navigable hierarchy and needs a compact location indicator.",
754
754
  "Audit log or document history page where the entity being reviewed (invoice, payment) is the current segment and the parent module (Accounting, Receivables) is a clickable ancestor.",
@@ -756,7 +756,7 @@ function MyShell({ children }: { children: React.ReactNode }) {
756
756
  ],
757
757
  related: [
758
758
  "PageContainer \u2014 accepts `breadcrumb` as `BreadcrumbItemProp[]` (raw array, not a ReactNode); use this when each page owns its own breadcrumb and you want it co-located with the page title, actions, and body.",
759
- "ShellApp \u2014 accepts `breadcrumb` as `ReactNode`; pass `<Breadcrumb items={\u2026} />` here when the breadcrumb is a persistent shell-level strip that sits above all page content rather than being owned by individual pages.",
759
+ "AppShell \u2014 accepts `breadcrumb` as `ReactNode`; pass `<Breadcrumb items={\u2026} />` here when the breadcrumb is a persistent shell-level strip that sits above all page content rather than being owned by individual pages.",
760
760
  "Steps \u2014 use instead of Breadcrumb when showing progress through an ordered multi-step flow (wizard, checkout, onboarding); Steps conveys sequence and completion state, not spatial location.",
761
761
  "PrefetchLink \u2014 if ancestor breadcrumb segments should prefetch their destination query on hover/focus, consider pairing the `to` values with `PrefetchLink` in a custom breadcrumb or pre-warming the cache on mount; Breadcrumb's internal links are plain react-router-dom `<Link>` with no prefetch behaviour."
762
762
  ],
@@ -850,7 +850,7 @@ import { Trash2 } from "lucide-react";
850
850
  name: "columns",
851
851
  type: "ColumnDef<T>[]",
852
852
  required: true,
853
- description: "Column definitions. Each column: { key: string; header: ReactNode; render?: (row: T) => ReactNode; sortable?: boolean; width?: string; align?: 'left'|'center'|'right'; hiddenOnMobile?: boolean }. If render is omitted, the raw value at row[key] is rendered as a string."
853
+ description: "Column definitions. Each column: { value: string; header: ReactNode; render?: (row: T) => ReactNode; sortable?: boolean; width?: string; align?: 'left'|'center'|'right'; hiddenOnMobile?: boolean }. If render is omitted, the raw value at row[key] is rendered as a string."
854
854
  },
855
855
  {
856
856
  name: "getRowId",
@@ -892,12 +892,12 @@ import { Trash2 } from "lucide-react";
892
892
  },
893
893
  {
894
894
  name: "sort",
895
- type: "{ key: string; direction: 'asc' | 'desc' }",
895
+ type: "{ value: string; direction: 'asc' | 'desc' }",
896
896
  description: "Active sort state. When provided alongside onSortChange, sortable columns show directional arrow icons and are clickable. Clicking the active column twice clears sort (calls onSortChange(undefined))."
897
897
  },
898
898
  {
899
899
  name: "onSortChange",
900
- type: "(sort: { key: string; direction: 'asc' | 'desc' } | undefined) => void",
900
+ type: "(sort: { value: string; direction: 'asc' | 'desc' } | undefined) => void",
901
901
  description: "Called when a sortable column header is clicked. Receives undefined when sort is cleared (third click on same column)."
902
902
  },
903
903
  {
@@ -956,10 +956,10 @@ type Invoice = {
956
956
  };
957
957
 
958
958
  const columns: ColumnDef<Invoice>[] = [
959
- { key: "id", header: "Invoice #", width: "w-32" },
960
- { key: "customer", header: "Customer" },
959
+ { value: "id", header: "Invoice #", width: "w-32" },
960
+ { value: "customer", header: "Customer" },
961
961
  {
962
- key: "status",
962
+ value: "status",
963
963
  header: "Status",
964
964
  render: (row) => (
965
965
  <Badge
@@ -971,7 +971,7 @@ const columns: ColumnDef<Invoice>[] = [
971
971
  </Badge>
972
972
  ),
973
973
  },
974
- { key: "amount", header: "Amount", align: "right", sortable: true },
974
+ { value: "amount", header: "Amount", align: "right", sortable: true },
975
975
  ];
976
976
 
977
977
  export default function InvoiceList({
@@ -982,7 +982,7 @@ export default function InvoiceList({
982
982
  loading: boolean;
983
983
  }) {
984
984
  const [selected, setSelected] = useState<Set<string>>(new Set());
985
- const [sort, setSort] = useState<{ key: string; direction: "asc" | "desc" } | undefined>();
985
+ const [sort, setSort] = useState<{ value: string; direction: "asc" | "desc" } | undefined>();
986
986
 
987
987
  return (
988
988
  <DataTable
@@ -1050,20 +1050,20 @@ export default function InvoiceList({
1050
1050
  "DO use <CardContent flush> for edge-to-edge children such as DataTable, Table, or a Tabs list \u2014 this removes horizontal padding. Combine with <CardContent tight> when there is no visual gap needed after the header, and <CardContent solo> when there is no CardHeader above (top padding matches the card shell).",
1051
1051
  "DO use <CardFooter separated> to render a top-bordered action band (Save/Cancel buttons, table summary row). Use <CardFooter flush> for a full-bleed footer bar.",
1052
1052
  "DO use <CardCover> as the first child for full-bleed cover media \u2014 the header below it uses card-section top spacing, not the card shell.",
1053
- "DON'T hand-roll a stat/KPI tile with <Card> + raw divs \u2014 use <CardStat> (label, value, hint, delta, layout, inverse props) which is already a Card internally with correct token-driven layout."
1053
+ "DON'T hand-roll a stat/KPI tile with <Card> + raw divs \u2014 use <StatCard> (label, value, hint, delta, layout, inverse props) which is already a Card internally with correct token-driven layout."
1054
1054
  ],
1055
1055
  useCases: [
1056
- 'Dashboard KPI summary row: wrap each metric in <CardStat> (or a plain <Card size="compact"> with <CardContent>) to render a uniform grid of labeled value tiles with optional trend deltas.',
1057
- 'Invoice or order detail panel: <Card accent="primary"> with <CardHeader banded><CardTitle>, <CardContent> body rows (use <KeyValueGrid> inside), and <CardFooter separated> holding approve/reject buttons.',
1056
+ 'Dashboard KPI summary row: wrap each metric in <StatCard> (or a plain <Card size="compact"> with <CardContent>) to render a uniform grid of labeled value tiles with optional trend deltas.',
1057
+ 'Invoice or order detail panel: <Card accent="primary"> with <CardHeader banded><CardTitle>, <CardContent> body rows (use <Descriptions> inside), and <CardFooter separated> holding approve/reject buttons.',
1058
1058
  "Section container on a settings or form page: a single <Card> wrapping a <CardHeader><CardTitle> plus <CardContent> containing <FormField> groups, with <CardFooter separated> for Save/Cancel.",
1059
1059
  "Data table with toolbar: <Card> + <CardHeader> (title + filter controls in <CardAction>) + <CardContent flush> containing <DataTable> \u2014 <CardContent flush> removes horizontal padding so the table header spans full width.",
1060
1060
  'Featured announcement or alert card: <Card variant="featured"> with an accent stripe (<accent="warning">) to visually elevate a card above sibling cards on the page.',
1061
1061
  "Media/cover card (e.g. entity profile): <CardCover> first (full-bleed image), then <CardHeader> + <CardContent> below it for structured metadata."
1062
1062
  ],
1063
1063
  related: [
1064
- "CardStat \u2014 use instead of a plain Card when rendering a KPI/metric tile (label + value + optional delta/hint). CardStat is a Card internally; do not re-wrap it in another Card.",
1064
+ "StatCard \u2014 use instead of a plain Card when rendering a KPI/metric tile (label + value + optional delta/hint). StatCard is a Card internally; do not re-wrap it in another Card.",
1065
1065
  "CardContent \u2014 mandatory inner wrapper for all body content inside Card. Provides the correct padding and supports flush/tight/solo variants. The only correct way to put padded content inside Card.",
1066
- "KeyValueGrid \u2014 use inside <CardContent> when body content is a label-value metadata list (e.g. entity details, invoice fields); do not hand-roll a dl/dt/dd grid.",
1066
+ "Descriptions \u2014 use inside <CardContent> when body content is a label-value metadata list (e.g. entity details, invoice fields); do not hand-roll a dl/dt/dd grid.",
1067
1067
  "DataState / InfiniteQueryState \u2014 use instead of Card when the content is a TanStack Query-driven list that needs automatic skeleton, empty, and error states; Card does not manage loading lifecycle."
1068
1068
  ],
1069
1069
  example: `import { Card, CardHeader, CardTitle, CardContent } from "@godxjp/ui/data-display";
@@ -1102,19 +1102,19 @@ export default function InvoiceList({
1102
1102
  "DO: Use <CardContent tight> when placing a flush toolbar or a Tabs list directly below a CardHeader \u2014 tight removes the top gap so the header and the body connect without an awkward spacing gap.",
1103
1103
  "DO: Use <CardContent solo> when the card has no CardHeader above it \u2014 solo gives the top padding that matches the card shell, ensuring visual balance.",
1104
1104
  "DON'T: Nest a FilterBar inside <CardContent flush> \u2014 flush strips horizontal padding and FilterBar will lose its own padding. Put FilterBar outside the flush CardContent or in a separate non-flush CardContent above it.",
1105
- "DON'T: Wrap a CardStat inside <Card><CardContent> \u2014 CardStat already renders its own Card border; double-wrapping produces a double border. Render CardStat directly in a ResponsiveGrid."
1105
+ "DON'T: Wrap a StatCard inside <Card><CardContent> \u2014 StatCard already renders its own Card border; double-wrapping produces a double border. Render StatCard directly in a ResponsiveGrid."
1106
1106
  ],
1107
1107
  useCases: [
1108
1108
  "Wrapping a form body (Input, Select, Textarea fields) inside a Card that has a CardHeader title \u2014 ensures the form fields have correct internal padding.",
1109
1109
  "Hosting a DataTable inside a Card edge-to-edge: <CardContent flush><DataTable .../></CardContent> \u2014 the table occupies the full card width with the card's border acting as the table container.",
1110
1110
  "Dashboard detail panels where the card has no title \u2014 <CardContent solo> gives top padding equivalent to the card shell so the content doesn't sit too close to the top border.",
1111
- "Placing a KeyValueGrid or Timeline inside a card to display invoice/accounting details \u2014 <CardContent> provides the standard 16px (or density-adjusted) padding without needing manual className.",
1111
+ "Placing a Descriptions or Timeline inside a card to display invoice/accounting details \u2014 <CardContent> provides the standard 16px (or density-adjusted) padding without needing manual className.",
1112
1112
  "Pairing with <CardHeader banded> and <CardFooter separated> in a multi-section layout such as a payment summary card \u2014 each section slot (header, content, footer) carries its own semantic spacing tokens.",
1113
1113
  "Putting a ScrollArea inside <CardContent> (not flush) to create a scrollable card body with consistent padding, e.g. a chat or log viewer panel."
1114
1114
  ],
1115
1115
  related: [
1116
1116
  "Card \u2014 the parent container; CardContent is always a direct child of Card. Card itself has zero internal padding; every visible body padding comes from CardContent (or CardHeader/CardFooter). Never put content directly inside Card.",
1117
- "CardStat \u2014 a self-contained KPI tile that IS already a Card; do not wrap it in <Card><CardContent>. Use CardStat directly inside a ResponsiveGrid.",
1117
+ "StatCard \u2014 a self-contained KPI tile that IS already a Card; do not wrap it in <Card><CardContent>. Use StatCard directly inside a ResponsiveGrid.",
1118
1118
  "ScrollArea \u2014 place ScrollArea inside CardContent (non-flush) when the card body needs to scroll; do not put ScrollArea outside CardContent or you lose the card's internal padding.",
1119
1119
  "SkeletonCard \u2014 the loading placeholder for a Card+CardContent shape; swap in SkeletonCard while card data is loading instead of rendering an empty CardContent."
1120
1120
  ],
@@ -1129,9 +1129,9 @@ export default function InvoiceList({
1129
1129
  rules: [37, 38]
1130
1130
  },
1131
1131
  {
1132
- name: "CardStat",
1132
+ name: "StatCard",
1133
1133
  group: "data-display",
1134
- tagline: "KPI tile. \u26A0\uFE0F CardStat IS ALREADY a bordered Card \u2014 render it DIRECTLY in ResponsiveGrid. NEVER wrap it in <Card>/<CardContent> (that double-borders it \u2192 looks too thick). NO accent prop (accent is a Card prop).",
1134
+ tagline: "KPI tile. \u26A0\uFE0F StatCard IS ALREADY a bordered Card \u2014 render it DIRECTLY in ResponsiveGrid. NEVER wrap it in <Card>/<CardContent> (that double-borders it \u2192 looks too thick). NO accent prop (accent is a Card prop).",
1135
1135
  props: [
1136
1136
  { name: "label", type: "ReactNode", required: true, description: "Metric name." },
1137
1137
  {
@@ -1155,12 +1155,12 @@ export default function InvoiceList({
1155
1155
  { name: "align", type: '"start" | "end"', description: "Align the metric group." }
1156
1156
  ],
1157
1157
  usage: [
1158
- "DO place CardStat directly as a child of ResponsiveGrid \u2014 it renders its own bordered Card shell internally, so no wrapping <Card> or <CardContent> is needed or allowed. Wrapping creates a double border.",
1158
+ "DO place StatCard directly as a child of ResponsiveGrid \u2014 it renders its own bordered Card shell internally, so no wrapping <Card> or <CardContent> is needed or allowed. Wrapping creates a double border.",
1159
1159
  "DO pass `delta` as a sign-prefixed string (e.g. '+12%' or '-3%') to get automatic color tone: '+' renders text-success, '-' renders text-destructive. For metrics where a negative delta is good (e.g. cost reduction, error rate), pass `inverse` so the tone is flipped correctly.",
1160
1160
  "DO use `hint` for secondary context (e.g. '\u5148\u6708\u6BD4 +3%', 'last 30 days'). In the default `stacked` layout hint renders below the value; in `inline` layout it renders beside the label.",
1161
- "DO NOT add an `accent` prop \u2014 accent is a Card prop and CardStat does not expose it. Passing accent has no effect and creates a false expectation.",
1162
- "DO NOT hand-roll a KPI tile using a plain <Card><CardContent>. CardStat is the correct primitive and token-aligns the label/value/hint/delta slots automatically.",
1163
- "WHILE data is loading, replace each CardStat with a <SkeletonCard /> at the same grid position \u2014 never render an empty value string or a spinner inside CardStat itself."
1161
+ "DO NOT add an `accent` prop \u2014 accent is a Card prop and StatCard does not expose it. Passing accent has no effect and creates a false expectation.",
1162
+ "DO NOT hand-roll a KPI tile using a plain <Card><CardContent>. StatCard is the correct primitive and token-aligns the label/value/hint/delta slots automatically.",
1163
+ "WHILE data is loading, replace each StatCard with a <SkeletonCard /> at the same grid position \u2014 never render an empty value string or a spinner inside StatCard itself."
1164
1164
  ],
1165
1165
  useCases: [
1166
1166
  "Dashboard KPI row: monthly revenue, invoice count, overdue balance, and collection rate displayed side-by-side in a ResponsiveGrid with delta trend vs previous period.",
@@ -1168,132 +1168,82 @@ export default function InvoiceList({
1168
1168
  "Coupon/membership admin overview: active members, live coupons, monthly redemptions, and total discount amount \u2014 the canonical example in the catalog.",
1169
1169
  "Inline variant for a narrow sidebar or detail panel where space is constrained: label on the left, large value on the right (layout='inline'), e.g. contract value next to a deal record.",
1170
1170
  "Cost or error-rate metrics where a falling number is positive: pass `inverse` so a '-15%' delta shows green, preventing misleading red-for-good UI.",
1171
- "Loading state for any KPI grid: render the same ResponsiveGrid columns filled with <SkeletonCard /> components while the query is in-flight, then replace with CardStat tiles once data resolves."
1171
+ "Loading state for any KPI grid: render the same ResponsiveGrid columns filled with <SkeletonCard /> components while the query is in-flight, then replace with StatCard tiles once data resolves."
1172
1172
  ],
1173
1173
  related: [
1174
- "ResponsiveGrid \u2014 required layout wrapper for CardStat grids; controls column count and responsive breakpoints. Always pair them together.",
1175
- "SkeletonCard \u2014 exact loading placeholder shaped like a CardStat tile; swap in while KPI data is fetching, then replace with the real CardStat.",
1176
- "KeyValueGrid \u2014 use instead when displaying multiple label/value metadata pairs on a detail page (not headline KPIs); KeyValueGrid is not card-bordered and does not show delta/hint slots.",
1177
- "Card + CardContent \u2014 use when you need a general-purpose content container with a header, footer, or arbitrary body; do NOT wrap CardStat inside these."
1174
+ "ResponsiveGrid \u2014 required layout wrapper for StatCard grids; controls column count and responsive breakpoints. Always pair them together.",
1175
+ "SkeletonCard \u2014 exact loading placeholder shaped like a StatCard tile; swap in while KPI data is fetching, then replace with the real StatCard.",
1176
+ "Descriptions \u2014 use instead when displaying multiple label/value metadata pairs on a detail page (not headline KPIs); Descriptions is not card-bordered and does not show delta/hint slots.",
1177
+ "Card + CardContent \u2014 use when you need a general-purpose content container with a header, footer, or arbitrary body; do NOT wrap StatCard inside these."
1178
1178
  ],
1179
- example: `import { CardStat } from "@godxjp/ui/data-display";
1179
+ example: `import { StatCard } from "@godxjp/ui/data-display";
1180
1180
  import { ResponsiveGrid } from "@godxjp/ui/layout";
1181
1181
 
1182
- // \u2705 CardStat sits directly in the grid \u2014 it draws its own card + border.
1182
+ // \u2705 StatCard sits directly in the grid \u2014 it draws its own card + border.
1183
1183
  <ResponsiveGrid columns={3}>
1184
- <CardStat label="\u7DCF\u4F1A\u54E1\u6570" value="12,450" hint="\u5148\u6708\u6BD4 +3%" />
1185
- <CardStat label="\u6708\u6B21\u58F2\u4E0A" value="\xA58,200,000" delta="+12%" />
1186
- <CardStat label="\u5229\u7528\u7387" value="68.4%" />
1184
+ <StatCard label="\u7DCF\u4F1A\u54E1\u6570" value="12,450" hint="\u5148\u6708\u6BD4 +3%" />
1185
+ <StatCard label="\u6708\u6B21\u58F2\u4E0A" value="\xA58,200,000" delta="+12%" />
1186
+ <StatCard label="\u5229\u7528\u7387" value="68.4%" />
1187
1187
  </ResponsiveGrid>
1188
1188
 
1189
- // \u274C Double border \u2014 do NOT wrap CardStat in a Card:
1190
- // <Card><CardContent><CardStat label="x" value="1" /></CardContent></Card>`,
1191
- storyPath: "data-display/CardStat.stories.tsx",
1189
+ // \u274C Double border \u2014 do NOT wrap StatCard in a Card:
1190
+ // <Card><CardContent><StatCard label="x" value="1" /></CardContent></Card>`,
1191
+ storyPath: "data-display/StatCard.stories.tsx",
1192
1192
  rules: []
1193
1193
  },
1194
1194
  {
1195
- name: "StatusBadge",
1195
+ name: "Badge",
1196
1196
  group: "data-display",
1197
- tagline: "Lifecycle chip that auto-maps English keys (active/draft/pending/failed/\u2026) to tone + icon. For localized labels or tiers, pass tone explicitly; pass icon={null} for tiers. Chips never wrap.",
1197
+ tagline: "Plain or lifecycle badge. Use `variant` for static chips, or `status` to auto-map lifecycle keys to semantic tone + icon. Labels never wrap.",
1198
1198
  props: [
1199
1199
  {
1200
- name: "status",
1201
- type: "string",
1202
- required: true,
1203
- description: "Lifecycle key or any domain string. Unknown strings fall back to neutral unless tone is set."
1200
+ name: "variant",
1201
+ type: '"default" | "secondary" | "outline" | "success" | "warning" | "destructive" | "info" | "neutral"',
1202
+ defaultValue: '"default"',
1203
+ description: "Visual variant. Overrides the auto-mapped status tone when status is provided."
1204
1204
  },
1205
1205
  {
1206
- name: "tone",
1207
- type: '"success" | "warning" | "destructive" | "info" | "neutral"',
1208
- description: "Override the resolved tone (escape hatch for localized / tier values)."
1206
+ name: "status",
1207
+ type: "string",
1208
+ description: "Lifecycle key. Known keys auto-map to variant + icon + i18n label; unknown keys fall back to neutral."
1209
1209
  },
1210
1210
  {
1211
1211
  name: "icon",
1212
- type: "LucideIcon | null",
1213
- description: "Override the icon; null hides it \u2014 preferred for tier / category badges."
1214
- },
1215
- {
1216
- name: "label",
1217
- type: "ReactNode",
1218
- description: "Override display text (default: i18n of key, or raw status)."
1219
- }
1220
- ],
1221
- usage: [
1222
- "DO pass one of the known English lifecycle keys (active, draft, pending, completed, failed, cancelled, deleted, bounced, scheduled, sending, temporary, permanent, done, delivered, succeeded, internal, public, private, ASSIGNMENT_STATUS_ACTIVE, ASSIGNMENT_STATUS_SUSPENDED, ASSIGNMENT_STATUS_TERMINATED) as `status` \u2014 these resolve to the correct tone + icon + i18n label automatically without any extra props.",
1223
- "DO pass `tone` explicitly when `status` is a localized string or categorical tier label (e.g. a Japanese tier name like '\u30D7\u30EC\u30DF\u30A2\u30E0') \u2014 unknown keys fall back silently to neutral grey, which is visually wrong for a success/warning tier.",
1224
- "DO pass `icon={null}` for categorical / tier badges (membership tiers, plan names, visibility levels) where a lifecycle glyph (checkmark, clock, X) would be semantically misleading. Never omit this override for non-lifecycle uses.",
1225
- "DON'T extend STATUS_MAP inline at call sites \u2014 to add a new global status, add the key to STATUS_MAP in status-badge.tsx AND add a `status.<key>` i18n key. Never inline a one-off `<Badge>` just because a status key doesn't exist yet.",
1226
- "DON'T use `label` to carry translated text for known keys \u2014 the component already calls `t('status.<key>')` automatically. Only use `label` when you must show a domain-specific override that diverges from the canonical i18n string.",
1227
- "A11y: the icon is rendered with `aria-hidden='true'`; the visible `<span>` child carries the accessible label. Do not add separate `aria-label` on the wrapper \u2014 it is redundant and will cause double-announce."
1228
- ],
1229
- useCases: [
1230
- "Invoice / payment status column in a DataTable \u2014 show 'draft', 'pending', 'completed', 'failed' states with color-coded tone and icon, no extra configuration needed for standard keys.",
1231
- "Journal entry or approval workflow state \u2014 lifecycle values like 'pending', 'active', 'cancelled' auto-resolve; pair with KeyValueGrid for detail pages showing a single entity's current state.",
1232
- "Subscription or membership tier label in a user detail view \u2014 pass the tier name as `status`, set `tone='success'|'warning'` for the tier rank, and `icon={null}` to suppress the lifecycle glyph.",
1233
- "Delivery or send status in an email / notification log table \u2014 'bounced', 'sending', 'delivered', 'scheduled' all resolve out of the box with distinct icons and tones.",
1234
- "Assignment or role activation status \u2014 the ASSIGNMENT_STATUS_* keys map to active/suspended/terminated tones and icons without any manual configuration.",
1235
- "Visibility / publication status chip (public / private / internal) \u2014 all three keys are mapped in STATUS_MAP, so a CMS or document list gets consistent color coding for free."
1236
- ],
1237
- related: [
1238
- "Badge \u2014 use Badge (not StatusBadge) for static category tags, labels, or counts that have no lifecycle meaning (e.g. 'A/B test', 'New', category chips). Badge takes a `variant` prop; StatusBadge takes a `status` key. The rule of thumb: if the value can change over time as an entity moves through a workflow, use StatusBadge; if it's a fixed label, use Badge.",
1239
- "CodeBadge \u2014 use CodeBadge for monospaced identifier chips (invoice numbers, error codes, HTTP status codes). Never use StatusBadge to display a code or ID string.",
1240
- "DataTable \u2014 StatusBadge is the canonical cell renderer for status columns inside DataTable. Compose them together: render StatusBadge inside the column `cell` callback rather than hand-rolling colored text or raw Badge variants for lifecycle data.",
1241
- "MutationFeedback \u2014 use MutationFeedback (not StatusBadge) to communicate the outcome of an in-flight or just-completed mutation (save, delete, submit). StatusBadge is a display-only chip for persisted entity state, not transient operation feedback."
1242
- ],
1243
- example: `import { StatusBadge } from "@godxjp/ui/data-display";
1244
-
1245
- <>
1246
- <StatusBadge status="active" label="\u516C\u958B\u4E2D" /> {/* green check */}
1247
- <StatusBadge status="\u30D7\u30EC\u30DF\u30A2\u30E0" tone="success" icon={null} /> {/* tier pill, no icon */}
1248
- <StatusBadge status="\u30B4\u30FC\u30EB\u30C9" tone="warning" icon={null} />
1249
- </>`,
1250
- storyPath: "data-display/StatusBadge.stories.tsx",
1251
- rules: [35, 36]
1252
- },
1253
- {
1254
- name: "Badge",
1255
- group: "data-display",
1256
- tagline: "Plain label chip with semantic variants. Use for static category tags; use StatusBadge for lifecycle status.",
1257
- props: [
1258
- {
1259
- name: "variant",
1260
- type: '"default" | "secondary" | "destructive" | "outline" | "success" | "warning"',
1261
- defaultValue: '"default"',
1262
- description: "Visual variant."
1212
+ type: "React.ComponentType<{ className?: string }> | null",
1213
+ description: "Leading icon override. Pass null to suppress the auto status icon."
1263
1214
  },
1264
- { name: "children", type: "ReactNode", required: true, description: "Badge text/content." }
1215
+ { name: "children", type: "ReactNode", description: "Badge label. When omitted with status, Badge renders the translated lifecycle label or raw status." }
1265
1216
  ],
1266
1217
  usage: [
1267
1218
  "DO pick the correct variant semantically: `success` (approved/paid), `warning` (pending/overdue), `destructive` (rejected/error), `secondary` (neutral category), `outline` (subtle label), `default` (primary accent). Never force a colour just for aesthetics \u2014 agents and screen readers read the variant as intent.",
1268
- "DO NOT use Badge for entity lifecycle statuses (active, draft, pending, cancelled, etc.). Those keys are registered in StatusBadge's STATUS_MAP with icons and i18n \u2014 use StatusBadge instead, or you will diverge from the system-wide status colour contract.",
1269
- "DO NOT use Badge for reference-code chips (internal order IDs, seller codes, carrier tracking numbers). CodeBadge ships with a kind prop (`internal` | `seller` | `yamato`) and renders the correct label + icon pair \u2014 never substitute a raw Badge.",
1219
+ "DO use `status` for entity lifecycle statuses (active, draft, pending, cancelled, failed, scheduled, etc.) so the component resolves the correct tone, icon, and i18n label.",
1220
+ "DO pass `variant` explicitly for localized labels or categorical tiers, and pass `icon={null}` when a lifecycle glyph would be misleading.",
1270
1221
  "Badge renders as a `<div>` (HTMLAttributes<HTMLDivElement>). It carries no interactive semantics. If you need a clickable chip, wrap it in a `<button>` or use a Button with a matching variant \u2014 never add an `onClick` directly to Badge without an accessible role.",
1271
1222
  "Badge is a leaf \u2014 pass plain text or a short ReactNode as children. Do NOT nest another Badge, a Button, or interactive controls inside it; that breaks focus order and creates invalid HTML (div-in-inline-context).",
1272
- "Use semantic tokens for any className overrides (`text-muted-foreground`, `bg-destructive`) \u2014 never raw Tailwind palette classes like `bg-green-500`. The `success` and `warning` variants already use `toneSuccessClass`/`toneWarningClass` from control-styles; extending with raw colours will break dark-mode and audit checks."
1223
+ "Use semantic tokens for any className overrides (`text-muted-foreground`, `bg-destructive`) \u2014 never raw Tailwind palette classes like `bg-green-500`."
1273
1224
  ],
1274
1225
  useCases: [
1275
1226
  'Category or tier labels on table rows \u2014 e.g. plan tier (`<Badge variant="secondary">Pro</Badge>`), document type (`<Badge variant="outline">Invoice</Badge>`), or locale tag (`<Badge variant="secondary">EN</Badge>`).',
1276
- 'Approval or review state in an accounting list where the value is not a lifecycle key in StatusBadge\'s STATUS_MAP \u2014 e.g. a custom approval tier like `<Badge variant="success">\u627F\u8A8D\u6E08</Badge>` or `<Badge variant="warning">\u8981\u78BA\u8A8D</Badge>`.',
1227
+ 'Approval or review state in an accounting list where the value is not a lifecycle key in Badge\'s STATUS_MAP \u2014 e.g. a custom approval tier like `<Badge variant="success">\u627F\u8A8D\u6E08</Badge>` or `<Badge variant="warning">\u8981\u78BA\u8A8D</Badge>`.',
1277
1228
  "Inline count or highlight next to a heading or nav item \u2014 e.g. `<Badge variant=\"destructive\">3</Badge>` beside 'Overdue invoices' to draw attention to a non-zero count.",
1278
1229
  'Feature flags or experiment variant labels on admin records \u2014 e.g. `<Badge variant="outline">A/B</Badge>` alongside a campaign row to indicate it is in a split test.',
1279
- "Read-only metadata chips inside a KeyValueGrid.Item or Card header where a full StatusBadge icon would be visually heavy \u2014 e.g. currency code, payment method, or region tag."
1230
+ "Read-only metadata chips inside a Descriptions.Item or Card header where a lifecycle icon would be visually heavy \u2014 e.g. currency code, payment method, or region tag."
1280
1231
  ],
1281
1232
  related: [
1282
- "StatusBadge \u2014 use instead of Badge whenever the value is an entity lifecycle status (active, draft, pending, cancelled, failed, etc.). StatusBadge auto-resolves icon, colour, and i18n label from STATUS_MAP; Badge does none of that. Mixing them for the same semantic concept breaks visual consistency.",
1283
- "CodeBadge \u2014 use instead of Badge for structured reference codes (internal order IDs, seller codes, Yamato tracking). CodeBadge has a typed `kind` prop and renders a prefix label + icon; Badge has no such structure.",
1284
1233
  "Button \u2014 use instead of Badge when the chip must be interactive (clickable, toggleable). Badge carries no button role or keyboard handler; a naked `onClick` on Badge is inaccessible."
1285
1234
  ],
1286
1235
  example: `import { Badge } from "@godxjp/ui/data-display";
1287
1236
 
1288
1237
  <Badge variant="secondary">A/B</Badge>
1289
- <Badge variant="success">\u627F\u8A8D\u6E08</Badge>`,
1238
+ <Badge status="active">\u516C\u958B\u4E2D</Badge>
1239
+ <Badge status="\u30D7\u30EC\u30DF\u30A2\u30E0" variant="success" icon={null}>\u30D7\u30EC\u30DF\u30A2\u30E0</Badge>`,
1290
1240
  storyPath: "data-display/Badge.stories.tsx",
1291
1241
  rules: [35]
1292
1242
  },
1293
1243
  {
1294
- name: "KeyValueGrid",
1244
+ name: "Descriptions",
1295
1245
  group: "data-display",
1296
- tagline: "Responsive definition grid for detail-page metadata. COMPOUND \u2014 value goes in KeyValueGrid.Item children.",
1246
+ tagline: "Responsive definition grid for detail-page metadata. COMPOUND \u2014 value goes in Descriptions.Item children.",
1297
1247
  props: [
1298
1248
  {
1299
1249
  name: "columns",
@@ -1305,39 +1255,39 @@ import { ResponsiveGrid } from "@godxjp/ui/layout";
1305
1255
  name: "children",
1306
1256
  type: "ReactNode",
1307
1257
  required: true,
1308
- description: "KeyValueGrid.Item elements."
1258
+ description: "Descriptions.Item elements."
1309
1259
  }
1310
1260
  ],
1311
1261
  usage: [
1312
- 'DO use KeyValueGrid.Item as the ONLY direct child \u2014 never raw <div>, <dt>/<dd>, or plain text nodes. Every label/value pair must be wrapped in <KeyValueGrid.Item label="\u2026">value</KeyValueGrid.Item>.',
1262
+ 'DO use Descriptions.Item as the ONLY direct child \u2014 never raw <div>, <dt>/<dd>, or plain text nodes. Every label/value pair must be wrapped in <Descriptions.Item label="\u2026">value</Descriptions.Item>.',
1313
1263
  "DO pass span={2} or span={3} on an Item when its value is long (e.g. a full address, a memo field, a JSON blob) \u2014 span={2} applies sm:col-span-2 and span={3} applies lg:col-span-3, keeping the grid aligned across breakpoints.",
1314
1264
  "DO pass mono on Item for machine-readable values: IDs, UUIDs, file paths, currency codes, JSON snippets. This sets font-mono + break-all so long strings wrap rather than overflow.",
1315
- "DO embed any ReactNode as the Item child \u2014 StatusBadge, Badge, formatDate output, a Tooltip-wrapped value, or a plain string all work. The value slot is not text-only.",
1316
- "DON'T use KeyValueGrid as a hand-rolled <dl>/<dt>/<dd> replacement for prose or running text \u2014 it is for structured metadata on detail/show pages only. For flowing key\u2192value prose, use a plain <dl>.",
1317
- "DON'T add className padding or margin to the root KeyValueGrid to simulate a Card \u2014 wrap it in CardContent instead. KeyValueGrid provides only grid layout (gap-x-6 gap-y-3); outer spacing is the Card/CardContent concern."
1265
+ "DO embed any ReactNode as the Item child \u2014 Badge, Badge, formatDate output, a Tooltip-wrapped value, or a plain string all work. The value slot is not text-only.",
1266
+ "DON'T use Descriptions as a hand-rolled <dl>/<dt>/<dd> replacement for prose or running text \u2014 it is for structured metadata on detail/show pages only. For flowing key\u2192value prose, use a plain <dl>.",
1267
+ "DON'T add className padding or margin to the root Descriptions to simulate a Card \u2014 wrap it in CardContent instead. Descriptions provides only grid layout (gap-x-6 gap-y-3); outer spacing is the Card/CardContent concern."
1318
1268
  ],
1319
1269
  useCases: [
1320
1270
  "Detail/show page header block \u2014 displaying entity metadata such as invoice number, status, due date, vendor name, and payment method in a 2- or 3-column grid before the line-item DataTable.",
1321
1271
  "Account or member profile panel \u2014 showing user ID (mono), plan, registered date, email, and a status Badge in one scannable block instead of a vertical stack of FormField-looking rows.",
1322
1272
  "Accounting journal entry detail \u2014 date, reference code (mono), debit account, credit account, amount, and memo (span={2}) grouped in a compact grid alongside a Timeline of audit events.",
1323
- "Read-only summary step in a multi-step form or wizard \u2014 displaying the values the user entered before final submission (Steps + KeyValueGrid), without any input controls.",
1324
- "Sidebar or Sheet detail pane \u2014 a narrow 1-column KeyValueGrid inside a Sheet presenting the selected row's metadata while the main DataTable stays visible.",
1325
- "API / webhook event inspector \u2014 showing event ID (mono, span={2}), event type, timestamp, HTTP status, and payload size in a grid, with a CodeBadge for the status code."
1273
+ "Read-only summary step in a multi-step form or wizard \u2014 displaying the values the user entered before final submission (Steps + Descriptions), without any input controls.",
1274
+ "Sidebar or Sheet detail pane \u2014 a narrow 1-column Descriptions inside a Sheet presenting the selected row's metadata while the main DataTable stays visible.",
1275
+ "API / webhook event inspector \u2014 showing event ID (mono, span={2}), event type, timestamp, HTTP status, and payload size in a grid, with a Badge for the status code."
1326
1276
  ],
1327
1277
  related: [
1328
- "Card / CardContent \u2014 KeyValueGrid provides the internal grid layout; Card/CardContent provides the outer container, padding, and border. Always wrap KeyValueGrid in CardContent (never add p-4 directly on KeyValueGrid). Use Card when you need the visual surface; use KeyValueGrid inside it for the label/value structure.",
1329
- "DataTable \u2014 use DataTable when you have multiple rows of the same entity type that need sorting, filtering, or pagination. Use KeyValueGrid when you have a single entity's fields laid out as labelled metadata (one row per field, not one row per record).",
1330
- "Table \u2014 use Table (the lower-level primitive) for tabular data with explicit column headers and multiple data rows. Use KeyValueGrid when the data is inherently label\u2192value (no column headers needed, each field is its own row/cell).",
1331
- "Stack / Inline \u2014 use Stack or Inline for arbitrary vertical/horizontal layout of heterogeneous UI elements. Use KeyValueGrid when every item follows the label-on-top / value-below pattern and you want responsive multi-column alignment for free."
1332
- ],
1333
- example: `import { KeyValueGrid } from "@godxjp/ui/data-display";
1334
-
1335
- <KeyValueGrid columns={2}>
1336
- <KeyValueGrid.Item label="\u4F1A\u54E1ID" mono>{member.id}</KeyValueGrid.Item>
1337
- <KeyValueGrid.Item label="\u30D7\u30E9\u30F3">{member.plan}</KeyValueGrid.Item>
1338
- <KeyValueGrid.Item label="\u30E1\u30E2" span={2}>{member.note}</KeyValueGrid.Item>
1339
- </KeyValueGrid>`,
1340
- storyPath: "data-display/KeyValueGrid.stories.tsx",
1278
+ "Card / CardContent \u2014 Descriptions provides the internal grid layout; Card/CardContent provides the outer container, padding, and border. Always wrap Descriptions in CardContent (never add p-4 directly on Descriptions). Use Card when you need the visual surface; use Descriptions inside it for the label/value structure.",
1279
+ "DataTable \u2014 use DataTable when you have multiple rows of the same entity type that need sorting, filtering, or pagination. Use Descriptions when you have a single entity's fields laid out as labelled metadata (one row per field, not one row per record).",
1280
+ "Table \u2014 use Table (the lower-level primitive) for tabular data with explicit column headers and multiple data rows. Use Descriptions when the data is inherently label\u2192value (no column headers needed, each field is its own row/cell).",
1281
+ "Stack / Inline \u2014 use Stack or Inline for arbitrary vertical/horizontal layout of heterogeneous UI elements. Use Descriptions when every item follows the label-on-top / value-below pattern and you want responsive multi-column alignment for free."
1282
+ ],
1283
+ example: `import { Descriptions } from "@godxjp/ui/data-display";
1284
+
1285
+ <Descriptions columns={2}>
1286
+ <Descriptions.Item label="\u4F1A\u54E1ID" mono>{member.id}</Descriptions.Item>
1287
+ <Descriptions.Item label="\u30D7\u30E9\u30F3">{member.plan}</Descriptions.Item>
1288
+ <Descriptions.Item label="\u30E1\u30E2" span={2}>{member.note}</Descriptions.Item>
1289
+ </Descriptions>`,
1290
+ storyPath: "data-display/Descriptions.stories.tsx",
1341
1291
  rules: []
1342
1292
  },
1343
1293
  {
@@ -1379,7 +1329,7 @@ import { ResponsiveGrid } from "@godxjp/ui/layout";
1379
1329
  rules: []
1380
1330
  },
1381
1331
  {
1382
- name: "ProgressMeter",
1332
+ name: "Progress",
1383
1333
  group: "data-display",
1384
1334
  tagline: "Horizontal progress bar 0\u2013100 with optional label and semantic tone.",
1385
1335
  props: [
@@ -1398,31 +1348,31 @@ import { ResponsiveGrid } from "@godxjp/ui/layout";
1398
1348
  }
1399
1349
  ],
1400
1350
  usage: [
1401
- 'DO import from `@godxjp/ui/data-display`, not from a generic UI path: `import { ProgressMeter } from "@godxjp/ui/data-display";`',
1351
+ 'DO import from `@godxjp/ui/data-display`, not from a generic UI path: `import { Progress } from "@godxjp/ui/data-display";`',
1402
1352
  "DO pass `value` as a 0\u2013100 number \u2014 the component clamps it internally via `Math.max(0, Math.min(100, value))`, so out-of-range values are safe but misleading; compute the real percentage before passing it.",
1403
- 'DO drive `tone` dynamically from business logic \u2014 e.g. `tone={pct >= 80 ? "warning" : "success"}` \u2014 to communicate threshold status semantically rather than with raw colour classes.',
1404
- "DON'T use a `disabled` Slider as a read-only progress bar \u2014 Slider is semantically an interactive control even when disabled, which pollutes the a11y tree and exposes the wrong ARIA role (`slider` vs `progressbar`). ProgressMeter renders the correct read-only indicator.",
1405
- "DON'T pass children or sub-components \u2014 ProgressMeter is a single self-contained element (track + bar + label). The `label` prop is the only text injection point; don't wrap it in a custom parent div to add a label alongside it.",
1406
- "DON'T use ProgressMeter for editable numeric input or range selection \u2014 it has no callbacks, no interactivity, and no form `name` prop. Use Slider (bounded range input) or Input (free-form number) for data-entry scenarios."
1353
+ 'DO drive `tone` dynamically from business logic \u2014 e.g. `variant={pct >= 80 ? "warning" : "success"}` \u2014 to communicate threshold status semantically rather than with raw colour classes.',
1354
+ "DON'T use a `disabled` Slider as a read-only progress bar \u2014 Slider is semantically an interactive control even when disabled, which pollutes the a11y tree and exposes the wrong ARIA role (`slider` vs `progressbar`). Progress renders the correct read-only indicator.",
1355
+ "DON'T pass children or sub-components \u2014 Progress is a single self-contained element (track + bar + label). The `label` prop is the only text injection point; don't wrap it in a custom parent div to add a label alongside it.",
1356
+ "DON'T use Progress for editable numeric input or range selection \u2014 it has no callbacks, no interactivity, and no form `name` prop. Use Slider (bounded range input) or Input (free-form number) for data-entry scenarios."
1407
1357
  ],
1408
1358
  useCases: [
1409
- 'Budget utilisation in an accounting dashboard \u2014 show how much of a monthly budget has been consumed, switching to `tone="warning"` when the figure crosses 80%.',
1359
+ 'Budget utilisation in an accounting dashboard \u2014 show how much of a monthly budget has been consumed, switching to `variant="warning"` when the figure crosses 80%.',
1410
1360
  'Invoice payment progress \u2014 display the proportion of an invoice total that has been settled (e.g. partial payments), with a label like `"\xA545,000 / \xA560,000 \u652F\u6255\u6E08"` computed before passing `value`.',
1411
1361
  "Storage or quota indicator in an admin panel \u2014 visualise disk usage, API quota, or seat licence consumption against a fixed limit.",
1412
1362
  "Sync / import job completion feedback \u2014 surface the completion percentage of a long-running background job (polling the server) without giving the user an interactive control.",
1413
- "CardStat companion \u2014 pair with a `CardStat` metric to add a visual fill below the KPI number, reinforcing how close a target is to being met.",
1414
- "Multi-step onboarding or setup checklist \u2014 render one ProgressMeter per section (e.g. 3/5 steps complete = 60%) to give users a quick scan of overall progress across areas."
1363
+ "StatCard companion \u2014 pair with a `StatCard` metric to add a visual fill below the KPI number, reinforcing how close a target is to being met.",
1364
+ "Multi-step onboarding or setup checklist \u2014 render one Progress per section (e.g. 3/5 steps complete = 60%) to give users a quick scan of overall progress across areas."
1415
1365
  ],
1416
1366
  related: [
1417
- "Slider \u2014 use Slider when the user must drag or set a bounded numeric value (volume, priority, price range); use ProgressMeter when the value is read-only and must not be interacted with.",
1418
- "Steps \u2014 use Steps for a discrete, named sequence of phases (onboarding wizard, checkout flow) where each step has a label and a clear current/done/pending state; use ProgressMeter for a continuous 0\u2013100 fill.",
1419
- 'StatusBadge / Badge \u2014 use StatusBadge or Badge to communicate a categorical status label (e.g. "Paid", "Overdue") without a fill metaphor; use ProgressMeter when the numeric proportion itself is the information.',
1420
- "CardStat \u2014 use CardStat to headline a single KPI metric with a title; compose ProgressMeter inside or alongside CardStat when a visual fill adds meaning to the number."
1367
+ "Slider \u2014 use Slider when the user must drag or set a bounded numeric value (volume, priority, price range); use Progress when the value is read-only and must not be interacted with.",
1368
+ "Steps \u2014 use Steps for a discrete, named sequence of phases (onboarding wizard, checkout flow) where each step has a label and a clear current/done/pending state; use Progress for a continuous 0\u2013100 fill.",
1369
+ 'Badge / Badge \u2014 use Badge or Badge to communicate a categorical status label (e.g. "Paid", "Overdue") without a fill metaphor; use Progress when the numeric proportion itself is the information.',
1370
+ "StatCard \u2014 use StatCard to headline a single KPI metric with a title; compose Progress inside or alongside StatCard when a visual fill adds meaning to the number."
1421
1371
  ],
1422
- example: `import { ProgressMeter } from "@godxjp/ui/data-display";
1372
+ example: `import { Progress } from "@godxjp/ui/data-display";
1423
1373
 
1424
- <ProgressMeter value={pct} label={pct + "% \u4F7F\u7528\u4E2D"} tone={pct >= 80 ? "warning" : "success"} />`,
1425
- storyPath: "data-display/ProgressMeter.stories.tsx",
1374
+ <Progress value={pct} label={pct + "% \u4F7F\u7528\u4E2D"} variant={pct >= 80 ? "warning" : "success"} />`,
1375
+ storyPath: "data-display/Progress.stories.tsx",
1426
1376
  rules: []
1427
1377
  },
1428
1378
  {
@@ -1440,7 +1390,7 @@ import { ResponsiveGrid } from "@godxjp/ui/layout";
1440
1390
  usage: [
1441
1391
  "DO pass an array of `TimelineItem` objects to `items` \u2014 this is the ONLY prop; there are no sub-components to compose. Each item is `{ title, location?, time?, note?, current? }`. All fields except `title` are optional.",
1442
1392
  "DO mark exactly one item with `current: true` to highlight the in-progress event. The component renders a `Plane` icon for the current item and a `CheckCircle2` icon for all past items \u2014 do NOT try to pass a custom icon; the icon is determined entirely by the `current` flag.",
1443
- "DO pass `ReactNode` to `title`, `location`, `time`, and `note` \u2014 you can embed formatted text, `<Badge>`, `<StatusBadge>`, or `<span>` inside those fields. Use `formatDate` to pre-format timestamps before passing them as `time`.",
1393
+ "DO pass `ReactNode` to `title`, `location`, `time`, and `note` \u2014 you can embed formatted text, `<Badge>`, `<Badge>`, or `<span>` inside those fields. Use `formatDate` to pre-format timestamps before passing them as `time`.",
1444
1394
  "DO NOT hand-roll a vertical event list with divs, icons, and connector lines \u2014 that is exactly what Timeline ships. Do not apply extra padding or wrapping outside the component; it manages its own rail and spacing internally.",
1445
1395
  "DO NOT use Timeline for user-facing wizard progress (steps the user must complete in order) \u2014 use `Steps` for that. Timeline is read-only historical/status display; it has no interactive state, no `onClick`, and no concept of 'go to step'.",
1446
1396
  "DO wrap Timeline in `<CardContent>` when placing it inside a `Card` \u2014 bare `Card` has no inner padding, so the rail will render flush against the card edge without `CardContent`."
@@ -1451,13 +1401,13 @@ import { ResponsiveGrid } from "@godxjp/ui/layout";
1451
1401
  "Support ticket / task history \u2014 displaying a chronological log of status transitions (Open \u2192 Assigned \u2192 In Review \u2192 Closed) with agent names in the `note` field and timestamps in `time`.",
1452
1402
  "MF sync log viewer \u2014 listing each sync run event (OAuth refresh, fetch, upsert) with timestamps and record counts so an operator can see what the last sync did.",
1453
1403
  "Approval workflow status panel \u2014 showing a multi-stage approval chain where completed stages have CheckCircle2 icons and the pending stage has the Plane (in-flight) icon.",
1454
- "Order / purchase-order lifecycle in an admin detail page \u2014 placed alongside a `KeyValueGrid` summary at the top of a `Card` to give a compact at-a-glance history."
1404
+ "Order / purchase-order lifecycle in an admin detail page \u2014 placed alongside a `Descriptions` summary at the top of a `Card` to give a compact at-a-glance history."
1455
1405
  ],
1456
1406
  related: [
1457
1407
  "Steps \u2014 use Steps (navigation group) when the user must actively progress through a wizard (interactive, shows step numbers/status, horizontal layout by default); use Timeline for read-only historical event sequences that have already happened.",
1458
- "KeyValueGrid \u2014 use KeyValueGrid to display a flat set of label/value metadata fields (e.g., invoice header); use Timeline when events are ordered chronologically and a connector rail communicates sequence and progress.",
1408
+ "Descriptions \u2014 use Descriptions to display a flat set of label/value metadata fields (e.g., invoice header); use Timeline when events are ordered chronologically and a connector rail communicates sequence and progress.",
1459
1409
  "DataTable \u2014 use DataTable for multi-row, multi-column tabular event logs where sorting, filtering, and pagination are needed; use Timeline when the sequence/rail visual is the primary communication and there are fewer than ~10 events.",
1460
- "StatusBadge \u2014 StatusBadge is a single-item inline indicator; Timeline sequences multiple statuses with connectors. Compose StatusBadge inside a Timeline `title` or `note` field for richer per-event context, but do not replace Timeline with a stack of StatusBadges."
1410
+ "Badge \u2014 Badge is a single-item inline indicator; Timeline sequences multiple statuses with connectors. Compose Badge inside a Timeline `title` or `note` field for richer per-event context, but do not replace Timeline with a stack of Badges."
1461
1411
  ],
1462
1412
  example: `import { Timeline } from "@godxjp/ui/data-display";
1463
1413
 
@@ -1500,7 +1450,7 @@ import { ResponsiveGrid } from "@godxjp/ui/layout";
1500
1450
  related: [
1501
1451
  "DataTable \u2014 choose DataTable for any data array that needs sorting, filtering, pagination, row selection, bulk actions, or density toggle. DataTable internally renders Table primitives, so switching up is non-breaking. Default to DataTable for all admin list pages.",
1502
1452
  "SkeletonTable \u2014 use as a loading placeholder before a Table or DataTable mounts. Drop it in the `skeleton` slot of DataState, or render it directly while data is fetching. Do not show a Table with empty rows as a loading state.",
1503
- "KeyValueGrid \u2014 choose KeyValueGrid when content is label\u2192value pairs (two columns, no repeated rows of the same type). Table is better when every row shares the same typed columns.",
1453
+ "Descriptions \u2014 choose Descriptions when content is label\u2192value pairs (two columns, no repeated rows of the same type). Table is better when every row shares the same typed columns.",
1504
1454
  "DataState \u2014 when your Table's data comes from `useQuery`, wrap it in DataState to handle loading/error/empty states declaratively instead of writing conditional logic around the Table yourself."
1505
1455
  ],
1506
1456
  example: `import { Table, TableHeader, TableBody, TableRow, TableHead, TableCell } from "@godxjp/ui/data-display";
@@ -1545,7 +1495,7 @@ import { ResponsiveGrid } from "@godxjp/ui/layout";
1545
1495
  "A detail page that loads a single invoice/journal entry via `useQuery` \u2014 DataState renders the skeleton row while fetching, an error alert with retry if the API fails, and the `<InvoiceCard>` only when data is confirmed non-null.",
1546
1496
  "A list page that shows a `DataTable` of members/partners \u2014 wrap the table in DataState so the skeleton matches the column count while loading and `EmptyState` appears when the filtered result set is empty.",
1547
1497
  "A sidebar panel that lazily loads related transactions for the selected entity \u2014 DataState keeps the panel in skeleton state during the background fetch without any manual `isPending` branching in the parent.",
1548
- "A dashboard stat card that calls a summary API \u2014 DataState handles the loading/error/empty lifecycle so `<CardStat>` is only rendered with fully resolved numbers, preventing NaN or undefined rendering.",
1498
+ "A dashboard stat card that calls a summary API \u2014 DataState handles the loading/error/empty lifecycle so `<StatCard>` is only rendered with fully resolved numbers, preventing NaN or undefined rendering.",
1549
1499
  "Any page using `useQuery` where the empty state and loading state are visually different \u2014 DataState enforces the correct visual for each phase without scattered `if` statements across the component tree."
1550
1500
  ],
1551
1501
  related: [
@@ -1705,7 +1655,7 @@ import { ResponsiveGrid } from "@godxjp/ui/layout";
1705
1655
  "DO pass a SINGLE React element as `children`. FormField calls `React.cloneElement` on it to inject `aria-describedby`, `aria-required`, and `aria-invalid` \u2014 if you pass a fragment or multiple nodes, cloneElement silently skips the injection and a11y attributes are lost.",
1706
1656
  "DO use the `error` prop (not a hand-rolled `<p>`) for validation messages \u2014 it renders with `role='alert'` and `text-destructive` styling and overrides `helper` automatically. Never render an error paragraph alongside FormField.",
1707
1657
  "DO use `labelAddon` (a ReactNode rendered inline after the label text) for supplementary controls such as a tooltip trigger or a 'copy' icon button \u2014 never insert such controls as siblings outside FormField, which breaks layout.",
1708
- "DON'T wrap `Switch` in FormField \u2014 use `SwitchField` instead, which already handles the label, hidden `<input name>` for HTML form submission, error, and helper internally.",
1658
+ "DON'T wrap `Switch` in FormField \u2014 use `ChoiceField` instead, which already handles the label, hidden `<input name>` for HTML form submission, error, and helper internally.",
1709
1659
  "DON'T use FormField for checkbox-beside-label or radio-beside-label patterns \u2014 use `ChoiceField` (single checkbox/radio with description) or `CheckboxGroup` / `RadioGroup` (multiple options), which have their own integrated labelling."
1710
1660
  ],
1711
1661
  useCases: [
@@ -1718,7 +1668,7 @@ import { ResponsiveGrid } from "@godxjp/ui/layout";
1718
1668
  ],
1719
1669
  related: [
1720
1670
  "Label \u2014 the bare Radix label component. Use directly only when you are building a fully custom layout that cannot accept FormField's stack wrapper, and you will manage aria-describedby/aria-invalid yourself. FormField is always preferred for standard form controls.",
1721
- "SwitchField \u2014 a self-contained field for boolean toggles: it already includes its own label, hidden `<input name>` for HTML form submission, helper, and error. Never wrap a bare `Switch` in FormField.",
1671
+ "ChoiceField \u2014 a self-contained field for boolean toggles: it already includes its own label, hidden `<input name>` for HTML form submission, helper, and error. Never wrap a bare `Switch` in FormField.",
1722
1672
  "ChoiceField \u2014 pairs a single checkbox or radio with a label and optional description in a horizontal layout (control beside text). Use ChoiceField instead of FormField when the control and its label sit side-by-side rather than stacked.",
1723
1673
  "CheckboxGroup / RadioGroup \u2014 for groups of options where FormField is not needed per-item; the group component handles its own legend/label and option layout."
1724
1674
  ],
@@ -2051,7 +2001,7 @@ export function PrioritySelect({ value, onValueChange }) {
2051
2001
  {
2052
2002
  name: "Switch",
2053
2003
  group: "data-entry",
2054
- tagline: "Radix toggle switch (bare). For a labelled row with a hidden form input use SwitchField.",
2004
+ tagline: "Radix toggle switch (bare). For a labelled row with a hidden form input use ChoiceField.",
2055
2005
  props: [
2056
2006
  { name: "checked", type: "boolean", description: "Controlled checked state." },
2057
2007
  {
@@ -2069,21 +2019,21 @@ export function PrioritySelect({ value, onValueChange }) {
2069
2019
  ],
2070
2020
  usage: [
2071
2021
  "DO use Switch (bare) only when you are building a custom inline toggle without a visible label \u2014 e.g., a DataTable row action column. Always pair it with a <Label htmlFor={id}> placed adjacent in the DOM; never leave it label-less for screen readers.",
2072
- "DO NOT pass a `name` prop to bare Switch expecting HTML form submission \u2014 Radix Switch renders no hidden input, so the value is silently dropped on submit. Use SwitchField (which mirrors a hidden `0`/`1` input) for any field that must submit inside an HTML <form>.",
2022
+ "DO NOT pass a `name` prop to bare Switch expecting HTML form submission \u2014 Radix Switch renders no hidden input, so the value is silently dropped on submit. Use ChoiceField (which mirrors a hidden `0`/`1` input) for any field that must submit inside an HTML <form>.",
2073
2023
  "DO use the `size` prop ('sm' | 'default') to control thumb size. 'sm' is appropriate in dense DataTable rows or filter bars; omit it (defaults to 'default') everywhere else.",
2074
- "DO wire controlled state: pass both `checked` (boolean) and `onCheckedChange` together. Passing only one causes a React controlled/uncontrolled warning. For uncontrolled use, pass neither \u2014 but bare Switch has no `defaultChecked` state management built in (SwitchField handles that internally).",
2075
- "DON'T hand-roll a <div> + <label> wrapper with bare Switch to get a labelled field \u2014 that is exactly what SwitchField provides, including aria-describedby, aria-invalid, error/helper text, and the hidden input. Reach for SwitchField instead.",
2024
+ "DO wire controlled state: pass both `checked` (boolean) and `onCheckedChange` together. Passing only one causes a React controlled/uncontrolled warning. For uncontrolled use, pass neither \u2014 but bare Switch has no `defaultChecked` state management built in (ChoiceField handles that internally).",
2025
+ "DON'T hand-roll a <div> + <label> wrapper with bare Switch to get a labelled field \u2014 that is exactly what ChoiceField provides, including aria-describedby, aria-invalid, error/helper text, and the hidden input. Reach for ChoiceField instead.",
2076
2026
  "DO link the switch to its label via matching `id` on Switch and `htmlFor` on Label. Without this pairing, clicking the label text does not toggle the switch and the a11y association is broken."
2077
2027
  ],
2078
2028
  useCases: [
2079
2029
  "Inline toggle in a DataTable action cell (e.g., 'Active' column) where the label is already provided by the column header and no form submission is involved.",
2080
- "Settings panel where a React state boolean is toggled immediately via an optimistic API call \u2014 no <form> submit, so SwitchField's hidden input is unnecessary.",
2081
- "Custom compound component where you compose Switch + Label yourself and need direct access to the Radix Root props (e.g., adding aria-controls or data-attributes not supported by SwitchField).",
2030
+ "Settings panel where a React state boolean is toggled immediately via an optimistic API call \u2014 no <form> submit, so ChoiceField's hidden input is unnecessary.",
2031
+ "Custom compound component where you compose Switch + Label yourself and need direct access to the Radix Root props (e.g., adding aria-controls or data-attributes not supported by ChoiceField).",
2082
2032
  "Filter toolbar toggle (e.g., 'Show archived') rendered inline next to other filter controls, using size='sm' for density parity with adjacent inputs.",
2083
2033
  "Preview/demo UI where the switch controls a local display state (dark-mode preview, feature flag preview) with no server persistence."
2084
2034
  ],
2085
2035
  related: [
2086
- "SwitchField \u2014 use this instead of bare Switch whenever the toggle needs a visible label, helper text, error message, or must submit its value inside an HTML <form>. SwitchField composes Label + Switch + hidden input automatically.",
2036
+ "ChoiceField \u2014 use this instead of bare Switch whenever the toggle needs a visible label, helper text, error message, or must submit its value inside an HTML <form>. ChoiceField composes Label + Switch + hidden input automatically.",
2087
2037
  "Checkbox \u2014 use Checkbox (or CheckboxGroup) when the user is selecting one or more items from a set, or when the binary choice semantically means 'agree/select' rather than 'enable/disable'. Switch implies an immediate, persistent state change; Checkbox implies a form choice.",
2088
2038
  "ChoiceField \u2014 use for a binary or small-set choice rendered as radio-style cards with rich descriptions, when the visual weight of a toggle is insufficient for the decision importance.",
2089
2039
  "RadioGroup \u2014 use when the user must choose exactly one option from 2\u20134 mutually exclusive values; Switch is only appropriate for a single on/off boolean."
@@ -2150,13 +2100,13 @@ export function PrioritySelect({ value, onValueChange }) {
2150
2100
  "DO: always pass `htmlFor` matching the `id` of the associated control \u2014 this is the entire purpose of the component. Without it, clicking the label text does NOT focus or toggle the control, breaking a11y and UX.",
2151
2101
  'DO: import from `@godxjp/ui/data-entry` (not shadcn or Radix directly). The godx-ui Label extends Radix\'s LabelPrimitive with `data-slot="label"`, `select-none`, and `group-data-[disabled]` opacity-50 \u2014 hand-rolling a `<label>` loses all of these.',
2152
2102
  "DON'T: use Label as a standalone visible heading or section title. It is a form-control association primitive. For page/section headings use semantic HTML (`<h2>`, etc.) or a typography class instead.",
2153
- "DON'T: wrap Label around a control that is already labelled internally. FormField, ChoiceField, SwitchField, and CheckboxGroup all render Label internally \u2014 adding a second Label creates a duplicate association and redundant screen-reader announcement.",
2154
- "DO: pair Label with Checkbox or Switch when NOT using the compound wrappers (ChoiceField / SwitchField). In that case generate the shared id with `React.useId()` and pass it to both `id` on the control and `htmlFor` on Label.",
2103
+ "DON'T: wrap Label around a control that is already labelled internally. FormField, ChoiceField, ChoiceField, and CheckboxGroup all render Label internally \u2014 adding a second Label creates a duplicate association and redundant screen-reader announcement.",
2104
+ "DO: pair Label with Checkbox or Switch when NOT using the compound wrappers (ChoiceField / ChoiceField). In that case generate the shared id with `React.useId()` and pass it to both `id` on the control and `htmlFor` on Label.",
2155
2105
  "PREFER FormField over a bare Label + control pair whenever you also need helper text, error messages, or `required` asterisk. FormField injects `aria-describedby` and `aria-invalid` automatically; a bare Label does not."
2156
2106
  ],
2157
2107
  useCases: [
2158
2108
  "Pairing with a standalone Checkbox when ChoiceField's two-line layout is unnecessary \u2014 e.g. a single 'Remember me' option in a login form.",
2159
- "Labelling a bare Switch (not SwitchField) in a settings row where the switch is controlled by parent state and no HTML form name attribute is needed.",
2109
+ "Labelling a bare Switch (not ChoiceField) in a settings row where the switch is controlled by parent state and no HTML form name attribute is needed.",
2160
2110
  "Adding a visible label to a custom or third-party control that accepts an `id` prop but isn't wrapped by FormField or ChoiceField.",
2161
2111
  "Labelling a Textarea in a free-text form field when FormField's helper/error slots aren't needed, keeping the markup minimal.",
2162
2112
  "Rendering an accessible label inside a table row where a FormField's block layout would break the inline/grid structure.",
@@ -2165,7 +2115,7 @@ export function PrioritySelect({ value, onValueChange }) {
2165
2115
  related: [
2166
2116
  "FormField \u2014 prefer this over a bare Label whenever the field needs helper text, an error message, or a required marker; FormField renders Label internally and wires aria-describedby/aria-invalid automatically.",
2167
2117
  "ChoiceField \u2014 use for a Checkbox or Radio.Item that needs a visible label and optional description line; it renders Label internally \u2014 do NOT add a second Label around it.",
2168
- "SwitchField \u2014 use instead of a bare Switch + Label pair when the control must submit a value via an HTML form name; SwitchField owns the Label + hidden input composition.",
2118
+ "ChoiceField \u2014 use instead of a bare Switch + Label pair when the control must submit a value via an HTML form name; ChoiceField owns the Label + hidden input composition.",
2169
2119
  "Checkbox \u2014 the most common bare-Label partner; pair with Label via shared useId() id/htmlFor when ChoiceField's layout is too heavy."
2170
2120
  ],
2171
2121
  example: `import { Label } from "@godxjp/ui/data-entry";
@@ -2209,7 +2159,7 @@ export function PrioritySelect({ value, onValueChange }) {
2209
2159
  ],
2210
2160
  related: [
2211
2161
  "CheckboxGroup \u2014 use instead of bare Checkbox when you have a list of 2+ options from an array; it handles id generation, ChoiceField wrapping, value array management, and the `name` prop for form submission. Checkbox is for a single boolean; CheckboxGroup is for multi-select.",
2212
- "Switch / SwitchField \u2014 use Switch when the action takes immediate effect (enable/disable a feature in settings) rather than selecting an option to be submitted later. Checkbox implies 'will be submitted as part of a form'; Switch implies 'applies now'. SwitchField adds a hidden input for HTML form compatibility.",
2162
+ "Switch / ChoiceField \u2014 use Switch when the action takes immediate effect (enable/disable a feature in settings) rather than selecting an option to be submitted later. Checkbox implies 'will be submitted as part of a form'; Switch implies 'applies now'. ChoiceField adds a hidden input for HTML form compatibility.",
2213
2163
  "RadioGroup \u2014 use when only one option in a group may be selected at a time (mutually exclusive). CheckboxGroup = multiple selections allowed; RadioGroup = single selection only.",
2214
2164
  "ChoiceField \u2014 the internal layout primitive (control slot + Label + description) that Checkbox.Group renders per item. Use it directly only when you need a one-off labelled checkbox or radio item outside of a group, and you want the consistent indent/description layout without the group's value-management overhead."
2215
2165
  ],
@@ -2264,7 +2214,7 @@ export function PrioritySelect({ value, onValueChange }) {
2264
2214
  related: [
2265
2215
  "CheckboxGroup \u2014 use when the user may select zero or more values simultaneously (multi-select); RadioGroup enforces exactly one selection. Both share the same options array shape and orientation prop.",
2266
2216
  "Select \u2014 use when there are 5 or more options or the option list is dynamic/searchable; RadioGroup is preferred for 2-4 fixed visible choices where scanning all options at once matters.",
2267
- "SwitchField \u2014 use when there are exactly two states that map to on/off (boolean); RadioGroup is the right pick when the two-or-more options are semantically distinct named values, not a toggle.",
2217
+ "ChoiceField \u2014 use when there are exactly two states that map to on/off (boolean); RadioGroup is the right pick when the two-or-more options are semantically distinct named values, not a toggle.",
2268
2218
  "ChoiceField \u2014 the low-level label+description wrapper that RadioGroup uses internally for each item. Use it directly only when manually composing Radio.Item children inside Radio.Group; never hand-roll a label alongside a bare Radio.Item without it."
2269
2219
  ],
2270
2220
  example: `import { RadioGroup } from "@godxjp/ui/data-entry";
@@ -2469,7 +2419,7 @@ function CreateDialog() {
2469
2419
  useCases: [
2470
2420
  "Filter/search panel: slide in from the right with filter FormFields (Select, DateRangePicker, CheckboxGroup) that affect a DataTable \u2014 preferred over a Dialog because filters do not require confirmation and benefit from seeing the table behind the overlay.",
2471
2421
  "Quick-edit drawer: open an entity's editable fields (e.g. invoice line items, account settings) without navigating away, with Save/Cancel in SheetFooter \u2014 use side='right' and keep the main page visible as context.",
2472
- "Detail peek panel: show read-only KeyValueGrid / Timeline of a selected record (e.g. a journal entry or invoice) from a DataTable row click, using side='right' with showCloseButton={true}.",
2422
+ "Detail peek panel: show read-only Descriptions / Timeline of a selected record (e.g. a journal entry or invoice) from a DataTable row click, using side='right' with showCloseButton={true}.",
2473
2423
  "Mobile-first navigation drawer: side='left' sheet acting as a slide-in nav menu on small viewports when the AppShell Sidebar is hidden \u2014 triggered by a hamburger Button.",
2474
2424
  "Step-by-step wizard side panel: multi-step form (Steps component inside SheetContent) for onboarding or import flows where full-page navigation would lose list context."
2475
2425
  ],
@@ -2577,7 +2527,7 @@ import { Button } from "@godxjp/ui/general";
2577
2527
  related: [
2578
2528
  "DataTable \u2014 sibling component that SkeletonTable precedes. Once DataTable mounts, use its `loading` prop (renders an in-table loading row) for subsequent refetches rather than swapping back to SkeletonTable. Pick SkeletonTable only for the pre-mount gap.",
2579
2529
  "DataState \u2014 query lifecycle widget from `@godxjp/ui/query`; accepts SkeletonTable as its `skeleton` prop and handles loading/empty/error transitions automatically. Prefer DataState + SkeletonTable over a hand-rolled ternary when the data comes from a useQuery hook.",
2580
- "SkeletonCard \u2014 sibling skeleton shaped like a CardStat tile; use inside a ResponsiveGrid to placeholder KPI dashboard cards, not tabular data.",
2530
+ "SkeletonCard \u2014 sibling skeleton shaped like a StatCard tile; use inside a ResponsiveGrid to placeholder KPI dashboard cards, not tabular data.",
2581
2531
  "DataTable \u2014 when data is already mounted but re-fetching (e.g. pagination, filter change), set `loading={true}` on DataTable directly instead of unmounting it and swapping in SkeletonTable; avoids layout shift and preserves scroll position."
2582
2532
  ],
2583
2533
  example: `import { SkeletonTable } from "@godxjp/ui/feedback";
@@ -2589,12 +2539,12 @@ import { Button } from "@godxjp/ui/general";
2589
2539
  {
2590
2540
  name: "SkeletonCard",
2591
2541
  group: "feedback",
2592
- tagline: "Loading placeholder shaped like a CardStat tile. Use inside a ResponsiveGrid while KPIs load.",
2542
+ tagline: "Loading placeholder shaped like a StatCard tile. Use inside a ResponsiveGrid while KPIs load.",
2593
2543
  props: [],
2594
2544
  usage: [
2595
- "DO render SkeletonCard directly inside a ResponsiveGrid (no Card wrapper needed) \u2014 it is a self-contained block with its own padding and shape, matching the three-layer anatomy of a CardStat tile (label line \u2192 value line \u2192 hint line).",
2596
- "DO render one SkeletonCard per expected CardStat tile so the grid dimensions stay stable during load. Four KPI tiles loading \u2192 four SkeletonCard siblings in the same ResponsiveGrid.",
2597
- "DON'T wrap SkeletonCard in <Card> or <CardContent> \u2014 it already owns its box layout. Double-wrapping adds unwanted padding and borders, identical to the CardStat double-border mistake.",
2545
+ "DO render SkeletonCard directly inside a ResponsiveGrid (no Card wrapper needed) \u2014 it is a self-contained block with its own padding and shape, matching the three-layer anatomy of a StatCard tile (label line \u2192 value line \u2192 hint line).",
2546
+ "DO render one SkeletonCard per expected StatCard tile so the grid dimensions stay stable during load. Four KPI tiles loading \u2192 four SkeletonCard siblings in the same ResponsiveGrid.",
2547
+ "DON'T wrap SkeletonCard in <Card> or <CardContent> \u2014 it already owns its box layout. Double-wrapping adds unwanted padding and borders, identical to the StatCard double-border mistake.",
2598
2548
  "DON'T use SkeletonCard for non-stat shapes: row lists \u2192 SkeletonTable or SkeletonRows; a single record detail page \u2192 SkeletonDetail. SkeletonCard is only correct when the loaded state is a stat/KPI tile.",
2599
2549
  `DON'T add aria-busy or aria-live yourself \u2014 the component sets aria-busy="true" on its root automatically. Adding them again duplicates announcements for screen readers.`,
2600
2550
  "DON'T pass any props \u2014 SkeletonCard takes none. If you need a different height or layout, check whether SkeletonDetail or a custom SkeletonBlock composition is the right tool instead."
@@ -2602,12 +2552,12 @@ import { Button } from "@godxjp/ui/general";
2602
2552
  useCases: [
2603
2553
  "Dashboard KPI row loading: while a deferred prop or async query fetches aggregate figures (total revenue, open invoices, overdue count, cash balance), render four SkeletonCards in a ResponsiveGrid columns={4} so the page shell holds its layout without a spinner overlay.",
2604
2554
  "Accounting summary header: the top-of-page stat strip on an invoice list or ledger view needs a stable layout before period-filtered totals resolve \u2014 SkeletonCard keeps each column's width locked.",
2605
- "Per-entity KPI switcher: when the user switches the active legal entity and a new round of stats is re-fetched, swap the CardStat tiles back to SkeletonCard during the transition to prevent stale values from flashing.",
2606
- "Report page initialisation: a profit-and-loss or balance-sheet summary card row uses SkeletonCard as the placeholder while the report query runs, then replaces each tile with a CardStat once data arrives.",
2555
+ "Per-entity KPI switcher: when the user switches the active legal entity and a new round of stats is re-fetched, swap the StatCard tiles back to SkeletonCard during the transition to prevent stale values from flashing.",
2556
+ "Report page initialisation: a profit-and-loss or balance-sheet summary card row uses SkeletonCard as the placeholder while the report query runs, then replaces each tile with a StatCard once data arrives.",
2607
2557
  "Deferred prop shell (Inertia v3): pair SkeletonCard tiles with Inertia's Deferred component so the page shell renders instantly and the stat row hydrates when the deferred payload resolves."
2608
2558
  ],
2609
2559
  related: [
2610
- "CardStat \u2014 the loaded counterpart. Replace SkeletonCard with CardStat (inside the same ResponsiveGrid slot) once data is available. CardStat also draws its own bordered card, so wrapping rules are identical \u2014 never add an extra Card around either.",
2560
+ "StatCard \u2014 the loaded counterpart. Replace SkeletonCard with StatCard (inside the same ResponsiveGrid slot) once data is available. StatCard also draws its own bordered card, so wrapping rules are identical \u2014 never add an extra Card around either.",
2611
2561
  "SkeletonTable \u2014 use instead of SkeletonCard when the loading area will become a DataTable or row-list. SkeletonTable renders a header row plus N body rows; SkeletonCard renders a three-line KPI block.",
2612
2562
  "SkeletonDetail \u2014 use instead of SkeletonCard when the loading area will become a single-record detail view (title + metadata key-value rows). Wrong pick: using SkeletonCard on a detail page leaves a mismatched shape.",
2613
2563
  "DataState \u2014 use instead of SkeletonCard when the loading area is driven by a TanStack Query result; DataState handles the skeleton/empty/error lifecycle automatically and does not require manual SkeletonCard/SkeletonTable placement."
@@ -2667,8 +2617,13 @@ toast.error("\u4FDD\u5B58\u306B\u5931\u6557\u3057\u307E\u3057\u305F");`,
2667
2617
  {
2668
2618
  name: "Tabs",
2669
2619
  group: "navigation",
2670
- tagline: "Radix tab container. Compose Tabs/TabsList/TabsTrigger/TabsContent. Controlled (value/onValueChange) or uncontrolled (defaultValue).",
2620
+ tagline: "Radix tab container with optional Ant-style `items` API. Pass items for the common full TabsList/TabsContent set, or compose TabsList/TabsTrigger/TabsContent manually when you need per-panel control.",
2671
2621
  props: [
2622
+ {
2623
+ name: "items",
2624
+ type: "{ value: string; label: React.ReactNode; content: React.ReactNode; disabled?: boolean }[]",
2625
+ description: "Optional data-driven tab list. When provided, Tabs renders all triggers and content panels."
2626
+ },
2672
2627
  { name: "value", type: "string", description: "Controlled active tab key." },
2673
2628
  { name: "defaultValue", type: "string", description: "Uncontrolled initial tab key." },
2674
2629
  {
@@ -2678,36 +2633,34 @@ toast.error("\u4FDD\u5B58\u306B\u5931\u6557\u3057\u307E\u3057\u305F");`,
2678
2633
  }
2679
2634
  ],
2680
2635
  usage: [
2681
- 'DO: always compose the full four-part tree \u2014 `<Tabs>` root, `<TabsList>` trigger bar, one `<TabsTrigger value="\u2026">` per tab, one `<TabsContent value="\u2026">` per matching trigger \u2014 every `value` string must be unique and match exactly between trigger and content.',
2636
+ "DO pass `items` when all tab content is known up front \u2014 each item needs a unique `value`, trigger `label`, and panel `content`.",
2637
+ 'When not using `items`, compose the full four-part tree \u2014 `<Tabs>` root, `<TabsList>` trigger bar, one `<TabsTrigger value="\u2026">` per tab, one `<TabsContent value="\u2026">` per matching trigger.',
2682
2638
  "DO: use `defaultValue` (uncontrolled) for simple local state; use `value` + `onValueChange` together (controlled) when the active tab is driven by URL query params, router state, or parent state. NEVER set both simultaneously.",
2683
- "DO: set `variant` on `TabsList` \u2014 `default` (pill/box, the built-in Radix look) for contained widgets; `line` (underline indicator, transparent background) for page-level section navigation. The variant is a prop on `TabsList`, NOT on `<Tabs>` root.",
2639
+ "DO use `variant` on Tabs when using `items`; when composing manually, set `variant` on `TabsList`.",
2684
2640
  'DO: pass `orientation="vertical"` to `<Tabs>` (not to `TabsList`) for a side-rail layout \u2014 the CSS group classes on root and triggers respond automatically, so no extra className gymnastics are needed.',
2685
- "DON'T: hand-roll the active-indicator underline or selected-state ring \u2014 `TabsTrigger` already applies `data-[state=active]` styles including the `after:` line element for the `line` variant. Adding your own underline breaks the design.",
2686
- "DON'T: reach for `Tabs` primitives when all content is known up-front and you don't need per-panel `forceMount` or custom `TabsContent` attributes \u2014 use `TabsItems` instead (items-array API, handles all composition internally and is less verbose)."
2641
+ "DON'T: hand-roll the active-indicator underline or selected-state ring \u2014 `TabsTrigger` already applies `data-[state=active]` styles including the `after:` line element for the `line` variant. Adding your own underline breaks the design."
2687
2642
  ],
2688
2643
  useCases: [
2689
- "Detail drawers or pages that need full per-panel control \u2014 e.g. an accounting journal-entry sheet where one panel has `forceMount` to keep a live chart mounted, requiring custom `TabsContent` props that `TabsItems` cannot pass.",
2644
+ "Detail drawers or pages that need full per-panel control \u2014 e.g. an accounting journal-entry sheet where one panel has `forceMount` to keep a live chart mounted, requiring custom `TabsContent` props that `Tabs` cannot pass.",
2690
2645
  "Controlled tabs driven by URL search params (e.g. `?tab=history`) where the parent reads/writes the active key and passes it to `value` / `onValueChange`.",
2691
2646
  'Vertical side-rail navigation inside a `SplitPane` or settings layout where `orientation="vertical"` on the root and `variant="line"` on `TabsList` combine to produce a sidebar-style tab strip.',
2692
2647
  "Lightweight widget tabs on a dashboard card \u2014 e.g. switching a `DataTable` between 'Pending' and 'Paid' invoice views \u2014 where an uncontrolled `defaultValue` is sufficient and no URL state is needed.",
2693
2648
  "Admin entity profile pages (company, partner, employee) where each `TabsContent` wraps an Inertia deferred prop panel, lazy-loading expensive data only when the tab is first activated."
2694
2649
  ],
2695
2650
  related: [
2696
- "TabsItems (@godxjp/ui/navigation) \u2014 higher-level wrapper that accepts a flat `items` array and composes TabsList/TabsTrigger/TabsContent internally. Prefer TabsItems for the common case where all content is known up-front and you do not need per-panel `forceMount` or custom TabsContent attributes; drop down to raw Tabs primitives only when you need that extra control.",
2697
2651
  "Steps (@godxjp/ui/navigation) \u2014 sequential wizard/progress indicator. Use Steps when order and completion state matter (multi-step forms, onboarding flows); use Tabs when panels are non-sequential and any tab can be visited freely.",
2698
2652
  "FilterBar / FilterGroup (@godxjp/ui/navigation) \u2014 horizontal filter chip row. Visually resembles `line`-variant tabs but is semantically different: FilterBar filters a dataset, it does not switch content panels. Never use Tabs as a filter control.",
2699
- "DropdownMenu (@godxjp/ui/navigation) \u2014 use for space-constrained contexts where showing all tab triggers at once is impractical (e.g. mobile overflow menu). If only 2-3 options exist and screen space is tight, a DropdownMenu is a lighter alternative to a full tab strip."
2700
- ],
2701
- example: `import { Tabs, TabsList, TabsTrigger, TabsContent } from "@godxjp/ui/navigation";
2702
-
2703
- <Tabs defaultValue="overview">
2704
- <TabsList>
2705
- <TabsTrigger value="overview">\u6982\u8981</TabsTrigger>
2706
- <TabsTrigger value="history">\u5C65\u6B74</TabsTrigger>
2707
- </TabsList>
2708
- <TabsContent value="overview">\u6982\u8981\u30B3\u30F3\u30C6\u30F3\u30C4</TabsContent>
2709
- <TabsContent value="history">\u5C65\u6B74\u30B3\u30F3\u30C6\u30F3\u30C4</TabsContent>
2710
- </Tabs>`,
2653
+ "DropdownMenu (@godxjp/ui/navigation) \u2014 use for space-constrained contexts where showing all tab triggers at once is impractical (e.g. mobile overflow menu). If only 2-3 options exist and screen space is tight, a DropdownSidebar is a lighter alternative to a full tab strip."
2654
+ ],
2655
+ example: `import { Tabs } from "@godxjp/ui/navigation";
2656
+
2657
+ <Tabs
2658
+ defaultValue="overview"
2659
+ items={[
2660
+ { value: "overview", label: "\u6982\u8981", content: "\u6982\u8981\u30B3\u30F3\u30C6\u30F3\u30C4" },
2661
+ { value: "history", label: "\u5C65\u6B74", content: "\u5C65\u6B74\u30B3\u30F3\u30C6\u30F3\u30C4" },
2662
+ ]}
2663
+ />`,
2711
2664
  storyPath: "navigation/Tabs.stories.tsx",
2712
2665
  rules: []
2713
2666
  },
@@ -2735,7 +2688,7 @@ toast.error("\u4FDD\u5B58\u306B\u5931\u6557\u3057\u307E\u3057\u305F");`,
2735
2688
  "DO manage filter state yourself (controlled). FilterBar has no internal state \u2014 it is a layout shell. Pass your state values into the filter controls as value/onValueChange, derive hasActiveFilters from whether any filter value differs from its empty/default state, and clear all state in onClear. Do NOT rely on form name/submission; FilterBar filters are instant-apply, not form-submitted.",
2736
2689
  "DO pass both hasActiveFilters AND onClear to show the clear-all button. The button only renders when BOTH props are truthy \u2014 passing onClear alone with hasActiveFilters defaulting to true shows the button even when no filters are active. Compute hasActiveFilters as a boolean expression over your state: hasActiveFilters={search !== '' || status !== 'all'}.",
2737
2690
  "DON'T hand-roll a clear button inside children. FilterBar renders its own Button variant='ghost' size='sm' with the localised 'clear filters' label (via useTranslation). Adding a second clear button inside children causes duplication and i18n inconsistency.",
2738
- "DON'T use FilterBar for tab-like navigation between content panels. It is semantically a filter strip \u2014 switching dataset predicates, not rendering different pages or sections. For panel switching use Tabs / TabsItems. The two look similar in line-variant style but FilterBar does not use role='tablist' and has no active-panel concept."
2691
+ "DON'T use FilterBar for tab-like navigation between content panels. It is semantically a filter strip \u2014 switching dataset predicates, not rendering different pages or sections. For panel switching use Tabs / Tabs. The two look similar in line-variant style but FilterBar does not use role='tablist' and has no active-panel concept."
2739
2692
  ],
2740
2693
  useCases: [
2741
2694
  "Invoice / journal-entry list page: SearchInput for free-text search, FilterGroup wrapping a Select for status (draft/posted/void), FilterGroup wrapping a DateRangePicker for fiscal-period, all above a DataTable card \u2014 hasActiveFilters derived from all three states.",
@@ -2748,7 +2701,7 @@ toast.error("\u4FDD\u5B58\u306B\u5931\u6557\u3057\u307E\u3057\u305F");`,
2748
2701
  related: [
2749
2702
  "FilterGroup (@godxjp/ui/navigation) \u2014 the required child wrapper for each labelled filter control inside FilterBar. Use FilterGroup for every Select/DatePicker/DateRangePicker slot; omit it only for SearchInput which does not need a visible label.",
2750
2703
  "SearchInput (@godxjp/ui/data-entry) \u2014 the free-text search control placed directly as a child of FilterBar (no FilterGroup wrapper needed). SearchInput handles debounce and the clear-X icon internally; do not wrap it in a FilterGroup or compose it manually from Input.",
2751
- "Tabs / TabsItems (@godxjp/ui/navigation) \u2014 for switching between content panels, not filtering a dataset. If the 'filter' is really changing which rendered section is visible (not which rows pass a predicate), use TabsItems instead of FilterBar.",
2704
+ "Tabs / Tabs (@godxjp/ui/navigation) \u2014 for switching between content panels, not filtering a dataset. If the 'filter' is really changing which rendered section is visible (not which rows pass a predicate), use Tabs instead of FilterBar.",
2752
2705
  "PageInset (@godxjp/ui/layout) \u2014 the layout wrapper that gives FilterBar its horizontal padding when the parent PageContainer is flush. Always wrap FilterBar in PageInset inside a flush container; in a non-flush container FilterBar's own padding-block tokens are sufficient."
2753
2706
  ],
2754
2707
  example: `import { FilterBar, FilterGroup } from "@godxjp/ui/navigation";
@@ -2885,14 +2838,14 @@ import { SearchInput, Select, SelectTrigger, SelectValue, SelectContent, SelectI
2885
2838
  "Topbar / avatar chip: a user-avatar Button triggers a DropdownMenu with Profile, Settings, DropdownMenuSeparator, Sign out \u2014 standard app-shell pattern for account actions.",
2886
2839
  "Bulk-action toolbar: after selecting rows, an 'Actions' Button opens a DropdownMenu with Approve, Reject, Export \u2014 prevents the toolbar from overflowing with individual buttons.",
2887
2840
  "Column visibility toggle in a report table: a 'Columns' Button opens a DropdownMenu whose items are DropdownMenuCheckboxItem entries, letting users show/hide columns without a Dialog.",
2888
- "Quick status change on an accounting entry: a StatusBadge-like trigger opens a DropdownMenu with DropdownMenuRadioGroup items (Draft, Posted, Voided) so the user can transition status without navigating away.",
2841
+ "Quick status change on an accounting entry: a Badge-like trigger opens a DropdownMenu with DropdownMenuRadioGroup items (Draft, Posted, Voided) so the user can transition status without navigating away.",
2889
2842
  "Context menu for a sidebar nav item: right-click or kebab on a project entry opens a DropdownMenu with Rename, Duplicate, Archive actions scoped to that item."
2890
2843
  ],
2891
2844
  related: [
2892
2845
  "Popover \u2014 use Popover when the floating panel needs arbitrary layout (filter forms, date pickers, rich content grids). Use DropdownMenu only for a list of discrete clickable actions or toggle items; DropdownMenu has no layout flexibility beyond label/separator/group.",
2893
2846
  "Command \u2014 use Command (cmdk) when the list is large, needs fuzzy-search filtering, or acts as a keyboard-driven command palette. DropdownMenu has no built-in search input; once the list exceeds ~8 items or needs filtering, switch to Command (often inside a Popover).",
2894
2847
  "Select \u2014 use Select when the purpose is choosing a value to submit in a form field (has a name prop for native form submission, renders a hidden select for a11y). Use DropdownMenu when the purpose is triggering actions, not picking a form value.",
2895
- "Menu \u2014 use Menu (or Sidebar) for persistent left-rail navigation. DropdownMenu is transient (opens on click, dismisses on select); Menu/Sidebar is always-visible structural navigation."
2848
+ "Sidebar \u2014 use Sidebar for persistent left-rail navigation. DropdownMenu is transient (opens on click, dismisses on select); Sidebar is always-visible structural navigation."
2896
2849
  ],
2897
2850
  example: `import { DropdownMenu, DropdownMenuTrigger, DropdownMenuContent, DropdownMenuItem, DropdownMenuSeparator } from "@godxjp/ui/navigation";
2898
2851
  import { Button } from "@godxjp/ui/general";
@@ -2935,7 +2888,7 @@ import { Button } from "@godxjp/ui/general";
2935
2888
  "DO: Pass all steps via the `items` array (each `{ title, subTitle?, description?/content?, icon?, status?, disabled? }`) \u2014 Steps is a single-component API with no child sub-components to compose manually.",
2936
2889
  "DO: Control the active step with `current` (0-based index). For async operations, set the top-level `status` prop (`'process'|'error'|'finish'`) to override the current step's icon \u2014 e.g. `status='error'` turns the active step red without touching `items`.",
2937
2890
  "DO: Use per-item `status` to pin individual steps independently of `current` (e.g. a skipped or already-errored step). Per-item `status` takes precedence over the derived status from `current`.",
2938
- "DON'T: Use Steps for navigation that needs URL routing or tab-switching \u2014 it has no built-in panel rendering. Pair it with your own conditional panel or a `Tabs`/`TabsItems` body; Steps only renders the indicator bar.",
2891
+ "DON'T: Use Steps for navigation that needs URL routing or tab-switching \u2014 it has no built-in panel rendering. Pair it with your own conditional panel or a `Tabs`/`Tabs` body; Steps only renders the indicator bar.",
2939
2892
  "DON'T: Wire `onChange` unless you actually support non-linear navigation. `onChange` makes every non-disabled step clickable (rendered as `<button>`); omitting it makes all steps non-interactive (`cursor-default`). Never set `disabled` on an item without also providing `onChange`, or the prop is meaningless.",
2940
2893
  "A11y: The `<ol>` is given `aria-label='Progress'` automatically. Individual steps render as `<button type='button'>` when `onChange` is present \u2014 ensure each `item.title` is descriptive enough to serve as the button label; avoid icon-only steps without a visible title."
2941
2894
  ],
@@ -2948,8 +2901,8 @@ import { Button } from "@godxjp/ui/general";
2948
2901
  ],
2949
2902
  related: [
2950
2903
  "Timeline \u2014 use Timeline (from @godxjp/ui) when you need a chronological event log with timestamps and variable content per entry; use Steps when the number of stages is fixed and forward-progress is the semantic.",
2951
- "Tabs / TabsItems \u2014 use TabsItems when each section has its own rendered panel and users switch freely between them; use Steps when stages are ordered and the indicator communicates completion state rather than just selection.",
2952
- "ProgressMeter \u2014 use ProgressMeter for a single continuous percentage (file upload, quota fill); use Steps for discrete named stages with individual pass/fail status.",
2904
+ "Tabs / Tabs \u2014 use Tabs when each section has its own rendered panel and users switch freely between them; use Steps when stages are ordered and the indicator communicates completion state rather than just selection.",
2905
+ "Progress \u2014 use Progress for a single continuous percentage (file upload, quota fill); use Steps for discrete named stages with individual pass/fail status.",
2953
2906
  "Breadcrumb \u2014 use Breadcrumb for hierarchical location within a page tree; use Steps for sequential workflow progress where order and completion matter."
2954
2907
  ],
2955
2908
  example: `import { Steps } from "@godxjp/ui/navigation";
@@ -3009,7 +2962,7 @@ import { Button } from "@godxjp/ui/general";
3009
2962
  "LocalePicker \u2014 the language-selector control that reads/writes AppProvider locale context automatically when used as a zero-prop child. Prefer LocalePicker over calling setLocale from useAppContext() directly in UI.",
3010
2963
  "TimezonePicker \u2014 the timezone-selector control; inherits `timezoneOptions` from AppProvider context when its own `options` prop is omitted. Both pickers require AppProvider to be in the tree unless controlled props are passed.",
3011
2964
  "formatDate \u2014 the MANDATORY date/time formatter that reads locale, timezone, timeFormat, and dateFormat from AppProvider context. Do NOT call date-fns or Intl.DateTimeFormat directly; formatDate is the single source of truth for display.",
3012
- "ShellApp \u2014 the top-level application shell that composes AppProvider, AppShell, Sidebar, and Topbar into a single ready-to-use layout. If your project uses ShellApp, AppProvider is already mounted inside it \u2014 do not add a second one."
2965
+ "AppShell \u2014 the top-level application shell that composes AppProvider, AppShell, Sidebar, and Topbar into a single ready-to-use layout. If your project uses AppShell, AppProvider is already mounted inside it \u2014 do not add a second one."
3013
2966
  ],
3014
2967
  example: `import { AppProvider } from "@godxjp/ui/app";
3015
2968
 
@@ -3047,7 +3000,7 @@ import { Button } from "@godxjp/ui/general";
3047
3000
  ],
3048
3001
  useCases: [
3049
3002
  "Rendering all date/time columns in a DataTable \u2014 invoice due dates (`kind: 'date'`), transaction timestamps (`kind: 'datetime'`), and elapsed time since last sync (`kind: 'relative'`) all go through `formatDate` so the locale/timezone/12h-24h setting from AppProvider is respected everywhere.",
3050
- "Displaying a single date/time field in a KeyValueGrid or Card detail panel, e.g. `formatDate(invoice.issuedAt)` for the issued-at row \u2014 no extra formatting logic needed, null is handled as `'\u2014'` automatically.",
3003
+ "Displaying a single date/time field in a Descriptions or Card detail panel, e.g. `formatDate(invoice.issuedAt)` for the issued-at row \u2014 no extra formatting logic needed, null is handled as `'\u2014'` automatically.",
3051
3004
  "Formatting a stored `HH:mm` string (24h canonical storage) for display according to the user's timeFormat preference \u2014 pass the raw `'14:30'` string and auto-detection routes it through `formatTimeOfDay`, outputting `'2:30 PM'` or `'14:30'` based on context.",
3052
3005
  "Rendering a 'last modified' timestamp with relative wording in an activity feed or audit log row \u2014 `formatDate(entry.updatedAt, { kind: 'relative' })` produces locale-correct relative strings like `'3\u65E5\u524D'` / `'3 days ago'`.",
3053
3006
  "Converting a `Date` selected from `DatePicker` (react-day-picker) back to a display string \u2014 pass `{ calendar: true }` to avoid the UTC midnight shift that the default instant path would apply."
@@ -3288,124 +3241,6 @@ export function InvoicePeriodFilter() {
3288
3241
  storyPath: "data-entry/DateRangePicker.stories.tsx",
3289
3242
  rules: [3, 6, 23, 31]
3290
3243
  },
3291
- {
3292
- name: "SwitchField",
3293
- group: "data-entry",
3294
- tagline: "Labelled boolean switch with a hidden 0/1 input for HTML form submission \u2014 use this instead of bare Switch whenever you need a label or a form name.",
3295
- props: [
3296
- {
3297
- name: "label",
3298
- type: "string",
3299
- required: true,
3300
- description: "Visible text label rendered to the right of the switch toggle."
3301
- },
3302
- {
3303
- name: "name",
3304
- type: "string",
3305
- required: true,
3306
- description: "HTML name attribute used by the hidden input \u2014 the value submitted is '1' (on) or '0' (off)."
3307
- },
3308
- {
3309
- name: "id",
3310
- type: "string",
3311
- description: "ID wired to the Switch and Label htmlFor. Auto-generated via useId() when omitted."
3312
- },
3313
- {
3314
- name: "checked",
3315
- type: "boolean",
3316
- description: "Controlled checked state. When provided the component is fully controlled; onCheckedChange must also be supplied."
3317
- },
3318
- {
3319
- name: "defaultChecked",
3320
- type: "boolean",
3321
- defaultValue: "false",
3322
- description: "Uncontrolled initial checked state. Ignored when checked is provided."
3323
- },
3324
- {
3325
- name: "onCheckedChange",
3326
- type: "(checked: boolean) => void",
3327
- description: "Fires with the new boolean value whenever the switch is toggled."
3328
- },
3329
- {
3330
- name: "required",
3331
- type: "boolean",
3332
- description: "Marks the field as required \u2014 renders a red asterisk next to the label and sets aria-required on the switch."
3333
- },
3334
- {
3335
- name: "helper",
3336
- type: "string",
3337
- description: "Secondary hint text shown below the label. Hidden when error is set."
3338
- },
3339
- {
3340
- name: "error",
3341
- type: "string",
3342
- description: "Validation error message. Renders below the row as a role=alert paragraph and sets aria-invalid on the switch."
3343
- },
3344
- {
3345
- name: "labelAddon",
3346
- type: "React.ReactNode",
3347
- description: "Optional node rendered inline after the label text (e.g. a Badge or tooltip trigger)."
3348
- },
3349
- { name: "disabled", type: "boolean", description: "Disables the switch toggle." },
3350
- {
3351
- name: "size",
3352
- type: '"sm" | "default"',
3353
- description: "Switch toggle size. Forwarded to the inner Switch primitive."
3354
- },
3355
- {
3356
- name: "className",
3357
- type: "string",
3358
- description: "Extra class names applied to the outer wrapper div."
3359
- }
3360
- ],
3361
- usage: [
3362
- "DO use SwitchField (not bare Switch) whenever a form name is required \u2014 it automatically mirrors a hidden input with value '1'/'0' so native HTML form.submit() and FormData work without extra wiring.",
3363
- "DO NOT use SwitchField without the name prop if you are not submitting via HTML form \u2014 pass name anyway; it is required by the type signature and is a safe no-op when FormData is not read.",
3364
- "Controlled mode: supply both checked and onCheckedChange. Uncontrolled mode: use defaultChecked only. Never mix \u2014 passing checked without onCheckedChange leaves the switch frozen.",
3365
- "The error prop replaces the helper text \u2014 both cannot appear simultaneously. Show validation errors via error, not as helper text that is always visible.",
3366
- "labelAddon is rendered inline after the label text at the same font size \u2014 use it for a small Badge ('Beta'), InfoTooltip, or similar inline decoration, NOT for a full action button.",
3367
- "NEVER hand-roll a Switch + Label + hidden-input pattern yourself; SwitchField already composes Switch, Label, hidden input, aria-describedby, aria-required, aria-invalid, and role=alert in a single component."
3368
- ],
3369
- useCases: [
3370
- "Settings toggles in an admin form (e.g. 'Enable auto-invoice', 'Allow concurrent sessions') where the page POSTs via Inertia useForm \u2014 the hidden 0/1 input is picked up by FormData automatically.",
3371
- "Permissions or feature-flag checkboxes on a user/role edit page where the UI needs a helper hint ('Allows access to billing module') alongside the toggle.",
3372
- "Inline required toggles in a multi-step wizard step where validation errors need to surface below the row via the error prop.",
3373
- "Accounting app: 'Mark as reconciled', 'Exclude from report', 'Apply tax-exempt status' \u2014 boolean flags that must be submitted with the record form.",
3374
- "Row-level status toggles in a card layout (e.g. 'Active' on a payment method card) using the sm size to keep the toggle compact.",
3375
- "Any boolean setting that needs a visible label + accessible association (htmlFor / aria) without writing the Switch + Label wiring manually."
3376
- ],
3377
- related: [
3378
- "Switch \u2014 bare Radix toggle with no label, no hidden input, and no error/helper. Use Switch when you are managing the label yourself (e.g. inside a custom flex row) and do not need HTML form submission. Use SwitchField for any form field that needs a label, helper, error, or native form submission.",
3379
- "Checkbox / CheckboxField \u2014 semantically for multi-select or tri-state (indeterminate). Use Checkbox for 'agree to terms' or multi-option selection. Use SwitchField for a single on/off boolean setting where the switch affordance fits better than a checkbox.",
3380
- "FormField \u2014 generic field wrapper used by Input, Select, etc. SwitchField already bundles its own label/error layout; do NOT also wrap it in FormField."
3381
- ],
3382
- example: `import { SwitchField } from "@godxjp/ui/data-entry";
3383
- import { useState } from "react";
3384
-
3385
- // Uncontrolled \u2014 defaultChecked, value submitted as hidden 0/1
3386
- <SwitchField
3387
- name="auto_invoice"
3388
- label="\u81EA\u52D5\u8ACB\u6C42\u3092\u6709\u52B9\u306B\u3059\u308B"
3389
- helper="\u6709\u52B9\u306B\u3059\u308B\u3068\u6708\u672B\u306B\u81EA\u52D5\u3067\u8ACB\u6C42\u66F8\u304C\u767A\u884C\u3055\u308C\u307E\u3059"
3390
- defaultChecked={false}
3391
- />
3392
-
3393
- // Controlled with validation error
3394
- function ReconcileToggle({ value, onChange }: { value: boolean; onChange: (v: boolean) => void }) {
3395
- return (
3396
- <SwitchField
3397
- name="reconciled"
3398
- label="\u7167\u5408\u6E08\u307F"
3399
- checked={value}
3400
- onCheckedChange={onChange}
3401
- required
3402
- error={!value ? "\u7167\u5408\u3092\u5B8C\u4E86\u3057\u3066\u304F\u3060\u3055\u3044" : undefined}
3403
- />
3404
- );
3405
- }`,
3406
- storyPath: "data-entry/SwitchField.stories.tsx",
3407
- rules: [3, 6, 13, 23]
3408
- },
3409
3244
  {
3410
3245
  name: "Cascader",
3411
3246
  group: "data-entry",
@@ -3516,11 +3351,11 @@ const REGIONS = [
3516
3351
  {
3517
3352
  value: "jp",
3518
3353
  label: "\u65E5\u672C",
3519
- children: [
3354
+ content: [
3520
3355
  {
3521
3356
  value: "tokyo",
3522
3357
  label: "\u6771\u4EAC\u90FD",
3523
- children: [
3358
+ content: [
3524
3359
  { value: "shinjuku", label: "\u65B0\u5BBF\u533A" },
3525
3360
  { value: "shibuya", label: "\u6E0B\u8C37\u533A" },
3526
3361
  ],
@@ -3530,11 +3365,11 @@ const REGIONS = [
3530
3365
  {
3531
3366
  value: "vn",
3532
3367
  label: "Vi\u1EC7t Nam",
3533
- children: [
3368
+ content: [
3534
3369
  {
3535
3370
  value: "hcm",
3536
3371
  label: "TP. H\u1ED3 Ch\xED Minh",
3537
- children: [
3372
+ content: [
3538
3373
  { value: "q1", label: "Qu\u1EADn 1" },
3539
3374
  { value: "q3", label: "Qu\u1EADn 3" },
3540
3375
  ],
@@ -3576,7 +3411,7 @@ function MultiRegionPicker() {
3576
3411
  // With custom field names (data uses 'name'/'id'/'nodes')
3577
3412
  <Cascader
3578
3413
  options={rawApiData}
3579
- fieldNames={{ label: "name", value: "id", children: "nodes" }}
3414
+ fieldNames={{ label: "name", value: "id", content: "nodes" }}
3580
3415
  defaultValue={["dept-1", "team-3"]}
3581
3416
  />
3582
3417
 
@@ -3682,7 +3517,7 @@ function MultiRegionPicker() {
3682
3517
  {
3683
3518
  name: "fieldNames",
3684
3519
  type: "{ label?: string; value?: string; children?: string }",
3685
- description: "Remap data object keys. Example: `{ label: 'name', value: 'id', children: 'items' }` so you don't have to transform your API response before passing it to `treeData`."
3520
+ description: "Remap data object keys. Example: `{ label: 'name', value: 'id', content: 'items' }` so you don't have to transform your API response before passing it to `treeData`."
3686
3521
  }
3687
3522
  ],
3688
3523
  usage: [
@@ -3714,13 +3549,13 @@ const accountTree = [
3714
3549
  {
3715
3550
  value: "assets",
3716
3551
  label: "Assets",
3717
- children: [
3718
- { value: "current-assets", label: "Current Assets", children: [
3552
+ content: [
3553
+ { value: "current-assets", label: "Current Assets", content: [
3719
3554
  { value: "cash", label: "Cash" },
3720
3555
  { value: "ar", label: "Accounts Receivable" },
3721
3556
  ],
3722
3557
  },
3723
- { value: "fixed-assets", label: "Fixed Assets", children: [
3558
+ { value: "fixed-assets", label: "Fixed Assets", content: [
3724
3559
  { value: "equipment", label: "Equipment" },
3725
3560
  ],
3726
3561
  },
@@ -3729,7 +3564,7 @@ const accountTree = [
3729
3564
  {
3730
3565
  value: "liabilities",
3731
3566
  label: "Liabilities",
3732
- children: [
3567
+ content: [
3733
3568
  { value: "ap", label: "Accounts Payable" },
3734
3569
  ],
3735
3570
  },
@@ -3863,11 +3698,11 @@ export function DepartmentFilter() {
3863
3698
  import { Transfer } from "@godxjp/ui/data-entry";
3864
3699
 
3865
3700
  const ALL_ACCOUNTS = [
3866
- { key: "1010", title: "Cash", description: "Asset" },
3867
- { key: "1020", title: "Accounts Receivable", description: "Asset" },
3868
- { key: "2010", title: "Accounts Payable", description: "Liability" },
3869
- { key: "3010", title: "Revenue", description: "Income" },
3870
- { key: "4010", title: "Cost of Goods Sold", description: "Expense", disabled: true },
3701
+ { value: "1010", title: "Cash", description: "Asset" },
3702
+ { value: "1020", title: "Accounts Receivable", description: "Asset" },
3703
+ { value: "2010", title: "Accounts Payable", description: "Liability" },
3704
+ { value: "3010", title: "Revenue", description: "Income" },
3705
+ { value: "4010", title: "Cost of Goods Sold", description: "Expense", disabled: true },
3871
3706
  ];
3872
3707
 
3873
3708
  export function AccountMapping() {
@@ -4323,7 +4158,7 @@ export function DisabledColor() {
4323
4158
  "Read-only visual indicator \u2014 pass `disabled` with a controlled `value` to show a progress-style bar that cannot be interacted with."
4324
4159
  ],
4325
4160
  related: [
4326
- "ProgressMeter \u2014 use ProgressMeter (not a disabled Slider) to show read-only progress; Slider with disabled is semantically a control, not a status indicator.",
4161
+ "Progress \u2014 use Progress (not a disabled Slider) to show read-only progress; Slider with disabled is semantically a control, not a status indicator.",
4327
4162
  "Input (type number) \u2014 use Input for free-form numeric entry; use Slider when the range is bounded and dragging is the expected UX.",
4328
4163
  "Switch \u2014 for boolean on/off; Slider is for continuous or stepped numeric ranges.",
4329
4164
  "RangeField (if present) \u2014 check the MCP first; if a composed range-input field exists, prefer it over wiring two Slider thumbs manually."
@@ -4719,7 +4554,7 @@ export function ReportRangeFilter() {
4719
4554
  "Invoice creation forms in an accounting app that require a supplier or customer country, with `allowEmpty={false}` to guarantee a code is always present.",
4720
4555
  "Optional 'country of origin' filter fields \u2014 use `allowEmpty={true}` so users can clear the selection back to 'no filter'.",
4721
4556
  "Multi-step onboarding flows that must pre-select a country inferred from the user's locale, then let them correct it.",
4722
- "Read-only display of a country name + flag in a KeyValueGrid or DataTable cell \u2014 use `CountryOptionLabel` directly (not CountrySelect) for non-interactive display."
4557
+ "Read-only display of a country name + flag in a Descriptions or DataTable cell \u2014 use `CountryOptionLabel` directly (not CountrySelect) for non-interactive display."
4723
4558
  ],
4724
4559
  related: [
4725
4560
  "Select \u2014 the generic primitive CountrySelect is built on; use Select directly when you need a controlled picker or options that are not country objects.",
@@ -5107,7 +4942,7 @@ function AccountQuickPick({ onSelect }: { onSelect: (id: string) => void }) {
5107
4942
  "RadioGroup \u2014 use when only ONE selection is allowed at a time (mutually exclusive). CheckboxGroup = multiple, RadioGroup = single.",
5108
4943
  "Checkbox (standalone) \u2014 use a bare `Checkbox` for a single boolean toggle (e.g. 'I agree to terms'). Use CheckboxGroup when you have 2+ related choices.",
5109
4944
  "Checkbox.Group \u2014 alias; the same component is also accessible as `Checkbox.Group` (the Checkbox export attaches CheckboxGroup as `.Group`). Both are equivalent \u2014 prefer the named `CheckboxGroup` import for clarity in larger files.",
5110
- "Switch / SwitchField \u2014 for a single binary on/off toggle with immediate effect (not a form submission value). Do not use CheckboxGroup to fake toggle rows.",
4945
+ "Switch / ChoiceField \u2014 for a single binary on/off toggle with immediate effect (not a form submission value). Do not use CheckboxGroup to fake toggle rows.",
5111
4946
  "Select (multi) \u2014 for a long list (10+ items) where space is limited; CheckboxGroup is better for \u226410 visible options that benefit from scanning all at once."
5112
4947
  ],
5113
4948
  example: `import { CheckboxGroup } from "@godxjp/ui/data-entry";
@@ -5218,7 +5053,7 @@ export function ControlledExample() {
5218
5053
  ],
5219
5054
  related: [
5220
5055
  "Checkbox.Group \u2014 use when users may select multiple options simultaneously; Radio.Group enforces single-selection only.",
5221
- "Switch / SwitchField \u2014 use for a single boolean on/off toggle (e.g. enable notifications); Radio.Group is for choosing one among three or more named options.",
5056
+ "Switch / ChoiceField \u2014 use for a single boolean on/off toggle (e.g. enable notifications); Radio.Group is for choosing one among three or more named options.",
5222
5057
  "Select \u2014 use when there are many options (5+) and vertical screen space is limited; Radio.Group is preferable for 2-4 short options where all choices should be visible at a glance."
5223
5058
  ],
5224
5059
  example: `{\`import { Radio } from "@godxjp/ui/data-entry";
@@ -5870,7 +5705,7 @@ export function FilterSection() {
5870
5705
  ],
5871
5706
  related: [
5872
5707
  "Timeline \u2014 use Timeline for chronological event sequences with timestamps; use TreeList for hierarchical parent-child structures.",
5873
- "KeyValueGrid \u2014 use KeyValueGrid for label/value pairs; use TreeList when items have a parent-child depth relationship.",
5708
+ "Descriptions \u2014 use Descriptions for label/value pairs; use TreeList when items have a parent-child depth relationship.",
5874
5709
  "DataTable \u2014 use DataTable for tabular data with columns, sorting, and selection; use TreeList for a single-column hierarchical list without those features.",
5875
5710
  "EmptyState \u2014 pair with EmptyState when the items array may be empty; TreeList renders nothing (no empty row) when given an empty array."
5876
5711
  ],
@@ -5890,115 +5725,6 @@ export function ChartOfAccounts() {
5890
5725
  storyPath: "data-display/TreeList.stories.tsx",
5891
5726
  rules: [3, 6, 23, 31]
5892
5727
  },
5893
- {
5894
- name: "ScanPanel",
5895
- group: "data-display",
5896
- tagline: "Square dashed placeholder panel with a scan-line icon \u2014 for empty/waiting states where content is scanned or uploaded; never hand-roll this pattern with a raw div.",
5897
- props: [
5898
- {
5899
- name: "title",
5900
- type: "string",
5901
- required: true,
5902
- description: "Primary label rendered below the scan-line icon in semibold large text."
5903
- },
5904
- {
5905
- name: "description",
5906
- type: "string",
5907
- description: "Optional secondary line rendered below the title in muted small text. Omit if no additional context is needed."
5908
- }
5909
- ],
5910
- usage: [
5911
- "DO use ScanPanel for any empty/awaiting-input area that represents a 'scan something here' or 'drop/upload file' prompt \u2014 it provides the icon, border, background tint, and typography in one call.",
5912
- "DON'T hand-roll a dashed-border div with a lucide icon and centered text \u2014 ScanPanel is exactly that pattern and keeps visual consistency across the app.",
5913
- "The panel renders 1:1 aspect-ratio (square) via CSS; constrain its width from the parent container (e.g. max-w-xs) rather than trying to override aspect-ratio.",
5914
- "DO pass a concise `title` (required) that names the action or state (e.g. 'Scan invoice', 'No file selected'). Use `description` only for a secondary instruction or status line.",
5915
- "DON'T put interactive controls inside ScanPanel \u2014 it is a pure display/empty-state element. Pair it with a nearby Button or file-input for the actual action.",
5916
- "ScanPanel is NOT a general EmptyState \u2014 use godx-ui EmptyState for table/list zero-row states. ScanPanel is specifically for scan/upload affordance panels."
5917
- ],
5918
- useCases: [
5919
- "An invoice-scanning workflow step where the user must point a camera or upload an image \u2014 show ScanPanel while no file is selected.",
5920
- "A QR-code / barcode reader pane rendered before the camera stream is active, indicating the scan target area.",
5921
- "A document upload dropzone placeholder in an accounting app (expense receipts, purchase orders) before any file is chosen.",
5922
- "An OCR processing panel shown while the system awaits a document scan input from a connected scanner device.",
5923
- "A 'no attachment yet' state in an accounting record detail view that invites the user to attach a scanned document."
5924
- ],
5925
- related: [
5926
- "EmptyState \u2014 use for zero-row table/list states or generic no-data screens; ScanPanel is specifically scan/upload affordance with its own icon and square layout.",
5927
- "DataState / InfiniteQueryState \u2014 use for TanStack Query lifecycle (loading/empty/error) in list views; not for scan prompts.",
5928
- "SkeletonTable \u2014 use while data is loading into a table; not for scan/upload placeholder states."
5929
- ],
5930
- example: `import { ScanPanel } from "@godxjp/ui/data-display";
5931
-
5932
- export function InvoiceScanStep() {
5933
- return (
5934
- <div className="max-w-xs mx-auto">
5935
- <ScanPanel
5936
- title="Scan invoice"
5937
- description="Point your camera at the invoice barcode or upload a file."
5938
- />
5939
- </div>
5940
- );
5941
- }`,
5942
- storyPath: "data-display/ScanPanel.stories.tsx",
5943
- rules: [31]
5944
- },
5945
- {
5946
- name: "CodeBadge",
5947
- group: "data-display",
5948
- tagline: "Compact labelled icon-chip for displaying typed reference codes (internal, seller, or carrier); never use Badge or a raw span for these domain codes.",
5949
- props: [
5950
- {
5951
- name: "kind",
5952
- type: '"internal" | "seller" | "yamato"',
5953
- required: true,
5954
- description: 'Determines the icon and abbreviated label prefix rendered inside the chip. "internal" \u2192 Hash icon + "INT"; "seller" \u2192 ShoppingBag + "SLR"; "yamato" \u2192 Truck + "YMT". Falls back to "internal" if an unknown kind is supplied.'
5955
- },
5956
- {
5957
- name: "value",
5958
- type: "string",
5959
- required: true,
5960
- description: "The actual reference code string to display after the icon (e.g. an order number, shipment tracking ID, or internal SKU)."
5961
- }
5962
- ],
5963
- usage: [
5964
- "DO: always supply both `kind` and `value` \u2014 both are required; omitting either leaves the chip meaningless or broken.",
5965
- 'DO: use `kind="internal"` for internal system IDs/SKUs, `kind="seller"` for seller/merchant reference codes, and `kind="yamato"` for Yamato carrier/shipment tracking codes.',
5966
- "DON'T: use a raw `<Badge>` or a plain `<span>` for domain reference codes \u2014 `CodeBadge` encodes the correct icon, label prefix, and semantic `data-kind` attribute that CSS/tests rely on.",
5967
- "DON'T: pass display-formatted values (e.g. with leading/trailing whitespace or HTML entities) \u2014 `value` is rendered as plain text inside a `<span>`.",
5968
- "DON'T: attempt to extend `kind` inline \u2014 if a new code type is needed, add it to `CodeBadgeKind` in the source and export; never pass an arbitrary string and expect the chip to render correctly (it silently falls back to `internal`).",
5969
- 'A11Y: the icon is rendered with `aria-hidden="true"` so screen readers see only the label prefix and value text \u2014 keep `value` human-readable (e.g. the actual code, not an opaque hash).'
5970
- ],
5971
- useCases: [
5972
- "Displaying an order's internal system reference code alongside seller and carrier codes in an order detail panel or DataTable column.",
5973
- "Rendering a Yamato shipment tracking number in a logistics/fulfillment table so operators can visually distinguish it from internal IDs at a glance.",
5974
- "Showing a seller's own reference code (e.g. merchant PO number) in an invoice or accounting line-item row.",
5975
- "Pairing multiple CodeBadge instances in a KeyValueGrid row \u2014 e.g. INT code next to SLR code \u2014 to show all reference IDs for a single transaction.",
5976
- "In a DataTable cell renderer, wrapping a code field so the column's kind is immediately obvious without a separate column header."
5977
- ],
5978
- related: [
5979
- "Badge \u2014 generic variant-styled pill (default/secondary/destructive/outline/success/warning). Use Badge for arbitrary status labels or counts. Use CodeBadge specifically for typed domain reference codes (internal/seller/yamato) that need a fixed icon+prefix.",
5980
- 'StatusBadge \u2014 displays a status string with a semantic tone. Use StatusBadge for workflow/order status chips (e.g. "Paid", "Pending"). Use CodeBadge for reference/ID codes, not status labels.'
5981
- ],
5982
- example: `{\`import { CodeBadge } from "@godxjp/ui/data-display";
5983
-
5984
- // Internal system reference code
5985
- <CodeBadge kind="internal" value="ORD-00123" />
5986
-
5987
- // Seller reference code
5988
- <CodeBadge kind="seller" value="SLR-98765" />
5989
-
5990
- // Yamato carrier tracking code
5991
- <CodeBadge kind="yamato" value="YMT-4444-5555-6666" />
5992
-
5993
- // Multiple codes for one order
5994
- <div className="flex flex-wrap gap-2">
5995
- <CodeBadge kind="internal" value="ORD-00123" />
5996
- <CodeBadge kind="seller" value="SLR-98765" />
5997
- <CodeBadge kind="yamato" value="YMT-4444-5555-6666" />
5998
- </div>\`}`,
5999
- storyPath: "data-display/CodeBadge.stories.tsx",
6000
- rules: [3, 6, 13, 35]
6001
- },
6002
5728
  {
6003
5729
  name: "PageHeader",
6004
5730
  group: "navigation",
@@ -6229,7 +5955,7 @@ export function TimezoneField() {
6229
5955
  import { AppProvider } from "@godxjp/ui/app";
6230
5956
  import { APP_TIMEZONE_PRESET } from "@godxjp/ui/navigation";
6231
5957
 
6232
- export function Shell({ children }: { children: React.ReactNode }) {
5958
+ export function Shell({ children }: { content: React.ReactNode }) {
6233
5959
  return (
6234
5960
  <AppProvider
6235
5961
  defaultLocale="ja"
@@ -6418,366 +6144,6 @@ export function SettingsForm() {
6418
6144
  storyPath: "navigation/TimeFormatPicker.stories.tsx",
6419
6145
  rules: [3, 5, 6, 13]
6420
6146
  },
6421
- {
6422
- name: "TabsItems",
6423
- group: "navigation",
6424
- tagline: "Ant Design-style items-array tabs wrapper over Radix Tabs \u2014 pass a flat `items` array instead of composing TabsList/TabsTrigger/TabsContent by hand; first item is the default tab unless you specify otherwise.",
6425
- props: [
6426
- {
6427
- name: "items",
6428
- type: "TabItemProp[]",
6429
- required: true,
6430
- description: "Array of tab definitions. Each entry has: `key: string` (unique, used as the Radix value), `label: React.ReactNode` (trigger label), `children: React.ReactNode` (panel content), `disabled?: boolean`, `icon?: React.ReactNode` (rendered left of label inside the trigger)."
6431
- },
6432
- {
6433
- name: "value",
6434
- type: "string",
6435
- description: "Controlled active tab key. When provided the component is fully controlled \u2014 you must also provide `onValueChange`. Do NOT combine with `defaultValue` in controlled mode."
6436
- },
6437
- {
6438
- name: "defaultValue",
6439
- type: "string",
6440
- description: "Uncontrolled initial active tab key. Defaults to `items[0].key` when omitted. Ignored when `value` is provided."
6441
- },
6442
- {
6443
- name: "onValueChange",
6444
- type: "(key: string) => void",
6445
- description: "Callback fired when the user switches tabs. Receives the selected item's `key`. Required in controlled mode."
6446
- },
6447
- {
6448
- name: "variant",
6449
- type: '"default" | "line" | "card"',
6450
- defaultValue: '"default"',
6451
- description: "Visual style. `default` = pill/box tabs (Radix default). `line` = underline-only tabs (border-b indicator, transparent background). `card` = card-style tabs with a subtle shadow on the active trigger."
6452
- },
6453
- {
6454
- name: "className",
6455
- type: "string",
6456
- description: "Extra Tailwind classes applied to the outer Radix `Tabs` root element."
6457
- },
6458
- {
6459
- name: "listClassName",
6460
- type: "string",
6461
- description: "Extra classes applied to the `TabsList` wrapper (the trigger bar). Useful to control width, alignment, or border overrides."
6462
- },
6463
- {
6464
- name: "contentClassName",
6465
- type: "string",
6466
- description: "Extra classes applied to every `TabsContent` panel. Use for padding, min-height, or background overrides shared across all panels."
6467
- }
6468
- ],
6469
- usage: [
6470
- "DO: pass a flat `items` array with unique `key` strings \u2014 TabsItems renders the full TabsList + all TabsContent panels for you. NEVER hand-compose TabsList/TabsTrigger/TabsContent inside TabsItems; they are rendered internally.",
6471
- "DO: use `defaultValue` (uncontrolled) for simple local tab state. Use `value` + `onValueChange` together (controlled) when the active tab is driven by router state, query params, or parent state. DO NOT set both `value` and `defaultValue` simultaneously.",
6472
- "DO: choose `variant='line'` for page-level section navigation (full-width underline style) and `variant='default'` for contained widget tabs (pill/box). `variant='card'` suits dashboard card contexts.",
6473
- "DON'T: rely on tab position for the default tab \u2014 always supply `defaultValue` explicitly when the first item should not be active, or when items order may change.",
6474
- "DO: use `icon` on each `TabItemProp` to prepend an icon node (e.g. a Lucide icon) inside the trigger. Icons are rendered in an inline-flex span with `mr-1.5` spacing; pass sized icon components, not raw SVG strings.",
6475
- "DON'T: use TabsItems to replace a full Radix Tabs composition when you need per-panel extra attributes (e.g. `forceMount`, custom `TabsContent` event handlers) \u2014 drop down to the lower-level `Tabs`, `TabsList`, `TabsTrigger`, `TabsContent` primitives from the same import subpath."
6476
- ],
6477
- useCases: [
6478
- "Admin detail pages with multiple sections (Overview / Transactions / Attachments / Audit Log) where all content is known up-front and switching is purely client-side.",
6479
- "Accounting invoice or journal-entry detail drawers that split metadata, line items, and comments into named tabs without needing URL routing.",
6480
- "Dashboard widgets presenting the same data in different views (e.g. Chart / Table / Raw) using controlled `value` driven by a toolbar toggle.",
6481
- "Settings pages with a vertical or horizontal tab strip (line variant) separating General / Notifications / Billing / Security sections.",
6482
- "Entity profile pages (company, partner, employee) where each tab loads deferred or lazy content \u2014 combine with Inertia deferred props per panel.",
6483
- "Any place where Ant Design-style `<Tabs items={[...]} />` would have been used \u2014 TabsItems is the direct godx-ui equivalent."
6484
- ],
6485
- related: [
6486
- "Tabs / TabsList / TabsTrigger / TabsContent (@godxjp/ui/navigation) \u2014 lower-level Radix primitives. Use these when you need per-panel forceMount, custom data attributes on individual panels, or full control over trigger/content rendering. TabsItems wraps these internally.",
6487
- "Steps (@godxjp/ui/navigation) \u2014 sequential wizard/progress indicator. Use Steps for multi-step flows where order matters and completion state must be tracked; use TabsItems for non-sequential switchable views.",
6488
- "FilterBar / FilterGroup (@godxjp/ui/navigation) \u2014 horizontal filter chip row. Use FilterBar when the 'tabs' are really dataset filters (not content panels). Visually similar to line-variant tabs but semantically and behaviourally different."
6489
- ],
6490
- example: `{\`import { TabsItems } from "@godxjp/ui/navigation";
6491
- import { FileText, List, BarChart2 } from "lucide-react";
6492
-
6493
- // Uncontrolled \u2014 first item active by default
6494
- export function InvoiceDetailTabs() {
6495
- return (
6496
- <TabsItems
6497
- variant="line"
6498
- items={[
6499
- {
6500
- key: "overview",
6501
- label: "Overview",
6502
- icon: <FileText size={14} />,
6503
- children: <OverviewPanel />,
6504
- },
6505
- {
6506
- key: "line-items",
6507
- label: "Line Items",
6508
- icon: <List size={14} />,
6509
- children: <LineItemsTable />,
6510
- },
6511
- {
6512
- key: "analytics",
6513
- label: "Analytics",
6514
- icon: <BarChart2 size={14} />,
6515
- disabled: false,
6516
- children: <AnalyticsChart />,
6517
- },
6518
- ]}
6519
- />
6520
- );
6521
- }
6522
-
6523
- // Controlled \u2014 active tab driven by URL search param
6524
- export function ControlledTabs() {
6525
- const [tab, setTab] = React.useState("overview");
6526
-
6527
- return (
6528
- <TabsItems
6529
- variant="default"
6530
- value={tab}
6531
- onValueChange={setTab}
6532
- items={[
6533
- { key: "overview", label: "Overview", children: <OverviewPanel /> },
6534
- { key: "history", label: "History", children: <HistoryPanel /> },
6535
- ]}
6536
- />
6537
- );
6538
- }\`}`,
6539
- storyPath: "navigation/TabsItems.stories.tsx",
6540
- rules: [3, 23, 31, 37]
6541
- },
6542
- {
6543
- name: "Menu",
6544
- group: "layout",
6545
- tagline: "A simplified sidebar-backed navigation menu \u2014 pass sections with active flags and Menu resolves the activeId automatically; never set activeId manually.",
6546
- props: [
6547
- {
6548
- name: "items",
6549
- type: "MenuSection[]",
6550
- required: true,
6551
- description: "Array of navigation sections. Each section has an optional label and an array of MenuItem objects. The first item with active: true becomes the active route; if none is marked the very first item is treated as active."
6552
- }
6553
- ],
6554
- usage: [
6555
- "DO: set `active: true` on the single MenuItem that matches the current route \u2014 Menu derives `activeId` from this flag automatically. DON'T try to pass `activeId` (it does not exist on Menu).",
6556
- "DO: supply every item with a unique `id` string \u2014 it is required by the underlying SidebarItemProp. Duplicate ids cause incorrect active/highlight state.",
6557
- "DO: provide a Lucide (or compatible SVG) icon component via the `icon` field on each MenuItem. The icon is required by SidebarItemProp and will cause a render error if omitted.",
6558
- "DO: nest child pages under an item using `children: SidebarItemProp[]` \u2014 this renders a collapsible submenu that auto-opens when any child is active.",
6559
- "DON'T use Menu when you need to control collapse state, show a product switcher, render a custom brand slot, or attach an onSelect handler \u2014 use Sidebar directly for those scenarios.",
6560
- "MenuItem extends SidebarItem so you may also pass `badge` (ReactNode), `disabled` (boolean), and `children` (nested items) on each item."
6561
- ],
6562
- useCases: [
6563
- "App-shell left-rail navigation where the current route is already known from the router and active state can be derived from a simple boolean flag on each item.",
6564
- "Admin dashboards with a small, fixed set of top-level nav entries (Dashboard, Invoices, Settings) grouped into labelled sections (e.g. 'Accounting', 'Admin').",
6565
- "Multi-section sidebars where some sections have nested child pages (e.g. Reports > Income Statement, Balance Sheet) and you want collapsible submenu groups without wiring up Sidebar manually.",
6566
- "Situations where the product branding chip shown in the sidebar is purely cosmetic and does not need a real switcher \u2014 Menu hard-codes a placeholder product; use Sidebar when the product chip must be interactive or the brand slot needs a custom node.",
6567
- "Rapid prototyping: quickly render a working sidebar nav by passing a flat MenuSection array without needing to understand Sidebar's full API."
6568
- ],
6569
- related: [
6570
- "Sidebar \u2014 the underlying component Menu wraps. Use Sidebar directly when you need: a real product/brand switcher (onProductClick, brand slot, productMenu), an onSelect handler to intercept navigation, a collapsed/rail mode toggle, or a custom footer node. Menu is a thin convenience layer on top of Sidebar that trades configurability for simplicity.",
6571
- "AppShell \u2014 the page-level shell that accepts a sidebar node. Pass a Menu (or Sidebar) as its sidebar prop.",
6572
- "Topbar \u2014 the horizontal bar that pairs with AppShell; not a replacement for Menu/Sidebar nav."
6573
- ],
6574
- example: `
6575
- import { Menu } from "@godxjp/ui/layout";
6576
- import { LayoutDashboard, FileText, Settings, Users } from "lucide-react";
6577
-
6578
- const sections = [
6579
- {
6580
- label: "Accounting",
6581
- items: [
6582
- { id: "dashboard", label: "Dashboard", icon: LayoutDashboard, active: true },
6583
- { id: "invoices", label: "Invoices", icon: FileText },
6584
- ],
6585
- },
6586
- {
6587
- label: "Admin",
6588
- items: [
6589
- {
6590
- id: "users",
6591
- label: "Users",
6592
- icon: Users,
6593
- children: [
6594
- { id: "users-list", label: "All Users", icon: Users },
6595
- { id: "users-invite", label: "Invite", icon: Users },
6596
- ],
6597
- },
6598
- { id: "settings", label: "Settings", icon: Settings },
6599
- ],
6600
- },
6601
- ];
6602
-
6603
- export default function AppSidebar() {
6604
- return <Menu items={sections} />;
6605
- }
6606
- `,
6607
- storyPath: "layout/Menu.stories.tsx",
6608
- rules: [23]
6609
- },
6610
- {
6611
- name: "ShellApp",
6612
- group: "layout",
6613
- tagline: "Opinionated full-page shell (AppShell + a hardcoded Topbar) \u2014 use AppShell directly if you need to configure the topbar.",
6614
- props: [
6615
- {
6616
- name: "menu",
6617
- type: "ReactNode",
6618
- required: true,
6619
- description: "Sidebar content \u2014 pass a configured <Sidebar> component here. Rendered inside the left rail."
6620
- },
6621
- {
6622
- name: "breadcrumb",
6623
- type: "ReactNode",
6624
- description: "Optional breadcrumb strip rendered above the main content area, inside the app-breadcrumb div."
6625
- },
6626
- {
6627
- name: "children",
6628
- type: "ReactNode",
6629
- required: true,
6630
- description: "Main page content \u2014 rendered inside <main class='app-main'>. Typically a <PageContainer> or a list of page-level components."
6631
- }
6632
- ],
6633
- usage: [
6634
- "DO pass a fully configured <Sidebar> (with activeId, onSelect, sections) as the `menu` prop \u2014 ShellApp only renders what you give it; it has no built-in nav state.",
6635
- "DO use ShellApp when you want the default GodX Topbar (product chip 'GodX', no live search/notification handlers) and only need sidebar + breadcrumb configuration. For any custom topbar (entity switcher, real onSearchOpen, onNotificationsOpen, user slot) use <AppShell> directly with your own <Topbar> passed to its `topbar` prop.",
6636
- "DO NOT nest a second shell or AppShell inside ShellApp's children \u2014 ShellApp renders the root app-root div with sidebar, header, and main; nesting shells breaks layout.",
6637
- "DO NOT pass layout-wrapper divs as children expecting them to fill the full viewport \u2014 ShellApp's <main> already provides the scroll container; add padding via <PageContainer> or <PageInset> inside children.",
6638
- "DO pass a <Breadcrumb> (from @godxjp/ui/layout) node to the `breadcrumb` prop for breadcrumb navigation \u2014 do not hand-roll a breadcrumb strip inside children.",
6639
- "The built-in Topbar rendered by ShellApp has no-op handlers for search and notifications (both fire () => undefined). Wire those interactions by switching to AppShell + Topbar directly."
6640
- ],
6641
- useCases: [
6642
- "Rapid admin panel scaffolding where the default GodX branding topbar is acceptable and the only variable parts are the sidebar menu and page content.",
6643
- "Internal tools and dashboards that need a consistent three-zone shell (sidebar / topbar / main) without customising the product chip or notification system.",
6644
- "Prototyping or demo apps where you want a full AppShell layout with minimal boilerplate \u2014 three props (menu, children, optional breadcrumb) vs AppShell's seven.",
6645
- "Multi-page Inertia SPA where the sidebar, topbar chrome, and layout are shared across all pages via a persistent layout and each page only provides its own <PageContainer> as children."
6646
- ],
6647
- related: [
6648
- "AppShell \u2014 the underlying primitive ShellApp wraps. Use AppShell directly when you need to supply your own <Topbar> (custom product chip, entity switcher via productMenu, real search/notification handlers, user avatar slot, rightSlot) or any of topbarLeft/topbarRight/logo/footer/sidebarCollapsed.",
6649
- "Sidebar \u2014 pass as the `menu` prop; handles activeId, collapsing, section labels, nested groups with Collapsible, and collapsed popover flyouts.",
6650
- "Topbar \u2014 ShellApp renders a frozen Topbar internally; import Topbar from @godxjp/ui/layout and pass it to AppShell's topbar prop when you need live handlers.",
6651
- "PageContainer \u2014 the right component to use as the direct child of ShellApp to get a titled, padded page with actions/breadcrumb/footer.",
6652
- "PageInset \u2014 use inside a flush PageContainer for padded strips (filter bars, intros) that sit above a full-bleed DataTable."
6653
- ],
6654
- example: `{\`import { ShellApp, Sidebar, PageContainer, Breadcrumb } from "@godxjp/ui/layout";
6655
- import { LayoutDashboard, FileText, Settings } from "lucide-react";
6656
-
6657
- const NAV_SECTIONS = [
6658
- {
6659
- items: [
6660
- { id: "dashboard", label: "Dashboard", icon: LayoutDashboard },
6661
- { id: "invoices", label: "Invoices", icon: FileText },
6662
- { id: "settings", label: "Settings", icon: Settings },
6663
- ],
6664
- },
6665
- ];
6666
-
6667
- export default function AdminLayout({ children }: { children: React.ReactNode }) {
6668
- const [activeId, setActiveId] = React.useState("dashboard");
6669
-
6670
- return (
6671
- <ShellApp
6672
- menu={
6673
- <Sidebar
6674
- activeId={activeId}
6675
- onSelect={setActiveId}
6676
- sections={NAV_SECTIONS}
6677
- product={{ name: "CoreBooks", color: "hsl(var(--attention))" }}
6678
- />
6679
- }
6680
- breadcrumb={
6681
- <Breadcrumb items={[{ label: "Home", href: "/" }, { label: "Dashboard" }]} />
6682
- }
6683
- >
6684
- {children}
6685
- </ShellApp>
6686
- );
6687
- }\`}`,
6688
- storyPath: "layout/ShellApp.stories.tsx",
6689
- rules: [23, 24, 31, 35]
6690
- },
6691
- {
6692
- name: "MobileFrame",
6693
- group: "layout",
6694
- tagline: "Simulated smartphone chrome (header + scrollable body + bottom nav) for handheld/PWA screens \u2014 it is a full-page layout shell, not a modal or decoration.",
6695
- props: [
6696
- {
6697
- name: "title",
6698
- type: "string",
6699
- required: true,
6700
- description: "Primary heading shown in the mobile header bar (e.g. page name or branch name)."
6701
- },
6702
- {
6703
- name: "subtitle",
6704
- type: "string",
6705
- description: "Secondary line rendered beneath the title \u2014 typically a context descriptor such as location or mode."
6706
- },
6707
- {
6708
- name: "status",
6709
- type: "string",
6710
- description: "Domain status string rendered as a secondary Badge in the header (e.g. 'Online', 'Offline'). Omit to suppress the badge."
6711
- },
6712
- {
6713
- name: "children",
6714
- type: "ReactNode",
6715
- required: true,
6716
- description: "Main body content rendered inside the scrollable <main> region of the frame."
6717
- },
6718
- {
6719
- name: "navItems",
6720
- type: "MobileFrameNavItem[]",
6721
- defaultValue: "[]",
6722
- description: "Array of bottom-navigation tab descriptors. Each item has { label: string; icon: ComponentType<SVGProps<SVGSVGElement>>; active?: boolean }. The footer is only rendered when the array is non-empty. Mark exactly one item active at a time to indicate the current tab."
6723
- }
6724
- ],
6725
- usage: [
6726
- "DO: Use MobileFrame as a full-page layout shell for handheld/PWA screens \u2014 wrap the entire screen content in it, not a subsection of a desktop page.",
6727
- "DO: Pass lucide-react (or equivalent) icon components directly as `icon` values in navItems; the frame renders them as SVG children and adds aria-hidden automatically.",
6728
- "DO: Mark the active nav item with `active: true` on exactly one navItems entry; multiple or zero active states are valid but only the first receives the active highlight.",
6729
- "DO NOT: Nest MobileFrame inside PageContainer or AppShell \u2014 it is an independent shell; mixing shells breaks the layout semantics.",
6730
- "DO NOT: Hand-roll a phone frame with raw divs, CSS borders and overflow \u2014 MobileFrame already owns the stage/frame/header/main/nav CSS tokens (ui-mobile-stage, ui-mobile-frame, etc.).",
6731
- "DO NOT: Use MobileFrame for desktop admin pages \u2014 it renders a narrow, phone-sized canvas. Use PageContainer + AppShell for desktop screens."
6732
- ],
6733
- useCases: [
6734
- "Warehouse handheld scanners: a picker/packer app where staff scan barcodes on a phone \u2014 title='Tokyo scan', subtitle='Branch handheld', status='Online', navItems for Scan/Receive/Pack/Flight tabs.",
6735
- "Field-agent PWA screens where the same codebase serves a desktop admin shell (AppShell) and a mobile companion app (MobileFrame) rendered at the phone viewport.",
6736
- "Storybook/design-review previews of a mobile screen at realistic size \u2014 set Storybook viewport to 'mobile1' and wrap the page in MobileFrame to see the chrome.",
6737
- "Prototype or demo of a consumer-facing mobile app embedded inside an admin dashboard as a live preview widget.",
6738
- "Delivery dispatch or order-tracking app rendered on a handheld device where bottom-tab navigation is the primary navigation affordance."
6739
- ],
6740
- related: [
6741
- "AppShell \u2014 the desktop counterpart (sidebar + topbar shell); use AppShell for admin/desktop layouts, MobileFrame for handheld/phone layouts. Never nest one inside the other.",
6742
- "PageContainer \u2014 the page-level content wrapper used inside AppShell for desktop pages; do not substitute for MobileFrame on mobile screens.",
6743
- "ShellApp \u2014 another desktop shell variant; same rule as AppShell \u2014 mutually exclusive with MobileFrame."
6744
- ],
6745
- example: `import { Archive, CalendarClock, Package, ScanLine } from "lucide-react";
6746
- import { MobileFrame, type MobileFrameNavItem } from "@godxjp/ui/layout";
6747
- import { Card, CardContent, CardHeader, CardTitle } from "@godxjp/ui/data-display";
6748
- import { Stack } from "@godxjp/ui/layout";
6749
-
6750
- const navItems: MobileFrameNavItem[] = [
6751
- { label: "Scan", icon: ScanLine, active: true },
6752
- { label: "Receive", icon: Archive },
6753
- { label: "Pack", icon: Package },
6754
- { label: "Schedule", icon: CalendarClock },
6755
- ];
6756
-
6757
- export default function HandheldScanPage() {
6758
- return (
6759
- <MobileFrame
6760
- title="Tokyo scan"
6761
- subtitle="Branch handheld"
6762
- status="Online"
6763
- navItems={navItems}
6764
- >
6765
- <Stack gap="md">
6766
- <Card>
6767
- <CardHeader banded>
6768
- <CardTitle>Last scan</CardTitle>
6769
- </CardHeader>
6770
- <CardContent>
6771
- Ready to scan vendor or internal code.
6772
- </CardContent>
6773
- </Card>
6774
- </Stack>
6775
- </MobileFrame>
6776
- );
6777
- }`,
6778
- storyPath: "layout/MobileFrame.stories.tsx",
6779
- rules: [2, 3, 23, 24]
6780
- },
6781
6147
  {
6782
6148
  name: "Tooltip",
6783
6149
  group: "feedback",
@@ -7044,7 +6410,7 @@ import { fetchInvoice } from "@/api/invoices";
7044
6410
  "DON'T use this for mutation triggers \u2014 it is wired to query.refetch(), not a mutation. For mutation actions use a plain Button + useMutation.",
7045
6411
  "DO place it in a page header or toolbar beside a title \u2014 it renders as size='sm' variant='outline' by default, matching header action patterns.",
7046
6412
  "The RefreshCw icon is always rendered automatically with a spin animation driven by data-fetching={query.isFetching} \u2014 do NOT add your own icon or loading spinner.",
7047
- "For i18n, pass a translated string as label or children: `<QueryRefetchButton query={q} label={t('refresh')} />` \u2014 the default label is the English string 'Refresh'."
6413
+ "For i18n, pass a translated string as label or content: `<QueryRefetchButton query={q} label={t('refresh')} />` \u2014 the default label is the English string 'Refresh'."
7048
6414
  ],
7049
6415
  useCases: [
7050
6416
  "Toolbar 'Refresh' button on an invoice list page that re-fetches from the server without a full navigation.",
@@ -7077,6 +6443,118 @@ export function InvoiceListHeader() {
7077
6443
  }\`}`,
7078
6444
  storyPath: "data-display/QueryRefetchButton.stories.tsx",
7079
6445
  rules: [3, 5, 6, 13]
6446
+ },
6447
+ {
6448
+ name: "Avatar",
6449
+ group: "data-display",
6450
+ tagline: "Radix Avatar wrapper with image and fallback slots for users, teams, and entities.",
6451
+ props: [
6452
+ { name: "children", type: "ReactNode", description: "Compose AvatarImage and AvatarFallback." },
6453
+ { name: "className", type: "string", description: "Extra classes on the avatar root." }
6454
+ ],
6455
+ usage: [
6456
+ "DO compose Avatar > AvatarImage + AvatarFallback so broken or missing images still show a readable fallback.",
6457
+ "DON'T use Avatar for decorative thumbnails; use CardCover or an img when the image is content rather than identity."
6458
+ ],
6459
+ useCases: ["User profile chips", "Team member lists", "Account owner cells in a DataTable"],
6460
+ related: ["Badge \u2014 use beside Avatar for role/status metadata."],
6461
+ example: `import { Avatar, AvatarFallback, AvatarImage } from "@godxjp/ui/data-display";
6462
+
6463
+ <Avatar>
6464
+ <AvatarImage src="/user.png" alt="User" />
6465
+ <AvatarFallback>UI</AvatarFallback>
6466
+ </Avatar>`,
6467
+ storyPath: "data-display/Avatar.stories.tsx",
6468
+ rules: [3, 35]
6469
+ },
6470
+ {
6471
+ name: "Separator",
6472
+ group: "layout",
6473
+ tagline: "Radix Separator wrapper for tokenized horizontal or vertical dividers.",
6474
+ props: [
6475
+ { name: "orientation", type: '"horizontal" | "vertical"', defaultValue: '"horizontal"', description: "Divider direction." },
6476
+ { name: "decorative", type: "boolean", defaultValue: "true", description: "Whether the separator is decorative for assistive tech." }
6477
+ ],
6478
+ usage: ["DO use Separator for section dividers instead of raw border divs.", "DO set orientation='vertical' only when the parent gives it a stable height."],
6479
+ useCases: ["Separating toolbar groups", "Dividing stacked page sections", "Vertical split between metadata groups"],
6480
+ related: ["Stack \u2014 use for vertical spacing without a visible rule."],
6481
+ example: `import { Separator } from "@godxjp/ui/layout";
6482
+
6483
+ <Separator />`,
6484
+ storyPath: "layout/Separator.stories.tsx",
6485
+ rules: [2, 3]
6486
+ },
6487
+ {
6488
+ name: "Skeleton",
6489
+ group: "feedback",
6490
+ tagline: "Base pulsing skeleton block for custom loading placeholders.",
6491
+ props: [
6492
+ { name: "className", type: "string", description: "Size and layout classes for the block." }
6493
+ ],
6494
+ usage: ["DO use Skeleton for a custom block when SkeletonRows/Table/Card do not match the final layout.", "DON'T use a spinner overlay for skeletonable page content."],
6495
+ useCases: ["Single loading line", "Custom card media placeholder", "Inline metadata placeholder"],
6496
+ related: ["SkeletonRows", "SkeletonTable", "SkeletonCard"],
6497
+ example: `import { Skeleton } from "@godxjp/ui/feedback";
6498
+
6499
+ <Skeleton className="h-6 w-48" />`,
6500
+ storyPath: "feedback/Skeleton.stories.tsx",
6501
+ rules: [3, 31]
6502
+ },
6503
+ {
6504
+ name: "Toggle",
6505
+ group: "data-entry",
6506
+ tagline: "Radix Toggle wrapper with default/outline variants and tokenized sizes.",
6507
+ props: [
6508
+ { name: "pressed", type: "boolean", description: "Controlled pressed state." },
6509
+ { name: "onPressedChange", type: "(pressed: boolean) => void", description: "Pressed-state callback." },
6510
+ { name: "variant", type: '"default" | "outline"', defaultValue: '"default"', description: "Visual style." },
6511
+ { name: "size", type: '"sm" | "default" | "lg"', defaultValue: '"default"', description: "Control size." }
6512
+ ],
6513
+ usage: ["DO provide an accessible label when the toggle only contains an icon.", "DON'T use Toggle for multi-option selection; use ToggleGroup."],
6514
+ useCases: ["Bold/italic toolbar buttons", "Pinned filter toggles", "Compact view mode buttons"],
6515
+ related: ["ToggleGroup", "Button"],
6516
+ example: `import { Toggle } from "@godxjp/ui/data-entry";
6517
+
6518
+ <Toggle aria-label="Bold">B</Toggle>`,
6519
+ storyPath: "data-entry/Toggle.stories.tsx",
6520
+ rules: [3, 13]
6521
+ },
6522
+ {
6523
+ name: "ToggleGroup",
6524
+ group: "data-entry",
6525
+ tagline: "Radix ToggleGroup wrapper for single or multiple toggle selection.",
6526
+ props: [
6527
+ { name: "type", type: '"single" | "multiple"', required: true, description: "Selection mode." },
6528
+ { name: "value", type: "string | string[]", description: "Controlled selected value(s)." },
6529
+ { name: "onValueChange", type: "(value: string | string[]) => void", description: "Selection callback." }
6530
+ ],
6531
+ usage: ["DO choose type='single' for mutually exclusive toolbar modes.", "DO choose type='multiple' for independent formatting toggles."],
6532
+ useCases: ["Text alignment selector", "Formatting toolbar", "View density switcher"],
6533
+ related: ["Toggle", "RadioGroup"],
6534
+ example: `import { ToggleGroup, ToggleGroupItem } from "@godxjp/ui/data-entry";
6535
+
6536
+ <ToggleGroup type="single">
6537
+ <ToggleGroupItem value="left">Left</ToggleGroupItem>
6538
+ </ToggleGroup>`,
6539
+ storyPath: "data-entry/ToggleGroup.stories.tsx",
6540
+ rules: [3, 13]
6541
+ },
6542
+ {
6543
+ name: "AspectRatio",
6544
+ group: "layout",
6545
+ tagline: "Radix AspectRatio wrapper for stable media and preview frames.",
6546
+ props: [
6547
+ { name: "ratio", type: "number", defaultValue: "16 / 9", description: "Width divided by height." },
6548
+ { name: "children", type: "ReactNode", description: "Content constrained to the ratio." }
6549
+ ],
6550
+ usage: ["DO use AspectRatio for media, maps, charts, or previews that must not jump during load.", "DON'T use it for unconstrained text content."],
6551
+ useCases: ["Video embed frame", "Image preview slot", "Dashboard chart placeholder"],
6552
+ related: ["CardCover", "Skeleton"],
6553
+ example: `import { AspectRatio } from "@godxjp/ui/layout";
6554
+
6555
+ <AspectRatio ratio={16 / 9}>...</AspectRatio>`,
6556
+ storyPath: "layout/AspectRatio.stories.tsx",
6557
+ rules: [2, 3]
7080
6558
  }
7081
6559
  ];
7082
6560
  function findComponent(name) {
@@ -7318,9 +6796,9 @@ var CARDINAL_RULES = [
7318
6796
  { number: 31, title: "No nested wrapper / convenience primitives", body: "One Radix base = one framework primitive. `<SimpleX>` over `<X>` is forbidden; add a prop to `<X>` instead. Composites under `src/components/composites/` that combine multiple primitives are NOT wrappers." },
7319
6797
  { number: 32, title: "No redundant props", body: "Before adding a prop / item field / variant, grep the existing surface; if a field already covers the concept, use it. Top-level prop that re-expresses an item field (Timeline `pending` \u2194 `items[i].animate`) is rejected." },
7320
6798
  { number: 33, title: "Stories / source / docs name-synchronized", body: "No two names for the same export across the framework surface; no legacy aliases in stories / docs (source may keep an alias for a deprecation cycle, but the marketing surfaces use the canonical name only). Rename PR runs `grep -rn '<oldName>' src docs` and clears it." },
7321
- { number: 34, title: "Storybook source panel = real, copy-paste-ready code", body: 'Storybook\'s react-docgen serializer strips every function value (`cell: ({row}) => <JSX/>`, `render: ({field}) => <Input/>`, `rowClassName`, `renderItem`, \u2026) to `() => {}`. Any story whose `render` passes a function-valued prop, references a module-level helper (`StatusBadge`, `EMPLOYEE_COLUMNS`, etc.), or uses a render-prop pattern MUST override `parameters.docs.source.code` with the literal copy-paste-ready snippet \u2014 type aliases, helper functions spelled out, column definitions with cell JSX visible, inline data array. The `render()` callback stays as-is (module-level constants are fine for runtime performance); `source.code` is the marketing surface. Skip ONLY for stories whose JSX is purely static primitives Storybook can serialize verbatim (`<Button variant="primary">Click</Button>`). The exemplar is `Table.Default` in `src/stories/data-display/Table.stories.tsx`.' },
7322
- { number: 35, title: "Status chips never wrap", body: "A `StatusBadge` / `Badge` reads as one atomic unit. Its label must never break across lines \u2014 pin `white-space: nowrap` on the chip (done in `badge-layout.css`), especially inside narrow `DataTable` cells (\u30B9\u30B3\u30FC\u30D7 / \u30B9\u30C6\u30FC\u30BF\u30B9 columns). If a cell is too tight, widen the column or shorten the label; never let the chip wrap." },
7323
- { number: 36, title: "StatusBadge tone/icon are the colour escape hatch", body: "`StatusBadge` auto-maps a fixed set of English lifecycle keys (active, draft, pending, scheduled, cancelled, failed, \u2026) to tone + icon. For ANY other value \u2014 localized labels (\u516C\u958B\u4E2D, \u30A2\u30AF\u30C6\u30A3\u30D6) or categorical tiers (\u4F1A\u54E1\u30E9\u30F3\u30AF, \u5951\u7D04\u30D7\u30E9\u30F3) \u2014 pass `tone` explicitly (success | warning | destructive | info | neutral) and, for non-lifecycle tiers, `icon={null}` to drop the misleading glyph. Don't let domain labels fall back to neutral grey + \u25CB. Map domain\u2192tone in the CONSUMER layer; the framework only provides the props." },
6799
+ { number: 34, title: "Storybook source panel = real, copy-paste-ready code", body: 'Storybook\'s react-docgen serializer strips every function value (`cell: ({row}) => <JSX/>`, `render: ({field}) => <Input/>`, `rowClassName`, `renderItem`, \u2026) to `() => {}`. Any story whose `render` passes a function-valued prop, references a module-level helper (`Badge`, `EMPLOYEE_COLUMNS`, etc.), or uses a render-prop pattern MUST override `parameters.docs.source.code` with the literal copy-paste-ready snippet \u2014 type aliases, helper functions spelled out, column definitions with cell JSX visible, inline data array. The `render()` callback stays as-is (module-level constants are fine for runtime performance); `source.code` is the marketing surface. Skip ONLY for stories whose JSX is purely static primitives Storybook can serialize verbatim (`<Button variant="primary">Click</Button>`). The exemplar is `Table.Default` in `src/stories/data-display/Table.stories.tsx`.' },
6800
+ { number: 35, title: "Status chips never wrap", body: "A `Badge` / `Badge` reads as one atomic unit. Its label must never break across lines \u2014 pin `white-space: nowrap` on the chip (done in `badge-layout.css`), especially inside narrow `DataTable` cells (\u30B9\u30B3\u30FC\u30D7 / \u30B9\u30C6\u30FC\u30BF\u30B9 columns). If a cell is too tight, widen the column or shorten the label; never let the chip wrap." },
6801
+ { number: 36, title: "Badge tone/icon are the colour escape hatch", body: "`Badge` auto-maps a fixed set of English lifecycle keys (active, draft, pending, scheduled, cancelled, failed, \u2026) to tone + icon. For ANY other value \u2014 localized labels (\u516C\u958B\u4E2D, \u30A2\u30AF\u30C6\u30A3\u30D6) or categorical tiers (\u4F1A\u54E1\u30E9\u30F3\u30AF, \u5951\u7D04\u30D7\u30E9\u30F3) \u2014 pass `tone` explicitly (success | warning | destructive | info | neutral) and, for non-lifecycle tiers, `icon={null}` to drop the misleading glyph. Don't let domain labels fall back to neutral grey + \u25CB. Map domain\u2192tone in the CONSUMER layer; the framework only provides the props." },
7324
6802
  { number: 37, title: "DataTable is full-width \u2014 never inside a narrow grid column", body: "A multi-column `DataTable` occupies its OWN row at the page's full width: `<Card><CardContent flush><DataTable \u2026/></CardContent></Card>`. Never nest it in a `lg:col-span-2` of a `ResponsiveGrid columns={3}` beside a chart \u2014 the columns get squeezed until CJK text collapses to one character per line. Charts / KPI cards go in their own row ABOVE the table. (See the `inertia-list-page` pattern.)" },
7325
6803
  { number: 38, title: "FilterBar stays OUT of CardContent flush", body: "`CardContent flush` strips horizontal padding for edge-to-edge tables. A `FilterBar` placed inside it loses all padding and sticks to the card edge. Render `FilterBar` as a STANDALONE block above the table card; wrap ONLY the `DataTable` / `EmptyState` in the `Card` + `CardContent flush`. Order on a list page: KPIs \u2192 FilterBar \u2192 table card." },
7326
6804
  { number: 39, title: "Long text columns get an explicit width", body: "For columns whose value can be long (name / title / segment / address), set `col.width` to a Tailwind width class (e.g. `w-64`, `w-48`) so the column reserves space instead of shrinking and wrapping to many lines; leave numeric / status columns auto. Table cells default to `white-space: nowrap`, so an over-tight table scrolls horizontally rather than crushing \u2014 give the important columns real widths so the default layout reads well before any scroll." },
@@ -7334,7 +6812,7 @@ function findRule(num) {
7334
6812
  var PATTERNS = [
7335
6813
  {
7336
6814
  name: "common-fixes",
7337
- tagline: "Fix the most common @godxjp/ui consumer mistakes & visual bugs (CardStat double-border, grey StatusBadge, crushed/empty table headers, washed-out sidebar footer, Inertia layout crash, SSR hydration). Before \u2192 after.",
6815
+ tagline: "Fix the most common @godxjp/ui consumer mistakes & visual bugs (StatCard double-border, grey Badge, crushed/empty table headers, washed-out sidebar footer, Inertia layout crash, SSR hydration). Before \u2192 after.",
7338
6816
  tags: ["fixes", "migration", "bug", "cardstat", "statusbadge", "datatable", "sidebar", "gotcha", "review"],
7339
6817
  code: `// \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
7340
6818
  // 0) \u2605 MOST COMMON: <Card> body has NO padding (content is flush against the edges)
@@ -7350,19 +6828,19 @@ var PATTERNS = [
7350
6828
  // empty rows \u2192 DataTable's built-in empty / <EmptyState> (not a custom data.length===0 guard).
7351
6829
  // If a primitive exists, USE it \u2014 don't reinvent it.
7352
6830
 
7353
- // 1) CardStat shows a DOUBLE border (too thick)
7354
- // Cause: CardStat IS already a bordered Card. Don't wrap it.
7355
- // \u274C <Card><CardContent><CardStat label="x" value="1" /></CardContent></Card>
7356
- // \u2705 <ResponsiveGrid columns={4}><CardStat label="x" value="1" /></ResponsiveGrid>
6831
+ // 1) StatCard shows a DOUBLE border (too thick)
6832
+ // Cause: StatCard IS already a bordered Card. Don't wrap it.
6833
+ // \u274C <Card><CardContent><StatCard label="x" value="1" /></CardContent></Card>
6834
+ // \u2705 <ResponsiveGrid columns={4}><StatCard label="x" value="1" /></ResponsiveGrid>
7357
6835
  // Need a section title? Use a heading, NOT a Card:
7358
6836
  // \u2705 <Stack gap="sm"><div className="text-sm font-medium">KPI</div>
7359
- // <ResponsiveGrid columns={4}><CardStat .../></ResponsiveGrid></Stack>
6837
+ // <ResponsiveGrid columns={4}><StatCard .../></ResponsiveGrid></Stack>
7360
6838
 
7361
- // 2) StatusBadge renders grey with a \u25CB (no colour) for localized/tier labels
6839
+ // 2) Badge renders grey with a \u25CB (no colour) for localized/tier labels
7362
6840
  // Cause: it auto-maps only English lifecycle keys. (@godxjp/ui >= 6.1)
7363
- // \u274C <StatusBadge status="\u30D7\u30EC\u30DF\u30A2\u30E0" />
7364
- // \u2705 <StatusBadge status="\u30D7\u30EC\u30DF\u30A2\u30E0" tone="success" icon={null} /> // tier \u2192 pill, no icon
7365
- // \u2705 <StatusBadge status="active" label="\u516C\u958B\u4E2D" /> // lifecycle \u2192 keep icon
6841
+ // \u274C <Badge status="\u30D7\u30EC\u30DF\u30A2\u30E0" />
6842
+ // \u2705 <Badge status="\u30D7\u30EC\u30DF\u30A2\u30E0" variant="success" icon={null} /> // tier \u2192 pill, no icon
6843
+ // \u2705 <Badge status="active">\u516C\u958B\u4E2D</Badge> // lifecycle \u2192 keep icon
7366
6844
 
7367
6845
  // 3) Table text collapses to one char per line, or a chip wraps
7368
6846
  // Cause: pre-6.1.2. (@godxjp/ui >= 6.1.2 \u2192 cells + chips are nowrap)
@@ -7399,7 +6877,7 @@ var PATTERNS = [
7399
6877
 
7400
6878
  // 10) Hide a column on mobile / sign-aware KPI delta (@godxjp/ui >= 6.2.0)
7401
6879
  // \u2705 columns: [{ key: "email", header: "\u30E1\u30FC\u30EB", hiddenOnMobile: true }]
7402
- // \u2705 <CardStat label="\u58F2\u4E0A" value="\xA58.2M" delta="+12%" /> // + green / - red; inverse flips`
6880
+ // \u2705 <StatCard label="\u58F2\u4E0A" value="\xA58.2M" delta="+12%" /> // + green / - red; inverse flips`
7403
6881
  },
7404
6882
  {
7405
6883
  name: "signup-form",
@@ -7553,12 +7031,12 @@ export default function Coupons({ coupons }: { coupons?: Coupon[] }) {
7553
7031
  },
7554
7032
  {
7555
7033
  name: "inertia-list-page",
7556
- tagline: "Inertia + @godxjp/ui list page \u2014 PageContainer + FilterBar + DataTable + StatusBadge + Pagination (current primitive API).",
7034
+ tagline: "Inertia + @godxjp/ui list page \u2014 PageContainer + FilterBar + DataTable + Badge + Pagination (current primitive API).",
7557
7035
  tags: ["inertia", "list", "table", "page", "filter", "pagination", "datatable", "crm"],
7558
7036
  code: `import { Head, router } from "@inertiajs/react"
7559
7037
  import { useMemo, useState } from "react"
7560
7038
  import { PageContainer, ResponsiveGrid, Stack } from "@godxjp/ui/layout"
7561
- import { Card, CardContent, CardStat, DataTable, EmptyState, StatusBadge, type ColumnDef } from "@godxjp/ui/data-display"
7039
+ import { Card, CardContent, StatCard, DataTable, EmptyState, Badge, type ColumnDef } from "@godxjp/ui/data-display"
7562
7040
  import { SearchInput, Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@godxjp/ui/data-entry"
7563
7041
  import { FilterBar, FilterGroup, Pagination } from "@godxjp/ui/navigation"
7564
7042
  import { formatDate } from "@godxjp/ui/datetime"
@@ -7582,8 +7060,8 @@ function Coupons({ coupons }: { coupons: Coupon[] }) {
7582
7060
  // ColumnDef = { key, header, render?, align?: "left"|"center"|"right", sortable?, width? }
7583
7061
  const columns: ColumnDef<Coupon>[] = [
7584
7062
  { key: "name", header: "\u30AF\u30FC\u30DD\u30F3\u540D", render: (c) => <span className="font-medium">{c.name}</span> },
7585
- { key: "scope", header: "\u30B9\u30B3\u30FC\u30D7", render: (c) => <StatusBadge status={c.scope} tone="info" icon={null} /> },
7586
- { key: "status", header: "\u30B9\u30C6\u30FC\u30BF\u30B9", render: (c) => <StatusBadge status={c.status} /> },
7063
+ { key: "scope", header: "\u30B9\u30B3\u30FC\u30D7", render: (c) => <Badge status={c.scope} variant="info" icon={null} /> },
7064
+ { key: "status", header: "\u30B9\u30C6\u30FC\u30BF\u30B9", render: (c) => <Badge status={c.status} /> },
7587
7065
  { key: "valid", header: "\u6709\u52B9\u671F\u9593", render: (c) => \`\${formatDate(c.validFrom)} \u301C \${formatDate(c.validTo)}\` },
7588
7066
  { key: "usage", header: "\u5229\u7528\u6570", align: "right", render: (c) => c.usage.toLocaleString() },
7589
7067
  ]
@@ -7595,9 +7073,9 @@ function Coupons({ coupons }: { coupons: Coupon[] }) {
7595
7073
  <PageContainer title="\u30AF\u30FC\u30DD\u30F3\u7BA1\u7406" subtitle="\u914D\u4FE1\u4E2D\u306E\u30AF\u30FC\u30DD\u30F3\u4E00\u89A7">
7596
7074
  <Stack gap="lg">
7597
7075
  <ResponsiveGrid columns={3}>
7598
- <CardStat label="\u516C\u958B\u4E2D" value={coupons.filter((c) => c.status === "\u516C\u958B\u4E2D").length} />
7599
- <CardStat label="\u7DCF\u5229\u7528\u6570" value={coupons.reduce((s, c) => s + c.usage, 0).toLocaleString()} />
7600
- <CardStat label="\u4EF6\u6570" value={coupons.length} />
7076
+ <StatCard label="\u516C\u958B\u4E2D" value={coupons.filter((c) => c.status === "\u516C\u958B\u4E2D").length} />
7077
+ <StatCard label="\u7DCF\u5229\u7528\u6570" value={coupons.reduce((s, c) => s + c.usage, 0).toLocaleString()} />
7078
+ <StatCard label="\u4EF6\u6570" value={coupons.length} />
7601
7079
  </ResponsiveGrid>
7602
7080
 
7603
7081
  <FilterBar hasActiveFilters={q !== "" || status !== "all"} onClear={() => { setQ(""); setStatus("all"); setPage(1) }}>
@@ -7637,11 +7115,11 @@ export default Coupons`
7637
7115
  },
7638
7116
  {
7639
7117
  name: "inertia-detail-page",
7640
- tagline: "Inertia detail page \u2014 receives {id} prop, KeyValueGrid (compound) + CardStat + EmptyState fallback.",
7118
+ tagline: "Inertia detail page \u2014 receives {id} prop, Descriptions (compound) + StatCard + EmptyState fallback.",
7641
7119
  tags: ["inertia", "detail", "show", "page", "keyvaluegrid", "crm"],
7642
7120
  code: `import { Head, router } from "@inertiajs/react"
7643
7121
  import { PageContainer, ResponsiveGrid, Stack } from "@godxjp/ui/layout"
7644
- import { Card, CardContent, CardStat, EmptyState, KeyValueGrid, StatusBadge } from "@godxjp/ui/data-display"
7122
+ import { Card, CardContent, StatCard, EmptyState, Descriptions, Badge } from "@godxjp/ui/data-display"
7645
7123
  import { Button } from "@godxjp/ui/general"
7646
7124
  import { formatDate } from "@godxjp/ui/datetime"
7647
7125
  import { ArrowLeft } from "lucide-react"
@@ -7670,20 +7148,20 @@ function MemberShow({ id }: { id: string }) {
7670
7148
  <PageContainer title={member.name} subtitle={\`\${member.id} / \${member.rank}\`}>
7671
7149
  <Stack gap="lg">
7672
7150
  <ResponsiveGrid columns={4}>
7673
- <CardStat label="\u7D2F\u8A08\u8CFC\u5165\u984D" value={\`\xA5\${member.total.toLocaleString()}\`} />
7674
- <CardStat label="\u6765\u5E97\u56DE\u6570" value={member.visits} />
7675
- <CardStat label="\u30DD\u30A4\u30F3\u30C8" value={member.points.toLocaleString()} />
7676
- <CardStat label="LTV" value={\`\xA5\${member.ltv.toLocaleString()}\`} />
7151
+ <StatCard label="\u7D2F\u8A08\u8CFC\u5165\u984D" value={\`\xA5\${member.total.toLocaleString()}\`} />
7152
+ <StatCard label="\u6765\u5E97\u56DE\u6570" value={member.visits} />
7153
+ <StatCard label="\u30DD\u30A4\u30F3\u30C8" value={member.points.toLocaleString()} />
7154
+ <StatCard label="LTV" value={\`\xA5\${member.ltv.toLocaleString()}\`} />
7677
7155
  </ResponsiveGrid>
7678
7156
  <Card>
7679
7157
  <CardContent>
7680
- {/* KeyValueGrid is COMPOUND \u2014 value goes in children, not a prop */}
7681
- <KeyValueGrid columns={2}>
7682
- <KeyValueGrid.Item label="\u6C0F\u540D">{member.name}</KeyValueGrid.Item>
7683
- <KeyValueGrid.Item label="\u30E9\u30F3\u30AF"><StatusBadge status={member.rank} tone="info" icon={null} /></KeyValueGrid.Item>
7684
- <KeyValueGrid.Item label="\u30B9\u30C6\u30FC\u30BF\u30B9"><StatusBadge status={member.status} /></KeyValueGrid.Item>
7685
- <KeyValueGrid.Item label="\u767B\u9332\u65E5">{formatDate(member.registeredAt)}</KeyValueGrid.Item>
7686
- </KeyValueGrid>
7158
+ {/* Descriptions is COMPOUND \u2014 value goes in children, not a prop */}
7159
+ <Descriptions columns={2}>
7160
+ <Descriptions.Item label="\u6C0F\u540D">{member.name}</Descriptions.Item>
7161
+ <Descriptions.Item label="\u30E9\u30F3\u30AF"><Badge status={member.rank} variant="info" icon={null} /></Descriptions.Item>
7162
+ <Descriptions.Item label="\u30B9\u30C6\u30FC\u30BF\u30B9"><Badge status={member.status} /></Descriptions.Item>
7163
+ <Descriptions.Item label="\u767B\u9332\u65E5">{formatDate(member.registeredAt)}</Descriptions.Item>
7164
+ </Descriptions>
7687
7165
  </CardContent>
7688
7166
  </Card>
7689
7167
  </Stack>
@@ -7729,31 +7207,31 @@ export const withCrmLayout = [CrmLayout] // \u2705 array \u2192 Inertia passes
7729
7207
  const seeded = (n: number) => { const x = Math.sin((n + 1) * 99.71) * 1e4; return x - Math.floor(x) }`
7730
7208
  },
7731
7209
  {
7732
- name: "status-badge-coloring",
7733
- tagline: "Colour a StatusBadge for localized labels and tiers via tone + icon (escape-hatch props, @godxjp/ui \u2265 6.1).",
7210
+ name: "badge-coloring",
7211
+ tagline: "Colour a Badge for localized labels and tiers via tone + icon (escape-hatch props, @godxjp/ui \u2265 6.1).",
7734
7212
  tags: ["statusbadge", "badge", "tone", "color", "status", "tier", "table"],
7735
- code: `import { StatusBadge } from "@godxjp/ui/data-display"
7213
+ code: `import { Badge } from "@godxjp/ui/data-display"
7736
7214
 
7737
- // StatusBadge auto-colours a fixed set of English LIFECYCLE keys:
7215
+ // Badge auto-colours a fixed set of English LIFECYCLE keys:
7738
7216
  // active/completed (success \u2713) \xB7 draft (neutral \u25CB) \xB7 pending/temporary (warning \u23F1)
7739
7217
  // scheduled/sending (info) \xB7 cancelled (neutral) \xB7 failed/deleted/bounced (destructive \u2715)
7740
7218
  // Anything else (localized labels, tiers) falls back to neutral grey \u25CB unless you override.
7741
7219
 
7742
7220
  // 1) Lifecycle with localized text \u2014 map to the key, keep JP via \`label\` (icon stays):
7743
- <StatusBadge status="active" label="\u516C\u958B\u4E2D" /> // green \u2713 \u516C\u958B\u4E2D
7221
+ <Badge status="active">\u516C\u958B\u4E2D</Badge> // green \u2713 \u516C\u958B\u4E2D
7744
7222
 
7745
7223
  // 2) Unknown label \u2014 set tone explicitly (no icon, since the key is unknown):
7746
- <StatusBadge status="\u516C\u958B\u4E2D" tone="success" />
7224
+ <Badge status="\u516C\u958B\u4E2D" variant="success" />
7747
7225
 
7748
7226
  // 3) Tier / category \u2014 coloured pill, drop the misleading glyph with icon={null}:
7749
- <StatusBadge status="\u30D7\u30EC\u30DF\u30A2\u30E0" tone="success" icon={null} />
7750
- <StatusBadge status="\u30B4\u30FC\u30EB\u30C9" tone="warning" icon={null} />
7751
- <StatusBadge status="\u6CD5\u4EBA\u5171\u901A" tone="info" icon={null} />
7227
+ <Badge status="\u30D7\u30EC\u30DF\u30A2\u30E0" variant="success" icon={null} />
7228
+ <Badge status="\u30B4\u30FC\u30EB\u30C9" variant="warning" icon={null} />
7229
+ <Badge status="\u6CD5\u4EBA\u5171\u901A" variant="info" icon={null} />
7752
7230
 
7753
- // tone: "success" | "warning" | "destructive" | "info" | "neutral" (import type StatusBadgeTone)
7231
+ // tone: "success" | "warning" | "destructive" | "info" | "neutral" (import type BadgeTone)
7754
7232
  // RULE: a chip never wraps \u2014 it is pinned white-space: nowrap, so it stays one line in
7755
7233
  // narrow table cells. Centralize the domain\u2192tone map in ONE small consumer wrapper and
7756
- // import that instead of the raw StatusBadge across pages.`
7234
+ // import that instead of the raw Badge across pages.`
7757
7235
  }
7758
7236
  ];
7759
7237
  function findPattern(name) {
@@ -8501,7 +7979,7 @@ content, system-bar safe area. The framework's \`useBreakpoint\`
8501
7979
  body: `10+ tabs at the top of a screen, no priority. User has to read all
8502
7980
  of them to find the right one. AI default: "more tabs = more
8503
7981
  features = better".`,
8504
- fix: `2-4 tabs max. If you have more categories, use a sidebar (Menu),
7982
+ fix: `2-4 tabs max. If you have more categories, use a sidebar (Sidebar),
8505
7983
  or a Cascader / Tree picker. Tabs are for switching between PEERS
8506
7984
  (2-4 mutually exclusive views of the same data).`
8507
7985
  },