@godxjp/ui-mcp 0.7.0 → 0.8.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
@@ -59,6 +59,28 @@ var COMPONENTS = [
59
59
  description: 'Pin footer to viewport bottom on scroll \u2014 pairs with variant="narrow".'
60
60
  }
61
61
  ],
62
+ usage: [
63
+ "DO: Always wrap every page's content in PageContainer \u2014 it is the mandatory page shell. Pass `title` (required, rendered as `<h1>`) for every page; omitting it leaves the page without an accessible heading.",
64
+ "DO: Use the `extra` prop (not a sibling div, not a wrapper) for action buttons or controls that sit right of the title row \u2014 e.g. `extra={<Button>\u65B0\u898F\u4F5C\u6210</Button>}`. Use the `footer` prop for a pinned action bar below the body (e.g. Save/Cancel on a form page); combine with `stickyFooter` to pin it to the viewport bottom on scroll.",
65
+ "DO: Use `variant='flush'` when the page body contains a full-bleed component like DataTable. Inside a flush container, wrap any padded strips (FilterBar, intro text) in `<PageInset>` to align them with the header. Never add manual `px-*` or `p-*` padding to compensate \u2014 use PageInset.",
66
+ "DO: Pass `breadcrumb` as an ordered array of `{ label, to? }` objects from root to current page. The last item is automatically rendered without a link and receives `aria-current='page'`; earlier items with `to` become router `<Link>` elements. Never hand-roll a breadcrumb nav inside a PageContainer.",
67
+ "DON'T: Use `density` to change individual control sizes \u2014 it cascades spacing across the entire page subtree. Set it once per page (e.g. `density='compact'` for data-dense list pages) and let all child components inherit it. Do not apply density classes manually.",
68
+ "DON'T: Confuse PageContainer's prop names with the deprecated PageHeader's prop names \u2014 PageContainer uses `subtitle` (not `description`) and `extra` (not `actions`). If you see those legacy names in old code, it is PageHeader, not PageContainer."
69
+ ],
70
+ useCases: [
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
+ "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
+ "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.",
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
+ "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
+ ],
78
+ related: [
79
+ "PageInset \u2014 use INSIDE a `variant='flush'` PageContainer to re-introduce horizontal padding for strips like FilterBar or intro text that should align with the page header, while the surrounding DataTable stays full-bleed. Not a standalone page shell.",
80
+ "PageHeader \u2014 DEPRECATED header-only predecessor to PageContainer. Has no `children`, `footer`, `variant`, `density`, or `stickyFooter` props. Use its prop-name aliases (`description` \u2192 `subtitle`, `actions` \u2192 `extra`) only when reading legacy code. Always prefer PageContainer for new pages.",
81
+ "AppShell \u2014 the outer shell that owns the sidebar/topbar layout grid; PageContainer lives inside AppShell's `children` slot. Do not put AppShell inside PageContainer \u2014 the nesting order is AppShell \u2192 PageContainer.",
82
+ "SplitPane \u2014 use instead of PageContainer when the page body needs a fixed-width aside panel alongside main content (e.g. a detail drawer next to a list). PageContainer has no aside slot; SplitPane fills that gap and can itself be placed inside PageContainer's children."
83
+ ],
62
84
  example: `import { PageContainer, Stack } from "@godxjp/ui/layout";
63
85
  import { Button } from "@godxjp/ui/general";
64
86
 
@@ -91,6 +113,28 @@ export default function OrdersPage() {
91
113
  { name: "className", type: "string", description: "Extra classes merged via cn()." },
92
114
  { name: "children", type: "ReactNode", description: "Block-level children to stack." }
93
115
  ],
116
+ usage: [
117
+ 'DO use `<Stack gap="lg">` (or sm/md/xl/xs) to separate major page sections \u2014 KPI row, FilterBar, table card \u2014 instead of writing `space-y-*` or `flex flex-col gap-*` utilities on a raw div. Token gap values (`xs`|`sm`|`md`|`lg`|`xl`) map to CSS custom properties; raw Tailwind spacing utilities (`space-y-4`) are forbidden on page layouts (rule 40).',
118
+ "DO pass `className` only for structural overrides (e.g., `w-full`, `min-h-0`) \u2014 NEVER to smuggle spacing utilities like `p-4` or `gap-6` that duplicate what the `gap` prop already does. Every spacing value must trace back to a design token.",
119
+ 'DO NOT use Stack for horizontal arrangements \u2014 that is `Inline`. Stack is vertical only (`flex-col`). Mixing `className="flex-row"` on a Stack to force horizontal is wrong; use `Inline` instead.',
120
+ 'DO NOT nest multiple bare Stack wrappers just to get different gaps \u2014 compose them: a top-level `<Stack gap="xl">` for page sections, an inner `<Stack gap="sm">` for tightly-related fields. Each level should correspond to a real semantic grouping.',
121
+ 'DO import from `@godxjp/ui/layout` (not the root `@godxjp/ui` barrel) \u2014 `import { Stack } from "@godxjp/ui/layout"`.',
122
+ "Stack is a plain `<div>` with `React.HTMLAttributes<HTMLDivElement>` \u2014 all standard HTML div props (`data-*`, `id`, `aria-*`, `role`) pass through. There is no controlled/uncontrolled state and no form submission role; use `FormField` for form layout concerns."
123
+ ],
124
+ useCases: [
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.",
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.',
129
+ 'Modal/Sheet interiors: `<Stack gap="md">` inside `<Dialog>` or `<Sheet>` content area to separate sections (description, form, preview) with consistent token spacing.',
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
+ ],
132
+ related: [
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.",
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
+ "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
+ ],
94
138
  example: `import { Stack } from "@godxjp/ui/layout";
95
139
 
96
140
  <Stack gap="lg">
@@ -115,6 +159,28 @@ export default function OrdersPage() {
115
159
  { name: "className", type: "string", description: "Extra classes merged via cn()." },
116
160
  { name: "children", type: "ReactNode", description: "Inline children in a row." }
117
161
  ],
162
+ usage: [
163
+ 'DO use `<Inline gap="sm">` instead of hand-rolling `flex gap-2 items-center` \u2014 the component bakes in `display:flex; flex-wrap:wrap; align-items:center` via the token class `ui-inline-*`, so you get consistent spacing and wrapping behavior without raw Tailwind utilities (rule 2).',
164
+ 'DO pass extra layout modifiers through `className` (e.g. `className="justify-between"` to push items to opposite ends, or `className="flex-nowrap"` to prevent wrapping when overflow must be clipped) \u2014 `Inline` spreads all HTML div attributes, so className is merged via `cn()`.',
165
+ "DON'T use `Inline` for vertical stacking \u2014 it is always a row. For vertical spacing between block elements use `Stack` instead. The most common mistake is reaching for `Inline` when the children should stack, or reaching for `Stack` when the children should sit side-by-side.",
166
+ "DON'T nest an `Inline` just to group icon + label inside a `Button` \u2014 Button already handles its own internal row layout. Use `Inline` at the call-site level to space multiple sibling Buttons or controls apart.",
167
+ 'DO use `gap="xs"` for tight icon+label pairs (e.g. flag + country name in CountrySelect) and `gap="sm"` (default) for button groups and toolbar-level groupings. `gap="md"` / `gap="lg"` suit section-header clusters with more breathing room.',
168
+ "NOTE: `Inline` has no alignment, justify, or wrap props beyond `gap` \u2014 any extra layout intent must come through `className`. There is no controlled/form-submission aspect; it is a pure layout shell with no ARIA role of its own."
169
+ ],
170
+ useCases: [
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
+ "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.",
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
+ '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
+ '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).'
177
+ ],
178
+ related: [
179
+ "Stack \u2014 use Stack when children should be arranged vertically (column direction). Inline is horizontal/row; Stack is vertical/column. They are the two axis-specific layout primitives and are often composed together (Stack of Inlines).",
180
+ "PageInset \u2014 use PageInset when you need a padded horizontal strip inside a flush PageContainer (e.g. for FilterBar or intro text above a full-bleed DataTable). PageInset adds top/side padding to a section; Inline only arranges children in a row with a gap.",
181
+ "FilterBar \u2014 use FilterBar (with FilterGroup) when the horizontal group of controls is a page-level filter row that semantically belongs together and may include responsive collapse behaviour. Use Inline for ad-hoc groupings of buttons or badges that don't need filter semantics.",
182
+ "ResponsiveGrid \u2014 use ResponsiveGrid when you need a multi-column grid of cards that collapses on mobile. Use Inline when children must stay in a single wrapping row at all breakpoints without explicit column counts."
183
+ ],
118
184
  example: `import { Inline } from "@godxjp/ui/layout";
119
185
  import { Button } from "@godxjp/ui/general";
120
186
 
@@ -143,6 +209,28 @@ import { Button } from "@godxjp/ui/general";
143
209
  description: "Grid items \u2014 typically Card or CardStat."
144
210
  }
145
211
  ],
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>.",
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
+ "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
+ "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.",
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
+ ],
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.",
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
+ ],
228
+ related: [
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
+ "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."
233
+ ],
146
234
  example: `import { ResponsiveGrid } from "@godxjp/ui/layout";
147
235
  import { CardStat } from "@godxjp/ui/data-display";
148
236
 
@@ -204,6 +292,28 @@ import { CardStat } from "@godxjp/ui/data-display";
204
292
  description: "App-level footer outside the main content area."
205
293
  }
206
294
  ],
295
+ usage: [
296
+ "DO pass a <Sidebar> node to `sidebar` (required) and page content to `children` (required) \u2014 these are the only two required props. Everything else is optional and omitting optional slots simply removes that zone from the rendered DOM.",
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
+ "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
+ "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.",
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
+ ],
303
+ useCases: [
304
+ "Full admin SPA shell: AppShell wraps a <Sidebar> nav rail and a <Topbar> (with productMenu entity-switcher, onSearchOpen, onNotificationsOpen, user avatar) and every Inertia page renders as children inside a <PageContainer>.",
305
+ "Collapsible-sidebar layout: maintain a `collapsed` boolean in a persistent Inertia layout component, pass it to both AppShell's `sidebarCollapsed` and Sidebar's `collapsed`, wire Topbar's `onToggleCollapsed` to flip it \u2014 AppShell handles the CSS transition automatically.",
306
+ "Multi-tenant accounting app: pass a <Topbar productMenu={<DropdownMenuContent>\u2026</DropdownMenuContent>}> to AppShell's `topbar` slot so the legal-entity chip opens an inline switcher without a modal.",
307
+ "App-level footer (e.g. version/build info, compliance notice): pass a <footer> node to AppShell's `footer` prop \u2014 it renders outside `<main>` so it stays pinned below the scroll area.",
308
+ "Rapid prototype or internal tool where you want a branded shell with minimal topbar: skip the `topbar` prop entirely and use `logo`, `topbarLeft`, `topbarRight` to build the rail declaratively without instantiating <Topbar>.",
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
+ ],
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.",
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
+ "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
+ "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."
316
+ ],
207
317
  example: `import { AppShell, Sidebar } from "@godxjp/ui/layout";
208
318
  import { LayoutDashboard, Users } from "lucide-react";
209
319
  import { router } from "@inertiajs/react";
@@ -537,6 +647,28 @@ function MyShell({ children }: { children: React.ReactNode }) {
537
647
  },
538
648
  { name: "className", type: "string", description: "Extra classes." }
539
649
  ],
650
+ usage: [
651
+ 'DO use PageInset exclusively inside a `<PageContainer variant="flush">` \u2014 flush strips the left/right padding from the container body, and PageInset re-applies the exact same `--space-page-active-x` token so content aligns pixel-for-pixel with the page header and footer. Outside of a flush container it doubles the padding.',
652
+ "DO NOT use PageInset to add padding inside a standard (non-flush) PageContainer \u2014 the container body already has `--space-page-active-x` padding on both sides; wrapping content in PageInset produces doubled inset.",
653
+ 'DO NOT hand-roll the padding with `className="px-6"` or `className="px-page"` \u2014 those values are not guaranteed to match the design token. PageInset is the only correct way to reproduce the page-header alignment inside a flush body.',
654
+ "DO place PageInset as a direct child of PageContainer's body (before or between full-bleed children such as DataTable or Table) \u2014 it is a plain `<div>` that wraps one cohesive strip (e.g., a FilterBar, a summary intro, or a quick-action toolbar) and does not provide vertical spacing itself.",
655
+ "DO accept all standard HTMLDivElement attributes (id, aria-*, data-*, style) via the spread \u2014 PageInsetProp extends React.HTMLAttributes<HTMLDivElement>, so accessibility attributes and test hooks pass through without extra wrappers.",
656
+ "COMMON MISTAKE: adding a PageInset around a DataTable inside a flush PageContainer \u2014 DataTable must remain full-bleed (no PageInset) so its borders reach the edges; only the filter/intro strips above or below the table need PageInset."
657
+ ],
658
+ useCases: [
659
+ "Flush list page with a FilterBar above a full-bleed DataTable: wrap the FilterBar (or FilterBar + FilterGroup) in PageInset so it aligns with the page title, while DataTable sits edge-to-edge beneath it.",
660
+ "Invoice or transaction index where a SearchInput + date range picker toolbar sits above a borderless table \u2014 PageInset keeps the toolbar inset-aligned without breaking the table's flush left/right edges.",
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
+ "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
+ '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."
665
+ ],
666
+ related: [
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).',
668
+ "FilterBar \u2014 the most common direct child of PageInset; FilterBar itself has no page-level padding, so always wrap it in PageInset when inside a flush PageContainer.",
669
+ 'CardContent \u2014 serves a similar "add-the-missing-padding" role but inside a Card, not a flush PageContainer. Use CardContent to pad content inside a Card; use PageInset to pad content inside a flush page body.',
670
+ "Stack \u2014 a vertical-spacing primitive that can wrap multiple PageInset strips but has no padding of its own; Stack and PageInset compose orthogonally (Stack for gaps, PageInset for horizontal alignment)."
671
+ ],
540
672
  example: `import { PageContainer, PageInset } from "@godxjp/ui/layout";
541
673
 
542
674
  <PageContainer title="\u5546\u54C1\u4E00\u89A7" variant="flush">
@@ -565,6 +697,27 @@ function MyShell({ children }: { children: React.ReactNode }) {
565
697
  description: "Width preset for the aside column."
566
698
  }
567
699
  ],
700
+ usage: [
701
+ "DO: pass all right-panel content via the `aside` prop \u2014 it renders inside a semantic `<aside>` element at a fixed rem width (sm=20rem, md=22rem). The `children` prop fills the main `1fr` column. Both accept any ReactNode.",
702
+ 'DO: choose `asideWidth="sm"` for compact detail panels (filters, quick stats, key-value summaries) and the default `asideWidth="md"` for richer panels (forms, timelines, long metadata lists).',
703
+ "DO: wrap SplitPane inside `PageContainer` or `PageInset` \u2014 SplitPane provides no page padding of its own. It is a grid primitive, not a page scaffold.",
704
+ "DON'T: expect two columns below 1080px. Below that breakpoint SplitPane stacks to a single column (main on top, aside below). Never use it for layouts that must remain side-by-side on tablet or mobile \u2014 use CSS Grid or `ResponsiveGrid` instead.",
705
+ "DON'T: add a CSS `overflow: hidden` or fixed height on the SplitPane wrapper; both columns carry `min-width: 0` to handle overflow correctly, and the grid uses `minmax(0, 1fr)` \u2014 adding external constraints will break the overflow contract.",
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
+ ],
708
+ useCases: [
709
+ "Invoice / transaction detail page: list of records in `children` (DataTable), selected-record detail panel in `aside` (KeyValueGrid + Timeline).",
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
+ "Document review workflow: PDF or rich-text viewer in `children`, approval form or annotation panel in `aside`.",
712
+ "Settings page with a category list or Steps navigator in `children` and a live preview or summary card in `aside`.",
713
+ "Kanban or task board where the main area holds the board columns and the aside shows the focused task detail without navigating away."
714
+ ],
715
+ related: [
716
+ "ResponsiveGrid \u2014 use when you need more than two columns, or when both columns must have equal or percentage-based widths rather than a fixed-rem aside. SplitPane always gives main a `1fr` and aside a fixed rem width.",
717
+ "PageContainer \u2014 use as the outer scaffold that provides page padding and vertical rhythm; nest SplitPane inside PageContainer, not the other way around.",
718
+ "Sheet \u2014 use when the detail/context panel should slide in as an overlay (drawer) rather than sitting permanently beside the main content. Prefer Sheet on mobile or when the aside content is secondary and on-demand.",
719
+ "Stack \u2014 use when content is purely vertical (single column, sequential sections). SplitPane is the right pick only when a persistent side panel is needed at the same hierarchy level as the main content."
720
+ ],
568
721
  example: `import { SplitPane } from "@godxjp/ui/layout";
569
722
 
570
723
  <SplitPane aside={<DetailPanel />} asideWidth="sm">
@@ -585,6 +738,28 @@ function MyShell({ children }: { children: React.ReactNode }) {
585
738
  description: "Array of { label, to? } \u2014 omit `to` on the last (current) segment."
586
739
  }
587
740
  ],
741
+ usage: [
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
+ '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} />}`.",
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
+ "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
+ "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
+ ],
749
+ useCases: [
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.",
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
+ "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
+ "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.",
755
+ "Prefetch pairing: wrap ancestor segments' `to` values with `PrefetchLink` semantics by putting them in `items` \u2014 each non-last item with `to` is already rendered as a router `<Link>`, so hovering naturally prefetches if `PrefetchLink` is used elsewhere on the same route."
756
+ ],
757
+ related: [
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.",
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
+ "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
+ ],
588
763
  example: `import { Breadcrumb } from "@godxjp/ui/layout";
589
764
 
590
765
  <Breadcrumb items={[
@@ -626,6 +801,28 @@ function MyShell({ children }: { children: React.ReactNode }) {
626
801
  description: "Click handler."
627
802
  }
628
803
  ],
804
+ usage: [
805
+ "DO pick the right variant for intent: `default` (primary CTA, one per section), `destructive` (irreversible actions like delete/revoke), `outline` (secondary actions alongside a primary), `secondary` (less prominent actions), `ghost` (toolbar icon-only actions), `link` (inline text-style navigation without an underline by default).",
806
+ "DO use icon-only sizes (`icon`, `icon-xs`, `icon-sm`, `icon-lg`) exclusively for buttons that contain only an SVG \u2014 these sizes set equal width/height. For text+icon buttons use `default|sm|lg|xs` sizes; icons inside are auto-sized to 1rem via `[&_svg:not([class*='size-'])]:size-4`.",
807
+ "DO use `asChild` to render the button as a React Router/Inertia `<Link>` or native `<a>` while keeping all button styling and a11y: `<Button asChild variant=\"outline\"><Link href={route('invoices.show', id)}>\u8A73\u7D30</Link></Button>`. Never wrap a `<button>` around an `<a>` \u2014 that is invalid HTML.",
808
+ "DON'T use raw `<button>` elements anywhere in the UI \u2014 always use this `Button`. The only exception is an `aria-hidden` native control used as an e2e/a11y hook paired with a visible godx-ui control.",
809
+ 'DO set `type="submit"` explicitly on form submit buttons (the default HTML button type inside `<form>` is already `submit`, but being explicit prevents accidental double-submissions when a `type="button"` sibling exists). For cancel/reset actions set `type="button"` to avoid accidental form submission.',
810
+ "DON'T apply raw padding, height, or `rounded-*` overrides to `Button` via `className` \u2014 the size variants encode the full box model. If a custom size is truly needed, use `buttonVariants` from `@godxjp/ui/general` to compose a new cva class rather than fighting the existing ones."
811
+ ],
812
+ useCases: [
813
+ 'Primary form submission in a Dialog or Sheet (e.g. `<Button type="submit" disabled={form.processing}>\u4FDD\u5B58</Button>`) \u2014 the `disabled` prop greys it out and blocks pointer events, preventing double-submit during async operations.',
814
+ 'Destructive confirmation inside a Dialog \u2014 pair `variant="destructive"` Button as the confirm action and `variant="outline"` as Cancel; never use `variant="default"` for a delete action.',
815
+ 'Icon-only toolbar actions in a DataTable column (edit, delete, copy) using `size="icon-sm"` + `variant="ghost"` + a Lucide icon child \u2014 gives equal-width square targets that don\'t distort the row.',
816
+ "Navigation links styled as buttons (e.g. 'New Invoice', 'Back to list') using `asChild` + Inertia `<Link>` \u2014 preserves SPA navigation while using the button's visual treatment.",
817
+ "Async mutation trigger in an accounting workflow (e.g. 'Sync from MF', 'Export CSV') \u2014 disable on processing state; pair with `MutationFeedback` for error/retry UI rather than inline `try/catch` alerts.",
818
+ "Refetch / retry trigger when NOT using TanStack Query \u2014 for manual cache refresh inside a TanStack Query context use `QueryRefetchButton` instead, which owns its own `disabled`/`onClick` lifecycle."
819
+ ],
820
+ related: [
821
+ "DropdownMenu \u2014 when a button needs to reveal a list of actions (e.g. 'Actions \u25BE' in a DataTable row), wrap the Button as a `DropdownMenuTrigger` inside a `DropdownMenu` compound; don't open a Sheet/Dialog just to show a list of options.",
822
+ "QueryRefetchButton \u2014 a pre-wired Button variant from `@godxjp/ui/query` that binds directly to a TanStack Query result (shows spinner, auto-disables while fetching, retries on click). Use it instead of a raw Button whenever the action is a query refetch \u2014 do not pass `onClick`/`disabled` to it manually.",
823
+ "MutationFeedback \u2014 for surfacing mutation errors and a retry action; it renders its own retry Button internally. Do not add a separate Button alongside MutationFeedback for the same mutation.",
824
+ "PrefetchLink \u2014 use when the goal is purely navigation with hover-prefetch (Inertia v3 prefetch); it renders as an `<a>` not a button. Only reach for `Button asChild + Link` when the navigation control must look like a button (primary CTA style)."
825
+ ],
629
826
  example: `import { Button } from "@godxjp/ui/general";
630
827
  import { Trash2 } from "lucide-react";
631
828
 
@@ -847,6 +1044,28 @@ export default function InvoiceList({
847
1044
  description: "Internal padding density (base 16 / tight 12 / cozy 20)."
848
1045
  }
849
1046
  ],
1047
+ usage: [
1048
+ 'DO always wrap body content in <CardContent> \u2014 the bare <Card> div has zero inner padding; content renders flush against card edges without it. Never add className="p-4" directly on <Card> as a substitute.',
1049
+ "DO put titles/descriptions in <CardHeader>/<CardTitle>/<CardDescription>. Use <CardHeader banded> for a visually separated muted-background header band (mirrors <CardFooter separated>). Pair with <CardAction> inside a flex-row CardHeader for header-level action buttons.",
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
+ "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
+ "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."
1054
+ ],
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.',
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
+ "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
+ '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
+ "Media/cover card (e.g. entity profile): <CardCover> first (full-bleed image), then <CardHeader> + <CardContent> below it for structured metadata."
1062
+ ],
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.",
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.",
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
+ ],
850
1069
  example: `import { Card, CardHeader, CardTitle, CardContent } from "@godxjp/ui/data-display";
851
1070
 
852
1071
  <Card accent="success">
@@ -877,6 +1096,28 @@ export default function InvoiceList({
877
1096
  description: "No header above: top padding matches the card shell."
878
1097
  }
879
1098
  ],
1099
+ usage: [
1100
+ "DO: Always wrap body content in <CardContent> \u2014 a bare <Card> has no internal padding, so any child placed directly inside it renders flush against the card edges.",
1101
+ "DO: Use <CardContent flush> for DataTable, Table, or Tabs \u2014 the flush prop removes horizontal padding so the content spans edge-to-edge inside the card border. Never add manual p-0 on the Card itself instead.",
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
+ "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
+ "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."
1106
+ ],
1107
+ useCases: [
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
+ "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
+ "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.",
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
+ "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
+ ],
1115
+ related: [
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.",
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
+ "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
+ ],
880
1121
  example: `import { Card, CardContent, DataTable } from "@godxjp/ui/data-display";
881
1122
 
882
1123
  <Card>
@@ -913,6 +1154,28 @@ export default function InvoiceList({
913
1154
  },
914
1155
  { name: "align", type: '"start" | "end"', description: "Align the metric group." }
915
1156
  ],
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.",
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
+ "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."
1164
+ ],
1165
+ useCases: [
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.",
1167
+ "Accounting summary header: total debits, total credits, and net balance for a journal entry list page, each with a hint showing the date range in scope.",
1168
+ "Coupon/membership admin overview: active members, live coupons, monthly redemptions, and total discount amount \u2014 the canonical example in the catalog.",
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
+ "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."
1172
+ ],
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."
1178
+ ],
916
1179
  example: `import { CardStat } from "@godxjp/ui/data-display";
917
1180
  import { ResponsiveGrid } from "@godxjp/ui/layout";
918
1181
 
@@ -955,6 +1218,28 @@ import { ResponsiveGrid } from "@godxjp/ui/layout";
955
1218
  description: "Override display text (default: i18n of key, or raw status)."
956
1219
  }
957
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
+ ],
958
1243
  example: `import { StatusBadge } from "@godxjp/ui/data-display";
959
1244
 
960
1245
  <>
@@ -978,6 +1263,26 @@ import { ResponsiveGrid } from "@godxjp/ui/layout";
978
1263
  },
979
1264
  { name: "children", type: "ReactNode", required: true, description: "Badge text/content." }
980
1265
  ],
1266
+ usage: [
1267
+ "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.",
1270
+ "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
+ "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."
1273
+ ],
1274
+ useCases: [
1275
+ '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>`.',
1277
+ "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
+ '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."
1280
+ ],
1281
+ 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
+ "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
+ ],
981
1286
  example: `import { Badge } from "@godxjp/ui/data-display";
982
1287
 
983
1288
  <Badge variant="secondary">A/B</Badge>
@@ -1003,6 +1308,28 @@ import { ResponsiveGrid } from "@godxjp/ui/layout";
1003
1308
  description: "KeyValueGrid.Item elements."
1004
1309
  }
1005
1310
  ],
1311
+ 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>.',
1313
+ "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
+ "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."
1318
+ ],
1319
+ useCases: [
1320
+ "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
+ "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
+ "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."
1326
+ ],
1327
+ 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
+ ],
1006
1333
  example: `import { KeyValueGrid } from "@godxjp/ui/data-display";
1007
1334
 
1008
1335
  <KeyValueGrid columns={2}>
@@ -1023,6 +1350,28 @@ import { ResponsiveGrid } from "@godxjp/ui/layout";
1023
1350
  { name: "icon", type: "LucideIcon", description: "Icon above the title." },
1024
1351
  { name: "action", type: "ReactNode", description: "CTA element (e.g. a Button)." }
1025
1352
  ],
1353
+ usage: [
1354
+ "DO always pass `title` \u2014 it is the only required prop and renders an `<h3>`; omitting it causes a blank silent render with no visible error.",
1355
+ "DO use the `icon` prop (a Lucide icon component, not a JSX element) to give visual context \u2014 e.g. `icon={InboxIcon}` for empty inboxes, `icon={SearchIcon}` after a failed search. Pass the component reference, not `<InboxIcon />`.",
1356
+ "DO use `action` (a `ReactNode`, typically a `<Button>`) for actionable zero-states \u2014 e.g. 'Create first invoice' \u2014 so users have a clear next step instead of a dead end.",
1357
+ "DO NOT hand-roll a `data.length === 0 ? <EmptyState /> : <DataTable />` conditional \u2014 `DataTable` already embeds an `EmptyState` in its body when `data` is empty. Use the `empty=` prop on `DataTable` to customise it, not a wrapper conditional.",
1358
+ "DO NOT use EmptyState inside a `DataState` or `InfiniteQueryState` for the loading or error states \u2014 those widgets handle skeleton/error themselves; pass `EmptyState` only to their `empty=` prop for the zero-items case.",
1359
+ "DO NOT add padding directly on `EmptyState` via `className` when placing it inside a `Card` \u2014 wrap it in `<CardContent>` first; EmptyState is a self-contained block with its own internal spacing via `ui-empty-state` styles."
1360
+ ],
1361
+ useCases: [
1362
+ "Zero-row admin list pages (invoices, accounts, transactions) that are NOT backed by a `DataTable` \u2014 e.g. a card-grid or custom list layout where DataTable's built-in empty state doesn't apply.",
1363
+ "Post-filter / post-search zero results \u2014 show `icon={SearchIcon}` + a `description` explaining what was searched and an `action` to clear filters.",
1364
+ "First-run onboarding screens where no data has been created yet \u2014 e.g. 'No entities added yet' with an action button to create the first legal entity.",
1365
+ "Passed as the `empty=` prop inside `DataState` or `InfiniteQueryState` to satisfy the TanStack Query lifecycle widget's zero-items slot without hand-rolling markup.",
1366
+ "Standalone section within a `CardContent` to indicate a sub-section (e.g. attachments, comments, related records) has no entries yet, separate from the page-level list.",
1367
+ "Error-adjacent zero states where the page loaded successfully but the filtered result set is empty \u2014 distinct from an error state handled by `DataState`/`MutationFeedback`."
1368
+ ],
1369
+ related: [
1370
+ "DataTable \u2014 already embeds an EmptyState automatically when `data` is empty; customise via the `empty=` prop. Do NOT wrap DataTable in a `data.length === 0` guard that renders EmptyState separately.",
1371
+ "DataState \u2014 TanStack Query lifecycle widget (`@godxjp/ui/query`). Pass `<EmptyState />` to its `empty=` prop for zero-items; DataState itself covers loading/error \u2014 do not use EmptyState for those states.",
1372
+ "InfiniteQueryState \u2014 same pattern as DataState but for `useInfiniteQuery`; pass EmptyState to `empty=` when the flattened list is empty.",
1373
+ "SkeletonTable \u2014 use for the loading skeleton before data arrives (pass to DataState's `skeleton=` or DataTable's `loading=`). EmptyState is for after data arrives and is empty, not while loading."
1374
+ ],
1026
1375
  example: `import { EmptyState } from "@godxjp/ui/data-display";
1027
1376
 
1028
1377
  <EmptyState title="\u8A72\u5F53\u30C7\u30FC\u30BF\u304C\u3042\u308A\u307E\u305B\u3093" description="\u691C\u7D22\u6761\u4EF6\u3092\u5909\u66F4\u3057\u3066\u304F\u3060\u3055\u3044\u3002" />`,
@@ -1048,6 +1397,28 @@ import { ResponsiveGrid } from "@godxjp/ui/layout";
1048
1397
  description: "Bar colour tone."
1049
1398
  }
1050
1399
  ],
1400
+ usage: [
1401
+ 'DO import from `@godxjp/ui/data-display`, not from a generic UI path: `import { ProgressMeter } from "@godxjp/ui/data-display";`',
1402
+ "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."
1407
+ ],
1408
+ 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%.',
1410
+ '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
+ "Storage or quota indicator in an admin panel \u2014 visualise disk usage, API quota, or seat licence consumption against a fixed limit.",
1412
+ "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."
1415
+ ],
1416
+ 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."
1421
+ ],
1051
1422
  example: `import { ProgressMeter } from "@godxjp/ui/data-display";
1052
1423
 
1053
1424
  <ProgressMeter value={pct} label={pct + "% \u4F7F\u7528\u4E2D"} tone={pct >= 80 ? "warning" : "success"} />`,
@@ -1066,6 +1437,28 @@ import { ResponsiveGrid } from "@godxjp/ui/layout";
1066
1437
  description: "Array of { title, location?, time?, note?, current? }."
1067
1438
  }
1068
1439
  ],
1440
+ usage: [
1441
+ "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
+ "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`.",
1444
+ "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
+ "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
+ "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`."
1447
+ ],
1448
+ useCases: [
1449
+ "Shipment / delivery tracking \u2014 showing a parcel's journey through 'Order placed \u2192 Packed \u2192 In transit \u2192 Delivered' with timestamps and a current-stop indicator.",
1450
+ "Accounting document audit trail \u2014 rendering the lifecycle of an invoice or payment (Draft \u2192 Submitted \u2192 Approved \u2192 Paid) with the current approval stage highlighted.",
1451
+ "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
+ "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
+ "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."
1455
+ ],
1456
+ related: [
1457
+ "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.",
1459
+ "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."
1461
+ ],
1069
1462
  example: `import { Timeline } from "@godxjp/ui/data-display";
1070
1463
 
1071
1464
  <Timeline items={[
@@ -1089,6 +1482,27 @@ import { ResponsiveGrid } from "@godxjp/ui/layout";
1089
1482
  },
1090
1483
  { name: "className", type: "string", description: "Extra classes on the table element." }
1091
1484
  ],
1485
+ usage: [
1486
+ "DO compose all six sub-parts in order: wrap with `<Table>`, then `<TableHeader>` containing `<TableRow><TableHead>\u2026</TableRow>`, then `<TableBody>` containing one or more `<TableRow><TableCell>\u2026` rows. Skipping any layer (e.g. bare `<th>` inside `<Table>`) bypasses the design tokens and hover/border styles.",
1487
+ 'DO use `TableHead` (not `TableCell`) for header cells \u2014 it renders `<th>` with `data-slot="table-head"` and the `--table-row-height` CSS variable for consistent header sizing across the design system. `TableCell` renders `<td>` with `data-slot="table-cell"` and is for body rows only.',
1488
+ 'DO apply numeric alignment via `className` on individual `TableHead`/`TableCell` elements (e.g. `className="text-right"`). There are no built-in alignment props \u2014 all styling goes through Tailwind class overrides.',
1489
+ "DO NOT hand-roll empty-state handling inside a Table composition. When data can be empty, switch to `DataTable` (which has a built-in empty state) or wrap the `<Table>` with a conditional that renders `<EmptyState>` \u2014 never leave a table with only a header and zero rows.",
1490
+ "DO NOT use Table for lists that need sorting, filtering, pagination, or row selection \u2014 those features are only in `DataTable`. Table is intentionally stateless: it owns no TanStack Table instance, no column definitions, and no toolbar.",
1491
+ "DO place `<Table>` inside a `<CardContent flush>` (or `p-0` card) when embedding in a Card, so the built-in `overflow-auto` wrapper sits flush to the card edges. Wrapping with plain `<CardContent>` adds padding that clips the horizontal scroll shadow."
1492
+ ],
1493
+ useCases: [
1494
+ "Invoice line-item breakdowns \u2014 a fixed, read-only list of product/quantity/unit-price/total rows where columns are predefined and will never need sort or filter controls.",
1495
+ "Summary/comparison tables inside a detail panel or Dialog, such as showing two payment plans side-by-side, where the structure is hand-authored and not driven by a data array.",
1496
+ "Print or PDF-export views where a minimal, stateless `<table>` element with predictable markup is required and DataTable's JS-driven features would interfere with server-side rendering or CSS print rules.",
1497
+ "Embedded sub-tables inside a DataTable expanded row (the inner table uses Table primitives because nesting a full DataTable instance inside another is unsupported).",
1498
+ "Static reference tables in documentation, onboarding, or settings pages \u2014 e.g. a permission matrix or feature comparison \u2014 where every cell is literal JSX content, not from a data array."
1499
+ ],
1500
+ related: [
1501
+ "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
+ "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.",
1504
+ "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
+ ],
1092
1506
  example: `import { Table, TableHeader, TableBody, TableRow, TableHead, TableCell } from "@godxjp/ui/data-display";
1093
1507
 
1094
1508
  <Table>
@@ -1119,6 +1533,27 @@ import { ResponsiveGrid } from "@godxjp/ui/layout";
1119
1533
  { name: "empty", type: "ReactNode", description: "Shown when isEmpty(data) is true." },
1120
1534
  { name: "isEmpty", type: "(data) => boolean", description: "Custom empty check." }
1121
1535
  ],
1536
+ usage: [
1537
+ "DO: pass a `UseQueryResult<T>` directly from `useQuery` \u2014 DataState reads `isPending`, `isError`, `isFetching`, `data`, and `error` off it; never destructure those fields manually and branch yourself.",
1538
+ "DO: always provide a `skeleton` \u2014 it renders during both the initial pending phase and during a re-fetch after an error; pass `<SkeletonTable />` for tabular data or `<SkeletonCard />` for card lists \u2014 never `null` or a spinner div.",
1539
+ 'DO: provide `empty` + `isEmpty` together when the data can legitimately return 0 items \u2014 e.g. `isEmpty={(d) => d.items.length === 0}` paired with `empty={<EmptyState title="\u2026" />}`. Omitting `empty` means an empty array still falls through to `children`, silently rendering a blank table.',
1540
+ "DON'T: wrap DataState in your own conditional \u2014 e.g. `{query.isSuccess && <DataState \u2026>}`. DataState IS the conditional; the outer guard is redundant and breaks the retry/refetch skeleton.",
1541
+ "DON'T: use DataState for `useInfiniteQuery` results. The `query` prop type is `UseQueryResult<T>`, not `UseInfiniteQueryResult`. Use `InfiniteQueryState` (from `@godxjp/ui/query`) instead, which accepts `flatten` and renders a load-more footer.",
1542
+ "DO: supply `errorRenderer` only when the default `AlertQueryError` + retry button is not enough \u2014 e.g. a full-page error boundary with navigation. Otherwise rely on `showRetry` (default `true`) and the built-in `AlertQueryError`, and override `onRetry` only if `query.refetch()` is not the right action."
1543
+ ],
1544
+ useCases: [
1545
+ "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
+ "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
+ "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.",
1549
+ "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
+ ],
1551
+ related: [
1552
+ "InfiniteQueryState \u2014 use instead of DataState when the query is `useInfiniteQuery`; it accepts a `flatten` function to reduce pages and adds a load-more footer. DataState cannot accept `UseInfiniteQueryResult`.",
1553
+ "SkeletonTable / SkeletonCard \u2014 pass as the `skeleton` prop of DataState; they are not standalone replacements for DataState, only the loading slot inside it.",
1554
+ "EmptyState \u2014 pass as the `empty` prop of DataState alongside a matching `isEmpty` predicate; do not hand-roll an empty-check outside DataState by inspecting `query.data` yourself.",
1555
+ "MutationFeedback \u2014 sibling widget for mutation (not query) lifecycle; use it below a form submit button to surface `useMutation` errors, not DataState which only handles `useQuery`."
1556
+ ],
1122
1557
  example: `import { DataState } from "@godxjp/ui/query";
1123
1558
 
1124
1559
  <DataState query={membersQuery} skeleton={<SkeletonTable />} isEmpty={(d) => d.items.length === 0} empty={<EmptyState title="\u4F1A\u54E1\u306A\u3057" />}>
@@ -1157,6 +1592,28 @@ import { ResponsiveGrid } from "@godxjp/ui/layout";
1157
1592
  description: "Render with flat data + { fetchNextPage, hasNextPage, isFetchingNextPage }."
1158
1593
  }
1159
1594
  ],
1595
+ usage: [
1596
+ "DO: Import from `@godxjp/ui/query` (not `@godxjp/ui`). Use the bundled `flattenItemPages` helper for any API that returns `{ items: T[] }` pages \u2014 it handles `undefined` data safely. Custom page shapes require a custom `flatten` function.",
1597
+ "DO: Always pass `skeleton` (e.g. `<SkeletonTable />` or `<SkeletonCard />`). It shows on initial `isPending`, on refetch-after-error, and whenever `data` is absent. Never show a blank area while loading.",
1598
+ "DO: Pass `empty` (an `<EmptyState>` node) to handle the zero-results case \u2014 without it the children render-prop is called with an empty array and you get a silent blank screen. Provide a custom `isEmpty` only when `TFlat` is not an array.",
1599
+ "DON'T: Hand-roll a load-more button. The component renders a default centered outline Button when `hasNextPage` is true. Override only via `loadMore` (custom node) or `showLoadMore={false}` (hide entirely). Never call `query.fetchNextPage()` outside the component for pagination.",
1600
+ "DON'T: Use `InfiniteQueryState` for a `useQuery` result \u2014 it expects `UseInfiniteQueryResult` shape (`pages`, `hasNextPage`, `fetchNextPage`, `isFetchingNextPage`). For regular `useQuery` use `DataState` instead.",
1601
+ "DON'T: Confuse the two generics: `TPage` is the raw page shape from the API, `TFlat` is what `flatten` returns (usually `TItem[]`). The `children` render-prop receives `TFlat`, not `TPage`. Pass `isEmpty` if `TFlat` is not a plain array so empty detection works correctly."
1602
+ ],
1603
+ useCases: [
1604
+ "Activity / audit-log feed that accumulates pages as the user scrolls down or clicks 'Load more' \u2014 the default footer button handles `fetchNextPage` automatically.",
1605
+ "Invoice or transaction list with cursor-based pagination where total count is unknown and pages are appended rather than replaced (replacing pages is DataTable's job).",
1606
+ "Notification inbox, comment thread, or journal entry list where new items are appended at the bottom and the user never pages backwards.",
1607
+ "Search results with a 'Show more' button rather than numbered pages \u2014 pass `showLoadMore={true}` (default) and hide the button once `hasNextPage` is false without any extra state.",
1608
+ "Admin dashboard 'recent events' widget backed by `useInfiniteQuery` \u2014 use `SkeletonTable` as `skeleton` and `<EmptyState title='No events yet' />` as `empty` so every state is handled.",
1609
+ "Infinite-scroll implementation: receive the `helpers` argument in `children` (`{ fetchNextPage, hasNextPage, isFetchingNextPage }`) to wire a scroll sentinel (Intersection Observer) instead of the built-in button, while still benefiting from error/skeleton/empty lifecycle handling."
1610
+ ],
1611
+ related: [
1612
+ "DataState \u2014 use instead when the query is a plain `useQuery` (not infinite). Identical lifecycle surface (skeleton/empty/error/children) but expects a single page of data, not accumulated pages. Pick DataState for any paginated table where only one page is visible at a time.",
1613
+ "DataTable \u2014 use for tabular data with server-side pagination where pages are swapped, not appended. DataTable manages its own pagination UI (cursor buttons); InfiniteQueryState is for append-only / infinite-scroll patterns.",
1614
+ "SkeletonTable / SkeletonCard \u2014 pass as the `skeleton` prop to InfiniteQueryState; do not render them manually alongside InfiniteQueryState since the component controls when skeleton is visible.",
1615
+ "QueryRefetchButton \u2014 companion component for the page header refresh action wired to `query.refetch()`. Use alongside InfiniteQueryState when you want an explicit refresh control in addition to the built-in load-more footer."
1616
+ ],
1160
1617
  example: `import { InfiniteQueryState, flattenItemPages } from "@godxjp/ui/query";
1161
1618
 
1162
1619
  <InfiniteQueryState query={q} skeleton={<SkeletonRows />} flatten={flattenItemPages} isEmpty={(it) => it.length === 0}>
@@ -1178,6 +1635,28 @@ import { ResponsiveGrid } from "@godxjp/ui/layout";
1178
1635
  },
1179
1636
  { name: "onRetry", type: "() => void", description: "Retry handler." }
1180
1637
  ],
1638
+ usage: [
1639
+ "DO: Import exclusively from `@godxjp/ui/query` \u2014 it is NOT exported from the main `@godxjp/ui` barrel.",
1640
+ "DO: Pass the full `useMutation` result object as `mutation`; the component reads `.isError`, `.error`, and `.isPending` \u2014 only those three fields are consumed, so a plain object mock works in tests.",
1641
+ "DO: Supply `onRetry` when the user can meaningfully re-trigger the mutation (e.g. re-submit a form, re-run a simulation). The Retry button is rendered by `AlertQueryError` only when `onRetry` is provided AND `showRetry` is not `false`. Set `showRetry={false}` to hide the button when retry is semantically wrong (e.g. a destructive delete that must not auto-repeat).",
1642
+ "DO: Use the `pending` prop to render an inline loading slot while `mutation.isPending` is true \u2014 it replaces the error area with arbitrary JSX (spinner, skeleton row) so the layout does not shift when the mutation transitions idle \u2192 pending \u2192 error.",
1643
+ "DON'T: Render `MutationFeedback` for query (fetch) errors \u2014 use `DataState` for those. `MutationFeedback` is scoped to write operations (`useMutation`), not read operations (`useQuery`).",
1644
+ "DON'T: Hand-roll an inline error alert for mutation failures \u2014 this component already wraps `Alert variant='destructive'` with i18n title, `humanError()` message formatting, and an accessible Retry button. Duplicating that logic breaks consistency and bypasses localisation."
1645
+ ],
1646
+ useCases: [
1647
+ "Form save failures \u2014 place `<MutationFeedback mutation={saveMutation} onRetry={saveMutation.mutate} />` directly below a submit button so the error appears inline next to the control that triggered it, keeping the user in context without a page-level toast.",
1648
+ "Blocking simulator / calculation runs \u2014 when a long-running mutation (e.g. tax computation, invoice generation) fails and the page must stay on the form until the user corrects and retries, use `MutationFeedback` with `pending={<SkeletonTable />}` to show a skeleton while running and flip to an error banner on failure.",
1649
+ "Multi-step wizard step submissions \u2014 show per-step mutation errors inline inside each `<Steps>` panel so the user sees exactly which step failed without scrolling to a page-level alert.",
1650
+ "Admin destructive actions (delete, void, archive) \u2014 pass `showRetry={false}` so no Retry button appears after a failed irreversible operation, preventing accidental double-execution.",
1651
+ "Accounting record mutations (journal entry save, invoice approval) \u2014 inline error keeps audit-sensitive feedback close to the triggering form rather than relying on a transient toast that the user might miss.",
1652
+ "Background job kicks that surface an immediate API error \u2014 when a mutation POSTs to a job-dispatch endpoint and the server rejects synchronously, `MutationFeedback` surfaces the error inline with a retry CTA without requiring a separate alert component."
1653
+ ],
1654
+ related: [
1655
+ "Toaster \u2014 use `toast.error()` (sonner) via `Toaster` for transient, non-blocking save confirmations where the user does not need to retry in-place; prefer `MutationFeedback` when the error is blocking and must stay visible until the user acts.",
1656
+ "DataState \u2014 use `DataState` for the full TanStack Query read lifecycle (pending skeleton / error / empty / data); `MutationFeedback` is the write-side counterpart for `useMutation` errors only.",
1657
+ "Alert \u2014 the raw destructive alert primitive; use `Alert variant='destructive'` directly only when the error is not from a `useMutation` result and you need full compositional control; `MutationFeedback` is the correct abstraction when you have a mutation object.",
1658
+ "QueryRefetchButton \u2014 for surfacing a manual refetch action on a `useQuery` result in a page header; `MutationFeedback` handles the equivalent retry on the mutation side and must not be conflated with query refetch patterns."
1659
+ ],
1181
1660
  example: `import { MutationFeedback } from "@godxjp/ui/query";
1182
1661
 
1183
1662
  <MutationFeedback mutation={saveMutation} />`,
@@ -1221,6 +1700,28 @@ import { ResponsiveGrid } from "@godxjp/ui/layout";
1221
1700
  description: "The single control to render."
1222
1701
  }
1223
1702
  ],
1703
+ usage: [
1704
+ "DO pass the same string to both `id` on `<FormField>` and `id` on the child control \u2014 the component wires `<Label htmlFor={id}>`, and builds `{id}-helper` / `{id}-error` ids for `aria-describedby`. If the ids diverge the label click and screen-reader announcements break.",
1705
+ "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
+ "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
+ "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.",
1709
+ "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
+ ],
1711
+ useCases: [
1712
+ "Labelling a text `Input` or `Textarea` in an invoice-entry form, showing a red asterisk for required fields and surfacing server validation errors returned from a Laravel FormRequest.",
1713
+ "Wrapping a `Select` or `DatePicker` inside a multi-field filter panel where each control needs a visible label, helper hint (e.g. 'YYYY/MM/DD'), and inline error state.",
1714
+ "Adding a `labelAddon` tooltip button next to a 'Tax rate' label in an accounting form to explain when different rates apply, without breaking the label\u2013control association.",
1715
+ "Enclosing a `DateRangePicker` or `TimePicker` in an admin settings page where the field needs a label, a muted hint ('Inclusive of start and end date'), and conditional error display.",
1716
+ "Wrapping a `SearchSelect` or `Autocomplete` control for vendor/account lookup in a journal-entry form where the `id` must be kept consistent for programmatic focus management.",
1717
+ "Providing structured error feedback for a `Cascader` or `TreeSelect` in a multi-level category assignment screen, replacing ad-hoc error rendering with the standardised `role='alert'` pattern."
1718
+ ],
1719
+ related: [
1720
+ "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.",
1722
+ "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
+ "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
+ ],
1224
1725
  example: `import { FormField, Input } from "@godxjp/ui/data-entry";
1225
1726
 
1226
1727
  <FormField id="coupon-name" label="\u30AF\u30FC\u30DD\u30F3\u540D" required error={errors.name} helper="\u6700\u592750\u6587\u5B57">
@@ -1244,6 +1745,27 @@ import { ResponsiveGrid } from "@godxjp/ui/layout";
1244
1745
  description: "Native change handler."
1245
1746
  }
1246
1747
  ],
1748
+ usage: [
1749
+ "DO always wrap Input in FormField when the field needs a label, helper text, or validation error \u2014 FormField injects aria-describedby and aria-invalid onto Input automatically; never wire these attributes by hand.",
1750
+ "DO match the `id` prop on Input to the `id` prop on its parent FormField so that `htmlFor` linkage and the generated helper/error ids are consistent.",
1751
+ "DO use Input in controlled mode (`value` + `onChange`) for forms driven by Inertia's `useForm` or React state; uncontrolled usage (no `value`) is only acceptable for fire-and-forget inline edits where form state is not needed.",
1752
+ "DON'T use a raw `<input>` element \u2014 Input adds the full token-based styling (border-input, focus ring, disabled/invalid states, file-slot styling) and the `data-slot='input'` marker that FormField relies on to inject aria attributes.",
1753
+ "DON'T hand-roll an error border or red ring with className \u2014 Input reads `aria-invalid` (set by FormField) and applies `border-destructive` + `ring-destructive/20` automatically; adding manual destructive classes will conflict.",
1754
+ "DON'T use Input for multi-line text \u2014 use Textarea; DON'T use it for filtered/debounced search \u2014 use SearchInput which fires `onSearch` after a debounce and includes a clear button."
1755
+ ],
1756
+ useCases: [
1757
+ "Single-line text fields in create/edit forms \u2014 invoice reference numbers, company names, contact emails, coupon codes, amounts typed as text (pair with `type='number'` for numeric entry).",
1758
+ "Inline editable cells or quick-edit dialogs where a single short value needs to be changed (e.g. editing a journal entry memo or an account code) and full Select/DatePicker overhead is unnecessary.",
1759
+ "File upload trigger when wrapped with `type='file'` \u2014 the file-slot classes style the native file button consistently without any extra wrapper.",
1760
+ "Password entry fields (`type='password'`) in auth or settings screens, where the styled focus ring and disabled-state opacity are needed without building a custom control.",
1761
+ "Numeric/currency input in accounting forms (`type='number'`, `inputMode='decimal'`) for quantities, exchange rates, or tax amounts where a free-form numeric entry is required rather than a slider or stepper."
1762
+ ],
1763
+ related: [
1764
+ "SearchInput \u2014 use instead of Input when the value drives a live filter or search query; SearchInput debounces internally, fires `onSearch` (not `onChange`), and provides a built-in clear button. Never put debounce logic on top of a plain Input.",
1765
+ "Textarea \u2014 use instead of Input for multi-line text (notes, descriptions, memo fields). Input is strictly single-line.",
1766
+ "FormField \u2014 always compose Input inside FormField when the field needs a visible label, helper hint, or validation error message; FormField handles all a11y wiring so Input stays a pure unstyled-but-styled primitive.",
1767
+ "Select \u2014 use instead of Input when the value must come from a fixed or async option list; never render a plain Input and parse free text when the set of valid values is enumerable."
1768
+ ],
1247
1769
  example: `import { Input } from "@godxjp/ui/data-entry";
1248
1770
 
1249
1771
  <Input id="qty" type="number" placeholder="\u4F8B: 500" value={value} onChange={(e) => setValue(e.target.value)} />`,
@@ -1276,6 +1798,27 @@ import { ResponsiveGrid } from "@godxjp/ui/layout";
1276
1798
  description: "Debounce delay (ms)."
1277
1799
  }
1278
1800
  ],
1801
+ usage: [
1802
+ "DO: listen to `onSearch`, not `onChange`. The component debounces internally (default 250 ms) and fires `onSearch(q)` after the delay \u2014 never wire your filter logic to `onChange` on SearchInput because it does not expose one.",
1803
+ "DO: choose controlled vs uncontrolled deliberately. Pass `value` + `onSearch` together for controlled mode (e.g. when search state lives in a URL param or shared parent). For local-only ephemeral search pass only `defaultValue` + `onSearch` \u2014 omitting `value` puts the component in uncontrolled mode.",
1804
+ "DO: supply an `ariaLabel` (or visible `label`) when no adjacent label exists. Without either prop, SearchInput falls back to the i18n key `common.search` rendered as a visually-hidden `<Label>` \u2014 still accessible, but providing a context-specific string (e.g. `ariaLabel='\u8ACB\u6C42\u66F8\u3092\u691C\u7D22'`) is more descriptive for screen readers.",
1805
+ "DON'T: use SearchInput inside a `<form>` expecting native form submission. The component has no `name` prop and does not emit a form field value \u2014 it is a filter-trigger widget. For a form search field, use a plain `Input` inside `FormField`.",
1806
+ "DON'T: hand-roll a debounced input when you need a search box. SearchInput ships the debounce, clear button (\xD7), search icon, and accessible label \u2014 recreating these with a raw `<Input>` adds code and misses the UX contract.",
1807
+ "DON'T: place SearchInput inside a `FilterGroup` wrapper \u2014 `FilterGroup` is for Select/DatePicker controls with a label chip. SearchInput goes directly as a child of `FilterBar` (or standalone above a table), not wrapped in `FilterGroup`."
1808
+ ],
1809
+ useCases: [
1810
+ "List-page filter bar: placed as the first child of `FilterBar` (before any `FilterGroup` children) to drive text-based filtering of a `DataTable`. The `onSearch` callback updates a query param or state variable that the table's data fetch reads.",
1811
+ "Inline client-side search over a small in-memory list (e.g. a sidebar nav list, a transfer panel, a settings category list) where results narrow immediately as the user types without a server round-trip \u2014 use uncontrolled mode (`defaultValue`) so no state is needed in the parent.",
1812
+ "URL-synced search: controlled mode where `value` comes from `useSearchParams()` and `onSearch` pushes to the URL, enabling deep-linkable, bookmarkable filtered views on invoice/transaction/customer index pages.",
1813
+ "Panel or dialog search: filtering a long dropdown list, a tree, or a multi-item selection panel that does not use the built-in `Command` palette \u2014 SearchInput provides the search box while the parent renders the filtered result set.",
1814
+ "Toolbar search on a data-heavy accounting page (e.g. journal-entry search, partner lookup in a subledger view) where the 250 ms debounce prevents a flood of API calls on every keystroke without requiring the developer to implement debounce logic."
1815
+ ],
1816
+ related: [
1817
+ "Input \u2014 use `Input` (inside `FormField`) when the search field is part of a submitted form and needs a `name` attribute, or when you need full `onChange` control without any debounce or clear button. SearchInput is the right pick when the field only triggers filtering, not form submission.",
1818
+ "FilterBar \u2014 SearchInput is almost always placed as a direct child of `FilterBar`, which provides the surrounding strip, clear-all button, and active-filter state. Do not use SearchInput as a standalone header widget when a full filter strip (with selects etc.) already exists \u2014 compose them together.",
1819
+ "Command \u2014 use `Command` + `CommandInput` when you need a keyboard-navigable command palette or combobox list with grouped items and keyboard selection. `Command` is only meaningful when paired with `CommandList`; SearchInput is the right pick for a plain filter box with no item-selection behavior.",
1820
+ "Select (with showSearch) \u2014 when users must pick a value from a list AND search to narrow it, use `<Select options={...} showSearch>` (which has its own built-in search input). SearchInput is for filtering an external data set, not for value selection from an option list."
1821
+ ],
1279
1822
  example: `import { SearchInput } from "@godxjp/ui/data-entry";
1280
1823
 
1281
1824
  <SearchInput placeholder="\u30AF\u30FC\u30DD\u30F3\u540D\u30FBID\u3067\u691C\u7D22" value={search} onSearch={setSearch} />`,
@@ -1524,6 +2067,27 @@ export function PrioritySelect({ value, onValueChange }) {
1524
2067
  description: "Disable the toggle."
1525
2068
  }
1526
2069
  ],
2070
+ usage: [
2071
+ "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>.",
2073
+ "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.",
2076
+ "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
+ ],
2078
+ useCases: [
2079
+ "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).",
2082
+ "Filter toolbar toggle (e.g., 'Show archived') rendered inline next to other filter controls, using size='sm' for density parity with adjacent inputs.",
2083
+ "Preview/demo UI where the switch controls a local display state (dark-mode preview, feature flag preview) with no server persistence."
2084
+ ],
2085
+ 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.",
2087
+ "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
+ "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
+ "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."
2090
+ ],
1527
2091
  example: `import { Switch, Label } from "@godxjp/ui/data-entry";
1528
2092
 
1529
2093
  <div className="flex items-center gap-2">
@@ -1547,6 +2111,27 @@ export function PrioritySelect({ value, onValueChange }) {
1547
2111
  description: "Change handler."
1548
2112
  }
1549
2113
  ],
2114
+ usage: [
2115
+ "DO always wrap Textarea in FormField when it appears in a form \u2014 FormField clones aria-describedby, aria-required, and aria-invalid onto the child, giving error/helper announcements and screen-reader labelling for free. Pass matching id props to both.",
2116
+ "DO use the godx-ui Textarea (`import { Textarea } from '@godxjp/ui/data-entry'`) \u2014 never a raw `<textarea>`. The component applies the `ui-control-multiline` token class that picks up density, focus-ring, and border tokens from the design system.",
2117
+ "DO control the value with `value` + `onChange` in React-managed forms (e.g. Inertia `useForm`). Textarea is a plain `forwardRef` over the native element so it accepts all standard `HTMLTextAreaElement` attributes \u2014 `rows`, `maxLength`, `disabled`, `name`, `placeholder`, `readOnly` all pass through directly.",
2118
+ "DO pass `name` when the textarea sits inside an HTML `<form>` for native form submission or when Inertia's `useForm` destructures field values by key \u2014 the `name` attribute maps the value into the form data bag.",
2119
+ "DON'T apply manual height or padding classes directly on Textarea to simulate a taller field \u2014 use the `rows` prop instead. The component does not auto-resize; if you need auto-grow behaviour you must wire a custom `onInput` handler that adjusts `style.height` explicitly.",
2120
+ "DON'T hand-roll label + error markup next to a bare Textarea. Always use FormField: it injects aria-invalid (red ring on the control), renders a `role='alert'` error paragraph, and links them via aria-describedby automatically."
2121
+ ],
2122
+ useCases: [
2123
+ "Free-text memo or note fields on an invoice or transaction detail form \u2014 e.g. '\u5099\u8003 / Notes' that can hold multi-line internal comments alongside structured Invoice fields.",
2124
+ "Rejection reason or approval comment in an admin workflow dialog \u2014 a short-to-medium text block a reviewer types before confirming an action in a Dialog or Sheet.",
2125
+ "Address or multi-line description input on a vendor / partner entity form where a single-line Input would be too restrictive.",
2126
+ "Email body composer or message template editor in a lightweight CRM or notification settings screen where rich text is not required.",
2127
+ "Audit log annotation \u2014 allowing an accountant to attach a plain-text explanation to a manual journal entry or adjustment record."
2128
+ ],
2129
+ related: [
2130
+ "Input \u2014 use Input for single-line values (names, amounts, codes). Use Textarea only when the expected value spans multiple lines or could be longer than ~80 characters.",
2131
+ "FormField \u2014 always the parent wrapper for Textarea in forms; provides label, helper text, error message, and injects all required aria attributes onto the Textarea child automatically.",
2132
+ "Select \u2014 when the user must pick from a finite set of multi-line-looking options (e.g. template choices) use Select, not a Textarea presenting options as free text.",
2133
+ "Autocomplete or SearchSelect \u2014 if the multi-line field is actually a tag/token input or a constrained lookup, prefer Autocomplete or SearchSelect over a Textarea that the user types into freely."
2134
+ ],
1550
2135
  example: `import { Textarea } from "@godxjp/ui/data-entry";
1551
2136
 
1552
2137
  <Textarea id="notes" rows={4} placeholder="\u81EA\u7531\u8A18\u8FF0" value={notes} onChange={(e) => setNotes(e.target.value)} />`,
@@ -1561,6 +2146,28 @@ export function PrioritySelect({ value, onValueChange }) {
1561
2146
  { name: "htmlFor", type: "string", description: "Id of the associated control." },
1562
2147
  { name: "children", type: "ReactNode", description: "Label content." }
1563
2148
  ],
2149
+ usage: [
2150
+ "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
+ '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
+ "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.",
2155
+ "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
+ ],
2157
+ useCases: [
2158
+ "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.",
2160
+ "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
+ "Labelling a Textarea in a free-text form field when FormField's helper/error slots aren't needed, keeping the markup minimal.",
2162
+ "Rendering an accessible label inside a table row where a FormField's block layout would break the inline/grid structure.",
2163
+ "Adding a label to a DatePicker, TimePicker, or ColorPicker inside a simple layout that doesn't need the full FormField wrapper."
2164
+ ],
2165
+ related: [
2166
+ "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
+ "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.",
2169
+ "Checkbox \u2014 the most common bare-Label partner; pair with Label via shared useId() id/htmlFor when ChoiceField's layout is too heavy."
2170
+ ],
1564
2171
  example: `import { Label } from "@godxjp/ui/data-entry";
1565
2172
 
1566
2173
  <Label htmlFor="stackable">\u4F75\u7528\u3092\u8A31\u53EF</Label>`,
@@ -1584,6 +2191,28 @@ export function PrioritySelect({ value, onValueChange }) {
1584
2191
  },
1585
2192
  { name: "id", type: "string", description: "Links to a <Label htmlFor>." }
1586
2193
  ],
2194
+ usage: [
2195
+ "DO pair every standalone Checkbox with a `<Label htmlFor={id}>` \u2014 the id prop on Checkbox must match the htmlFor on Label so screen readers announce the label on focus. Without this pairing the control is inaccessible.",
2196
+ "DO use the controlled pattern (`checked` + `onCheckedChange`) for any form-bound checkbox. `onCheckedChange` receives `boolean | 'indeterminate'` \u2014 always coerce with `!!v` or an explicit guard before storing in state.",
2197
+ "DO use `Checkbox.Group` (alias for CheckboxGroup) with the `options` prop when you have \u22652 choices from an array \u2014 it renders each item inside a `ChoiceField` (label + optional description), generates stable ids automatically, and manages the `string[]` value array. NEVER hand-roll a loop of bare `<Checkbox>` elements for a multi-select list.",
2198
+ "DO pass `name` on `Checkbox.Group` (not on individual checkboxes) when the group must submit as form fields \u2014 the group propagates the name to each internal checkbox so the browser serialises all checked values under that key.",
2199
+ "DON'T use `checked='indeterminate'` on `Checkbox.Group` children \u2014 indeterminate is only meaningful on a parent 'select-all' control you wire manually; the group itself does not auto-compute it.",
2200
+ "DON'T wrap a standalone Checkbox in `ChoiceField` manually \u2014 `ChoiceField` is the internal composition primitive that `Checkbox.Group` uses. For a single boolean with a label, use `<div className='flex items-center gap-2'><Checkbox id='x' .../><Label htmlFor='x'>...</Label></div>` as shown in the catalog example; for a full labelled-checkbox with description, use `ChoiceField` directly only if you need a one-off item outside a group."
2201
+ ],
2202
+ useCases: [
2203
+ "A 'Select all' / bulk-action row above a DataTable \u2014 standalone Checkbox with `checked='indeterminate'` when some (not all) rows are selected, toggling between all-selected and none-selected.",
2204
+ "A multi-step filter panel (e.g. filter invoices by payment status: Paid, Unpaid, Overdue) \u2014 `Checkbox.Group` with `options` prop and `orientation='vertical'`, controlled value wired to FilterBar state.",
2205
+ "Confirmation or consent acknowledgement before a destructive action in a Dialog \u2014 standalone Checkbox with controlled state used to enable/disable the confirm Button.",
2206
+ "Settings panel where each feature flag is a boolean toggle with a description line \u2014 `Checkbox.Group` with options carrying a `description` field so each row renders label + subtext via ChoiceField.",
2207
+ "Bulk-edit form row in an accounting ledger (e.g. 'Apply to all selected entries') \u2014 standalone Checkbox with name + value inside a `<form>` for native HTML form submission.",
2208
+ "Onboarding checklist (e.g. 'I have read the terms', 'I consent to data processing') with multiple distinct items whose values are independent \u2014 two separate standalone Checkboxes, each with their own id/state, not a Checkbox.Group (since each item maps to a different boolean field)."
2209
+ ],
2210
+ related: [
2211
+ "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.",
2213
+ "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
+ "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
+ ],
1587
2216
  example: `import { Checkbox, Label } from "@godxjp/ui/data-entry";
1588
2217
 
1589
2218
  <div className="flex items-center gap-2">
@@ -1616,6 +2245,28 @@ export function PrioritySelect({ value, onValueChange }) {
1616
2245
  description: "Layout direction."
1617
2246
  }
1618
2247
  ],
2248
+ usage: [
2249
+ "DO use the `options` prop for the data-driven path: pass `{ label, value, disabled?, description? }[]` and RadioGroup renders every option as a correctly labelled ChoiceField automatically \u2014 never hand-roll Radio.Item + Label pairs in a loop yourself.",
2250
+ "DO provide `name` whenever the group lives inside an HTML form: Radix renders a hidden `<input name={name}>` carrying the selected string value, making the field natively form-submittable without a separate hidden input.",
2251
+ "DO use controlled mode (`value` + `onValueChange`) for any form managed by useForm or a state manager. Use `defaultValue` only for truly uncontrolled UI where you never need to read the value in code.",
2252
+ "DO NOT reach for children / manual composition unless the options list is dynamic-JSX (e.g. each item needs a custom rendered label with an icon). When you do compose children manually, wrap each Radio.Item in a ChoiceField \u2014 rendering a bare Radio.Item without ChoiceField skips the label and breaks a11y.",
2253
+ "DO NOT use RadioGroup when the user may select zero or multiple items \u2014 that is CheckboxGroup. RadioGroup enforces exactly one selection at all times (or none before first interaction when uncontrolled).",
2254
+ "A11y: the Radix root emits `role=radiogroup`; each item gets `role=radio` and is keyboard-navigable with arrow keys. Never suppress `name` on the Root when inside a form \u2014 without it the hidden input is unnamed and won't submit."
2255
+ ],
2256
+ useCases: [
2257
+ "Selecting a single billing cycle (monthly / quarterly / annual) in an invoice or subscription settings form where all 2-4 options must be visible at once.",
2258
+ "Choosing a report output format (PDF / CSV / Excel) before triggering an async export job \u2014 keeps all options scannable without opening a dropdown.",
2259
+ "Picking a transaction type (income / expense / transfer) on an accounting entry form where the choice changes which subsequent fields are shown.",
2260
+ "Selecting a sync trigger mode (first purchase / birthday / manual) in a campaign or automation settings panel \u2014 matches the catalog example exactly.",
2261
+ "Filtering a compact inline control (horizontal orientation) such as date granularity (day / week / month) inside a dashboard filter bar where a full Select dropdown would be over-engineered.",
2262
+ "Choosing an approval status (pending / approved / rejected) on an admin detail sheet where all states must be visible so reviewers can compare them without interaction."
2263
+ ],
2264
+ related: [
2265
+ "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
+ "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.",
2268
+ "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
+ ],
1619
2270
  example: `import { RadioGroup } from "@godxjp/ui/data-entry";
1620
2271
 
1621
2272
  <RadioGroup value={trigger} onValueChange={setTrigger} orientation="horizontal" options={[
@@ -1748,6 +2399,27 @@ export function InvoiceDueDateField() {
1748
2399
  description: "form = Radix Dialog (\xD7 close); confirm = AlertDialog (no \xD7)."
1749
2400
  }
1750
2401
  ],
2402
+ usage: [
2403
+ 'DO use `mode="confirm"` (alertdialog role, no \xD7 button) for all destructive or irreversible actions \u2014 deletes, voids, bulk-overwrite \u2014 and `mode="form"` (default, dialog role, \xD7 button shown) for all data-entry or wizard steps. Never toggle these manually or add a custom close icon in confirm mode.',
2404
+ "DO use `Dialog.Confirm` (or `DialogConfirm`) as the pre-built preset for confirm flows: pass `open`, `onOpenChange`, `title`, `description`, `onConfirm`, `pending`, and optionally `confirmPhrase` for type-to-confirm friction (GitHub/Stripe style). This eliminates boilerplate for the most common confirm pattern.",
2405
+ "DO always control open state via `open` + `onOpenChange`. Dialog has no uncontrolled shortcut \u2014 omitting `open` means the trigger alone drives state, which is fine for simple trigger-only cases, but any async submission flow must use controlled state so you can hold the dialog open while `pending=true` and close it only on success.",
2406
+ "DO include `DialogHeader` with `DialogTitle` (and optionally `DialogDescription`) inside every `DialogContent`. Radix requires an accessible title for screen readers; omitting it triggers a console warning and breaks a11y. In confirm mode `DialogTitle` maps to `AlertDialogPrimitive.Title` automatically.",
2407
+ "DON'T put `DialogContent` outside a `Dialog` (or `DialogRoot`) \u2014 the `mode` context won't be set and the sub-parts will render as the wrong Radix primitive (dialog vs alertdialog), breaking keyboard focus trap and role semantics.",
2408
+ 'DON\'T add a manual \xD7 close button in confirm mode \u2014 `DialogContent` only renders the `X` button when `mode="form"` (`showCloseButton` defaults to `true`). For confirm flows, pass `showCloseButton={false}` or use `mode="confirm"`, which suppresses it automatically. Pressing Escape still closes both modes via Radix defaults.'
2409
+ ],
2410
+ useCases: [
2411
+ 'Inline form dialog \u2014 create or edit a record (invoice line, supplier, coupon) without navigating away. Use `mode="form"`, place `FormField`/`Input`/`Select` inside `DialogContent`, and wire the submit button to your mutation; hold `open` while `pending` to prevent double-submit.',
2412
+ "Destructive confirm \u2014 delete a journal entry, void an invoice, or remove a user. Use `Dialog.Confirm` with `variant=\"destructive\"` and optionally `confirmPhrase` (the record name or 'DELETE') to add type-to-confirm friction for high-stakes ops.",
2413
+ "Wizard / multi-step flow \u2014 step through entity setup (legal entity \u2192 fiscal year \u2192 opening balances) using a single Dialog whose `DialogContent` conditionally renders different step panels. Control which step is shown in local state; use `keepOpenOnConfirm` if the confirm action advances steps rather than closing.",
2414
+ 'Read-only detail popup \u2014 show a full transaction audit trail, attachment preview, or approval history in a modal without leaving the list page. Use `mode="form"` with no `DialogFooter` action buttons, just a close trigger.',
2415
+ "Batch action confirmation \u2014 confirm bulk-approve or bulk-archive of selected rows. Use `Dialog.Confirm` wired to a selection count message; pass `pending` from the mutation to disable the confirm button while the batch runs."
2416
+ ],
2417
+ related: [
2418
+ "Sheet \u2014 use Sheet instead of Dialog when the content is a slide-in panel (filters, detail sidebar, settings drawer). Sheet uses `side` prop and is better suited for wide filter forms or contextual detail panels that don't demand full focus interruption.",
2419
+ "Alert \u2014 use Alert for inline, non-modal status messages (validation errors, success banners on the page). Dialog is modal and focus-trapping; Alert is inline and never blocks interaction.",
2420
+ "Popover \u2014 use Popover for lightweight non-modal overlays anchored to a trigger (quick-edit a single field, tooltip-style confirmation for low-stakes actions). Dialog is full-modal; Popover stays near its trigger and doesn't dim the page.",
2421
+ "MutationFeedback \u2014 use MutationFeedback for toast/inline feedback after the Dialog closes, not inside it. Putting a success toast inside a Dialog that is about to unmount causes it to disappear immediately; emit the feedback after `onOpenChange(false)` resolves."
2422
+ ],
1751
2423
  example: `import { useState } from "react";
1752
2424
  import { Dialog, DialogTrigger, DialogContent, DialogHeader, DialogTitle, DialogDescription, DialogFooter } from "@godxjp/ui/feedback";
1753
2425
  import { Button } from "@godxjp/ui/general";
@@ -1786,6 +2458,27 @@ function CreateDialog() {
1786
2458
  description: "Open-state change handler."
1787
2459
  }
1788
2460
  ],
2461
+ usage: [
2462
+ "DO use all named sub-parts in order: Sheet (root) > SheetTrigger (opener) > SheetContent (panel) > SheetHeader > SheetTitle (required for a11y \u2014 maps to Radix DialogPrimitive.Title, announced as the accessible name) > optional SheetDescription > body content > SheetFooter. Never skip SheetTitle inside an open SheetContent.",
2463
+ "DO control state explicitly with open + onOpenChange on Sheet root when you need to close programmatically (e.g. after form submit). Uncontrolled (no props) works for simple trigger-only cases but gives you no hook to reset form state on close.",
2464
+ "DO use SheetTrigger asChild to wrap a Button or other interactive element \u2014 this avoids a nested <button> in the DOM. Never render a raw <button> as a direct child of SheetTrigger.",
2465
+ "DO use SheetFooter (renders at the bottom via mt-auto) for primary/cancel action Buttons. Never float action Buttons inside the body \u2014 they will not stick to the panel bottom.",
2466
+ "DON'T set showCloseButton={false} on SheetContent unless you provide your own SheetClose element; omitting both leaves users with no keyboard-accessible close path and breaks a11y.",
2467
+ "DON'T put a Sheet inside a Dialog (nested Radix portals conflict). If you need a slide-over triggered from within a modal, close the Dialog first, then open the Sheet."
2468
+ ],
2469
+ useCases: [
2470
+ "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
+ "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}.",
2473
+ "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
+ "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
+ ],
2476
+ related: [
2477
+ "Dialog \u2014 use Dialog (centered modal) when the action is destructive, requires full user focus, or needs a confirm/alertdialog (mode='confirm'). Use Sheet when the user benefits from seeing the page content behind the slide-over (filters, detail peek, quick-edit).",
2478
+ "FilterBar/FilterGroup \u2014 use FilterBar for inline persistent filter controls above a DataTable (no overlay). Use Sheet when the filter set is large (>4 controls) or on mobile where inline controls collapse poorly.",
2479
+ "Popover \u2014 use Popover for lightweight, anchor-positioned context menus or single-control overlays (date picker, color picker). Use Sheet when the panel has a header, multiple fields, or footer actions that need a dedicated panel.",
2480
+ "SplitPane \u2014 use SplitPane for a persistent side-by-side layout where both panes are always visible. Use Sheet when the secondary panel is transient and should overlay the primary content."
2481
+ ],
1789
2482
  example: `import { Sheet, SheetTrigger, SheetContent, SheetHeader, SheetTitle } from "@godxjp/ui/feedback";
1790
2483
  import { Button } from "@godxjp/ui/general";
1791
2484
 
@@ -1821,6 +2514,28 @@ import { Button } from "@godxjp/ui/general";
1821
2514
  description: "Override or hide (false) the icon."
1822
2515
  }
1823
2516
  ],
2517
+ usage: [
2518
+ 'DO compose with sub-parts in order: wrap text content in `<Alert.Content>` (or bare `<AlertContent>`), then `<Alert.Title>` + `<Alert.Description>` inside it, then `<Alert.Actions>` for any retry/CTA buttons. Example: `<Alert variant="destructive"><Alert.Content><Alert.Title>Error</Alert.Title><Alert.Description>{msg}</Alert.Description></Alert.Content><Alert.Actions><Button \u2026/></Alert.Actions></Alert>`.',
2519
+ "DO use `Alert.QueryError` (alias `AlertQueryError`) for TanStack Query / API failure surfaces \u2014 it already renders humanError(error), an i18n title, and an optional Retry button. Never hand-roll that pattern.",
2520
+ 'DON\'T pass raw action elements directly as top-level children of `<Alert>` without wrapping them in `<Alert.Actions>` \u2014 the layout slot only activates correctly via the `data-slot="alert-actions"` wrapper.',
2521
+ 'DON\'T hand-roll a dismiss \u2715 button \u2014 pass `onDismiss` to `<Alert>` and the component renders its own accessible dismiss button with `aria-label="Dismiss"`. The `onDismiss` handler may return a Promise.',
2522
+ 'DON\'T suppress the icon with `icon={false}` unless there is a deliberate design reason; the icon is the primary a11y cue for sighted users since the root already carries `role="alert"` for screen readers.',
2523
+ "DO NOT use `Alert` for transient ephemeral feedback (e.g. 'saved successfully'). Use `toast()` from sonner + `<Toaster>` for that. `Alert` is for persistent, page-scoped banners that stay visible until the user acts or dismisses."
2524
+ ],
2525
+ useCases: [
2526
+ 'Page-level error banner after a form submission fails server-side validation \u2014 `variant="destructive"` with `Alert.Title` summarising the error and `Alert.Description` listing field issues, paired with `onDismiss` so the user can clear it.',
2527
+ "Inline warning at the top of an accounting invoice list when the OAuth token for the MF sync is about to expire \u2014 `variant=\"warning\"` with an `Alert.Actions` containing a 'Reconnect' Button.",
2528
+ 'Success confirmation banner rendered after a bulk-import job completes and the user returns to the list page \u2014 `variant="success"` with `Alert.Description` showing the record count imported.',
2529
+ "TanStack Query data-fetch failure inside a Card body \u2014 use `<Alert.QueryError error={error} onRetry={refetch} />` instead of writing a custom error state.",
2530
+ "Informational notice at the top of a settings page when a feature is in beta or requires a plan upgrade \u2014 `variant=\"default\"` (Info icon) with a short description and an `Alert.Actions` 'Learn more' link.",
2531
+ 'Dismissible billing-overdue notice at the top of the dashboard \u2014 `variant="destructive"` with `onDismiss` that sets a session flag so it does not reappear until the next login.'
2532
+ ],
2533
+ related: [
2534
+ "Toaster \u2014 use for transient, auto-dismissing feedback ('Record saved', 'Deleted'). Alert is for persistent page-scoped banners; Toaster is for fire-and-forget notifications triggered by toast() from sonner.",
2535
+ "MutationFeedback \u2014 use when you want inline success/error feedback tightly coupled to a form mutation's state (renders inline below the submit button). Alert requires you to manage show/hide state yourself.",
2536
+ "DataState \u2014 use for full query lifecycle (loading skeleton + empty state + error) inside a data-fetching section. Alert.QueryError is the error sub-component DataState uses internally; prefer DataState when you also need the loading/empty states.",
2537
+ "EmptyState \u2014 use for the zero-data case inside a list or table section, not for errors or warnings. Alert is for status messages; EmptyState is for the absence of data."
2538
+ ],
1824
2539
  example: `import { Alert, AlertTitle, AlertDescription } from "@godxjp/ui/feedback";
1825
2540
 
1826
2541
  <Alert variant="warning">
@@ -1843,6 +2558,28 @@ import { Button } from "@godxjp/ui/general";
1843
2558
  description: "Columns in header + body."
1844
2559
  }
1845
2560
  ],
2561
+ usage: [
2562
+ "DO use SkeletonTable as the pre-mount placeholder \u2014 either as a ternary fallback (`{!data ? <SkeletonTable rows={10} columns={6} /> : <DataTable \u2026 />}`) for Inertia deferred props, or as the `skeleton` prop of `DataState` (`<DataState query={q} skeleton={<SkeletonTable />} \u2026>`). It is NOT for in-table loading; once DataTable has mounted use its own `loading` prop instead.",
2563
+ "DO match rows/columns to the final DataTable layout: pass `rows` equal to your expected page size and `columns` equal to your column count so the skeleton doesn't visually jump on hydration. Defaults are rows=8, columns=5.",
2564
+ "DO NOT use SkeletonTable when data is already present but refetching \u2014 use `DataTable loading={isFetching}` for in-table refetch states. SkeletonTable is only for the initial pre-mount gap before DataTable is rendered.",
2565
+ "DO NOT wrap SkeletonTable in a Card \u2014 it renders its own header + body structure matching DataTable's DOM. Placing it inside CardContent adds unwanted padding around the skeleton rail.",
2566
+ 'The root element carries `aria-busy="true"` automatically \u2014 do not add a second aria-busy on a wrapper. Screen readers announce the loading state correctly without extra markup.',
2567
+ "Import from `@godxjp/ui/feedback` (not `@godxjp/ui/admin`). Both paths resolve but the canonical export is `feedback`."
2568
+ ],
2569
+ useCases: [
2570
+ "Inertia deferred props: the server streams the page shell immediately and defers the table data; render SkeletonTable until the prop arrives (`{!invoices ? <SkeletonTable rows={20} columns={7} /> : <DataTable data={invoices} columns={columns} />}`).",
2571
+ "TanStack Query initial load via DataState: pass SkeletonTable as the `skeleton` prop so DataState shows the correct table shape during the query's loading state before switching to the populated DataTable.",
2572
+ "Filter / search reset that unmounts and remounts DataTable: briefly show SkeletonTable while the new dataset fetches, preventing a flash of the empty state before results arrive.",
2573
+ "Admin list pages (invoices, journal entries, partners) where the table has a known column count \u2014 tune `columns` to match so column widths feel stable and don't reflow on hydration.",
2574
+ "Page-level Suspense boundaries: use SkeletonTable as the `fallback` of a React Suspense wrapping a lazy-loaded data table component.",
2575
+ "Route prefetch / navigation transitions: render SkeletonTable in the destination slot while Inertia visits are in-flight, keeping perceived layout stable."
2576
+ ],
2577
+ related: [
2578
+ "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
+ "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.",
2581
+ "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
+ ],
1846
2583
  example: `import { SkeletonTable } from "@godxjp/ui/feedback";
1847
2584
 
1848
2585
  {!coupons ? <SkeletonTable rows={10} columns={6} /> : <DataTable data={coupons} columns={columns} />}`,
@@ -1854,6 +2591,27 @@ import { Button } from "@godxjp/ui/general";
1854
2591
  group: "feedback",
1855
2592
  tagline: "Loading placeholder shaped like a CardStat tile. Use inside a ResponsiveGrid while KPIs load.",
1856
2593
  props: [],
2594
+ 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.",
2598
+ "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
+ `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
+ "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."
2601
+ ],
2602
+ useCases: [
2603
+ "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
+ "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.",
2607
+ "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
+ ],
2609
+ 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.",
2611
+ "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
+ "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
+ "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."
2614
+ ],
1857
2615
  example: `import { SkeletonCard } from "@godxjp/ui/feedback";
1858
2616
  import { ResponsiveGrid } from "@godxjp/ui/layout";
1859
2617
 
@@ -1874,6 +2632,26 @@ import { ResponsiveGrid } from "@godxjp/ui/layout";
1874
2632
  },
1875
2633
  { name: "richColors", type: "boolean", description: "Enable Sonner rich variant colours." }
1876
2634
  ],
2635
+ usage: [
2636
+ "DO: Mount exactly ONE `<Toaster richColors />` at the app root (e.g. inside your layout or AppShell children). Multiple mounts create duplicate toast stacks \u2014 there is no provider context, only DOM portals.",
2637
+ 'DO: Import `toast` from `"sonner"` directly (not from `@godxjp/ui`) to fire toasts anywhere: `toast.success(\u2026)`, `toast.error(\u2026)`, `toast.warning(\u2026)`, `toast.info(\u2026)`, `toast.loading(\u2026)`, `toast.promise(\u2026)`.',
2638
+ "DON'T: Try to import a `toast` helper from `@godxjp/ui/feedback` \u2014 it does not exist. The component re-exports only the `Toaster` mount; the imperative API lives in the `sonner` package.",
2639
+ "DO: Let the wrapper handle theming \u2014 it uses `useDocumentTheme()` to sync with the document `dark` class and `prefers-color-scheme` automatically. Never pass a hardcoded `theme` prop unless you are deliberately overriding.",
2640
+ "DON'T: Use `Toaster` for persistent errors or blocking confirmations. Toasts auto-dismiss; they are not a substitute for `Alert` (inline persistent warnings) or `Dialog` (decisions requiring user input).",
2641
+ "DO: Pass `position` to relocate the stack if a persistent sidebar/footer would obscure the default `bottom-right`. The wrapper already sets a safe `mobileOffset`; don't add redundant mobile offsets unless your layout differs."
2642
+ ],
2643
+ useCases: [
2644
+ 'After a successful form save (invoice, journal entry, vendor record) \u2014 show `toast.success("\u4FDD\u5B58\u3057\u307E\u3057\u305F")` to confirm without blocking navigation.',
2645
+ 'After a background job is enqueued (e.g. bulk sync or export) \u2014 show `toast.info("\u30A8\u30AF\u30B9\u30DD\u30FC\u30C8\u3092\u958B\u59CB\u3057\u307E\u3057\u305F")` then later update with `toast.promise()` to track completion.',
2646
+ "Mutation error fallback when the error is transient and retrying is the right UX \u2014 show `toast.error(message)` instead of replacing page content; reserve `MutationFeedback` for inline, persistent error display inside a form.",
2647
+ 'Soft destructive action confirmation outcome \u2014 e.g. "\u524A\u9664\u3057\u307E\u3057\u305F" after an item is removed, paired with an undo action via `toast("\u2026", { action: { label: \'\u5143\u306B\u623B\u3059\', onClick: undo } })`.',
2648
+ 'OAuth / session expiry warnings \u2014 surface a brief `toast.warning("\u30BB\u30C3\u30B7\u30E7\u30F3\u306E\u6709\u52B9\u671F\u9650\u304C\u8FD1\u3065\u3044\u3066\u3044\u307E\u3059")` without interrupting the user\'s current form state.'
2649
+ ],
2650
+ related: [
2651
+ "Alert \u2014 use for persistent, inline feedback that must stay visible (validation summaries, page-level warnings, destructive notices). Unlike Toaster, Alert does not auto-dismiss and lives in the document flow.",
2652
+ "MutationFeedback \u2014 use when you have a TanStack `useMutation` result and want an inline error + retry UI inside a form or card. Renders nothing on success/idle; pairs naturally with a `toast.success` in `onSuccess`.",
2653
+ "Dialog \u2014 use when the user must make a conscious decision (confirm delete, resolve conflict) before proceeding. Toaster toasts are fire-and-forget; Dialog blocks until the user responds."
2654
+ ],
1877
2655
  example: `// app root \u2014 mount once
1878
2656
  import { Toaster } from "@godxjp/ui/feedback";
1879
2657
  <>{children}<Toaster richColors /></>
@@ -1899,6 +2677,27 @@ toast.error("\u4FDD\u5B58\u306B\u5931\u6557\u3057\u307E\u3057\u305F");`,
1899
2677
  description: "Active-tab change handler."
1900
2678
  }
1901
2679
  ],
2680
+ 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.',
2682
+ "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.",
2684
+ '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)."
2687
+ ],
2688
+ 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.",
2690
+ "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
+ '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
+ "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
+ "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
+ ],
2695
+ 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
+ "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
+ "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
+ ],
1902
2701
  example: `import { Tabs, TabsList, TabsTrigger, TabsContent } from "@godxjp/ui/navigation";
1903
2702
 
1904
2703
  <Tabs defaultValue="overview">
@@ -1930,6 +2729,28 @@ toast.error("\u4FDD\u5B58\u306B\u5931\u6557\u3057\u307E\u3057\u305F");`,
1930
2729
  },
1931
2730
  { name: "onClear", type: "() => void", description: "Clear-all handler." }
1932
2731
  ],
2732
+ usage: [
2733
+ "DO place FilterBar ABOVE the table Card in the page layout \u2014 never inside CardContent or CardContent flush. It carries its own padding-block via CSS tokens; nesting it inside a flush card strips that spacing and breaks the visual rhythm. The correct pattern is: <PageInset><FilterBar \u2026/></PageInset> then a sibling <Card><CardContent flush><DataTable /></CardContent></Card>.",
2734
+ "DO wrap every labelled filter control (Select, DatePicker, DateRangePicker, etc.) in a FilterGroup with a descriptive label prop. A bare Select dropped directly in FilterBar has no label anchor and breaks the stacked-column layout on mobile. SearchInput is the one exception \u2014 it does NOT need a FilterGroup wrapper because it carries its own visible placeholder.",
2735
+ "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
+ "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
+ "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."
2739
+ ],
2740
+ useCases: [
2741
+ "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.",
2742
+ "Partner / customer ledger list: FilterGroup for account type (AR/AP), FilterGroup for legal entity, SearchInput for partner name \u2014 clear-all resets the entity switcher back to 'all' as well as local filter state.",
2743
+ "Transaction history with multi-dimension filtering: date range + account Select + currency Select \u2014 FilterBar's responsive flex-wrap layout automatically stacks filters vertically on mobile and flows them into a row on \u2265640px without any custom CSS.",
2744
+ "Admin user / permission list: SearchInput for email search + FilterGroup wrapping a Select for role \u2014 the clear button becomes visible only when either control departs from its default, so the toolbar stays clean on first load.",
2745
+ "Report parameter bar above a read-only DataTable: two DatePickers inside FilterGroups set the report start/end dates, a Select picks the reporting entity \u2014 hasActiveFilters is always true once the user first applies the report, giving a one-click reset.",
2746
+ "Any list page migrating away from hand-rolled Tailwind flex rows with inline labels \u2014 drop existing label+control pairs into FilterGroup to get consistent label colour (muted-foreground), gap, and the responsive stacking behaviour for free."
2747
+ ],
2748
+ related: [
2749
+ "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
+ "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.",
2752
+ "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
+ ],
1933
2754
  example: `import { FilterBar, FilterGroup } from "@godxjp/ui/navigation";
1934
2755
  import { SearchInput, Select, SelectTrigger, SelectValue, SelectContent, SelectItem } from "@godxjp/ui/data-entry";
1935
2756
 
@@ -1961,6 +2782,27 @@ import { SearchInput, Select, SelectTrigger, SelectValue, SelectContent, SelectI
1961
2782
  },
1962
2783
  { name: "children", type: "ReactNode", required: true, description: "The filter control." }
1963
2784
  ],
2785
+ usage: [
2786
+ "DO: Always nest FilterGroup directly inside FilterBar \u2014 it renders a vertical label+control stack (ui-stack-xs) that aligns with FilterBar's flex-wrap row layout. Never use FilterGroup as a standalone wrapper outside FilterBar.",
2787
+ "DO: Pass exactly ONE filter control as children (Select, DatePicker, DateRangePicker, SearchSelect, etc.). FilterGroup is a single-control labeled slot, not a multi-control panel \u2014 it renders the label div above the control and sizes the slot to full-width mobile / auto desktop.",
2788
+ "DON'T: Pass a raw label string that contains complex JSX beyond text \u2014 label is ReactNode but is styled as muted small-size text (ui-filter-label). Do not add Tooltip or interactive elements inside the label as it is purely presentational.",
2789
+ "DON'T: Place FilterGroup inside CardContent (including flush variant) \u2014 this breaks horizontal padding. FilterBar (and thus all its FilterGroups) must sit above the table Card as a standalone block. Page order: KPIs \u2192 FilterBar (containing FilterGroups) \u2192 Card wrapping DataTable.",
2790
+ "DO: Use FilterGroup for every labelled filter slot; do NOT hand-roll a label+control pair with a div. The label placement, color (muted-foreground), and font-size are token-driven through ui-filter-label \u2014 duplicating this with raw Tailwind will drift from the design.",
2791
+ "A11y: label is rendered as a visible div, not a form <label> element \u2014 it is not wired to the child control's id. If the child control (e.g. Select) has its own accessible label, that is sufficient. Do not add htmlFor on the FilterGroup label; instead ensure the inner control has aria-label or its own Label via the control's API."
2792
+ ],
2793
+ useCases: [
2794
+ "A list page for invoices/journal entries where each column value can be filtered: wrap each Select (status, period, entity) in its own FilterGroup inside a FilterBar placed above the DataTable card.",
2795
+ "An admin transaction log page with a date range picker and a partner Select \u2014 each gets its own FilterGroup label so users can scan 'Period' / 'Partner' labels horizontally before interacting.",
2796
+ "A report filter panel that shows 'Fiscal Year', 'Department', and 'Account Type' dropdowns \u2014 FilterGroups communicate the semantic name of each filter without requiring the controls themselves to have visible labels (where Select placeholder alone is ambiguous).",
2797
+ "Mobile-first responsive filter strip: FilterBar + FilterGroups stack vertically on narrow screens (full-width per FilterGroup) and wrap into a horizontal row at sm:, making them safe for 320 px viewports without extra breakpoint overrides.",
2798
+ "When a filter control (e.g. DateRangePicker) has no intrinsic visible label of its own \u2014 wrapping it in FilterGroup provides the visible label without hand-rolling a div/label pattern that would differ from design tokens."
2799
+ ],
2800
+ related: [
2801
+ "FilterBar \u2014 the required parent container. FilterGroup has no useful meaning outside FilterBar; FilterBar also owns the clear-all button (onClear + hasActiveFilters). Always compose FilterGroup inside FilterBar, never standalone.",
2802
+ "SearchInput \u2014 sits directly inside FilterBar as a sibling to FilterGroup (not wrapped in FilterGroup) because it is self-labelling via its placeholder and search icon. Use FilterGroup only for controls that need a separate visible label.",
2803
+ "Select \u2014 the canonical child for FilterGroup when filtering by a categorical value (status, type, entity). Pair with FilterGroup for the label; do not hand-roll a label+Select div.",
2804
+ "Tabs \u2014 an alternative filtering/scoping pattern when the filter has exactly 2-4 mutually exclusive states and you want them visible as persistent tabs rather than a dropdown. Use Tabs+TabsList above the table instead of FilterBar+FilterGroup+Select when the values are few and well-known."
2805
+ ],
1964
2806
  example: `import { FilterGroup } from "@godxjp/ui/navigation";
1965
2807
 
1966
2808
  <FilterGroup label="\u30B9\u30B3\u30FC\u30D7"><Select>{/* ... */}</Select></FilterGroup>`,
@@ -1991,6 +2833,27 @@ import { SearchInput, Select, SelectTrigger, SelectValue, SelectContent, SelectI
1991
2833
  description: "Page / page-size change handler."
1992
2834
  }
1993
2835
  ],
2836
+ usage: [
2837
+ "DO always control Pagination externally: store `current` and `pageSize` in React state (or URL params), and update both in the `onChange(page, pageSize)` callback. Pagination is fully controlled \u2014 it has no internal state and will not move unless `current` changes.",
2838
+ "DO pass `total` as the raw item count (not page count). The component computes `Math.ceil(total / pageSize)` internally; passing a pre-computed page count as `total` will over-paginate.",
2839
+ "DO use `showSizeChanger` together with `pageSizeOptions` when the user needs density control (default options are [10, 20, 50, 100]). When `showSizeChanger` is omitted the page-size Select is not rendered at all \u2014 do NOT hand-roll your own Select beside Pagination.",
2840
+ "DO use `simple` mode for compact contexts (mobile, sidebars, sheet footers) \u2014 it renders Prev / `n / total` / Next with no page-number buttons. Use the full form for primary admin list pages.",
2841
+ "DO use `showTotal` to surface item counts: pass `true` for the built-in i18n label, or a function `(total, [from, to]) => ReactNode` for a custom range label like '1\u201310 of 342 invoices'. Never hard-code a total string beside the component.",
2842
+ "DON'T use Pagination for cursor- or infinite-scroll-based lists. Pagination is strictly offset/page-based (`current` is a page number). For cursor pagination inside a DataTable use `DataTable.Pagination`; for infinite scroll use `InfiniteQueryState`."
2843
+ ],
2844
+ useCases: [
2845
+ "Standalone offset-paginated admin list pages (e.g. invoice list, customer list, transaction history) rendered outside DataTable \u2014 place Pagination below the table card, outside the card border, with `showTotal` and optionally `showSizeChanger`.",
2846
+ "Search results pages where the backend accepts `page` + `per_page` query parameters and returns a total count \u2014 wire `current` and `pageSize` to URL search params so the URL is shareable and browser-back works.",
2847
+ "Reports and filtered data grids where the user needs to export 'all selected pages': `showTotal` with a custom function lets you show '1\u201350 of 1 200 rows' so the user understands the scope before exporting.",
2848
+ "Compact modal or sheet footers with a long list (e.g. selecting from a product catalog inside a dialog) \u2014 use `simple` mode to save horizontal space while keeping navigation accessible.",
2849
+ "DataTable instances where the server returns an offset-based total and `DataTable.Pagination` is not being used: attach a standalone Pagination below the card and pass the same `page` / `pageSize` state to both the DataTable `data` prop and the API fetch."
2850
+ ],
2851
+ related: [
2852
+ "DataTable.Pagination \u2014 use instead of standalone Pagination when the list is rendered inside a DataTable compound and uses cursor-based navigation (cursor + hasMore + onChange). DataTable.Pagination handles First/Next without page arithmetic; standalone Pagination requires a known total.",
2853
+ "InfiniteQueryState \u2014 use for infinite-scroll / load-more lists driven by useInfiniteQuery. It auto-manages skeleton, empty, and error states; Pagination is inappropriate here because there is no discrete page number.",
2854
+ "DataTable \u2014 when offset pagination is needed inside DataTable, prefer composing DataTable with a standalone Pagination below the card rather than DataTable.Pagination if the API is offset-based and returns a total count. DataTable itself does not paginate; you supply `data` for the current page.",
2855
+ "SearchInput \u2014 often placed in the same toolbar as Pagination. Resetting `current` to 1 inside the search `onChange` handler is mandatory; forgetting this is the most common bug when combining search and Pagination."
2856
+ ],
1994
2857
  example: `import { Pagination } from "@godxjp/ui/navigation";
1995
2858
 
1996
2859
  <Pagination current={page} total={filtered.length} pageSize={10} showTotal onChange={(p) => setPage(p)} />`,
@@ -2009,6 +2872,28 @@ import { SearchInput, Select, SelectTrigger, SelectValue, SelectContent, SelectI
2009
2872
  description: "Open-state change handler."
2010
2873
  }
2011
2874
  ],
2875
+ usage: [
2876
+ "DO compose the full sub-part tree: DropdownMenu (root) \u2192 DropdownMenuTrigger (with asChild to delegate to your Button/icon) \u2192 DropdownMenuContent \u2192 DropdownMenuItem / DropdownMenuSeparator / DropdownMenuLabel / DropdownMenuGroup. Omitting any level (e.g. rendering DropdownMenuContent without DropdownMenu as ancestor) breaks Radix context and the menu will not open.",
2877
+ "DO use DropdownMenuTrigger with asChild and pass a godx-ui Button or icon Button as the child \u2014 never render a raw <button> or <div> as the trigger, and never omit asChild when the child is already a button-like element (double-button nesting breaks a11y).",
2878
+ "DO use variant='destructive' on DropdownMenuItem for irreversible actions (delete, revoke, void) \u2014 this applies the semantic destructive colour token automatically without any className override.",
2879
+ "DO use DropdownMenuSub + DropdownMenuSubTrigger + DropdownMenuSubContent for nested sub-menus (e.g. 'Export' \u2192 'CSV', 'PDF'). The ChevronRight icon is rendered automatically by DropdownMenuSubTrigger \u2014 do not add your own.",
2880
+ "DO use DropdownMenuCheckboxItem (with checked + onCheckedChange) or DropdownMenuRadioGroup + DropdownMenuRadioItem for toggle/selection menus such as column visibility or active view. These items manage their own checked indicator \u2014 do not layer a Checkbox or RadioGroup inside a plain DropdownMenuItem.",
2881
+ "DON'T use DropdownMenu for form submission \u2014 items fire onSelect callbacks, not form field values. There is no name prop for native form submission. If a menu selection must feed a form field, lift state into a controlled value and wire a hidden Input or use Select instead."
2882
+ ],
2883
+ useCases: [
2884
+ "Row action menu in a DataTable: a '...' icon Button opens a DropdownMenu with Edit, Duplicate, DropdownMenuSeparator, then Delete (variant='destructive') \u2014 keeps the row compact and avoids inline button clutter.",
2885
+ "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
+ "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
+ "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.",
2889
+ "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
+ ],
2891
+ related: [
2892
+ "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
+ "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
+ "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."
2896
+ ],
2012
2897
  example: `import { DropdownMenu, DropdownMenuTrigger, DropdownMenuContent, DropdownMenuItem, DropdownMenuSeparator } from "@godxjp/ui/navigation";
2013
2898
  import { Button } from "@godxjp/ui/general";
2014
2899
 
@@ -2046,6 +2931,27 @@ import { Button } from "@godxjp/ui/general";
2046
2931
  description: "Layout direction."
2047
2932
  }
2048
2933
  ],
2934
+ usage: [
2935
+ "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
+ "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
+ "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.",
2939
+ "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
+ "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
+ ],
2942
+ useCases: [
2943
+ "Multi-step form wizard (entity onboarding, invoice creation): render Steps above a form, drive `current` from local state, advance on validated submit \u2014 use `status='error'` on the current step when server validation fails.",
2944
+ "Async background job tracker: display steps for a long-running import/export pipeline; poll job status and map job phases to `StepStatusProp` values (`'process'` with spinner for in-flight, `'finish'` for done, `'error'` for failed).",
2945
+ "Document approval workflow (accounting, contracts): map approval stages (Draft \u2192 Review \u2192 Approved \u2192 Archived) to `items` with per-item `status` reflecting the real state from the server \u2014 use `orientation='vertical'` for a sidebar timeline feel.",
2946
+ "Onboarding checklist sidebar: `orientation='vertical'` + `type='dot'` + `size='small'` for a compact sidebar progress guide alongside a multi-section settings page.",
2947
+ "Non-linear step navigation (e.g. revisit a previous step to correct data): provide `onChange` and leave only future steps `disabled`; past and current steps become clickable buttons."
2948
+ ],
2949
+ related: [
2950
+ "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.",
2953
+ "Breadcrumb \u2014 use Breadcrumb for hierarchical location within a page tree; use Steps for sequential workflow progress where order and completion matter."
2954
+ ],
2049
2955
  example: `import { Steps } from "@godxjp/ui/navigation";
2050
2956
 
2051
2957
  <Steps current={1} items={[{ title: "\u7533\u8ACB" }, { title: "\u5BE9\u67FB\u4E2D" }, { title: "\u5B8C\u4E86" }]} />`,
@@ -2083,6 +2989,28 @@ import { Button } from "@godxjp/ui/general";
2083
2989
  description: "Initial clock format."
2084
2990
  }
2085
2991
  ],
2992
+ usage: [
2993
+ "DO mount AppProvider ONCE at the application root (e.g. in app.tsx or the Inertia layout), wrapping ALL children \u2014 every godx-ui picker (LocalePicker, TimezonePicker, DateFormatPicker, TimeFormatPicker), every formatDate call, and the Toaster all rely on the single context it provides. Nesting two AppProviders creates split contexts; inner pickers silently read the wrong one.",
2994
+ "DO NOT omit AppProvider and then try to use LocalePicker, TimezonePicker, or formatDate standalone \u2014 useAppContext() throws 'useAppContext must be used within <AppProvider>' at runtime. The only exception is using those pickers in fully controlled mode (value + onChange) which reads useOptionalAppContext() and returns null safely.",
2995
+ "DO use the `persist={false}` prop on AppProvider when writing isolated tests or standalone settings forms where localStorage should not be read or written. With the default `persist={true}` the provider reads from localStorage key `godxjp.app` on mount (after first render), so initial state may differ between SSR and client.",
2996
+ "DO set `defaultTimezone='system'` together with `systemTimezone={serverTimezone}` when your backend knows the legal entity's canonical timezone (e.g. 'Asia/Ho_Chi_Minh'). Use `defaultTimezone='browser'` (the default) only when you want the user's browser clock. Do NOT pass a raw IANA string to `defaultTimezone` if the user may be in a different zone \u2014 use the named aliases.",
2997
+ "DO wire `onLocaleChange`, `onTimezoneChange`, `onTimeFormatChange`, `onDateFormatChange` to persist changes server-side (e.g. patch user profile via Inertia router) in addition to the automatic localStorage write. These callbacks fire after state is set, so the new value is already reflected in context.",
2998
+ "DO restrict the timezone dropdown by passing `timezoneOptions={APP_TIMEZONE_PRESET}` (an exported constant) to AppProvider \u2014 all TimezonePicker instances that omit their own `options` prop will inherit this restricted list automatically from context. Without it, TimezonePicker renders the full IANA list (~600 entries)."
2999
+ ],
3000
+ useCases: [
3001
+ "App bootstrap in a multi-locale SaaS admin (ja/en/vi) \u2014 mount AppProvider at the root with the tenant's preferred locale and IANA timezone so every DataTable date column, every formatDate call, and every picker renders consistently in the user's locale without any per-component configuration.",
3002
+ "User settings page \u2014 render LocalePicker, TimezonePicker, DateFormatPicker, and TimeFormatPicker as zero-prop children inside the existing AppProvider; each picker reads and writes context automatically. Wire `onLocaleChange` to an Inertia form submit to persist the change to the server profile.",
3003
+ "Server-rendered Inertia app with SSR hydration \u2014 pass `defaultTimezone='system'` and `systemTimezone={sharedProps.timezone}` (injected via HandleInertiaRequests) so the initial render is timezone-deterministic and avoids hydration mismatches caused by browser-timezone detection.",
3004
+ "Multi-entity accounting dashboard \u2014 use `timezoneOptions` to restrict the picker to the legal entity's permissible zones (e.g. Southeast Asian IANA ids only), preventing users from accidentally switching to an out-of-scope timezone that would misrepresent transaction timestamps.",
3005
+ "Isolated preview / Storybook story \u2014 wrap a single component in `<AppProvider persist={false} defaultLocale='en'>` to give it a stable context without polluting localStorage between stories.",
3006
+ "Test harness \u2014 wrap the component under test in `<AppProvider persist={false} defaultLocale='ja' defaultDateFormat='iso'>` to assert locale-sensitive formatting output deterministically, independent of whatever the browser or stored preferences report."
3007
+ ],
3008
+ related: [
3009
+ "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
+ "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
+ "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."
3013
+ ],
2086
3014
  example: `import { AppProvider } from "@godxjp/ui/app";
2087
3015
 
2088
3016
  <AppProvider defaultLocale="ja" defaultTimezone="Asia/Tokyo" defaultDateFormat="iso" defaultTimeFormat="24h">
@@ -2109,6 +3037,27 @@ import { Button } from "@godxjp/ui/general";
2109
3037
  description: "Output preset; auto infers from the value."
2110
3038
  }
2111
3039
  ],
3040
+ usage: [
3041
+ "DO import from `@godxjp/ui/datetime` \u2014 NOT from `date-fns` or any other datetime utility. `formatDate` is the single mandatory display entry point; calling `date-fns/format` directly bypasses AppProvider locale/timezone/dateFormat/timeFormat context and produces inconsistent output across the app.",
3042
+ "DO ensure `AppProvider` is mounted at the app root before the first `formatDate` call. The function reads a module-level context synced by `AppProvider` via `syncDatetimeContext`. Without it the fallback locale is `'vi'` / timezone `'Asia/Ho_Chi_Minh'` / `'24h'`, which will silently produce wrong output in Japanese or English apps.",
3043
+ "DO pass `null` or `undefined` safely \u2014 `formatDate` returns an em-dash `'\u2014'` for null/undefined/empty string values. Never guard with a ternary before calling it.",
3044
+ "DO use `options.kind` when auto-detection is wrong: pass `kind: 'date'` for an ISO datetime string you want displayed as date-only, `kind: 'relative'` for age display (e.g. `'3\u65E5\u524D'`), `kind: 'long'` for full PPP format in modals/detail panels. Auto-detection maps plain `yyyy-MM-dd` \u2192 `'date'`, `HH:mm` \u2192 `'time'`, everything else \u2192 `'datetime'`.",
3045
+ "DO pass `{ calendar: true }` when the `Date` object came from a react-day-picker calendar pick \u2014 this prevents timezone shift that would occur if the Date were treated as a UTC instant.",
3046
+ "DON'T hand-roll per-call locale/timezone resolution with `Intl.DateTimeFormat` or raw `date-fns/format`. The `options.locale` / `options.timezone` overrides exist for one-off per-cell display differences (e.g. showing a partner's local time), not as a substitute for AppProvider context."
3047
+ ],
3048
+ useCases: [
3049
+ "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.",
3051
+ "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
+ "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
+ "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."
3054
+ ],
3055
+ related: [
3056
+ "AppProvider \u2014 required peer that seeds locale, timezone, dateFormat, and timeFormat into the module-level context that `formatDate` reads. Must be mounted once at app root; omitting it means `formatDate` silently falls back to Vietnamese/Ho Chi Minh City defaults.",
3057
+ "DatePicker \u2014 the corresponding input control for calendar dates. Use `DatePicker` to capture a date from the user; use `formatDate(value, { calendar: true })` to display the picked `Date` object back as a string.",
3058
+ "DateFormatPicker / TimeFormatPicker / TimezonePicker \u2014 preference pickers that update AppProvider context; their selections are automatically picked up by subsequent `formatDate` calls with no extra wiring needed.",
3059
+ "TimePicker \u2014 the corresponding input control for HH:mm time values. Use `TimePicker` to capture time; use `formatDate(hhmm)` (auto-detects `'time'` kind) to display the stored `HH:mm` string respecting the user's 12h/24h preference."
3060
+ ],
2112
3061
  example: `import { formatDate } from "@godxjp/ui/datetime";
2113
3062
 
2114
3063
  formatDate(coupon.validFrom); // "2026-05-01"