@godxjp/ui-mcp 18.4.0 → 18.6.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/README.md +11 -10
- package/dist/index.js +925 -177
- package/package.json +2 -2
package/dist/index.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import{Server as Pe}from"@modelcontextprotocol/sdk/server/index.js";import{StdioServerTransport as Oe}from"@modelcontextprotocol/sdk/server/stdio.js";import{CallToolRequestSchema as Ie,ListResourcesRequestSchema as Re,ListToolsRequestSchema as Ne,ReadResourceRequestSchema as Fe}from"@modelcontextprotocol/sdk/types.js";var y=[{name:"PageContainer",group:"layout",tagline:"Mandatory page shell \u2014 EVERY page wraps its content in PageContainer (title/subtitle/extra/footer/breadcrumb).",props:[{name:"title",type:"string",required:!0,description:"Page heading rendered as <h1>."},{name:"subtitle",type:"string",description:"Secondary line beneath the title."},{name:"extra",type:"ReactNode",description:"Action buttons / controls rendered right of the title row."},{name:"footer",type:"ReactNode",description:"Content area pinned below the page body."},{name:"breadcrumb",type:"BreadcrumbItemProp[]",description:"Ordered trail of { label, to? } segments above the title."},{name:"breadcrumbAriaLabel",type:"string",description:`Override the breadcrumb nav landmark's accessible name (defaults to a localized "Breadcrumb"). Required when more than one PageContainer (each with its own breadcrumb) renders on the same page/view \u2014 two nav landmarks sharing one name/role fail landmark-unique.`},{name:"variant",type:'"default" | "narrow" | "flush" | "ghost"',defaultValue:'"default"',description:"Page shell layout; flush removes padding for full-bleed content."},{name:"density",type:'"compact" | "default" | "comfortable"',defaultValue:'"default"',description:"Spacing density across the page subtree."},{name:"stickyFooter",type:"boolean",defaultValue:"false",description:'Pin footer to viewport bottom on scroll \u2014 pairs with variant="narrow".'},{name:"footerReveal",type:'"always" | "onScroll"',defaultValue:'"always"',description:'When the footer is sticky, control WHEN it shows. "always" keeps it pinned the whole time; "onScroll" hides it until the header scrolls out of view then slides it up \u2014 the standard edit/create save bar. Stays mounted (no reflow \u2192 no jitter).'},{name:"fill",type:"boolean",defaultValue:"false",description:"Grow the body to fill the remaining shell height. Default false = top-packed, content-height (short pages leave no stretched void). Enable for a full-height DataTable, SplitPane, or a chat surface."},{name:"linkComponent",type:"React.ElementType",description:"Link component used for breadcrumb / header links (e.g. an Inertia or React Router `Link`). Defaults to a native `<a>`."}],usage:["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.","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.","DO: Use `variant='flush'` when the page body contains a full-bleed component like DataTable. Inside a flush container, wrap any padded strips (Toolbar, intro text) in `<PageContainer.Inset>` to align them with the header. Never add manual `px-*` or `p-*` padding to compensate \u2014 use PageContainer.Inset.","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.","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.","DON'T: Confuse PageContainer's prop names with the old PageHeader's prop names \u2014 PageContainer uses `subtitle` (not `description`) and `extra` (not `actions`). If you see those legacy names in old code, migrate them to PageContainer.","DO: Leave `fill` off (the default) for ordinary pages \u2014 the body is content-height and top-packed, so a short page on a tall viewport leaves no stretched empty void below the content (the page background simply spans the shell). Only set `fill` when the body itself should occupy the full remaining height: a full-height DataTable, a SplitPane, or a chat surface whose message list scrolls and whose composer is pinned to the bottom via `footer` + `stickyFooter`. DON'T add a manual `min-h-screen` / `flex-1` wrapper or a spacer div to fight or fake this.","DO: Know the header draws NO bottom divider by default \u2014 it is governed by the semantic token `--page-header-divider` (default `none`). A service theme opts in once, globally, with `--page-header-divider: 1px solid hsl(var(--border));` in its theme CSS. Never re-create the divider with a `border-b` utility on the header or a `<Separator>` under the title; `variant='ghost'` stays divider-less regardless of the token."],useCases:["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'` + `<PageContainer.Inset>` for the Toolbar above the table.","A detail / edit form page where the footer holds Save and Cancel buttons \u2014 use `footer={<Flex direction='row' justify='between' className='w-full'><Button variant='outline'>\u524A\u9664</Button><Button>\u4FDD\u5B58</Button></Flex>}` with `stickyFooter` + `footerReveal='onScroll'` so the save bar slides up only once the header (and its actions) scroll out of view \u2014 the canonical edit/create pattern.","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.","A dashboard page with KPI cards and chart sections \u2014 use `variant='default'` with `children={<Flex direction='col' gap='lg'>\u2026</Flex>}` to vertically stack multiple Card/StatCard sections beneath the page title.","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.","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, Toolbar, and controls in a single prop.","A chat / messaging detail page where the message list should scroll inside the page and the composer stays pinned at the bottom \u2014 use `fill` so the body occupies the full shell height, with `footer={<Composer/>}` + `stickyFooter`. Without `fill` the page would top-pack and the composer would float mid-screen on a tall viewport."],related:["PageContainer.Inset \u2014 use INSIDE a `variant='flush'` PageContainer to re-introduce horizontal padding for strips like Toolbar or intro text that should align with the page header, while the surrounding DataTable stays full-bleed. Not a standalone page shell.","PageContainer \u2014 always use PageContainer for new pages; it supports `children`, `footer`, `variant`, `density`, `stickyFooter`, and `fill`. Legacy code using the old prop names (`description` \u2192 `subtitle`, `actions` \u2192 `extra`) should be migrated to PageContainer.","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.","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."],example:`import { PageContainer, Flex } from "@godxjp/ui/layout";
|
|
2
|
+
import{Server as Le}from"@modelcontextprotocol/sdk/server/index.js";import{StdioServerTransport as Me}from"@modelcontextprotocol/sdk/server/stdio.js";import{CallToolRequestSchema as Fe,ListResourcesRequestSchema as Be,ListToolsRequestSchema as Ve,ReadResourceRequestSchema as qe}from"@modelcontextprotocol/sdk/types.js";var v=[{name:"PageContainer",group:"layout",tagline:"Mandatory page shell \u2014 EVERY page wraps its content in PageContainer (title/subtitle/extra/footer/breadcrumb).",props:[{name:"title",type:"string",required:!0,description:"Page heading rendered as <h1>."},{name:"subtitle",type:"string",description:"Secondary line beneath the title."},{name:"extra",type:"ReactNode",description:"Action buttons / controls rendered right of the title row."},{name:"footer",type:"ReactNode",description:"Content area pinned below the page body."},{name:"breadcrumb",type:"BreadcrumbItemProp[]",description:"Ordered trail of { label, to? } segments above the title."},{name:"breadcrumbAriaLabel",type:"string",description:`Override the breadcrumb nav landmark's accessible name (defaults to a localized "Breadcrumb"). Required when more than one PageContainer (each with its own breadcrumb) renders on the same page/view \u2014 two nav landmarks sharing one name/role fail landmark-unique.`},{name:"variant",type:'"default" | "narrow" | "flush" | "ghost"',defaultValue:'"default"',description:"Page shell layout; flush removes padding for full-bleed content."},{name:"density",type:'"compact" | "default" | "comfortable"',defaultValue:'"default"',description:"Spacing density across the page subtree."},{name:"preset",type:'"default" | "admin-collection"',defaultValue:'"default"',description:'Whole-page semantic composition. "admin-collection" owns header-to-toolbar rhythm, collection search measure, control height and table density for the subtree through themeable tokens.'},{name:"headerLayout",type:'"stack" | "responsive-inline"',defaultValue:'"stack"',description:'How the title band and `extra` share the header row BELOW the 640px step. "stack" (default) drops `extra` onto its own full-width line under the subtitle. "responsive-inline" keeps it beside the title at the token-owned --page-header-extra-measure (11rem) and lets the title/subtitle wrap \u2014 use it for ONE compact control (a search field, a single primary action) that must stay on the title row at 390px. At >=640px the two arrangements are identical.'},{name:"measure",type:'"default" | "narrow" | "medium"',defaultValue:'"default"',description:'Bounded page MEASURE shared by the header AND the body \u2014 a third axis, ORTHOGONAL to `variant` (chrome) and `headerLayout` (arrangement). "default" applies no cap (the historical fluid page). "narrow" (--page-measure-narrow, 42rem outer \u2192 624px visible surface) and "medium" (--page-measure-medium, 48rem outer \u2192 720px visible surface) cap BOTH bands, so a header `extra` action ends flush with the body surface instead of stranded at the page edge \u2014 unlike variant="narrow", which caps only the body. The package-owned page gutters sit INSIDE the cap, and it is a max, so a 390px viewport stays fluid (358px surface at the 16px compact gutter). The footer is intentionally not capped (its border/background is page chrome when `stickyFooter` pins it).'},{name:"stickyFooter",type:"boolean",defaultValue:"false",description:'Pin footer to viewport bottom on scroll \u2014 pairs with variant="narrow".'},{name:"footerReveal",type:'"always" | "onScroll"',defaultValue:'"always"',description:'When the footer is sticky, control WHEN it shows. "always" keeps it pinned the whole time; "onScroll" hides it until the header scrolls out of view then slides it up \u2014 the standard edit/create save bar. Stays mounted (no reflow \u2192 no jitter).'},{name:"fill",type:"boolean",defaultValue:"false",description:"Grow the body to fill the remaining shell height. Default false = top-packed, content-height (short pages leave no stretched void). Enable for a full-height DataTable, SplitPane, or a chat surface."},{name:"linkComponent",type:"React.ElementType",description:"Link component used for breadcrumb / header links (e.g. an Inertia or React Router `Link`). Defaults to a native `<a>`."}],usage:["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.","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.","DO: Use `variant='flush'` when the page body contains a full-bleed component like DataTable. Inside a flush container, wrap any padded strips (Toolbar, intro text) in `<PageContainer.Inset>` to align them with the header. Never add manual `px-*` or `p-*` padding to compensate \u2014 use PageContainer.Inset.","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.","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.","DO: Use `preset='admin-collection'` for canonical Admin list pages. It owns the toolbar/search/control/table composition once at PageContainer level; do not repeat widths, heights, cell padding or media queries on child fields and rows.","DON'T: Confuse PageContainer's prop names with the old PageHeader's prop names \u2014 PageContainer uses `subtitle` (not `description`) and `extra` (not `actions`). If you see those legacy names in old code, migrate them to PageContainer.","DO: Leave `fill` off (the default) for ordinary pages \u2014 the body is content-height and top-packed, so a short page on a tall viewport leaves no stretched empty void below the content (the page background simply spans the shell). Only set `fill` when the body itself should occupy the full remaining height: a full-height DataTable, a SplitPane, or a chat surface whose message list scrolls and whose composer is pinned to the bottom via `footer` + `stickyFooter`. DON'T add a manual `min-h-screen` / `flex-1` wrapper or a spacer div to fight or fake this.",'DO: Reach for `headerLayout="responsive-inline"` when a SINGLE compact header control (a member search, one primary action) must stay beside the title at 390px instead of wrapping under the subtitle. Its measure is the token `--page-header-extra-measure` (11rem) \u2014 never a consumer `w-[176px]` or a media query in app CSS. Keep the default `stack` when `extra` holds a toolbar of several buttons; squeezing those into the compact measure only makes them wrap in a narrower box.',"DO: Know the header draws NO bottom divider by default \u2014 it is governed by the semantic token `--page-header-divider` (default `none`). A service theme opts in once, globally, with `--page-header-divider: 1px solid hsl(var(--border));` in its theme CSS. Never re-create the divider with a `border-b` utility on the header or a `<Separator>` under the title; `variant='ghost'` stays divider-less regardless of the token.",'DO: Bound a readable/feed page with `measure="medium"` (720px visible surface) or `measure="narrow"` (624px) \u2014 NEVER a page-local `max-w-[720px]`, a wrapper div, or a consumer CSS variable override. `measure` caps the HEADER and the BODY together, which is the whole point: with `variant="narrow"` the header action stays out at the page edge while the body is 624px, so the action and the card do not share an end edge. Retune the presets once in a service theme via `--page-measure-narrow` / `--page-measure-medium`.','DO: Compose the axes \u2014 `variant="ghost" measure="medium" headerLayout="responsive-inline"` is the canonical quiet notification/inbox feed: ghost owns the quiet header rhythm (no divider, no header bottom pad, tighter title\u2192body gap), `measure` owns the shared 720px measure, `headerLayout` keeps one compact control on the title row at 390px. They are independent props precisely so chrome and measure are no longer one variant axis. DON\'T stack `variant="narrow"` on top of `measure` \u2014 the measure rule simply wins on the body (verified in Chromium: variant="narrow" + measure="medium" resolves the body to 768px, not the intersection), so the `variant="narrow"` is dead weight that only misleads the next reader. `variant="narrow"` is the legacy body-only cap; `measure="narrow"` is the same 624px surface with the header included.'],useCases:["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'` + `<PageContainer.Inset>` for the Toolbar above the table.","A detail / edit form page where the footer holds Save and Cancel buttons \u2014 use `footer={<Flex direction='row' justify='between' className='w-full'><Button variant='outline'>\u524A\u9664</Button><Button>\u4FDD\u5B58</Button></Flex>}` with `stickyFooter` + `footerReveal='onScroll'` so the save bar slides up only once the header (and its actions) scroll out of view \u2014 the canonical edit/create pattern.","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.","A dashboard page with KPI cards and chart sections \u2014 use `variant='default'` with `children={<Flex direction='col' gap='lg'>\u2026</Flex>}` to vertically stack multiple Card/StatCard sections beneath the page title.","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.","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, Toolbar, and controls in a single prop.","A chat / messaging detail page where the message list should scroll inside the page and the composer stays pinned at the bottom \u2014 use `fill` so the body occupies the full shell height, with `footer={<Composer/>}` + `stickyFooter`. Without `fill` the page would top-pack and the composer would float mid-screen on a tall viewport."],related:["PageContainer.Inset \u2014 use INSIDE a `variant='flush'` PageContainer to re-introduce horizontal padding for strips like Toolbar or intro text that should align with the page header, while the surrounding DataTable stays full-bleed. Not a standalone page shell.","PageContainer \u2014 always use PageContainer for new pages; it supports `children`, `footer`, `variant`, `density`, `stickyFooter`, and `fill`. Legacy code using the old prop names (`description` \u2192 `subtitle`, `actions` \u2192 `extra`) should be migrated to PageContainer.","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.","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."],example:`import { PageContainer, Flex } from "@godxjp/ui/layout";
|
|
3
3
|
import { Button } from "@godxjp/ui/general";
|
|
4
4
|
|
|
5
5
|
export default function OrdersPage() {
|
|
@@ -13,7 +13,7 @@ export default function OrdersPage() {
|
|
|
13
13
|
<Flex direction="col" gap="lg">{/* page content */}</Flex>
|
|
14
14
|
</PageContainer>
|
|
15
15
|
);
|
|
16
|
-
}`,storyPath:"layout/PageContainer.stories.tsx",rules:[23]},{name:"Flex",group:"layout",tagline:"Token-spaced flex primitive with explicit direction, alignment, justification, and wrapping controls.",props:[{name:"direction",type:'"row" | "col"',defaultValue:'"row"',description:"Main axis direction. Defaults to the CSS platform initial value, row; use col explicitly for vertical stacks."},{name:"gap",type:'"xs" | "sm" | "md" | "lg" | "xl"',defaultValue:'"md"',description:"Token gap between children, shared with other layout primitives."},{name:"align",type:'"start" | "center" | "end" | "stretch" | "baseline"',description:"Cross-axis alignment, emitted as a data attribute for the layout CSS."},{name:"justify",type:'"start" | "center" | "end" | "between" | "around" | "evenly"',description:"Main-axis distribution, emitted as a data attribute for the layout CSS."},{name:"wrap",type:"boolean",defaultValue:"false",description:"Allows children to wrap onto additional flex lines."}],usage:['DO import from `@godxjp/ui/layout` and reach for Flex when the axis, alignment, justification, or wrap behavior is part of the component contract: `import { Flex } from "@godxjp/ui/layout"`.',"DO keep spacing on the `gap` prop instead of raw `gap-*`, `space-*`, or padding utilities. Flex uses the same token scale as other layout primitives, so spacing remains tied to the design system.",'DO use `direction="row"` with `wrap` for responsive control rows, chip clusters, and action groups that need more control than simple row composition.','DO use `direction="col"` for vertical groupings that need explicit `align` or `justify` behavior. For pure vertical stacking without alignment control, `direction="col"` is sufficient.',"DON'T override the axis with `className` after choosing a direction prop. Keep the layout intent in props so catalog guidance and data attributes stay accurate.","Flex is a plain div with React.HTMLAttributes<HTMLDivElement>; pass `id`, `role`, `aria-*`, `data-*`, and structural className values as needed, but do not use it as a semantic form or button wrapper."],useCases:["Toolbar internals where controls should sit in a row, wrap on narrow widths, and stay vertically centered.","Card headers that need title content on the left and actions on the right via `justify='between'` without hand-rolling flex utility classes.","Empty-state or loading blocks that center content on both axes using `align='center'` and `justify='center'`.","Form sub-sections where a vertical group needs stretched children or centered helper content beyond what a plain column Flex provides.","Badge, chip, or tag clusters where wrapping is required but the caller also needs explicit gap control.","Low-level layout composition inside custom components where raw flex classes would duplicate the primitive."],related:["Flex `direction='col'` \u2014 the standard pattern for ordinary vertical block spacing; use explicit `align`, `justify`, or `wrap` props when you need more control.","Flex `direction='row'` \u2014 the standard pattern for simple horizontal groups; add `wrap` and `align='center'` for the typical row with wrapped centered items.","ResponsiveGrid \u2014 use for equal-width, multi-column tile layouts. Flex arranges children on one flex axis and does not provide column-count behavior.","PageContainer \u2014 page scaffold and padding context. Flex is an inner layout primitive used inside page sections, cards, dialogs, and toolbars."],example:`import { Flex } from "@godxjp/ui/layout";
|
|
16
|
+
}`,storyPath:"layout/PageContainer.stories.tsx",rules:[23]},{name:"Flex",group:"layout",tagline:"Token-spaced flex primitive with explicit direction, alignment, justification, and wrapping controls.",props:[{name:"direction",type:'"row" | "col"',defaultValue:'"row"',description:"Main axis direction. Defaults to the CSS platform initial value, row; use col explicitly for vertical stacks."},{name:"gap",type:'"xs" | "sm" | "md" | "lg" | "xl"',defaultValue:'"md"',description:"Token gap between children, shared with other layout primitives."},{name:"align",type:'"start" | "center" | "end" | "stretch" | "baseline"',description:"Cross-axis alignment, emitted as a data attribute for the layout CSS."},{name:"justify",type:'"start" | "center" | "end" | "between" | "around" | "evenly"',description:"Main-axis distribution, emitted as a data attribute for the layout CSS."},{name:"wrap",type:"boolean",defaultValue:"false",description:"Allows children to wrap onto additional flex lines."},{name:"hideBelow",type:'"sm" | "md" | "lg" | "xl"',description:'Drop this region below a canonical breakpoint step (sm 40rem, md 48rem, lg 64rem, xl 80rem). THE public way to make a region responsive without a page-local media query \u2014 a public header hides its anchor navigation with hideBelow="md" (gh#252). Omitted by default, so no attribute is emitted and no rule matches. display:none also removes it from the a11y tree, so keep its destinations reachable elsewhere (a footer nav) at that width.'},{name:"hideFrom",type:'"sm" | "md" | "lg" | "xl"',description:"Inverse of hideBelow \u2014 drop the region FROM that step upwards, i.e. keep it only on the narrow side (a compact-only affordance)."}],usage:['DO import from `@godxjp/ui/layout` and reach for Flex when the axis, alignment, justification, or wrap behavior is part of the component contract: `import { Flex } from "@godxjp/ui/layout"`.',"DO keep spacing on the `gap` prop instead of raw `gap-*`, `space-*`, or padding utilities. Flex uses the same token scale as other layout primitives, so spacing remains tied to the design system.",'DO use `direction="row"` with `wrap` for responsive control rows, chip clusters, and action groups that need more control than simple row composition.','DO use `direction="col"` for vertical groupings that need explicit `align` or `justify` behavior. For pure vertical stacking without alignment control, `direction="col"` is sufficient.',"DON'T override the axis with `className` after choosing a direction prop. Keep the layout intent in props so catalog guidance and data attributes stay accurate.","Flex is a plain div with React.HTMLAttributes<HTMLDivElement>; pass `id`, `role`, `aria-*`, `data-*`, and structural className values as needed, but do not use it as a semantic form or button wrapper."],useCases:["Toolbar internals where controls should sit in a row, wrap on narrow widths, and stay vertically centered.","Card headers that need title content on the left and actions on the right via `justify='between'` without hand-rolling flex utility classes.","Empty-state or loading blocks that center content on both axes using `align='center'` and `justify='center'`.","Form sub-sections where a vertical group needs stretched children or centered helper content beyond what a plain column Flex provides.","Badge, chip, or tag clusters where wrapping is required but the caller also needs explicit gap control.","Low-level layout composition inside custom components where raw flex classes would duplicate the primitive."],related:["Flex `direction='col'` \u2014 the standard pattern for ordinary vertical block spacing; use explicit `align`, `justify`, or `wrap` props when you need more control.","Flex `direction='row'` \u2014 the standard pattern for simple horizontal groups; add `wrap` and `align='center'` for the typical row with wrapped centered items.","ResponsiveGrid \u2014 use for equal-width, multi-column tile layouts. Flex arranges children on one flex axis and does not provide column-count behavior.","PageContainer \u2014 page scaffold and padding context. Flex is an inner layout primitive used inside page sections, cards, dialogs, and toolbars."],example:`import { Flex } from "@godxjp/ui/layout";
|
|
17
17
|
import { Button } from "@godxjp/ui/general";
|
|
18
18
|
|
|
19
19
|
<Flex direction="row" gap="sm" align="center" justify="between" wrap>
|
|
@@ -30,7 +30,7 @@ import { StatCard } from "@godxjp/ui/data-display";
|
|
|
30
30
|
<StatCard label="\u516C\u958B\u4E2D\u30AF\u30FC\u30DD\u30F3" value="8" />
|
|
31
31
|
<StatCard label="\u6708\u9593\u5229\u7528\u6570" value="3,210" />
|
|
32
32
|
<StatCard label="\u5272\u5F15\u7DCF\u984D" value="\xA5480,000" />
|
|
33
|
-
</ResponsiveGrid>`,storyPath:"layout/ResponsiveGrid.stories.tsx",rules:[24,40]},{name:"AppShell",group:"layout",tagline:"Root application shell \u2014 composes sidebar, topbar rail, main content area, and optional footer.",props:[{name:"sidebar",type:"ReactNode",required:!0,description:"Sidebar node \u2014 typically a <Sidebar>."},{name:"children",type:"ReactNode",required:!0,description:"Main page content rendered in <main>."},{name:"topbar",type:"ReactNode",description:"Full topbar override; else a rail is built from topbarLeft/topbarRight/logo."},{name:"topbarRight",type:"ReactNode",description:"Right slot of the auto-built topbar rail (user menu, switcher)."},{name:"topbarLeft",type:"ReactNode",description:"Left slot of the auto-built topbar rail."},{name:"logo",type:"ReactNode",description:"Brand mark at the far-left of the auto-built topbar rail (e.g. an Avatar)."},{name:"sidebarCollapsed",type:"boolean",defaultValue:"false",description:"Collapse the sidebar to icon-only mode."},{name:"footer",type:"ReactNode",description:"App-level footer outside the main content area."},{name:"breadcrumb",type:"BreadcrumbProp",description:"Breadcrumb trail rendered in the topbar header for back-navigation."},{name:"mobileNav",type:"ReactNode",description:"Navigation shown in the AppShell-owned mobile drawer below `lg` (where the docked sidebar is hidden). Defaults to the `sidebar` node; pass a tailored menu, or null to opt out."},{name:"mobileNavLabel",type:"string",description:"Accessible title for the mobile navigation drawer. Defaults to localized 'Menu'."},{name:"mobileNavOpen",type:"boolean",description:"Controlled open state of the mobile drawer. Omit for AppShell-owned state."},{name:"onMobileNavOpenChange",type:"(open: boolean) => void",description:"Change handler for the mobile drawer open state."}],usage:["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.","DO rely on AppShell's OWNED mobile drawer below `lg`: it renders a hamburger trigger in the topbar and a focus-trapped Sheet (Esc + overlay close, focus returns to the trigger). `mobileNav` defaults to the `sidebar` node, so the same nav is reachable on mobile with no wiring \u2014 never hide the sidebar without providing this. Pass a tailored `mobileNav`, or `mobileNav={null}` only when navigation lives elsewhere (e.g. a bottom bar).","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.","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.","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>.","DO NOT nest a second AppShell or AppShell inside AppShell's children \u2014 AppShell renders the root `app-root` div; nesting shells breaks the CSS grid layout.","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 <PageContainer.Inset> inside a flush PageContainer) inside children to get standard page padding."],useCases:["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>.","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.","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.","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.","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>.","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."],related:["AppShell \u2014 opinionated wrapper that composes AppShell + a frozen default Topbar in three props (menu, children, breadcrumb). Use AppShell for quick scaffolding when the default GodX product chip and no-op search/notification handlers are acceptable; switch to AppShell directly the moment you need a custom entity switcher, real onSearchOpen, user slot, or any topbar configuration.","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.","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.","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."],example:`import { AppShell, Sidebar } from "@godxjp/ui/layout";
|
|
33
|
+
</ResponsiveGrid>`,storyPath:"layout/ResponsiveGrid.stories.tsx",rules:[24,40]},{name:"AppShell",group:"layout",tagline:"Root application shell \u2014 composes sidebar, topbar rail, main content area, and optional footer.",props:[{name:"sidebar",type:"ReactNode",required:!0,description:"Sidebar node \u2014 typically a <Sidebar>."},{name:"children",type:"ReactNode",required:!0,description:"Main page content rendered in <main>."},{name:"topbar",type:"ReactNode",description:"Full topbar override; else a rail is built from topbarLeft/topbarRight/logo."},{name:"topbarRight",type:"ReactNode",description:"Right slot of the auto-built topbar rail (user menu, switcher)."},{name:"topbarLeft",type:"ReactNode",description:"Left slot of the auto-built topbar rail."},{name:"logo",type:"ReactNode",description:"Brand mark at the far-left of the auto-built topbar rail (e.g. an Avatar)."},{name:"sidebarCollapsed",type:"boolean",defaultValue:"false",description:"Collapse the sidebar to icon-only mode."},{name:"responsiveNavigation",type:'"drawer" | "docked"',defaultValue:'"drawer"',description:"Navigation strategy below the canonical 900px breakpoint. drawer exposes the accessible mobile Sheet; docked retains the token-sized sidebar grid track and suppresses the redundant drawer trigger."},{name:"footer",type:"ReactNode",description:"App-level footer outside the main content area."},{name:"breadcrumb",type:"BreadcrumbProp",description:"Breadcrumb trail rendered in the topbar header for back-navigation."},{name:"mobileNav",type:"ReactNode",description:"Navigation shown in the AppShell-owned mobile drawer below `lg` (where the docked sidebar is hidden). Defaults to the `sidebar` node; pass a tailored menu, or null to opt out."},{name:"mobileNavLabel",type:"string",description:"Accessible title for the mobile navigation drawer. Defaults to localized 'Menu'."},{name:"mobileNavOpen",type:"boolean",description:"Controlled open state of the mobile drawer. Omit for AppShell-owned state."},{name:"onMobileNavOpenChange",type:"(open: boolean) => void",description:"Change handler for the mobile drawer open state."}],usage:["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.","DO rely on AppShell's OWNED mobile drawer below `lg`: it renders a hamburger trigger in the topbar and a focus-trapped Sheet (Esc + overlay close, focus returns to the trigger). `mobileNav` defaults to the `sidebar` node, so the same nav is reachable on mobile with no wiring \u2014 never hide the sidebar without providing this. Pass a tailored `mobileNav`, or `mobileNav={null}` only when navigation lives elsewhere (e.g. a bottom bar).",'DO set `responsiveNavigation="docked"` only when the approved product contract retains its sidebar below 900px. AppShell keeps the same sidebar/footer/active navigation in a token-sized grid track and removes the redundant drawer trigger; never reproduce this with consumer media queries.',"DO let the drawer nav own its own inset: AppShell renders `mobileNav` in a Sheet body whose inline padding is the `--app-shell-mobile-nav-inset` token (near-zero by default) instead of the generic 24px sheet chrome inset, so a <Sidebar> in the drawer is not double-padded (its own --sidebar-nav-scroll-padding already insets each row). If a custom `mobileNav` node needs the full chrome inset, set `--app-shell-mobile-nav-inset: var(--space-6)` in the service theme \u2014 never patch the drawer with a `[data-slot='sheet-body']` selector in app CSS.","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.","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.","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>.","DO NOT nest a second AppShell or AppShell inside AppShell's children \u2014 AppShell renders the root `app-root` div; nesting shells breaks the CSS grid layout.","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 <PageContainer.Inset> inside a flush PageContainer) inside children to get standard page padding."],useCases:["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>.","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.","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.","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.","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>.","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."],related:["AppShell \u2014 opinionated wrapper that composes AppShell + a frozen default Topbar in three props (menu, children, breadcrumb). Use AppShell for quick scaffolding when the default GodX product chip and no-op search/notification handlers are acceptable; switch to AppShell directly the moment you need a custom entity switcher, real onSearchOpen, user slot, or any topbar configuration.","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.","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.","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."],example:`import { AppShell, Sidebar } from "@godxjp/ui/layout";
|
|
34
34
|
import { LayoutDashboard, Users } from "lucide-react";
|
|
35
35
|
import { router } from "@inertiajs/react";
|
|
36
36
|
|
|
@@ -48,28 +48,42 @@ const sidebar = (
|
|
|
48
48
|
|
|
49
49
|
export function CrmLayout({ children }: { content: React.ReactNode }) {
|
|
50
50
|
return <AppShell sidebar={sidebar}>{children}</AppShell>;
|
|
51
|
-
}`,storyPath:"layout/AppShell.stories.tsx",rules:[23]},{name:"AuthShell",group:"layout",tagline:"Centred auth/login page shell \u2014 brand bar (top) + centred card (main) + footer, over min-h-dvh, at comfortable control density.",props:[{name:"children",type:"ReactNode",required:!0,description:"Centred content \u2014 typically a single auth <Card> holding the form."},{name:"brand",type:"ReactNode",description:"Brand bar slot pinned to the top (e.g. a <Logo> / product mark)."},{name:"footer",type:"ReactNode",description:"Footer slot pinned to the bottom (legal links, locale switch, support)."}],usage:["DO pass a single <Card> (with the form inside <CardContent>) as `children` \u2014 AuthShell centres it and constrains its width via `--auth-shell-card-max-width`; do NOT hand-roll a `.auth-shell-main` / `.ui-auth-scope` wrapper.","DO put the product/brand mark in `brand` (a <Logo> or an <Avatar>) \u2014 it renders as the top banner landmark; omit it and the banner is not rendered.","DO use `footer` for compliance/legal/support links or a locale switch \u2014 it renders as the contentinfo landmark below the card.","DO wrap the card in <Reveal> for the entrance animation (`<AuthShell><Reveal><Card/></Reveal></AuthShell>`) \u2014 Reveal honours prefers-reduced-motion; AuthShell itself stays layout-only.","DO NOT re-scope control height or heading size in the app \u2014 AuthShell already sets the comfortable control tier (44px, WCAG touch floor) and the larger auth heading via `--auth-shell-control-height` / `--auth-shell-heading-size`; a service retunes those tokens, not a bespoke class.","DO NOT nest AuthShell inside AppShell (or vice-versa) \u2014 AuthShell is the ROOT shell for unauthenticated pages (login/mfa/passkey/device/reset); AppShell is for the authenticated app."],useCases:["Login page: <AuthShell brand={<Logo/>} footer={<AuthFooter/>}> wrapping a <Card> with the email/password form and a primary <Button fullWidth>.","MFA / passkey / device-authorisation step: same shell, a <Card> with the one-time-code <InputOTP> or a passkey prompt.","Password reset / forgot-password / accept-invite: the centred single-card flow with a brand bar and a legal footer.",'SSO landing / success confirmation: pair with an <EmptyState tone="success"> inside the card for an approved-device confirmation.'],related:["AppShell \u2014 the shell for AUTHENTICATED app pages (sidebar + topbar + main). AuthShell is its unauthenticated counterpart (brand bar + centred card + footer). Never nest the two.","Reveal \u2014 wrap the auth <Card> in <Reveal> for the entrance animation; AuthShell delegates motion (and prefers-reduced-motion handling) to it rather than baking an animation in.","Card \u2014 the canonical container for the auth form; place the form inside <CardContent>. AuthShell centres and width-constrains it.",'EmptyState \u2014 with `tone="success"` for a confirmation card inside the shell (e.g. device approved).'],example:`import { AuthShell } from "@godxjp/ui/layout";
|
|
51
|
+
}`,storyPath:"layout/AppShell.stories.tsx",rules:[23]},{name:"AuthShell",group:"layout",tagline:"Centred auth/login page shell \u2014 brand bar (top) + centred card (main) + footer, over min-h-dvh, at comfortable control density.",props:[{name:"children",type:"ReactNode",required:!0,description:"Centred content \u2014 typically a single auth <Card> holding the form."},{name:"brand",type:"ReactNode",description:"Brand bar slot pinned to the top (e.g. a <Logo> / product mark)."},{name:"footer",type:"ReactNode",description:"Footer slot pinned to the bottom (legal links, locale switch, support)."},{name:"variant",type:'"default" | "canonical"',defaultValue:'"default"',description:'Visual contract for the auth surface. "canonical" applies the shared DXS compact geometry (36px controls, 22.5rem/360px card measure, responsive page insets, tighter field labels) through component tokens. "default" keeps the comfortable 44px shell with the 24rem card.'},{name:"preset",type:'"default" | "login" | "device-authorization" | "context-selection" | "account-recovery"',defaultValue:'"default"',description:'Named flow GEOMETRY \u2014 the package-owned layout contract for a canonical hosted-identity flow. "login" (gh#237) anchors the 360px SCR-001 card at x=540/332/15 and y=363/363/353 for 1440x900, 1024x900 and 390x844; its fixed identity slot absorbs standalone, one-line requester and wrapped two-line requester states without truncating data. "device-authorization" (gh#220) = a 380px card at 1440/1024 with a 5px inline page gutter at 390. "context-selection" (gh#217) = a 25rem card on desktop/tablet, edge-to-edge on mobile, plus tokenized section rhythm. "account-recovery" (gh#233) = the 27rem/432px SCR-008 recovery/MFA panel with a 15px mobile gutter. ORTHOGONAL to `variant`, so `variant="canonical" preset="login"` keeps canonical control chrome while the preset owns layout. Selecting a preset REPLACES consumer-side geometry overrides.'},{name:"density",type:'"comfortable" | "compact"',description:'Vertical density scoped to auth-card descendants. Defaults to "compact" under variant="canonical" and "comfortable" otherwise.'},{name:"className",type:"string",description:"Extra CSS classes merged onto the shell root."}],usage:["DO pass a single <Card> (with the form inside <CardContent>) as `children` \u2014 AuthShell centres it and constrains its width via `--auth-shell-card-max-width`; do NOT hand-roll a `.auth-shell-main` / `.ui-auth-scope` wrapper.",'DO use `variant="canonical" preset="login"` for SCR-001 and pass <AuthIdentity>, <Card>, <AuthFooter> as direct children in that order (an anchor may wrap AuthIdentity). The preset owns the identity slot, card anchor, 20px section rhythm and compact card block inset for standalone and real requester states. Do not wrap the three sections in a consumer Flex/Stack or the semantic grid cannot anchor them.','DO select a `preset` instead of overriding geometry: `preset="login"` for the stable SCR-001 identity/card/footer anchor, `preset="device-authorization"` for the 380px OAuth device-grant measure, `preset="context-selection"` for the 25rem organisation/context picker, `preset="account-recovery"` for the 432px SCR-008 recovery/MFA panel. Page-local width/inset/vertical-offset variables are the exact anti-pattern these presets replace (gh#217/gh#220/gh#233/gh#237).','DO build the password-recovery and sign-in MFA CHALLENGE panels as a COMPOSITION inside `preset="account-recovery"` \u2014 there is NO PasswordRecoveryPanel and NO MfaChallengePanel component (gh#233 Gate 0): Card > CardHeader(CardTitle + CardDescription, INSIDE the bordered surface) > CardContent > AuthStack[ Alert notice \xB7 FormField fields \xB7 Button fullWidth \xB7 Flex justify="between" wrap fallback row ]. Do NOT put AuthIdentity above the panel there (it always renders the hosted mark), and NEVER reuse TwoFactorSetup \u2014 that is the ENROLLMENT dialog, not a sign-in challenge. See the `auth-recovery-panels` pattern.','DO combine `variant` and `preset` \u2014 they are orthogonal: `variant` owns control density + heading size, `preset` owns the page measure. `variant="canonical" preset="device-authorization"` is the canonical device screen.','DO let `preset="context-selection"` space the auth column: it turns the card slot into a flex column with a tokenized `--auth-shell-card-stack-gap`, so an intro (<AuthIdentity>), the choice <Card> and a trailing "remember" row pass as three siblings with NO page-local spacing.',"DO NOT add a page-local width, inset or colour to hit an artboard \u2014 if a measure is missing, it is a library gap: a new preset or token, never consumer CSS (rules #44/#45).","DO put the product/brand mark in `brand` (a <Logo> or an <Avatar>) \u2014 it renders as the top banner landmark; omit it and the banner is not rendered.","DO use `footer` for compliance/legal/support links or a locale switch \u2014 it renders as the contentinfo landmark below the card.","DO wrap the card in <Reveal> for the entrance animation (`<AuthShell><Reveal><Card/></Reveal></AuthShell>`) \u2014 Reveal honours prefers-reduced-motion; AuthShell itself stays layout-only.","DO NOT re-scope control height or heading size in the app \u2014 AuthShell already sets the comfortable control tier (44px, WCAG touch floor) and the larger auth heading via `--auth-shell-control-height` / `--auth-shell-heading-size`; a service retunes those tokens, not a bespoke class.","DO tune the COMPACT auth card through its three independent knobs, never a consumer selector on `[data-slot=\"card-content\"]` (gh#232): `--auth-shell-compact-card-inset` = the inline column, `--auth-shell-card-padding-block-compact` = the card's block (top/bottom) padding \u2014 it is wired to the card's `--card-space-shell-y` and really moves CardContent's block edges \u2014 and `--auth-shell-card-body-gap-compact` = the header\u2194body gap. Each defaults to today's canonical rhythm, so overriding one moves ONLY that axis.","DO NOT nest AuthShell inside AppShell (or vice-versa) \u2014 AuthShell is the ROOT shell for unauthenticated pages (login/mfa/passkey/device/reset); AppShell is for the authenticated app."],useCases:['Canonical Login (SCR-001): <AuthShell variant="canonical" preset="login"> with direct AuthIdentity \xB7 Card \xB7 AuthFooter children; the card remains anchored when requester is absent, one line or wraps to two lines (gh#237).',"Login page: <AuthShell brand={<Logo/>} footer={<AuthFooter/>}> wrapping a <Card> with the email/password form and a primary <Button fullWidth>.","MFA / passkey / device-authorisation step: same shell, a <Card> with the one-time-code <InputOTP> or a passkey prompt.",'OAuth device-grant screen (SCR-004): <AuthShell variant="canonical" preset="device-authorization"> \u2014 a 380px card at 1440/1024 and a 5px inline gutter at 390, with zero page-local CSS (gh#220).','Organisation / context selection (/select-context): <AuthShell variant="canonical" preset="context-selection" brand={<Logo mark="godx" />}> with an <AuthIdentity> intro, a <Card><CardContent flush> list of <ListRow as="li"> organisations, and a trailing "remember this choice" <Checkbox> \u2014 the preset spaces the three sections (gh#217).',"Password reset / forgot-password / accept-invite: the centred single-card flow with a brand bar and a legal footer.",'SSO landing / success confirmation: pair with an <EmptyState tone="success"> inside the card for an approved-device confirmation.'],related:["AppShell \u2014 the shell for AUTHENTICATED app pages (sidebar + topbar + main). AuthShell is its unauthenticated counterpart (brand bar + centred card + footer). Never nest the two.","Reveal \u2014 wrap the auth <Card> in <Reveal> for the entrance animation; AuthShell delegates motion (and prefers-reduced-motion handling) to it rather than baking an animation in.","Card \u2014 the canonical container for the auth form; place the form inside <CardContent>. AuthShell centres and width-constrains it.",'EmptyState \u2014 with `tone="success"` for a confirmation card inside the shell (e.g. device approved).','AuthIdentity / AuthFooter / AuthStack / AuthDivider \u2014 the auth composites that fill the shell: the GoDX identity mark + heading + requesting-client line, the mono legal footer (its `locale` slot takes an <AppSettingPicker kind="locale" appearance="labeled" compact />), the 12px section rhythm, and the labelled "or" rule.','ListRow \u2014 compose the organisation choice list for `preset="context-selection"` as <Card><CardContent flush><ul> of <ListRow as="li">: shared row dividers, no per-row card outline. There is no separate OrganizationChoiceList component \u2014 that is a composition pattern (rule #46), not a framework component.'],example:`import { AuthShell, AuthIdentity, AuthFooter } from "@godxjp/ui/layout";
|
|
52
52
|
import { Reveal, Logo, Button } from "@godxjp/ui/general";
|
|
53
53
|
import { Card, CardContent, CardHeader, CardTitle } from "@godxjp/ui/data-display";
|
|
54
|
-
import {
|
|
54
|
+
import { AppSettingPicker } from "@godxjp/ui/navigation";
|
|
55
55
|
|
|
56
|
-
export function
|
|
56
|
+
export function DeviceAuthorizationPage() {
|
|
57
57
|
return (
|
|
58
|
-
|
|
58
|
+
// 380px card at 1440/1024 \xB7 5px inline gutter at 390 \u2014 all token-owned, no page CSS.
|
|
59
|
+
<AuthShell
|
|
60
|
+
variant="canonical"
|
|
61
|
+
preset="device-authorization"
|
|
62
|
+
brand={<Logo mark="godx" tone="success" />}
|
|
63
|
+
footer={
|
|
64
|
+
<AuthFooter
|
|
65
|
+
product="GoDX ID"
|
|
66
|
+
terms="Terms"
|
|
67
|
+
privacy="Privacy"
|
|
68
|
+
locale={<AppSettingPicker kind="locale" appearance="labeled" compact />}
|
|
69
|
+
/>
|
|
70
|
+
}
|
|
71
|
+
>
|
|
59
72
|
<Reveal>
|
|
60
73
|
<Card>
|
|
61
74
|
<CardHeader>
|
|
62
|
-
<
|
|
75
|
+
<AuthIdentity title="\u30C7\u30D0\u30A4\u30B9\u3092\u8A8D\u8A3C" requester="\u52E4\u6020\u7BA1\u7406\u304C\u8A8D\u8A3C\u3092\u8981\u6C42\u3057\u3066\u3044\u307E\u3059" />
|
|
76
|
+
<CardTitle level={2}>\u78BA\u8A8D\u30B3\u30FC\u30C9\u3092\u5165\u529B</CardTitle>
|
|
63
77
|
</CardHeader>
|
|
64
78
|
<CardContent>
|
|
65
|
-
{/*
|
|
66
|
-
<Button fullWidth>\
|
|
79
|
+
{/* InputOTP + actions */}
|
|
80
|
+
<Button fullWidth>\u30C7\u30D0\u30A4\u30B9\u3092\u627F\u8A8D</Button>
|
|
67
81
|
</CardContent>
|
|
68
82
|
</Card>
|
|
69
83
|
</Reveal>
|
|
70
84
|
</AuthShell>
|
|
71
85
|
);
|
|
72
|
-
}`,storyPath:"layout/AuthShell.stories.tsx",rules:[23]},{name:"CenteredShell",group:"layout",tagline:"Authenticated, no-sidebar, centred-column page shell (hosted-ID My Page / account / standalone settings) \u2014 padded topbar with real actions + a width-tiered centred column, zero custom CSS.",props:[{name:"children",type:"ReactNode",required:!0,description:"Centred column content \u2014 page sections (identity hero, org picker, service grid, team list). Top-aligned and scrolls; NOT vertically centred like AuthShell's card."},{name:"topbar",type:"ReactNode",description:"Top bar slot (banner) \u2014 a <Topbar> with brand + real actions (AppSettingPicker, user menu, sign-out). Wrapped in the SAME padded chrome as AppShell's topbar (inline padding, border, backdrop) WITHOUT a sidebar; omit \u2192 no banner. Never hand-roll a bar (the bare Topbar ships no padding \u2014 the .ui-topbar zero-inset footgun)."},{name:"footer",type:"ReactNode",description:"Footer slot (contentinfo) pinned to the bottom (legal links, locale switch, support). Omit \u2192 no footer."},{name:"width",type:'"sm" | "md" | "lg"',description:"Max-width of the centred column: sm ~32rem, md (default) ~46rem, lg ~64rem \u2014 all wider than AuthShell's 24rem auth card. A service retunes each tier via --centered-shell-width-*."}],usage:["DO use CenteredShell for an AUTHENTICATED page that has a topbar with actions but NO sidebar \u2014 the hosted-ID 'My Page', an account / self-service surface, a standalone settings page. It is the third shell: AppShell (needs a sidebar) \xB7 AuthShell (unauthenticated narrow card) \xB7 CenteredShell (authenticated centred column).","DO put a <Topbar start={<brand/>} end={<actions/>}/> in `topbar` \u2014 CenteredShell wraps it in the padded `.app-topbar` chrome, so you get inline padding + border + backdrop with zero custom CSS. Do NOT hand-roll a bar with raw `padding-inline` \u2014 the bare Topbar primitive ships no inset (the .ui-topbar zero-padding footgun) and content sits flush to the edge.","DO pick `width` by content: `sm` (~32rem) for a single settings form, `md` (default, ~46rem) for a My Page of stacked sections, `lg` (~64rem) for a service-launcher grid. All are wider than AuthShell's 24rem card.","DO wrap an individual section in <Reveal> for entrance motion \u2014 CenteredShell stays layout-only and delegates prefers-reduced-motion handling to Reveal (same as AuthShell).","DO NOT use AuthShell for an authenticated page (it centres a narrow card VERTICALLY and has no actions slot), and DO NOT force AppShell with an empty sidebar \u2014 use CenteredShell. Never nest it inside AppShell/AuthShell (or vice-versa); it is a ROOT shell."],useCases:[`Hosted GoDX ID 'My Page': <CenteredShell topbar={<Topbar start={<Brand/>} end={<><AppSettingPicker kind="locale"/><UserMenu/></>}/>} footer={<Footer/>} width="md"> with an identity hero, an org picker, a service-launcher grid and a team list.`,"Account / self-service settings surface (no admin sidebar): stacked <Card> sections (profile, security, sessions) in a centred `md` column under a topbar with a user menu.",'Standalone single settings page: `width="sm"` with one <Card> of <Field>s and a save action.','Service launcher / app picker after sign-in: `width="lg"` with a <ResponsiveGrid> of app cards under the brand topbar.'],related:["AppShell \u2014 the shell for authenticated app pages WITH a sidebar nav rail. CenteredShell is its no-sidebar sibling (same padded topbar chrome, a centred column instead of a full-bleed main).","AuthShell \u2014 the UNAUTHENTICATED root shell (login/mfa/reset): a narrow ~24rem card centred vertically, no actions slot. CenteredShell is the AUTHENTICATED centred-page counterpart. Never nest the two.","Topbar \u2014 compose it into `topbar`; CenteredShell supplies the padded chrome the bare Topbar lacks.","PageContainer \u2014 for a titled section INSIDE the column; or compose <Card>/<ResponsiveGrid> sections directly."],example:`import { CenteredShell, Topbar, Flex } from "@godxjp/ui/layout";
|
|
86
|
+
}`,storyPath:"layout/AuthShell.stories.tsx",rules:[23]},{name:"CenteredShell",group:"layout",tagline:"Authenticated, no-sidebar, centred-column page shell (hosted-ID My Page / account / standalone settings) \u2014 padded topbar with real actions + a width-tiered centred column, zero custom CSS.",props:[{name:"children",type:"ReactNode",required:!0,description:"Centred column content \u2014 page sections (identity hero, org picker, service grid, team list). Top-aligned and scrolls; NOT vertically centred like AuthShell's card."},{name:"topbar",type:"ReactNode",description:"Top bar slot (banner) \u2014 a <Topbar> with brand + real actions (AppSettingPicker, user menu, sign-out). Wrapped in the SAME padded chrome as AppShell's topbar (inline padding, border, backdrop) WITHOUT a sidebar; omit \u2192 no banner. Never hand-roll a bar (the bare Topbar ships no padding \u2014 the .ui-topbar zero-inset footgun)."},{name:"footer",type:"ReactNode",description:"Footer slot (contentinfo) pinned to the bottom (legal links, locale switch, support). Omit \u2192 no footer."},{name:"width",type:'"sm" | "md" | "lg"',description:"Max-width of the centred column: sm ~32rem, md (default) ~46rem, lg ~64rem \u2014 all wider than AuthShell's 24rem auth card. A service retunes each tier via --centered-shell-width-*."},{name:"align",type:'"start" | "center"',description:'Block alignment of the centred column inside the 100dvh shell. "start" (default) keeps the top-aligned flowing/scrolling page shape. "center" centres the column in the viewport \u2014 the SYSTEM-level standalone surface (a 500/503 error page, a maintenance notice); the full-page geometry stays package-owned (--centered-shell-column-offset-block: auto) instead of a consumer writing min-h-dvh + flex centring or a className. Overflowing content still scrolls from the top, so a long localized message is never clipped.'},{name:"preset",type:'"default" | "public-landing"',defaultValue:'"default"',description:'Whole-page shell contract. "default" emits no attribute and keeps the exact box. "public-landing" owns the PUBLIC landing geometry (gh#252): ONE content measure shared by the header bar, the centred column and the footer (--centered-shell-landing-max-width, 67.5rem), the section rhythm between page sections, the flat elevation-free card chrome (--centered-shell-landing-card-shadow: none) and the hero h1 tier \u2014 plus the compact step at 40rem. A landing composition therefore needs no page-local CSS, no max-width wrapper and no descendant selector against shell internals.'}],usage:["DO use CenteredShell for an AUTHENTICATED page that has a topbar with actions but NO sidebar \u2014 the hosted-ID 'My Page', an account / self-service surface, a standalone settings page. It is the third shell: AppShell (needs a sidebar) \xB7 AuthShell (unauthenticated narrow card) \xB7 CenteredShell (authenticated centred column).","DO put a <Topbar start={<brand/>} end={<actions/>}/> in `topbar` \u2014 CenteredShell wraps it in the padded `.app-topbar` chrome, so you get inline padding + border + backdrop with zero custom CSS. Do NOT hand-roll a bar with raw `padding-inline` \u2014 the bare Topbar primitive ships no inset (the .ui-topbar zero-padding footgun) and content sits flush to the edge.","DO pick `width` by content: `sm` (~32rem) for a single settings form, `md` (default, ~46rem) for a My Page of stacked sections, `lg` (~64rem) for a service-launcher grid. All are wider than AuthShell's 24rem card.","DO wrap an individual section in <Reveal> for entrance motion \u2014 CenteredShell stays layout-only and delegates prefers-reduced-motion handling to Reveal (same as AuthShell).","DO NOT use AuthShell for an authenticated page (it centres a narrow card VERTICALLY and has no actions slot), and DO NOT force AppShell with an empty sidebar \u2014 use CenteredShell. Never nest it inside AppShell/AuthShell (or vice-versa); it is a ROOT shell.",'DO use `align="center"` (+ `width="sm"`) for a SYSTEM-level standalone page \u2014 a 500/503 error surface, a maintenance notice. It centres the column in the 100dvh shell at 1440/1024/390 with no consumer `min-h-dvh` / flex CSS and no className; the knob is --centered-shell-column-offset-block. For an actual 403/404/500/503 page do NOT wire this by hand \u2014 use `ErrorSurface`, which renders this shell itself in `mode="system"`.'],useCases:[`Hosted GoDX ID 'My Page': <CenteredShell topbar={<Topbar start={<Brand/>} end={<><AppSettingPicker kind="locale"/><UserMenu/></>}/>} footer={<Footer/>} width="md"> with an identity hero, an org picker, a service-launcher grid and a team list.`,"Account / self-service settings surface (no admin sidebar): stacked <Card> sections (profile, security, sessions) in a centred `md` column under a topbar with a user menu.",'Standalone single settings page: `width="sm"` with one <Card> of <Field>s and a save action.','System error / maintenance page (500 \xB7 503): `<CenteredShell align="center" width="sm">` around the canonical error body (status code <Text mono tabular> + <EmptyState icon tone title description action> + optional request-ID / maintenance line). See the `error-pages` pattern.','Service launcher / app picker after sign-in: `width="lg"` with a <ResponsiveGrid> of app cards under the brand topbar.'],related:["AppShell \u2014 the shell for authenticated app pages WITH a sidebar nav rail. CenteredShell is its no-sidebar sibling (same padded topbar chrome, a centred column instead of a full-bleed main).","AuthShell \u2014 the UNAUTHENTICATED root shell (login/mfa/reset): a narrow ~24rem card centred vertically, no actions slot. CenteredShell is the AUTHENTICATED centred-page counterpart. Never nest the two.","Topbar \u2014 compose it into `topbar`; CenteredShell supplies the padded chrome the bare Topbar lacks.","PageContainer \u2014 for a titled section INSIDE the column; or compose <Card>/<ResponsiveGrid> sections directly."],example:`import { CenteredShell, Topbar, Flex } from "@godxjp/ui/layout";
|
|
73
87
|
import { AppSettingPicker } from "@godxjp/ui/navigation";
|
|
74
88
|
import { Avatar, AvatarFallback, Card, CardContent, CardHeader, CardTitle } from "@godxjp/ui/data-display";
|
|
75
89
|
import { Button, Text } from "@godxjp/ui/general";
|
|
@@ -103,25 +117,31 @@ export function MyPage() {
|
|
|
103
117
|
</Flex>
|
|
104
118
|
</CenteredShell>
|
|
105
119
|
);
|
|
106
|
-
}`,storyPath:"layout/CenteredShell.stories.tsx",rules:[23]},{name:"Sidebar",group:"layout",tagline:"Data-driven vertical nav rail with collapsible submenu groups and a collapsed icon-only mode \u2014 never build nav manually with raw buttons.",props:[{name:"activeId",type:"string",required:!0,description:"The id of the currently active nav item. For group items, the parent is automatically highlighted when any descendant id matches."},{name:"sections",type:"SidebarSectionProp[]",required:!0,description:"Ordered list of nav sections. Each section has an optional string label and a required items array of SidebarItemProp."},{name:"onSelect",type:"(id: string) => void",description:"Called with the item id when a leaf nav item is clicked. Not called for group triggers or disabled items."},{name:"collapsed",type:"boolean",defaultValue:"false",description:"When true, renders the icon-only collapsed rail. Labels become Tooltips on hover; group items open a portaled flyout popover on click. Section labels are hidden."},{name:"product",type:"SidebarProductProp",description:"Renders a product/app chip at the top of the sidebar (name, optional role subtitle, optional color swatch). Mutually exclusive with brand \u2014 brand takes precedence."},{name:"onProductClick",type:"() => void",description:"Click handler for the product chip button. Use to open an entity/workspace switcher sheet or dropdown."},{name:"brand",type:"ReactNode",description:"Custom brand slot rendered above the nav scroll area. When provided, the product chip is not rendered."},{name:"footer",type:"ReactNode",description:"Slot pinned to the bottom of the sidebar below the scrollable nav area. Commonly used for user identity, online status, or version info."},{name:"aria-label",type:"string",description:`Override the nav landmark's accessible name (defaults to a localized "Main navigation"). Required when more than one Sidebar renders at once (e.g. a docked sidebar + its mobile-drawer twin) \u2014 two nav landmarks sharing one name/role fail landmark-unique.`}],usage:["DO: Define all nav items as a SidebarSectionProp[] data structure and pass it to sections \u2014 never hand-roll nav buttons alongside or instead of the Sidebar.","DO: Add
|
|
120
|
+
}`,storyPath:"layout/CenteredShell.stories.tsx",rules:[23]},{name:"Sidebar",group:"layout",tagline:"Data-driven vertical nav rail with collapsible submenu groups and a collapsed icon-only mode \u2014 never build nav manually with raw buttons.",props:[{name:"activeId",type:"string",required:!0,description:"The id of the currently active nav item. For group items, the parent is automatically highlighted when any descendant id matches."},{name:"sections",type:"SidebarSectionProp[]",required:!0,description:"Ordered list of nav sections. Each section has an optional string label and a required items array of SidebarItemProp."},{name:"onSelect",type:"(id: string) => void",description:"Called with the item id when a leaf nav item is clicked. Not called for group triggers or disabled items."},{name:"collapsed",type:"boolean",defaultValue:"false",description:"When true, renders the icon-only collapsed rail. Labels become Tooltips on hover; group items open a portaled flyout popover on click. Section labels are hidden."},{name:"product",type:"SidebarProductProp",description:"Renders a product/app chip at the top of the sidebar (name, optional role subtitle, optional color swatch). Mutually exclusive with brand \u2014 brand takes precedence."},{name:"onProductClick",type:"() => void",description:"Click handler for the product chip button. Use to open an entity/workspace switcher sheet or dropdown."},{name:"brand",type:"ReactNode",description:"Custom brand slot rendered above the nav scroll area. When provided, the product chip is not rendered."},{name:"footer",type:"ReactNode",description:"Slot pinned to the bottom of the sidebar below the scrollable nav area. Commonly used for user identity, online status, or version info."},{name:"linkComponent",type:"SidebarLinkComponentProp",description:"THE framework-router contract (gh#213). Supply only the link ELEMENT TYPE; the library keeps composing every row \u2014 icon slot, label, badge, data-active/aria-current, the icon-only collapsed rail and its tooltip name \u2014 and passes it to the link as SidebarLinkProp children. Applied to every row that carries an href: top-level leaves, submenu children, collapsed-rail leaves and collapsed flyout entries. Build it with createSidebarLink(Link, 'to') for React Router / TanStack, createSidebarLink(Link) or inertiaSidebarLink(Link) from @godxjp/ui/inertia for Inertia / Next.js. Rows without an href keep the button + onSelect shape; a group TRIGGER stays a button because it owns aria-expanded disclosure semantics."},{name:"renderItem",type:"(item: SidebarItemData, rowProps: SidebarRenderItemProp) => ReactNode",description:"DEPRECATED (gh#213) \u2014 use linkComponent, or asChild on SidebarItem. Legacy escape hatch that left row CONTENT to the caller, which is how a <Link>{item.label}</Link> silently dropped every icon and badge in production. Still supported and still wins over linkComponent; rowProps now also carries the library-composed children, so spreading rowProps (or rendering rowProps.children) restores the canonical row."},{name:"aria-label",type:"string",description:`Override the nav landmark's accessible name (defaults to a localized "Main navigation"). Required when more than one Sidebar renders at once (e.g. a docked sidebar + its mobile-drawer twin) \u2014 two nav landmarks sharing one name/role fail landmark-unique.`}],usage:["DO: Define all nav items as a SidebarSectionProp[] data structure and pass it to sections \u2014 never hand-roll nav buttons alongside or instead of the Sidebar.","DO: Add children: SidebarItemProp[] to any SidebarItemProp to create a collapsible submenu group. The parent item's icon is required even for groups. The group auto-opens and highlights when activeId matches any descendant.","DO: Mirror the collapsed boolean between AppShell's sidebarCollapsed prop and Sidebar's collapsed prop \u2014 they must stay in sync so the shell layout grid adjusts correctly.","DO: Use the footer prop for user info or status \u2014 it is pinned below the scroll area and does not scroll away.","DO: Give every navigable item an `item.href` and let the library render it. A plain `href` becomes a real <a> (context-menu open-in-new-tab, middle-click); with `linkComponent` that same href drives your framework router's <Link>. Either way the link IS the row and the ONLY interactive element (no nested <button>). Never put a <button>/<a> inside a default row.","DO: Wire a framework router with `linkComponent` \u2014 `createSidebarLink(Link, 'to')` (React Router / TanStack), `createSidebarLink(Link)` (Next.js), or `inertiaSidebarLink(Link)` from `@godxjp/ui/inertia`. That is the WHOLE integration: you pass the element type, the library composes the row. It threads through leaves, submenu children, the collapsed rail and the collapsed flyout. When you compose rows by hand, the same contract is `<SidebarItem item={item} asChild><Link to=\u2026 /></SidebarItem>` \u2014 write NO children; the library injects the icon, label and badge.","DON'T: Use `renderItem` in new code \u2014 it is DEPRECATED (gh#213). It hands you a className + active state and leaves the row CONTENT to you, so `renderItem={(item) => <Link href={\u2026}>{item.label}</Link>}` renders a row with NO icon and NO badge. That is the exact production regression that motivated `linkComponent`. If you must keep it, render `rowProps.children` (the library-composed row) instead of hand-writing `.sb-icon` / `.sb-label` spans, which are internal class names and not a public contract.","DO: Rely on route-synchronized group expansion \u2014 a group OPENS automatically whenever `activeId` moves to one of its children (e.g. after a deep-link navigation), revealing the newly-active child; users can still collapse/expand manually.","DO: Theme the nav ICON and the row/label SEPARATELY with tokens (gh#228) \u2014 the icon reads `--sidebar-nav-icon-foreground` (+ `-hover-`/`-active-`/`-disabled-` variants) and the row/label reads `--sidebar-nav-item-foreground` (+ `-hover-`/`-disabled-`). Defaults are unchanged (both = `hsl(var(--muted-foreground))`, hover/active = `hsl(var(--foreground))`), so setting `--sidebar-nav-icon-foreground: hsl(var(--foreground))` in your theme is all it takes to get canonical darker 16px icons beside muted labels. NEVER write a page-local `.sb-nav-item svg { color: \u2026 }` rule and never re-tint `--muted-foreground` globally to fix sidebar icons.","DO: Give every item an `icon` \u2014 it is required by SidebarItemProp and by the canonical rail (the collapsed mode is icon-only). An item whose data arrives without one no longer crashes the shell; it renders an EMPTY 16px icon slot so the row keeps its geometry and label column, but it reads as a hole in the rail.","DON'T: Change icon SIZE or row geometry through these colour knobs \u2014 icon size stays `--sidebar-nav-icon-size` (16px) and row geometry stays `--sidebar-nav-item-height` / `--sidebar-nav-item-gap` / `--sidebar-nav-item-padding-x`. The active row's fill/label keep `--sidebar-item-active-background` / `--sidebar-item-active-foreground`.","DON'T: Manage collapse state inside the Sidebar \u2014 it is stateless. Hoist the boolean to your shell/page state and pass it down via both AppShell.sidebarCollapsed and Sidebar.collapsed.","DON'T: Nest children more than one level deep \u2014 only top-level items can have children; grandchild items are not rendered."],useCases:["Admin application shell nav with grouped sections (e.g. Operations / Fulfillment / Administration) where the sidebar can be collapsed to an icon rail for more content space.","Accounting app with a collapsible 'Ledger' group containing Journal, Chart of Accounts, and Period Close sub-pages \u2014 activeId reflects the current sub-page and the group stays open automatically.","Multi-tenant SaaS where onProductClick opens an entity/legal-entity switcher sheet and product.role shows the active tenant name beneath the product logo.","Any app using AppShell where navigation must degrade gracefully to an icon-only rail on narrow viewports or via a user toggle in the Topbar.","Apps with infrequent-access admin pages (Users, Roles, Password) grouped in a dedicated section that appears below primary operations sections."],related:["AppShell \u2014 the shell that hosts Sidebar in its sidebar slot and owns the sidebarCollapsed layout grid; always compose Sidebar inside AppShell, not standalone in a page.","Topbar \u2014 the horizontal bar that renders the collapse toggle (onToggleCollapsed) and its collapsed prop must mirror the sidebar's collapsed state.","PageContainer \u2014 used for page-level title/subtitle/extra/breadcrumb inside AppShell's children slot, not inside Sidebar."],example:`
|
|
107
121
|
{\`import { useState } from "react";
|
|
108
122
|
import { LayoutDashboard, FileText, Users, Shield, CreditCard, BookOpen } from "lucide-react";
|
|
109
|
-
import {
|
|
123
|
+
import { Link } from "react-router-dom";
|
|
124
|
+
import { AppShell, createSidebarLink } from "@godxjp/ui/layout";
|
|
110
125
|
import { Sidebar, type SidebarSection } from "@godxjp/ui/layout";
|
|
111
126
|
import { Topbar } from "@godxjp/ui/layout";
|
|
112
127
|
|
|
128
|
+
// The WHOLE router integration (gh#213): pass the element type, the library composes every row
|
|
129
|
+
// (icon \xB7 label \xB7 badge \xB7 active \xB7 collapsed rail). Inertia: inertiaSidebarLink(Link) from
|
|
130
|
+
// "@godxjp/ui/inertia". Next.js: createSidebarLink(Link).
|
|
131
|
+
const NavLink = createSidebarLink(Link, "to");
|
|
132
|
+
|
|
113
133
|
const sections: SidebarSection[] = [
|
|
114
134
|
{
|
|
115
135
|
label: "Accounting",
|
|
116
136
|
items: [
|
|
117
|
-
{ id: "dashboard", label: "Dashboard", icon: LayoutDashboard },
|
|
137
|
+
{ id: "dashboard", label: "Dashboard", icon: LayoutDashboard, href: "/dashboard" },
|
|
118
138
|
{
|
|
119
139
|
id: "ledger",
|
|
120
140
|
label: "Ledger",
|
|
121
141
|
icon: BookOpen,
|
|
122
|
-
|
|
123
|
-
{ id: "journal", label: "Journal", icon: FileText },
|
|
124
|
-
{ id: "chart-of-accounts", label: "Chart of Accounts", icon: CreditCard },
|
|
142
|
+
children: [
|
|
143
|
+
{ id: "journal", label: "Journal", icon: FileText, href: "/ledger/journal" },
|
|
144
|
+
{ id: "chart-of-accounts", label: "Chart of Accounts", icon: CreditCard, href: "/ledger/coa" },
|
|
125
145
|
],
|
|
126
146
|
},
|
|
127
147
|
],
|
|
@@ -129,8 +149,8 @@ const sections: SidebarSection[] = [
|
|
|
129
149
|
{
|
|
130
150
|
label: "Administration",
|
|
131
151
|
items: [
|
|
132
|
-
{ id: "users", label: "Users", icon: Users },
|
|
133
|
-
{ id: "roles", label: "Roles", icon: Shield, disabled: true },
|
|
152
|
+
{ id: "users", label: "Users", icon: Users, href: "/users" },
|
|
153
|
+
{ id: "roles", label: "Roles", icon: Shield, href: "/roles", disabled: true },
|
|
134
154
|
],
|
|
135
155
|
},
|
|
136
156
|
];
|
|
@@ -148,6 +168,7 @@ export default function Shell() {
|
|
|
148
168
|
collapsed={collapsed}
|
|
149
169
|
onSelect={setActiveId}
|
|
150
170
|
sections={sections}
|
|
171
|
+
linkComponent={NavLink}
|
|
151
172
|
product={{ name: "CoreBooks", role: "Admin Console", color: "hsl(var(--primary))" }}
|
|
152
173
|
onProductClick={() => {/* open entity switcher */}}
|
|
153
174
|
footer={
|
|
@@ -171,7 +192,7 @@ export default function Shell() {
|
|
|
171
192
|
</AppShell>
|
|
172
193
|
);
|
|
173
194
|
}\`}
|
|
174
|
-
`,storyPath:"layout/Sidebar.preview.tsx",rules:[3,6,23,31]},{name:"Topbar",group:"layout",tagline:"A PURE SLOT bar for the app shell \u2014 positions three clusters (start / center / end) and owns ONLY the bar layout. It bakes NO chrome: no product switcher, no search box, no notification bell, no language picker. The CONSUMER composes those from real primitives and drops them into a slot; whether a control is icon-only / labelled / bordered is that control's own config, never the shell's.",props:[{name:"start",type:"ReactNode",defaultValue:"undefined",description:"Inline-start cluster \u2014 typically the sidebar toggle (a `Button` with a `PanelLeftClose` icon), the brand mark (an `Avatar`), and primary nav."},{name:"center",type:"ReactNode",defaultValue:"undefined",description:"Center cluster (grows + centers) \u2014 optional. e.g. a search trigger (`Button` + `Search` icon opening your command palette) or a page/entity switcher (`DropdownMenu`)."},{name:"end",type:"ReactNode",defaultValue:"undefined",description:'Inline-end cluster \u2014 settings pickers (`AppSettingPicker kind="locale"|"theme"`), a notifications `Button`, the user-menu `DropdownMenu`.'},{name:"children",type:"ReactNode",defaultValue:"undefined",description:"Escape hatch \u2014 render fully custom bar content instead of the three slots. When set, `start`/`center`/`end` are ignored."},{name:"className",type:"string",description:"Merged onto the bar element; arbitrary props (aria-*, etc.) are forwarded."}],usage:["DO compose the bar yourself: a brand mark (an `Avatar`) + sidebar toggle in `start`, a search trigger in `center`, settings pickers + notifications + user menu in `end`. The shell only positions; it never decides WHICH controls exist.",'DO build the sidebar toggle as a `Button variant="ghost" size="icon-sm"` with a `PanelLeftClose`/`PanelLeftOpen` icon and your own `t()` aria-label, wired to AppShell\'s `sidebarCollapsed`. There is no baked toggle.',"DO put a locale/theme switcher in `end` using `AppSettingPicker` (or your own control) \u2014 icon-only vs labelled, bordered vs not, is THAT component's prop, not Topbar's. Topbar does not ship or force a language picker.","DON'T look for `product`/`project`/`onSearchOpen`/`onNotificationsOpen`/`collapsed` props \u2014 they were removed. A chrome control only exists if YOU put it in a slot, so there is never a dead dropdown / empty search with nothing behind it.","DO render Topbar inside `AppShell`'s `topbar` slot (or any `<header>`). For a non-three-cluster layout, pass `children` and lay it out yourself."],useCases:["Admin shell: `start` = sidebar toggle + brand mark (`Avatar`) + an entity switcher (`DropdownMenu` around a `Button`); `center` = a `Button` search trigger; `end` = `AppSettingPicker` (locale) + a notifications `Button` + a user `DropdownMenu`.","Minimal shell (no search, no notifications): pass only `start` (brand mark) and `end` (user menu). Nothing else renders \u2014 no empty chrome.","Marketing / docs header: pass `children` with a fully custom flex layout when the three-cluster model doesn't fit."],related:["AppShell \u2014 place Topbar in its `topbar` slot. AppShell also exposes its own `logo`/`topbarLeft`/`topbarRight` slots if you don't want a separate Topbar at all.",'Avatar \u2014 the brand mark for the `start` slot (use `className="rounded-md"` for a square-ish product glyph).',"AppSettingPicker \u2014 locale/theme/timezone/currency picker; the consumer drops it into `end`. Its appearance (icon-only, labelled, bordered) is configured on IT, not on Topbar.","DropdownMenu \u2014 wrap a `Button` to build an entity switcher or user menu yourself, then place it in a slot."],example:`import { Topbar, AppShell } from "@godxjp/ui/layout";
|
|
195
|
+
`,storyPath:"layout/Sidebar.preview.tsx",rules:[3,6,23,31]},{name:"Topbar",group:"layout",tagline:"A PURE SLOT bar for the app shell \u2014 positions three clusters (start / center / end) and owns ONLY the bar layout. It bakes NO chrome: no product switcher, no search box, no notification bell, no language picker. The CONSUMER composes those from real primitives and drops them into a slot; whether a control is icon-only / labelled / bordered is that control's own config, never the shell's.",props:[{name:"start",type:"ReactNode",defaultValue:"undefined",description:"Inline-start cluster \u2014 typically the sidebar toggle (a `Button` with a `PanelLeftClose` icon), the brand mark (an `Avatar`), and primary nav."},{name:"center",type:"ReactNode",defaultValue:"undefined",description:"Center cluster (grows + centers) \u2014 optional. e.g. a search trigger (`Button` + `Search` icon opening your command palette) or a page/entity switcher (`DropdownMenu`)."},{name:"end",type:"ReactNode",defaultValue:"undefined",description:'Inline-end cluster \u2014 settings pickers (`AppSettingPicker kind="locale"|"theme"`), a notifications `Button`, the user-menu `DropdownMenu`.'},{name:"children",type:"ReactNode",defaultValue:"undefined",description:"Escape hatch \u2014 render fully custom bar content instead of the three slots. When set, `start`/`center`/`end` are ignored."},{name:"className",type:"string",description:"Merged onto the bar element; arbitrary props (aria-*, etc.) are forwarded."}],usage:["DO compose the bar yourself: a brand mark (an `Avatar`) + sidebar toggle in `start`, a search trigger in `center`, settings pickers + notifications + user menu in `end`. The shell only positions; it never decides WHICH controls exist.",'DO build the sidebar toggle as a `Button variant="ghost" size="icon-sm"` with a `PanelLeftClose`/`PanelLeftOpen` icon and your own `t()` aria-label, wired to AppShell\'s `sidebarCollapsed`. There is no baked toggle.',"DO put a locale/theme switcher in `end` using `AppSettingPicker` (or your own control) \u2014 icon-only vs labelled, bordered vs not, is THAT component's prop, not Topbar's. Topbar does not ship or force a language picker.","DON'T look for `product`/`project`/`onSearchOpen`/`onNotificationsOpen`/`collapsed` props \u2014 they were removed. A chrome control only exists if YOU put it in a slot, so there is never a dead dropdown / empty search with nothing behind it.","DO render Topbar inside `AppShell`'s `topbar` slot (or any `<header>`). For a non-three-cluster layout, pass `children` and lay it out yourself.","DO rely on the built-in shrink contract instead of hand-tuning widths: the bar never exceeds its shell allocation, `start` shrinks first and `center` yields its whole box, each cluster CLIPS its own overflow (so a long tenant/brand string can never spill over a sibling or leak a horizontal document scroll), and `end` keeps its natural width anchored inline-end \u2014 the locale picker and user menu stay visible at 1024px with a 16rem sidebar. If a label must degrade gracefully rather than be cut, give THAT element `truncate`/`text-overflow` yourself; don't add `overflow`/`flex` overrides to the slots."],useCases:["Admin shell: `start` = sidebar toggle + brand mark (`Avatar`) + an entity switcher (`DropdownMenu` around a `Button`); `center` = a `Button` search trigger; `end` = `AppSettingPicker` (locale) + a notifications `Button` + a user `DropdownMenu`.","Minimal shell (no search, no notifications): pass only `start` (brand mark) and `end` (user menu). Nothing else renders \u2014 no empty chrome.","Marketing / docs header: pass `children` with a fully custom flex layout when the three-cluster model doesn't fit."],related:["AppShell \u2014 place Topbar in its `topbar` slot. AppShell also exposes its own `logo`/`topbarLeft`/`topbarRight` slots if you don't want a separate Topbar at all.",'Avatar \u2014 the brand mark for the `start` slot (use `className="rounded-md"` for a square-ish product glyph).',"AppSettingPicker \u2014 locale/theme/timezone/currency picker; the consumer drops it into `end`. Its appearance (icon-only, labelled, bordered) is configured on IT, not on Topbar.","DropdownMenu \u2014 wrap a `Button` to build an entity switcher or user menu yourself, then place it in a slot."],example:`import { Topbar, AppShell } from "@godxjp/ui/layout";
|
|
175
196
|
import { Button } from "@godxjp/ui/general";
|
|
176
197
|
import { Avatar, AvatarFallback } from "@godxjp/ui/data-display";
|
|
177
198
|
import { AppSettingPicker } from "@godxjp/ui/navigation";
|
|
@@ -208,11 +229,105 @@ import { PanelLeftClose, Search } from "lucide-react";
|
|
|
208
229
|
}
|
|
209
230
|
>
|
|
210
231
|
{children}
|
|
211
|
-
</AppShell>`,storyPath:"layout/Topbar.stories.tsx",rules:[2,3,5,6]},{name:"
|
|
232
|
+
</AppShell>`,storyPath:"layout/Topbar.stories.tsx",rules:[2,3,5,6]},{name:"MasterDetail",group:"layout",tagline:"Responsive master-detail composition: a fluid list beside a token-owned 300px/320px fixed rail, with a themeable collapse threshold. `rail` picks which region is fixed \u2014 default `detail` (the canonical 1fr/320px list + detail rail); `master` for a leading navigator rail.",props:[{name:"master",type:"ReactNode",required:!0,description:"Selectable collection. Always FIRST in DOM order, so the stacked (mobile) order is list-then-detail whichever region owns the rail."},{name:"children",type:"ReactNode",required:!0,description:"Detail surface for the current master selection."},{name:"rail",type:'"master" | "detail"',defaultValue:'"detail"',description:"Which region keeps the fixed track; the other one is fluid. `detail` = the canonical fluid-list + fixed detail rail. `master` = leading category/navigator rail beside a fluid detail surface."},{name:"railWidth",type:'"compact" | "standard"',defaultValue:'"standard"',description:"Token-owned rail preset: compact=300px (--master-detail-rail-compact), standard=320px (--master-detail-rail-standard)."},{name:"masterViewport",type:'"auto" | "compact" | "standard"',defaultValue:'"auto"',description:"Bound the master collection to a scrollable viewport. `auto` (default) never bounds it \u2014 the region grows with its content. `compact` (--master-detail-master-viewport-compact, 20rem) / `standard` (28rem) cap its block size and scroll the collection INSIDE the region, and make that region a keyboard-reachable scroll container (tabIndex 0, --master-detail-master-viewport-inset reserves focus-ring room). No raw pixel prop exists \u2014 retune the tokens in a service theme."},{name:"collapseBelow",type:'"sm" | "md" | "lg" | "xl" | false',description:"Per-instance stacking threshold (sm=40rem, md=48rem, lg=64rem, xl=80rem; false never stacks). OMIT it to inherit the themeable --master-detail-collapse-below token (default 40rem) \u2014 that is the global knob a service theme retunes once."},{name:"masterLabel",type:"string",description:"Accessible name for the master region landmark."},{name:"detailLabel",type:"string",description:"Accessible name for the detail region landmark."},{name:"detailId",type:"string",description:"Id of the detail region so the selection controls inside `master` can point at it with aria-controls, and the app can move focus to it after a selection (the region carries tabIndex={-1} for exactly this)."}],usage:['DO use MasterDetail for team, member, service or settings screens where a selectable collection drives a detail surface. Default `rail="detail"` gives the canonical fluid list + 320px detail rail; pass `rail="master"` for a leading navigator rail.',"DO keep selection and keyboard semantics on the controls inside `master` (`aria-pressed`, roving focus, or listbox semantics as appropriate) \u2014 only the caller knows which APG pattern applies; MasterDetail preserves whatever you render.","DO wire the two together: give the master controls `aria-controls={detailId}`, and move focus to `document.getElementById(detailId)` when a selection replaces the detail (the region is `tabIndex={-1}` so that focus call works).","DO choose `compact` for the 300px rail and `standard` for 320px. Never reproduce these tracks with consumer CSS.","DO provide `masterLabel` and `detailLabel` when the surrounding headings do not already identify both regions.","DON'T use ResponsiveGrid for master-detail hierarchy; its equal tracks cannot represent this composition.",'DO set `masterViewport="compact"` (or `"standard"`) whenever the collection is a REAL, unbounded list. Left at `auto` a 200-row list renders ~3,700px tall, and once the layout stacks the detail lands thousands of pixels below the fold; bounded, the collection scrolls inside the rail and the detail stays near the top of the screen. Pass `masterLabel` too, so the scroll region is announced.',"DON'T reproduce that bound with a consumer `max-height`/`overflow` rule or a pixel prop \u2014 there is none by design. Retune `--master-detail-master-viewport-compact` / `-standard` in the service theme, and `--master-detail-master-viewport-inset` if the collection's focus ring needs more room.","The collapse threshold is a real token: below `--master-detail-collapse-below` (default 40rem, measured against the COMPOSITION's own inline size, never the viewport) master stacks above detail. A theme retunes it globally, `collapseBelow` overrides it per instance. It is implemented as a flex-basis threshold rather than a media query precisely because a query CONDITION cannot read a var().","Measured geometry with the defaults: 1440 \u2192 fluid master + 320px rail; 1024 \u2192 fluid master + 320px rail; 390 \u2192 stacked full-width master then detail. Same JSX at every width \u2014 no consumer-local CSS, grid tracks or per-screen spacing."],useCases:["Teams screen: the team DataTable stays fluid while the selected team's detail keeps the 320px rail.",'Organization service access: services in the compact leading rail (`rail="master"`) and selected service roles in the fluid detail surface.',"Settings navigator: categories in the leading rail and the selected configuration form in the detail surface."],related:["SplitPane \u2014 a main surface plus a COMPLEMENTARY aside (its own content), not a selection-driven detail; use MasterDetail when the trailing pane is the detail OF the selection.","ResponsiveGrid \u2014 use for equal-width independent cards, never for a master-detail relationship.","PageContainer \u2014 outer page scaffold that supplies page insets and title hierarchy around MasterDetail."],example:`import { MasterDetail } from "@godxjp/ui/layout";
|
|
233
|
+
|
|
234
|
+
// Canonical: fluid list + fixed 320px detail rail (stacks below 40rem).
|
|
235
|
+
<MasterDetail
|
|
236
|
+
masterLabel="Teams"
|
|
237
|
+
detailLabel="Selected team"
|
|
238
|
+
detailId="team-detail"
|
|
239
|
+
master={<TeamTable onRowClick={select} detailId="team-detail" />}
|
|
240
|
+
>
|
|
241
|
+
<TeamDetail team={selected} />
|
|
242
|
+
</MasterDetail>
|
|
243
|
+
|
|
244
|
+
// Leading navigator rail instead.
|
|
245
|
+
<MasterDetail rail="master" railWidth="compact" masterLabel="Categories">
|
|
246
|
+
<SettingsForm />
|
|
247
|
+
</MasterDetail>
|
|
248
|
+
|
|
249
|
+
// A long real collection: bound the master so it scrolls in place and the detail
|
|
250
|
+
// stays near the top of a stacked mobile page.
|
|
251
|
+
<MasterDetail masterViewport="compact" masterLabel="Members" detailLabel="Selected member">
|
|
252
|
+
<MemberDetail member={selected} />
|
|
253
|
+
</MasterDetail>`,storyPath:"layout/MasterDetail.stories.tsx",rules:[24,40]},{name:"SplitPane",group:"layout",tagline:"Two-column layout with a main content area and a fixed-width aside panel.",props:[{name:"children",type:"ReactNode",required:!0,description:"Main (left) content."},{name:"aside",type:"ReactNode",required:!0,description:"Aside (right) panel content."},{name:"asideWidth",type:'"sm" | "md"',defaultValue:'"md"',description:"Width preset for the aside column."}],usage:["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.",'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).',"DO: wrap SplitPane inside `PageContainer` or `PageContainer.Inset` \u2014 SplitPane provides no page padding of its own. It is a grid primitive, not a page scaffold.","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.","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.","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."],useCases:["Invoice / transaction detail page: list of records in `children` (DataTable), selected-record detail panel in `aside` (Descriptions + Timeline).",'Accounting ledger drill-down: account list on the left, chart-of-accounts metadata or running balance breakdown on the right using `asideWidth="sm"`.',"Document review workflow: PDF or rich-text viewer in `children`, approval form or annotation panel in `aside`.","Settings page with a category list or Steps navigator in `children` and a live preview or summary card in `aside`.","Kanban or task board where the main area holds the board columns and the aside shows the focused task detail without navigating away."],related:["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.","PageContainer \u2014 use as the outer scaffold that provides page padding and vertical rhythm; nest SplitPane inside PageContainer, not the other way around.","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.","Flex direction='col' \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."],example:`import { SplitPane } from "@godxjp/ui/layout";
|
|
212
254
|
|
|
213
255
|
<SplitPane aside={<DetailPanel />} asideWidth="sm">
|
|
214
256
|
<MainContent />
|
|
215
|
-
</SplitPane>`,storyPath:"layout/SplitPane.stories.tsx",rules:[24]},{name:"
|
|
257
|
+
</SplitPane>`,storyPath:"layout/SplitPane.stories.tsx",rules:[24]},{name:"ErrorSurface",group:"layout",tagline:'Package-owned semantic exception surface for 403 / 404 / 500 / 503. `mode` is the SHELL CONTRACT: "application" renders the body you put inside the AppShell the route already provides (chrome PRESERVED, never reconstructed); "system" owns the whole page via CenteredShell align="center" (package-owned 1440/1024/390 geometry). Exactly one recovery action, plus semantic request-id / permission / organization / maintenance metadata slots.',props:[{name:"mode",type:'"application" | "system"',required:!0,description:`The shell contract, not a skin. "application" (403/404) renders ONLY the surface block \u2014 put it in AppShell's children (usually inside a PageContainer) so the sidebar/topbar/breadcrumb stay mounted; it cannot build chrome, because nav data and the user menu are consumer-owned. "system" (500/503) renders the whole page: CenteredShell align="center", so no consumer min-h-dvh / flex class / media query.`},{name:"status",type:"403 | 404 | 500 | 503",required:!0,description:"The HTTP status (a NUMBER, not a string). It is the input that drives the default icon (ShieldAlert 403 \xB7 SearchX 404 \xB7 ServerCrash 500 \xB7 Wrench 503) and tone (warning 403/503 \xB7 muted 404 \xB7 destructive 500). Also rendered as the compact tabular status code, announced as 'HTTP status 403' rather than the cardinal number."},{name:"title",type:"ReactNode",required:!0,description:"Headline. Consumer-owned copy from the APP's own t() \u2014 @godxjp/ui ships no product text."},{name:"action",type:"ReactNode",required:!0,description:"The ONE recovery action (a Button, or Button asChild wrapping a router Link). A single slot IS the enforcement: pass a fragment with two buttons and only the first renders, with a development-time console error. Support contact goes in `description`, never in a second CTA."},{name:"description",type:"ReactNode",description:"Supporting sentence under the title. Put support-contact guidance here. Its measure is owned by --empty-state-description-max-width."},{name:"requestId",type:"string",description:"Support correlation id, rendered as a <dt>/<dd> metadata row with a localized 'Request ID' label and a mono/tabular value so it can be read out or copied accurately. Pass the bare id \u2014 never write it into `description` as prose."},{name:"permission",type:"ReactNode",description:"The permission/role the viewer is missing (403). Pass the bare permission name ('reports.view'); the localized 'Required permission' label is the surface's."},{name:"organization",type:"ReactNode",description:"The organization/tenant the failed request was scoped to. Together with `permission` this is what distinguishes a wrong-workspace 403 from a missing-role 403."},{name:"maintenance",type:"{ start: string; end?: string; timeZone?: string; progress?: number }",description:"Planned-outage timing (503). `start`/`end` are ISO-8601 INSTANTS and `timeZone` an IANA id: the surface formats them with Intl.DateTimeFormat(locale).formatRange() and keeps the ISO value in <time dateTime>. NEVER pass a pre-formatted '18:00 - 20:00 JST' string. `progress` (0-100) adds a labelled Progress meter named via Intl.NumberFormat percent style; it is SERVER-SENT on purpose \u2014 deriving it from the client clock breaks SSR hydration."},{name:"icon",type:"ComponentType<{ className?: string }>",description:"Override the status-derived icon. Use only when the product has a truer glyph for the failure, never to change perceived severity."},{name:"tone",type:'"muted" | "info" | "success" | "warning" | "destructive"',description:"Override the status-derived tone (same union as EmptyState.tone). Defaults: 403/503 warning \xB7 404 muted \xB7 500 destructive."},{name:"titleLevel",type:"1 | 2 | 3 | 4",description:"Semantic heading level of `title`. Defaults to 2 in application mode (a PageContainer h1 sits above) and 1 in system mode (the surface IS the page). Choose it to keep the outline valid, never for size."},{name:"brand",type:"ReactNode",description:"system mode ONLY \u2014 brand slot above the status code (a Logo). Ignored in application mode, where the shell already shows the brand."},{name:"footer",type:"ReactNode",description:"system mode ONLY \u2014 the page footer (contentinfo): copyright, status page link, locale switch."},{name:"width",type:'"sm" | "md" | "lg"',defaultValue:'"sm"',description:"system mode ONLY \u2014 measure of the centred column (the CenteredShell width tier)."},{name:"id",type:"string",description:"Root element id."},{name:"className",type:"string",description:"Root class override (rarely needed)."}],usage:["DO use ErrorSurface for ANY 403 / 404 / 500 / 503 page. It is a real import from @godxjp/ui/layout \u2014 never hand-compose an error page from AuthShell + a generic Card, and never add a consumer-local `.canonical-auth-card`-style class (that workaround IS the gh#251 regression).",'DO put mode="application" INSIDE the shell the route already renders: <AppShell \u2026><PageContainer \u2026><ErrorSurface mode="application" \u2026/></PageContainer></AppShell>. The surface returns only its own block on purpose, so the sidebar/topbar/breadcrumb survive and the user can navigate away.','DO use mode="system" for 500/503 and pass NOTHING for geometry \u2014 the surface renders CenteredShell align="center" itself. A className="min-h-dvh flex items-center" is always wrong.',"DO pass ONE action. A second CTA is dropped with a development error; put 'contact support' in `description`.","DO pass `maintenance` as ISO-8601 instants + an IANA timeZone and let Intl format it. A hand-built window string cannot localize, and a client-derived `progress` breaks SSR hydration.","DO let `status` pick the icon and tone. Override them only for a truer product glyph \u2014 never to recolour a status into a different severity.","DO NOT retune the geometry with a className: --error-surface-max-width, --error-surface-gap, --error-surface-padding-block(-compact), --error-surface-meta-* and --error-surface-progress-max-width are the knobs. The metadata divider defaults to `none` (rule #44); opt in with --error-surface-meta-border.","DO NOT write the request id, the missing permission or the maintenance window into `description` as prose \u2014 each is a semantic <dt>/<dd> slot, and prose loses the label\u2194value relationship for a screen reader.","DO NOT use AuthShell for an error page: it is the UNAUTHENTICATED root and imposes auth-card geometry."],useCases:['Inertia/Laravel exception page (SCR-006): one Error.tsx receives `status` from Handler::render() and renders <ErrorSurface mode={status >= 500 ? "system" : "application"} status={status} \u2026 />, with Error.layout keeping the authenticated shell for 403/404 only.','Forbidden report inside the console: <ErrorSurface mode="application" status={403} permission="reports.view" organization="Acme KK" action={<Button asChild><Link href="/">Back</Link></Button>} /> inside the existing AppShell + PageContainer.','Planned maintenance page: <ErrorSurface mode="system" status={503} maintenance={{ start, end, timeZone: "Asia/Tokyo", progress: 40 }} brand={<Logo glyph="G" />} footer={\u2026} />.','Unexpected failure with a support correlation id: <ErrorSurface mode="system" status={500} requestId="01J9Z0\u2026" action={<Button onClick={reload}>Reload</Button>} />.'],related:['CenteredShell \u2014 the viewport-centred page shell that ErrorSurface renders internally for mode="system". Use it directly only for a standalone surface that is NOT one of the four HTTP statuses.','AppShell + PageContainer \u2014 what you wrap around a mode="application" surface. ErrorSurface never builds them: nav sections and the user menu are consumer-owned data.',"EmptyState \u2014 the zero-state primitive ErrorSurface composes for its icon/title/description/action body. Use it directly for an empty LIST or an empty section, not for an HTTP exception page.","AlertQueryError / humanError \u2014 inline, in-page query failure feedback. ErrorSurface is the whole PAGE; an inline alert is the right pick when the surrounding page still works.","Progress \u2014 the meter ErrorSurface renders for maintenance.progress."],example:`import { Button, Logo, Text } from "@godxjp/ui/general";
|
|
258
|
+
import { AppShell, ErrorSurface, PageContainer, Sidebar } from "@godxjp/ui/layout";
|
|
259
|
+
|
|
260
|
+
// 403 \u2014 APPLICATION mode: the body inside the shell the route ALREADY renders.
|
|
261
|
+
// The sidebar/topbar/breadcrumb are preserved; the surface builds no chrome.
|
|
262
|
+
export function ForbiddenPage() {
|
|
263
|
+
return (
|
|
264
|
+
<AppShell sidebar={<Sidebar activeId="reports" sections={sections} />}>
|
|
265
|
+
<PageContainer title="\u30EC\u30DD\u30FC\u30C8" breadcrumb={[{ label: "\u30DB\u30FC\u30E0", to: "/" }, { label: "\u30EC\u30DD\u30FC\u30C8" }]}>
|
|
266
|
+
<ErrorSurface
|
|
267
|
+
mode="application"
|
|
268
|
+
status={403}
|
|
269
|
+
title={t("errors.403.title")}
|
|
270
|
+
description={t("errors.403.description")}
|
|
271
|
+
permission="reports.view"
|
|
272
|
+
organization="\u682A\u5F0F\u4F1A\u793E\u30B4\u30C3\u30C9\u30A8\u30C3\u30AF\u30B9"
|
|
273
|
+
action={<Button onClick={goHome}>{t("errors.backHome")}</Button>}
|
|
274
|
+
/>
|
|
275
|
+
</PageContainer>
|
|
276
|
+
</AppShell>
|
|
277
|
+
);
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
// 503 \u2014 SYSTEM mode: the surface owns the page. No className, no min-h-dvh, no media query.
|
|
281
|
+
export function MaintenancePage() {
|
|
282
|
+
return (
|
|
283
|
+
<ErrorSurface
|
|
284
|
+
mode="system"
|
|
285
|
+
status={503}
|
|
286
|
+
brand={<Logo glyph="G" />}
|
|
287
|
+
title={t("errors.503.title")}
|
|
288
|
+
description={t("errors.503.description")}
|
|
289
|
+
maintenance={{
|
|
290
|
+
start: "2026-08-02T18:00:00Z", // ISO-8601 instant, server-sent
|
|
291
|
+
end: "2026-08-02T20:00:00Z",
|
|
292
|
+
timeZone: "Asia/Tokyo", // IANA \u2014 explicit, so SSR and client agree
|
|
293
|
+
progress: 40, // 0-100, server-sent (never from the client clock)
|
|
294
|
+
}}
|
|
295
|
+
footer={<Text size="xs" tone="muted">2026 GodX</Text>}
|
|
296
|
+
action={<Button onClick={reload}>{t("errors.reload")}</Button>}
|
|
297
|
+
/>
|
|
298
|
+
);
|
|
299
|
+
}`,storyPath:"layout/ErrorSurface.stories.tsx",rules:[23,24]},{name:"LegalDocumentShell",group:"layout",tagline:"Long-form legal/policy document surface (terms, privacy, DPA, cookie policy, SLA) \u2014 semantic article/nav/section landmarks, real anchors, a sticky table-of-contents rail, scroll-spy aria-current, hash deep links with a token-driven scroll offset and focus handoff. All legal text stays consumer-owned.",props:[{name:"title",type:"ReactNode",required:!0,description:"Document title \u2014 the <h1> that names the <article> (e.g. 'Terms of Service')."},{name:"sections",type:"{ id: string; title: string; content: ReactNode }[]",required:!0,description:"The document's sections in reading order. Drives BOTH the contents list and the body: `id` is the REAL anchor target (<section id> + href='#id'), `title` becomes the <h2> AND the contents label, `content` is the consumer-owned legal copy."},{name:"version",type:"string",description:"Bare version identifier (e.g. '2.4'). Rendered as a localized 'Version {version}' \u2014 never pass a pre-localized sentence."},{name:"effectiveDate",type:"string",description:"ISO 8601 calendar date (yyyy-MM-dd) or a full ISO instant. Formatted with Intl.DateTimeFormat in the active locale and emitted inside <time dateTime={effectiveDate}>, so the machine-readable value is always the ISO input. NEVER pass a pre-formatted date string."},{name:"summary",type:"ReactNode",description:"Short plain-language summary rendered under the metadata, above the contents."},{name:"contentsLabel",type:"string",description:"Accessible name + visible caption of the contents <nav>. Defaults to a localized 'Contents'. Override it when two documents share a view, so the two nav landmarks stay distinguishable (axe landmark-unique, WCAG 2.4.1)."},{name:"activeSection",type:"string",description:"Controlled active section id (the entry marked aria-current='location'). Pair with onActiveSectionChange; omit both for the uncontrolled form."},{name:"defaultActiveSection",type:"string",description:"Uncontrolled initial active section id. Defaults to the first section."},{name:"onActiveSectionChange",type:"(sectionId: string) => void",description:"Fires on contents-anchor activation, on an initial hash deep link, and continuously from the scroll spy as the reader moves through the document."},{name:"documentNavigation",type:"ReactNode",description:"Rail slot above the contents list \u2014 a switcher across the legal set (Terms \xB7 Privacy \xB7 Cookies). Rendered as a plain wrapper, so the consumer owns its semantics."},{name:"footerAction",type:"ReactNode",description:"Slot below the last section \u2014 accept / download / print / contact actions."},{name:"id",type:"string",description:"Root element id; also seeds the internal ids."},{name:"className",type:"string",description:"Root class override (rarely needed)."}],usage:["DO use LegalDocumentShell for ANY long-form legal/policy document \u2014 terms of service, privacy policy, DPA, cookie policy, SLA, EULA, security policy. It is the only primitive that owns the document behaviour: scroll-spy active section, hash deep links, scroll offset, focus handoff and reduced-motion scrolling.","DO pass `effectiveDate` as an ISO 8601 string ('2026-04-01'). The shell formats it with Intl.DateTimeFormat in the active locale and keeps the ISO value in <time dateTime>. A pre-formatted string ('April 1, 2026') is a bug \u2014 it will not localize.","DO keep ALL legal text in the consumer: the shell only receives `sections` (+ the `summary` / `documentNavigation` / `footerAction` slots). It never ships legal copy.","DO give every section a URL-safe, page-unique `id` \u2014 it is simultaneously the <section id>, the contents href, the deep-link target and the aria-current key.","DO NOT hand-roll this from CenteredShell + SplitPane + `.legal-*` CSS. The geometry is the easy half; the scroll spy, hash offset, focus handoff and aria-current wiring are what the shell exists to own, and no token can express them.","DO NOT add a consumer `className` for the measure, the rail width, the section rhythm or the dividers \u2014 every one of those is a --legal-document-* token. Dividers default to `none` (rule #44): opt in with `--legal-document-toc-border: 1px solid hsl(var(--border))`.","DO NOT expect a viewport media query: the shell owns its query container, so the one-column \u21C4 two-column split (56rem) is decided by the SHELL's own width. Below it the contents are a STATIC compact block (never pinned on a phone); above it they are a sticky rail."],useCases:['Hosted legal terms/privacy screen (SCR-005): <CenteredShell width="lg" topbar={<Topbar \u2026/>}><LegalDocumentShell title="\u5229\u7528\u898F\u7D04" version="2.4" effectiveDate="2026-04-01" contentsLabel="\u76EE\u6B21" sections={sections} activeSection={active} onActiveSectionChange={setActive} documentNavigation={<DocumentSwitcher/>} footerAction={<Button>\u540C\u610F\u3059\u308B</Button>} /></CenteredShell>',"In-app policy viewer inside a Dialog/Sheet during onboarding: the same `sections` with `footerAction` carrying the accept button; the shell stays single-column because its container is narrow.","Deep-linkable DPA / sub-processor document: link to /legal/dpa#data-retention \u2014 the shell selects, scrolls to (with the scroll offset) and focuses that section on arrival.","Multi-document legal set (Terms \xB7 Privacy \xB7 Cookies): render the switcher in `documentNavigation` so it sits above the contents in the sticky rail."],related:["CenteredShell \u2014 the page shell to put a LegalDocumentShell inside (brand bar + centred column + footer). LegalDocumentShell is the document, not the page chrome; never nest two shells of the same kind.","SplitPane \u2014 a generic main + fixed aside. It gives similar GEOMETRY but owns no scroll-spy / hash / focus behaviour, so it is the wrong pick for a document with a table of contents.","PageContainer \u2014 for a titled section inside an app page; LegalDocumentShell already renders its own document header (h1 + version + effective date + summary).","Text / Heading \u2014 compose the section `content` from these; the shell only supplies the section heading (h2) and the body wrapper."],example:`import { LegalDocumentShell, CenteredShell, Flex, Topbar } from "@godxjp/ui/layout";
|
|
300
|
+
import { Button, Text } from "@godxjp/ui/general";
|
|
301
|
+
import { useState } from "react";
|
|
302
|
+
|
|
303
|
+
export function TermsPage() {
|
|
304
|
+
const [activeSection, setActiveSection] = useState("acceptance");
|
|
305
|
+
|
|
306
|
+
return (
|
|
307
|
+
<CenteredShell width="lg" topbar={<Topbar start={<Brand />} />}>
|
|
308
|
+
<LegalDocumentShell
|
|
309
|
+
title="\u5229\u7528\u898F\u7D04"
|
|
310
|
+
version="2.4"
|
|
311
|
+
effectiveDate="2026-04-01"
|
|
312
|
+
summary="\u672C\u898F\u7D04\u306F\u3001\u5F53\u793E\u304C\u63D0\u4F9B\u3059\u308B\u30B5\u30FC\u30D3\u30B9\u306E\u5229\u7528\u6761\u4EF6\u3092\u5B9A\u3081\u308B\u3082\u306E\u3067\u3059\u3002"
|
|
313
|
+
contentsLabel="\u76EE\u6B21"
|
|
314
|
+
activeSection={activeSection}
|
|
315
|
+
onActiveSectionChange={setActiveSection}
|
|
316
|
+
sections={[
|
|
317
|
+
{ id: "acceptance", title: "\u7B2C1\u6761(\u672C\u898F\u7D04\u3078\u306E\u540C\u610F)", content: <Text as="p">\u2026</Text> },
|
|
318
|
+
{ id: "accounts", title: "\u7B2C2\u6761(\u30A2\u30AB\u30A6\u30F3\u30C8\u306E\u7BA1\u7406)", content: <Text as="p">\u2026</Text> },
|
|
319
|
+
]}
|
|
320
|
+
documentNavigation={
|
|
321
|
+
<Flex direction="col" gap="xs" role="group" aria-label="\u6CD5\u7684\u6587\u66F8">
|
|
322
|
+
<Button variant="secondary" size="sm" fullWidth aria-current="page">\u5229\u7528\u898F\u7D04</Button>
|
|
323
|
+
<Button variant="ghost" size="sm" fullWidth>\u30D7\u30E9\u30A4\u30D0\u30B7\u30FC\u30DD\u30EA\u30B7\u30FC</Button>
|
|
324
|
+
</Flex>
|
|
325
|
+
}
|
|
326
|
+
footerAction={<Button size="sm">\u540C\u610F\u3059\u308B</Button>}
|
|
327
|
+
/>
|
|
328
|
+
</CenteredShell>
|
|
329
|
+
);
|
|
330
|
+
}`,storyPath:"layout/LegalDocumentShell.stories.tsx",rules:[23,24]},{name:"Breadcrumb",group:"layout",tagline:"Standalone breadcrumb nav rendering an ordered trail of page segments.",props:[{name:"items",type:"BreadcrumbItemProp[]",required:!0,description:"Array of { label, to? } \u2014 omit `to` on the last (current) segment."},{name:"aria-label",type:"string",description:`Override the nav landmark's accessible name (defaults to a localized "Breadcrumb"). Required when more than one Breadcrumb renders on the same page/view \u2014 two nav landmarks sharing one name/role fail landmark-unique.`}],usage:["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.",'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.',"DO pass the Breadcrumb node as a ReactNode to the `breadcrumb` prop of `AppShell` (or `AppShell`) for shell-level breadcrumbs, or to `PageContainer`'s `breadcrumb` prop (which accepts `BreadcrumbItemProp[]` directly \u2014 not a ReactNode). When passing to `PageContainer`, pass the raw array; when passing to `AppShell`, wrap it: `breadcrumb={<Breadcrumb items={\u2026} />}`.",'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`.',"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.","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."],useCases:["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>`.","Persistent shell-level breadcrumb in a `AppShell` or `AppShell` layout that updates as the user navigates between Inertia/React Router pages; constructed from route params and passed as a ReactNode to `AppShell`'s `breadcrumb` prop.","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.","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.","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.","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."],related:["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.","AppShell \u2014 accepts `breadcrumb` as `ReactNode`; pass `<Breadcrumb items={\u2026} />` here when the breadcrumb is a persistent shell-level strip that sits above all page content rather than being owned by individual pages.","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.","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."],example:`import { Breadcrumb } from "@godxjp/ui/layout";
|
|
216
331
|
|
|
217
332
|
<Breadcrumb items={[
|
|
218
333
|
{ label: "\u30DB\u30FC\u30E0", to: "/" },
|
|
@@ -232,14 +347,15 @@ import { Trash2 } from "lucide-react";
|
|
|
232
347
|
<Text size="xs" mono tone="muted">RC-204881</Text>`},{name:"Heading",group:"general",tagline:"Section heading sized from the --heading-h* tokens. `level` sets the size AND the semantic <h1..h4>.",props:[{name:"level",type:"1 | 2 | 3 | 4",defaultValue:"2",description:"Heading level \u2014 sizes from --heading-h{1..4} and renders the matching <h*>."},{name:"as",type:'"h1" | "h2" | "h3" | "h4" | "div"',description:"Override the rendered element (e.g. a visual h2 that is a real <h1>)."},{name:"tone",type:'"default" | "muted" | "primary" | "success" | "warning" | "destructive" | "info"',defaultValue:'"default"',description:"Semantic foreground colour."},{name:"align",type:'"start" | "center" | "end"',description:"Logical text alignment."},{name:"truncate",type:"boolean",description:"Single-line ellipsis."}],usage:['DO use `<Heading level>` for section titles instead of a raw `<h2 className="text-lg font-semibold">`. The level drives both the token size and the semantic element.',"Inside a Card use `<CardTitle>`; use `<Heading>` for free-standing page/section headings not covered by a component slot."],useCases:["A section heading on a dashboard: `<Heading level={3}>\u4ECA\u6708\u306EKPI</Heading>`.",'A visually-smaller heading that must stay an <h1> for a11y: `<Heading level={1} as="h1">\u2026</Heading>`.'],storyPath:"general/typography.tsx",rules:[6,23],example:`import { Heading } from "@godxjp/ui/general";
|
|
233
348
|
|
|
234
349
|
<Heading level={2}>\u8ACB\u6C42\u66F8\u4E00\u89A7</Heading>
|
|
235
|
-
<Heading level={3} tone="muted">\u88DC\u8DB3\u30BB\u30AF\u30B7\u30E7\u30F3</Heading>`},{name:"Logo",group:"general",tagline:"The product brand
|
|
236
|
-
import { Text } from "@godxjp/ui/general";
|
|
350
|
+
<Heading level={3} tone="muted">\u88DC\u8DB3\u30BB\u30AF\u30B7\u30E7\u30F3</Heading>`},{name:"Logo",group:"general",tagline:"The product brand mark \u2014 a glyph/identity box, or (with `wordmark`) the full mark + wordmark LOCKUP. Tokenised size/radius/type/gap, and `size` scales every branch (boxed glyph, identity mark, lockup); the boxed fill reads --primary, while the GoDX identity mark and wordmark read the --brand IDENTITY role instead, so an action-colour re-theme never recolours the brand.",props:[{name:"glyph",type:"React.ReactNode",defaultValue:'"g"',description:'The brand glyph \u2014 a short mark (letter/initials) or a custom inline <svg>. Only read when mark="glyph".'},{name:"mark",type:'"glyph" | "godx"',defaultValue:'"glyph"',description:'Semantic mark artwork. "godx" renders THE CANONICAL GoDX IDENTITY MARK as an inline vector owned by the package \u2014 use it for hosted-identity surfaces (AuthShell brand bar, AuthIdentity, CenteredShell topbar); do NOT re-draw or import a brand SVG in the app. Its box + colour are tokenized (--logo-godx-size-{xs,sm,md,lg} driven by the `size` prop, pinnable at every tier via --logo-godx-size; --logo-godx-color, defaulting to the --brand IDENTITY role = canonical emerald #009766, never --primary and never the --success status green) and it drops the boxed fill/radius. "glyph" keeps the configurable boxed-glyph treatment.'},{name:"size",type:'"xs" | "sm" | "md" | "lg"',defaultValue:'"md"',description:'Box size tier (tokenised) \u2014 it applies to EVERY mark, including mark="godx". The boxed glyph reads --logo-size-* (md = 1.75rem); the identity mark reads its own --logo-godx-size-* scale (xs 1.5 / sm 1.75 / md 2 / lg 2.5rem \u2014 the artwork is a capsule inside a square viewBox, so it needs slightly more box to read at the same optical weight). On a `wordmark` lockup the tier scales the mark and the wordmark together. To freeze the identity box at ONE size across every tier, a service theme sets --logo-godx-size (unset by default).'},{name:"tone",type:'"primary" | "success"',defaultValue:'"primary"',description:`Semantic fill role. "success" names the IDENTITY slot, not the status role: it gives the canonical GoDX emerald mark without re-tinting the application's primary action colour (it reads --logo-success-background / --logo-success-foreground; the FILL defaults to the --brand role \u2014 independent of BOTH --primary and the --success status green, gh#250). The boxed glyph's INK defaults to --logo-identity-foreground (near-black), NOT --brand-foreground: --brand-foreground is the artwork KNOCKOUT colour and clears only 3.67:1 on the emerald, while a boxed glyph renders real TEXT and owes WCAG 2.2 SC 1.4.3's 4.5:1 (14px bold is not "large text"). Re-theming --brand to a DARK fill? Override --logo-success-foreground to re-invert the ink.`},{name:"wordmark",type:"React.ReactNode",description:'Readable product name rendered BESIDE the mark as ONE lockup \u2014 pass the localized product name (or an inline <svg> logotype when a real asset exists). Set it INSTEAD of hand-rolling `inline-flex items-center gap-2` around a Logo and a Text. The lockup root takes ref/className/\u2026props; the mark becomes decorative and the wordmark text carries the accessible name, so the pair is announced once. Colour/face/weight/tracking/size and the mark\u2194wordmark gap are tokens (--logo-wordmark-*); on mark="godx" / tone="success" the wordmark is canonical emerald from the --brand identity role and NEVER reads --primary or --success. The wordmark span carries `data-logotype`: WCAG 2.2 SC 1.4.3 exempts brand-name artwork from the text-contrast minimum, so a consumer contrast audit must skip it rather than darken the brand colour. Omit for the bare mark (unchanged behaviour).'},{name:"label",type:"string",description:"Accessible name. Set \u2192 exposed as a named image (role img); omitted \u2192 decorative (aria-hidden), the correct default when a readable wordmark sits beside it. With `wordmark` set, `label` overrides the lockup's name (the wordmark text is otherwise the name)."}],usage:['DO import from `@godxjp/ui/general`: `import { Logo } from "@godxjp/ui/general";`','DO use Logo INSTEAD of hand-rolling `<span aria-hidden className="grid size-7 place-items-center rounded-md bg-primary text-sm font-bold text-primary-foreground">g</span>` \u2014 that repeats literal size/radius and puts type utilities on a bare span (rules #45/#46).',"DO leave `label` unset when a readable wordmark sits beside the mark (shell header, topbar) \u2014 the mark stays decorative and the wordmark carries the accessible name. Set `label` only when the mark stands alone.","DON'T pass more than 1\u20132 glyphs \u2014 the box is square and centres its content; a long string overflows. The product NAME goes in `wordmark`, never in `glyph`.",'DO use `wordmark` for the full lockup: `<Logo mark="godx" tone="success" wordmark="GoDX" />`. It replaces the hand-rolled `<span className="inline-flex items-center gap-2"><Logo/><Text/></span>` \u2014 the gap, face, weight, tracking, per-tier size and the brand colour are all tokens, so a shell header / auth brand bar needs no page CSS (gh#214).',"NOTE: the package ships NO wordmark ARTWORK \u2014 `wordmark` typesets the name in the design-system display face (`--logo-wordmark-font-family`). When design supplies a real logotype, pass it as an inline `<svg>` node to `wordmark`; do not approximate letterforms in CSS.",'DON\'T re-tint via `className="bg-*"` \u2014 the fill reads the `--primary` role token; retune it through a service theme (`--primary`, `--logo-radius`, `--logo-size-*`), not utilities.',"DO use `mark=\"godx\"` for the canonical GoDX identity mark on hosted-identity screens \u2014 it is ALREADY in the package as real inline vector artwork. DON'T pass a hand-drawn brand SVG as `glyph`, and don't ship a brand asset in the app, to reproduce it."],useCases:['App-shell header brand lockup \u2014 `<Logo glyph="c" wordmark="CoreBooks" />` in the sidebar/topbar: mark decorative, wordmark readable, spacing tokenized.','Auth screen \u2014 a standalone labelled mark above the sign-in form: `<Logo label="CoreBooks" size="lg" />`.','Tenant/workspace switcher row \u2014 a small `size="sm"` mark as the leading slot of a ListRow or menu item.',"Custom SVG brand \u2014 pass an inline `<svg>` as `glyph` to render a real logomark on the primary fill instead of a letter.",'Hosted GoDX identity surface \u2014 `<Logo mark="godx" tone="success" />` in an AuthShell `brand` bar or inside <AuthIdentity>: the canonical GoDX mark the package already owns. There is no separate identity-mark component and no asset to import.','Brand lockup in a shell header / auth brand bar \u2014 `<Logo mark="godx" tone="success" wordmark="GoDX" />`: one element, brand-green, no wrapper div and no page CSS.'],related:["AuthIdentity (@godxjp/ui/layout) \u2014 the canonical auth heading block; it already renders the GoDX mark, so don't add a second Logo above it.","Text / Heading \u2014 only for a bespoke lockup the `wordmark` prop cannot express; for the ordinary mark + product name, use `wordmark` (it owns the gap, face and brand colour as tokens).","Avatar \u2014 use Avatar for a PERSON/entity image or initials; use Logo for the PRODUCT brand mark. They look similar (square/rounded glyph) but carry different meaning."],storyPath:"general/Logo.stories.tsx",rules:[45,46],example:`import { Logo } from "@godxjp/ui/general";
|
|
237
351
|
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
<Text weight="medium">CoreBooks</Text>
|
|
241
|
-
</span>
|
|
352
|
+
// Full lockup \u2014 mark + wordmark as ONE element (no wrapper, no page CSS).
|
|
353
|
+
<Logo glyph="c" wordmark="CoreBooks" />
|
|
242
354
|
|
|
355
|
+
// Canonical brand-green GoDX lockup \u2014 identity role, independent of --primary.
|
|
356
|
+
<Logo mark="godx" tone="success" wordmark="GoDX" />
|
|
357
|
+
|
|
358
|
+
// Bare mark standing alone \u2192 give it an accessible name.
|
|
243
359
|
<Logo label="CoreBooks" size="lg" />`},{name:"Reveal",group:"general",tagline:"The official entrance-motion primitive (staggered fade-up) \u2014 reads DS motion tokens and honours prefers-reduced-motion, replacing hand-rolled @keyframes + .app-reveal/.d1..d6 classes.",props:[{name:"children",type:"ReactNode",required:!0,description:"Content to reveal on enter."},{name:"delay",type:"0 | 1 | 2 | 3 | 4 | 5 | 6",defaultValue:"0",description:"Stagger ordinal \u2014 an INDEX into the motion ladder, never a raw ms. Each step adds one `--reveal-stagger-step` of delay so a column of reveals cascades. 0 = enter immediately."},{name:"asChild",type:"boolean",defaultValue:"false",description:"Merge the reveal onto the single child element (no wrapper <div>) \u2014 use when an extra box would break a grid/flex layout."}],usage:["DO use <Reveal> INSTEAD of hand-rolling `@keyframes auth-fade-up` + `.app-reveal` + `.d1..d6` in a consumer global.css \u2014 that repeats literal durations/delays and violates the tokens-only rule. Reveal reads `--duration-slow` / `--ease-emphasized` / `--reveal-distance` / `--reveal-stagger-step`.","DO stagger a list/column by passing an increasing `delay` (1, 2, 3\u2026) to successive siblings \u2014 the ordinal maps to `--reveal-stagger-step`, so a service retunes the cascade rhythm from one token.","DO pass `asChild` when wrapping an element that must keep its own box in a grid/flex row (the reveal merges onto that element instead of adding a <div>).","DO rely on the built-in reduced-motion behaviour \u2014 under `prefers-reduced-motion: reduce` the animation is dropped and content renders in its final, fully-visible position with no layout shift. Never gate visibility on the animation.","DO NOT set a raw ms delay or a literal translate distance \u2014 the whole point is that `delay` is a controlled ordinal and the distance/duration come from tokens."],useCases:["Auth card entrance: `<AuthShell><Reveal><Card/></Reveal></AuthShell>` \u2014 the sign-in card fades up on load, respecting reduced-motion.","Staggered dashboard: map stat cards with `<Reveal delay={i + 1}>` so the row cascades in.","Section reveal on a settings/detail page \u2014 wrap each Card in <Reveal> for a calm entrance without hand-written CSS.","asChild on a grid item: `<Reveal asChild delay={2}><ResponsiveGrid.Item/></Reveal>` keeps the grid cell intact while animating it in."],related:["AuthShell \u2014 pairs with Reveal for the auth card entrance; AuthShell delegates all motion to Reveal.","Card \u2014 the most common thing to wrap in <Reveal> (dashboard cards, auth card, settings sections).","ResponsiveGrid \u2014 combine with `<Reveal delay={n}>` (or `asChild`) per grid item for a staggered grid reveal."],example:`import { Reveal } from "@godxjp/ui/general";
|
|
244
360
|
import { Card, CardContent } from "@godxjp/ui/data-display";
|
|
245
361
|
|
|
@@ -253,7 +369,7 @@ import { Card, CardContent } from "@godxjp/ui/data-display";
|
|
|
253
369
|
<Reveal key={item.id} delay={Math.min(i + 1, 6) as 1 | 2 | 3 | 4 | 5 | 6}>
|
|
254
370
|
<Card><CardContent>{item.label}</CardContent></Card>
|
|
255
371
|
</Reveal>
|
|
256
|
-
))}`,storyPath:"general/Reveal.stories.tsx",rules:[]},{name:"DataTable",group:"data-display",tagline:"The one TanStack-powered compound admin list \u2014 sticky header, sorting, global search, column visibility ('set view'), bulk selection, BOTH cursor and numbered pagination, density, and built-in empty/loading states. Keep the SIMPLE `data` + lean `columns` (ColumnDef) API for the common case; opt into the full grid chrome via the compound parts. Internally driven by @tanstack/react-table (a real dependency). Lives on @godxjp/ui/data-display only (it is NOT on the runtime-neutral root/admin barrel because it pulls TanStack). This is the single table component \u2014 the former DataGrid has been merged in and removed. Never hand-roll a data.length===0 guard around it.",props:[{name:"data",type:"T[]",required:!0,description:"Array of row data. When empty and loading is false, a built-in EmptyState renders automatically inside the table body \u2014 no external guard needed."},{name:"columns",type:"ColumnDef<T>[]",required:!0,description:"Lean column definitions (adapted to TanStack internally). Each column: { key: string; header: ReactNode; ariaLabel?: string; render?: (row: T) => ReactNode; sortable?: boolean; width?: string; align?: 'left'|'center'|'right'; hiddenOnMobile?: boolean; enableHiding?: boolean; pin?: 'end' }. If render is omitted, the raw value at row[key] is rendered as a string. sortable opts the column into the sort cycle (client-side by default, or server-side via sort+onSortChange). enableHiding (default true) lists the column in DataTable.ViewOptions; set false to keep a key/actions column always visible. pin:'end' sticks the column (typically row actions) to the inline-end edge on horizontal scroll with a separating shadow \u2014 pin at most one column. ariaLabel gives a VISUALLY-EMPTY header (header='' \u2014 an action or selection column) a screen-reader name (e.g. 'Actions'/'Select'): it renders as an sr-only label inside the <th> so the column is never nameless (axe: empty-table-header). DataTable dev-warns when a column has an empty header and no ariaLabel."},{name:"getRowId",type:"(row: T) => string",defaultValue:"(row) => String(row.id)",description:"Extracts a stable unique string key per row. Required when selectable is true or rows lack an 'id' field. Falls back to row.id cast to string."},{name:"selectable",type:"boolean",defaultValue:"false",description:"Adds a checkbox column and a SelectAll header checkbox. Use with selected + onSelectChange for controlled selection, or omit both for uncontrolled."},{name:"selected",type:"Set<string>",description:"Controlled set of selected row IDs. Pair with onSelectChange. Omit for uncontrolled."},{name:"onSelectChange",type:"(next: Set<string>) => void",description:"Called with the full new selection set after any checkbox interaction."},{name:"onRowClick",type:"(row: T) => void",description:"Makes rows clickable for navigation. Row click is suppressed when the user clicks an interactive descendant (button, a, input, select, textarea, [role=menuitem])."},{name:"density",type:"'compact' | 'default' | 'comfortable'",defaultValue:"'compact'",description:"Controlled row density across all three tiers (compact 28 / default 36 / comfortable 48) \u2014 drive it from a \u8868\u793A\u5BC6\u5EA6 radio. Omit to let DataTable manage it internally (DataTable.DensityToggle flips compact\u2194comfortable)."},{name:"onDensityChange",type:"(density: 'compact' | 'default' | 'comfortable') => void",description:"Called when the user toggles density. Only needed when density is controlled."},{name:"striped",type:"boolean",defaultValue:"false",description:"Zebra-stripe the body rows (even rows get a subtle muted fill)."},{name:"hoverable",type:"boolean",defaultValue:"false",description:"Highlight a row on hover even when it is not clickable. onRowClick already implies hover; use this for read-only tables that still want the hover affordance."},{name:"stickyHeader",type:"boolean",defaultValue:"true",description:"Pin the header to the top while the body scrolls (\u30D8\u30C3\u30C0\u8FFD\u5F93). Set false to let it scroll away with the rows."},{name:"sort",type:"{ key: string; direction: 'asc' | 'desc' }",description:"Active sort state (controlled/server surface). When provided alongside onSortChange, sortable columns show directional arrow icons and clicking the active column twice clears sort (calls onSortChange(undefined)). Omit both sort and onSortChange to sort client-side via TanStack."},{name:"onSortChange",type:"(sort: { key: string; direction: 'asc' | 'desc' } | undefined) => void",description:"Called when a sortable column header is clicked. Receives undefined when sort is cleared (third click on same column). Providing sort or onSortChange opts into the controlled (server) sort surface; omit both and the table sorts client-side via TanStack."},{name:"globalFilter / onGlobalFilterChange",type:"string / (next: string) => void",description:"Global search term surfaced by DataTable.Search. Omit both for client-side filtering; pass them to drive a server query (with manualFiltering)."},{name:"pagination / onPaginationChange / rowCount",type:"{ pageIndex: number; pageSize: number } / OnChangeFn / number",description:"Numbered-pagination state surfaced by DataTable.Pagination (page-size form). For server pagination pass all three (rowCount = total) with manualPagination; omit for client pagination."},{name:"columnVisibility / onColumnVisibilityChange",type:"VisibilityState / OnChangeFn<VisibilityState>",description:"Column show/hide state surfaced by DataTable.ViewOptions ('set view'). Internal if omitted."},{name:"manualSorting / manualFiltering / manualPagination",type:"boolean",defaultValue:"false",description:"Default false so the simple data+columns case sorts/filters/paginates in-browser. Set the relevant flag true and drive the matching state from your query for server-side behaviour."},{name:"loading",type:"boolean",defaultValue:"false",description:"When true, swaps the body for SHAPED skeleton rows rendered inside the table's own grid (one border, aligned columns) \u2014 never a separate <SkeletonTable> in a Card (that double-borders). With React Query keepPreviousData, drive this off isPlaceholderData (pagination/search) || isLoading (first load), NOT isLoading alone. Suppresses the empty state while true."},{name:"empty",type:"ReactNode",description:"Custom content rendered inside the table body when data is empty and loading is false. Defaults to a built-in EmptyState with a localised 'No data' message. Pass a custom <EmptyState title='...' description='...' action={...}/> to tailor the message."},{name:"className",type:"string",description:"Extra classes applied to the root wrapper div (ui-data-table-root)."},{name:"children",type:"ReactNode",description:"Compound sub-parts: DataTable.Toolbar, DataTable.Search (global filter), DataTable.ViewOptions (column show/hide), DataTable.SelectAll, DataTable.BulkActions (ReactNode children OR a (count)=>node render-prop), DataTable.DensityToggle, DataTable.Pagination (cursor first/next when given cursor+hasMore+onChange, else numbered page-size form), DataTable.RowActions (kebab trigger), DataTable.Content. If no DataTable.Content is present in children, one is auto-rendered."}],usage:["DO pass loading={isFetching} during data fetches \u2014 it renders a loading row in the table body and suppresses the empty state. Never show a spinner outside DataTable while the table is visible.","DO NOT add a data.length===0 conditional around DataTable. When data is empty and loading is false, the built-in EmptyState renders automatically. Pass empty={<EmptyState title='...'/>} only when you need a custom message.","DO provide getRowId when selectable is true or when rows do not have a string/number 'id' field \u2014 the default falls back to row.id and silently returns '' for missing IDs, which breaks selection.","DO use DataTable.Toolbar as the immediate child that wraps search/filter controls on the left and DataTable.DensityToggle/action buttons on the right. DataTable.BulkActions inside the toolbar auto-hides when selection count is 0; it accepts either plain ReactNode children (built-in 'N selected' status bar) or a (count)=>node render-prop (you own the whole bar).","DO reach for the grid chrome (DataTable.Search, DataTable.ViewOptions, DataTable.Pagination pageSizeOptions) when you need global search, a column 'set view' picker, or numbered pagination \u2014 these are the merged former-DataGrid features, now on the one DataTable. Drive them client-side by default; pass the matching state + manual* flag for a server query.","DO use ColumnDef.render for custom cell content (Badge, Link, RowActions). For plain string/number fields render can be omitted \u2014 DataTable falls back to String(row[key]).","DO give every visually-empty column an accessible header via `ariaLabel` \u2014 a row-actions column (`header: ''`, `pin: 'end'`) sets `ariaLabel: t('actions')`, so screen readers announce the column and axe reports no `empty-table-header`. DataTable dev-warns any column that renders a `<th>` with neither visible text nor an `ariaLabel`. The selection column added by `selectable` is already named by its SelectAll checkbox \u2014 no `ariaLabel` needed there.","COLUMN SEMANTICS + KEYBOARD: a `sortable` header renders as a real <button> inside the <th> with `aria-sort` (ascending/descending/none) on the <th>; it is Tab-reachable and toggles asc \u2192 desc \u2192 cleared on Enter/Space/click. A selection column exposes a header 'select all' Checkbox (indeterminate when a subset is selected) and a per-row Checkbox, each keyboard-operable with Space. An action column is visually empty but carries an `ariaLabel`; its per-row controls (kebab menu / buttons) own their own accessible names and keyboard behavior. Row click (`onRowClick`) is suppressed when the user activates an interactive descendant.","DO NOT nest DataTable.Content in a conditional \u2014 it is already guarded internally. If you need to override the table body slot, drop exactly one <DataTable.Content /> in children; DataTable auto-detects it by displayName and skips the default."],useCases:["Admin list pages (invoices, customers, orders, accounts) where rows are clickable for detail navigation via onRowClick.","Bulk-action workflows (e.g. mark invoices paid, export selected rows) \u2014 use selectable + DataTable.BulkActions to show contextual action buttons only when something is selected.","Server-side sorted tables: pass sort + onSortChange and update the data prop after the API call; DataTable renders asc/desc/neutral icons on the header automatically.","Cursor-paginated lists: add DataTable.Pagination with cursor + hasMore + onChange inside children to get First/Next navigation without offset arithmetic. For page-size + numbered prev/next instead, use DataTable.Pagination with pageSizeOptions (no cursor/onChange) driven by the internal TanStack pagination.","Full grid screens (global search + column 'set view' + numbered pagination): compose DataTable.Search, DataTable.ViewOptions, DataTable.DensityToggle in the toolbar and DataTable.Pagination pageSizeOptions={[\u2026]} \u2014 client-side by default, or server-side by passing globalFilter/pagination/sort state with the matching manual* flag.","Responsive admin tables where lower-priority columns (e.g. internal IDs, dates) should collapse below mobile breakpoints \u2014 set hiddenOnMobile: true on those ColumnDef entries.","Loading skeletons during initial page load or filter change: set loading={true} alongside an empty data={[]} to show the loading row without flashing an empty state."],related:["Table \u2014 raw primitive (TableHeader/TableBody/TableRow/TableCell). Use DataTable instead; only reach for Table directly when you need a non-standard layout that DataTable cannot express.","SkeletonTable \u2014 standalone skeleton placeholder rendered before any DataTable mounts (e.g. in a Suspense fallback or deferred-prop skeleton slot). DataTable.loading covers in-table loading; SkeletonTable covers pre-mount skeletons.","EmptyState \u2014 standalone empty state for non-table lists. DataTable already embeds EmptyState in its body; only use bare EmptyState for card content, non-tabular lists, or zero-state pages outside a DataTable.","LineChart / BarChart / AreaChart / PieChart (@godxjp/ui/charts) \u2014 when the SHAPE or trend of aggregated data matters more than exact per-row figures, visualize it with a chart instead of (or alongside) the table; keep DataTable when users need to read, sort, or act on individual rows.","DataState / InfiniteQueryState \u2014 TanStack Query lifecycle widgets from @godxjp/ui/query. Prefer these over DataTable when your list is driven by useQuery/useInfiniteQuery and you want automatic skeleton/empty/error handling at the query level rather than at the table level."],example:`import { useState } from "react";
|
|
372
|
+
))}`,storyPath:"general/Reveal.stories.tsx",rules:[]},{name:"DataTable",group:"data-display",tagline:"The one TanStack-powered compound admin list \u2014 sticky header, sorting, global search, column visibility ('set view'), bulk selection, BOTH cursor and numbered pagination, density, and built-in empty/loading states. Keep the SIMPLE `data` + lean `columns` (ColumnDef) API for the common case; opt into the full grid chrome via the compound parts. Internally driven by @tanstack/react-table (a real dependency). Lives on @godxjp/ui/data-display only (it is NOT on the runtime-neutral root/admin barrel because it pulls TanStack). This is the single table component \u2014 the former DataGrid has been merged in and removed. Never hand-roll a data.length===0 guard around it.",props:[{name:"data",type:"T[]",required:!0,description:"Array of row data. When empty and loading is false, a built-in EmptyState renders automatically inside the table body \u2014 no external guard needed."},{name:"columns",type:"ColumnDef<T>[]",required:!0,description:"Lean column definitions (adapted to TanStack internally \u2014 `meta.lean` is the declared home for every custom column option, so `priority` needs no second TanStack channel). Each column: { key: string; header: ReactNode; ariaLabel?: string; render?: (row: T) => ReactNode; sortable?: boolean; width?: string; align?: 'left'|'center'|'right'; hiddenOnMobile?: boolean; enableHiding?: boolean; pin?: 'end'; priority?: 'primary'|'secondary'|'meta'|'actions' }. priority is the column-priority contract read by preset=\"action-collection\" (gh#253) \u2014 DataTable stamps it as data-priority on the <th> AND every <td> of the column, so the preset can allocate the narrow-frame measure; leave the free-text column unmarked (it takes the remaining space), and prefer priority over width under the preset because an explicit width utility wins the cascade and defeats the measure. If render is omitted, the raw value at row[key] is rendered as a string. sortable opts the column into the sort cycle (client-side by default, or server-side via sort+onSortChange). enableHiding (default true) lists the column in DataTable.ViewOptions; set false to keep a key/actions column always visible. pin:'end' sticks the column (typically row actions) to the inline-end edge on horizontal scroll with a separating shadow \u2014 pin at most one column. ariaLabel gives a VISUALLY-EMPTY header (header='' \u2014 an action or selection column) a screen-reader name (e.g. 'Actions'/'Select'): it renders as an sr-only label inside the <th> so the column is never nameless (axe: empty-table-header). DataTable dev-warns when a column has an empty header and no ariaLabel."},{name:"getRowId",type:"(row: T) => string",defaultValue:"(row) => String(row.id)",description:"Extracts a stable unique string key per row. Required when selectable is true or rows lack an 'id' field. Falls back to row.id cast to string."},{name:"selectable",type:"boolean",defaultValue:"false",description:"Adds a checkbox column and a SelectAll header checkbox. Use with selected + onSelectChange for controlled selection, or omit both for uncontrolled."},{name:"selected",type:"Set<string>",description:"Controlled set of selected row IDs. Pair with onSelectChange. Omit for uncontrolled."},{name:"onSelectChange",type:"(next: Set<string>) => void",description:"Called with the full new selection set after any checkbox interaction."},{name:"onRowClick",type:"(row: T) => void",description:"Makes rows clickable for navigation. Row click is suppressed when the user clicks an interactive descendant (button, a, input, select, textarea, [role=menuitem])."},{name:"density",type:"'compact' | 'default' | 'comfortable'",defaultValue:"'compact'",description:"Controlled row density across all three tiers (compact 28 / default 36 / comfortable 48) \u2014 drive it from a \u8868\u793A\u5BC6\u5EA6 radio. Omit to let DataTable manage it internally (DataTable.DensityToggle flips compact\u2194comfortable)."},{name:"onDensityChange",type:"(density: 'compact' | 'default' | 'comfortable') => void",description:"Called when the user toggles density. Only needed when density is controlled."},{name:"striped",type:"boolean",defaultValue:"false",description:"Zebra-stripe the body rows (even rows get a subtle muted fill)."},{name:"hoverable",type:"boolean",defaultValue:"false",description:"Highlight a row on hover even when it is not clickable. onRowClick already implies hover; use this for read-only tables that still want the hover affordance."},{name:"stickyHeader",type:"boolean",defaultValue:"true",description:"Pin the header to the top while the body scrolls (\u30D8\u30C3\u30C0\u8FFD\u5F93). Set false to let it scroll away with the rows."},{name:"preset",type:"'default' | 'action-collection'",defaultValue:"'default'",description:"Named collection contract (gh#253) \u2014 the SAME preset the Table primitive owns, forwarded to the table DataTable renders. 'default' emits NO attribute and matches no selector, so an existing DataTable is byte-identical. 'action-collection' is the canonical dense approval/action queue: below collapseBelow the desktop INTRINSIC column widths give way to the token-owned column-PRIORITY measures (--table-action-collection-*) under table-layout: fixed, cells wrap, and the bordered surface drops its --table-surface-min-inline-size floor \u2014 so requester \xB7 target \xB7 reason \xB7 requested date \xB7 row actions all stay inside a 390px frame with no horizontal scroll. Mark each column with `priority` on its ColumnDef. Semantics are untouched (no display change, no role rewriting, no card swap), so header association, aria-sort and screen-reader table navigation are identical at 390 and 1440. Measured: table 1182 / 766 / 388px at 1440 / 1024 / 390, document scrollWidth === clientWidth at every width, LTR and RTL."},{name:"collapseBelow",type:"'sm' | 'md' | 'lg' | 'xl'",defaultValue:"'sm'",description:`Step at which preset="action-collection" switches to the compact priority measures, measured against the TABLE'S OWN container (a container query on sm 40rem \xB7 md 48rem \xB7 lg 64rem \xB7 xl 80rem), not the viewport \u2014 a table inside a master rail collapses before the page does. Ignored while preset is 'default'.`},{name:"sort",type:"{ key: string; direction: 'asc' | 'desc' }",description:"Active sort state (controlled/server surface). When provided alongside onSortChange, sortable columns show directional arrow icons and clicking the active column twice clears sort (calls onSortChange(undefined)). Omit both sort and onSortChange to sort client-side via TanStack."},{name:"onSortChange",type:"(sort: { key: string; direction: 'asc' | 'desc' } | undefined) => void",description:"Called when a sortable column header is clicked. Receives undefined when sort is cleared (third click on same column). Providing sort or onSortChange opts into the controlled (server) sort surface; omit both and the table sorts client-side via TanStack."},{name:"globalFilter / onGlobalFilterChange",type:"string / (next: string) => void",description:"Global search term surfaced by DataTable.Search. Omit both for client-side filtering; pass them to drive a server query (with manualFiltering)."},{name:"pagination / onPaginationChange / rowCount",type:"{ pageIndex: number; pageSize: number } / OnChangeFn / number",description:"Numbered-pagination state surfaced by DataTable.Pagination (page-size form). For server pagination pass all three (rowCount = total) with manualPagination; omit for client pagination."},{name:"columnVisibility / onColumnVisibilityChange",type:"VisibilityState / OnChangeFn<VisibilityState>",description:"Column show/hide state surfaced by DataTable.ViewOptions ('set view'). Internal if omitted."},{name:"manualSorting / manualFiltering / manualPagination",type:"boolean",defaultValue:"false",description:"Default false so the simple data+columns case sorts/filters/paginates in-browser. Set the relevant flag true and drive the matching state from your query for server-side behaviour."},{name:"loading",type:"boolean",defaultValue:"false",description:"When true, swaps the body for SHAPED skeleton rows rendered inside the table's own grid (one border, aligned columns) \u2014 never a separate <SkeletonTable> in a Card (that double-borders). With React Query keepPreviousData, drive this off isPlaceholderData (pagination/search) || isLoading (first load), NOT isLoading alone. Suppresses the empty state while true."},{name:"empty",type:"ReactNode",description:"Custom content rendered inside the table body when data is empty and loading is false. Defaults to a built-in EmptyState with a localised 'No data' message. Pass a custom <EmptyState title='...' description='...' action={...}/> to tailor the message."},{name:"error",type:"ReactNode",description:"FAILURE state (gh#216). Pass error={isError} \u2014 `true` renders the built-in localized destructive EmptyState ('Couldn't load this list') announced with role='alert'; any other node REPLACES that copy (e.g. an <Alert> carrying an error code + request id). `false`/`undefined` means the read succeeded. NEVER pass a raw Error object (it is not renderable). Suppresses the empty state."},{name:"denied",type:"ReactNode",description:"PERMISSION-DENIED state (gh#216) \u2014 the read was REFUSED (403), not failed. `true` renders the built-in localized warning EmptyState ('You don't have access to this list') with NO retry, announced politely (aria-live) because a permission boundary is expected information, not a fault. Takes precedence over `error`. Any other node replaces the copy."},{name:"onRetry",type:"() => void",description:"Retry handler surfaced as a Retry button inside the BUILT-IN error state only. Omit it to render the error without a retry affordance; it is intentionally never offered for `denied` (repeating a 403 cannot succeed)."},{name:"className",type:"string",description:"Extra classes applied to the root wrapper div (ui-data-table-root)."},{name:"children",type:"ReactNode",description:"Compound sub-parts: DataTable.Toolbar, DataTable.Search (global filter), DataTable.ViewOptions (column show/hide), DataTable.SelectAll, DataTable.BulkActions (ReactNode children OR a (count)=>node render-prop), DataTable.DensityToggle, DataTable.Pagination (cursor first/next when given cursor+hasMore+onChange, else numbered page-size form), DataTable.RowActions (kebab trigger), DataTable.Content. If no DataTable.Content is present in children, one is auto-rendered."}],usage:["DO pass loading={isFetching} during data fetches \u2014 it renders a loading row in the table body and suppresses the empty state. Never show a spinner outside DataTable while the table is visible.","DO NOT add a data.length===0 conditional around DataTable. When data is empty and loading is false, the built-in EmptyState renders automatically. Pass empty={<EmptyState title='...'/>} only when you need a custom message.","SIX STATES, ZERO HAND-ROLLING (gh#216): loading (`loading`), empty (automatic / `empty`), error (`error` + optional `onRetry`), denied (`denied`), pagination (`DataTable.Pagination`), row actions (`DataTable.RowActions`). Wire them straight off the query \u2014 `<DataTable loading={isPending} error={isError} denied={status === 403} onRetry={refetch} \u2026/>` \u2014 and never branch the page around the table to render your own alert/empty/forbidden block. Precedence is loading > denied > error > empty > rows, so exactly one state ever shows.","DO provide getRowId when selectable is true or when rows do not have a string/number 'id' field \u2014 the default falls back to row.id and silently returns '' for missing IDs, which breaks selection.","DO use DataTable.Toolbar as the immediate child that wraps search/filter controls on the left and DataTable.DensityToggle/action buttons on the right. DataTable.BulkActions inside the toolbar auto-hides when selection count is 0; it accepts either plain ReactNode children (built-in 'N selected' status bar) or a (count)=>node render-prop (you own the whole bar).","DO reach for the grid chrome (DataTable.Search, DataTable.ViewOptions, DataTable.Pagination pageSizeOptions) when you need global search, a column 'set view' picker, or numbered pagination \u2014 these are the merged former-DataGrid features, now on the one DataTable. Drive them client-side by default; pass the matching state + manual* flag for a server query.","DataTable.Pagination OWNS ITS OWN INSET (gh#236, fixed in 18.6.0). The footer is a self-contained slot: it declares `padding-block` + `padding-inline` from `--table-pagination-padding-{y,x}` (block default = `--space-stack-sm`, inline default = `--table-cell-space-x`, so the 'rows per page' label lands on the same optical axis as the first column's text). Before the fix it declared `padding-top` only, so inside the documented flush container (`<Card><CardContent flush><DataTable/>`) the label and page-size Select sat flush against the container edge and border. DON'T ship a local `.ui-data-table-pagination { padding: \u2026 }` override in an app \u2014 retune the two tokens in your theme instead.",'RESPONSIVE APPROVAL / ACTION QUEUE (gh#253): reach for `preset="action-collection"` when a dense five-column queue (requester \xB7 target \xB7 reason \xB7 requested date \xB7 row actions) must stay readable at 390px, and give every column a `priority` on its ColumnDef \u2014 `primary` (the row subject), `secondary` (its target), `meta` (a timestamp/id), `actions` (the row-action affordance, whose measure is reserved FIRST so it can never be pushed off-screen). Leave the free-text column unmarked; it takes the remaining space. This is the SAME contract, the SAME `--table-action-collection-*` tokens and the SAME container query as `Table preset="action-collection"` \u2014 there is no separate DataTable family. Never add a consumer width, a hidden column, `hiddenOnMobile` or a page-local breakpoint to make a table fit: retune the tokens instead.',"DON'T set `width` on a column that also has a `priority` \u2014 an explicit width utility wins the cascade over the priority measure and re-opens the horizontal scroll. Under the preset, `pin: 'end'` is also redundant: nothing scrolls sideways, so the actions column is already in frame.",'The DataTable surface\'s narrow-viewport width floor is `--table-surface-min-inline-size` (default 640px, released at the `sm` viewport step). It used to be a hard-coded `min-w-[640px] sm:min-w-0` utility pair on the surface \u2014 the literal that forced the horizontal scroll at 390. Retune (or zero) the token in your theme; `preset="action-collection"` already opts out of it.',"DO use ColumnDef.render for custom cell content (Badge, Link, RowActions). For plain string/number fields render can be omitted \u2014 DataTable falls back to String(row[key]).","DO give every visually-empty column an accessible header via `ariaLabel` \u2014 a row-actions column (`header: ''`, `pin: 'end'`) sets `ariaLabel: t('actions')`, so screen readers announce the column and axe reports no `empty-table-header`. DataTable dev-warns any column that renders a `<th>` with neither visible text nor an `ariaLabel`. The selection column added by `selectable` is already named by its SelectAll checkbox \u2014 no `ariaLabel` needed there.","COLUMN SEMANTICS + KEYBOARD: a `sortable` header renders as a real <button> inside the <th> with `aria-sort` (ascending/descending/none) on the <th>; it is Tab-reachable and toggles asc \u2192 desc \u2192 cleared on Enter/Space/click. A selection column exposes a header 'select all' Checkbox (indeterminate when a subset is selected) and a per-row Checkbox, each keyboard-operable with Space. An action column is visually empty but carries an `ariaLabel`; its per-row controls (kebab menu / buttons) own their own accessible names and keyboard behavior. Row click (`onRowClick`) is suppressed when the user activates an interactive descendant.","DO NOT nest DataTable.Content in a conditional \u2014 it is already guarded internally. If you need to override the table body slot, drop exactly one <DataTable.Content /> in children; DataTable auto-detects it by displayName and skips the default."],useCases:["Admin list pages (invoices, customers, orders, accounts) where rows are clickable for detail navigation via onRowClick.","Bulk-action workflows (e.g. mark invoices paid, export selected rows) \u2014 use selectable + DataTable.BulkActions to show contextual action buttons only when something is selected.","Server-side sorted tables: pass sort + onSortChange and update the data prop after the API call; DataTable renders asc/desc/neutral icons on the header automatically.","Cursor-paginated lists: add DataTable.Pagination with cursor + hasMore + onChange inside children to get First/Next navigation without offset arithmetic. For page-size + numbered prev/next instead, use DataTable.Pagination with pageSizeOptions (no cursor/onChange) driven by the internal TanStack pagination.","Full grid screens (global search + column 'set view' + numbered pagination): compose DataTable.Search, DataTable.ViewOptions, DataTable.DensityToggle in the toolbar and DataTable.Pagination pageSizeOptions={[\u2026]} \u2014 client-side by default, or server-side by passing globalFilter/pagination/sort state with the matching manual* flag.",'Responsive admin tables where lower-priority columns (e.g. internal IDs, dates) should collapse below mobile breakpoints \u2014 set hiddenOnMobile: true on those ColumnDef entries. When the columns must all stay DISCOVERABLE at 390 instead (an approval/action queue), use preset="action-collection" + ColumnDef.priority rather than hiding anything.',`Access-approval / action queues at 390px (SCR-105, gh#253): preset="action-collection" + a priority on each ColumnDef keeps requester \xB7 target \xB7 reason \xB7 requested date \xB7 row actions inside the initial narrow frame with no page-local CSS, no consumer width, no hidden column and no horizontal scroll \u2014 see the DataTable 'Approval queue' example page.`,"Loading skeletons during initial page load or filter change: set loading={true} alongside an empty data={[]} to show the loading row without flashing an empty state."],related:["Table \u2014 raw primitive (TableHeader/TableBody/TableRow/TableCell). Use DataTable instead; only reach for Table directly when you need a non-standard layout that DataTable cannot express.","SkeletonTable \u2014 standalone skeleton placeholder rendered before any DataTable mounts (e.g. in a Suspense fallback or deferred-prop skeleton slot). DataTable.loading covers in-table loading; SkeletonTable covers pre-mount skeletons.","EmptyState \u2014 standalone empty state for non-table lists. DataTable already embeds EmptyState in its body; only use bare EmptyState for card content, non-tabular lists, or zero-state pages outside a DataTable.","LineChart / BarChart / AreaChart / PieChart (@godxjp/ui/charts) \u2014 when the SHAPE or trend of aggregated data matters more than exact per-row figures, visualize it with a chart instead of (or alongside) the table; keep DataTable when users need to read, sort, or act on individual rows.","DataState / InfiniteQueryState \u2014 TanStack Query lifecycle widgets from @godxjp/ui/query. Prefer these over DataTable when your list is driven by useQuery/useInfiniteQuery and you want automatic skeleton/empty/error handling at the query level rather than at the table level."],example:`import { useState } from "react";
|
|
257
373
|
import { Badge, DataTable, type ColumnDef } from "@godxjp/ui/data-display";
|
|
258
374
|
import { EmptyState } from "@godxjp/ui/data-display";
|
|
259
375
|
|
|
@@ -321,7 +437,7 @@ export default function InvoiceList({
|
|
|
321
437
|
</DataTable.Toolbar>
|
|
322
438
|
</DataTable>
|
|
323
439
|
);
|
|
324
|
-
}`,storyPath:"data-display/DataTable.stories.tsx",rules:[24,31,35,37]},{name:"Card",group:"data-display",tagline:'Surface container with optional accent stripe, variant fill, size, and density. \u26A0\uFE0F The bare <Card> has NO inner padding \u2014 body content MUST be wrapped in <CardContent> (titles in <CardHeader>), or it sits FLUSH against the card edges. Never hand-roll padding with className="p-4"; use <CardContent>. Compose with CardHeader/CardTitle/CardContent/CardFooter. For a tab/toolbar/filter strip (view tabs, list controls) use <CardBar extra={\u2026}> \u2014 a positionable bar that auto-draws its separator from its position (top\u2192bottom border, bottom\u2192top border, middle\u2192both) and pins `extra` content to the inline-end edge; place it as first/last child of the Card. Never hand-roll a bordered div for this.',props:[{name:"accent",type:'"primary" | "success" | "warning" | "info" | "attention" | "destructive"',description:"3px left-edge semantic accent stripe."},{name:"variant",type:'"default" | "muted" | "outline" | "featured"',defaultValue:'"default"',description:"Surface fill style."},{name:"size",type:'"md" | "compact"',defaultValue:'"md"',description:"Card size preset."},{name:"density",type:'"tight" | "cozy"',description:"Internal padding density (base 16 / tight 12 / cozy 20)."}],usage:['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.',"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.",'DO set <CardTitle level={n}> to keep a valid document outline (h1 \u2192 h2 \u2192 h3, no skipped levels): CardTitle renders <h3> by default, so a section card directly under a page <h1> needs level={2}. Pick the level by OUTLINE position, NEVER for visual size \u2014 the title size is fixed by tokens and does not change with level. When the card title is a styled label rather than a section heading, use <CardTitle as="p"> so it is not announced as a heading.',"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).","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.","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.","DON'T hand-roll a stat/KPI tile with <Card> + raw divs \u2014 use <StatCard> (label, value, hint, delta, layout, inverse props) which is already a Card internally with correct token-driven layout.","SPACING IS BORDER-AWARE & token-driven (theme via src/tokens/components/card.css, never hard-code padding on slots): `--card-space-inset` is the shared horizontal column every slot (header/content/footer) aligns to. A DIVIDED section \u2014 a `banded` header or a `separated` footer, i.e. one carrying a divider border \u2014 pads SYMMETRICALLY top+bottom from `--card-space-divided-y` (a band reads as its own region). A PLAIN header flows into the body instead: top `--card-space-
|
|
440
|
+
}`,storyPath:"data-display/DataTable.stories.tsx",rules:[24,31,35,37]},{name:"Card",group:"data-display",tagline:'Surface container with optional accent stripe, variant fill, size, and density. \u26A0\uFE0F The bare <Card> has NO inner padding \u2014 body content MUST be wrapped in <CardContent> (titles in <CardHeader>), or it sits FLUSH against the card edges. Never hand-roll padding with className="p-4"; use <CardContent>. Compose with CardHeader/CardTitle/CardContent/CardFooter. For a tab/toolbar/filter strip (view tabs, list controls) use <CardBar extra={\u2026}> \u2014 a positionable bar that auto-draws its separator from its position (top\u2192bottom border, bottom\u2192top border, middle\u2192both) and pins `extra` content to the inline-end edge; place it as first/last child of the Card. Never hand-roll a bordered div for this.',props:[{name:"accent",type:'"primary" | "success" | "warning" | "info" | "attention" | "destructive"',description:"3px left-edge semantic accent stripe."},{name:"variant",type:'"default" | "muted" | "outline" | "featured"',defaultValue:'"default"',description:"Surface fill style."},{name:"size",type:'"md" | "compact"',defaultValue:'"md"',description:"Card size preset."},{name:"density",type:'"tight" | "cozy"',description:"Internal padding density (base 16 / tight 12 / cozy 20)."}],usage:['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.',"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.",'DO set <CardTitle level={n}> to keep a valid document outline (h1 \u2192 h2 \u2192 h3, no skipped levels): CardTitle renders <h3> by default, so a section card directly under a page <h1> needs level={2}. Pick the level by OUTLINE position, NEVER for visual size \u2014 the title size is fixed by tokens and does not change with level. When the card title is a styled label rather than a section heading, use <CardTitle as="p"> so it is not announced as a heading.',"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).","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.","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.","DON'T hand-roll a stat/KPI tile with <Card> + raw divs \u2014 use <StatCard> (label, value, hint, delta, layout, inverse props) which is already a Card internally with correct token-driven layout.","SPACING IS BORDER-AWARE & token-driven (theme via src/tokens/components/card.css, never hard-code padding on slots): `--card-space-inset` is the shared horizontal column every slot (header/content/footer) aligns to. A DIVIDED section \u2014 a `banded` header or a `separated` footer, i.e. one carrying a divider border \u2014 pads SYMMETRICALLY top+bottom from `--card-space-divided-y` (a band reads as its own region). A PLAIN header flows into the body instead: top `--card-space-shell-y`, no bottom, and the body supplies the gap via `--card-space-body-y`. THE TWO AXES ARE INDEPENDENT (gh#232): `--card-space-inset` is inline-only, while `--card-space-shell-y` owns the BLOCK shell edges (plain-header top, `solo` body top, terminal slot bottom) and defaults to the inset \u2014 so a shell/theme can make a card SHORTER without narrowing its column by overriding `--card-space-shell-y` alone (this is how AuthShell's `--auth-shell-card-padding-block-compact` reaches CardContent). Never bridge it with a consumer selector on the card-content slot. Special case: a header above `<CardContent flush>` with a <Table> gets its own `--card-space-body-y` bottom gap (the flush table zeroes its top), so the title never butts the table. `--card-space-gap` is the in-slot stack gap (title\u2195description). Tune the band rhythm once at `--card-space-divided-y`; tune the accent stripe width at `--card-accent-rail-width` (default 6px)."],useCases:['Dashboard KPI summary row: wrap each metric in <StatCard> (or a plain <Card size="compact"> with <CardContent>) to render a uniform grid of labeled value tiles with optional trend deltas.','Invoice or order detail panel: <Card accent="primary"> with <CardHeader banded><CardTitle>, <CardContent> body rows (use <Descriptions> inside), and <CardFooter separated> holding approve/reject buttons.',"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.","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.",'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.',"Media/cover card (e.g. entity profile): <CardCover> first (full-bleed image), then <CardHeader> + <CardContent> below it for structured metadata."],related:["StatCard \u2014 use instead of a plain Card when rendering a KPI/metric tile (label + value + optional delta/hint). StatCard is a Card internally; do not re-wrap it in another Card.","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.","Descriptions \u2014 use inside <CardContent> when body content is a label-value metadata list (e.g. entity details, invoice fields); do not hand-roll a dl/dt/dd grid.","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."],example:`import { Card, CardHeader, CardTitle, CardContent } from "@godxjp/ui/data-display";
|
|
325
441
|
|
|
326
442
|
<Card accent="success">
|
|
327
443
|
<CardHeader><CardTitle>\u6CE8\u6587\u30B5\u30DE\u30EA\u30FC</CardTitle></CardHeader>
|
|
@@ -343,11 +459,45 @@ import { ResponsiveGrid } from "@godxjp/ui/layout";
|
|
|
343
459
|
</ResponsiveGrid>
|
|
344
460
|
|
|
345
461
|
// \u274C Double border \u2014 do NOT wrap StatCard in a Card:
|
|
346
|
-
// <Card><CardContent><StatCard label="x" value="1" /></CardContent></Card>`,storyPath:"data-display/StatCard.stories.tsx",rules:[]},{name:"
|
|
462
|
+
// <Card><CardContent><StatCard label="x" value="1" /></CardContent></Card>`,storyPath:"data-display/StatCard.stories.tsx",rules:[]},{name:"ServiceLauncherCard",group:"data-display",tagline:"Token-owned downstream-service launcher tile with semantic icon, status, metadata, action, disabled reason, matching skeleton, and companion catalog CTA.",props:[{name:"icon",type:"LucideIcon",required:!0,description:"Decorative service glyph rendered in the canonical semantic icon surface."},{name:"title",type:"ReactNode",required:!0,description:"Real downstream service display name."},{name:"titleLevel",type:"1 | 2 | 3 | 4",defaultValue:"2",description:"Semantic heading level; visual styling remains token-owned."},{name:"statusLabel",type:"ReactNode",description:"Consumer-provided access/readiness label. The component never infers status."},{name:"statusTone",type:'"success" | "warning" | "destructive" | "info" | "neutral" | "muted"',defaultValue:'"neutral"',description:"Semantic tone corresponding to the real statusLabel."},{name:"description",type:"ReactNode",description:"Localized service summary."},{name:"metadata",type:"ReactNode",description:"Compact mono metadata such as real hostname and subscribed plan."},{name:"action",type:"ReactNode",required:!0,description:"Real launch/detail action, normally a Button or Button asChild link."},{name:"disabledReason",type:"ReactNode",description:"Localized prose reason accompanying a disabled/unavailable action. Rendered ABOVE the action (so assistive tech meets the explanation before the disabled control) and marks the tile data-unavailable, which mutes the medallion via --card-service-launcher-unavailable-icon-*. It never disables the action itself \u2014 that stays the consumer's Button prop."}],usage:["DO provide status, hostname, plan, access state and action from the product's real API contract. ServiceLauncherCard deliberately performs no entitlement or URL inference \u2014 it has no href/entitlement/available prop at all.","DO own the layout with ResponsiveGrid columns={{ sm: 1, md: 2, lg: 3 }} \u2014 the canonical 3\u21922\u21921 launcher grid. ResponsiveGrid queries its OWN container (40/48/64rem), so never hand-write grid-template-columns or a media query in the page. The shorthand columns={3} also works but widens to 2 columns earlier (40rem).","DO render ServiceLauncherCard directly as a grid child; it already owns its Card shell, its 36px medallion (--control-height-lg tier) and the canonical internal rhythm.","DO replace it with ServiceLauncherCardSkeleton while loading (it carries a required `label` and aria-busy, and deliberately opens no live region). Use ServiceCatalogCta as the peer tile only when a real catalog/add route exists.","DO keep `metadata` to machine identifiers (hostname \xB7 plan) \u2014 it is the only mono line. Sentences belong in `description` / `disabledReason`.","DON'T recreate launcher geometry with page-local CSS, utility padding, grid tracks, or a hand-built Card hierarchy. Retune it with the --card-service-launcher-* tokens instead.","DON'T show LIVE, a hostname, subscribed plan, or launch action merely because a service is active in the global catalog."],useCases:["Organization console launcher showing subscribed downstream applications with a real SSO launch action.","Service picker where unavailable apps remain visible with a disabled action and permission/subscription reason.","Responsive 3\u21922\u21921 launcher grid with a final ServiceCatalogCta tile linked to an existing catalog route."],related:["ServiceLauncherCardSkeleton \u2014 shape-matched initial loading placeholder (required `label`, aria-busy, no live region).","ServiceCatalogCta \u2014 dashed companion tile for an existing catalog/add route.","ResponsiveGrid \u2014 OWNS the 3\u21922\u21921 layout around launcher tiles; the launcher never ships grid tracks of its own.","Card \u2014 general-purpose surface; use ServiceLauncherCard instead for this established composite."],example:`import { Clock } from "lucide-react";
|
|
463
|
+
import {
|
|
464
|
+
ServiceCatalogCta,
|
|
465
|
+
ServiceLauncherCard,
|
|
466
|
+
ServiceLauncherCardSkeleton,
|
|
467
|
+
} from "@godxjp/ui/data-display";
|
|
468
|
+
import { Button } from "@godxjp/ui/general";
|
|
469
|
+
import { ResponsiveGrid } from "@godxjp/ui/layout";
|
|
470
|
+
|
|
471
|
+
// ResponsiveGrid owns the canonical 3 \u2192 2 \u2192 1 ladder; the page writes no tracks.
|
|
472
|
+
<ResponsiveGrid columns={{ sm: 1, md: 2, lg: 3 }}>
|
|
473
|
+
{loading
|
|
474
|
+
? services.map((s) => <ServiceLauncherCardSkeleton key={s.id} label={t("loadingService")} />)
|
|
475
|
+
: services.map((s) => (
|
|
476
|
+
<ServiceLauncherCard
|
|
477
|
+
key={s.id}
|
|
478
|
+
icon={Clock}
|
|
479
|
+
title={s.name}
|
|
480
|
+
statusLabel={s.accessLabel}
|
|
481
|
+
statusTone={s.accessTone}
|
|
482
|
+
description={s.description}
|
|
483
|
+
metadata={s.hostnameAndPlan}
|
|
484
|
+
disabledReason={s.blockedReason}
|
|
485
|
+
action={
|
|
486
|
+
<Button asChild={s.canLaunch} disabled={!s.canLaunch}>
|
|
487
|
+
{s.canLaunch ? <a href={s.launchUrl}>{t("launch")}</a> : t("launch")}
|
|
488
|
+
</Button>
|
|
489
|
+
}
|
|
490
|
+
/>
|
|
491
|
+
))}
|
|
492
|
+
<ServiceCatalogCta
|
|
493
|
+
title={t("addFromCatalog")}
|
|
494
|
+
action={<Button variant="outline">{t("viewCatalog")}</Button>}
|
|
495
|
+
/>
|
|
496
|
+
</ResponsiveGrid>`,storyPath:"data-display/ServiceLauncherCard.stories.tsx",rules:[40]},{name:"Badge",group:"data-display",tagline:"Plain or lifecycle badge. Use `variant` for static chips, or `status` to auto-map lifecycle keys to semantic tone + icon. Labels never wrap.",props:[{name:"variant",type:'"default" | "secondary" | "outline" | "dashed"',defaultValue:'"default"',description:"STRUCTURAL emphasis only (fill/border style) \u2014 NOT colour. Use `tone` for semantic colour. `dashed` = dashed border."},{name:"tone",type:'"default" | "primary" | "success" | "warning" | "destructive" | "info" | "muted" | "neutral"',description:'SEMANTIC colour intent (BadgeTone = ToneProp + `primary`). This is the colour knob \u2014 success/warning/destructive/info/etc. `primary` is a SOFT brand pill (tinted brand fill + brand text) for the dashboard role-pill pattern; for a SOLID brand fill use `variant="default"`. Keep variant for structure, tone for meaning.'},{name:"shape",type:'"default" | "pill" | "sharp"',defaultValue:'"default"',description:"Corner radius from the tokens \u2014 `default` (badge radius), `pill` (fully rounded), `sharp` (square). Use the prop instead of a `rounded-*` className."},{name:"status",type:"string",description:"Lifecycle key. Known keys auto-map to tone + icon + i18n label; unknown keys fall back to neutral."},{name:"icon",type:"React.ComponentType<{ className?: string }> | null",description:"Leading icon override. Pass null to suppress the auto status icon."},{name:"children",type:"ReactNode",description:"Badge label. When omitted with status, Badge renders the translated lifecycle label or raw status."}],usage:["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.","DO use `status` for entity lifecycle statuses (active, draft, pending, cancelled, failed, scheduled, etc.) so the component resolves the correct tone, icon, and i18n label.","DO pass `variant` explicitly for localized labels or categorical tiers, and pass `icon={null}` when a lifecycle glyph would be misleading.","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.","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).","Use semantic tokens for any className overrides (`text-muted-foreground`, `bg-destructive`) \u2014 never raw Tailwind palette classes like `bg-green-500`."],useCases:['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>`).','Approval or review state in an accounting list where the value is not a lifecycle key in Badge\'s STATUS_MAP \u2014 e.g. a custom approval tier like `<Badge tone="success">\u627F\u8A8D\u6E08</Badge>` or `<Badge tone="warning">\u8981\u78BA\u8A8D</Badge>`.',"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.",'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.',"Read-only metadata chips inside a Descriptions.Item or Card header where a lifecycle icon would be visually heavy \u2014 e.g. currency code, payment method, or region tag."],related:["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."],example:`import { Badge } from "@godxjp/ui/data-display";
|
|
347
497
|
|
|
348
498
|
<Badge variant="secondary">A/B</Badge>
|
|
349
499
|
<Badge status="active">\u516C\u958B\u4E2D</Badge>
|
|
350
|
-
<Badge status="\u30D7\u30EC\u30DF\u30A2\u30E0" tone="success" icon={null}>\u30D7\u30EC\u30DF\u30A2\u30E0</Badge>`,storyPath:"data-display/Badge.stories.tsx",rules:[35]},{name:"ListRow",group:"data-display",tagline:"Single-line entity row (leading \xB7 title/description \xB7 trailing action) for SHORT lists inside a Card \u2014 sessions, API tokens, linked accounts, passkeys, MFA factors, invitations.",props:[{name:"title",type:"ReactNode",required:!0,description:"Primary line \u2014 rendered in medium weight; truncates to one line."},{name:"description",type:"ReactNode",description:"Secondary line under the title (muted, xs); truncates to one line."},{name:"leading",type:"ReactNode",description:"Leading slot \u2014 a decorative icon or an Avatar. Mark a purely decorative icon `aria-hidden`."},{name:"trailing",type:"ReactNode",description:"Trailing slot \u2014 the row action(s): a Button / DropdownMenu trigger, a Badge, or a Switch."},{name:"align",type:'"center" | "start"',defaultValue:'"center"',description:"Cross-axis alignment of the columns; `start` for multi-line content."},{name:"as",type:'"div" | "li"',defaultValue:'"div"',description:"Render element \u2014 `li` when the parent is a semantic `<ul>`/`<ol>`."}],usage:["DO use ListRow for a SHORT (\u22482\u20138 item) list of entities inside a Card where each row is one line with an action \u2014 account sessions, API keys, linked identities, passkeys. Stack rows in a `<Card><CardContent flush>` so the rows draw their own quiet dividers edge-to-edge.","DON'T reach for DataTable here \u2014 it carries sorting/selection/pagination chrome that a 3-item list doesn't need. DON'T nest a Card per row either (card-in-card). ListRow is the in-between surface.",'DON\'T hand-roll `<div className="flex items-center justify-between border-b py-3">` \u2014 that is exactly the repeated pattern ListRow replaces (border/radius/padding are tokenized via `--list-row-*`).','DO put the row\'s action in `trailing` (a `ghost`/`outline` Button, a DropdownMenu trigger, a Switch, or a status Badge). DO pass `as="li"` when the rows live inside a semantic `<ul>`.'],useCases:["Account security page \u2014 a list of active sessions (device + last-seen as title/description, a destructive 'Revoke' Button in trailing).","Developer settings \u2014 API tokens or passkeys, each row showing the name + created date and a DropdownMenu of actions.","Linked accounts / SSO \u2014 an IdP icon in leading, the provider name + connected email, and a Switch or 'Disconnect' Button trailing."],related:["DataTable \u2014 use instead when the list is long or needs sorting/selection/pagination; ListRow is for short, chrome-light lists.","Card \u2014 ListRow is designed to live inside `<CardContent flush>`; the Card supplies the outer surface and the closing border.","Descriptions \u2014 for a key/value metadata grid on a detail page (no per-row action); ListRow is for actionable entity rows."],example:`import { Card, CardContent, CardHeader, CardTitle, ListRow, Badge } from "@godxjp/ui/data-display";
|
|
500
|
+
<Badge status="\u30D7\u30EC\u30DF\u30A2\u30E0" tone="success" icon={null}>\u30D7\u30EC\u30DF\u30A2\u30E0</Badge>`,storyPath:"data-display/Badge.stories.tsx",rules:[35]},{name:"ListRow",group:"data-display",tagline:"Single-line entity row (leading \xB7 title/description \xB7 trailing action) for SHORT lists inside a Card \u2014 sessions, API tokens, linked accounts, passkeys, MFA factors, invitations.",props:[{name:"title",type:"ReactNode",required:!0,description:"Primary line \u2014 rendered in medium weight; truncates to one line."},{name:"description",type:"ReactNode",description:"Secondary line under the title (muted, xs); truncates to one line."},{name:"leading",type:"ReactNode",description:"Leading slot \u2014 a decorative icon or an Avatar. Mark a purely decorative icon `aria-hidden`."},{name:"trailing",type:"ReactNode",description:"Trailing slot \u2014 the row action(s): a Button / DropdownMenu trigger, a Badge, or a Switch."},{name:"align",type:'"center" | "start"',defaultValue:'"center"',description:"Cross-axis alignment of the columns; `start` for multi-line content."},{name:"as",type:'"div" | "li"',defaultValue:'"div"',description:"Render element \u2014 `li` when the parent is a semantic `<ul>`/`<ol>`."},{name:"overflow",type:'"truncate" | "wrap"',defaultValue:'"truncate"',description:"How title/description resolve content longer than the row \u2014 `truncate` (one line + ellipsis) or `wrap` (multi-line; long unbroken tokens break via `overflow-wrap: anywhere`). Either way the content column may shrink below its intrinsic width, so the row never widens the page root."},{name:"density",type:'"default" | "compact"',defaultValue:'"default"',description:"Row geometry. `compact` is the inline-actions preset (gh#246): tighter block padding and column gap plus a LOWER body threshold (`--list-row-compact-*`), so a leading Avatar, the title/description and one or two small trailing Buttons stay INLINE inside a narrow card (\u2248326px content) at 390px, and a history Badge + ISO-8601 date stays on the title's line. It only lowers thresholds \u2014 the row and its trailing cluster still wrap, so a cluster that cannot fit drops to its own line rather than widening the page root."},{name:"unread",type:"boolean",description:"Read/unread state for notification rows \u2014 renders the indicator dot (with localized `sr-only` text, never colour alone) plus the tokenized `--list-row-unread-background`. OMIT the prop for rows with no read state; pass `false` for a read row so its title keeps the same optical axis as the unread ones."}],usage:["DO use ListRow for a SHORT (\u22482\u20138 item) list of entities inside a Card where each row is one line with an action \u2014 account sessions, API keys, linked identities, passkeys. Stack rows in a `<Card><CardContent flush>` so the rows draw their own quiet dividers edge-to-edge.","DON'T reach for DataTable here \u2014 it carries sorting/selection/pagination chrome that a 3-item list doesn't need. DON'T nest a Card per row either (card-in-card). ListRow is the in-between surface.",'DON\'T hand-roll `<div className="flex items-center justify-between border-b py-3">` \u2014 that is exactly the repeated pattern ListRow replaces (border/radius/padding are tokenized via `--list-row-*`).','DO put the row\'s action in `trailing` (a `ghost`/`outline` Button, a DropdownMenu trigger, a Switch, or a status Badge). DO pass `as="li"` when the rows live inside a semantic `<ul>`.','DO use `unread` for a notification list \u2014 the dot is a SHAPE with localized `sr-only` text ("Unread"/"Read"), so it never reads as colour alone, and the row surface reads `--list-row-unread-background` (default `hsl(var(--muted))` \u2014 chosen so the xs muted description line stays WCAG AA on the emphasized surface; `--accent` would drop it to 4.23:1). DON\'T substitute a `Badge` \u2014 that renders a labelled pill, not a compact status dot.','DO pass `density="compact"` for the canonical invitation / history row \u2014 an Avatar, a title (+ description) and one or two small trailing Buttons that must read as ONE line inside a narrow card (\u2248326px content at 390px), or a history row whose status Badge + ISO-8601 date belongs beside the title. Measured at 390px: 62px tall vs 126px at the default density (where the actions wrapped), history row 41px vs 114px. DON\'T reach for it just to "make things tighter" on a roomy page \u2014 the default density is the entity-row measure.','DON\'T add one-off `min-width`/wrapping CSS in the consumer app for a long title + two trailing Buttons. The row already shrinks and WRAPS: the content column keeps only `min(var(--list-row-body-min-width), 100%)` and the trailing actions drop onto their own line below the threshold. Retune the threshold with `--list-row-body-min-width` (default 12rem) and the action gap with `--list-row-trailing-gap`; pass `overflow="wrap"` (usually with `align="start"`) when the title must stay fully readable at 390px instead of truncating.'],useCases:["Account security page \u2014 a list of active sessions (device + last-seen as title/description, a destructive 'Revoke' Button in trailing).","Developer settings \u2014 API tokens or passkeys, each row showing the name + created date and a DropdownMenu of actions.","Linked accounts / SSO \u2014 an IdP icon in leading, the provider name + connected email, and a Switch or 'Disconnect' Button trailing.",'Notifications inbox \u2014 `unread` rows carry the dot + emphasized surface, `overflow="wrap"` keeps a long JA/EN/VI title and its ISO-8601 timestamp readable, and two inline trailing Buttons (Mark as read / Open) wrap under the text at 390px.',"Pending invitations \u2014 Avatar in leading, the organization/invitation name as title, and Accept + Decline Buttons in trailing that stack at narrow widths without a horizontal page scrollbar."],related:["DataTable \u2014 use instead when the list is long or needs sorting/selection/pagination; ListRow is for short, chrome-light lists.","Card \u2014 ListRow is designed to live inside `<CardContent flush>`; the Card supplies the outer surface and the closing border.","Descriptions \u2014 for a key/value metadata grid on a detail page (no per-row action); ListRow is for actionable entity rows."],example:`import { Card, CardContent, CardHeader, CardTitle, ListRow, Badge } from "@godxjp/ui/data-display";
|
|
351
501
|
import { Button } from "@godxjp/ui/general";
|
|
352
502
|
import { Smartphone } from "lucide-react";
|
|
353
503
|
|
|
@@ -369,6 +519,26 @@ import { Smartphone } from "lucide-react";
|
|
|
369
519
|
trailing={<Button size="xs" variant="outline">\u30ED\u30B0\u30A2\u30A6\u30C8</Button>}
|
|
370
520
|
/>
|
|
371
521
|
</CardContent>
|
|
522
|
+
</Card>
|
|
523
|
+
|
|
524
|
+
// Notifications \u2014 unread dot + emphasized surface, wrapping title, two inline actions
|
|
525
|
+
<Card>
|
|
526
|
+
<CardContent flush>
|
|
527
|
+
<ListRow
|
|
528
|
+
unread
|
|
529
|
+
align="start"
|
|
530
|
+
overflow="wrap"
|
|
531
|
+
title="\u7D44\u7E54\u300C\u30B0\u30ED\u30FC\u30D0\u30EB\u30FB\u30C8\u30E9\u30F3\u30B9\u30D5\u30A9\u30FC\u30E1\u30FC\u30B7\u30E7\u30F3\u63A8\u9032\u672C\u90E8\u300D\u3078\u306E\u62DB\u5F85\u304C\u5C4A\u3044\u3066\u3044\u307E\u3059"
|
|
532
|
+
description="2026-07-30 09:12 JST"
|
|
533
|
+
trailing={
|
|
534
|
+
<>
|
|
535
|
+
<Button size="xs" variant="ghost">\u65E2\u8AAD\u306B\u3059\u308B</Button>
|
|
536
|
+
<Button size="xs" variant="outline">\u958B\u304F</Button>
|
|
537
|
+
</>
|
|
538
|
+
}
|
|
539
|
+
/>
|
|
540
|
+
<ListRow unread={false} align="start" overflow="wrap" title="\u8ACB\u6C42\u66F8\u304C\u767A\u884C\u3055\u308C\u307E\u3057\u305F" description="2026-07-28 18:40 JST" />
|
|
541
|
+
</CardContent>
|
|
372
542
|
</Card>`,storyPath:"data-display/ListRow.stories.tsx",rules:[42,44]},{name:"CredentialReveal",group:"data-display",tagline:"One-time secret surface \u2014 masked-by-default value with a show/hide toggle, a copy button that confirms the copy, optional download, and an optional acknowledge action to pair with Dialog. The GitHub/Stripe token-reveal pattern as a real primitive so consumers stop hand-rolling it.",props:[{name:"secret",type:"string",required:!0,description:"The one-time secret value."},{name:"label",type:"string",description:"Accessible name / caption for the secret (e.g. 'API key')."},{name:"warning",type:"React.ReactNode | null",description:"Caution banner copy; defaults to a localized 'shown only once' warning. Pass null to suppress the banner."},{name:"revealed",type:"boolean",description:"Controlled reveal state (with defaultRevealed / onRevealedChange)."},{name:"defaultRevealed",type:"boolean",defaultValue:"false",description:"Uncontrolled initial reveal state."},{name:"onRevealedChange",type:"(revealed: boolean) => void",description:"Reveal toggle handler."},{name:"onCopy",type:"(secret: string) => void",description:"Called after the secret is written to the clipboard."},{name:"onAcknowledge",type:"() => void",description:"Renders a confirm button; wire it to the Dialog's onOpenChange(false)."},{name:"downloadable",type:"boolean",defaultValue:"false",description:"Offer a download-as-file button."},{name:"size",type:'"xs" | "sm" | "md" | "lg"',defaultValue:'"md"',description:"Action button size tier."},{name:"tone",type:'"warning" | "destructive" | "info"',defaultValue:'"warning"',description:"Caution banner severity."}],usage:["DO use for a secret shown exactly once after creation (device credential, API key, service-account secret) \u2014 it masks by default and confirms the copy.","DO pair it inside a Dialog and reset via controlled `revealed`/`onRevealedChange` (or let it re-blur automatically when the `secret` prop changes) so a reopened dialog starts masked.","DO pass `onAcknowledge` to gate the dialog close behind an explicit 'I've saved it' confirmation.","DON'T use it for an editable password field \u2014 that's PasswordInput. CredentialReveal is read-only display of an issued secret.","DON'T hand-roll the copy button + copied-state + aria-live announcement; it's built in."],useCases:["Device credential issued after enrollment","API key / personal access token shown once on creation","Service-account secret / client secret reveal","Recovery code or one-time bootstrap password"],related:["PasswordInput \u2014 editable password/secret ENTRY with a show/hide toggle (data-entry); CredentialReveal is read-only DISPLAY of an issued secret.","Dialog \u2014 the modal CredentialReveal is designed to live inside.","Alert \u2014 the caution banner CredentialReveal composes internally."],example:`import { CredentialReveal } from "@godxjp/ui/data-display";
|
|
373
543
|
import { Dialog, DialogContent, DialogHeader, DialogTitle } from "@godxjp/ui/feedback";
|
|
374
544
|
|
|
@@ -405,7 +575,7 @@ import { Flex } from "@godxjp/ui/layout";
|
|
|
405
575
|
<Descriptions.Item label="\u4F1A\u54E1ID" mono>{member.id}</Descriptions.Item>
|
|
406
576
|
<Descriptions.Item label="\u30D7\u30E9\u30F3">{member.plan}</Descriptions.Item>
|
|
407
577
|
<Descriptions.Item label="\u30E1\u30E2" span={2}>{member.note}</Descriptions.Item>
|
|
408
|
-
</Descriptions>`,storyPath:"data-display/Descriptions.stories.tsx",rules:[]},{name:"EmptyState",group:"data-display",tagline:"Centred empty placeholder with icon, title, description, and optional CTA.",props:[{name:"title",type:"string",required:!0,description:"Primary empty message."},{name:"description",type:"string",description:"Secondary helper text."},{name:"icon",type:"LucideIcon",description:"Icon above the title."},{name:"action",type:"ReactNode",description:"CTA element (e.g. a Button)."},{name:"variant",type:'"page" | "section" | "compact"',defaultValue:'"page"',description:"Contextual visual weight. Compact omits the icon medallion."},{name:"tone",type:'"muted" | "success" | "warning" | "destructive" | "info"',defaultValue:'"muted"',description:"Medallion colour intent (a subset of the shared tone vocabulary; `destructive` is the DS name for a danger state).
|
|
578
|
+
</Descriptions>`,storyPath:"data-display/Descriptions.stories.tsx",rules:[]},{name:"EmptyState",group:"data-display",tagline:"Centred empty placeholder with icon, title, description, and optional CTA.",props:[{name:"title",type:"string",required:!0,description:"Primary empty message."},{name:"description",type:"string",description:"Secondary helper text."},{name:"icon",type:"LucideIcon",description:"Icon above the title."},{name:"action",type:"ReactNode",description:"CTA element (e.g. a Button)."},{name:"variant",type:'"page" | "section" | "compact"',defaultValue:'"page"',description:"Contextual visual weight. Compact omits the icon medallion."},{name:"tone",type:'"muted" | "success" | "warning" | "destructive" | "info"',defaultValue:'"muted"',description:"Medallion colour intent (a subset of the shared tone vocabulary; `destructive` is the DS name for a danger state). Recolours BOTH the icon GLYPH and the medallion fill from the matching role token \u2014 set `success` for a confirmation zero-state (e.g. device approved) instead of hand-rolling a `.ui-success-state` class. The glyph inherits `--empty-state-icon-foreground`; never pass a `text-*` colour utility on your icon, it would out-specify the token and pin every tone to muted."},{name:"titleLevel",type:"1 | 2 | 3 | 4",defaultValue:"3",description:"Semantic heading level of the title. Pick it to keep the page outline valid (h1 \u2192 h2 \u2192 h3, no skipped levels), NEVER for visual size \u2014 the title size is fixed regardless of level. A page/onboarding empty state directly under the page h1 uses titleLevel={2}; one nested in an already-h2 section keeps the default 3."},{name:"titleAs",type:'"h1" | "h2" | "h3" | "h4" | "p" | "div"',description:"Render the title as a non-heading element (p/div) instead of a heading \u2014 for a compact/section empty state inside a section that already owns its heading, so the message is not announced as a heading and cannot skip an outline level. Overrides titleLevel."}],usage:["DO always pass `title` \u2014 it is the only required prop and renders a heading (`<h3>` by default); omitting it causes a blank silent render with no visible error.",'DO set `titleLevel` to match the page outline (page h1 \u2192 section h2 \u2192 nested h3) so the empty state does not trigger a heading-order violation. Choose the level for OUTLINE position, never for visual size \u2014 the size never changes with the level. When the empty state sits in a section that already has its own heading, use `titleAs="p"` so the message is not a heading at all.','DO use `tone="success"` (or warning/destructive/info) for a semantic confirmation/alert zero-state \u2014 it recolours the icon medallion from the role token; do NOT hand-roll a `.ui-success-state` class that scopes `--empty-state-icon-*`.',"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 />`.","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.","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.","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.","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.","DO omit optional secondary sections when absence has no user value. Otherwise use variant='compact' or 'section'; reserve page for the primary page job.","DO match empty-state visual weight to the section's importance and expected content density \u2014 a low-priority 'no received invitations' block uses variant='compact' (no medallion, minimal padding), not the full page treatment that would outweigh real content.","DO NOT wrap every empty condition in its own bordered Card. A compact/section empty state sits directly in the existing CardContent / section it belongs to; a dedicated bordered Card is only for a page-level or standalone zero-state."],useCases:["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.","Post-filter / post-search zero results \u2014 show `icon={SearchIcon}` + a `description` explaining what was searched and an `action` to clear filters.","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.","Passed as the `empty=` prop inside `DataState` or `InfiniteQueryState` to satisfy the TanStack Query lifecycle widget's zero-items slot without hand-rolling markup.","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.","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`/`AlertMutationFeedback`."],related:["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.","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.","InfiniteQueryState \u2014 same pattern as DataState but for `useInfiniteQuery`; pass EmptyState to `empty=` when the flattened list is empty.","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."],example:`import { EmptyState } from "@godxjp/ui/data-display";
|
|
409
579
|
|
|
410
580
|
<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" />`,storyPath:"data-display/EmptyState.stories.tsx",rules:[]},{name:"Progress",group:"data-display",tagline:"Horizontal progress bar 0\u2013100 with optional label, semantic tone, and an over-capacity (striped) state for over-limit meters.",props:[{name:"value",type:"number",required:!0,description:"Progress percentage 0\u2013100 (clamped unless `over`)."},{name:"label",type:"string",description:"Text label beside/below the bar."},{name:"tone",type:'"success" | "warning" | "destructive"',defaultValue:'"success"',description:"Bar colour tone. Over-capacity defaults to destructive."},{name:"over",type:"boolean",defaultValue:"false",description:"Allow value > 100 to render an over-capacity fill: bar caps at 100% width but gets a diagonal hatch + destructive tone (e.g. 252%). aria-valuetext reports the real ratio. Off by default (clamps to 100)."}],usage:['DO import from `@godxjp/ui/data-display`, not from a generic UI path: `import { Progress } from "@godxjp/ui/data-display";`',"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.",'DO drive `tone` dynamically from business logic \u2014 e.g. `variant={pct >= 80 ? "warning" : "success"}` \u2014 to communicate threshold status semantically rather than with raw colour classes.',"DON'T use a `disabled` Slider as a read-only progress bar \u2014 Slider is semantically an interactive control even when disabled, which pollutes the a11y tree and exposes the wrong ARIA role (`slider` vs `progressbar`). Progress renders the correct read-only indicator.","DON'T pass children or sub-components \u2014 Progress is a single self-contained element (track + bar + label). The `label` prop is the only text injection point; don't wrap it in a custom parent div to add a label alongside it.","DON'T use Progress for editable numeric input or range selection \u2014 it has no callbacks, no interactivity, and no form `name` prop. Use Slider (bounded range input) or Input (free-form number) for data-entry scenarios."],useCases:['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%.','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`.',"Storage or quota indicator in an admin panel \u2014 visualise disk usage, API quota, or seat licence consumption against a fixed limit.","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.","StatCard companion \u2014 pair with a `StatCard` metric to add a visual fill below the KPI number, reinforcing how close a target is to being met.","Multi-step onboarding or setup checklist \u2014 render one Progress per section (e.g. 3/5 steps complete = 60%) to give users a quick scan of overall progress across areas.","Over-capacity meter \u2014 an air-cargo weight/volume load or an over-booked resource pushed past its limit (e.g. 252%): pass `over` with the real ratio to get a red diagonal-hatched bar that reads unmistakably as over-limit, not merely full."],related:["Slider \u2014 use Slider when the user must drag or set a bounded numeric value (volume, priority, price range); use Progress when the value is read-only and must not be interacted with.","Steps \u2014 use Steps for a discrete, named sequence of phases (onboarding wizard, checkout flow) where each step has a label and a clear current/done/pending state; use Progress for a continuous 0\u2013100 fill.",'Badge / Badge \u2014 use Badge or Badge to communicate a categorical status label (e.g. "Paid", "Overdue") without a fill metaphor; use Progress when the numeric proportion itself is the information.',"StatCard \u2014 use StatCard to headline a single KPI metric with a title; compose Progress inside or alongside StatCard when a visual fill adds meaning to the number.","BarChart / PieChart / LineChart (@godxjp/ui/charts) \u2014 Progress shows ONE ratio against a target; the moment you have several series, categories, or a part-to-whole split (or a value changing over time), move up to a chart instead of stacking many Progress bars."],example:`import { Progress } from "@godxjp/ui/data-display";
|
|
411
581
|
|
|
@@ -430,7 +600,7 @@ import { Flex } from "@godxjp/ui/layout";
|
|
|
430
600
|
{ title: "\u8ACB\u6C42\u66F8\u3092\u767A\u884C", status: "done" },
|
|
431
601
|
{ title: "\u627F\u8A8D\u5F85\u3061", status: "current" },
|
|
432
602
|
{ title: "\u6D88\u8CBB\u7A0E\u3092\u8A08\u4E0A", status: "pending" },
|
|
433
|
-
]} />`,storyPath:"data-display/Timeline.stories.tsx",rules:[]},{name:"Table",group:"data-display",tagline:"Primitive table shell (Table/TableHeader/TableBody/TableRow/TableHead/TableCell). Prefer DataTable for admin lists; use these for custom one-off tables.",props:[{name:"children",type:"ReactNode",required:!0,description:"TableHeader / TableBody composition."},{name:"className",type:"string",description:"Extra classes on the table element."},{name:"scrollable",type:"boolean",description:"Whether Table owns its own horizontal-scroll region (default true). Leave true for a standalone table so a table wider than its container scrolls in a keyboard-reachable wrapper. Set false only when an ancestor already provides the scroll region (DataTable does) to avoid a redundant nested scroller + duplicate keyboard tab stop."}],usage:["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.",'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.','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.',"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.","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.","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."],useCases:["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.","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.","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.","Embedded sub-tables inside a DataTable expanded row (the inner table uses Table primitives because nesting a full DataTable instance inside another is unsupported).","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."],related:["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.","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.","Descriptions \u2014 choose Descriptions when content is label\u2192value pairs (two columns, no repeated rows of the same type). Table is better when every row shares the same typed columns.","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."],example:`import { Table, TableHeader, TableBody, TableRow, TableHead, TableCell } from "@godxjp/ui/data-display";
|
|
603
|
+
]} />`,storyPath:"data-display/Timeline.stories.tsx",rules:[]},{name:"Table",group:"data-display",tagline:"Primitive table shell (Table/TableHeader/TableBody/TableRow/TableHead/TableCell). Prefer DataTable for admin lists; use these for custom one-off tables.",props:[{name:"children",type:"ReactNode",required:!0,description:"TableHeader / TableBody composition."},{name:"className",type:"string",description:"Extra classes on the table element."},{name:"scrollable",type:"boolean",description:"Whether Table owns its own horizontal-scroll region (default true). Leave true for a standalone table so a table wider than its container scrolls in a keyboard-reachable wrapper. Set false only when an ancestor already provides the scroll region (DataTable does) to avoid a redundant nested scroller + duplicate keyboard tab stop."},{name:"preset",type:'"default" | "action-collection"',defaultValue:'"default"',description:'Named collection contract. "default" emits no attribute and keeps the plain table. "action-collection" is the canonical dense approval/action queue (gh#253): the desktop INTRINSIC column widths (which make a five-column queue wider than its card and force a horizontal scroll at 390) are replaced by table-layout: fixed plus the token-owned column PRIORITY measures (--table-action-collection-*), and cells wrap. Mark each column with `priority` on its TableHead AND its TableCell. Semantics are untouched \u2014 no display change, no role rewriting, no card transformation \u2014 so header association, aria-sort and screen-reader table navigation are identical at 390 and 1440.'},{name:"collapseBelow",type:'"sm" | "md" | "lg" | "xl"',defaultValue:'"sm"',description:`Step at which preset="action-collection" switches to the compact priority measures, measured against the TABLE'S OWN container (a container query), not the viewport \u2014 a table inside a master rail collapses before the page does. Ignored while preset is "default".`}],usage:["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.",'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.','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.',"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.","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.",'DO reach for `preset="action-collection"` for a dense approval / action queue (requester \xB7 target \xB7 reason \xB7 requested date \xB7 row actions) that must stay readable at 390px, and mark every column with `priority` on BOTH its `TableHead` and its `TableCell`: `primary` (the row subject), `secondary` (its target), `meta` (a timestamp/id), `actions` (the row-action affordance, whose measure is reserved first so it can never be pushed off-screen). Leave the free-text column unmarked \u2014 it takes the remaining space. Never add a consumer width, a hidden column or a page-local breakpoint to make a table fit; retune `--table-action-collection-*` instead. The IDENTICAL preset exists on `DataTable` (`preset` + `collapseBelow` on the table, `priority` on the `ColumnDef`) sharing these same tokens \u2014 use DataTable when the queue is data-driven and needs sorting/selection/pagination, and reach for the raw `Table` only for a hand-authored queue.',"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."],useCases:["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.","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.","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.","Embedded sub-tables inside a DataTable expanded row (the inner table uses Table primitives because nesting a full DataTable instance inside another is unsupported).","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."],related:["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.","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.","Descriptions \u2014 choose Descriptions when content is label\u2192value pairs (two columns, no repeated rows of the same type). Table is better when every row shares the same typed columns.","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."],example:`import { Table, TableHeader, TableBody, TableRow, TableHead, TableCell } from "@godxjp/ui/data-display";
|
|
434
604
|
|
|
435
605
|
<Table>
|
|
436
606
|
<TableHeader><TableRow><TableHead>\u9805\u76EE</TableHead><TableHead className="text-right">\u91D1\u984D</TableHead></TableRow></TableHeader>
|
|
@@ -647,7 +817,7 @@ function CreateDialog() {
|
|
|
647
817
|
</DialogContent>
|
|
648
818
|
</Dialog>
|
|
649
819
|
);
|
|
650
|
-
}`,storyPath:"feedback/Dialog.stories.tsx",rules:[23,3]},{name:"AlertDialog",group:"feedback",tagline:'Canonical modal confirmation flow (destructive / high-stakes decisions). Preserves confirm semantics with `role="alertdialog"` and built-in cancel/confirm handling.',props:[{name:"open",type:"boolean",description:"Controlled open state."},{name:"onOpenChange",type:"(open: boolean) => void",description:"Open-state change handler."},{name:"title",type:"string",required:!0,description:"Accessible title/announcement for the alertdialog."},{name:"description",type:"string",description:"Optional supporting explanatory text."},{name:"confirmLabel",type:"string",description:"Primary action label (defaults to translated continue)."},{name:"cancelLabel",type:"string",description:"Dismiss action label (defaults to translated cancel)."},{name:"variant",type:'"default" | "destructive"',defaultValue:'"default"',description:"Variant passed through to the confirm button."},{name:"confirmPhrase",type:"string",description:"Optional type-to-confirm phrase to prevent accidental confirm."},{name:"challenge",type:"string",description:"Semantic alias of `confirmPhrase` \u2014 the exact token to type (e.g. an org slug) before confirm arms."},{name:"onConfirm",type:"() => Promise<void> | void",description:"Primary action handler."},{name:"stepUp",type:"() => Promise<boolean> | boolean",description:"Optional step-up re-auth (passkey/2FA) gate; must resolve truthy before `onConfirm` fires. Returning false keeps the dialog open and announces failure."},{name:"keepOpenOnConfirm",type:"boolean",description:"Keep modal open after confirm when true."},{name:"pending",type:"boolean",description:"Disable actions while async work is running."}],usage:["Use `AlertDialog` for destructive/irreversible actions (delete, void, unpublish, archive, etc.).","Use `confirmPhrase`/`challenge` for high-friction operations (e.g. typing an org slug) to reduce accidental confirmation \u2014 both force the destructive tone.","Pass `stepUp` for a passkey/2FA re-auth gate that must resolve truthy before `onConfirm` runs (refunds, org deletion).","Pass `keepOpenOnConfirm` when the confirm handler advances a multi-step flow and should not close immediately."],useCases:["Dangerous delete or irreversible workflow confirmation that should block the background UI.","Organization/resource deletion gated behind typing the exact slug (`challenge`).","Refunds or privileged actions requiring step-up re-authentication before they run.","Destructive batch operations that should remain modal and explicit until action is intentionally confirmed."],related:["Dialog \u2014 use for form-style and non-destructive modal flows, no confirm preset behavior."],example:`import { AlertDialog } from "@godxjp/ui/feedback";
|
|
820
|
+
}`,storyPath:"feedback/Dialog.stories.tsx",rules:[23,3]},{name:"AlertDialog",group:"feedback",tagline:'Canonical modal confirmation flow (destructive / high-stakes decisions). Preserves confirm semantics with `role="alertdialog"` and built-in cancel/confirm handling.',props:[{name:"open",type:"boolean",description:"Controlled open state."},{name:"onOpenChange",type:"(open: boolean) => void",description:"Open-state change handler."},{name:"title",type:"string",required:!0,description:"Accessible title/announcement for the alertdialog."},{name:"description",type:"string",description:"Optional supporting explanatory text."},{name:"confirmLabel",type:"string",description:"Primary action label (defaults to translated continue)."},{name:"cancelLabel",type:"string",description:"Dismiss action label (defaults to translated cancel)."},{name:"variant",type:'"default" | "destructive"',defaultValue:'"default"',description:"Variant passed through to the confirm button."},{name:"confirmPhrase",type:"string",description:"Optional type-to-confirm phrase to prevent accidental confirm."},{name:"challenge",type:"string",description:"Semantic alias of `confirmPhrase` \u2014 the exact token to type (e.g. an org slug) before confirm arms."},{name:"onConfirm",type:"() => Promise<void> | void",description:"Primary action handler."},{name:"stepUp",type:"() => Promise<boolean> | boolean",description:"Optional step-up re-auth (passkey/2FA) gate; must resolve truthy before `onConfirm` fires. Returning false keeps the dialog open and announces failure."},{name:"keepOpenOnConfirm",type:"boolean",description:"Keep modal open after confirm when true."},{name:"pending",type:"boolean",description:"Disable actions while async work is running."}],usage:["Use `AlertDialog` for destructive/irreversible actions (delete, void, unpublish, archive, etc.).","Use `confirmPhrase`/`challenge` for high-friction operations (e.g. typing an org slug) to reduce accidental confirmation \u2014 both force the destructive tone.","Pass `stepUp` for a passkey/2FA re-auth gate that must resolve truthy before `onConfirm` runs (refunds, org deletion).","Pass `keepOpenOnConfirm` when the confirm handler advances a multi-step flow and should not close immediately."],useCases:["Dangerous delete or irreversible workflow confirmation that should block the background UI.","Organization/resource deletion gated behind typing the exact slug (`challenge`).","Refunds or privileged actions requiring step-up re-authentication before they run.","Destructive batch operations that should remain modal and explicit until action is intentionally confirmed."],related:["Dialog \u2014 use for form-style and non-destructive modal flows, no confirm preset behavior.","AlertDialogRoot \u2014 the compound counterpart. Reach for it only when the confirmation body needs content this flat preset does not cover (a summary table, a diff, a nested list); the preset already covers title/description/challenge/step-up."],example:`import { AlertDialog } from "@godxjp/ui/feedback";
|
|
651
821
|
|
|
652
822
|
<AlertDialog
|
|
653
823
|
open={open}
|
|
@@ -661,7 +831,34 @@ function CreateDialog() {
|
|
|
661
831
|
setOpen(false);
|
|
662
832
|
}}
|
|
663
833
|
variant="destructive"
|
|
664
|
-
/>`,storyPath:"feedback/AlertDialog.stories.tsx",rules:[23,3]},{name:"
|
|
834
|
+
/>`,storyPath:"feedback/AlertDialog.stories.tsx",rules:[23,3]},{name:"AlertDialogRoot",group:"feedback",tagline:`Compound alertdialog Root \u2014 the role="alertdialog" mirror of Dialog's root. Wraps Radix AlertDialog.Root and supplies the context AlertDialogTitle/AlertDialogDescription/AlertDialogAction/AlertDialogCancel read. Parts: AlertDialogTrigger/AlertDialogPortal/AlertDialogOverlay/AlertDialogContent/AlertDialogHeader/AlertDialogFooter/AlertDialogTitle/AlertDialogDescription/AlertDialogAction/AlertDialogCancel.`,props:[{name:"open",type:"boolean",description:"Controlled open state."},{name:"defaultOpen",type:"boolean",description:"Uncontrolled initial open state (use with AlertDialogTrigger)."},{name:"onOpenChange",type:"(open: boolean) => void",description:"Open-state change handler."},{name:"children",type:"React.ReactNode",description:"The trigger and the portalled alertdialog parts."}],usage:["Reach for the flat `AlertDialog` preset FIRST \u2014 it already covers title/description/confirm/cancel, the typed `challenge`, `stepUp` re-auth and `pending`. Use `AlertDialogRoot` only when the confirm body needs content the preset does not model (an impact summary, a diff, a nested list).","DO name it `AlertDialogRoot`, not `AlertDialog` \u2014 the `AlertDialog` export is the flat preset and takes a completely different (non-compound) prop API.","DO include `AlertDialogHeader` with `AlertDialogTitle` inside every `AlertDialogContent` \u2014 Radix requires an accessible title for `role=alertdialog`; omitting it warns in the console and breaks screen-reader announcement. `AlertDialogHeader` also takes the prop-driven `title`/`subtitle`/`extra`/`tone` form, where `subtitle` renders the `AlertDialogDescription`.","DO portal explicitly: `AlertDialogContent` does NOT self-portal (unlike `DialogContent`). Wrap it in `AlertDialogPortal` with a sibling `AlertDialogOverlay`, or the scrim and stacking context are wrong.","DO use `AlertDialogAction` / `AlertDialogCancel` for the footer buttons \u2014 they carry the button styling AND the Radix close semantics. Do not wrap them in `asChild` `<Button variant=\u2026>`: the Root's button classes and the child's would both land on the element and the variant would not win.","DO NOT import `@radix-ui/react-alert-dialog` directly in a consumer app. Everything the compound needs is exported from `@godxjp/ui/feedback`."],useCases:["Confirm-with-impact-summary \u2014 a destructive confirmation that must show what will be affected (a list of 3 downstream jobs, a table of invoices) before the user commits. The flat preset only takes a string `description`.","Batch-close confirmation whose body renders a rich breakdown (counts, totals, per-tenant rows) alongside the standard confirm/cancel pair.","Any confirmation that must keep `role=alertdialog` semantics (focus trap, no dismiss-on-outside-click) but needs freeform children \u2014 use Dialog only when the flow is non-destructive."],related:["AlertDialog \u2014 the flat preset. Prefer it; it is the canonical destructive-confirm recipe and needs no compound markup.","Dialog \u2014 compound modal for form-style, non-destructive flows. `role=dialog`, dismissible on outside click.","AlertDialogHeader \u2014 the header band; `tone` tints only the background (default | success | warning | destructive | info | muted | neutral)."],example:`import {
|
|
835
|
+
AlertDialogRoot, AlertDialogTrigger, AlertDialogPortal, AlertDialogOverlay,
|
|
836
|
+
AlertDialogContent, AlertDialogHeader, AlertDialogTitle, AlertDialogDescription,
|
|
837
|
+
AlertDialogFooter, AlertDialogAction, AlertDialogCancel,
|
|
838
|
+
} from "@godxjp/ui/feedback";
|
|
839
|
+
import { Button } from "@godxjp/ui/general";
|
|
840
|
+
|
|
841
|
+
function ConfirmSettlement() {
|
|
842
|
+
return (
|
|
843
|
+
<AlertDialogRoot>
|
|
844
|
+
<AlertDialogTrigger asChild><Button variant="outline" size="sm">\u652F\u6255\u3092\u78BA\u5B9A</Button></AlertDialogTrigger>
|
|
845
|
+
<AlertDialogPortal>
|
|
846
|
+
<AlertDialogOverlay />
|
|
847
|
+
<AlertDialogContent className="max-w-md">
|
|
848
|
+
<AlertDialogHeader>
|
|
849
|
+
<AlertDialogTitle>2026\u5E747\u6708\u5206\u306E\u652F\u6255\u3092\u78BA\u5B9A\u3057\u307E\u3059\u304B\uFF1F</AlertDialogTitle>
|
|
850
|
+
<AlertDialogDescription>\u78BA\u5B9A\u3059\u308B\u3068\u632F\u8FBC\u30C7\u30FC\u30BF\u304C\u751F\u6210\u3055\u308C\u307E\u3059\u3002</AlertDialogDescription>
|
|
851
|
+
</AlertDialogHeader>
|
|
852
|
+
{/* freeform impact summary goes here */}
|
|
853
|
+
<AlertDialogFooter>
|
|
854
|
+
<AlertDialogCancel>\u623B\u308B</AlertDialogCancel>
|
|
855
|
+
<AlertDialogAction>\u78BA\u5B9A\u3059\u308B</AlertDialogAction>
|
|
856
|
+
</AlertDialogFooter>
|
|
857
|
+
</AlertDialogContent>
|
|
858
|
+
</AlertDialogPortal>
|
|
859
|
+
</AlertDialogRoot>
|
|
860
|
+
);
|
|
861
|
+
}`,storyPath:"feedback/AlertDialog.stories.tsx",rules:[23,3]},{name:"Sheet",group:"feedback",tagline:"Side-panel drawer / responsive detail panel (Radix Dialog). Parts: Sheet/SheetTrigger/SheetContent(side=right|left|top|bottom, responsive=auto|side|bottom)/SheetHeader/SheetBody/SheetTitle/SheetFooter.",props:[{name:"open",type:"boolean",description:"Controlled open state."},{name:"onOpenChange",type:"(open: boolean) => void",description:"Open-state change handler."},{name:"width",type:"number | string",description:"On SheetContent (side left/right): desired panel width (number\u2192px). Caps at the viewport \u2014 full-width on a small screen (min(width,100%)), NOT a hard fixed width. Default w-3/4 sm:max-w-md."},{name:"responsive",type:'"auto" | "side" | "bottom"',defaultValue:'"side"',description:'On SheetContent: the responsive drawer / detail-panel contract. "side" (default) always renders the physical `side` you named. "auto" renders the desktop side panel above --sheet-responsive-breakpoint-width (48rem/768px) and the mobile BOTTOM sheet at/below it, capped by --sheet-bottom-max-height (85dvh). "bottom" pins the bottom-sheet presentation. The resolved presentation is exposed as data-side on the panel. Never hand-roll useMediaQuery in app code \u2014 import useSheetResponsiveMode() if a composite must make the same decision.'},{name:"title / subtitle / extra / tone",type:"ReactNode / ReactNode / ReactNode / ToneProp",description:"On SheetHeader (Ant-style): title (\u2192 SheetTitle, accessible name), subtitle (\u2192 SheetDescription), right-aligned extra actions, and a soft semantic `tone` background band. Children still supported."}],usage:["DO build the panel with SheetHeader (pass `title`/`subtitle`/`extra`/`tone` OR children) > SheetBody (scrollable, ring-safe) > SheetFooter (pinned). SheetTitle is required for a11y \u2014 the `title` prop renders it for you. Never skip the title.","DO set `width` on SheetContent for a wider/narrower panel (e.g. width={480}); it caps at the viewport so small screens still get a full-width panel.",'DO use responsive="auto" for a record detail panel / drawer that must be a desktop side panel and a mobile bottom sheet \u2014 ONE <Sheet>, no page-local media query. The breakpoint is the --sheet-responsive-breakpoint-width token, so a service moves the line once for every overlay. `width` is ignored while the bottom presentation is active (a bottom sheet is full-bleed).',`DON'T hardcode an overlay breakpoint in app code (useMediaQuery("(max-width: 390px)")). If a composite must swap a desktop surface for a mobile sheet (a Popover\u2192Sheet switcher, for example), call the exported useSheetResponsiveMode("auto") hook so it reads the same themeable token.`,"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.","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.","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.","DO wrap a long/scrolling body in SheetBody (between SheetHeader and a pinned SheetFooter). It is the ring-safe scroll slot: a hand-rolled <div className='overflow-y-auto'> clips the 3px focus ring of a full-width Input/Select at the scroll edges \u2014 SheetBody insets the content so the ring never clips.","DO use SheetFooter (renders at the bottom via mt-auto, symmetric 16/24 padding, full-bleed top border) for primary/cancel action Buttons. Never float action Buttons inside the body \u2014 they will not stick to the panel bottom.","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.","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."],useCases:["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.","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.","Detail peek panel: show read-only Descriptions / Timeline of a selected record (e.g. a journal entry or invoice) from a DataTable row click, using side='right' with showCloseButton={true}. Add responsive='auto' so the same panel becomes a bottom sheet on a phone instead of a 100%-wide slab.","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.","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."],related:["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).","Toolbar/ToolbarGroup \u2014 use Toolbar 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.","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.","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."],example:`import { Sheet, SheetTrigger, SheetContent, SheetHeader, SheetTitle } from "@godxjp/ui/feedback";
|
|
665
862
|
import { Button } from "@godxjp/ui/general";
|
|
666
863
|
|
|
667
864
|
<Sheet open={open} onOpenChange={setOpen}>
|
|
@@ -684,7 +881,7 @@ import { Toaster } from "@godxjp/ui/feedback";
|
|
|
684
881
|
// anywhere \u2014 import toast from "sonner"
|
|
685
882
|
import { toast } from "sonner";
|
|
686
883
|
toast.success("\u30AF\u30FC\u30DD\u30F3\u3092\u516C\u958B\u3057\u307E\u3057\u305F");
|
|
687
|
-
toast.error("\u4FDD\u5B58\u306B\u5931\u6557\u3057\u307E\u3057\u305F");`,storyPath:"feedback/Toaster.stories.tsx",rules:[]},{name:"Tabs",group:"navigation",tagline:"Radix tab container with optional Ant-style `items` API. Pass items for the common full TabsList/TabsContent set, or compose TabsList/TabsTrigger/TabsContent manually when you need per-panel control.",props:[{name:"items",type:"{ value: string; label: React.ReactNode; content: React.ReactNode; disabled?: boolean }[]",description:"Optional data-driven tab list. When provided, Tabs renders all triggers and content panels. When Tabs owns the initial selection (no `value`, and no `defaultValue` naming an existing ENABLED item), it falls back to the first item that is NOT `disabled` \u2014 never a disabled one \u2014 and selects nothing if every item is disabled (gh#175)."},{name:"value",type:"string",description:"Controlled active tab key."},{name:"defaultValue",type:"string",description:"Uncontrolled initial tab key. Ignored (falls back to the first enabled item) when it names a disabled item or an unknown key."},{name:"onValueChange",type:"(value: string) => void",description:"Active-tab change handler."}],usage:["DO pass `items` when all tab content is known up front \u2014 each item needs a unique `value`, trigger `label`, and panel `content`.",'When not using `items`, compose the full four-part tree \u2014 `<Tabs>` root, `<TabsList>` trigger bar, one `<TabsTrigger value="\u2026">` per tab, one `<TabsContent value="\u2026">` per matching trigger.',"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.","DO use `variant` on Tabs when using `items`; when composing manually, set `variant` on `TabsList`.",'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.',"DON'T: hand-roll the active-indicator underline or selected-state ring \u2014 `TabsTrigger` already applies `data-[state=active]` styles including the
|
|
884
|
+
toast.error("\u4FDD\u5B58\u306B\u5931\u6557\u3057\u307E\u3057\u305F");`,storyPath:"feedback/Toaster.stories.tsx",rules:[]},{name:"Tabs",group:"navigation",tagline:"Radix tab container with optional Ant-style `items` API. Pass items for the common full TabsList/TabsContent set, or compose TabsList/TabsTrigger/TabsContent manually when you need per-panel control.",props:[{name:"items",type:"{ value: string; label: React.ReactNode; content: React.ReactNode; disabled?: boolean }[]",description:"Optional data-driven tab list. When provided, Tabs renders all triggers and content panels. When Tabs owns the initial selection (no `value`, and no `defaultValue` naming an existing ENABLED item), it falls back to the first item that is NOT `disabled` \u2014 never a disabled one \u2014 and selects nothing if every item is disabled (gh#175)."},{name:"value",type:"string",description:"Controlled active tab key."},{name:"defaultValue",type:"string",description:"Uncontrolled initial tab key. Ignored (falls back to the first enabled item) when it names a disabled item or an unknown key."},{name:"onValueChange",type:"(value: string) => void",description:"Active-tab change handler."},{name:"variant",type:'"default" | "line" | "card"',defaultValue:'"default"',description:'Trigger-strip appearance. `default`/`card` keep the pill/card chrome (a selected trigger gets a soft `ring-primary/25` ring). `line` is UNDERLINE-ONLY (gh#248): the selected trigger gets no ring or card border at all \u2014 only the token-owned 2px primary bar (--tabs-indicator-{background,size,offset}) \u2014 so the `:focus-visible` keyboard ring stays visible and clearly distinct from selection. With `items`, the variant is forwarded to the list; when composing manually, pass the same value to `<TabsList variant="line">`.'}],usage:["DO pass `items` when all tab content is known up front \u2014 each item needs a unique `value`, trigger `label`, and panel `content`.",'When not using `items`, compose the full four-part tree \u2014 `<Tabs>` root, `<TabsList>` trigger bar, one `<TabsTrigger value="\u2026">` per tab, one `<TabsContent value="\u2026">` per matching trigger.',"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.","DO use `variant` on Tabs when using `items`; when composing manually, set `variant` on `TabsList`.",'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.',"DON'T: hand-roll the active-indicator underline or selected-state ring \u2014 `TabsTrigger` already applies `data-[state=active]` styles, including the token-owned indicator bar for the `line` variant. Adding your own `border-b-2 border-primary` (or a page-local `ring-0` override to remove one) breaks the design; retune --tabs-indicator-{background,size,offset} in the service theme instead.","DO trust the horizontal `TabsList` to scroll its own overflow (hidden scrollbar, swipeable) instead of clipping when tab labels \u2014 especially long localized ones (Japanese, German) \u2014 don't fit a narrow container. Don't wrap it in your own `overflow-x-auto` div or truncate labels to work around clipping; that was gh#175 and is now the framework's job (#175).","DON'T assume the first item is ever auto-selected when it is `disabled` \u2014 Tabs always resolves the fallback to the first ENABLED item (or none, if all are disabled). A `disabled: true` first item is safe to author without also setting `defaultValue`.",'DON\'T write your own resize/scroll-into-view effect to keep the selected tab on screen \u2014 `TabsList` observes its own size and its triggers\' `data-state` and re-pins the active (or focused, under `activationMode="manual"`) trigger with `scrollIntoView({ block: "nearest", inline: "nearest" })`, honoring `prefers-reduced-motion` and leaving a deliberate manual scroll alone. Before gh#204 a 1440 \u2192 1024 \u2192 390 resize could strand the ACTIVE FIRST tab entirely outside the strip while it still reported `aria-selected="true"`.'],useCases:["Detail drawers or pages that need full per-panel control \u2014 e.g. an accounting journal-entry sheet where one panel has `forceMount` to keep a live chart mounted, requiring custom `TabsContent` props that `Tabs` cannot pass.","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`.",'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.',"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.","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."],related:["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.","Toolbar / ToolbarGroup (@godxjp/ui/navigation) \u2014 horizontal filter chip row. Visually resembles `line`-variant tabs but is semantically different: Toolbar filters a dataset, it does not switch content panels. Never use Tabs as a filter control.","DropdownMenu (@godxjp/ui/navigation) \u2014 use for space-constrained contexts where showing all tab triggers at once is impractical (e.g. mobile overflow menu). If only 2-3 options exist and screen space is tight, a DropdownSidebar is a lighter alternative to a full tab strip."],example:`import { Tabs } from "@godxjp/ui/navigation";
|
|
688
885
|
|
|
689
886
|
<Tabs
|
|
690
887
|
defaultValue="overview"
|
|
@@ -704,9 +901,9 @@ import { Button } from "@godxjp/ui/general";
|
|
|
704
901
|
<DropdownMenuSeparator />
|
|
705
902
|
<DropdownMenuItem tone="destructive">\u524A\u9664</DropdownMenuItem>
|
|
706
903
|
</DropdownMenuContent>
|
|
707
|
-
</DropdownMenu>`,storyPath:"navigation/DropdownMenu.stories.tsx",rules:[]},{name:"Steps",group:"navigation",tagline:"Multi-step progress indicator \u2014 horizontal or vertical, default or dot style.",props:[{name:"items",type:"StepItemProp[]",description:"Array of { title, subtitle?, description?, icon?, status? }."},{name:"value",type:"number",defaultValue:"0",description:"Active step index (0-based)."},{name:"defaultValue",type:"number",defaultValue:"0",description:"Base offset for the first rendered step index."},{name:"orientation",type:'"horizontal" | "vertical"',defaultValue:'"horizontal"',description:"Layout direction."},{name:"status",type:'"wait" | "process" | "finish" | "error"',description:"Status of the CURRENT step (drives the active step colour)."},{name:"type",type:'"default" | "dot"',description:"Render full
|
|
904
|
+
</DropdownMenu>`,storyPath:"navigation/DropdownMenu.stories.tsx",rules:[]},{name:"Steps",group:"navigation",tagline:"Multi-step progress indicator \u2014 horizontal or vertical, default or dot style.",props:[{name:"items",type:"StepItemProp[]",description:"Array of { title, subtitle?, description?, icon?, status? }."},{name:"value",type:"number",defaultValue:"0",description:"Active step index (0-based)."},{name:"defaultValue",type:"number",defaultValue:"0",description:"Base offset for the first rendered step index."},{name:"orientation",type:'"horizontal" | "vertical"',defaultValue:'"horizontal"',description:"Layout direction."},{name:"status",type:'"wait" | "process" | "finish" | "error"',description:"Status of the CURRENT step (drives the active step colour)."},{name:"type",type:'"default" | "dot" | "inline"',description:"Render full markers, compact dots, or a numbered inline auth progress row."},{name:"size",type:'"md" | "sm"',description:"Step size."},{name:"titlePlacement",type:'"horizontal" | "vertical"',description:"Lay step titles beside or below the step icons."},{name:"onValueChange",type:"(value: number) => void",description:"Fires with the clicked step index (0-based)."}],usage:["DO: Pass all steps via the `items` array (each `{ title, subtitle?, description?, icon?, status?, disabled? }`) \u2014 Steps is a single-component API with no child sub-components to compose manually.","DO: Control the active step with `value` (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`.","DO: Use per-item `status` to pin individual steps independently of `value` (e.g. a skipped or already-errored step). Per-item `status` takes precedence over the derived status from `value`.","DO: Use type='inline' for compact hosted-auth/device progress. It retains process/finish/error/wait and aria-current semantics without the tall icon rail; never rebuild the row from Text and arrows in a consumer.","DON'T: Use Steps for navigation that needs URL routing or tab-switching \u2014 it has no built-in panel rendering. Pair it with your own conditional panel or a `Tabs`/`Tabs` body; Steps only renders the indicator bar.","DON'T: Wire `onValueChange` unless you actually support non-linear navigation. `onValueChange` 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 `onValueChange`, or the prop is meaningless.","A11y: The `<ol>` is given `aria-label='Progress'` automatically. Individual steps render as `<button type='button'>` when `onValueChange` is present \u2014 ensure each `item.title` is descriptive enough to serve as the button label; avoid icon-only steps without a visible title."],useCases:["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.","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).","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.","Onboarding checklist sidebar: `orientation='vertical'` + `type='dot'` + `size='sm'` for a compact sidebar progress guide alongside a multi-section settings page.","Non-linear step navigation (e.g. revisit a previous step to correct data): provide `onValueChange` and leave only future steps `disabled`; past and current steps become clickable buttons."],related:["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.","Tabs / Tabs \u2014 use Tabs when each section has its own rendered panel and users switch freely between them; use Steps when stages are ordered and the indicator communicates completion state rather than just selection.","Progress \u2014 use Progress for a single continuous percentage (file upload, quota fill); use Steps for discrete named stages with individual pass/fail status.","Breadcrumb \u2014 use Breadcrumb for hierarchical location within a page tree; use Steps for sequential workflow progress where order and completion matter."],example:`import { Steps } from "@godxjp/ui/navigation";
|
|
708
905
|
|
|
709
|
-
<Steps value={1} items={[{ title: "\u7533\u8ACB" }, { title: "\u5BE9\u67FB\u4E2D" }, { title: "\u5B8C\u4E86" }]} />`,storyPath:"navigation/Steps.stories.tsx",rules:[]},{name:"Toolbar",group:"navigation",tagline:"List-page filter strip (the framework FilterBar) \u2014 SearchInput + labelled ToolbarGroup filter slots + a clear-all affordance, optionally sticky.",props:[{name:"children",type:"ReactNode",required:!0,description:"Filter controls. Place SearchInput directly; wrap each labelled Select/DatePicker in a ToolbarGroup."},{name:"onClear",type:"() => void",description:"Clear-all handler. When provided AND hasActiveFilters is true, Toolbar renders the trailing 'Clear filters' button."},{name:"hasActiveFilters",type:"boolean",defaultValue:"true",description:"Whether any filter is applied \u2014 gates the clear-all button visibility."},{name:"sticky",type:"boolean",defaultValue:"false",description:"Pin the strip to the top of its scroll container while the list scrolls beneath it (#197). Opt-in; tune offset/fill via the --filter-bar-sticky-offset / --filter-bar-sticky-background theme knobs."},{name:"className",type:"string",description:"Extra classes on the role='toolbar' element."}],usage:["DO place Toolbar ABOVE the table Card, never inside a `CardContent flush`. Put SearchInput as a direct child (it self-labels) and wrap every other control in a ToolbarGroup with a `label`.","DO drive the clear-all button with `hasActiveFilters` + `onClear` \u2014 it only renders when both are truthy. The strip collapses to a single stacked column below 640px automatically.","DO set `sticky` for long list pages so the filters stay reachable while scrolling; if a topbar sits above the list, raise `--filter-bar-sticky-offset` so the strip parks below it.","DON'T build active-filter chips by nesting a Button inside a Badge (invalid markup + broken focus). Render each chip as a Badge label with a SIBLING icon Button (`aria-label` = 'clear <filter>'); a ghost `size='sm'` Button clears all.","DON'T hand-roll a debounced search box or a raw `<select>` \u2014 compose SearchInput and Select. Toolbar is layout + clear-all only; the controls own their own state and a11y."],useCases:["Master list screens (members, organizations, subscriptions, invoices) that need free-text search plus a few dropdown filters above a DataTable.","Sticky filter strip over a tall list where the filters must remain visible as the user scrolls the results.","Filtered views that surface applied conditions as removable chips (clear-one via each chip's \xD7 Button, clear-all via `onClear`)."],related:["ToolbarGroup \u2014 the labelled wrapper for each individual filter control inside a Toolbar (SearchInput is placed directly, without a group).","DataTable.Toolbar \u2014 the in-table strip for column/density/bulk-action controls; Toolbar (this) is the page-level filter strip that sits ABOVE the table.","SearchInput \u2014 the debounced free-text control placed as the first child of Toolbar.","Badge \u2014 compose Badge + a sibling icon Button to render each active-filter chip; Badge itself is a non-interactive leaf."],example:`import { Toolbar, ToolbarGroup } from "@godxjp/ui/navigation";
|
|
906
|
+
<Steps value={1} items={[{ title: "\u7533\u8ACB" }, { title: "\u5BE9\u67FB\u4E2D" }, { title: "\u5B8C\u4E86" }]} />`,storyPath:"navigation/Steps.stories.tsx",rules:[]},{name:"Toolbar",group:"navigation",tagline:"List-page filter strip (the framework FilterBar) \u2014 SearchInput + labelled ToolbarGroup filter slots + a clear-all affordance, optionally sticky.",props:[{name:"children",type:"ReactNode",required:!0,description:"Filter controls. Place SearchInput directly; wrap each labelled Select/DatePicker in a ToolbarGroup."},{name:"onClear",type:"() => void",description:"Clear-all handler. When provided AND hasActiveFilters is true, Toolbar renders the trailing 'Clear filters' button."},{name:"hasActiveFilters",type:"boolean",defaultValue:"true",description:"Whether any filter is applied \u2014 gates the clear-all button visibility."},{name:"sticky",type:"boolean",defaultValue:"false",description:"Pin the strip to the top of its scroll container while the list scrolls beneath it (#197). Opt-in; tune offset/fill via the --filter-bar-sticky-offset / --filter-bar-sticky-background theme knobs."},{name:"overflow",type:"'wrap' | 'scroll'",defaultValue:"'wrap'",description:"Responsive overflow strategy (gh#216). 'wrap' stacks the groups into one column below 640px, then wraps onto extra rows. 'scroll' keeps ONE bounded row at >=640px that scrolls inline (groups never shrink; the clear-all button stays sticky at the inline end) \u2014 use it when many filters with long JA/EN/VI labels would otherwise push the table below the fold. Below 640px 'scroll' still stacks, so a 390px viewport never hides a filter behind an invisible horizontal scroll. Gutter knob: --filter-bar-scroll-padding-y."},{name:"className",type:"string",description:"Extra classes on the role='toolbar' element."}],usage:["DO place Toolbar ABOVE the table Card, never inside a `CardContent flush`. Put SearchInput as a direct child (it self-labels) and wrap every other control in a ToolbarGroup with a `label`.","DO drive the clear-all button with `hasActiveFilters` + `onClear` \u2014 it only renders when both are truthy. The strip collapses to a single stacked column below 640px automatically.","DO set `sticky` for long list pages so the filters stay reachable while scrolling; if a topbar sits above the list, raise `--filter-bar-sticky-offset` so the strip parks below it.","DO pass `controlId` on a ToolbarGroup that wraps ONE control (gh#216) and give that control the same `id` \u2014 the visible caption then becomes the control's real `<label htmlFor>` (WCAG 2.5.3 label-in-name). Without it the caption only names the group wrapper and the control itself is NAMELESS (axe: select-name / button-name), so you must fall back to an `aria-label` that repeats the caption.","DO switch to `overflow='scroll'` when a list page carries more filters than fit one row (gh#216) \u2014 one bounded row that scrolls inline keeps the table above the fold, where the default `wrap` would grow a 3-row strip with long JA/EN/VI labels. Never re-implement the geometry in the page: no page-local flex/grid/width rules on a filter strip.","DON'T build active-filter chips by nesting a Button inside a Badge (invalid markup + broken focus). Render each chip as a Badge label with a SIBLING icon Button (`aria-label` = 'clear <filter>'); a ghost `size='sm'` Button clears all.","DON'T hand-roll a debounced search box or a raw `<select>` \u2014 compose SearchInput and Select. Toolbar is layout + clear-all only; the controls own their own state and a11y."],useCases:["Master list screens (members, organizations, subscriptions, invoices) that need free-text search plus a few dropdown filters above a DataTable.","Sticky filter strip over a tall list where the filters must remain visible as the user scrolls the results.","Filtered views that surface applied conditions as removable chips (clear-one via each chip's \xD7 Button, clear-all via `onClear`)."],related:["ToolbarGroup \u2014 the labelled wrapper for each individual filter control inside a Toolbar (SearchInput is placed directly, without a group).","DataTable.Toolbar \u2014 the in-table strip for column/density/bulk-action controls; Toolbar (this) is the page-level filter strip that sits ABOVE the table.","SearchInput \u2014 the debounced free-text control placed as the first child of Toolbar.","Badge \u2014 compose Badge + a sibling icon Button to render each active-filter chip; Badge itself is a non-interactive leaf."],example:`import { Toolbar, ToolbarGroup } from "@godxjp/ui/navigation";
|
|
710
907
|
import { SearchInput, Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@godxjp/ui/data-entry";
|
|
711
908
|
|
|
712
909
|
<Toolbar sticky hasActiveFilters={hasFilters} onClear={clearAll}>
|
|
@@ -720,7 +917,7 @@ import { SearchInput, Select, SelectContent, SelectItem, SelectTrigger, SelectVa
|
|
|
720
917
|
</SelectContent>
|
|
721
918
|
</Select>
|
|
722
919
|
</ToolbarGroup>
|
|
723
|
-
</Toolbar>`,docPath:"navigation/toolbar",storyPath:"navigation/toolbar.tsx",rules:[23,44,45,46]},{name:"AppProvider",group:"providers",tagline:"Root locale/timezone/date-time context \u2014 wrap the app ONCE. All pickers + formatDate read from it. Import from @godxjp/ui/app.",props:[{name:"defaultLocale",type:'"ja" | "en" | "vi"',defaultValue:'"vi"',description:"Initial locale."},{name:"defaultTimezone",type:'string | "browser" | "system"',defaultValue:'"browser"',description:"Initial IANA timezone."},{name:"defaultDateFormat",type:'"iso" | "dmy" | "mdy" | "locale"',defaultValue:'"locale"',description:"Initial date display format."},{name:"defaultTimeFormat",type:'"24h" | "12h" | "locale"',defaultValue:'"locale"',description:"Initial clock format."},{name:"theme",type:'"light" | "dark"',defaultValue:'"light"',description:'Theme axis \u2192 <html data-theme>. Equal alias of the legacy .dark class. Persisted; change via setTheme / <AppSettingPicker kind="theme">.'},{name:"brand",type:'"brand" | "crm" | "logistics" | "partner" | "slate" | null',defaultValue:"null",description:
|
|
920
|
+
</Toolbar>`,docPath:"navigation/toolbar",storyPath:"navigation/toolbar.tsx",rules:[23,44,45,46]},{name:"AppProvider",group:"providers",tagline:"Root locale/timezone/date-time context \u2014 wrap the app ONCE. All pickers + formatDate read from it. Import from @godxjp/ui/app.",props:[{name:"defaultLocale",type:'"ja" | "en" | "vi"',defaultValue:'"vi"',description:"Initial locale."},{name:"defaultTimezone",type:'string | "browser" | "system"',defaultValue:'"browser"',description:"Initial IANA timezone."},{name:"defaultDateFormat",type:'"iso" | "dmy" | "mdy" | "locale"',defaultValue:'"locale"',description:"Initial date display format."},{name:"defaultTimeFormat",type:'"24h" | "12h" | "locale"',defaultValue:'"locale"',description:"Initial clock format."},{name:"theme",type:'"light" | "dark"',defaultValue:'"light"',description:'Theme axis \u2192 <html data-theme>. Equal alias of the legacy .dark class. Persisted; change via setTheme / <AppSettingPicker kind="theme">.'},{name:"brand",type:'"brand" | "crm" | "logistics" | "partner" | "slate" | "dxs" | null',defaultValue:"null",description:'Brand-palette axis \u2192 <html data-brand> (sets --primary/--ring/--accent). OPT-IN: null keeps the --primary your own theme.css defines. "dxs" is THE CANONICAL DXS PRESET and is more than a tint: it also binds the canonical hosted-identity surface contract (36px auth controls, 22.5rem auth card measure, 16px page inset / 15px below 30rem), so a DXS surface needs ZERO page CSS for auth geometry, density, insets, logo colour, divider or footer. Stylesheet-only apps (no provider) import "@godxjp/ui/theme/dxs.canonical.css" instead \u2014 same contract, guarded against drift by a test.'},{name:"density",type:'"compact" | "default" | "comfortable"',defaultValue:'"default"',description:"Density axis \u2192 <html data-density>. A named preset of the global --scaling factor (compact .92 / default 1 / comfortable 1.08): every size token rescales in proportion app-wide. PageContainer density= overrides locally."},{name:"scaling",type:"number | null",defaultValue:"null",description:"Continuous global size multiplier \u2192 inline --scaling on <html> (Radix model). Scales spacing, control/table/checkbox/switch heights, radius in proportion. null defers to the density preset; a number (e.g. 0.95) overrides it. Type is NOT scaled (separate fontSize axis)."},{name:"fontSize",type:'"sm" | "default" | "lg"',defaultValue:'"default"',description:"Base type-size axis \u2192 <html data-font-size>. A preset sets --font-size-base and the whole golden scale rescales. Orthogonal to --scaling."}],usage:['DO drive the four theme axes (theme / brand / density / fontSize) from AppProvider props ONLY \u2014 they are written to <html data-*> and read by every component via tokens. Never hand-set --font-size-base or .ui-density-* in app CSS; that bypasses persistence + the runtime switchers. For runtime switching mount `<AppSettingPicker kind="density" | "fontSize" | "theme" | "brand" >` or call setDensity/setFontSize/setTheme/setBrand from useAppContext().',"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.","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.","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.","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.","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.","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)."],useCases:["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.","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.","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.","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.","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.","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."],related:["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.","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.","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.","AppShell \u2014 the top-level application shell that composes AppProvider, AppShell, Sidebar, and Topbar into a single ready-to-use layout. If your project uses AppShell, AppProvider is already mounted inside it \u2014 do not add a second one."],example:`import { AppProvider } from "@godxjp/ui/app";
|
|
724
921
|
|
|
725
922
|
<AppProvider defaultLocale="ja" defaultTimezone="Asia/Tokyo" defaultDateFormat="iso" defaultTimeFormat="24h">
|
|
726
923
|
{children}
|
|
@@ -1452,21 +1649,28 @@ import { fetchInvoice } from "@/api/invoices";
|
|
|
1452
1649
|
prefetchOn="none"
|
|
1453
1650
|
>
|
|
1454
1651
|
{invoice.number}
|
|
1455
|
-
</PrefetchLink>`,storyPath:"data-display/PrefetchLink.stories.tsx",rules:[2,3,31]},{name:"Avatar",group:"data-display",tagline:"Radix Avatar wrapper with image and fallback slots for users, teams, and entities.",props:[{name:"children",type:"ReactNode",description:"Compose AvatarImage and AvatarFallback."},{name:"className",type:"string",description:"Extra classes on the avatar root."}],usage:["DO compose Avatar > AvatarImage + AvatarFallback so broken or missing images still show a readable fallback.","DON'T use Avatar for decorative thumbnails; use CardCover or an img when the image is content rather than identity."],useCases:["User profile chips","Team member lists","Account owner cells in a DataTable"],related:["Badge \u2014 use beside Avatar for role/status metadata."],example:`import { Avatar, AvatarFallback, AvatarImage } from "@godxjp/ui/data-display";
|
|
1652
|
+
</PrefetchLink>`,storyPath:"data-display/PrefetchLink.stories.tsx",rules:[2,3,31]},{name:"Avatar",group:"data-display",tagline:"Radix Avatar wrapper with image and fallback slots for users, teams, and entities.",props:[{name:"shape",type:'"circle" | "square"',defaultValue:'"circle"',description:'Identity geometry. `circle` (default, inert) is the PERSON avatar \u2014 the round --radius-pill mark on the muted surface. `square` is the ENTITY-HEADER organization/service mark (gh#249): a compact rounded square on the brand surface, whose radius, box size, fill and glyph colour are all --avatar-square-{radius,size,background,foreground} tokens. Pick the shape by WHAT the mark represents; never hand-roll it with className="rounded-md bg-primary".'},{name:"children",type:"ReactNode",description:"Compose AvatarImage and AvatarFallback."},{name:"className",type:"string",description:"Extra classes on the avatar root."}],usage:["DO compose Avatar > AvatarImage + AvatarFallback so broken or missing images still show a readable fallback.",'DO use `shape="square"` for an organization / service / tenant mark in an entity header, and keep the default `shape="circle"` for people. The square appearance already carries the brand surface and an AA-contrast glyph colour \u2014 a className/colour override on the call site is never needed (and is forbidden by the API-first redesign policy).',"DON'T retune the entity mark per call site: set --avatar-square-{radius,size,background,foreground} ONCE in the service theme (e.g. --avatar-square-background: hsl(var(--muted)) for a neutral mark).","DON'T use Avatar for decorative thumbnails; use CardCover or an img when the image is content rather than identity."],useCases:["User profile chips","Team member lists","Account owner cells in a DataTable",'Organization / service entity headers (shape="square" beside the entity name and code)'],related:["Badge \u2014 use beside Avatar for role/status metadata."],example:`import { Avatar, AvatarFallback, AvatarImage } from "@godxjp/ui/data-display";
|
|
1456
1653
|
|
|
1457
1654
|
<Avatar>
|
|
1458
1655
|
<AvatarImage src="/user.png" alt="User" />
|
|
1459
1656
|
<AvatarFallback>UI</AvatarFallback>
|
|
1657
|
+
</Avatar>
|
|
1658
|
+
|
|
1659
|
+
// Organization entity header mark
|
|
1660
|
+
<Avatar shape="square">
|
|
1661
|
+
<AvatarFallback>\u5C71</AvatarFallback>
|
|
1460
1662
|
</Avatar>`,storyPath:"data-display/Avatar.stories.tsx",rules:[3,35]},{name:"Separator",group:"layout",tagline:"Radix Separator wrapper for tokenized horizontal or vertical dividers.",props:[{name:"orientation",type:'"horizontal" | "vertical"',defaultValue:'"horizontal"',description:"Divider direction."},{name:"decorative",type:"boolean",defaultValue:"true",description:"Whether the separator is decorative for assistive tech."}],usage:["DO use Separator for section dividers instead of raw border divs.","DO set orientation='vertical' only when the parent gives it a stable height."],useCases:["Separating toolbar groups","Dividing stacked page sections","Vertical split between metadata groups"],related:["Flex direction='col' \u2014 use for vertical spacing without a visible rule."],example:`import { Separator } from "@godxjp/ui/layout";
|
|
1461
1663
|
|
|
1462
1664
|
<Separator />`,storyPath:"layout/Separator.stories.tsx",rules:[2,3]},{name:"Skeleton",group:"feedback",tagline:"Base pulsing skeleton block for custom loading placeholders.",props:[{name:"className",type:"string",description:"Size and layout classes for the block."}],usage:["DO use Skeleton for a custom block when SkeletonRows/Table/Card do not match the final layout.","DON'T use a spinner overlay for skeletonable page content."],useCases:["Single loading line","Custom card media placeholder","Inline metadata placeholder"],related:["SkeletonRows","SkeletonTable","SkeletonStat"],example:`import { Skeleton } from "@godxjp/ui/feedback";
|
|
1463
1665
|
|
|
1464
1666
|
<Skeleton className="h-6 w-48" />`,storyPath:"feedback/Skeleton.stories.tsx",rules:[3,31]},{name:"Toggle",group:"data-entry",tagline:"Radix Toggle wrapper with default/outline variants and tokenized sizes.",props:[{name:"pressed",type:"boolean",description:"Controlled pressed state."},{name:"onPressedChange",type:"(pressed: boolean) => void",description:"Pressed-state callback."},{name:"variant",type:'"default" | "outline"',defaultValue:'"default"',description:"Visual style."},{name:"size",type:'"sm" | "md" | "lg"',defaultValue:'"md"',description:"Control size."}],usage:["DO provide an accessible label when the toggle only contains an icon.","DON'T use Toggle for multi-option selection; use ToggleGroup."],useCases:["Bold/italic toolbar buttons","Pinned filter toggles","Compact view mode buttons"],related:["ToggleGroup","Button"],example:`import { Toggle } from "@godxjp/ui/data-entry";
|
|
1465
1667
|
|
|
1466
|
-
<Toggle aria-label="Bold">B</Toggle>`,storyPath:"data-entry/Toggle.stories.tsx",rules:[3,13]},{name:"ToggleGroup",group:"data-entry",tagline:"Radix ToggleGroup wrapper for single or multiple toggle selection.",props:[{name:"type",type:'"single" | "multiple"',required:!0,description:"Selection mode."},{name:"value",type:"string | string[]",description:"Controlled selected value(s)."},{name:"onValueChange",type:"(value: string | string[]) => void",description:"Selection callback."}],usage:["DO choose type='single' for mutually exclusive toolbar modes.","DO choose type='multiple' for independent formatting toggles."],useCases:["Text alignment selector","Formatting toolbar","View density switcher"],related:["Toggle","RadioGroup"],example:`import { ToggleGroup, ToggleGroupItem } from "@godxjp/ui/data-entry";
|
|
1668
|
+
<Toggle aria-label="Bold">B</Toggle>`,storyPath:"data-entry/Toggle.stories.tsx",rules:[3,13]},{name:"ToggleGroup",group:"data-entry",tagline:"Radix ToggleGroup wrapper for single or multiple toggle selection.",props:[{name:"type",type:'"single" | "multiple"',required:!0,description:"Selection mode."},{name:"value",type:"string | string[]",description:"Controlled selected value(s)."},{name:"defaultValue",type:"string | string[]",description:"Uncontrolled initial value(s)."},{name:"onValueChange",type:"(value: string | string[]) => void",description:"Selection callback."},{name:"variant",type:'"default" | "outline"',defaultValue:'"default"',description:"Visual style, PROVIDED TO EVERY ITEM via context \u2014 set it once on the group, not on each ToggleGroupItem. An explicit `variant` on an item still wins. The default is applied per item by toggleVariants, so an unset group emits no `data-variant` at all."},{name:"size",type:'"sm" | "md" | "lg"',defaultValue:'"md"',description:"Control size, PROVIDED TO EVERY ITEM via context \u2014 set it once on the group. An explicit `size` on an item still wins. Heights come from the --control-height tier (sm 28px \xB7 md 32px \xB7 lg 36px)."},{name:"disabled",type:"boolean",description:"Disables the whole group; individual items also accept `disabled`."}],usage:["DO choose type='single' for mutually exclusive toolbar modes.","DO choose type='multiple' for independent formatting toggles.","DO set `variant`/`size` ONCE on the ToggleGroup \u2014 they propagate to every ToggleGroupItem through context. Repeating them on each item is redundant (it still works, and an explicit item prop overrides the group).","DO set `size`/`variant` on an individual ToggleGroupItem only when that ONE item must differ from the group.","DON'T pass size='default' \u2014 it is not a member of the `sm | md | lg` union. Omit `size` for the md default.","DO give the group an accessible name (`aria-label`) \u2014 it renders a radiogroup (single) or a group of toggle buttons (multiple)."],useCases:["Text alignment selector","Formatting toolbar","View density switcher"],related:["Toggle","RadioGroup"],example:`import { ToggleGroup, ToggleGroupItem } from "@godxjp/ui/data-entry";
|
|
1467
1669
|
|
|
1468
|
-
|
|
1670
|
+
// size/variant are set ONCE on the group and reach every item.
|
|
1671
|
+
<ToggleGroup type="single" size="lg" variant="outline" defaultValue="left" aria-label="Alignment">
|
|
1469
1672
|
<ToggleGroupItem value="left">Left</ToggleGroupItem>
|
|
1673
|
+
<ToggleGroupItem value="center">Center</ToggleGroupItem>
|
|
1470
1674
|
</ToggleGroup>`,storyPath:"data-entry/ToggleGroup.stories.tsx",rules:[3,13]},{name:"AspectRatio",group:"layout",tagline:"Radix AspectRatio wrapper for stable media and preview frames.",props:[{name:"ratio",type:"number",defaultValue:"16 / 9",description:"Width divided by height."},{name:"children",type:"ReactNode",description:"Content constrained to the ratio."}],usage:["DO use AspectRatio for media, maps, charts, or previews that must not jump during load.","DON'T use it for unconstrained text content."],useCases:["Video embed frame","Image preview slot","Dashboard chart placeholder"],related:["CardCover","Skeleton"],example:`import { AspectRatio } from "@godxjp/ui/layout";
|
|
1471
1675
|
|
|
1472
1676
|
<AspectRatio ratio={16 / 9}>...</AspectRatio>`,storyPath:"layout/AspectRatio.stories.tsx",rules:[2,3]},{name:"Accordion",group:"data-display",tagline:"Radix accordion \u2014 vertically stacked, collapsible sections. Compose Accordion > AccordionItem > AccordionTrigger + AccordionContent.",props:[{name:"type",type:'"single" | "multiple"',required:!0,description:"single = one open at a time; multiple = independent."},{name:"collapsible",type:"boolean",description:"When type=single, allow closing the open item."},{name:"value",type:"string | string[]",description:"Controlled open item(s)."},{name:"defaultValue",type:"string | string[]",description:"Uncontrolled initial open item(s)."},{name:"onValueChange",type:"(value: string | string[]) => void",description:"Open-state callback."}],usage:['DO compose the full set: <Accordion type="single" collapsible><AccordionItem value="a"><AccordionTrigger/><AccordionContent/></AccordionItem></Accordion>.',"DO give each AccordionItem a unique `value`.","DON'T use it for primary navigation \u2014 that's Sidebar/Tabs. Accordion is for collapsible content/FAQ."],useCases:["FAQ lists","Grouped settings sections","Collapsible detail panels on a record page","Filter facet groups in a sidebar"],related:["Collapsible (single open/close region, no item set)","Tabs (mutually-exclusive views, always one visible)"],example:`import { Accordion, AccordionItem, AccordionTrigger, AccordionContent } from "@godxjp/ui/data-display";
|
|
@@ -1495,7 +1699,7 @@ export default function PasswordBlock() {
|
|
|
1495
1699
|
<PasswordStrength value={value} rules={rules} />
|
|
1496
1700
|
</div>
|
|
1497
1701
|
);
|
|
1498
|
-
}`,storyPath:"data-entry/PasswordStrength.stories.tsx",rules:[3,6]},{name:"InputOTP",group:"data-entry",tagline:"One-time-code / 2FA input (input-otp) \u2014 N single-character slots that behave as one field. Compose InputOTP > InputOTPGroup > InputOTPSlot.",props:[{name:"maxLength",type:"number",required:!0,description:"Number of slots (e.g. 6)."},{name:"value",type:"string",description:"Controlled value."},{name:"onChange",type:"(value: string) => void",description:"Value callback (this is a true text input \u2014 onChange is the DOM-style value handler here)."},{name:"pattern",type:"string",description:"Allowed-char regex (e.g. digits only)."}],usage:["DO set `maxLength` to the code length and render that many InputOTPSlot with sequential `index`.","DO wrap slots in InputOTPGroup; use InputOTPSeparator between groups (e.g. 3 + 3).","DON'T build N separate Inputs \u2014 this is ONE field with paste, arrow-key, and caret handling built in."],useCases:["2FA / OTP verification code","Email / SMS confirmation code","PIN entry","Invite / redemption code"],related:["Input (a normal single text field)","PasswordInput (masked secret field)"],example:`import { InputOTP, InputOTPGroup, InputOTPSlot } from "@godxjp/ui/data-entry";
|
|
1702
|
+
}`,storyPath:"data-entry/PasswordStrength.stories.tsx",rules:[3,6]},{name:"InputOTP",group:"data-entry",tagline:"One-time-code / 2FA input (input-otp) \u2014 N single-character slots that behave as one field. Compose InputOTP > InputOTPGroup > InputOTPSlot.",props:[{name:"maxLength",type:"number",required:!0,description:"Number of slots (e.g. 6)."},{name:"value",type:"string",description:"Controlled value."},{name:"onChange",type:"(value: string) => void",description:"Value callback (this is a true text input \u2014 onChange is the DOM-style value handler here)."},{name:"pattern",type:"string",description:"Allowed-char regex (e.g. digits only)."}],usage:["DO set `maxLength` to the code length and render that many InputOTPSlot with sequential `index`.","DO wrap slots in InputOTPGroup; use InputOTPSeparator between groups (e.g. 3 + 3).","For device codes, set `appearance='grouped'` on each InputOTPGroup to render one outline per group while preserving the single hidden input, paste, caret, keyboard and screen-reader behavior.","DON'T build N separate Inputs \u2014 this is ONE field with paste, arrow-key, and caret handling built in.","DO widen the slots with `--otp-slot-size` (gh#233) when a challenge row must fill a wide auth panel \u2014 it defaults to the live `--control-height` tier, so re-scoping `--control-height` on the card instead would also resize the submit button and every other input in it. Set a NAMED tier (`var(--control-height-lg)`), never an ad-hoc calc offset.","DO drive the sign-in MFA challenge from FormField: `error` wires aria-invalid + aria-errormessage + a role=alert message onto the single field, and the slot borders turn destructive. State is never colour-only."],useCases:["2FA / OTP verification code","Email / SMS confirmation code","PIN entry","Invite / redemption code"],related:["Input (a normal single text field)","PasswordInput (masked secret field)"],example:`import { InputOTP, InputOTPGroup, InputOTPSlot } from "@godxjp/ui/data-entry";
|
|
1499
1703
|
|
|
1500
1704
|
<InputOTP maxLength={6}>
|
|
1501
1705
|
<InputOTPGroup>
|
|
@@ -1553,7 +1757,7 @@ export default function PasswordBlock() {
|
|
|
1553
1757
|
<CarouselPrevious />
|
|
1554
1758
|
<CarouselNext />
|
|
1555
1759
|
<CarouselDots />
|
|
1556
|
-
</Carousel>`},{name:"AppSettingPicker",group:"navigation",tagline:"One provider-bound Select for a single AppProvider setting, chosen by `kind` (locale | timezone | dateFormat | timeFormat | theme | brand | density | fontSize) \u2014 covers locale/format AND the four theme axes. Throws if used without AppProvider AND without controlled value+onValueChange.",props:[{name:"kind",type:'"locale" | "timezone" | "dateFormat" | "timeFormat" | "theme" | "brand" | "density" | "fontSize"',description:"Which AppProvider setting this picker reads and writes. Determines the option list, icon, trigger width, and the context value/setter used. The theme-axis kinds (theme/brand/density/fontSize) write <html data-*>; brand's first option opts out (null \u2192 app token)."},{name:"value",type:"string",description:"Controlled value for the chosen kind. When omitted, reads the current value from AppProvider context for that kind."},{name:"onValueChange",type:"(value: string) => void",description:"Controlled change handler. When omitted, calls the matching AppProvider setter (setLocale/setTimezone/setDateFormat/setTimeFormat). Required together with value when no AppProvider is present."},{name:"appearance",type:'"labeled" | "icon"',description:'Trigger presentation. "labeled"
|
|
1760
|
+
</Carousel>`},{name:"AppSettingPicker",group:"navigation",tagline:"One provider-bound Select for a single AppProvider setting, chosen by `kind` (locale | timezone | dateFormat | timeFormat | theme | brand | density | fontSize) \u2014 covers locale/format AND the four theme axes. Throws if used without AppProvider AND without controlled value+onValueChange.",props:[{name:"kind",type:'"locale" | "timezone" | "dateFormat" | "timeFormat" | "theme" | "brand" | "density" | "fontSize"',description:"Which AppProvider setting this picker reads and writes. Determines the option list, icon, trigger width, and the context value/setter used. The theme-axis kinds (theme/brand/density/fontSize) write <html data-*>; brand's first option opts out (null \u2192 app token)."},{name:"value",type:"string",description:"Controlled value for the chosen kind. When omitted, reads the current value from AppProvider context for that kind."},{name:"onValueChange",type:"(value: string) => void",description:"Controlled change handler. When omitted, calls the matching AppProvider setter (setLocale/setTimezone/setDateFormat/setTimeFormat). Required together with value when no AppProvider is present."},{name:"appearance",type:'"labeled" | "icon" | "inline"',description:'Trigger presentation. "labeled" shows the leading icon + selected value: it hugs its content below `sm` (`w-auto max-w-full`) and takes a per-kind fixed width from `sm` up \u2014 it no longer stretches to `w-full` on narrow screens, so it fits a topbar (pass className="w-full" for a full-width form field). "icon" is the supported icon-only topbar trigger (e.g. a globe locale switcher): it structurally drops the value text and the picker\'s owned width and hides the chevron, squares the box to the density-aware --control-height tap target (\u226544px on touch), and always keeps the localized aria-label so it can never ship nameless. "inline" renders the selected value as a chrome-less text trigger for a legal/auth footer (no border, no box). DEFAULT IS KIND-DEPENDENT: kind="locale" defaults to "icon" (its product contract is the compact language switcher); every other kind defaults to "labeled". Use these instead of overriding internal descendants / width classes with CSS.'},{name:"compact",type:"boolean",defaultValue:"false",description:'Compact trigger density (gh#217): re-tiers the box to the official --control-height-sm tier and DROPS the picker\'s owned per-kind width, so a labelled trigger hugs its value. This is the supported auth/legal-footer locale switch \u2014 `<AppSettingPicker kind="locale" appearance="labeled" compact />` \u2014 when the square icon-only default reads as a stray button but the full labelled trigger is too tall. All geometry is tokenized (--app-setting-picker-compact-{control-height,padding-x,gap,font-size}); the accessible name and the visible value are both preserved. No effect on appearance="inline", which is already chrome-less.'},{name:"className",type:"string",description:"Extra CSS classes merged onto the SelectTrigger."},{name:"disabled",type:"boolean",description:"Disables the Select control."},{name:"id",type:"string",description:"HTML id forwarded to the SelectTrigger for label association."}],usage:["DO: Mount inside <AppProvider> for zero-config use \u2014 the picker reads and writes the context value named by kind, no value/onValueChange needed.","DO: Use controlled mode (value + onValueChange) when managing state outside AppProvider, e.g. a standalone settings form or a Storybook story. Both are required together in this mode.","DO NOT: Render without AppProvider and without both controlled props \u2014 it throws 'AppSettingPicker requires <AppProvider> or controlled value + onValueChange'.","DO: Render four instances with different kind values to build a full preferences panel; they all share the same AppProvider context and stay in sync.",'DO: For an icon-only topbar utility (a globe language switcher), pass appearance="icon" \u2014 the supported compact trigger. NEVER hand-roll it by hiding the value/width with descendant-selector CSS.','DO: For an AUTH/LEGAL FOOTER locale switch, pass appearance="labeled" compact \u2014 a small, content-hugging labelled trigger (readable language name, --control-height-sm box). Use appearance="inline" instead when the footer wants no control chrome at all. NEVER re-size the trigger with a page-local height/width class.',"DON'T hand-roll a locale/timezone/format Select \u2014 AppSettingPicker already composes Select + the right icon + translated, context-wired options. There is no separate LocalePicker/TimezonePicker/DateFormatPicker/TimeFormatPicker anymore; use kind."],useCases:['App-shell top-nav language switcher: <AppSettingPicker kind="locale" /> under AppProvider, persisting to localStorage with no extra state.','Icon-only topbar locale switcher (globe): <AppSettingPicker kind="locale" appearance="icon" /> in a Topbar `end` slot \u2014 square, value-less, keyboard + aria-label preserved.','Auth-footer locale switch: <AppSettingPicker kind="locale" appearance="labeled" compact /> inside an <AuthFooter locale={\u2026}> slot \u2014 the readable language name at the small control tier, hugging its value.',"User settings page with all four preferences \u2014 render kind=locale, kind=timezone, kind=dateFormat, kind=timeFormat together under one AppProvider.","Onboarding step that picks language/timezone before the rest of the app is configured \u2014 AppProvider persist={false} + controlled values to keep state local.",'Storybook/test harness without AppProvider \u2014 fully controlled: <AppSettingPicker kind="timeFormat" value="24h" onValueChange={fn} />.'],related:["AppProvider \u2014 required peer unless fully controlled. Supplies locale/timezone/dateFormat/timeFormat plus their setters and the i18n context.","Select \u2014 the data-entry primitive AppSettingPicker is built on; reach for Select directly for any non-AppProvider dropdown.","formatDate \u2014 reads the same AppProvider date/time context that kind='dateFormat'/'timeFormat' write to."],example:`{\`// Uncontrolled \u2014 AppProvider manages and persists every setting
|
|
1557
1761
|
import { AppProvider } from "@godxjp/ui/app";
|
|
1558
1762
|
import { AppSettingPicker } from "@godxjp/ui/navigation";
|
|
1559
1763
|
|
|
@@ -1610,7 +1814,18 @@ export function NotifyRow() {
|
|
|
1610
1814
|
categoryKey="region"
|
|
1611
1815
|
series={[{ dataKey: "sales", label: t("metric.sales") }]}
|
|
1612
1816
|
numberFormat={{ notation: "compact" }}
|
|
1613
|
-
/>`,storyPath:"charts/BarChart.stories.tsx",rules:[]},{name:"
|
|
1817
|
+
/>`,storyPath:"charts/BarChart.stories.tsx",rules:[]},{name:"CompactBarTrend",group:"data-display",importPath:"@godxjp/ui/charts/compact-bar-trend",tagline:"DEPENDENCY-FREE compact vertical bar trend for dashboard summary cards \u2014 N category/value pairs, muted marks plus ONE emphasized 'current' bar, all geometry from --chart-trend-* tokens. Needs NO recharts. Sparkline / micro-chart / activity pulse / KPI trend strip.",props:[{name:"data",type:"ChartDatum[]",required:!0,description:"Row data \u2014 one bar per row. Any point count (7 is not hard-coded)."},{name:"categoryKey",type:"string",required:!0,description:"Key into each datum holding the category (tick) label."},{name:"valueKey",type:"string",required:!0,description:"Key into each datum holding the plotted numeric value."},{name:"label",type:"string",required:!0,description:"Accessible name + visible caption."},{name:"description",type:"string",description:"Extra context appended to the screen-reader description."},{name:"size",type:'"xs" | "sm" | "md" | "lg"',defaultValue:'"xs"',description:"Plot-height tier (--chart-trend-plot-height-*). xs is the dashboard summary-card density."},{name:"emphasizedIndex",type:"number",description:"Index of the emphasized 'current' bar. Negative counts from the end (-1 = latest); out of range = no emphasis. Also annotated in the text alternative, so it is never colour-only."},{name:"showCategoryLabels",type:"boolean",defaultValue:"true",description:"Render the category tick labels under the plot."},{name:"numberFormat",type:"Intl.NumberFormatOptions",description:"Locale-aware formatting for the values in the text alternative."},{name:"footer",type:"ReactNode",description:"Activity footer slot below the plot, rendered OUTSIDE the role=img graphic so links/buttons in it stay reachable."},{name:"emptyMessage",type:"string",description:"Message shown when `data` is empty."},{name:"ref",type:"React.Ref<HTMLElement>",description:"Forwarded to the <figure> element."}],usage:['DO import from the isolated entry: `import { CompactBarTrend } from "@godxjp/ui/charts/compact-bar-trend";` \u2014 unlike the charts barrel, this path never links modules that require the optional `recharts` peer.','DO reach for it INSIDE a dashboard summary card (a 7-day signup/organization/activity strip under a StatCard headline) \u2014 that is the density `size="xs"` is tuned for.',"DO mark the current period with `emphasizedIndex={-1}` (or an explicit index); the highlight is duplicated in the screen-reader text alternative, so it never depends on colour alone.","DO retheme through the `--chart-trend-*` tokens (bar gap/radius/width, plot heights, muted + emphasis fills, opt-in baseline). NEVER add page-local CSS, an inline height calculation, or a hardcoded colour.","DON'T use it when you need axes, a grid, tooltips, multiple series, or a continuous trend \u2014 that is BarChart / LineChart / AreaChart (recharts peer). DON'T fake bars with styled divs in the app.",'DON\'T put interactive content in the plot: anything clickable goes in `footer`, which renders outside the `role="img"` graphic.'],useCases:["Seven-day new-organizations / new-users trend inside an admin dashboard summary card (SCR-201).","Weekly activity pulse beside a KPI headline, where the SHAPE matters more than exact figures.","A dependency-constrained app (no recharts) that still needs a token-driven chart."],related:["BarChart \u2014 full cartesian bar chart with axes/grid/tooltip/legend (requires the recharts optional peer).","StatCard \u2014 the KPI headline this trend usually sits under.","Progress \u2014 one ratio against a target, not a series over time."],example:`import { CompactBarTrend } from "@godxjp/ui/charts/compact-bar-trend";
|
|
1818
|
+
|
|
1819
|
+
<CompactBarTrend
|
|
1820
|
+
label={t("dashboard.newOrganizations7d")}
|
|
1821
|
+
description={t("dashboard.newOrganizationsHint")}
|
|
1822
|
+
data={trend}
|
|
1823
|
+
categoryKey="date"
|
|
1824
|
+
valueKey="count"
|
|
1825
|
+
emphasizedIndex={-1}
|
|
1826
|
+
size="xs"
|
|
1827
|
+
footer={<Text size="xs" tone="muted">{t("dashboard.lastUpdated", { at })}</Text>}
|
|
1828
|
+
/>`,storyPath:"charts/CompactBarTrend.stories.tsx",rules:[]},{name:"AreaChart",group:"data-display",importPath:"@godxjp/ui/charts",tagline:"Magnitude over an ordered category axis \u2014 overlay or `stacked` areas, optional `curved` smoothing, localized formatting + text alternative. Data-visualization graph / plot.",props:[{name:"data",type:"ChartDatum[]",required:!0,description:"Row data: one category per row with a numeric value per series."},{name:"series",type:"ChartSeriesProp[]",required:!0,description:"Plotted series: { dataKey, label?, color? }."},{name:"categoryKey",type:"string",required:!0,description:"Key into each datum holding the x-axis category label."},{name:"label",type:"string",required:!0,description:"Accessible name + visible caption."},{name:"description",type:"string",description:"Extra context appended to the screen-reader description."},{name:"size",type:'"xs" | "sm" | "md" | "lg"',defaultValue:'"md"',description:"Canvas height preset. Ignored when `height` is set."},{name:"height",type:"number",description:"Explicit canvas height in px (overrides `size`)."},{name:"showLegend",type:"boolean",defaultValue:"true",description:"Show the series legend."},{name:"showGrid",type:"boolean",defaultValue:"true",description:"Show the cartesian background grid."},{name:"numberFormat",type:"Intl.NumberFormatOptions",description:"Locale-aware formatting for ticks + tooltip values."},{name:"stacked",type:"boolean",defaultValue:"false",description:"Stack series areas instead of overlaying them."},{name:"curved",type:"boolean",defaultValue:"false",description:"Render smooth (monotone) areas instead of straight segments."},{name:"emptyMessage",type:"string",description:"Message shown when `data` is empty."}],usage:['DO import from the charts entry: `import { AreaChart } from "@godxjp/ui/charts";` (recharts optional peer required).',"DO use `stacked` to show how parts accumulate into a total over time.","DON'T overlay more than 2-3 unstacked areas \u2014 fill opacity makes dense overlays unreadable; switch to LineChart."],useCases:["Cumulative volume over time (e.g. total transactions per day).","Stacked composition trend (traffic sources, revenue streams)."],related:["LineChart \u2014 when only the trend line matters, not the filled magnitude.","BarChart \u2014 discrete category comparison."],example:`import { AreaChart } from "@godxjp/ui/charts";
|
|
1614
1829
|
|
|
1615
1830
|
<AreaChart
|
|
1616
1831
|
label={t("dashboard.trafficByChannel")}
|
|
@@ -1630,7 +1845,58 @@ export function NotifyRow() {
|
|
|
1630
1845
|
nameKey="category"
|
|
1631
1846
|
numberFormat={{ style: "currency", currency: "JPY" }}
|
|
1632
1847
|
donut
|
|
1633
|
-
/>`,storyPath:"charts/PieChart.stories.tsx",rules:[]}];function P(a){let t=a.trim().toLowerCase();return y.find(e=>e.name.toLowerCase()===t)}function E(a){return y.filter(t=>t.group===a)}var w=[{name:"ValueProp<T = string>",concept:"Abstract controlled value.",values:["generic"],usedBy:["CheckboxGroup","Upload","Cascader","TreeSelect","Tabs","SearchSelect"]},{name:"DefaultValueProp<T = string>",concept:"Abstract uncontrolled initial value.",values:["generic"],usedBy:["CheckboxGroup","Upload","Cascader","TreeSelect","Tabs"]},{name:"OnValueChangeProp<T = string>",concept:"Callback for abstract value changes. DOM events continue to use onChange.",values:["(value: T) => void"],usedBy:["CheckboxGroup","Upload","Cascader","TreeSelect","Transfer","settings pickers"]},{name:"OpenProp / DefaultOpenProp / OnOpenChangeProp",concept:"Disclosure state.",values:["boolean","(open: boolean) => void"],usedBy:["Dialog","Sheet","Popover"]},{name:"SizeProp",concept:"Shared public size names.",values:["xs","sm","md","lg"],usedBy:["Button","Steps","Switch"],notes:"Component-specific subsets must be documented. Old alias small is sm."},{name:"ToneProp",concept:"Semantic status/color intent.",values:["default","success","warning","destructive","info","muted","neutral"],usedBy:["Badge","Alert"],notes:"Status values belong in tone, not variant."},{name:"GapProp",concept:"Shared layout gap scale.",values:["xs","sm","md","lg","xl"],usedBy:["Flex"],notes:'The single shared gap scale on Flex \u2014 the one layout primitive (default direction="row"; use direction="col" for vertical rhythm; these replaced the removed Stack/Inline).'},{name:"TitleProp",concept:"Primary heading text.",values:["React.ReactNode"],usedBy:["PageContainer","PageHeader","EmptyState","Dialog"]},{name:"DensityProp",concept:"Page/subtree density.",values:["compact","default","comfortable"],usedBy:["PageContainer"]}];function z(a){let t=a.trim().toLowerCase().replace(/prop(?:<.*>)?$/i,"");return w.find(e=>e.name.toLowerCase().replace(/prop(?:<.*>)?$/i,"")===t)}var x=[{name:"--wa-*",category:"primitive",tier:"primitive",role:"Neutral decorative Japanese accent primitives for charts/tags/decoration only."},{name:"--chart-1..6",category:"primitive",tier:"primitive",role:"Neutral decorative chart series palette. The @godxjp/ui/charts components (LineChart/BarChart/AreaChart/PieChart) read these by series index automatically \u2014 a service rethemes every chart at once by overriding --chart-1..6; per-series/per-slice overrides go through the component's series.color / colors props."},{name:"--space-0..12",category:"primitive",tier:"primitive",role:"Raw spacing scale."},{name:"--font-size-*",category:"primitive",tier:"primitive",role:"Raw typography scale."},{name:"--font-sans-base",category:"semantic",tier:"semantic",role:"The default sans face. FONT-AGNOSTIC by default (pure system stack) \u2014 the library ships no hardcoded brand font. Override this for one face everywhere. --font-family-sans defaults to it; --font-family-display / --font-family-body default to --font-family-sans (override those for a dual display+body brand). Consumers supply the actual @font-face (next/font, @fontsource, self-host); the opt-in @godxjp/ui/styles/fonts fills this with the bundled Noto Sans JP."},{name:"--font-sans-{ja,ko,vi,zh-hans,zh-hant}",category:"semantic",tier:"semantic",role:"Per-language font SLOT tokens. styles/base.css wires each [lang] to read its slot with --font-sans-base as fallback, so a consumer switches a locale's face by setting e.g. `--font-sans-ja: \"Noto Sans JP\", var(--font-sans-base)` \u2014 NO [lang] selectors to write. Empty by default. zh-hans matches lang zh|zh-Hans|zh-CN; zh-hant matches zh-Hant|zh-TW. Deciding which @font-face ships on which locale route is the consumer's font pipeline, not the library."},{name:"--duration-{fast,base,slow}",category:"primitive",tier:"primitive",role:"Motion durations (150 / 250 / 500ms). Read these instead of a literal `0.5s` for enter/transition timing (rule #2). the reference design keeps motion short; honour `prefers-reduced-motion` at the call site."},{name:"--ease-{standard,emphasized,decelerate,accelerate}",category:"primitive",tier:"primitive",role:"Motion easing curves. `standard` for most transitions, `emphasized` for entrances/overlays (the vaul drawer curve), `decelerate` for settling in, `accelerate` for exits. Read instead of a literal `cubic-bezier(\u2026)`."},{name:"--reveal-distance",category:"primitive",tier:"primitive",role:"Distance (10px) a revealed element travels on enter (translateY/-X). Read instead of a literal `translateY(10px)` for staggered reveals."},{name:"--reveal-stagger-step",category:"primitive",tier:"primitive",role:"One step (60ms) of the Reveal stagger ladder \u2014 `<Reveal delay={n}>` waits n \xD7 this before entering, so a column of reveals cascades. Read instead of a literal `.04s`/`.09s` per-item delay."},{name:"--shadow-color",category:"primitive",tier:"primitive",role:"RGB channels (space-separated, e.g. `12 26 49`) that tint the WHOLE elevation ramp (--shadow-xs..2xl) at once. Default `0 0 0`. Single-brand :root only \u2014 a scoped override does not re-resolve the ramp (the steps compute at :root); for a scoped/multi-tenant card lift set --card-shadow to a literal value."},{name:"--shadow-glow",category:"primitive",tier:"primitive",role:"Opt-in brand GLOW halo layered on the primary CTA's resting shadow. Default invisible (`0 0 0 0 transparent`, valid inside a comma shadow list). A service sets the whole value, e.g. `--shadow-glow: 0 8px 20px hsl(var(--primary) / .32)` \u2014 works scoped under [data-tenant] because the service declares it inside the scope."},{name:"--focus-ring-color",category:"primitive",tier:"primitive",role:"Themeable hue of EVERY keyboard-focus ring (HSL components, default var(--ring)). A leaf token, so it re-resolves at the element that paints the ring \u2014 override it once, even scoped under [data-tenant], to retint all focus rings. Pair with --focus-ring-width."},{name:"--focus-ring-width",category:"primitive",tier:"primitive",role:"Thickness (2px) of the solid keyboard-focus ring. Leaf token \u2014 :focus-visible rules read it directly as `0 0 0 var(--focus-ring-width) hsl(var(--focus-ring-color))`, never via an intermediate composite (which would freeze at :root). Propagates scoped."},{name:"--gradient-{brand,hero,glow}",category:"primitive",tier:"primitive",role:"Opt-in decorative gradient fills, default `none`. --gradient-hero paints the PageContainer header (hero banner); --gradient-glow paints the AppShell .app-main (ambient brand wash); --gradient-brand is a spare. A service sets the full gradient, e.g. `--gradient-glow: radial-gradient(60% 80% at 50% 0%, hsl(var(--primary) / .25), transparent)`."},{name:"--primary",category:"semantic",tier:"semantic",role:"Brand/action color role."},{name:"--success",category:"semantic",tier:"semantic",role:"Success status role."},{name:"--warning",category:"semantic",tier:"semantic",role:"Warning status role."},{name:"--destructive",category:"semantic",tier:"semantic",role:"Destructive/error status role."},{name:"--info",category:"semantic",tier:"semantic",role:"Information status role."},{name:"--attention",category:"semantic",tier:"semantic",role:"Attention status role."},{name:"--page-header-divider",category:"semantic",tier:"semantic",role:"PageContainer header bottom divider. Default none; a service theme opts in with `1px solid hsl(var(--border))`."},{name:"--overlay-background",category:"semantic",tier:"semantic",role:"Modal scrim \u2014 the single backdrop colour shared by every overlay (Dialog, AlertDialog, Sheet, Drawer). Default `rgb(0 0 0 / 0.5)`. A service tints it once, e.g. a navy `rgb(12 26 49 / .55)`. NOTE: portaled overlays render outside a [data-tenant] subtree, so for multi-tenant scoping put the tenant attribute on the portal container too."},{name:"--page-header-pad-bottom",category:"semantic",tier:"semantic",role:"PageContainer header bottom inset. Defaults to page top padding minus the section gap so the title band is vertically balanced."},{name:"--badge-space-*",category:"component",tier:"component",role:"Badge spacing."},{name:"--card-*",category:"component",tier:"component",role:"Card surface, border, spacing, and typography."},{name:"--control-*",category:"component",tier:"component",role:"Shared form control heights, padding, icons, and focus chrome."},{name:"--table-*",category:"component",tier:"component",role:"Table row/cell sizing."},{name:"--form-label-width",category:"component",tier:"component",role:"Label column width in horizontal Form layout. Default max-content; a service theme sets it once (e.g. 110px) \u2014 the labelWidth prop overrides per form/field."},{name:"--form-label-gap",category:"component",tier:"component",role:"Label\u2194control column gap in horizontal Form layout. Default 16px (--space-4)."},{name:"--dialog-* / --alert-* / --skeleton-*",category:"component",tier:"component",role:"Feedback component sizing and spacing."},{name:"--qr-code-*",category:"component",tier:"component",role:"QrCode scanner-safe foreground/background and xs/sm/md/lg natural-size tiers. Keep foreground/background at strong contrast; the defaults remain dark-on-light in dark application themes."}];function O(a){return x.filter(t=>t.category===a)}var L=[{name:"--badge-space-gap",value:"var(--space-inline-xs)",description:"Badge component tokens."},{name:"--badge-space-x",value:"var(--space-2)",description:"Badge component tokens."},{name:"--badge-space-y",value:"var(--space-1)",description:"Badge component tokens."},{name:"--badge-font-size",value:"var(--font-size-xs)",description:"Small-by-design (badge/pill/counter). A knob (rule #45) so a service can * re-tune badge text without touching the global --font-size-xs step."},{name:"--card-space-inset",value:"var(--space-section-active)",description:"Horizontal inset of every slot (header / content / footer) + the resting top/bottom * shell padding. This is the column the title, body and footer all align to."},{name:"--card-space-header-y",value:"var(--space-stack-sm)",description:"Vertical padding of a BANDED header band (top = bottom). Drives --card-space-divided-y."},{name:"--card-space-body-y",value:"var(--space-section-active)",description:"Gap between the header and the body, and the body's own top padding \u2014 the breathing * room under a title before content begins."},{name:"--card-space-footer-y",value:"var(--space-stack-sm)",description:"Vertical padding of a SEPARATED footer band (top = bottom). Drives --card-space-divided-y."},{name:"--card-space-divided-y",value:"var(--card-space-header-y)",description:"DIVIDED-section vertical padding (rule #44/#45). A header/footer that carries a divider * border (banded header, separated footer) reads as its own band, so it pads SYMMETRICALLY * top+bottom \u2014 distinct from a plain header that flows into the body (top inset, no bottom). * One themeable knob keeps the header- and footer-band rhythm in sync; a service theme tunes * the band density here instead of forking per-slot CSS."},{name:"--card-space-gap",value:"var(--space-stack-xs)",description:"Vertical gap between stacked items WITHIN a slot (e.g. title \u2195 description in the header)."},{name:"--card-title-font-size",value:"var(--font-size-base)",description:"Vertical gap between stacked items WITHIN a slot (e.g. title \u2195 description in the header)."},{name:"--card-title-line-height",value:"var(--line-height-tight)",description:"Vertical gap between stacked items WITHIN a slot (e.g. title \u2195 description in the header)."},{name:"--card-title-font-weight",value:"var(--font-weight-semibold)",description:"Vertical gap between stacked items WITHIN a slot (e.g. title \u2195 description in the header)."},{name:"--card-description-font-size",value:"var(--font-size-sm)",description:"Vertical gap between stacked items WITHIN a slot (e.g. title \u2195 description in the header)."},{name:"--card-description-line-height",value:"var(--line-height-normal)",description:"Vertical gap between stacked items WITHIN a slot (e.g. title \u2195 description in the header)."},{name:"--card-background",value:"initial",description:'Card fill + edge \u2014 opt-in knobs that DEFAULT to the live --card / --border roles. Declared * `initial` (not `var(--card)`) so the default re-resolves at the call site under a scoped theme: * a :root binding to a role var freezes at the :root value and a scoped `[data-tenant]` override of * the role never reaches it (see docs/STANDARDS-vocabulary-tokens.md \xB7 "role-mirror knobs"). A * service still overrides the knob directly (--card-background: \u2026) to win over the role default.'},{name:"--card-border",value:"initial",description:"default = hsl(var(--card))"},{name:"--card-header-background",value:"initial",description:"Banded-header fill \u2014 role-tintable (rule #45): a service points this at any role, * e.g. --card-header-background: var(--primary), and tunes --card-header-background-alpha for * the wash strength. Default = the live --muted role (resolved at the call site)."},{name:"--card-header-background-alpha",value:"0.55",description:"default = hsl(var(--muted))"},{name:"--card-header-border-bottom",value:"initial",description:"Banded-header divider \u2014 tokenised (rule #44) so a service theme can make it * dashed / heavier / none without forking CSS. Pair with * --card-header-background-alpha: 0 for a quiet borderless-band header. * Default = 1px solid hsl(var(--card-border)) (resolved at the call site)."},{name:"--card-radius",value:"var(--radius-xl)",description:"Banded-header divider \u2014 tokenised (rule #44) so a service theme can make it * dashed / heavier / none without forking CSS. Pair with * --card-header-background-alpha: 0 for a quiet borderless-band header. * Default = 1px solid hsl(var(--card-border)) (resolved at the call site)."},{name:"--card-shadow",value:"var(--shadow-sm)",description:"The DXS hi-fi baseline uses a quiet 10px data surface with one shadow-sm * elevation layer. Consumers can still flatten or lift cards through this knob."},{name:"--card-glow",value:"0 0 0 0 transparent",description:"Brand glow layer \u2014 invisible no-op at rest (rule #44). Paired AFTER --card-shadow in the * surface box-shadow so a service can wash every card with the global glow, e.g. * --card-glow: var(--shadow-glow), with no markup change."},{name:"--card-tint",value:"transparent",description:"Fill tint \u2014 subtle role wash over the card background (default transparent = invisible). * Painted as an overlay so a service sets --card-tint: hsl(var(--primary) / 0.04) once."},{name:"--card-accent-rail-width",value:"6px",description:"Accent edge \u2014 width of the semantic leading-edge stripe (data-accent). * Tokenised (rule #44) so a service theme can re-tune it without forking CSS. * The slot padding compensation in card-layout.css subtracts the same token, * so content stays aligned on the shell whatever the rail width."},{name:"--stat-card-label-font-size",value:"var(--font-size-xs)",description:"Accent edge \u2014 width of the semantic leading-edge stripe (data-accent). * Tokenised (rule #44) so a service theme can re-tune it without forking CSS. * The slot padding compensation in card-layout.css subtracts the same token, * so content stays aligned on the shell whatever the rail width."},{name:"--stat-card-label-font-weight",value:"var(--font-weight-medium)",description:"Accent edge \u2014 width of the semantic leading-edge stripe (data-accent). * Tokenised (rule #44) so a service theme can re-tune it without forking CSS. * The slot padding compensation in card-layout.css subtracts the same token, * so content stays aligned on the shell whatever the rail width."},{name:"--stat-card-label-letter-spacing",value:"0.04em",description:"Accent edge \u2014 width of the semantic leading-edge stripe (data-accent). * Tokenised (rule #44) so a service theme can re-tune it without forking CSS. * The slot padding compensation in card-layout.css subtracts the same token, * so content stays aligned on the shell whatever the rail width."},{name:"--stat-card-value-font-size",value:"var(--font-size-2xl)",description:"Accent edge \u2014 width of the semantic leading-edge stripe (data-accent). * Tokenised (rule #44) so a service theme can re-tune it without forking CSS. * The slot padding compensation in card-layout.css subtracts the same token, * so content stays aligned on the shell whatever the rail width."},{name:"--stat-card-value-line-height",value:"1.1",description:"Accent edge \u2014 width of the semantic leading-edge stripe (data-accent). * Tokenised (rule #44) so a service theme can re-tune it without forking CSS. * The slot padding compensation in card-layout.css subtracts the same token, * so content stays aligned on the shell whatever the rail width."},{name:"--stat-card-value-font-weight",value:"var(--font-weight-semibold)",description:"Accent edge \u2014 width of the semantic leading-edge stripe (data-accent). * Tokenised (rule #44) so a service theme can re-tune it without forking CSS. * The slot padding compensation in card-layout.css subtracts the same token, * so content stays aligned on the shell whatever the rail width."},{name:"--stat-card-hint-font-size",value:"var(--font-size-xs)",description:"Accent edge \u2014 width of the semantic leading-edge stripe (data-accent). * Tokenised (rule #44) so a service theme can re-tune it without forking CSS. * The slot padding compensation in card-layout.css subtracts the same token, * so content stays aligned on the shell whatever the rail width."},{name:"--stat-card-gap",value:"var(--space-stack-xs)",description:"Accent edge \u2014 width of the semantic leading-edge stripe (data-accent). * Tokenised (rule #44) so a service theme can re-tune it without forking CSS. * The slot padding compensation in card-layout.css subtracts the same token, * so content stays aligned on the shell whatever the rail width."},{name:"--stat-card-icon-size",value:"2.25rem",description:"Accent edge \u2014 width of the semantic leading-edge stripe (data-accent). * Tokenised (rule #44) so a service theme can re-tune it without forking CSS. * The slot padding compensation in card-layout.css subtracts the same token, * so content stays aligned on the shell whatever the rail width."},{name:"--stat-card-icon-glyph-size",value:"1.25rem",description:"Accent edge \u2014 width of the semantic leading-edge stripe (data-accent). * Tokenised (rule #44) so a service theme can re-tune it without forking CSS. * The slot padding compensation in card-layout.css subtracts the same token, * so content stays aligned on the shell whatever the rail width."},{name:"--stat-card-icon-radius",value:"var(--radius-md)",description:"Accent edge \u2014 width of the semantic leading-edge stripe (data-accent). * Tokenised (rule #44) so a service theme can re-tune it without forking CSS. * The slot padding compensation in card-layout.css subtracts the same token, * so content stays aligned on the shell whatever the rail width."},{name:"--stat-card-icon-background",value:"initial",description:"Medallion tint \u2014 soft brand wash + brand glyph by default; a service retints by overriding * --primary or these tokens directly (rule #44/#45). `initial` so the --primary default * re-resolves at the call site under a scoped theme (no :root freeze). * Defaults = hsl(var(--primary) / 0.1) fill \xB7 hsl(var(--primary)) glyph."},{name:"--stat-card-icon-foreground",value:"initial",description:"Medallion tint \u2014 soft brand wash + brand glyph by default; a service retints by overriding * --primary or these tokens directly (rule #44/#45). `initial` so the --primary default * re-resolves at the call site under a scoped theme (no :root freeze). * Defaults = hsl(var(--primary) / 0.1) fill \xB7 hsl(var(--primary)) glyph."},{name:"--stat-card-delta-font-size",value:"var(--font-size-xs)",description:"Medallion tint \u2014 soft brand wash + brand glyph by default; a service retints by overriding * --primary or these tokens directly (rule #44/#45). `initial` so the --primary default * re-resolves at the call site under a scoped theme (no :root freeze). * Defaults = hsl(var(--primary) / 0.1) fill \xB7 hsl(var(--primary)) glyph."},{name:"--control-height-compact",value:"1.75rem",description:"Control primitive tokens: heights, horizontal padding, adjacent control sizes."},{name:"--control-height-default",value:"2rem",description:"Control primitive tokens: heights, horizontal padding, adjacent control sizes."},{name:"--control-height-comfortable",value:"2.75rem",description:"Control primitive tokens: heights, horizontal padding, adjacent control sizes."},{name:"--control-padding-x-compact",value:"var(--space-2)",description:"Control primitive tokens: heights, horizontal padding, adjacent control sizes."},{name:"--control-padding-x-default",value:"var(--space-3)",description:"Control primitive tokens: heights, horizontal padding, adjacent control sizes."},{name:"--control-padding-x-comfortable",value:"var(--space-4)",description:"Control primitive tokens: heights, horizontal padding, adjacent control sizes."},{name:"--control-height",value:"calc(var(--control-height-default) * var(--scaling))",description:"Control primitive tokens: heights, horizontal padding, adjacent control sizes."},{name:"--control-height-sm",value:"calc(var(--control-height) - calc(0.25rem * var(--scaling)))",description:"Adjacent control sizes, derived from the active --control-height. The \xB1step * is scaled too so the whole control ladder stays proportional under --scaling."},{name:"--control-height-lg",value:"calc(var(--control-height) + calc(0.25rem * var(--scaling)))",description:"Adjacent control sizes, derived from the active --control-height. The \xB1step * is scaled too so the whole control ladder stays proportional under --scaling."},{name:"--control-height-xs",value:"calc(var(--control-height) - calc(0.5rem * var(--scaling)))",description:"Adjacent control sizes, derived from the active --control-height. The \xB1step * is scaled too so the whole control ladder stays proportional under --scaling."},{name:"--control-padding-x",value:"var(--control-padding-x-default)",description:"Adjacent control sizes, derived from the active --control-height. The \xB1step * is scaled too so the whole control ladder stays proportional under --scaling."},{name:"--control-gap",value:"var(--space-inline-sm)",description:"Adjacent control sizes, derived from the active --control-height. The \xB1step * is scaled too so the whole control ladder stays proportional under --scaling."},{name:"--control-gap-sm",value:"var(--space-inline-xs)",description:"Adjacent control sizes, derived from the active --control-height. The \xB1step * is scaled too so the whole control ladder stays proportional under --scaling."},{name:"--control-radius",value:"var(--radius)",description:"Adjacent control sizes, derived from the active --control-height. The \xB1step * is scaled too so the whole control ladder stays proportional under --scaling."},{name:"--button-radius",value:"var(--radius-md)",description:"Button corner radius \u2014 defaults to the button's historical `rounded-md` so nothing * changes by default, but is its OWN knob so a service theme can retune the button * radius INDEPENDENTLY of input/control radius (issue #124)."},{name:"--control-font-size",value:"var(--font-size-base)",description:"Control surface knobs \u2014 font-size, border width and resting shadow of every * `.ui-control` (input / picker trigger). Tokenised so a service theme tunes them * once instead of each component hard-coding Tailwind utilities. Defaults preserve * the historical look (font-size-base, 1px border, shadow-xs)."},{name:"--control-border-width",value:"1px",description:"Control surface knobs \u2014 font-size, border width and resting shadow of every * `.ui-control` (input / picker trigger). Tokenised so a service theme tunes them * once instead of each component hard-coding Tailwind utilities. Defaults preserve * the historical look (font-size-base, 1px border, shadow-xs)."},{name:"--control-shadow",value:"var(--shadow-xs)",description:"Control surface knobs \u2014 font-size, border width and resting shadow of every * `.ui-control` (input / picker trigger). Tokenised so a service theme tunes them * once instead of each component hard-coding Tailwind utilities. Defaults preserve * the historical look (font-size-base, 1px border, shadow-xs)."},{name:"--control-icon-size",value:"calc(1rem * var(--scaling))",description:"Control surface knobs \u2014 font-size, border width and resting shadow of every * `.ui-control` (input / picker trigger). Tokenised so a service theme tunes them * once instead of each component hard-coding Tailwind utilities. Defaults preserve * the historical look (font-size-base, 1px border, shadow-xs)."},{name:"--control-icon-size-sm",value:"calc(0.875rem * var(--scaling))",description:"Control surface knobs \u2014 font-size, border width and resting shadow of every * `.ui-control` (input / picker trigger). Tokenised so a service theme tunes them * once instead of each component hard-coding Tailwind utilities. Defaults preserve * the historical look (font-size-base, 1px border, shadow-xs)."},{name:"--control-focus-ring-width",value:"var(--focus-ring-width)",description:"Control surface knobs \u2014 font-size, border width and resting shadow of every * `.ui-control` (input / picker trigger). Tokenised so a service theme tunes them * once instead of each component hard-coding Tailwind utilities. Defaults preserve * the historical look (font-size-base, 1px border, shadow-xs)."},{name:"--checkbox-size",value:"calc(1rem * var(--scaling))",description:"Control surface knobs \u2014 font-size, border width and resting shadow of every * `.ui-control` (input / picker trigger). Tokenised so a service theme tunes them * once instead of each component hard-coding Tailwind utilities. Defaults preserve * the historical look (font-size-base, 1px border, shadow-xs)."},{name:"--checkbox-size-compact",value:"0.875rem",description:"Control surface knobs \u2014 font-size, border width and resting shadow of every * `.ui-control` (input / picker trigger). Tokenised so a service theme tunes them * once instead of each component hard-coding Tailwind utilities. Defaults preserve * the historical look (font-size-base, 1px border, shadow-xs)."},{name:"--checkbox-size-comfortable",value:"1.125rem",description:"Control surface knobs \u2014 font-size, border width and resting shadow of every * `.ui-control` (input / picker trigger). Tokenised so a service theme tunes them * once instead of each component hard-coding Tailwind utilities. Defaults preserve * the historical look (font-size-base, 1px border, shadow-xs)."},{name:"--choice-gap",value:"var(--space-inline-sm)",description:"Control surface knobs \u2014 font-size, border width and resting shadow of every * `.ui-control` (input / picker trigger). Tokenised so a service theme tunes them * once instead of each component hard-coding Tailwind utilities. Defaults preserve * the historical look (font-size-base, 1px border, shadow-xs)."},{name:"--choice-group-gap-x",value:"var(--space-6)",description:"Control surface knobs \u2014 font-size, border width and resting shadow of every * `.ui-control` (input / picker trigger). Tokenised so a service theme tunes them * once instead of each component hard-coding Tailwind utilities. Defaults preserve * the historical look (font-size-base, 1px border, shadow-xs)."},{name:"--choice-group-gap-y",value:"var(--space-3)",description:"Control surface knobs \u2014 font-size, border width and resting shadow of every * `.ui-control` (input / picker trigger). Tokenised so a service theme tunes them * once instead of each component hard-coding Tailwind utilities. Defaults preserve * the historical look (font-size-base, 1px border, shadow-xs)."},{name:"--choice-description-gap",value:"0.125rem",description:"Control surface knobs \u2014 font-size, border width and resting shadow of every * `.ui-control` (input / picker trigger). Tokenised so a service theme tunes them * once instead of each component hard-coding Tailwind utilities. Defaults preserve * the historical look (font-size-base, 1px border, shadow-xs)."},{name:"--choice-control-offset",value:"0.125rem",description:"Control surface knobs \u2014 font-size, border width and resting shadow of every * `.ui-control` (input / picker trigger). Tokenised so a service theme tunes them * once instead of each component hard-coding Tailwind utilities. Defaults preserve * the historical look (font-size-base, 1px border, shadow-xs)."},{name:"--switch-width",value:"calc(2.25rem * var(--scaling))",description:"Control surface knobs \u2014 font-size, border width and resting shadow of every * `.ui-control` (input / picker trigger). Tokenised so a service theme tunes them * once instead of each component hard-coding Tailwind utilities. Defaults preserve * the historical look (font-size-base, 1px border, shadow-xs)."},{name:"--switch-width-compact",value:"2rem",description:"Control surface knobs \u2014 font-size, border width and resting shadow of every * `.ui-control` (input / picker trigger). Tokenised so a service theme tunes them * once instead of each component hard-coding Tailwind utilities. Defaults preserve * the historical look (font-size-base, 1px border, shadow-xs)."},{name:"--switch-width-comfortable",value:"2.5rem",description:"Control surface knobs \u2014 font-size, border width and resting shadow of every * `.ui-control` (input / picker trigger). Tokenised so a service theme tunes them * once instead of each component hard-coding Tailwind utilities. Defaults preserve * the historical look (font-size-base, 1px border, shadow-xs)."},{name:"--switch-height",value:"calc(1.25rem * var(--scaling))",description:"Control surface knobs \u2014 font-size, border width and resting shadow of every * `.ui-control` (input / picker trigger). Tokenised so a service theme tunes them * once instead of each component hard-coding Tailwind utilities. Defaults preserve * the historical look (font-size-base, 1px border, shadow-xs)."},{name:"--switch-height-compact",value:"1.125rem",description:"Control surface knobs \u2014 font-size, border width and resting shadow of every * `.ui-control` (input / picker trigger). Tokenised so a service theme tunes them * once instead of each component hard-coding Tailwind utilities. Defaults preserve * the historical look (font-size-base, 1px border, shadow-xs)."},{name:"--switch-height-comfortable",value:"1.375rem",description:"Control surface knobs \u2014 font-size, border width and resting shadow of every * `.ui-control` (input / picker trigger). Tokenised so a service theme tunes them * once instead of each component hard-coding Tailwind utilities. Defaults preserve * the historical look (font-size-base, 1px border, shadow-xs)."},{name:"--switch-thumb-size",value:"calc(1rem * var(--scaling))",description:"Control surface knobs \u2014 font-size, border width and resting shadow of every * `.ui-control` (input / picker trigger). Tokenised so a service theme tunes them * once instead of each component hard-coding Tailwind utilities. Defaults preserve * the historical look (font-size-base, 1px border, shadow-xs)."},{name:"--switch-thumb-size-compact",value:"0.875rem",description:"Control surface knobs \u2014 font-size, border width and resting shadow of every * `.ui-control` (input / picker trigger). Tokenised so a service theme tunes them * once instead of each component hard-coding Tailwind utilities. Defaults preserve * the historical look (font-size-base, 1px border, shadow-xs)."},{name:"--switch-thumb-size-comfortable",value:"1.125rem",description:"Control surface knobs \u2014 font-size, border width and resting shadow of every * `.ui-control` (input / picker trigger). Tokenised so a service theme tunes them * once instead of each component hard-coding Tailwind utilities. Defaults preserve * the historical look (font-size-base, 1px border, shadow-xs)."},{name:"--switch-thumb-translate",value:"calc(1rem * var(--scaling))",description:"Control surface knobs \u2014 font-size, border width and resting shadow of every * `.ui-control` (input / picker trigger). Tokenised so a service theme tunes them * once instead of each component hard-coding Tailwind utilities. Defaults preserve * the historical look (font-size-base, 1px border, shadow-xs)."},{name:"--switch-thumb-translate-compact",value:"0.875rem",description:"Control surface knobs \u2014 font-size, border width and resting shadow of every * `.ui-control` (input / picker trigger). Tokenised so a service theme tunes them * once instead of each component hard-coding Tailwind utilities. Defaults preserve * the historical look (font-size-base, 1px border, shadow-xs)."},{name:"--switch-thumb-translate-comfortable",value:"1.125rem",description:"Control surface knobs \u2014 font-size, border width and resting shadow of every * `.ui-control` (input / picker trigger). Tokenised so a service theme tunes them * once instead of each component hard-coding Tailwind utilities. Defaults preserve * the historical look (font-size-base, 1px border, shadow-xs)."},{name:"--slider-track-height",value:"0.375rem",description:"Control surface knobs \u2014 font-size, border width and resting shadow of every * `.ui-control` (input / picker trigger). Tokenised so a service theme tunes them * once instead of each component hard-coding Tailwind utilities. Defaults preserve * the historical look (font-size-base, 1px border, shadow-xs)."},{name:"--slider-thumb-size",value:"1rem",description:"Control surface knobs \u2014 font-size, border width and resting shadow of every * `.ui-control` (input / picker trigger). Tokenised so a service theme tunes them * once instead of each component hard-coding Tailwind utilities. Defaults preserve * the historical look (font-size-base, 1px border, shadow-xs)."},{name:"--checkbox-checked-background",value:"initial",description:'Checked/on/active fills \u2014 `initial` so the --primary default re-resolves at the call site * under a scoped theme (a :root binding to var(--primary) freezes at the :root value and a scoped * [data-tenant] override of --primary never reaches it). A service retints the "selected" state * by overriding these directly. Defaults = hsl(var(--primary)) \xB7 slider track 0.2\u03B1.'},{name:"--switch-checked-background",value:"initial",description:'Checked/on/active fills \u2014 `initial` so the --primary default re-resolves at the call site * under a scoped theme (a :root binding to var(--primary) freezes at the :root value and a scoped * [data-tenant] override of --primary never reaches it). A service retints the "selected" state * by overriding these directly. Defaults = hsl(var(--primary)) \xB7 slider track 0.2\u03B1.'},{name:"--toggle-on-background",value:"initial",description:'Checked/on/active fills \u2014 `initial` so the --primary default re-resolves at the call site * under a scoped theme (a :root binding to var(--primary) freezes at the :root value and a scoped * [data-tenant] override of --primary never reaches it). A service retints the "selected" state * by overriding these directly. Defaults = hsl(var(--primary)) \xB7 slider track 0.2\u03B1.'},{name:"--slider-track-background",value:"initial",description:'Checked/on/active fills \u2014 `initial` so the --primary default re-resolves at the call site * under a scoped theme (a :root binding to var(--primary) freezes at the :root value and a scoped * [data-tenant] override of --primary never reaches it). A service retints the "selected" state * by overriding these directly. Defaults = hsl(var(--primary)) \xB7 slider track 0.2\u03B1.'},{name:"--slider-range-background",value:"initial",description:'Checked/on/active fills \u2014 `initial` so the --primary default re-resolves at the call site * under a scoped theme (a :root binding to var(--primary) freezes at the :root value and a scoped * [data-tenant] override of --primary never reaches it). A service retints the "selected" state * by overriding these directly. Defaults = hsl(var(--primary)) \xB7 slider track 0.2\u03B1.'},{name:"--color-picker-input-width",value:"6.5rem",description:'Checked/on/active fills \u2014 `initial` so the --primary default re-resolves at the call site * under a scoped theme (a :root binding to var(--primary) freezes at the :root value and a scoped * [data-tenant] override of --primary never reaches it). A service retints the "selected" state * by overriding these directly. Defaults = hsl(var(--primary)) \xB7 slider track 0.2\u03B1.'},{name:"--command-list-max-height",value:"min(300px, 50vh)",description:'Checked/on/active fills \u2014 `initial` so the --primary default re-resolves at the call site * under a scoped theme (a :root binding to var(--primary) freezes at the :root value and a scoped * [data-tenant] override of --primary never reaches it). A service retints the "selected" state * by overriding these directly. Defaults = hsl(var(--primary)) \xB7 slider track 0.2\u03B1.'},{name:"--command-input-padding-x",value:"var(--space-3)",description:'Checked/on/active fills \u2014 `initial` so the --primary default re-resolves at the call site * under a scoped theme (a :root binding to var(--primary) freezes at the :root value and a scoped * [data-tenant] override of --primary never reaches it). A service retints the "selected" state * by overriding these directly. Defaults = hsl(var(--primary)) \xB7 slider track 0.2\u03B1.'},{name:"--command-group-padding",value:"var(--space-1)",description:'Checked/on/active fills \u2014 `initial` so the --primary default re-resolves at the call site * under a scoped theme (a :root binding to var(--primary) freezes at the :root value and a scoped * [data-tenant] override of --primary never reaches it). A service retints the "selected" state * by overriding these directly. Defaults = hsl(var(--primary)) \xB7 slider track 0.2\u03B1.'},{name:"--command-item-padding-y",value:"var(--space-2)",description:'Checked/on/active fills \u2014 `initial` so the --primary default re-resolves at the call site * under a scoped theme (a :root binding to var(--primary) freezes at the :root value and a scoped * [data-tenant] override of --primary never reaches it). A service retints the "selected" state * by overriding these directly. Defaults = hsl(var(--primary)) \xB7 slider track 0.2\u03B1.'},{name:"--command-item-padding-x",value:"var(--space-2)",description:'Checked/on/active fills \u2014 `initial` so the --primary default re-resolves at the call site * under a scoped theme (a :root binding to var(--primary) freezes at the :root value and a scoped * [data-tenant] override of --primary never reaches it). A service retints the "selected" state * by overriding these directly. Defaults = hsl(var(--primary)) \xB7 slider track 0.2\u03B1.'},{name:"--search-input-edge-inset",value:"var(--space-3)",description:'Checked/on/active fills \u2014 `initial` so the --primary default re-resolves at the call site * under a scoped theme (a :root binding to var(--primary) freezes at the :root value and a scoped * [data-tenant] override of --primary never reaches it). A service retints the "selected" state * by overriding these directly. Defaults = hsl(var(--primary)) \xB7 slider track 0.2\u03B1.'},{name:"--search-input-start-padding",value:"calc( var(--search-input-edge-inset) + var(--control-icon-size) + var(--control-gap) )",description:'Checked/on/active fills \u2014 `initial` so the --primary default re-resolves at the call site * under a scoped theme (a :root binding to var(--primary) freezes at the :root value and a scoped * [data-tenant] override of --primary never reaches it). A service retints the "selected" state * by overriding these directly. Defaults = hsl(var(--primary)) \xB7 slider track 0.2\u03B1.'},{name:"--search-input-end-padding",value:"calc( var(--search-input-edge-inset) + var(--control-icon-size) + var(--control-gap) )",description:'Checked/on/active fills \u2014 `initial` so the --primary default re-resolves at the call site * under a scoped theme (a :root binding to var(--primary) freezes at the :root value and a scoped * [data-tenant] override of --primary never reaches it). A service retints the "selected" state * by overriding these directly. Defaults = hsl(var(--primary)) \xB7 slider track 0.2\u03B1.'},{name:"--choice-description-font-size",value:"var(--font-size-xs)",description:'Checked/on/active fills \u2014 `initial` so the --primary default re-resolves at the call site * under a scoped theme (a :root binding to var(--primary) freezes at the :root value and a scoped * [data-tenant] override of --primary never reaches it). A service retints the "selected" state * by overriding these directly. Defaults = hsl(var(--primary)) \xB7 slider track 0.2\u03B1.'},{name:"--color-picker-hex-font-size",value:"var(--font-size-xs)",description:'Checked/on/active fills \u2014 `initial` so the --primary default re-resolves at the call site * under a scoped theme (a :root binding to var(--primary) freezes at the :root value and a scoped * [data-tenant] override of --primary never reaches it). A service retints the "selected" state * by overriding these directly. Defaults = hsl(var(--primary)) \xB7 slider track 0.2\u03B1.'},{name:"--command-group-heading-font-size",value:"var(--font-size-xs)",description:'Checked/on/active fills \u2014 `initial` so the --primary default re-resolves at the call site * under a scoped theme (a :root binding to var(--primary) freezes at the :root value and a scoped * [data-tenant] override of --primary never reaches it). A service retints the "selected" state * by overriding these directly. Defaults = hsl(var(--primary)) \xB7 slider track 0.2\u03B1.'},{name:"--search-input-label-font-size",value:"var(--font-size-xs)",description:'Checked/on/active fills \u2014 `initial` so the --primary default re-resolves at the call site * under a scoped theme (a :root binding to var(--primary) freezes at the :root value and a scoped * [data-tenant] override of --primary never reaches it). A service retints the "selected" state * by overriding these directly. Defaults = hsl(var(--primary)) \xB7 slider track 0.2\u03B1.'},{name:"--tag-input-chip-font-size",value:"var(--font-size-xs)",description:'Checked/on/active fills \u2014 `initial` so the --primary default re-resolves at the call site * under a scoped theme (a :root binding to var(--primary) freezes at the :root value and a scoped * [data-tenant] override of --primary never reaches it). A service retints the "selected" state * by overriding these directly. Defaults = hsl(var(--primary)) \xB7 slider track 0.2\u03B1.'},{name:"--toggle-sm-font-size",value:"var(--font-size-xs)",description:'Checked/on/active fills \u2014 `initial` so the --primary default re-resolves at the call site * under a scoped theme (a :root binding to var(--primary) freezes at the :root value and a scoped * [data-tenant] override of --primary never reaches it). A service retints the "selected" state * by overriding these directly. Defaults = hsl(var(--primary)) \xB7 slider track 0.2\u03B1.'},{name:"--button-sm-font-size",value:"var(--font-size-xs)",description:'Checked/on/active fills \u2014 `initial` so the --primary default re-resolves at the call site * under a scoped theme (a :root binding to var(--primary) freezes at the :root value and a scoped * [data-tenant] override of --primary never reaches it). A service retints the "selected" state * by overriding these directly. Defaults = hsl(var(--primary)) \xB7 slider track 0.2\u03B1.'},{name:"--control-height-compact",value:"2.75rem",description:"Rule #24 \u2014 on touch devices (coarse pointer) interactive controls keep a \u226544px tap target * regardless of density; desktop (fine pointer) keeps the compact heights above. --control-height * resolves through these via var(), so inputs/buttons/selects/table rows all bump together."},{name:"--control-height-default",value:"2.75rem",description:"Rule #24 \u2014 on touch devices (coarse pointer) interactive controls keep a \u226544px tap target * regardless of density; desktop (fine pointer) keeps the compact heights above. --control-height * resolves through these via var(), so inputs/buttons/selects/table rows all bump together."},{name:"--progress-label-font-size",value:"var(--font-size-xs)",description:"Data-display component tokens \u2014 small-by-design text knobs (rule #45/#46)."},{name:"--tree-item-title-font-size",value:"var(--font-size-xs)",description:"Data-display component tokens \u2014 small-by-design text knobs (rule #45/#46)."},{name:"--tree-item-description-font-size",value:"var(--font-size-xs)",description:"Data-display component tokens \u2014 small-by-design text knobs (rule #45/#46)."},{name:"--timeline-note-font-size",value:"var(--font-size-xs)",description:"Data-display component tokens \u2014 small-by-design text knobs (rule #45/#46)."},{name:"--avatar-background",value:"initial",description:"Avatar surface \u2014 `initial` so the --muted default re-resolves at the call site under a scoped theme (a :root binding to a role var freezes at :root; a scoped role override never reaches it). A service re-tints the placeholder fill once (e.g. --avatar-background: hsl(var(--accent))). Default = hsl(var(--muted))."},{name:"--avatar-tint",value:"transparent",description:"Optional role wash over the avatar (default transparent = invisible, rule #44). Painted as an overlay so a service sets --avatar-tint: hsl(var(--primary) / 0.08)."},{name:"--progress-track-background",value:"initial",description:"Progress track + fill \u2014 `initial` so the role defaults re-resolve under a scoped theme. Track reads --secondary, fill reads --success; a service re-tones once. Defaults = hsl(var(--secondary)) track \xB7 hsl(var(--success)) fill."},{name:"--progress-fill-background",value:"initial",description:"Progress track + fill \u2014 `initial` so the role defaults re-resolve under a scoped theme. Track reads --secondary, fill reads --success; a service re-tones once. Defaults = hsl(var(--secondary)) track \xB7 hsl(var(--success)) fill."},{name:"--timeline-dot-done-background",value:"initial",description:"Timeline accents \u2014 `initial` so the dot/line role defaults re-resolve under a scoped theme. Defaults = hsl(var(--success)) done \xB7 hsl(var(--primary)) current/line."},{name:"--timeline-dot-current-background",value:"initial",description:"Timeline accents \u2014 `initial` so the dot/line role defaults re-resolve under a scoped theme. Defaults = hsl(var(--success)) done \xB7 hsl(var(--primary)) current/line."},{name:"--timeline-line-completed-background",value:"initial",description:"Timeline accents \u2014 `initial` so the dot/line role defaults re-resolve under a scoped theme. Defaults = hsl(var(--success)) done \xB7 hsl(var(--primary)) current/line."},{name:"--tree-item-active-border",value:"initial",description:"Tree active item \u2014 border + soft bg tint over the --primary role. `initial` so the default re-resolves under a scoped theme. Defaults = hsl(var(--primary) / 0.3) border \xB7 0.05 fill."},{name:"--tree-item-active-background",value:"initial",description:"Tree active item \u2014 border + soft bg tint over the --primary role. `initial` so the default re-resolves under a scoped theme. Defaults = hsl(var(--primary) / 0.3) border \xB7 0.05 fill."},{name:"--qr-code-foreground",value:"0 0% 0%",description:"QR codes stay scanner-safe in light and dark application themes. Consumers may override these component tokens only when the resulting pair retains strong contrast."},{name:"--qr-code-background",value:"0 0% 100%",description:"QR codes stay scanner-safe in light and dark application themes. Consumers may override these component tokens only when the resulting pair retains strong contrast."},{name:"--qr-code-size-xs",value:"6rem",description:"QR codes stay scanner-safe in light and dark application themes. Consumers may override these component tokens only when the resulting pair retains strong contrast."},{name:"--qr-code-size-sm",value:"8rem",description:"QR codes stay scanner-safe in light and dark application themes. Consumers may override these component tokens only when the resulting pair retains strong contrast."},{name:"--qr-code-size-md",value:"10rem",description:"QR codes stay scanner-safe in light and dark application themes. Consumers may override these component tokens only when the resulting pair retains strong contrast."},{name:"--qr-code-size-lg",value:"12.5rem",description:"QR codes stay scanner-safe in light and dark application themes. Consumers may override these component tokens only when the resulting pair retains strong contrast."},{name:"--password-strength-score-font-size",value:"var(--font-size-xs)",description:"Data-entry component tokens \u2014 small-by-design text knobs (rule #45/#46)."},{name:"--password-strength-checklist-font-size",value:"var(--font-size-xs)",description:"Data-entry component tokens \u2014 small-by-design text knobs (rule #45/#46)."},{name:"--descriptions-label-width",value:"8rem",description:'Width of the label column when <Descriptions layout="horizontal">. Labels align to this * shared column so the values line up (the horizontal-detail look, mirroring <Form layout>). * A rem value gives a fixed aligned column; set `max-content` to size each label to its text. * (rule #44/#45 \u2014 a service theme tunes it here instead of forking CSS.)'},{name:"--dialog-space-x",value:"var(--space-chrome-x)",description:"Dialog inset defaults to the shared global chrome tokens (override --space-chrome-* once for the * whole system, or --dialog-space-x/-y for dialogs only)."},{name:"--dialog-space-y",value:"var(--space-chrome-y)",description:"Dialog inset defaults to the shared global chrome tokens (override --space-chrome-* once for the * whole system, or --dialog-space-x/-y for dialogs only)."},{name:"--dialog-space-inset",value:"var(--dialog-space-y) var(--dialog-space-x)",description:"Dialog inset defaults to the shared global chrome tokens (override --space-chrome-* once for the * whole system, or --dialog-space-x/-y for dialogs only)."},{name:"--dialog-space-gap",value:"var(--space-stack-md)",description:"Dialog inset defaults to the shared global chrome tokens (override --space-chrome-* once for the * whole system, or --dialog-space-x/-y for dialogs only)."},{name:"--dialog-close-space-offset",value:"var(--space-4)",description:"Dialog inset defaults to the shared global chrome tokens (override --space-chrome-* once for the * whole system, or --dialog-space-x/-y for dialogs only)."},{name:"--alert-space-inset",value:"var(--space-section-active)",description:"Dialog inset defaults to the shared global chrome tokens (override --space-chrome-* once for the * whole system, or --dialog-space-x/-y for dialogs only)."},{name:"--alert-space-gap",value:"var(--space-inline-md)",description:"Dialog inset defaults to the shared global chrome tokens (override --space-chrome-* once for the * whole system, or --dialog-space-x/-y for dialogs only)."},{name:"--alert-inner-space-gap",value:"var(--space-stack-sm)",description:"Dialog inset defaults to the shared global chrome tokens (override --space-chrome-* once for the * whole system, or --dialog-space-x/-y for dialogs only)."},{name:"--alert-dismiss-space-offset",value:"var(--space-3)",description:"Dialog inset defaults to the shared global chrome tokens (override --space-chrome-* once for the * whole system, or --dialog-space-x/-y for dialogs only)."},{name:"--alert-bg-alpha",value:"0.05",description:"Soft (subtle) semantic tint ratios \u2014 themeable so a service can hit its exact spec * (a brand's success-bg/-border are often more present than the faint 5%/30% default)."},{name:"--alert-border-alpha",value:"0.3",description:"Soft (subtle) semantic tint ratios \u2014 themeable so a service can hit its exact spec * (a brand's success-bg/-border are often more present than the faint 5%/30% default)."},{name:"--dialog-content-glow",value:"0 0 0 0 transparent",description:"Brand glow layer for the raised dialog/sheet panel \u2014 invisible no-op at rest (rule #44). * Paired AFTER --shadow-lg in the surface box-shadow so a service can wash the overlay with the * global glow, e.g. --dialog-content-glow: var(--shadow-glow), with no markup change."},{name:"--empty-state-space-y",value:"var(--space-10)",description:"Brand glow layer for the raised dialog/sheet panel \u2014 invisible no-op at rest (rule #44). * Paired AFTER --shadow-lg in the surface box-shadow so a service can wash the overlay with the * global glow, e.g. --dialog-content-glow: var(--shadow-glow), with no markup change."},{name:"--empty-state-space-x",value:"var(--space-6)",description:"Brand glow layer for the raised dialog/sheet panel \u2014 invisible no-op at rest (rule #44). * Paired AFTER --shadow-lg in the surface box-shadow so a service can wash the overlay with the * global glow, e.g. --dialog-content-glow: var(--shadow-glow), with no markup change."},{name:"--empty-state-section-space-y",value:"var(--space-6)",description:"Brand glow layer for the raised dialog/sheet panel \u2014 invisible no-op at rest (rule #44). * Paired AFTER --shadow-lg in the surface box-shadow so a service can wash the overlay with the * global glow, e.g. --dialog-content-glow: var(--shadow-glow), with no markup change."},{name:"--empty-state-section-space-x",value:"var(--space-4)",description:"Brand glow layer for the raised dialog/sheet panel \u2014 invisible no-op at rest (rule #44). * Paired AFTER --shadow-lg in the surface box-shadow so a service can wash the overlay with the * global glow, e.g. --dialog-content-glow: var(--shadow-glow), with no markup change."},{name:"--empty-state-compact-space-y",value:"var(--space-3)",description:"Brand glow layer for the raised dialog/sheet panel \u2014 invisible no-op at rest (rule #44). * Paired AFTER --shadow-lg in the surface box-shadow so a service can wash the overlay with the * global glow, e.g. --dialog-content-glow: var(--shadow-glow), with no markup change."},{name:"--empty-state-compact-space-x",value:"var(--space-2)",description:"Brand glow layer for the raised dialog/sheet panel \u2014 invisible no-op at rest (rule #44). * Paired AFTER --shadow-lg in the surface box-shadow so a service can wash the overlay with the * global glow, e.g. --dialog-content-glow: var(--shadow-glow), with no markup change."},{name:"--empty-state-icon-foreground",value:"initial",description:"EmptyState icon medallion colour \u2014 `initial` so the role defaults re-resolve at the call site * under a scoped theme (rule #44). A service recolours the glyph (--empty-state-icon-foreground) * or washes the medallion fill (--empty-state-icon-tint) without forking. * Defaults = hsl(var(--muted-foreground)) glyph \xB7 hsl(var(--muted)) fill."},{name:"--empty-state-icon-tint",value:"initial",description:"EmptyState icon medallion colour \u2014 `initial` so the role defaults re-resolve at the call site * under a scoped theme (rule #44). A service recolours the glyph (--empty-state-icon-foreground) * or washes the medallion fill (--empty-state-icon-tint) without forking. * Defaults = hsl(var(--muted-foreground)) glyph \xB7 hsl(var(--muted)) fill."},{name:"--skeleton-row-gap",value:"var(--space-stack-sm)",description:"EmptyState icon medallion colour \u2014 `initial` so the role defaults re-resolve at the call site * under a scoped theme (rule #44). A service recolours the glyph (--empty-state-icon-foreground) * or washes the medallion fill (--empty-state-icon-tint) without forking. * Defaults = hsl(var(--muted-foreground)) glyph \xB7 hsl(var(--muted)) fill."},{name:"--skeleton-cell-gap",value:"var(--space-inline-lg)",description:"EmptyState icon medallion colour \u2014 `initial` so the role defaults re-resolve at the call site * under a scoped theme (rule #44). A service recolours the glyph (--empty-state-icon-foreground) * or washes the medallion fill (--empty-state-icon-tint) without forking. * Defaults = hsl(var(--muted-foreground)) glyph \xB7 hsl(var(--muted)) fill."},{name:"--skeleton-card-inset",value:"var(--space-section-active)",description:"EmptyState icon medallion colour \u2014 `initial` so the role defaults re-resolve at the call site * under a scoped theme (rule #44). A service recolours the glyph (--empty-state-icon-foreground) * or washes the medallion fill (--empty-state-icon-tint) without forking. * Defaults = hsl(var(--muted-foreground)) glyph \xB7 hsl(var(--muted)) fill."},{name:"--skeleton-radius",value:"var(--radius)",description:"EmptyState icon medallion colour \u2014 `initial` so the role defaults re-resolve at the call site * under a scoped theme (rule #44). A service recolours the glyph (--empty-state-icon-foreground) * or washes the medallion fill (--empty-state-icon-tint) without forking. * Defaults = hsl(var(--muted-foreground)) glyph \xB7 hsl(var(--muted)) fill."},{name:"--skeleton-background",value:"initial",description:"Skeleton placeholder fill \u2014 `initial` so the --muted default re-resolves at the call site under * a scoped theme (a :root binding to a role var freezes at :root). A service tints the shimmer to * its surface (rule #44) without forking the keyframes. Default = hsl(var(--muted))."},{name:"--form-label-width",value:"max-content",description:"Width of the label column in horizontal/inline layout. A service theme sets * this once (e.g. 110px) to align every form to its design grid; the Form/ * FormField `labelWidth` prop overrides per form/field."},{name:"--form-label-gap",value:"var(--space-4)",description:"Column gap between the label and its control in horizontal/inline layout."},{name:"--list-row-padding-y",value:"var(--space-3)",description:"ListRow component tokens \u2014 a single-line entity row for short lists inside a Card * (sessions / API tokens / linked accounts / passkeys \u2026). Sits in a flush CardContent; * rows separate with a quiet divider (#44 \u2014 chrome defaults to the calm semantic border)."},{name:"--list-row-padding-x",value:"var(--space-4)",description:"ListRow component tokens \u2014 a single-line entity row for short lists inside a Card * (sessions / API tokens / linked accounts / passkeys \u2026). Sits in a flush CardContent; * rows separate with a quiet divider (#44 \u2014 chrome defaults to the calm semantic border)."},{name:"--list-row-gap",value:"var(--space-3)",description:"ListRow component tokens \u2014 a single-line entity row for short lists inside a Card * (sessions / API tokens / linked accounts / passkeys \u2026). Sits in a flush CardContent; * rows separate with a quiet divider (#44 \u2014 chrome defaults to the calm semantic border)."},{name:"--list-row-border",value:"initial",description:"Row divider \u2014 `initial` so the --border default re-resolves at the call site under a scoped * theme (a :root binding to a role var freezes at :root). Default = 1px solid hsl(var(--border))."},{name:"--logo-radius",value:"var(--radius)",description:"Logo component tokens \u2014 the product brand-mark box (a glyph on the primary fill), used in the * app shell header, auth screens, and topbars. Size + radius + per-tier font-size are knobs so a * service theme retunes the mark without forking CSS (rules #44/#45). Colours read the primary * role tokens directly, so a re-themed --primary re-tints the mark automatically."},{name:"--logo-size-xs",value:"1.25rem",description:"Logo component tokens \u2014 the product brand-mark box (a glyph on the primary fill), used in the * app shell header, auth screens, and topbars. Size + radius + per-tier font-size are knobs so a * service theme retunes the mark without forking CSS (rules #44/#45). Colours read the primary * role tokens directly, so a re-themed --primary re-tints the mark automatically."},{name:"--logo-size-sm",value:"1.5rem",description:"Logo component tokens \u2014 the product brand-mark box (a glyph on the primary fill), used in the * app shell header, auth screens, and topbars. Size + radius + per-tier font-size are knobs so a * service theme retunes the mark without forking CSS (rules #44/#45). Colours read the primary * role tokens directly, so a re-themed --primary re-tints the mark automatically."},{name:"--logo-size-md",value:"1.75rem",description:"Logo component tokens \u2014 the product brand-mark box (a glyph on the primary fill), used in the * app shell header, auth screens, and topbars. Size + radius + per-tier font-size are knobs so a * service theme retunes the mark without forking CSS (rules #44/#45). Colours read the primary * role tokens directly, so a re-themed --primary re-tints the mark automatically."},{name:"--logo-size-lg",value:"2.25rem",description:"Logo component tokens \u2014 the product brand-mark box (a glyph on the primary fill), used in the * app shell header, auth screens, and topbars. Size + radius + per-tier font-size are knobs so a * service theme retunes the mark without forking CSS (rules #44/#45). Colours read the primary * role tokens directly, so a re-themed --primary re-tints the mark automatically."},{name:"--logo-font-size-xs",value:"var(--font-size-2xs)",description:"Logo component tokens \u2014 the product brand-mark box (a glyph on the primary fill), used in the * app shell header, auth screens, and topbars. Size + radius + per-tier font-size are knobs so a * service theme retunes the mark without forking CSS (rules #44/#45). Colours read the primary * role tokens directly, so a re-themed --primary re-tints the mark automatically."},{name:"--logo-font-size-sm",value:"var(--font-size-xs)",description:"Logo component tokens \u2014 the product brand-mark box (a glyph on the primary fill), used in the * app shell header, auth screens, and topbars. Size + radius + per-tier font-size are knobs so a * service theme retunes the mark without forking CSS (rules #44/#45). Colours read the primary * role tokens directly, so a re-themed --primary re-tints the mark automatically."},{name:"--logo-font-size-md",value:"var(--font-size-sm)",description:"Logo component tokens \u2014 the product brand-mark box (a glyph on the primary fill), used in the * app shell header, auth screens, and topbars. Size + radius + per-tier font-size are knobs so a * service theme retunes the mark without forking CSS (rules #44/#45). Colours read the primary * role tokens directly, so a re-themed --primary re-tints the mark automatically."},{name:"--logo-font-size-lg",value:"var(--font-size-base)",description:"Logo component tokens \u2014 the product brand-mark box (a glyph on the primary fill), used in the * app shell header, auth screens, and topbars. Size + radius + per-tier font-size are knobs so a * service theme retunes the mark without forking CSS (rules #44/#45). Colours read the primary * role tokens directly, so a re-themed --primary re-tints the mark automatically."},{name:"--pagination-gap",value:"var(--space-inline-sm)",description:"Navigation primitive tokens: pagination, filters, compact pickers."},{name:"--pagination-item-gap",value:"var(--space-inline-xs)",description:"Navigation primitive tokens: pagination, filters, compact pickers."},{name:"--pagination-size-width",value:"5.5rem",description:"Navigation primitive tokens: pagination, filters, compact pickers."},{name:"--pagination-total-font-size",value:"var(--font-size-sm)",description:"Navigation primitive tokens: pagination, filters, compact pickers."},{name:"--filter-bar-gap",value:"var(--space-3)",description:"Navigation primitive tokens: pagination, filters, compact pickers."},{name:"--filter-bar-padding-y",value:"var(--space-2)",description:"Navigation primitive tokens: pagination, filters, compact pickers."},{name:"--filter-label-font-size",value:"var(--font-size-xs)",description:"Navigation primitive tokens: pagination, filters, compact pickers."},{name:"--filter-picker-width-sm",value:"11rem",description:"Navigation primitive tokens: pagination, filters, compact pickers."},{name:"--filter-picker-width-md",value:"14rem",description:"Navigation primitive tokens: pagination, filters, compact pickers."},{name:"--breadcrumb-font-size",value:"var(--font-size-xs)",description:"Navigation primitive tokens: pagination, filters, compact pickers."},{name:"--menubar-shortcut-font-size",value:"var(--font-size-xs)",description:"Navigation primitive tokens: pagination, filters, compact pickers."},{name:"--tabs-list-max-inline-size",value:"100%",description:"Navigation primitive tokens: pagination, filters, compact pickers."},{name:"--tabs-list-overflow",value:"auto",description:"Navigation primitive tokens: pagination, filters, compact pickers."},{name:"--menubar-item-hover-background",value:"initial",description:"Menu item hover/highlight tint \u2014 `initial` so the --accent default re-resolves at the call site * under a scoped theme (a :root binding to a role var freezes at :root). * Defaults = hsl(var(--accent)) fill \xB7 hsl(var(--accent-foreground)) text."},{name:"--menubar-item-hover-foreground",value:"initial",description:"Menu item hover/highlight tint \u2014 `initial` so the --accent default re-resolves at the call site * under a scoped theme (a :root binding to a role var freezes at :root). * Defaults = hsl(var(--accent)) fill \xB7 hsl(var(--accent-foreground)) text."},{name:"--filter-bar-sticky-offset",value:"0px",description:"Sticky offset for the pinned filter strip (Toolbar `sticky`, #197). Default 0 (chrome, * rule #44); raise it so the strip parks below a fixed topbar."},{name:"--filter-bar-sticky-background",value:"initial",description:"Fill painted only when the filter strip is pinned. Role-mirror knob (rule #45): `initial` * so --background re-resolves at the call site under a scoped [data-tenant]/.dark theme."},{name:"--sidebar-section-label-font-size",value:"var(--font-size-2xs)",description:"Shell (sidebar / topbar / kbd) component tokens \u2014 small-by-design text * knobs (rule #45/#46). A service re-tunes chrome text without moving the * global scale."},{name:"--sidebar-product-tenant-font-size",value:"var(--font-size-2xs)",description:"Shell (sidebar / topbar / kbd) component tokens \u2014 small-by-design text * knobs (rule #45/#46). A service re-tunes chrome text without moving the * global scale."},{name:"--sidebar-badge-font-size",value:"var(--font-size-2xs)",description:"Shell (sidebar / topbar / kbd) component tokens \u2014 small-by-design text * knobs (rule #45/#46). A service re-tunes chrome text without moving the * global scale."},{name:"--sidebar-user-role-font-size",value:"var(--font-size-2xs)",description:"Shell (sidebar / topbar / kbd) component tokens \u2014 small-by-design text * knobs (rule #45/#46). A service re-tunes chrome text without moving the * global scale."},{name:"--sidebar-nav-sub-font-size",value:"var(--font-size-xs)",description:"Shell (sidebar / topbar / kbd) component tokens \u2014 small-by-design text * knobs (rule #45/#46). A service re-tunes chrome text without moving the * global scale."},{name:"--sidebar-flyout-title-font-size",value:"var(--font-size-xs)",description:"Shell (sidebar / topbar / kbd) component tokens \u2014 small-by-design text * knobs (rule #45/#46). A service re-tunes chrome text without moving the * global scale."},{name:"--topbar-chip-icon-font-size",value:"var(--font-size-2xs)",description:"Shell (sidebar / topbar / kbd) component tokens \u2014 small-by-design text * knobs (rule #45/#46). A service re-tunes chrome text without moving the * global scale."},{name:"--kbd-font-size",value:"var(--font-size-2xs)",description:"Shell (sidebar / topbar / kbd) component tokens \u2014 small-by-design text * knobs (rule #45/#46). A service re-tunes chrome text without moving the * global scale."},{name:"--sidebar-logo-mark-font-size",value:"var(--font-size-xs)",description:"Shell (sidebar / topbar / kbd) component tokens \u2014 small-by-design text * knobs (rule #45/#46). A service re-tunes chrome text without moving the * global scale."},{name:"--sidebar-avatar-font-size",value:"var(--font-size-2xs)",description:"Shell (sidebar / topbar / kbd) component tokens \u2014 small-by-design text * knobs (rule #45/#46). A service re-tunes chrome text without moving the * global scale."},{name:"--sidebar-user-name-font-size",value:"var(--font-size-xs)",description:"Shell (sidebar / topbar / kbd) component tokens \u2014 small-by-design text * knobs (rule #45/#46). A service re-tunes chrome text without moving the * global scale."},{name:"--sidebar-gradient",value:"none",description:"Brand-chrome gradient hooks \u2014 opt-in, invisible by default. A service paints * the sidebar/topbar surface by setting these to a gradient (no-op = none)."},{name:"--topbar-gradient",value:"none",description:"Brand-chrome gradient hooks \u2014 opt-in, invisible by default. A service paints * the sidebar/topbar surface by setting these to a gradient (no-op = none)."},{name:"--app-shell-bar-height",value:"3rem",description:"DXS application-shell geometry. These defaults mirror the checked-in * Admin/Console hi-fi source while remaining themeable by consumers."},{name:"--app-shell-page-max-width",value:"80rem",description:"DXS application-shell geometry. These defaults mirror the checked-in * Admin/Console hi-fi source while remaining themeable by consumers."},{name:"--app-shell-main-background",value:"hsl(var(--muted) / 0.4)",description:"DXS application-shell geometry. These defaults mirror the checked-in * Admin/Console hi-fi source while remaining themeable by consumers."},{name:"--sidebar-brand-mark-size",value:"1.375rem",description:"DXS application-shell geometry. These defaults mirror the checked-in * Admin/Console hi-fi source while remaining themeable by consumers."},{name:"--sidebar-nav-item-height",value:"2rem",description:"DXS application-shell geometry. These defaults mirror the checked-in * Admin/Console hi-fi source while remaining themeable by consumers."},{name:"--sidebar-nav-item-font-size",value:"0.8125rem",description:"DXS application-shell geometry. These defaults mirror the checked-in * Admin/Console hi-fi source while remaining themeable by consumers."},{name:"--topbar-search-max-width",value:"26.25rem",description:"DXS application-shell geometry. These defaults mirror the checked-in * Admin/Console hi-fi source while remaining themeable by consumers."},{name:"--sidebar-item-active-color",value:"initial",description:"Sidebar active-item tint/marker \u2014 `initial` so the role defaults re-resolve at the call site * under a scoped theme (a :root binding to a role var freezes at :root; a scoped role override * never reaches it). A service re-tunes the active sub-item accent without forking CSS. * Defaults = hsl(var(--primary)) marker/tint."},{name:"--sidebar-item-active-tint",value:"initial",description:"Sidebar active-item tint/marker \u2014 `initial` so the role defaults re-resolve at the call site * under a scoped theme (a :root binding to a role var freezes at :root; a scoped role override * never reaches it). A service re-tunes the active sub-item accent without forking CSS. * Defaults = hsl(var(--primary)) marker/tint."},{name:"--sidebar-item-active-background",value:"initial",description:"Main nav-item active row \u2014 defaults mirror the hover state (accent bg, foreground text); a * service overrides these to brand the selected row (e.g. a gold tint + gold text on a navy * sidebar). `initial` so the defaults re-resolve under a scoped theme. * Defaults = hsl(var(--accent)) fill \xB7 hsl(var(--foreground)) text."},{name:"--sidebar-item-active-foreground",value:"initial",description:"Main nav-item active row \u2014 defaults mirror the hover state (accent bg, foreground text); a * service overrides these to brand the selected row (e.g. a gold tint + gold text on a navy * sidebar). `initial` so the defaults re-resolve under a scoped theme. * Defaults = hsl(var(--accent)) fill \xB7 hsl(var(--foreground)) text."},{name:"--auth-shell-control-height",value:"var(--control-height-comfortable)",description:"AuthShell \u2014 centred auth/login page shell. Comfortable control density (44px, WCAG touch floor) * + a larger auth heading, scoped to the shell; a service re-tunes the auth card width, insets * and heading size without forking."},{name:"--auth-shell-heading-size",value:"var(--font-size-2xl)",description:"AuthShell \u2014 centred auth/login page shell. Comfortable control density (44px, WCAG touch floor) * + a larger auth heading, scoped to the shell; a service re-tunes the auth card width, insets * and heading size without forking."},{name:"--auth-shell-card-max-width",value:"24rem",description:"AuthShell \u2014 centred auth/login page shell. Comfortable control density (44px, WCAG touch floor) * + a larger auth heading, scoped to the shell; a service re-tunes the auth card width, insets * and heading size without forking."},{name:"--auth-shell-bar-padding",value:"var(--space-5) var(--space-6)",description:"AuthShell \u2014 centred auth/login page shell. Comfortable control density (44px, WCAG touch floor) * + a larger auth heading, scoped to the shell; a service re-tunes the auth card width, insets * and heading size without forking."},{name:"--auth-shell-main-padding",value:"var(--space-6)",description:"AuthShell \u2014 centred auth/login page shell. Comfortable control density (44px, WCAG touch floor) * + a larger auth heading, scoped to the shell; a service re-tunes the auth card width, insets * and heading size without forking."},{name:"--auth-shell-footer-padding",value:"var(--space-3) var(--space-6) var(--space-4)",description:"AuthShell \u2014 centred auth/login page shell. Comfortable control density (44px, WCAG touch floor) * + a larger auth heading, scoped to the shell; a service re-tunes the auth card width, insets * and heading size without forking."},{name:"--centered-shell-bar-height",value:"var(--app-shell-bar-height)",description:'CenteredShell \u2014 authenticated, no-sidebar, centred-column page shell (hosted-ID "My Page", * account, standalone settings). The bar mirrors AppShell\'s `.app-topbar` chrome (fixed height + * inline padding); the column max-width has three tiers, all wider than the 24rem auth card. A * service retunes the bar inset, block padding and each width tier without forking CSS.'},{name:"--centered-shell-bar-padding-x",value:"var(--space-4)",description:'CenteredShell \u2014 authenticated, no-sidebar, centred-column page shell (hosted-ID "My Page", * account, standalone settings). The bar mirrors AppShell\'s `.app-topbar` chrome (fixed height + * inline padding); the column max-width has three tiers, all wider than the 24rem auth card. A * service retunes the bar inset, block padding and each width tier without forking CSS.'},{name:"--centered-shell-main-padding",value:"var(--space-6)",description:'CenteredShell \u2014 authenticated, no-sidebar, centred-column page shell (hosted-ID "My Page", * account, standalone settings). The bar mirrors AppShell\'s `.app-topbar` chrome (fixed height + * inline padding); the column max-width has three tiers, all wider than the 24rem auth card. A * service retunes the bar inset, block padding and each width tier without forking CSS.'},{name:"--centered-shell-footer-padding",value:"var(--space-3) var(--space-6) var(--space-4)",description:'CenteredShell \u2014 authenticated, no-sidebar, centred-column page shell (hosted-ID "My Page", * account, standalone settings). The bar mirrors AppShell\'s `.app-topbar` chrome (fixed height + * inline padding); the column max-width has three tiers, all wider than the 24rem auth card. A * service retunes the bar inset, block padding and each width tier without forking CSS.'},{name:"--centered-shell-width-sm",value:"32rem",description:'CenteredShell \u2014 authenticated, no-sidebar, centred-column page shell (hosted-ID "My Page", * account, standalone settings). The bar mirrors AppShell\'s `.app-topbar` chrome (fixed height + * inline padding); the column max-width has three tiers, all wider than the 24rem auth card. A * service retunes the bar inset, block padding and each width tier without forking CSS.'},{name:"--centered-shell-width-md",value:"46rem",description:'CenteredShell \u2014 authenticated, no-sidebar, centred-column page shell (hosted-ID "My Page", * account, standalone settings). The bar mirrors AppShell\'s `.app-topbar` chrome (fixed height + * inline padding); the column max-width has three tiers, all wider than the 24rem auth card. A * service retunes the bar inset, block padding and each width tier without forking CSS.'},{name:"--centered-shell-width-lg",value:"64rem",description:'CenteredShell \u2014 authenticated, no-sidebar, centred-column page shell (hosted-ID "My Page", * account, standalone settings). The bar mirrors AppShell\'s `.app-topbar` chrome (fixed height + * inline padding); the column max-width has three tiers, all wider than the 24rem auth card. A * service retunes the bar inset, block padding and each width tier without forking CSS.'},{name:"--table-row-height-compact",value:"1.75rem",description:"Table component tokens: row height, cell padding."},{name:"--table-row-height-default",value:"2rem",description:"Table component tokens: row height, cell padding."},{name:"--table-row-height-comfortable",value:"2.75rem",description:"Table component tokens: row height, cell padding."},{name:"--table-row-height",value:"calc(var(--table-row-height-default) * var(--scaling))",description:"Table component tokens: row height, cell padding."},{name:"--table-cell-padding-y",value:"var(--space-2)",description:"Table component tokens: row height, cell padding."},{name:"--table-cell-space-x",value:"var(--control-padding-x)",description:"Table component tokens: row height, cell padding."},{name:"--table-head-font-size",value:"var(--font-size-xs)",description:"Table component tokens: row height, cell padding."},{name:"--table-header-background",value:"initial",description:"Header band \u2014 its OWN bg + fg knobs (decoupled from --secondary). Declared `initial` so the * default re-resolves to the LIVE --muted / --muted-foreground roles at the call site: a :root * binding to a role var freezes at the :root value and a scoped [data-tenant] role override never * reaches it. A brand sets both header tokens together to keep band/text contrast. * Defaults = hsl(var(--muted)) band \xB7 hsl(var(--muted-foreground)) text."},{name:"--table-header-foreground",value:"initial",description:"Header band \u2014 its OWN bg + fg knobs (decoupled from --secondary). Declared `initial` so the * default re-resolves to the LIVE --muted / --muted-foreground roles at the call site: a :root * binding to a role var freezes at the :root value and a scoped [data-tenant] role override never * reaches it. A brand sets both header tokens together to keep band/text contrast. * Defaults = hsl(var(--muted)) band \xB7 hsl(var(--muted-foreground)) text."},{name:"--table-pin-shadow",value:"-6px 0 6px -5px hsl(var(--foreground) / 0.12)",description:"Inline-end shadow that lifts a pinned (sticky) action column off the body it scrolls over."},{name:"--table-row-striped-background",value:"initial",description:"Row-state tint washes \u2014 translucent muted over the opaque base. `initial` so the --muted * default re-resolves under a scoped theme; a service retints by reading another role (e.g. * --primary). Defaults = hsl(var(--muted) / 0.4 striped \xB7 0.5 hover \xB7 0.3 selected)."},{name:"--table-row-hover-background",value:"initial",description:"Row-state tint washes \u2014 translucent muted over the opaque base. `initial` so the --muted * default re-resolves under a scoped theme; a service retints by reading another role (e.g. * --primary). Defaults = hsl(var(--muted) / 0.4 striped \xB7 0.5 hover \xB7 0.3 selected)."},{name:"--table-row-selected-background",value:"initial",description:"Row-state tint washes \u2014 translucent muted over the opaque base. `initial` so the --muted * default re-resolves under a scoped theme; a service retints by reading another role (e.g. * --primary). Defaults = hsl(var(--muted) / 0.4 striped \xB7 0.5 hover \xB7 0.3 selected)."}];var u=[{number:1,title:"Storybook is mandatory",body:"Every primitive / shell / composite has a paired story under `src/stories/<group>/<Name>.stories.tsx` covering every variant + state on light + dark."},{number:2,title:"Tokens, not utilities",body:"Visual values come from CSS custom properties in `src/tokens/` + `src/styles/theme.css`. Token-named Tailwind utilities (`bg-background`) are fine; raw value utilities (`bg-blue-500`) are forbidden. (ADR-0003)"},{number:3,title:"Radix for interactive primitives",body:"Anything with keyboard / ARIA / portal wraps the relevant Radix primitive. (ADR-0001)"},{number:4,title:"shadcn-style ownership",body:"Primitives are thin wrappers; consumers can fork the source in place. (ADR-0002)"},{number:5,title:"One i18next singleton",body:"`initI18n()` in `src/i18n/index.ts` is THE instance; consumers extend via `addResourceBundle`. (ADR-0004)"},{number:6,title:"WCAG 2.1 AA baseline",body:"Every interactive primitive passes axe-core (keyboard nav, ARIA, focus-visible, 4.5:1 contrast, `prefers-reduced-motion`). Stories double as a11y test surfaces."},{number:7,title:"SemVer 2.0 + Keep a Changelog 1.1",body:"Every release-worthy change updates `CHANGELOG.md` under `## Unreleased` in the same PR."},{number:8,title:"Inclusive naming",body:"`allowlist` / `denylist`, `main` / `primary` / `replica` / `secondary`, `they/them`. Never `whitelist` / `blacklist` / `master` / `slave`. Lint-enforced."},{number:9,title:"No marketing speak",body:'Banned: "powerful", "robust", "blazing fast", "best-in-class", "seamless", "enterprise-grade". State what it does.'},{number:10,title:"English is canonical for docs",body:"Localised docs at `docs/i18n/<bcp47>/`; front-matter tracks staleness."},{number:11,title:"Submodule discipline",body:"Two-PR workflow: (1) submodule PR \u2192 `main`, (2) downstream PR \u2192 bump pin. Never push a pin to a SHA not on the submodule remote."},{number:12,title:"Branch + PR workflow",body:"`feat/<scope>` / `fix/<scope>` \u2192 submodule `main`. CI green + squash-merge. No direct push to `main`. `--no-verify` forbidden."},{number:13,title:"TypeScript strict",body:"Explicit types on every export. `forwardRef` for components; `ComponentPropsWithoutRef` for extension. No `any`. No `@ts-ignore` without comment + issue link."},{number:14,title:"Every third-party library is shadcn / Radix-recommended",body:"Locked stack: Radix UI, cmdk, sonner, lucide-react, react-aria-components + `@internationalized/date`, i18next + react-i18next, class-variance-authority + clsx + tailwind-merge. New peer \u2192 ADR documenting why it's the canonical choice."},{number:15,title:"No `@apply` re-encoding tokens",body:"Inside a primitive `.tsx`, don't `@apply` a Tailwind utility that re-encodes a token \u2014 reference the canonical CSS class from `tokens.css` instead. Composite token-named utilities remain fine."},{number:16,title:"CSS source-of-truth is `src/tokens/` + `src/styles/theme.css`",body:"A primitive that needs a new color / spacing / radius adds it there FIRST, then references it."},{number:17,title:"`src/stories/` \u2194 `src/components/` parity",body:"Story set matches primitive set under each group. CI-checked via `scripts/check-stories-parity.mjs`."},{number:18,title:"`docs/reference/<group>/` \u2194 `src/components/<group>/` parity",body:"Every primitive has a reference page; every page maps to a primitive. CI-checked via `scripts/check-docs-parity.mjs`."},{number:19,title:"No service-specific anything",body:'`me-service`, `forge-service`, `admin-service` never appear in source / comments / prop names. Per-deployment brand color lives at `[data-accent="<palette>"]`.'},{number:20,title:'No "platform-only" exports',body:"Every primitive ships via `package.json::exports`. Internal-only helpers stay un-exported."},{number:21,title:"Every component honours every theme axis",body:"`data-theme` (light / dark), `data-accent` (6 palettes), `data-density` (compact / default / comfortable), `data-font-size` (sm / base / lg / xl). Read from tokens, never hardcode values. Verify every PR via the Storybook toolbar sweep."},{number:22,title:"100% match to the design canon",body:'Every visual literal comes from `design-handoff/ui-system/<latest-bundle>/`. Token-pin canon literals; never substitute "close enough". If the bundle doesn\'t cover a case \u2014 STOP, ask the user to mock it.'},{number:23,title:"Concept-first prop API",body:"One concept per prop. Reuse shared vocabulary (`size`, `variant`, `color`, `tone`, `accent`, `padding`, `density`, `orientation`, `placement`, `current`, `value` / `defaultValue` / `onValueChange`, `open` / `defaultOpen` / `onOpenChange`, `justify`, `sticky`, `offset`). Before adding a new prop or token: grep for an existing one."},{number:24,title:"Mobile-first",body:"Defaults target `xs` (\u22650px); progressive enhancement via `sm:` / `md:` / `lg:` / `xl:` / `2xl:`. Touch targets \u2265 44 \xD7 44 px (`--touch-target-min`, does NOT scale with density). Runtime viewport via `useBreakpoint`, never `window.innerWidth`. Stories render at narrow viewport first."},{number:25,title:"Stories are docs; UI is the primitive",body:"When a story looks wrong, fix the primitive / CSS / token. Never paper over with a story tweak. Story-only diff without a paired primitive / CSS / token diff is rejected."},{number:26,title:"Library isolation",body:"`dist/` ships only the consumer surface. Storybook, tests, scripts, design-handoff, `dev-probe/` stay out of npm. Every `dependencies` entry is `external` in `tsup`. Verification via `pnpm pack` + grep of `dist/`."},{number:27,title:"Per-group folder structure",body:"Primitives at `src/components/<group>/<Name>.tsx`; six canonical groups (general, layout, data-display, data-entry, feedback, navigation). Barrel = `src/components/primitives.ts` (single file). Stories + reference docs mirror the same group hierarchy."},{number:28,title:"`src/` folder taxonomy",body:"Three classes: consumer surface (matched by `tsup` entry + `package.json::exports`), Storybook-only (`src/stories/`), build-input-only (`cn.ts`, per-group sources consumed via the barrel). No `src/lib/`, `src/utils/`, `src/internal/`, `src/clients/`, `src/screens/`. Service clients live with the composite that uses them."},{number:29,title:"Stories consume framework primitives only",body:"No raw `<button>` / `<input>` / hand-rolled chips when a primitive exists. HTML semantics (`<section>`, `<article>`, \u2026) for structure are fine. Inline `style={{}}` limited to layout / positioning; no colour / radius / typography overrides."},{number:30,title:"Story `render` returns JSX directly",body:"No opaque `<XyzDemo />` wrapper components, no zero-arg `Demo` helpers. Use `render: function StoryName() { \u2026 }` so Storybook's source panel shows runnable JSX, not `<XyzDemo />`."},{number:31,title:"No nested wrapper / convenience primitives",body:"One Radix base = one framework primitive. `<SimpleX>` over `<X>` is forbidden; add a prop to `<X>` instead. Composites under `src/components/composites/` that combine multiple primitives are NOT wrappers."},{number:32,title:"No redundant props",body:"Before adding a prop / item field / variant, grep the existing surface; if a field already covers the concept, use it. Top-level prop that re-expresses an item field (Timeline `pending` \u2194 `items[i].animate`) is rejected."},{number:33,title:"Stories / source / docs name-synchronized",body:"No two names for the same export across the framework surface; no legacy aliases in stories / docs (source may keep an alias for a deprecation cycle, but the marketing surfaces use the canonical name only). Rename PR runs `grep -rn '<oldName>' src docs` and clears it."},{number:34,title:"Storybook source panel = real, copy-paste-ready code",body:'Storybook\'s react-docgen serializer strips every function value (`cell: ({row}) => <JSX/>`, `render: ({field}) => <Input/>`, `rowClassName`, `renderItem`, \u2026) to `() => {}`. Any story whose `render` passes a function-valued prop, references a module-level helper (`Badge`, `EMPLOYEE_COLUMNS`, etc.), or uses a render-prop pattern MUST override `parameters.docs.source.code` with the literal copy-paste-ready snippet \u2014 type aliases, helper functions spelled out, column definitions with cell JSX visible, inline data array. The `render()` callback stays as-is (module-level constants are fine for runtime performance); `source.code` is the marketing surface. Skip ONLY for stories whose JSX is purely static primitives Storybook can serialize verbatim (`<Button variant="primary">Click</Button>`). The exemplar is `Table.Default` in `src/stories/data-display/Table.stories.tsx`.'},{number:35,title:"Status chips never wrap",body:"A `Badge` / `Badge` reads as one atomic unit. Its label must never break across lines \u2014 pin `white-space: nowrap` on the chip (done in `badge-layout.css`), especially inside narrow `DataTable` cells (\u30B9\u30B3\u30FC\u30D7 / \u30B9\u30C6\u30FC\u30BF\u30B9 columns). If a cell is too tight, widen the column or shorten the label; never let the chip wrap."},{number:36,title:"Badge tone/icon are the colour escape hatch",body:"`Badge` auto-maps a fixed set of English lifecycle keys (active, draft, pending, scheduled, cancelled, failed, \u2026) to tone + icon. For ANY other value \u2014 localized labels (\u516C\u958B\u4E2D, \u30A2\u30AF\u30C6\u30A3\u30D6) or categorical tiers (\u4F1A\u54E1\u30E9\u30F3\u30AF, \u5951\u7D04\u30D7\u30E9\u30F3) \u2014 pass `tone` explicitly (success | warning | destructive | info | neutral) and, for non-lifecycle tiers, `icon={null}` to drop the misleading glyph. Don't let domain labels fall back to neutral grey + \u25CB. Map domain\u2192tone in the CONSUMER layer; the framework only provides the props."},{number:37,title:"DataTable is full-width \u2014 never inside a narrow grid column",body:"A multi-column `DataTable` occupies its OWN row at the page's full width: `<Card><CardContent flush><DataTable \u2026/></CardContent></Card>`. Never nest it in a `lg:col-span-2` of a `ResponsiveGrid columns={3}` beside a chart \u2014 the columns get squeezed until CJK text collapses to one character per line. Charts / KPI cards go in their own row ABOVE the table. (See the `inertia-list-page` pattern.)"},{number:38,title:"FilterBar stays OUT of CardContent flush",body:"`CardContent flush` strips horizontal padding for edge-to-edge tables. A `FilterBar` placed inside it loses all padding and sticks to the card edge. Render `FilterBar` as a STANDALONE block above the table card; wrap ONLY the `DataTable` / `EmptyState` in the `Card` + `CardContent flush`. Order on a list page: KPIs \u2192 FilterBar \u2192 table card."},{number:39,title:"Long text columns get an explicit width",body:"For columns whose value can be long (name / title / segment / address), set `col.width` to a Tailwind width class (e.g. `w-64`, `w-48`) so the column reserves space instead of shrinking and wrapping to many lines; leave numeric / status columns auto. Table cells default to `white-space: nowrap`, so an over-tight table scrolls horizontally rather than crushing \u2014 give the important columns real widths so the default layout reads well before any scroll."},{number:40,title:"Pages are mobile-first",body:'Author and verify every page at 320\u2013390px FIRST. Spacing comes only from `Flex` `gap` (vertical rhythm = `Flex direction="col"`, control rows = the default `direction="row"`) + `ResponsiveGrid columns={2|3|4}` (which collapse to a single column on narrow screens) \u2014 never raw `p-*` / `gap-*` / `space-*` utilities for page layout. Wide tables scroll horizontally on small screens (don\'t force-fit them); dialogs and sheets are full-height on mobile. Touch targets \u2265 44\xD744px.'},{number:41,title:"Drawer & dialog footer layout",body:'Sheet/Dialog/AlertDialog footers are a pinned action bar (Ant Design Drawer footer): the footer sticks to the bottom, SheetFooter draws a full-bleed top border, and actions are RIGHT-aligned with the PRIMARY button rightmost (Cancel/secondary to its left). A destructive / clear / reset action goes far-LEFT \u2014 give that button `className="mr-auto"`. NEVER stack footer buttons full-width or center them.'},{number:42,title:"Props & Tokens Before Customization",body:"Before reaching for a Tailwind class, inline `style`, or extra CSS, you MUST first check whether the component already supports the need via a PROP, a design TOKEN, or a layout/typography PRIMITIVE. godx-ui is meant to be enough on its own (Ant-Design-style): `className` is for genuine one-offs only \u2014 never to redo what an API already does. Specifically: (1) NEVER hand-roll typography \u2014 no `text-[13px]`/`text-[11px]` arbitrary px (bypasses the golden type scale), no `font-medium`/`font-semibold`/`text-muted-foreground` on a raw `<span>`; use `<Text size tone weight tabular mono>` / `<Heading level>`. (2) NEVER hand-roll a trivial flex/grid wrapper; use `<Flex>` / `<ResponsiveGrid>` / `<PageContainer>`. (3) NEVER set a control's radius/height/colour with a utility when a `shape`/`size`/`tone`/token exists. If a real need has NO prop/token/primitive, that is a library GAP \u2014 file it (draft_bug_report), don't paper over it with ad-hoc Tailwind."},{number:43,title:"Every form control goes through FormField",body:"Consumers MUST wrap every labelled form control (Input, Select, DatePicker, DateRangePicker, NumberInput, Radio.Group, Checkbox groups, range pairs, ...) in FormField \u2014 it owns the label (aria-labelledby, never a dangling <label for>), auto-generates/injects the control id, and wires aria-describedby/aria-errormessage/aria-invalid. Bare controls are the rare exception (e.g. a toolbar quick-filter with its own aria-label) and must carry id/name + aria-label themselves. Never hand-roll a label+control stack with Text/Label."},{number:44,title:"Chrome is a token, default quiet",body:"Any decorative chrome a component draws \u2014 dividers, separator borders, and the padding that exists only to space that chrome \u2014 MUST read a token; never hard-code it in `src/styles/*.css` (a hard-coded `border-bottom: 1px solid hsl(var(--border))` leaves consumers no off-switch short of a variant fork). The DEFAULT is the quietest state (`none` / balanced rhythm); a service theme opts IN, e.g. `--page-header-divider: 1px solid hsl(var(--border))`. Born from real consumption: PageContainer's header divider was undisableable until tokenised."},{number:45,title:"Every service-tunable constant gets a knob",body:'When component CSS encodes a geometry choice that a service plausibly re-tunes to match its design handoff \u2014 form label column width, label\u2194control gap, header insets \u2014 it MUST be a documented component token (current value as the default). The theme sets it ONCE globally; props (`labelWidth`) override per instance; Form\u2192FormField priority stays intact. The test: "would a service theme.css want to change this to match its design grid?" If yes and the only route is forking CSS, that is a library gap \u2014 fix the library, don\'t patch the app. Born from real consumption: `--form-label-width` / `--form-label-gap` (design spec said 110px/8px; the values were prop-only and hard-coded `--space-4`).'},{number:46,title:"Typography is tokens, default is base",body:"A UI framework gives consumers knobs: every font-size in `src/styles/*.css` MUST reference a token \u2014 the global modular scale `var(--font-size-{2xs|xs|sm|base|lg|xl|2xl})` or a per-component `var(--{component}-\u2026-font-size)` knob (rule #45) \u2014 never a hard-coded literal (`font-size: 12px` can't be re-themed). The DEFAULT body size is `--font-size-base`; components render body/UI text at `base`, not at the `sm` alias. Smaller-by-design text (badge, section label, caption) is a component token defaulting to a small step (`--badge-font-size: var(--font-size-xs)`), so a service re-tunes that part without moving the global scale. The `sm`/`xs` tokens stay for the explicit `<Text size>` API. Every component token is surfaced in the MCP `get_component` output (check:mcp-token-sync). Enforced by `check:typography`."}];function S(a){return u.find(t=>t.number===a)}var g=[{name:"common-fixes",tagline:"Fix the most common @godxjp/ui consumer mistakes & visual bugs (StatCard double-border, grey Badge, crushed/empty table headers, washed-out sidebar footer, Inertia layout crash, SSR hydration). Before \u2192 after.",tags:["fixes","migration","bug","cardstat","statusbadge","datatable","sidebar","gotcha","review"],code:`// \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
|
|
1848
|
+
/>`,storyPath:"charts/PieChart.stories.tsx",rules:[]},{name:"CommandPalette",group:"data-entry",tagline:"Searchable command dialog with controlled or uncontrolled open state and consumer-owned selection.",props:[{name:"groups",type:"CommandPaletteGroup[]",required:!0,description:"Grouped command items."},{name:"labels",type:"CommandPaletteLabels",required:!0,description:"Localized dialog and search copy."},{name:"onSelect",type:"(item: CommandPaletteItem) => void",required:!0,description:"Consumer-owned selection handler."},{name:"open",type:"boolean",description:"Controlled open state."},{name:"defaultOpen",type:"boolean",defaultValue:"false",description:"Initial uncontrolled open state."},{name:"onOpenChange",type:"(open: boolean) => void",description:"Open-state callback."},{name:"loading",type:"boolean",defaultValue:"false",description:"Shows the supplied loading content."},{name:"error",type:"ReactNode",description:"Consumer-supplied error content."}],usage:["Provide localized labels and real command groups; the component does not fetch commands.","Use either `open` plus `onOpenChange` or `defaultOpen`; do not mirror both state models."],example:`import { CommandPalette } from "@godxjp/ui/data-entry";
|
|
1849
|
+
|
|
1850
|
+
<CommandPalette
|
|
1851
|
+
groups={[{ id: "pages", label: "Pages", items: [{ id: "home", label: "Home" }] }]}
|
|
1852
|
+
labels={{ open: "Open commands", title: "Commands", description: "Choose a command", placeholder: "Search", empty: "No results" }}
|
|
1853
|
+
onSelect={(item) => navigate(item.id)}
|
|
1854
|
+
/>`,storyPath:"data-entry/CommandPalette.stories.tsx",rules:[]},{name:"TwoFactorSetup",group:"feedback",tagline:"Canonical two-factor enrollment dialog for QR/manual-key verification and recovery-code acknowledgement.",props:[{name:"open",type:"boolean",required:!0,description:"Controlled dialog state."},{name:"onOpenChange",type:"(open: boolean) => void",required:!0,description:"Dialog state callback."},{name:"manualKey",type:"string",required:!0,description:"Consumer-provided enrollment secret."},{name:"qrValue",type:"string",description:"Optional QR payload for the same enrollment secret."},{name:"code",type:"string",required:!0,description:"Current verification code."},{name:"onCodeChange",type:"(code: string) => void",required:!0,description:"Verification-code callback."},{name:"onConfirm",type:"() => void",required:!0,description:"Consumer-owned verification action."},{name:"recoveryCodes",type:"string[]",description:"Codes shown only after successful enrollment."},{name:"onAcknowledge",type:"() => void",required:!0,description:"Recovery-code acknowledgement action."},{name:"labels",type:"TwoFactorSetupLabels",required:!0,description:"All localized copy."},{name:"pending",type:"boolean",defaultValue:"false",description:"Disables actions while the consumer request runs."}],usage:["Keep enrollment, verification, persistence, and secret lifecycle in the consumer; this component is presentational.","Only pass recovery codes after verification succeeds, and clear them when the dialog lifecycle ends."],example:`import { TwoFactorSetup } from "@godxjp/ui/feedback";
|
|
1855
|
+
|
|
1856
|
+
<TwoFactorSetup
|
|
1857
|
+
open={open}
|
|
1858
|
+
onOpenChange={setOpen}
|
|
1859
|
+
qrValue={enrollment.qr}
|
|
1860
|
+
manualKey={enrollment.secret}
|
|
1861
|
+
code={code}
|
|
1862
|
+
onCodeChange={setCode}
|
|
1863
|
+
onConfirm={verify}
|
|
1864
|
+
recoveryCodes={recoveryCodes}
|
|
1865
|
+
onAcknowledge={finish}
|
|
1866
|
+
labels={labels}
|
|
1867
|
+
/>`,storyPath:"feedback/TwoFactorSetup.stories.tsx",rules:[]},{name:"AuthDivider",group:"layout",tagline:"Localized auth-form divider with equal rules and an accessible separator label.",props:[{name:"label",type:"string",required:!0,description:"Localized conjunction such as \u201Cor\u201D."},{name:"className",type:"string",description:"Optional structural class override."}],example:`import { AuthDivider } from "@godxjp/ui/layout";
|
|
1868
|
+
|
|
1869
|
+
<AuthDivider label="or" />`,storyPath:"layout/AuthDivider.stories.tsx",rules:[]},{name:"AuthFooter",group:"layout",tagline:"Compact hosted-auth footer for consumer-owned product, legal, privacy, and locale content.",props:[{name:"product",type:"ReactNode",required:!0,description:"Product identity content."},{name:"terms",type:"ReactNode",required:!0,description:"Terms link or localized text."},{name:"privacy",type:"ReactNode",required:!0,description:"Privacy link or localized text."},{name:"locale",type:"ReactNode",description:"Optional consumer-owned locale control."},{name:"className",type:"string",description:"Optional structural class override merged onto the footer root."}],usage:["Pass real links and locale controls from the consumer; AuthFooter never invents navigation.","DO drop it into AuthShell's `footer` slot \u2014 that slot supplies the contentinfo landmark, so AuthFooter itself renders a plain div and can also sit inside an existing footer without nesting landmarks.","AuthFooter owns ONLY the geometry: mono ramp, wrap, and a `\xB7` separator between the slots that are actually PRESENT (omit `locale` and its separator disappears). Don't hand-write separators into the slot content.","Public type: `AuthFooterProp` (alias `AuthFooterProps`) from `@godxjp/ui/layout` \u2014 registered in the prop registry, not a local interface.","Retune the line through `--auth-footer-content-gap` / `--auth-footer-text-font-size`, never page CSS (rule #45)."],example:`import { AuthFooter } from "@godxjp/ui/layout";
|
|
1870
|
+
|
|
1871
|
+
<AuthFooter product="Acme ID" terms={<a href="/terms">Terms</a>} privacy={<a href="/privacy">Privacy</a>} locale="English" />`,storyPath:"layout/AuthFooter.stories.tsx",rules:[45]},{name:"AuthIdentity",group:"layout",tagline:"Hosted-auth identity heading with the shared mark and optional requesting-client context.",props:[{name:"title",type:"ReactNode",required:!0,description:"Primary auth heading."},{name:"requester",type:"ReactNode",description:"Optional real requesting-client context."},{name:"className",type:"string",description:"Optional structural class override merged onto the identity root."}],usage:["Only show `requester` when the consumer has authoritative client context.",'It ALREADY renders the canonical brand-green GoDX mark (`Logo mark="godx" tone="success"`, independent of --primary) plus the page h1 \u2014 don\'t add a second Logo or heading above it.',"Centring and rhythm are token-owned (`--auth-identity-gap` / `--auth-requester-*`); no page CSS (rule #45).","Public type: `AuthIdentityProp` (alias `AuthIdentityProps`) from `@godxjp/ui/layout` \u2014 registered in the prop registry, not a local interface."],example:`import { AuthIdentity } from "@godxjp/ui/layout";
|
|
1872
|
+
|
|
1873
|
+
<AuthIdentity title="Sign in" requester="Acme Portal is requesting access" />`,storyPath:"layout/AuthIdentity.stories.tsx",rules:[45]},{name:"AuthAccountSummary",group:"layout",tagline:"Compact signed-in account row for hosted auth: avatar fallback, authoritative email and switch action.",props:[{name:"email",type:"string",required:!0,description:"Authoritative signed-in email."},{name:"avatarSrc",type:"string",description:"Optional real avatar URL."},{name:"avatarFallback",type:"ReactNode",description:"Optional localized/text fallback; defaults to a user glyph."},{name:"actionLabel",type:"ReactNode",required:!0,description:"Localized visible switch-account action label."},{name:"onAction",type:"() => void",required:!0,description:"Consumer-owned account-switch navigation/action."},{name:"disabled",type:"boolean",description:"Disable the action while the consumer cannot switch."},{name:"className",type:"string",description:"Optional structural class override."}],usage:["Pass only the authoritative signed-in email; AuthAccountSummary never fetches or invents identity data.","Pass localized actionLabel and the existing account-switch handler. The component owns no route, mutation or permission behavior.","The package owns avatar fallback, long-email truncation, responsive action wrapping and keyboard focus. Retune only through --auth-account-summary-* tokens."],useCases:["OAuth device consent and authorization cards where the user must confirm or switch the signed-in account.","Hosted consent screens that need a compact identity confirmation row without a second profile card."],related:["AuthIdentity \u2014 page identity heading and requesting-client context; use AuthAccountSummary inside the card for the signed-in user.","Avatar \u2014 use Avatar directly for general profile surfaces; AuthAccountSummary owns only the compact hosted-auth row."],example:`import { AuthAccountSummary } from "@godxjp/ui/layout";
|
|
1874
|
+
|
|
1875
|
+
<AuthAccountSummary email={user.email} actionLabel={t("auth.switchAccount")} onAction={switchAccount} />`,storyPath:"layout/AuthAccountSummary.stories.tsx",rules:[45]},{name:"AuthStack",group:"layout",tagline:"Token-spaced vertical stack for direct sections inside a canonical auth card.",props:[{name:"children",type:"ReactNode",description:"Auth sections in visual order."},{name:"className",type:"string",description:"Optional structural class override."}],example:`import { AuthStack } from "@godxjp/ui/layout";
|
|
1876
|
+
|
|
1877
|
+
<AuthStack><PasskeyAction /><CredentialsForm /></AuthStack>`,storyPath:"layout/AuthStack.stories.tsx",rules:[]},{name:"OrgSwitcher",group:"layout",tagline:"Searchable organization switcher with canonical trigger geometry, desktop popover, and mobile sheet.",props:[{name:"organizations",type:"readonly OrgSwitcherOrganization[]",required:!0,description:"Consumer-provided organization choices."},{name:"value",type:"string",description:"Selected organization id."},{name:"onValueChange",type:"(value: string) => void",description:"Selection callback; persistence remains consumer-owned."},{name:"labels",type:"OrgSwitcherLabels",required:!0,description:"Localized trigger, search, state, and retry copy."},{name:"collapsed",type:"boolean",defaultValue:"false",description:"Compact trigger presentation."},{name:"disabled",type:"boolean",defaultValue:"false",description:"Disables the trigger."},{name:"loading",type:"boolean",defaultValue:"false",description:"Shows the loading state."},{name:"error",type:"ReactNode",description:"Consumer-supplied error state."},{name:"onRetry",type:"() => void",description:"Consumer-owned retry callback."},{name:"responsive",type:'"auto" | "popover" | "sheet"',defaultValue:'"auto"',description:'Responsive presentation contract. "auto" resolves through the SHARED Sheet hook useSheetResponsiveMode(): desktop popover above --sheet-responsive-breakpoint-width (48rem/768px), focus-trapped bottom Sheet at/below it. Move that one token to move the line for every overlay.'},{name:"open",type:"boolean",description:"Controlled open state."},{name:"onOpenChange",type:"(open: boolean) => void",description:"Open-state callback."}],usage:["Provide only organizations the current user may select; the component does not authorize or fetch tenants.","Keep persistence and navigation in `onValueChange`; use loading/error props for the real query state.","DO put a plan/status affordance in `organization.badge` (a <Badge>) instead of stuffing it into `meta` \u2014 the badge is end-aligned in the expanded trigger and in the menu row, and hidden in the collapsed rail. ALWAYS pair it with a localized `organization.badgeLabel`: the trigger's accessible name comes from `labels.trigger`, so the badge is announced as an aria-describedby DESCRIPTION and the raw node is marked presentational (WCAG 1.1.1). `badgeLabel` also becomes a search keyword.",'DON\'T wrap OrgSwitcher in your own media query to pick popover vs sheet \u2014 `responsive="auto"` already reads the shared --sheet-responsive-breakpoint-width token.'],example:`import { OrgSwitcher } from "@godxjp/ui/layout";
|
|
1878
|
+
import { Badge } from "@godxjp/ui/data-display";
|
|
1879
|
+
|
|
1880
|
+
<OrgSwitcher
|
|
1881
|
+
organizations={[
|
|
1882
|
+
{
|
|
1883
|
+
id: "dxs",
|
|
1884
|
+
name: "DXS Holdings",
|
|
1885
|
+
meta: t("org.role.owner"),
|
|
1886
|
+
badge: <Badge variant="secondary">{t("org.plan.trial")}</Badge>,
|
|
1887
|
+
badgeLabel: t("org.plan.trial.sr"),
|
|
1888
|
+
},
|
|
1889
|
+
]}
|
|
1890
|
+
value={organizationId}
|
|
1891
|
+
onValueChange={setOrganizationId}
|
|
1892
|
+
labels={labels}
|
|
1893
|
+
/>`,storyPath:"layout/OrgSwitcher.stories.tsx",rules:[]},{name:"FilterBar",group:"navigation",tagline:"Domain-neutral list-page filter toolbar with optional clear action and labelled groups.",props:[{name:"children",type:"ReactNode",description:"Filter controls and groups."},{name:"onClear",type:"() => void",description:"Consumer-owned clear action."},{name:"hasActiveFilters",type:"boolean",defaultValue:"true",description:"Shows clear only when filters are active."},{name:"sticky",type:"boolean",defaultValue:"false",description:"Uses the token-owned sticky presentation."},{name:"overflow",type:"'wrap' | 'scroll'",defaultValue:"'wrap'",description:"Responsive overflow strategy (gh#216). 'wrap': stacked column below 640px, wrapping rows above. 'scroll': one bounded inline-scrolling row above 640px (still stacked below) with the clear-all action pinned at the inline end. The geometry is entirely token/CSS owned \u2014 never re-implement it in the page."},{name:"className",type:"string",description:"Optional structural class override."}],usage:["Compose real controls as children; filter state and URL synchronization remain consumer-owned.","Give each FilterBarGroup a `controlId` matching its single control's `id` (gh#216) so the visible caption is that control's real <label>; otherwise the control is nameless to a screen reader.","Reach for `overflow='scroll'` on filter-heavy list pages so a long JA/EN/VI label set never grows the strip into multiple rows and pushes the table below the fold."],example:`import { FilterBar, FilterBarGroup } from "@godxjp/ui/navigation";
|
|
1894
|
+
|
|
1895
|
+
<FilterBar overflow="scroll" onClear={clearFilters} hasActiveFilters={hasFilters}>
|
|
1896
|
+
<FilterBarGroup label="Status" controlId="f-status">
|
|
1897
|
+
<StatusSelect id="f-status" />
|
|
1898
|
+
</FilterBarGroup>
|
|
1899
|
+
</FilterBar>`,storyPath:"navigation/FilterBar.stories.tsx",rules:[]}];function O(t){let a=t.trim().toLowerCase();return v.find(e=>e.name.toLowerCase()===a)}function F(t){return v.filter(a=>a.group===t)}var k=[{name:"ValueProp<T = string>",concept:"Abstract controlled value.",values:["generic"],usedBy:["CheckboxGroup","Upload","Cascader","TreeSelect","Tabs","SearchSelect"]},{name:"DefaultValueProp<T = string>",concept:"Abstract uncontrolled initial value.",values:["generic"],usedBy:["CheckboxGroup","Upload","Cascader","TreeSelect","Tabs"]},{name:"OnValueChangeProp<T = string>",concept:"Callback for abstract value changes. DOM events continue to use onChange.",values:["(value: T) => void"],usedBy:["CheckboxGroup","Upload","Cascader","TreeSelect","Transfer","settings pickers"]},{name:"OpenProp / DefaultOpenProp / OnOpenChangeProp",concept:"Disclosure state.",values:["boolean","(open: boolean) => void"],usedBy:["Dialog","Sheet","Popover"]},{name:"SizeProp",concept:"Shared public size names.",values:["xs","sm","md","lg"],usedBy:["Button","Steps","Switch"],notes:"Component-specific subsets must be documented. Old alias small is sm."},{name:"ToneProp",concept:"Semantic status/color intent.",values:["default","success","warning","destructive","info","muted","neutral"],usedBy:["Badge","Alert"],notes:"Status values belong in tone, not variant."},{name:"GapProp",concept:"Shared layout gap scale.",values:["xs","sm","md","lg","xl"],usedBy:["Flex"],notes:'The single shared gap scale on Flex \u2014 the one layout primitive (default direction="row"; use direction="col" for vertical rhythm; these replaced the removed Stack/Inline).'},{name:"TitleProp",concept:"Primary heading text.",values:["React.ReactNode"],usedBy:["PageContainer","PageHeader","EmptyState","Dialog"]},{name:"DensityProp",concept:"Page/subtree density.",values:["compact","default","comfortable"],usedBy:["PageContainer"]},{name:"CenteredShellPresetProp",concept:"Named whole-page SHELL contract for CenteredShell \u2014 the page measure, section rhythm, chrome flatness and hero heading tier, all owned by component tokens.",values:["default","public-landing"],usedBy:["CenteredShell"],notes:'public-landing is the PUBLIC marketing/product landing geometry (gh#252): one 67.5rem measure shared by the header bar, the centred column and the footer, the section rhythm, flat elevation-free cards and the hero h1 tier. "default" emits NO data-preset at all, so the existing shell box is untouched. Pair with `Flex hideBelow`/`hideFrom` for header region visibility \u2014 a landing must never own a page-local media query or a max-width wrapper.'},{name:"TablePresetProp",concept:"Named collection contract for a table \u2014 how a dense collection behaves when its container gets narrow.",values:["default","action-collection"],usedBy:["Table","DataTable"],notes:"action-collection (gh#253) swaps the desktop intrinsic column widths for table-layout: fixed + the token-owned column PRIORITY measures below `collapseBelow`, so a five-column approval queue keeps every column \u2014 including row actions \u2014 inside a 390px frame with no page-local CSS, no hidden column and no horizontal scroll. It changes the SIZING model only: no display change, no role rewriting, no card transformation, so table semantics and screen-reader navigation are identical at every width. ONE contract, two entry points: the `Table` primitive (mark `priority` on each TableHead/TableCell yourself) and the TanStack `DataTable` (mark `priority` on the ColumnDef; it stamps both cells for you, and additionally releases the surface's `--table-surface-min-inline-size` floor). Both read the same `--table-action-collection-*` tokens \u2014 there is no parallel DataTable family."},{name:"TableColumnPriorityProp",concept:"Relative importance of a table column, used by the action-collection preset to allocate the narrow-frame measure.",values:["primary","secondary","meta","actions"],usedBy:["Table","DataTable"],notes:"On the raw `Table`, set it on BOTH the TableHead and the TableCell of a column; on `DataTable` set it once on the `ColumnDef` and it is stamped onto the <th> and every <td>. `actions` reserves its measure first so the row-action affordance can never be pushed outside the viewport; a column with no priority takes the remaining space (the free-text column)."},{name:"AuthShellPresetProp",concept:"Named hosted-identity FLOW MEASURE for AuthShell \u2014 the card max-width plus the desktop and mobile page gutters, all owned by component tokens.",values:["default","device-authorization","context-selection"],usedBy:["AuthShell"],notes:'Orthogonal to AuthShell\'s `variant` (which owns control density + heading size) and applied after it, so `variant="canonical" preset="device-authorization"` composes. device-authorization = 380px card / 5px inline gutter at 390 (gh#220); context-selection = 25rem card, edge-to-edge on mobile (gh#217). Selecting a preset REPLACES any consumer-side --auth-shell-card-max-width override \u2014 a page-local width is the anti-pattern these presets exist to remove.'},{name:"AvatarShapeProp",concept:"Avatar geometry \u2014 WHAT the identity mark represents: a person or an entity. Deliberately NOT the control ShapeProp (default|pill|sharp), whose `sharp` is --radius-sharp: 0 and cannot express the rounded rect an entity mark needs.",values:["circle","square"],usedBy:["Avatar"],notes:'circle (default, inert) = the round --radius-pill person avatar on the muted surface; square = the entity-header organization/service mark \u2014 a compact rounded square on the brand surface driven by --avatar-square-{radius,size,background,foreground} (gh#249). Choose by meaning, never by look: a className="rounded-md bg-primary" override on the call site is the anti-pattern this prop removes.'}];function B(t){let a=t.trim().toLowerCase().replace(/prop(?:<.*>)?$/i,"");return k.find(e=>e.name.toLowerCase().replace(/prop(?:<.*>)?$/i,"")===a)}var C=[{name:"--wa-*",category:"primitive",tier:"primitive",role:"Neutral decorative Japanese accent primitives for charts/tags/decoration only."},{name:"--chart-1..6",category:"primitive",tier:"primitive",role:"Neutral decorative chart series palette. The @godxjp/ui/charts components (LineChart/BarChart/AreaChart/PieChart) read these by series index automatically \u2014 a service rethemes every chart at once by overriding --chart-1..6; per-series/per-slice overrides go through the component's series.color / colors props."},{name:"--space-0..12",category:"primitive",tier:"primitive",role:"Raw spacing scale."},{name:"--font-size-*",category:"primitive",tier:"primitive",role:"Raw typography scale."},{name:"--font-sans-base",category:"semantic",tier:"semantic",role:'The default sans face. FONT-AGNOSTIC by default (pure system stack) \u2014 the library ships no hardcoded brand font. Override this for one face everywhere. --font-family-sans defaults to it; --font-family-display / --font-family-body default to --font-family-sans (override those for a dual display+body brand). Consumers supply the actual @font-face (next/font, @fontsource, self-host); the opt-in @godxjp/ui/styles/fonts OVERWRITES this (and --font-sans-vi) with the bundled DXS stack \u2014 "M PLUS 2" primary (incl. the Vietnamese coverage) with "Noto Sans JP" as the CJK fallback. BUNDLE CHANGED IN v18: v16 bundled Noto Sans JP + Montserrat, v18 bundles M PLUS 2 + Noto Sans JP \u2014 if a design spec names the v16 faces, set the tokens explicitly instead of relying on the bundle. IMPORT ORDER IS LOAD-BEARING: styles/fonts must be imported AFTER styles/base, because foundation.css declares this system-stack default on :root at the same specificity (0,1,0) and the later declaration wins (the all-in-one @godxjp/ui/styles entry already orders it correctly \u2014 issue #210).'},{name:"--font-sans-{ja,ko,vi,zh-hans,zh-hant}",category:"semantic",tier:"semantic",role:'Per-language font SLOT tokens. styles/base.css wires each [lang] to read its slot with --font-sans-base as fallback, so a consumer switches a locale\'s face by setting e.g. `--font-sans-ja: "Noto Sans JP", var(--font-sans-base)` \u2014 NO [lang] selectors to write. Empty by default. zh-hans matches lang zh|zh-Hans|zh-CN; zh-hant matches zh-Hant|zh-TW. Deciding which @font-face ships on which locale route is the consumer\'s font pipeline, not the library. The opt-in @godxjp/ui/styles/fonts only fills --font-sans-vi (with the bundled "M PLUS 2", "Noto Sans JP" stack); the other slots stay empty and fall back to --font-sans-base.'},{name:"--duration-{fast,base,slow}",category:"primitive",tier:"primitive",role:"Motion durations (150 / 250 / 500ms). Read these instead of a literal `0.5s` for enter/transition timing (rule #2). the reference design keeps motion short; honour `prefers-reduced-motion` at the call site."},{name:"--ease-{standard,emphasized,decelerate,accelerate}",category:"primitive",tier:"primitive",role:"Motion easing curves. `standard` for most transitions, `emphasized` for entrances/overlays (the vaul drawer curve), `decelerate` for settling in, `accelerate` for exits. Read instead of a literal `cubic-bezier(\u2026)`."},{name:"--reveal-distance",category:"primitive",tier:"primitive",role:"Distance (10px) a revealed element travels on enter (translateY/-X). Read instead of a literal `translateY(10px)` for staggered reveals."},{name:"--reveal-stagger-step",category:"primitive",tier:"primitive",role:"One step (60ms) of the Reveal stagger ladder \u2014 `<Reveal delay={n}>` waits n \xD7 this before entering, so a column of reveals cascades. Read instead of a literal `.04s`/`.09s` per-item delay."},{name:"--shadow-color",category:"primitive",tier:"primitive",role:"RGB channels (space-separated, e.g. `12 26 49`) that tint the WHOLE elevation ramp (--shadow-xs..2xl) at once. Default `0 0 0`. Single-brand :root only \u2014 a scoped override does not re-resolve the ramp (the steps compute at :root); for a scoped/multi-tenant card lift set --card-shadow to a literal value."},{name:"--shadow-glow",category:"primitive",tier:"primitive",role:"Opt-in brand GLOW halo layered on the primary CTA's resting shadow. Default invisible (`0 0 0 0 transparent`, valid inside a comma shadow list). A service sets the whole value, e.g. `--shadow-glow: 0 8px 20px hsl(var(--primary) / .32)` \u2014 works scoped under [data-tenant] because the service declares it inside the scope."},{name:"--focus-ring-color",category:"primitive",tier:"primitive",role:"Themeable hue of EVERY keyboard-focus ring (HSL components, default var(--ring)). A leaf token, so it re-resolves at the element that paints the ring \u2014 override it once, even scoped under [data-tenant], to retint all focus rings. Pair with --focus-ring-width."},{name:"--focus-ring-width",category:"primitive",tier:"primitive",role:"Thickness (2px) of the solid keyboard-focus ring. Leaf token \u2014 :focus-visible rules read it directly as `0 0 0 var(--focus-ring-width) hsl(var(--focus-ring-color))`, never via an intermediate composite (which would freeze at :root). Propagates scoped."},{name:"--gradient-{brand,hero,glow}",category:"primitive",tier:"primitive",role:"Opt-in decorative gradient fills, default `none`. --gradient-hero paints the PageContainer header (hero banner); --gradient-glow paints the AppShell .app-main (ambient brand wash); --gradient-brand is a spare. A service sets the full gradient, e.g. `--gradient-glow: radial-gradient(60% 80% at 50% 0%, hsl(var(--primary) / .25), transparent)`."},{name:"--primary",category:"semantic",tier:"semantic",role:"Action color role."},{name:"--brand / --brand-foreground",category:"semantic",tier:"semantic",role:'The IDENTITY role \u2014 the canonical GoDX emerald (light #009766 = oklch(0.595 0.137 162.94); dark lifts lightness only, #00b87c). It is what `<Logo mark="godx">` / `tone="success">` / the godx wordmark / the `@godxjp/ui/email` brand capsule paint, and it is distinct from THREE lookalikes: --primary (the ACTION colour \u2014 re-theme it freely, the mark never moves), --success (\u82E5\u7AF9 #68be8d, the STATUS green for badges/progress/"saved" text \u2014 the mark borrowed this before gh#250 and shipped \u0394E76 \u2248 17.5 off canonical), and --text-brand (the blue brand-NUMERAL text slot behind the Tailwind `text-brand` utility). There is no `bg-brand` utility: `--color-brand` is already taken by --text-brand, so identity surfaces read `hsl(var(--brand))` directly in component CSS. Re-brand a service by overriding --brand once (scoped `[data-tenant]` works \u2014 every logo knob resolves it at the call site).'},{name:"--success",category:"semantic",tier:"semantic",role:'Success STATUS role (\u82E5\u7AF9 wakatake #68be8d) \u2014 badges, alerts, progress fills, timeline done dots, `tone="success"` text. NOT the product identity: the GoDX mark reads --brand (gh#250).'},{name:"--warning",category:"semantic",tier:"semantic",role:"Warning status role."},{name:"--destructive",category:"semantic",tier:"semantic",role:"Destructive/error status role."},{name:"--info",category:"semantic",tier:"semantic",role:"Information status role."},{name:"--attention",category:"semantic",tier:"semantic",role:"Attention status role."},{name:"--page-header-divider",category:"semantic",tier:"semantic",role:"PageContainer header bottom divider. Default none; a service theme opts in with `1px solid hsl(var(--border))`."},{name:"--overlay-background",category:"semantic",tier:"semantic",role:"Modal scrim \u2014 the single backdrop colour shared by every overlay (Dialog, AlertDialog, Sheet, Drawer, AppShell mobile nav). Default `rgb(0 0 0 / 0.5)`. A service tints it once, e.g. a navy `rgb(12 26 49 / .55)`. HOW IT REACHES AN OVERLAY (gh#215 \u2014 until v18 it reached NOTHING; every overlay carried a private literal and this token was dead): each surface declares its own `--*-overlay-background` knob `initial` and resolves a SHARE of this colour at the call site, `color-mix(in srgb, var(--overlay-background) var(--*-overlay-alpha), transparent)`, so the calibrated per-surface depth survives (Sheet/drawer 40% = 0.2, Dialog 60% = 0.3) while ONE override here retints them all. NOTE: portaled overlays render outside a [data-tenant] subtree, so for multi-tenant scoping put the tenant attribute on the portal container too."},{name:"--page-header-pad-bottom",category:"semantic",tier:"semantic",role:"PageContainer header bottom inset. Defaults to page top padding minus the section gap so the title band is vertically balanced."},{name:"--master-detail-rail-{compact,standard}",category:"semantic",tier:"semantic",role:"MasterDetail fixed rail track \u2014 18.75rem (300px) / 20rem (320px). The `railWidth` prop picks the preset; a service theme retunes the presets themselves. Consumers never author a grid track."},{name:"--master-detail-gap",category:"semantic",tier:"semantic",role:"Gap between the MasterDetail master and detail regions (default --space-stack-md), side by side and stacked."},{name:"--master-detail-collapse-below",category:"semantic",tier:"semantic",role:"MasterDetail stacking threshold (default 40rem / the `sm` step), measured against the composition's OWN inline size, not the viewport: wider keeps the split, narrower stacks master above detail. A real knob \u2014 the layout resolves it inside a flex-basis calc(), because a media/container-query CONDITION cannot read a var(). The `collapseBelow` prop overrides it per instance (rule #45)."},{name:"--master-detail-master-viewport-{compact,standard}",category:"semantic",tier:"semantic",role:'Bounded MasterDetail master viewport \u2014 20rem (320px) / 28rem (448px), selected by the semantic `masterViewport` prop. `masterViewport="auto"` (the default) applies NO bound, so existing compositions are unchanged; the presets cap the collection\'s block size and scroll it INSIDE the region, which is what stops a long real collection from pushing the detail below the fold once the layout stacks. Retune the presets in a service theme exactly like --master-detail-rail-*; never author a pixel max-height in consumer CSS.'},{name:"--master-detail-master-viewport-inset",category:"semantic",tier:"semantic",role:"Focus-ring room inside a bounded MasterDetail master region (default --space-1 / 4px), and its scroll-padding. An overflow container clips BOTH axes, so without this inset the focus ring of a row at the region's edge would be cut off; it also keeps a row scrolled into view off the clip edge. Only applies when `masterViewport` is compact/standard."},{name:"--page-header-extra-measure",category:"semantic",tier:"semantic",role:'Inline measure of the PageContainer header `extra` region while `headerLayout="responsive-inline"` and the page is below the 640px step (default 11rem / 176px). The default `headerLayout="stack"` never reads it \u2014 `extra` keeps wrapping onto its own full-width line under the subtitle. At >=640px both arrangements are identical, so this token only governs the compact range.'},{name:"--page-measure-{narrow,medium}",category:"semantic",tier:"semantic",role:'Bounded PageContainer MEASURE presets \u2014 42rem (672px) / 48rem (768px), selected by the semantic `measure` prop. They are OUTER measures: the package-owned page gutters (--space-page-active-x, 24px each side) sit INSIDE the cap, so the VISIBLE surface is 624px / 720px, and at 390px nothing binds (358px surface at the 16px compact gutter). Unlike variant="narrow" \u2014 which caps only .ui-page-body \u2014 the measure caps the page HEADER and the BODY together, so a header `extra` action ends flush with the body surface (gh#245/gh#247). `measure="default"` (the default) reads NEITHER token and matches no rule, so existing pages are unchanged. Retune the presets in a service theme; never author a page-local max-width.'},{name:"--badge-space-*",category:"component",tier:"component",role:"Badge spacing."},{name:"--logo-godx-{size,size-xs|sm|md|lg,color} / --logo-success-{background,foreground} / --logo-identity-foreground",category:"component",tier:"component",role:'The GoDX IDENTITY mark. COLOUR: its default is the --brand IDENTITY role (canonical emerald) \u2014 deliberately independent of --primary, so a re-themed action colour never recolours the brand, AND of --success, the \u82E5\u7AF9 STATUS green it wrongly defaulted to before gh#250 (`tone="success"` names the semantic SLOT, not the status role). Role-mirror knobs: declared `initial` at :root with the role default at the CALL SITE (`hsl(var(--logo-godx-color, var(--brand)))`), so a scoped `.dark`/`[data-tenant]` override of --brand actually reaches the mark. BOX: `--logo-godx-size-{xs,sm,md,lg}` (1.5 / 1.75 / 2 / 2.5rem) is the identity mark\'s own size ramp \u2014 the `size` prop drives it exactly like --logo-size-* drives the boxed glyph, one tier above its glyph sibling because the artwork is a capsule inside a square viewBox. `--logo-godx-size` is the PIN, not the base: `initial` by default (so the tiers apply) and, set once by a service theme, it freezes the mark at that box on EVERY tier. INK: `--logo-identity-foreground` (48 9% 9%, #191815) is the colour the boxed `mark="glyph"` sets its TEXT in on the identity fill \u2014 deliberately NOT --brand-foreground. --brand-foreground is the artwork KNOCKOUT colour (it tracks --background in both themes, because `mark="godx"` punches its inner bar as an evenodd HOLE and the email mark paints that hole solid to match) and as negative space only owes WCAG 2.2 SC 1.4.11\'s 3:1. The boxed glyph renders real TEXT, so SC 1.4.3 applies at 4.5:1 \u2014 14px bold is NOT "large text" (that needs 18.66px bold / 24px) \u2014 and knockout white on the emerald measured only 3.67:1, a straight AA failure. The ink is theme-INVARIANT (it is the same near-black spine the dark theme already resolved to), so dark is unchanged at 6.89:1 and light rises to 4.74:1. It is NOT a role-mirror knob \u2014 its default is a real value, not a role, so there is no role to freeze and it is declared at :root, not `initial`. Re-theming --brand to a DARK fill? Override the public knob --logo-success-foreground to re-invert the ink. Applies to `<Logo mark="godx">` / `tone="success"`.'},{name:"--logo-wordmark-{gap,font-size-xs|sm|md|lg,font-weight,letter-spacing,font-family,color}",category:"component",tier:"component",role:'Wordmark/lockup knobs for `<Logo wordmark="\u2026" />` \u2014 the mark\u2194wordmark gap, the per-size-tier wordmark type ramp, and its face/weight/tracking/colour. The package ships NO wordmark artwork: `--logo-wordmark-font-family` defaults (at the call site) to --font-family-display, so the wordmark is typeset in the design-system face; pass an inline <svg> as `wordmark` when a real logotype exists. `--logo-wordmark-color` is a role-mirror knob (`initial`) whose call-site default is hsl(var(--foreground)), or the --brand IDENTITY role on the godx / tone="success" lockup \u2014 never --primary and never the --success status green (gh#250). Setting it once in a service theme re-colours every lockup with no page CSS (gh#214).'},{name:"--card-*",category:"component",tier:"component",role:"Card surface, border, spacing, and typography."},{name:"--control-*",category:"component",tier:"component",role:"Shared form control heights, padding, icons, and focus chrome."},{name:"--table-*",category:"component",tier:"component",role:"Table row/cell sizing."},{name:"--table-pagination-padding-{y,x}",category:"component",tier:"component",role:"DataTable.Pagination footer inset (gh#236). The footer used to declare `padding-top` ONLY, so in the documented flush container (`<Card><CardContent flush><DataTable/>`) the 'rows per page' label and the page-size Select sat flush against the container edge and its closing border. It now owns `padding-block` + `padding-inline`. Both knobs are declared `initial` with the default at the CALL SITE \u2014 block `var(--space-stack-sm)` (the old padding-top value, now on both block edges), inline `var(--table-cell-space-x)` so the label lands on the same optical axis as the first column's text. `initial` is load-bearing: both defaults are density-scaled and re-declared inside a `.ui-density-*` subtree, and a `:root` binding would freeze the footer at the `:root` density. Consumers no longer need a local `.ui-data-table-pagination` override; a service on another grid retunes either axis."},{name:"--table-surface-min-inline-size",category:"component",tier:"component",role:'DataTable surface narrow-viewport width FLOOR (gh#253). Below the `sm` viewport step a multi-column admin grid whose cells are `white-space: nowrap` would be crushed, so the bordered surface keeps a minimum inline size and `.ui-data-table-scroll` scrolls instead. This shipped as a hard-coded `min-w-[640px] sm:min-w-0` utility pair ON the surface element \u2014 the literal that forced the horizontal scroll at 390 \u2014 leaving a service that wants a narrower (or no) floor with no route but forking the component (rule #45). Default `640px`, byte-identical to the old utility, and deliberately px so the floor releases at exactly the same width as the px-based `sm` media query that clears it (a rem value drifts under a non-16px root and re-introduces the scroll between the two thresholds). Set `0` to opt out; `DataTable preset="action-collection"` already does, because there the column-priority measures own the width.'},{name:"--form-label-width",category:"component",tier:"component",role:"Label column width in horizontal Form layout. Default max-content; a service theme sets it once (e.g. 110px) \u2014 the labelWidth prop overrides per form/field."},{name:"--form-label-gap",category:"component",tier:"component",role:"Label\u2194control column gap in horizontal Form layout. Default 16px (--space-4)."},{name:"--dialog-* / --alert-* / --skeleton-*",category:"component",tier:"component",role:"Feedback component sizing and spacing."},{name:"--sheet-responsive-breakpoint-width / --sheet-bottom-max-height",category:"component",tier:"component",role:`The responsive drawer / detail-panel contract (SheetContent responsive="auto"). --sheet-responsive-breakpoint-width (default 48rem = 768px, the library's canonical mobile line) is the viewport width at and below which the desktop side panel becomes a mobile bottom sheet; --sheet-bottom-max-height (default 85dvh) caps THAT bottom presentation only (a plain side="bottom" sheet stays content-sized). Because a CSS @media cannot resolve a custom property, the breakpoint is read off :root at runtime by the exported useSheetResponsiveMode() hook \u2014 which is also what OrgSwitcher responsive="auto" uses, so ONE knob moves the popover\u2192sheet and side\u2192bottom line for every overlay at once. Accepts px/rem/em.`},{name:"--qr-code-*",category:"component",tier:"component",role:"QrCode scanner-safe foreground/background and xs/sm/md/lg natural-size tiers. Keep foreground/background at strong contrast; the defaults remain dark-on-light in dark application themes."},{name:"--chart-trend-plot-height{,-xs,-sm,-md,-lg}",category:"component",tier:"component",role:"CompactBarTrend plot-height tiers (xs 3.5rem = dashboard summary-card density, sm 5rem, md 7.5rem, lg 10rem). The `size` prop picks a step via data-size; a service retunes the whole scale here. This is why a consumer NEVER writes an inline height for a compact trend."},{name:"--chart-trend-bar-{gap,radius,max-width,min-height}",category:"component",tier:"component",role:"CompactBarTrend mark geometry: gap between bars (default --space-2), rounded data-end radius (--radius-sm), the per-mark width ceiling (1.5rem, so a 3-point trend does not render slabs) and the min height floor (2px, so a zero value still reads as a plotted category). All service-tunable (rule #45)."},{name:"--chart-trend-bar-background{,-alpha} / --chart-trend-bar-emphasis-background",category:"component",tier:"component",role:"CompactBarTrend fills \u2014 role-mirror knobs declared `initial` with the role default at the call site. Muted marks default to hsl(var(--muted-foreground) / --chart-trend-bar-background-alpha) (0.75, which clears the 3:1 non-text contrast floor); the emphasized 'current' mark defaults to hsl(var(--primary)). Point them at --chart-N to tint a trend to a series colour."},{name:"--chart-trend-baseline-border",category:"component",tier:"component",role:"CompactBarTrend baseline rule. QUIET by default (rule #44 \u2014 resolves to `none`); a service opts in with `--chart-trend-baseline-border: 1px solid hsl(var(--border));`."},{name:"--chart-trend-{tick-gap,tick-font-size,footer-gap}",category:"component",tier:"component",role:"CompactBarTrend category-tick and activity-footer rhythm \u2014 the plot\u2194tick gap, tick type size (--font-size-xs) and the plot\u2194footer gap."},{name:"--email-shell-{width,padding,page-padding,border-width} / --email-card-{radius,reference-height} / --email-stack-gap{,-sm}",category:"component",tier:"component",role:"Transactional-email shell geometry: the 480px card width, its 32px inset, the 24px viewport gutter, the 1px edge, the 10px radius (mirrors --card-radius) and the block rhythm. The canonical invitation reference measures 480\xD7407 (--email-card-reference-height is a visual-regression target, NEVER a fixed height on the card). Values are LITERAL px \u2014 email clients resolve neither var() nor rem \u2014 and are consumed through the `@godxjp/ui/email` export (EMAIL_SHELL), never by a stylesheet."},{name:"--email-{body,heading}-{font-size,line-height,font-weight} / --email-font-family-{sans,mono}",category:"component",tier:"component",role:`Email type ramp, reconciled with the SCR-302 canonical reference (gh#250). Body 14px/1.9, title 17px/1.7 at weight 500 (a transactional title is CALM, not bold) \u2014 deliberately looser and quieter than the web ramp, because a mail client gives the reader no density control, so do NOT 'fix' it back to --heading-h1/--line-height-body. --email-font-family-sans names the canonical M PLUS 2 face first and degrades Hiragino \u2192 Yu Gothic \u2192 Noto Sans JP \u2192 Meiryo \u2192 system UI \u2192 Arial \u2192 sans-serif; no email client honours @font-face, so the stack is the whole fallback contract. --email-font-family-mono (mirrors --font-family-mono) sets invoice ids, masked card numbers, ISO dates and amounts. Family names export SINGLE-quoted so the value drops into a double-quoted style="\u2026" attribute unescaped (emailInlineStyle throws on a double quote). Exposed as EMAIL_TYPOGRAPHY.`},{name:"--email-mark-{width,height,gap} / --email-wordmark-{font-size,font-weight}",category:"component",tier:"component",role:'Email brand-mark LOCKUP (gh#250). The ARTWORK is byte-identical to <Logo mark="godx" /> (the 32\xD732 capsule + internal glyph, asserted by test); only the rendered box is email-specific \u2014 the canonical header sets a 22px mark, an 8px gap (mirrors --logo-wordmark-gap) and a 13px/700 wordmark (mirrors --logo-wordmark-font-weight). In that pairing the mark is DECORATIVE (render it with label: "") and the wordmark carries the accessible name, so the brand is announced once. Exposed as EMAIL_BRAND_MARK; the svg / dataUri / tableHtml deliveries all derive from this one geometry source.'},{name:"--email-cta-{height,line-height,padding-x,radius,font-size,font-weight} / --email-focus-border-width",category:"component",tier:"component",role:"The single primary email CTA, reconciled with the SCR-302 canonical reference (gh#250): 36px tall (mirrors --control-height-lg) with line-height EQUAL to the height so the label centres without flexbox in Outlook, 16px inline padding (--space-4), the 6px --radius base, 14px/500 label. 36px still clears WCAG 2.2 SC 2.5.8 (24\xD724) and the mobile reflow takes the CTA full-bleed. Fill/label are EMAIL_COLORS.primary / primaryForeground, derived from the --primary role \u2014 never paste a canonical hex, or the email stops tracking a re-theme. --email-focus-border-width mirrors --focus-ring-width for the webmail panes that honour :focus-visible. Exposed as EMAIL_CTA / EMAIL_FOCUS."},{name:"--email-footer-{font-size,line-height,link-gap,padding-top,border-width} / --email-mobile-*",category:"component",tier:"component",role:"Legal-footer typography (11px/1.8 quiet type per the SCR-302 canonical, 12px between adjacent links, 20px above the hairline) and the narrow-viewport reflow (520px breakpoint, fluid card, 20px inset, 12px gutter, 16px heading \u2014 one step under the 17px desktop title, full-bleed CTA). The canonical 390px reference raster is unusable (435px wide and already clipping the card, dxs-platform/platform#496), so the mobile ramp is the library's documented contract rather than a measured value. Exposed as EMAIL_FOOTER / EMAIL_MOBILE; every reflow rule needs an inline fallback because most clients strip <style>."},{name:"--auth-shell-device-{card-max-width,main-padding,main-padding-mobile}",category:"component",tier:"component",role:'AuthShell `preset="device-authorization"` measure (gh#220) \u2014 the canonical OAuth device-grant artboard: a 23.75rem/380px card at 1440/1024 and a 15px-block \xB7 5px-inline page gutter at 390 (so the card renders x=5px, width=380px). LITERAL lengths, not --space-*, because the artboard specifies exact device pixels that must not drift with --scaling. Selecting the preset replaces a consumer-side --auth-shell-card-max-width override or a forked `.auth-shell--wide` class; the canonical 360px/15px default is untouched.'},{name:"--auth-shell-context-{card-max-width,main-padding,main-padding-mobile,card-stack-gap}",category:"component",tier:"component",role:'AuthShell `preset="context-selection"` measure (gh#217) \u2014 the organisation/context picker: a 25rem card on desktop/tablet, edge-to-edge on mobile (0 inline gutter), and a 1rem rhythm between the three direct sections of the auth column (intro \xB7 choice card \xB7 "remember" row).'},{name:"--auth-shell-recovery-{card-max-width,main-padding,main-padding-mobile}",category:"component",tier:"component",role:'AuthShell `preset="account-recovery"` measure (gh#233) \u2014 the SCR-008 27rem/432px panel, shared by the password-recovery panel (request \xB7 sent \xB7 new-password \xB7 expired) AND the sign-in MFA challenge panel (OTP \xB7 recovery-code \xB7 passkey-failure), because both canonical desktop panels measure w=432 at 1440. Mobile gutter = 15px inline at <=30rem, so the panel is x=15, width=360 \u2014 the SAME rhythm as the canonical Login flow, chosen deliberately: the supplied 390 reference is a desktop 2x2 composite that crops horizontally and is NOT a valid source. The canonical 360px Login measure and the 24rem un-preset shell are untouched.'},{name:"--otp-slot-size",category:"component",tier:"component",role:"InputOTP slot box (gh#233). Default `var(--control-height)` \u2014 byte-identical to before and still density-aware \u2014 but its OWN knob, so an auth panel can widen the 6-slot challenge row to fill a wide surface WITHOUT re-scoping --control-height (which would resize the primary button and every other input in the same card). Set it to a NAMED tier (`var(--control-height-lg)`), never an ad-hoc calc offset."},{name:"--tabs-indicator-{background,size,offset}",category:"component",tier:"component",role:"Tabs `line` ACTIVE indicator (gh#248) \u2014 the only selected-state decoration the line variant paints; it never gets a surrounding ring/card border, so the `:focus-visible` keyboard ring stays visible and distinct (WCAG 2.4.7). --tabs-indicator-background is declared `initial` so its hsl(var(--primary)) default re-resolves at the CALL SITE under a scoped [data-tenant]/.dark theme. Defaults = primary \xB7 2px \xB7 offset 0 (flush with the trigger edge); raise the offset to park the bar on a thicker strip hairline. The selected ring on the default/card variants is unchanged."},{name:"--avatar-square-{radius,size,background,foreground}",category:"component",tier:"component",role:'Avatar `shape="square"` entity-header mark (gh#249) \u2014 the compact rounded square an organization/service header uses instead of the round person avatar. --avatar-square-background / --avatar-square-foreground are declared `initial` so the hsl(var(--primary)) / hsl(var(--primary-foreground)) defaults re-resolve at the CALL SITE under a scoped theme. Defaults = --radius-lg corners \xB7 --control-height box (swapping shape never reflows the header) \xB7 primary fill at 4.65:1 contrast. `shape="circle"` (the default) is inert and reads --avatar-background / --avatar-tint as before.'},{name:"--steps-inline-{gap,item-gap,font-size,separator-size}",category:"component",tier:"component",role:"Steps type=inline compact auth/device progress rhythm and typography. Consumers select the semantic appearance; services retune the row once without page CSS."},{name:"--auth-account-summary-{min-height,gap,padding,identity-min-width,avatar-size,avatar-glyph-size,email-font-size}",category:"component",tier:"component",role:"AuthAccountSummary compact signed-in identity row: wrapping threshold, avatar, email and action rhythm. The default touch-safe row is package-owned and remains themeable without consumer selectors."},{name:"--auth-shell-{main-align,card-stack-gap}",category:"component",tier:"component",role:"AuthShell column knobs shared by every preset. --auth-shell-main-align is the block alignment of the auth column (default `center`, the vertically-centred card; set `start` for a tall intro+card+footnote stack). --auth-shell-card-stack-gap is the gap between the card slot's direct sections \u2014 default `0px` (quiet, rule #44) so an existing single-card consumer is byte-for-byte unchanged; a preset opts in."},{name:"--card-space-shell-y",category:"component",tier:"component",role:"Card BLOCK-axis shell padding (gh#232) \u2014 a plain header's top, a `solo` body's top and the terminal slot's bottom. Split off --card-space-inset, which is now inline-only, so a theme can make a card SHORTER without narrowing its column. Declared `initial`: the default resolves at the CALL SITE to --card-space-inset, so a card that overrides neither (and `density=\"tight|cozy\"`, which re-declares it `initial` to re-arm the per-instance inset) renders exactly as before."},{name:"--auth-shell-{compact-card-inset,card-padding-block-compact,card-body-gap-compact,card-gap-compact}",category:"component",tier:"component",role:"The compact AuthShell card's four independent knobs (gh#232) \u2014 one per axis, so the canonical Login card is tunable WITHOUT a consumer selector on the card-content slot: `compact-card-inset` = the inline column (\u2192 --card-space-inset); `card-padding-block-compact` = the card's block/top-bottom padding (\u2192 --card-space-shell-y, `initial` so its default mirrors the live inline inset and canonical output is unchanged); `card-body-gap-compact` = the header\u2194body gap (\u2192 --card-space-body-y, 12px, the role the block knob used to be mis-wired to); `card-gap-compact` = the in-slot title\u2195description stack gap (\u2192 --card-space-gap)."},{name:"--app-setting-picker-compact-{control-height,padding-x,gap,font-size}",category:"component",tier:"component",role:'AppSettingPicker `compact` trigger (gh#217) \u2014 the small, content-hugging LABELLED switcher for an auth/legal footer (`kind="locale" appearance="labeled" compact`). The box height reads the official --control-height-sm tier (never a literal or an ad-hoc calc), and padding/gap/type are separate knobs so a service theme retunes the footer switcher without forking CSS.'},{name:"--centered-shell-column-offset-block",category:"component",tier:"component",role:'CenteredShell column block offset (gh#221) \u2014 default `0` (the quiet state, rule #44: the top-aligned flowing/scrolling page). `<CenteredShell align="center">` flips it to `auto`, which centres the column in the 100dvh shell: the package-owned geometry for a SYSTEM-level standalone surface (a 500/503 error page, a maintenance notice) at 1440/1024/390, so a consumer never writes `min-h-dvh` + flex-centring CSS or a className. Auto block offsets collapse to 0 when the content is taller than the viewport, so a long localized message scrolls from the top instead of clipping.'},{name:"--error-surface-*",category:"component",tier:"component",role:'ErrorSurface knobs (gh#221 / gh#251) \u2014 the whole 403/404/500/503 exception surface is themeable without a single consumer class or media query, in BOTH `mode="application"` and `mode="system"`. Geometry: `--error-surface-max-width` (32rem measure of the surface block, so a long JA/VI headline wraps readably even inside a wide PageContainer), `--error-surface-gap` (rhythm between status code \xB7 body \xB7 metadata \xB7 progress), `--error-surface-padding-block` (--space-10, the 1440/1024 steps) and `--error-surface-padding-block-compact` (--space-6, below the narrow step, i.e. the 390 case), `--error-surface-brand-gap` (system-mode Logo slot) and `--error-surface-progress-max-width` (18rem, so the maintenance meter reads as metadata, not as the page\'s primary content). Metadata list: `--error-surface-meta-gap` (row rhythm) \xB7 `--error-surface-meta-row-gap` (label\u2194value gap inside one row) \xB7 `--error-surface-meta-padding-block`. Chrome is QUIET by default (rule #44): `--error-surface-meta-border` is `none`; a service opts in with `1px solid hsl(var(--border))`. The narrow step is a CONTAINER query at 30rem on the SURFACE\'s own width, not a viewport media query, so it compacts correctly whether the squeeze came from a 390px phone or from an application shell with an expanded sidebar; below it, metadata rows stack label-over-value. System-mode viewport centring is delegated to `--centered-shell-column-offset-block` (via CenteredShell align="center"), and the description measure to `--empty-state-description-max-width`.'},{name:"--centered-shell-landing-*",category:"component",tier:"component",role:'CenteredShell public-landing knobs (gh#252) \u2014 the SHELL geometry of a public marketing/product landing, so the landing itself stays a COMPOSITION (Hero/Navbar/Footer fail the Framework-Component Test) with ZERO page-local CSS. Read only under `preset="public-landing"`; the default shell emits no `data-preset`, so none of them can match. Measure: `--centered-shell-landing-max-width` (67.5rem, shared by the header bar, the centred column AND the footer \u2014 the bar/footer inline padding is `max(gutter, (100% - measure) / 2)`, so header content starts on exactly the column edge and no consumer max-width wrapper exists), `--centered-shell-landing-inset-inline` (+ `-compact`, the 390 gutter), `--centered-shell-landing-main-padding-block` (+ `-compact`), `--centered-shell-landing-section-gap` (+ `-compact`, the rhythm BETWEEN plain `<section>` elements \u2014 the consumer never sets a gap) and `--centered-shell-landing-footer-padding-block`. Chrome is QUIET by default (rule #44): `--centered-shell-landing-card-shadow` is `none`, which flattens every Card in the subtree from ONE knob instead of a consumer `.landing .card { box-shadow: none }`. Type: `--centered-shell-landing-heading-size` (+ `-compact`) re-points `--heading-h1`, so a hero title is a real `Heading level={1}` and never a page font-size. `--centered-shell-landing-background` is a ROLE-MIRROR declared `initial` with the role default at the call site (`var(--centered-shell-landing-background, var(--background))`), so a scoped [data-tenant]/.dark override of `--background` still reaches the landing canvas. The compact step is a 40rem media query owned by the package; region visibility (anchor nav below the tablet step, wordmark and the secondary action at mobile) is `Flex hideBelow` / `hideFrom`, never a consumer @media.'},{name:"--table-action-collection-*",category:"component",tier:"component",role:'Table action-collection knobs (gh#253) \u2014 the canonical dense approval/action queue at 1440 \xB7 1024 \xB7 390. Read only under `preset="action-collection"`. Column PRIORITY measures replace the desktop intrinsic widths (a `white-space: nowrap` free-text column is what makes a five-column queue wider than its card and forces the horizontal scroll): `--table-action-collection-primary-width` (18%), `-secondary-width` (22%), `-meta-width` (12%) and `-actions-width` (3.5rem \u2014 an absolute measure because the row-action affordance must never be squeezed below its touch target); an unmarked column takes the remaining space. Below the collapse step the same four re-point to their `-compact` tier (24% / 22% / 20% / 2.75rem) together with `--table-action-collection-font-size-compact`, `-row-height-compact`, `-cell-space-x-compact` and `-cell-padding-y-compact`. The step is a CONTAINER query on the table\'s own width (sm 40rem \xB7 md 48rem \xB7 lg 64rem \xB7 xl 80rem via `collapseBelow`), not a viewport media query, so a table inside a master rail collapses before the page does. Percentages so the ratio holds at any card width; a service retunes the ratio once instead of authoring column widths per screen (rule #45). ONE family serves BOTH entry points \u2014 the `Table` primitive (`priority` on `TableHead`/`TableCell`) and the TanStack `DataTable` (`priority` on the `ColumnDef`, stamped onto both cells for you). There is deliberately no parallel `--data-table-action-collection-*` family: retune these once and every queue follows.'},{name:"--legal-document-*",category:"component",tier:"component",role:"LegalDocumentShell knobs (gh#222) \u2014 the whole legal/policy document surface is themeable without a single consumer `.legal-*` class. Geometry: `--legal-document-measure-max-width` (46rem readable measure, caps header/body/footer), `--legal-document-toc-width` \xB7 `--legal-document-column-gap` \xB7 `--legal-document-toc-inset-block-start` (sticky offset) \xB7 `--legal-document-toc-max-height`, `--legal-document-section-gap` \xB7 `--legal-document-section-title-gap` \xB7 `--legal-document-header-gap` \xB7 `--legal-document-meta-gap` \xB7 `--legal-document-nav-gap` \xB7 `--legal-document-footer-gap`, `--legal-document-meta-font-size`, `--legal-document-body-line-height` (1.8 long-form leading) and `--legal-document-scroll-offset` (the `scroll-margin-block-start` a hash jump leaves above a heading \u2014 this is why no app needs JS offset arithmetic). Contents entries: `--legal-document-toc-font-size` \xB7 `--legal-document-toc-title-font-size` \xB7 `--legal-document-toc-item-padding` \xB7 `--legal-document-toc-item-radius` \xB7 `--legal-document-toc-marker-width`. Chrome is QUIET by default (rule #44): `--legal-document-toc-border` / `--legal-document-header-border` / `--legal-document-footer-border` are all `none`; a service opts in with `1px solid hsl(var(--border))`. The six colour knobs are ROLE-MIRRORS declared `initial` with the role default at the call site \u2014 `--legal-document-toc-foreground` (\u2192 --muted-foreground, also paints the contents caption), `--legal-document-toc-active-foreground` (\u2192 --foreground), `--legal-document-toc-active-background` (\u2192 --accent), `--legal-document-toc-marker-color` (\u2192 --primary), `--legal-document-meta-foreground` (\u2192 --muted-foreground) and `--legal-document-summary-foreground` (\u2192 --muted-foreground) \u2014 so a scoped [data-tenant]/.dark override of the role still reaches them. The one-column \u21C4 two-column split is a CONTAINER query at 56rem on the shell's own width, not a viewport media query."},{name:"--empty-state-description-max-width",category:"component",tier:"component",role:"EmptyState description MEASURE (gh#221) \u2014 default 28rem (~65 Latin characters per line). A service or locale that needs a shorter/longer measure for its own copy (JA/VI error-page descriptions, a narrow system surface) retunes this one knob instead of forking `.ui-empty-state-description` (rule #45)."},{name:"--sidebar-nav-item-{foreground,hover-foreground,disabled-foreground} / --sidebar-nav-icon-{foreground,hover-foreground,active-foreground,disabled-foreground}",category:"component",tier:"component",role:"Sidebar navigation row + icon COLOUR, split so they can differ (gh#228). Previously `.sb-nav-item` set one `color` for the whole row, so the Lucide SVG inherited `--muted-foreground` along with the label and the icons read as 'missing' against a canonical shell that wants darker 16px icons. `--sidebar-nav-item-*` drives the row/label (incl. sub rows), `--sidebar-nav-icon-*` drives `.sb-icon`. All are role-mirror knobs declared `initial` at `:root` with the role default at the call site, so a scoped `[data-tenant]`/`.dark` override reaches them; icon defaults resolve to `currentColor`, i.e. rendering is unchanged until a service opts in (`--sidebar-nav-icon-foreground: hsl(var(--foreground))` is the canonical darker-icon setting). Colour ONLY \u2014 the 16px icon, 32px row height and 10px gap are untouched. The active row keeps `--sidebar-item-active-{background,foreground}`."},{name:"--list-row-compact-{padding-y,padding-x,gap,body-min-width}",category:"component",tier:"component",role:'ListRow `density="compact"` inline-actions geometry (gh#246) \u2014 the compact invitation/history row: a 36px leading Avatar, a shrinkable title+description and one or two small trailing Buttons on ONE line inside the canonical 358px card (326px content). `--list-row-compact-body-min-width` (6rem) is the compact body threshold \u2014 the default 12rem `--list-row-body-min-width` is what forced the actions (and a history Badge + date) onto a second line at 390px; it is consumed as `min(<token>, 100%)` exactly like the default knob, so the #224 wrap contract is intact and a cluster that genuinely cannot fit still wraps instead of widening the page root. `--list-row-compact-{padding-y,padding-x,gap}` are declared `initial` with the density-scaled default at the CALL SITE (`var(--space-2)` block, `var(--list-row-padding-x)` inline, `var(--space-2)` gap) \u2014 a `:root` binding would freeze compact rows at the `:root` density. Measured at 390px: invitation row 62px (was 126px, actions wrapped), history row 41px (was 114px), documentElement scrollWidth === clientWidth.'},{name:"--list-row-{body-min-width,trailing-gap,indicator-size,read-background,unread-background,indicator-color}",category:"component",tier:"component",role:"ListRow shrink + unread state (gh#224, gh#225). `--list-row-body-min-width` (12rem) is consumed as `min(<token>, 100%)` \u2014 the `min()` is load-bearing, a bare length re-inflates the row's intrinsic width and reintroduces page-root overflow. `--list-row-trailing-gap` spaces wrapped trailing actions. The unread trio are role-mirror knobs declared `initial` with the role at the call site: `--list-row-unread-background` defaults to `hsl(var(--muted))` NOT `--accent` \u2014 accent measured 4.23:1 against the xs muted-foreground description line, below WCAG AA; muted is 4.63:1 light / 5.47:1 dark. `--list-row-indicator-color` (\u2192 `--primary`) and `--list-row-indicator-size` drive the unread dot, whose meaning is carried by localized sr-only text, never by colour alone."},{name:"--filter-bar-scroll-padding-y",category:"component",tier:"component",role:'Scrollbar gutter for `<FilterBar overflow="scroll">` (gh#216), default `var(--space-1)`. Only applies at \u2265640px, where a wide filter set becomes a single nowrap strip with a sticky inline-end clear-all; below that the bar still stacks, so the gutter would be dead space. Pair with the existing `--filter-bar-sticky-background` (already an `initial` role-mirror knob) when theming the strip.'},{name:"--app-shell-{sidebar-width,rail-width}",category:"component",tier:"component",role:"AppShell docked navigation rail widths (gh#213) \u2014 `--app-shell-sidebar-width` (default 16rem) is the expanded rail, `--app-shell-rail-width` (default 4rem) the icon-only rail used at `<AppShell sidebarCollapsed>`. These were hard-coded `grid-template-columns` literals; a service on a different design grid (e.g. a 255px rail) now sets ONE token in its theme instead of forking `.app-root`. Below the 900px shell breakpoint both collapse to a single full-width column and the sidebar moves into AppShell's drawer."},{name:"--app-shell-bar-{inset,inset-compact,gap}",category:"component",tier:"component",role:"AppShell top-bar chrome (gh#213) \u2014 `--app-shell-bar-inset` is the bar's inline padding (default --space-4), `--app-shell-bar-inset-compact` the tighter value applied below the 900px breakpoint where the docked rail is gone (default --space-3), `--app-shell-bar-gap` the gap between the bar's direct children (default --space-3). Height stays `--app-shell-bar-height` (3rem), which now holds at EVERY width \u2014 a duplicate 768px rule used to override it with a literal."},{name:"--topbar-{height,inset,gap}",category:"component",tier:"component",role:"Standalone `<Topbar>` box knobs (gh#213). Topbar is a pure slot bar, so the defaults are the QUIET ones and rendering is unchanged from before they existed: `--topbar-height: auto` and `--topbar-inset: 0px` (inside AppShell the `.app-topbar` grid row owns height and inset), `--topbar-gap: var(--space-2)`. A service that mounts Topbar DIRECTLY on a page sizes it from the theme (`--topbar-height: 3.5rem; --topbar-inset: var(--space-4)`) instead of an app-local class. `--topbar-gap` is both the gap BETWEEN the start/center/end clusters and the gap INSIDE each, so one knob re-rhythms the whole bar. None of them touch the gh#226 shrink contract (start clips, center yields first, end stays anchored inline-end)."},{name:"--{dialog,sheet}-overlay-background / --app-shell-mobile-nav-background (+ -alpha)",category:"component",tier:"component",role:"Overlay scrims (gh#215). All three are ROLE-MIRROR knobs declared `initial` at `:root`, with the default resolved at the CALL SITE as a SHARE of the one semantic `--overlay-background` \u2014 `color-mix(in srgb, var(--overlay-background) var(--<x>-overlay-alpha), transparent)`. Previously each carried a private literal, so setting `--overlay-background` had NO effect anywhere. The share knobs (`--dialog-overlay-alpha` 60%, `--sheet-overlay-alpha` 40%, `--app-shell-mobile-nav-alpha` 40%) keep every default byte-identical (0.3 / 0.2 / 0.2 black) while letting one `--overlay-background` override retint the whole system, including scoped `[data-tenant]` / `.dark` themes. Setting a `*-overlay-background` knob directly still wins outright. NOTE: overlays render in a portal, so a multi-tenant scope must also sit on the portal container."}];function I(t){return C.filter(a=>a.category===t)}var V=[{name:"--badge-space-gap",value:"var(--space-inline-xs)",description:"Badge component tokens."},{name:"--badge-space-x",value:"var(--space-2)",description:"Badge component tokens."},{name:"--badge-space-y",value:"var(--space-1)",description:"Badge component tokens."},{name:"--badge-font-size",value:"var(--font-size-xs)",description:"Small-by-design (badge/pill/counter). A knob (rule #45) so a service can * re-tune badge text without touching the global --font-size-xs step."},{name:"--card-space-inset",value:"var(--space-section-active)",description:"Horizontal inset of every slot (header / content / footer) + the resting top/bottom * shell padding. This is the column the title, body and footer all align to."},{name:"--card-space-shell-y",value:"initial",description:'BLOCK-axis (top/bottom) shell padding of the slotted card rhythm \u2014 a plain header\'s top, a * `solo` body\'s top, and the terminal slot\'s bottom (gh#232). Split off --card-space-inset so a * shell (AuthShell compact) can retune the card\'s height WITHOUT moving the inline column. * Declared `initial` \u2014 NOT `var(--card-space-inset)` \u2014 so the default resolves at the CALL SITE: * a :root binding would freeze at the :root inset and `[data-density="tight"|"cozy"]` (which * override --card-space-inset on the card element) would stop reaching it. See docs/TOKENS.md \xB7 * "Role-mirror knobs MUST be `initial`" \u2014 the same call-site rule applies to derived knobs.'},{name:"--card-space-header-y",value:"var(--space-stack-sm)",description:"Vertical padding of a BANDED header band (top = bottom). Drives --card-space-divided-y."},{name:"--card-space-body-y",value:"var(--space-section-active)",description:"Gap between the header and the body, and the body's own top padding \u2014 the breathing * room under a title before content begins."},{name:"--card-space-solo-y",value:"initial",description:'Symmetric block padding for a SOLO content slot, so a composed shell can tune block and * inline spacing independently without targeting CardContent. * Declared `initial` for the SAME reason as --card-space-shell-y above: written as * `var(--card-space-inset)` here it would freeze at the :root inset, and a card carrying * [data-density="tight"|"cozy"] (which override --card-space-inset ON THE CARD) would keep the * :root value. The default now resolves at the call site through the chain * solo-y \u2192 shell-y \u2192 inset, so a solo body follows the general block knob when only that is * set, and an explicit solo-y still wins.'},{name:"--card-space-footer-y",value:"var(--space-stack-sm)",description:"Vertical padding of a SEPARATED footer band (top = bottom). Drives --card-space-divided-y."},{name:"--card-space-divided-y",value:"var(--card-space-header-y)",description:"DIVIDED-section vertical padding (rule #44/#45). A header/footer that carries a divider * border (banded header, separated footer) reads as its own band, so it pads SYMMETRICALLY * top+bottom \u2014 distinct from a plain header that flows into the body (top inset, no bottom). * One themeable knob keeps the header- and footer-band rhythm in sync; a service theme tunes * the band density here instead of forking per-slot CSS."},{name:"--card-space-gap",value:"var(--space-stack-xs)",description:"Vertical gap between stacked items WITHIN a slot (e.g. title \u2195 description in the header)."},{name:"--card-title-font-size",value:"var(--font-size-base)",description:"Vertical gap between stacked items WITHIN a slot (e.g. title \u2195 description in the header)."},{name:"--card-title-line-height",value:"var(--line-height-tight)",description:"Vertical gap between stacked items WITHIN a slot (e.g. title \u2195 description in the header)."},{name:"--card-title-font-weight",value:"var(--font-weight-semibold)",description:"Vertical gap between stacked items WITHIN a slot (e.g. title \u2195 description in the header)."},{name:"--card-description-font-size",value:"var(--font-size-sm)",description:"Vertical gap between stacked items WITHIN a slot (e.g. title \u2195 description in the header)."},{name:"--card-description-line-height",value:"var(--line-height-normal)",description:"Vertical gap between stacked items WITHIN a slot (e.g. title \u2195 description in the header)."},{name:"--card-background",value:"initial",description:'Card fill + edge \u2014 opt-in knobs that DEFAULT to the live --card / --border roles. Declared * `initial` (not `var(--card)`) so the default re-resolves at the call site under a scoped theme: * a :root binding to a role var freezes at the :root value and a scoped `[data-tenant]` override of * the role never reaches it (see docs/STANDARDS-vocabulary-tokens.md \xB7 "role-mirror knobs"). A * service still overrides the knob directly (--card-background: \u2026) to win over the role default.'},{name:"--card-border",value:"initial",description:"default = hsl(var(--card))"},{name:"--card-header-background",value:"initial",description:"Banded-header fill \u2014 role-tintable (rule #45): a service points this at any role, * e.g. --card-header-background: var(--primary), and tunes --card-header-background-alpha for * the wash strength. Default = the live --muted role (resolved at the call site)."},{name:"--card-header-background-alpha",value:"0.55",description:"default = hsl(var(--muted))"},{name:"--card-header-border-bottom",value:"initial",description:"Banded-header divider \u2014 tokenised (rule #44) so a service theme can make it * dashed / heavier / none without forking CSS. Pair with * --card-header-background-alpha: 0 for a quiet borderless-band header. * Default = 1px solid hsl(var(--card-border)) (resolved at the call site)."},{name:"--card-radius",value:"var(--radius-xl)",description:"Banded-header divider \u2014 tokenised (rule #44) so a service theme can make it * dashed / heavier / none without forking CSS. Pair with * --card-header-background-alpha: 0 for a quiet borderless-band header. * Default = 1px solid hsl(var(--card-border)) (resolved at the call site)."},{name:"--card-shadow",value:"var(--shadow-sm)",description:"The DXS hi-fi baseline uses a quiet 10px data surface with one shadow-sm * elevation layer. Consumers can still flatten or lift cards through this knob."},{name:"--card-glow",value:"0 0 0 0 transparent",description:"Brand glow layer \u2014 invisible no-op at rest (rule #44). Paired AFTER --card-shadow in the * surface box-shadow so a service can wash every card with the global glow, e.g. * --card-glow: var(--shadow-glow), with no markup change."},{name:"--card-tint",value:"transparent",description:"Fill tint \u2014 subtle role wash over the card background (default transparent = invisible). * Painted as an overlay so a service sets --card-tint: hsl(var(--primary) / 0.04) once."},{name:"--card-accent-rail-width",value:"6px",description:"Accent edge \u2014 width of the semantic leading-edge stripe (data-accent). * Tokenised (rule #44) so a service theme can re-tune it without forking CSS. * The slot padding compensation in card-layout.css subtracts the same token, * so content stays aligned on the shell whatever the rail width."},{name:"--stat-card-label-font-size",value:"var(--font-size-xs)",description:"Accent edge \u2014 width of the semantic leading-edge stripe (data-accent). * Tokenised (rule #44) so a service theme can re-tune it without forking CSS. * The slot padding compensation in card-layout.css subtracts the same token, * so content stays aligned on the shell whatever the rail width."},{name:"--stat-card-label-font-weight",value:"var(--font-weight-medium)",description:"Accent edge \u2014 width of the semantic leading-edge stripe (data-accent). * Tokenised (rule #44) so a service theme can re-tune it without forking CSS. * The slot padding compensation in card-layout.css subtracts the same token, * so content stays aligned on the shell whatever the rail width."},{name:"--stat-card-label-letter-spacing",value:"0.04em",description:"Accent edge \u2014 width of the semantic leading-edge stripe (data-accent). * Tokenised (rule #44) so a service theme can re-tune it without forking CSS. * The slot padding compensation in card-layout.css subtracts the same token, * so content stays aligned on the shell whatever the rail width."},{name:"--stat-card-value-font-size",value:"var(--font-size-2xl)",description:"Accent edge \u2014 width of the semantic leading-edge stripe (data-accent). * Tokenised (rule #44) so a service theme can re-tune it without forking CSS. * The slot padding compensation in card-layout.css subtracts the same token, * so content stays aligned on the shell whatever the rail width."},{name:"--stat-card-value-line-height",value:"1.1",description:"Accent edge \u2014 width of the semantic leading-edge stripe (data-accent). * Tokenised (rule #44) so a service theme can re-tune it without forking CSS. * The slot padding compensation in card-layout.css subtracts the same token, * so content stays aligned on the shell whatever the rail width."},{name:"--stat-card-value-font-weight",value:"var(--font-weight-semibold)",description:"Accent edge \u2014 width of the semantic leading-edge stripe (data-accent). * Tokenised (rule #44) so a service theme can re-tune it without forking CSS. * The slot padding compensation in card-layout.css subtracts the same token, * so content stays aligned on the shell whatever the rail width."},{name:"--stat-card-hint-font-size",value:"var(--font-size-xs)",description:"Accent edge \u2014 width of the semantic leading-edge stripe (data-accent). * Tokenised (rule #44) so a service theme can re-tune it without forking CSS. * The slot padding compensation in card-layout.css subtracts the same token, * so content stays aligned on the shell whatever the rail width."},{name:"--stat-card-gap",value:"var(--space-stack-xs)",description:"Accent edge \u2014 width of the semantic leading-edge stripe (data-accent). * Tokenised (rule #44) so a service theme can re-tune it without forking CSS. * The slot padding compensation in card-layout.css subtracts the same token, * so content stays aligned on the shell whatever the rail width."},{name:"--stat-card-icon-size",value:"2.25rem",description:"Accent edge \u2014 width of the semantic leading-edge stripe (data-accent). * Tokenised (rule #44) so a service theme can re-tune it without forking CSS. * The slot padding compensation in card-layout.css subtracts the same token, * so content stays aligned on the shell whatever the rail width."},{name:"--stat-card-icon-glyph-size",value:"1.25rem",description:"Accent edge \u2014 width of the semantic leading-edge stripe (data-accent). * Tokenised (rule #44) so a service theme can re-tune it without forking CSS. * The slot padding compensation in card-layout.css subtracts the same token, * so content stays aligned on the shell whatever the rail width."},{name:"--stat-card-icon-radius",value:"var(--radius-md)",description:"Accent edge \u2014 width of the semantic leading-edge stripe (data-accent). * Tokenised (rule #44) so a service theme can re-tune it without forking CSS. * The slot padding compensation in card-layout.css subtracts the same token, * so content stays aligned on the shell whatever the rail width."},{name:"--stat-card-icon-background",value:"initial",description:"Medallion tint \u2014 soft brand wash + brand glyph by default; a service retints by overriding * --primary or these tokens directly (rule #44/#45). `initial` so the --primary default * re-resolves at the call site under a scoped theme (no :root freeze). * Defaults = hsl(var(--primary) / 0.1) fill \xB7 hsl(var(--primary)) glyph."},{name:"--stat-card-icon-foreground",value:"initial",description:"Medallion tint \u2014 soft brand wash + brand glyph by default; a service retints by overriding * --primary or these tokens directly (rule #44/#45). `initial` so the --primary default * re-resolves at the call site under a scoped theme (no :root freeze). * Defaults = hsl(var(--primary) / 0.1) fill \xB7 hsl(var(--primary)) glyph."},{name:"--stat-card-delta-font-size",value:"var(--font-size-xs)",description:"Medallion tint \u2014 soft brand wash + brand glyph by default; a service retints by overriding * --primary or these tokens directly (rule #44/#45). `initial` so the --primary default * re-resolves at the call site under a scoped theme (no :root freeze). * Defaults = hsl(var(--primary) / 0.1) fill \xB7 hsl(var(--primary)) glyph."},{name:"--card-service-launcher-space-gap",value:"var(--space-stack-sm)",description:"\u2500\u2500 ServiceLauncherCard (gh#219) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 * Internal rhythm of a launcher tile: one gap knob drives the vertical stack AND the * icon\u2194title\u2194status row, so a service theme retunes the whole tile density once."},{name:"--card-service-launcher-icon-size",value:"var(--control-height-lg)",description:"Semantic icon surface. `--control-height-lg` is the 36px control tier the hi-fi calls for \u2014 * a tier token, never a literal, so the medallion tracks --scaling with its sibling controls."},{name:"--card-service-launcher-icon-glyph-size",value:"var(--space-5)",description:"Semantic icon surface. `--control-height-lg` is the 36px control tier the hi-fi calls for \u2014 * a tier token, never a literal, so the medallion tracks --scaling with its sibling controls."},{name:"--card-service-launcher-icon-radius",value:"var(--radius-md)",description:"Semantic icon surface. `--control-height-lg` is the 36px control tier the hi-fi calls for \u2014 * a tier token, never a literal, so the medallion tracks --scaling with its sibling controls."},{name:"--card-service-launcher-icon-background",value:"initial",description:"Available-service medallion tint. `initial` (role-mirror rule, docs/TOKENS.md) so the role * defaults resolve at the CALL SITE and a scoped [data-tenant]/.dark override reaches them. * Defaults = hsl(var(--accent)) fill \xB7 hsl(var(--primary)) glyph."},{name:"--card-service-launcher-icon-foreground",value:"initial",description:"Available-service medallion tint. `initial` (role-mirror rule, docs/TOKENS.md) so the role * defaults resolve at the CALL SITE and a scoped [data-tenant]/.dark override reaches them. * Defaults = hsl(var(--accent)) fill \xB7 hsl(var(--primary)) glyph."},{name:"--card-service-launcher-unavailable-icon-background",value:"initial",description:"Unavailable medallion tint \u2014 applied when the consumer supplies `disabledReason`, so an * un-launchable service never reads as brand-live. `initial` for the same role-mirror reason. * Defaults = hsl(var(--muted)) fill \xB7 hsl(var(--muted-foreground)) glyph."},{name:"--card-service-launcher-unavailable-icon-foreground",value:"initial",description:"Unavailable medallion tint \u2014 applied when the consumer supplies `disabledReason`, so an * un-launchable service never reads as brand-live. `initial` for the same role-mirror reason. * Defaults = hsl(var(--muted)) fill \xB7 hsl(var(--muted-foreground)) glyph."},{name:"--card-service-launcher-description-font-size",value:"var(--font-size-xs)",description:"Description prose \u2014 also drives the disabled-reason line and the catalog-CTA caption."},{name:"--card-service-launcher-description-line-height",value:"var(--line-height-body)",description:"Description prose \u2014 also drives the disabled-reason line and the catalog-CTA caption."},{name:"--card-service-launcher-metadata-font-size",value:"var(--font-size-xs)",description:"Mono metadata line (hostname \xB7 plan) \u2014 tighter leading than prose by design."},{name:"--card-service-launcher-metadata-line-height",value:"var(--line-height-normal)",description:"Mono metadata line (hostname \xB7 plan) \u2014 tighter leading than prose by design."},{name:"--card-service-launcher-cta-min-height",value:"calc(var(--control-height-lg) * 4)",description:"Dashed catalog/add companion tile: kept tall enough to sit level with a populated launcher * tile in the same grid row. Four control tiers \u2248 medallion + title + description + action."},{name:"--card-service-launcher-skeleton-title-width",value:"var(--space-12)",description:"Skeleton bar widths \u2014 shape-matched placeholders for the title and status badge."},{name:"--card-service-launcher-skeleton-status-width",value:"var(--space-10)",description:"Skeleton bar widths \u2014 shape-matched placeholders for the title and status badge."},{name:"--chart-trend-plot-height-xs",value:"3.5rem",description:"\u2500\u2500 Plot height tiers (size = xs|sm|md|lg) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 * `xs` is the canonical dashboard-summary-card density: a seven-day trend that sits under * a KPI headline without stealing its vertical rhythm. A service retunes the whole scale * here; a screen picks a step with the `size` prop."},{name:"--chart-trend-plot-height-sm",value:"5rem",description:"\u2500\u2500 Plot height tiers (size = xs|sm|md|lg) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 * `xs` is the canonical dashboard-summary-card density: a seven-day trend that sits under * a KPI headline without stealing its vertical rhythm. A service retunes the whole scale * here; a screen picks a step with the `size` prop."},{name:"--chart-trend-plot-height-md",value:"7.5rem",description:"\u2500\u2500 Plot height tiers (size = xs|sm|md|lg) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 * `xs` is the canonical dashboard-summary-card density: a seven-day trend that sits under * a KPI headline without stealing its vertical rhythm. A service retunes the whole scale * here; a screen picks a step with the `size` prop."},{name:"--chart-trend-plot-height-lg",value:"10rem",description:"\u2500\u2500 Plot height tiers (size = xs|sm|md|lg) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 * `xs` is the canonical dashboard-summary-card density: a seven-day trend that sits under * a KPI headline without stealing its vertical rhythm. A service retunes the whole scale * here; a screen picks a step with the `size` prop."},{name:"--chart-trend-plot-height",value:"var(--chart-trend-plot-height-xs)",description:"Active tier \u2014 chart-layout.css re-points this per `data-size`; overriding it directly * pins one height for every trend regardless of `size`."},{name:"--chart-trend-bar-gap",value:"var(--space-2)",description:"\u2500\u2500 Bar marks \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 * Thin marks, a surface gap between neighbours and a rounded data-end anchored to the * baseline (the data-visualization mark spec)."},{name:"--chart-trend-bar-radius",value:"var(--radius-sm)",description:"\u2500\u2500 Bar marks \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 * Thin marks, a surface gap between neighbours and a rounded data-end anchored to the * baseline (the data-visualization mark spec)."},{name:"--chart-trend-bar-max-width",value:"1.5rem",description:"Ceiling on a single mark so a 3-point trend does not render slabs."},{name:"--chart-trend-bar-min-height",value:"2px",description:"Floor so a zero/absent value still reads as a plotted category rather than a hole."},{name:"--chart-trend-bar-background",value:"initial",description:'Muted mark fill \u2014 role-mirror knob, declared `initial` so the role default re-resolves * at the CALL SITE under `.dark` / `[data-tenant]` (docs/TOKENS.md \xB7 "Role-mirror knobs * MUST be `initial`"). Default = hsl(var(--muted-foreground) / 0.75), which clears the * 3:1 non-text contrast floor (WCAG 1.4.11) against the card surface.'},{name:"--chart-trend-bar-background-alpha",value:"0.75",description:"Opacity applied to the muted-fill role default."},{name:"--chart-trend-bar-emphasis-background",value:"initial",description:'Emphasized ("current") mark fill \u2014 role-mirror knob. Default = hsl(var(--primary)).'},{name:"--chart-trend-baseline-border",value:"initial",description:"\u2500\u2500 Baseline + category ticks \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 * Chrome defaults QUIET (rule #44): no baseline rule at rest. A service opts in with * `--chart-trend-baseline-border: 1px solid hsl(var(--border));`."},{name:"--chart-trend-tick-gap",value:"var(--space-1)",description:"\u2500\u2500 Baseline + category ticks \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 * Chrome defaults QUIET (rule #44): no baseline rule at rest. A service opts in with * `--chart-trend-baseline-border: 1px solid hsl(var(--border));`."},{name:"--chart-trend-tick-font-size",value:"var(--font-size-xs)",description:"\u2500\u2500 Baseline + category ticks \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 * Chrome defaults QUIET (rule #44): no baseline rule at rest. A service opts in with * `--chart-trend-baseline-border: 1px solid hsl(var(--border));`."},{name:"--chart-trend-footer-gap",value:"var(--space-2)",description:"Gap between the plot block and the optional activity footer slot."},{name:"--control-height-compact",value:"1.75rem",description:"Control primitive tokens: heights, horizontal padding, adjacent control sizes."},{name:"--control-height-default",value:"2rem",description:"Control primitive tokens: heights, horizontal padding, adjacent control sizes."},{name:"--control-height-comfortable",value:"2.75rem",description:"Control primitive tokens: heights, horizontal padding, adjacent control sizes."},{name:"--control-padding-x-compact",value:"var(--space-2)",description:"Control primitive tokens: heights, horizontal padding, adjacent control sizes."},{name:"--control-padding-x-default",value:"var(--space-3)",description:"Control primitive tokens: heights, horizontal padding, adjacent control sizes."},{name:"--control-padding-x-comfortable",value:"var(--space-4)",description:"Control primitive tokens: heights, horizontal padding, adjacent control sizes."},{name:"--control-height",value:"calc(var(--control-height-default) * var(--scaling))",description:"Control primitive tokens: heights, horizontal padding, adjacent control sizes."},{name:"--control-height-sm",value:"calc(var(--control-height) - calc(0.25rem * var(--scaling)))",description:"Adjacent control sizes, derived from the active --control-height. The \xB1step * is scaled too so the whole control ladder stays proportional under --scaling."},{name:"--control-height-lg",value:"calc(var(--control-height) + calc(0.25rem * var(--scaling)))",description:"Adjacent control sizes, derived from the active --control-height. The \xB1step * is scaled too so the whole control ladder stays proportional under --scaling."},{name:"--control-height-xs",value:"calc(var(--control-height) - calc(0.5rem * var(--scaling)))",description:"Adjacent control sizes, derived from the active --control-height. The \xB1step * is scaled too so the whole control ladder stays proportional under --scaling."},{name:"--control-padding-x",value:"var(--control-padding-x-default)",description:"Adjacent control sizes, derived from the active --control-height. The \xB1step * is scaled too so the whole control ladder stays proportional under --scaling."},{name:"--control-gap",value:"var(--space-inline-sm)",description:"Adjacent control sizes, derived from the active --control-height. The \xB1step * is scaled too so the whole control ladder stays proportional under --scaling."},{name:"--control-gap-sm",value:"var(--space-inline-xs)",description:"Adjacent control sizes, derived from the active --control-height. The \xB1step * is scaled too so the whole control ladder stays proportional under --scaling."},{name:"--control-radius",value:"var(--radius)",description:"Adjacent control sizes, derived from the active --control-height. The \xB1step * is scaled too so the whole control ladder stays proportional under --scaling."},{name:"--button-radius",value:"var(--radius-md)",description:"Button corner radius \u2014 defaults to the button's historical `rounded-md` so nothing * changes by default, but is its OWN knob so a service theme can retune the button * radius INDEPENDENTLY of input/control radius (issue #124)."},{name:"--control-font-size",value:"var(--font-size-base)",description:"Control surface knobs \u2014 font-size, border width and resting shadow of every * `.ui-control` (input / picker trigger). Tokenised so a service theme tunes them * once instead of each component hard-coding Tailwind utilities. Defaults preserve * the historical look (font-size-base, 1px border, shadow-xs)."},{name:"--control-border-width",value:"1px",description:"Control surface knobs \u2014 font-size, border width and resting shadow of every * `.ui-control` (input / picker trigger). Tokenised so a service theme tunes them * once instead of each component hard-coding Tailwind utilities. Defaults preserve * the historical look (font-size-base, 1px border, shadow-xs)."},{name:"--control-shadow",value:"var(--shadow-xs)",description:"Control surface knobs \u2014 font-size, border width and resting shadow of every * `.ui-control` (input / picker trigger). Tokenised so a service theme tunes them * once instead of each component hard-coding Tailwind utilities. Defaults preserve * the historical look (font-size-base, 1px border, shadow-xs)."},{name:"--control-icon-size",value:"calc(1rem * var(--scaling))",description:"Control surface knobs \u2014 font-size, border width and resting shadow of every * `.ui-control` (input / picker trigger). Tokenised so a service theme tunes them * once instead of each component hard-coding Tailwind utilities. Defaults preserve * the historical look (font-size-base, 1px border, shadow-xs)."},{name:"--control-icon-size-sm",value:"calc(0.875rem * var(--scaling))",description:"Control surface knobs \u2014 font-size, border width and resting shadow of every * `.ui-control` (input / picker trigger). Tokenised so a service theme tunes them * once instead of each component hard-coding Tailwind utilities. Defaults preserve * the historical look (font-size-base, 1px border, shadow-xs)."},{name:"--control-focus-ring-width",value:"var(--focus-ring-width)",description:"Control surface knobs \u2014 font-size, border width and resting shadow of every * `.ui-control` (input / picker trigger). Tokenised so a service theme tunes them * once instead of each component hard-coding Tailwind utilities. Defaults preserve * the historical look (font-size-base, 1px border, shadow-xs)."},{name:"--checkbox-size",value:"calc(1rem * var(--scaling))",description:"Control surface knobs \u2014 font-size, border width and resting shadow of every * `.ui-control` (input / picker trigger). Tokenised so a service theme tunes them * once instead of each component hard-coding Tailwind utilities. Defaults preserve * the historical look (font-size-base, 1px border, shadow-xs)."},{name:"--checkbox-size-compact",value:"0.875rem",description:"Control surface knobs \u2014 font-size, border width and resting shadow of every * `.ui-control` (input / picker trigger). Tokenised so a service theme tunes them * once instead of each component hard-coding Tailwind utilities. Defaults preserve * the historical look (font-size-base, 1px border, shadow-xs)."},{name:"--checkbox-size-comfortable",value:"1.125rem",description:"Control surface knobs \u2014 font-size, border width and resting shadow of every * `.ui-control` (input / picker trigger). Tokenised so a service theme tunes them * once instead of each component hard-coding Tailwind utilities. Defaults preserve * the historical look (font-size-base, 1px border, shadow-xs)."},{name:"--choice-gap",value:"var(--space-inline-sm)",description:"Control surface knobs \u2014 font-size, border width and resting shadow of every * `.ui-control` (input / picker trigger). Tokenised so a service theme tunes them * once instead of each component hard-coding Tailwind utilities. Defaults preserve * the historical look (font-size-base, 1px border, shadow-xs)."},{name:"--choice-group-gap-x",value:"var(--space-6)",description:"Control surface knobs \u2014 font-size, border width and resting shadow of every * `.ui-control` (input / picker trigger). Tokenised so a service theme tunes them * once instead of each component hard-coding Tailwind utilities. Defaults preserve * the historical look (font-size-base, 1px border, shadow-xs)."},{name:"--choice-group-gap-y",value:"var(--space-3)",description:"Control surface knobs \u2014 font-size, border width and resting shadow of every * `.ui-control` (input / picker trigger). Tokenised so a service theme tunes them * once instead of each component hard-coding Tailwind utilities. Defaults preserve * the historical look (font-size-base, 1px border, shadow-xs)."},{name:"--choice-description-gap",value:"0.125rem",description:"Control surface knobs \u2014 font-size, border width and resting shadow of every * `.ui-control` (input / picker trigger). Tokenised so a service theme tunes them * once instead of each component hard-coding Tailwind utilities. Defaults preserve * the historical look (font-size-base, 1px border, shadow-xs)."},{name:"--choice-control-offset",value:"0.125rem",description:"Control surface knobs \u2014 font-size, border width and resting shadow of every * `.ui-control` (input / picker trigger). Tokenised so a service theme tunes them * once instead of each component hard-coding Tailwind utilities. Defaults preserve * the historical look (font-size-base, 1px border, shadow-xs)."},{name:"--switch-width",value:"calc(2.25rem * var(--scaling))",description:"Control surface knobs \u2014 font-size, border width and resting shadow of every * `.ui-control` (input / picker trigger). Tokenised so a service theme tunes them * once instead of each component hard-coding Tailwind utilities. Defaults preserve * the historical look (font-size-base, 1px border, shadow-xs)."},{name:"--switch-width-compact",value:"2rem",description:"Control surface knobs \u2014 font-size, border width and resting shadow of every * `.ui-control` (input / picker trigger). Tokenised so a service theme tunes them * once instead of each component hard-coding Tailwind utilities. Defaults preserve * the historical look (font-size-base, 1px border, shadow-xs)."},{name:"--switch-width-comfortable",value:"2.5rem",description:"Control surface knobs \u2014 font-size, border width and resting shadow of every * `.ui-control` (input / picker trigger). Tokenised so a service theme tunes them * once instead of each component hard-coding Tailwind utilities. Defaults preserve * the historical look (font-size-base, 1px border, shadow-xs)."},{name:"--switch-height",value:"calc(1.25rem * var(--scaling))",description:"Control surface knobs \u2014 font-size, border width and resting shadow of every * `.ui-control` (input / picker trigger). Tokenised so a service theme tunes them * once instead of each component hard-coding Tailwind utilities. Defaults preserve * the historical look (font-size-base, 1px border, shadow-xs)."},{name:"--switch-height-compact",value:"1.125rem",description:"Control surface knobs \u2014 font-size, border width and resting shadow of every * `.ui-control` (input / picker trigger). Tokenised so a service theme tunes them * once instead of each component hard-coding Tailwind utilities. Defaults preserve * the historical look (font-size-base, 1px border, shadow-xs)."},{name:"--switch-height-comfortable",value:"1.375rem",description:"Control surface knobs \u2014 font-size, border width and resting shadow of every * `.ui-control` (input / picker trigger). Tokenised so a service theme tunes them * once instead of each component hard-coding Tailwind utilities. Defaults preserve * the historical look (font-size-base, 1px border, shadow-xs)."},{name:"--switch-thumb-size",value:"calc(1rem * var(--scaling))",description:"Control surface knobs \u2014 font-size, border width and resting shadow of every * `.ui-control` (input / picker trigger). Tokenised so a service theme tunes them * once instead of each component hard-coding Tailwind utilities. Defaults preserve * the historical look (font-size-base, 1px border, shadow-xs)."},{name:"--switch-thumb-size-compact",value:"0.875rem",description:"Control surface knobs \u2014 font-size, border width and resting shadow of every * `.ui-control` (input / picker trigger). Tokenised so a service theme tunes them * once instead of each component hard-coding Tailwind utilities. Defaults preserve * the historical look (font-size-base, 1px border, shadow-xs)."},{name:"--switch-thumb-size-comfortable",value:"1.125rem",description:"Control surface knobs \u2014 font-size, border width and resting shadow of every * `.ui-control` (input / picker trigger). Tokenised so a service theme tunes them * once instead of each component hard-coding Tailwind utilities. Defaults preserve * the historical look (font-size-base, 1px border, shadow-xs)."},{name:"--switch-thumb-translate",value:"calc(1rem * var(--scaling))",description:"Control surface knobs \u2014 font-size, border width and resting shadow of every * `.ui-control` (input / picker trigger). Tokenised so a service theme tunes them * once instead of each component hard-coding Tailwind utilities. Defaults preserve * the historical look (font-size-base, 1px border, shadow-xs)."},{name:"--switch-thumb-translate-compact",value:"0.875rem",description:"Control surface knobs \u2014 font-size, border width and resting shadow of every * `.ui-control` (input / picker trigger). Tokenised so a service theme tunes them * once instead of each component hard-coding Tailwind utilities. Defaults preserve * the historical look (font-size-base, 1px border, shadow-xs)."},{name:"--switch-thumb-translate-comfortable",value:"1.125rem",description:"Control surface knobs \u2014 font-size, border width and resting shadow of every * `.ui-control` (input / picker trigger). Tokenised so a service theme tunes them * once instead of each component hard-coding Tailwind utilities. Defaults preserve * the historical look (font-size-base, 1px border, shadow-xs)."},{name:"--slider-track-height",value:"0.375rem",description:"Control surface knobs \u2014 font-size, border width and resting shadow of every * `.ui-control` (input / picker trigger). Tokenised so a service theme tunes them * once instead of each component hard-coding Tailwind utilities. Defaults preserve * the historical look (font-size-base, 1px border, shadow-xs)."},{name:"--slider-thumb-size",value:"1rem",description:"Control surface knobs \u2014 font-size, border width and resting shadow of every * `.ui-control` (input / picker trigger). Tokenised so a service theme tunes them * once instead of each component hard-coding Tailwind utilities. Defaults preserve * the historical look (font-size-base, 1px border, shadow-xs)."},{name:"--otp-slot-size",value:"initial",description:'InputOTP slot box (gh#233). Its own knob so an auth surface can widen the 6-slot challenge row * to fill a wide panel WITHOUT re-scoping --control-height (which would resize every other * control in the same card). Declared `initial` \u2014 the tier-mirror form of the role-mirror rule in * docs/TOKENS.md: the default must resolve at the CALL SITE (`var(--otp-slot-size, * var(--control-height))`), because `--otp-slot-size: var(--control-height)` here would FREEZE at * the :root tier (32px) and an OTP row inside `.ui-auth-shell[data-variant="canonical"]`, which * re-scopes --control-height to 36px, would silently shrink. Verified in Chromium: 36px before * and after. A service opts in with a NAMED tier (`var(--control-height-lg)`), never a calc.'},{name:"--checkbox-checked-background",value:"initial",description:'Checked/on/active fills \u2014 `initial` so the --primary default re-resolves at the call site * under a scoped theme (a :root binding to var(--primary) freezes at the :root value and a scoped * [data-tenant] override of --primary never reaches it). A service retints the "selected" state * by overriding these directly. Defaults = hsl(var(--primary)) \xB7 slider track 0.2\u03B1.'},{name:"--switch-checked-background",value:"initial",description:'Checked/on/active fills \u2014 `initial` so the --primary default re-resolves at the call site * under a scoped theme (a :root binding to var(--primary) freezes at the :root value and a scoped * [data-tenant] override of --primary never reaches it). A service retints the "selected" state * by overriding these directly. Defaults = hsl(var(--primary)) \xB7 slider track 0.2\u03B1.'},{name:"--toggle-on-background",value:"initial",description:'Checked/on/active fills \u2014 `initial` so the --primary default re-resolves at the call site * under a scoped theme (a :root binding to var(--primary) freezes at the :root value and a scoped * [data-tenant] override of --primary never reaches it). A service retints the "selected" state * by overriding these directly. Defaults = hsl(var(--primary)) \xB7 slider track 0.2\u03B1.'},{name:"--slider-track-background",value:"initial",description:'Checked/on/active fills \u2014 `initial` so the --primary default re-resolves at the call site * under a scoped theme (a :root binding to var(--primary) freezes at the :root value and a scoped * [data-tenant] override of --primary never reaches it). A service retints the "selected" state * by overriding these directly. Defaults = hsl(var(--primary)) \xB7 slider track 0.2\u03B1.'},{name:"--slider-range-background",value:"initial",description:'Checked/on/active fills \u2014 `initial` so the --primary default re-resolves at the call site * under a scoped theme (a :root binding to var(--primary) freezes at the :root value and a scoped * [data-tenant] override of --primary never reaches it). A service retints the "selected" state * by overriding these directly. Defaults = hsl(var(--primary)) \xB7 slider track 0.2\u03B1.'},{name:"--color-picker-input-width",value:"6.5rem",description:'Checked/on/active fills \u2014 `initial` so the --primary default re-resolves at the call site * under a scoped theme (a :root binding to var(--primary) freezes at the :root value and a scoped * [data-tenant] override of --primary never reaches it). A service retints the "selected" state * by overriding these directly. Defaults = hsl(var(--primary)) \xB7 slider track 0.2\u03B1.'},{name:"--command-list-max-height",value:"min(300px, 50vh)",description:'Checked/on/active fills \u2014 `initial` so the --primary default re-resolves at the call site * under a scoped theme (a :root binding to var(--primary) freezes at the :root value and a scoped * [data-tenant] override of --primary never reaches it). A service retints the "selected" state * by overriding these directly. Defaults = hsl(var(--primary)) \xB7 slider track 0.2\u03B1.'},{name:"--command-input-padding-x",value:"var(--space-3)",description:'Checked/on/active fills \u2014 `initial` so the --primary default re-resolves at the call site * under a scoped theme (a :root binding to var(--primary) freezes at the :root value and a scoped * [data-tenant] override of --primary never reaches it). A service retints the "selected" state * by overriding these directly. Defaults = hsl(var(--primary)) \xB7 slider track 0.2\u03B1.'},{name:"--command-group-padding",value:"var(--space-1)",description:'Checked/on/active fills \u2014 `initial` so the --primary default re-resolves at the call site * under a scoped theme (a :root binding to var(--primary) freezes at the :root value and a scoped * [data-tenant] override of --primary never reaches it). A service retints the "selected" state * by overriding these directly. Defaults = hsl(var(--primary)) \xB7 slider track 0.2\u03B1.'},{name:"--command-item-padding-y",value:"var(--space-2)",description:'Checked/on/active fills \u2014 `initial` so the --primary default re-resolves at the call site * under a scoped theme (a :root binding to var(--primary) freezes at the :root value and a scoped * [data-tenant] override of --primary never reaches it). A service retints the "selected" state * by overriding these directly. Defaults = hsl(var(--primary)) \xB7 slider track 0.2\u03B1.'},{name:"--command-item-padding-x",value:"var(--space-2)",description:'Checked/on/active fills \u2014 `initial` so the --primary default re-resolves at the call site * under a scoped theme (a :root binding to var(--primary) freezes at the :root value and a scoped * [data-tenant] override of --primary never reaches it). A service retints the "selected" state * by overriding these directly. Defaults = hsl(var(--primary)) \xB7 slider track 0.2\u03B1.'},{name:"--command-palette-width",value:"35rem",description:'Checked/on/active fills \u2014 `initial` so the --primary default re-resolves at the call site * under a scoped theme (a :root binding to var(--primary) freezes at the :root value and a scoped * [data-tenant] override of --primary never reaches it). A service retints the "selected" state * by overriding these directly. Defaults = hsl(var(--primary)) \xB7 slider track 0.2\u03B1.'},{name:"--command-palette-inset-top",value:"calc(var(--space-6) * 4)",description:'Checked/on/active fills \u2014 `initial` so the --primary default re-resolves at the call site * under a scoped theme (a :root binding to var(--primary) freezes at the :root value and a scoped * [data-tenant] override of --primary never reaches it). A service retints the "selected" state * by overriding these directly. Defaults = hsl(var(--primary)) \xB7 slider track 0.2\u03B1.'},{name:"--command-palette-viewport-inset",value:"var(--space-3)",description:'Checked/on/active fills \u2014 `initial` so the --primary default re-resolves at the call site * under a scoped theme (a :root binding to var(--primary) freezes at the :root value and a scoped * [data-tenant] override of --primary never reaches it). A service retints the "selected" state * by overriding these directly. Defaults = hsl(var(--primary)) \xB7 slider track 0.2\u03B1.'},{name:"--command-palette-hint-padding-y",value:"var(--space-2)",description:'Checked/on/active fills \u2014 `initial` so the --primary default re-resolves at the call site * under a scoped theme (a :root binding to var(--primary) freezes at the :root value and a scoped * [data-tenant] override of --primary never reaches it). A service retints the "selected" state * by overriding these directly. Defaults = hsl(var(--primary)) \xB7 slider track 0.2\u03B1.'},{name:"--command-palette-hint-padding-x",value:"var(--space-4)",description:'Checked/on/active fills \u2014 `initial` so the --primary default re-resolves at the call site * under a scoped theme (a :root binding to var(--primary) freezes at the :root value and a scoped * [data-tenant] override of --primary never reaches it). A service retints the "selected" state * by overriding these directly. Defaults = hsl(var(--primary)) \xB7 slider track 0.2\u03B1.'},{name:"--command-palette-hint-gap",value:"var(--space-4)",description:'Checked/on/active fills \u2014 `initial` so the --primary default re-resolves at the call site * under a scoped theme (a :root binding to var(--primary) freezes at the :root value and a scoped * [data-tenant] override of --primary never reaches it). A service retints the "selected" state * by overriding these directly. Defaults = hsl(var(--primary)) \xB7 slider track 0.2\u03B1.'},{name:"--search-input-edge-inset",value:"var(--space-3)",description:'Checked/on/active fills \u2014 `initial` so the --primary default re-resolves at the call site * under a scoped theme (a :root binding to var(--primary) freezes at the :root value and a scoped * [data-tenant] override of --primary never reaches it). A service retints the "selected" state * by overriding these directly. Defaults = hsl(var(--primary)) \xB7 slider track 0.2\u03B1.'},{name:"--search-input-start-padding",value:"calc( var(--search-input-edge-inset) + var(--control-icon-size) + var(--control-gap) )",description:'Checked/on/active fills \u2014 `initial` so the --primary default re-resolves at the call site * under a scoped theme (a :root binding to var(--primary) freezes at the :root value and a scoped * [data-tenant] override of --primary never reaches it). A service retints the "selected" state * by overriding these directly. Defaults = hsl(var(--primary)) \xB7 slider track 0.2\u03B1.'},{name:"--search-input-end-padding",value:"calc( var(--search-input-edge-inset) + var(--control-icon-size) + var(--control-gap) )",description:'Checked/on/active fills \u2014 `initial` so the --primary default re-resolves at the call site * under a scoped theme (a :root binding to var(--primary) freezes at the :root value and a scoped * [data-tenant] override of --primary never reaches it). A service retints the "selected" state * by overriding these directly. Defaults = hsl(var(--primary)) \xB7 slider track 0.2\u03B1.'},{name:"--choice-description-font-size",value:"var(--font-size-xs)",description:'Checked/on/active fills \u2014 `initial` so the --primary default re-resolves at the call site * under a scoped theme (a :root binding to var(--primary) freezes at the :root value and a scoped * [data-tenant] override of --primary never reaches it). A service retints the "selected" state * by overriding these directly. Defaults = hsl(var(--primary)) \xB7 slider track 0.2\u03B1.'},{name:"--color-picker-hex-font-size",value:"var(--font-size-xs)",description:'Checked/on/active fills \u2014 `initial` so the --primary default re-resolves at the call site * under a scoped theme (a :root binding to var(--primary) freezes at the :root value and a scoped * [data-tenant] override of --primary never reaches it). A service retints the "selected" state * by overriding these directly. Defaults = hsl(var(--primary)) \xB7 slider track 0.2\u03B1.'},{name:"--command-group-heading-font-size",value:"var(--font-size-xs)",description:'Checked/on/active fills \u2014 `initial` so the --primary default re-resolves at the call site * under a scoped theme (a :root binding to var(--primary) freezes at the :root value and a scoped * [data-tenant] override of --primary never reaches it). A service retints the "selected" state * by overriding these directly. Defaults = hsl(var(--primary)) \xB7 slider track 0.2\u03B1.'},{name:"--search-input-label-font-size",value:"var(--font-size-xs)",description:'Checked/on/active fills \u2014 `initial` so the --primary default re-resolves at the call site * under a scoped theme (a :root binding to var(--primary) freezes at the :root value and a scoped * [data-tenant] override of --primary never reaches it). A service retints the "selected" state * by overriding these directly. Defaults = hsl(var(--primary)) \xB7 slider track 0.2\u03B1.'},{name:"--tag-input-chip-font-size",value:"var(--font-size-xs)",description:'Checked/on/active fills \u2014 `initial` so the --primary default re-resolves at the call site * under a scoped theme (a :root binding to var(--primary) freezes at the :root value and a scoped * [data-tenant] override of --primary never reaches it). A service retints the "selected" state * by overriding these directly. Defaults = hsl(var(--primary)) \xB7 slider track 0.2\u03B1.'},{name:"--toggle-sm-font-size",value:"var(--font-size-xs)",description:'Checked/on/active fills \u2014 `initial` so the --primary default re-resolves at the call site * under a scoped theme (a :root binding to var(--primary) freezes at the :root value and a scoped * [data-tenant] override of --primary never reaches it). A service retints the "selected" state * by overriding these directly. Defaults = hsl(var(--primary)) \xB7 slider track 0.2\u03B1.'},{name:"--button-sm-font-size",value:"var(--font-size-xs)",description:'Checked/on/active fills \u2014 `initial` so the --primary default re-resolves at the call site * under a scoped theme (a :root binding to var(--primary) freezes at the :root value and a scoped * [data-tenant] override of --primary never reaches it). A service retints the "selected" state * by overriding these directly. Defaults = hsl(var(--primary)) \xB7 slider track 0.2\u03B1.'},{name:"--control-height-compact",value:"2.75rem",description:"Rule #24 \u2014 on touch devices (coarse pointer) interactive controls keep a \u226544px tap target * regardless of density; desktop (fine pointer) keeps the compact heights above. --control-height * resolves through these via var(), so inputs/buttons/selects/table rows all bump together."},{name:"--control-height-default",value:"2.75rem",description:"Rule #24 \u2014 on touch devices (coarse pointer) interactive controls keep a \u226544px tap target * regardless of density; desktop (fine pointer) keeps the compact heights above. --control-height * resolves through these via var(), so inputs/buttons/selects/table rows all bump together."},{name:"--progress-label-font-size",value:"var(--font-size-xs)",description:"Data-display component tokens \u2014 small-by-design text knobs (rule #45/#46)."},{name:"--tree-item-title-font-size",value:"var(--font-size-xs)",description:"Data-display component tokens \u2014 small-by-design text knobs (rule #45/#46)."},{name:"--tree-item-description-font-size",value:"var(--font-size-xs)",description:"Data-display component tokens \u2014 small-by-design text knobs (rule #45/#46)."},{name:"--timeline-note-font-size",value:"var(--font-size-xs)",description:"Data-display component tokens \u2014 small-by-design text knobs (rule #45/#46)."},{name:"--avatar-background",value:"initial",description:"Avatar surface \u2014 `initial` so the --muted default re-resolves at the call site under a scoped theme (a :root binding to a role var freezes at :root; a scoped role override never reaches it). A service re-tints the placeholder fill once (e.g. --avatar-background: hsl(var(--accent))). Default = hsl(var(--muted))."},{name:"--avatar-tint",value:"transparent",description:"Optional role wash over the avatar (default transparent = invisible, rule #44). Painted as an overlay so a service sets --avatar-tint: hsl(var(--primary) / 0.08)."},{name:"--avatar-square-radius",value:"var(--radius-lg)",description:'Entity-header mark \u2014 `<Avatar shape="square">` (gh#249): the compact rounded SQUARE an organization/service header uses, as opposed to the round person avatar. Every value is a knob (rule #45) so a service matches the mark to its own grid without a className override. --avatar-square-background / --avatar-square-foreground are role-mirror knobs: `initial` so the --primary / --primary-foreground defaults re-resolve at the CALL SITE under a scoped [data-tenant]/.dark theme (a :root binding to a role var would freeze at the :root value). Defaults = --radius-lg corners \xB7 --control-height box (same as the circle avatar, so swapping shape never reflows a header) \xB7 hsl(var(--primary)) fill \xB7 hsl(var(--primary-foreground)) glyph.'},{name:"--avatar-square-size",value:"var(--control-height)",description:'Entity-header mark \u2014 `<Avatar shape="square">` (gh#249): the compact rounded SQUARE an organization/service header uses, as opposed to the round person avatar. Every value is a knob (rule #45) so a service matches the mark to its own grid without a className override. --avatar-square-background / --avatar-square-foreground are role-mirror knobs: `initial` so the --primary / --primary-foreground defaults re-resolve at the CALL SITE under a scoped [data-tenant]/.dark theme (a :root binding to a role var would freeze at the :root value). Defaults = --radius-lg corners \xB7 --control-height box (same as the circle avatar, so swapping shape never reflows a header) \xB7 hsl(var(--primary)) fill \xB7 hsl(var(--primary-foreground)) glyph.'},{name:"--avatar-square-background",value:"initial",description:'Entity-header mark \u2014 `<Avatar shape="square">` (gh#249): the compact rounded SQUARE an organization/service header uses, as opposed to the round person avatar. Every value is a knob (rule #45) so a service matches the mark to its own grid without a className override. --avatar-square-background / --avatar-square-foreground are role-mirror knobs: `initial` so the --primary / --primary-foreground defaults re-resolve at the CALL SITE under a scoped [data-tenant]/.dark theme (a :root binding to a role var would freeze at the :root value). Defaults = --radius-lg corners \xB7 --control-height box (same as the circle avatar, so swapping shape never reflows a header) \xB7 hsl(var(--primary)) fill \xB7 hsl(var(--primary-foreground)) glyph.'},{name:"--avatar-square-foreground",value:"initial",description:'Entity-header mark \u2014 `<Avatar shape="square">` (gh#249): the compact rounded SQUARE an organization/service header uses, as opposed to the round person avatar. Every value is a knob (rule #45) so a service matches the mark to its own grid without a className override. --avatar-square-background / --avatar-square-foreground are role-mirror knobs: `initial` so the --primary / --primary-foreground defaults re-resolve at the CALL SITE under a scoped [data-tenant]/.dark theme (a :root binding to a role var would freeze at the :root value). Defaults = --radius-lg corners \xB7 --control-height box (same as the circle avatar, so swapping shape never reflows a header) \xB7 hsl(var(--primary)) fill \xB7 hsl(var(--primary-foreground)) glyph.'},{name:"--progress-track-background",value:"initial",description:"Progress track + fill \u2014 `initial` so the role defaults re-resolve under a scoped theme. Track reads --secondary, fill reads --success; a service re-tones once. Defaults = hsl(var(--secondary)) track \xB7 hsl(var(--success)) fill."},{name:"--progress-fill-background",value:"initial",description:"Progress track + fill \u2014 `initial` so the role defaults re-resolve under a scoped theme. Track reads --secondary, fill reads --success; a service re-tones once. Defaults = hsl(var(--secondary)) track \xB7 hsl(var(--success)) fill."},{name:"--timeline-dot-done-background",value:"initial",description:"Timeline accents \u2014 `initial` so the dot/line role defaults re-resolve under a scoped theme. Defaults = hsl(var(--success)) done \xB7 hsl(var(--primary)) current/line."},{name:"--timeline-dot-current-background",value:"initial",description:"Timeline accents \u2014 `initial` so the dot/line role defaults re-resolve under a scoped theme. Defaults = hsl(var(--success)) done \xB7 hsl(var(--primary)) current/line."},{name:"--timeline-line-completed-background",value:"initial",description:"Timeline accents \u2014 `initial` so the dot/line role defaults re-resolve under a scoped theme. Defaults = hsl(var(--success)) done \xB7 hsl(var(--primary)) current/line."},{name:"--tree-item-active-border",value:"initial",description:"Tree active item \u2014 border + soft bg tint over the --primary role. `initial` so the default re-resolves under a scoped theme. Defaults = hsl(var(--primary) / 0.3) border \xB7 0.05 fill."},{name:"--tree-item-active-background",value:"initial",description:"Tree active item \u2014 border + soft bg tint over the --primary role. `initial` so the default re-resolves under a scoped theme. Defaults = hsl(var(--primary) / 0.3) border \xB7 0.05 fill."},{name:"--qr-code-foreground",value:"0 0% 0%",description:"QR codes stay scanner-safe in light and dark application themes. Consumers may override these component tokens only when the resulting pair retains strong contrast."},{name:"--qr-code-background",value:"0 0% 100%",description:"QR codes stay scanner-safe in light and dark application themes. Consumers may override these component tokens only when the resulting pair retains strong contrast."},{name:"--qr-code-size-xs",value:"6rem",description:"QR codes stay scanner-safe in light and dark application themes. Consumers may override these component tokens only when the resulting pair retains strong contrast."},{name:"--qr-code-size-sm",value:"8rem",description:"QR codes stay scanner-safe in light and dark application themes. Consumers may override these component tokens only when the resulting pair retains strong contrast."},{name:"--qr-code-size-md",value:"10rem",description:"QR codes stay scanner-safe in light and dark application themes. Consumers may override these component tokens only when the resulting pair retains strong contrast."},{name:"--qr-code-size-lg",value:"12.5rem",description:"QR codes stay scanner-safe in light and dark application themes. Consumers may override these component tokens only when the resulting pair retains strong contrast."},{name:"--password-strength-score-font-size",value:"var(--font-size-xs)",description:"Data-entry component tokens \u2014 small-by-design text knobs (rule #45/#46)."},{name:"--password-strength-checklist-font-size",value:"var(--font-size-xs)",description:"Data-entry component tokens \u2014 small-by-design text knobs (rule #45/#46)."},{name:"--descriptions-label-width",value:"8rem",description:'Width of the label column when <Descriptions layout="horizontal">. Labels align to this * shared column so the values line up (the horizontal-detail look, mirroring <Form layout>). * A rem value gives a fixed aligned column; set `max-content` to size each label to its text. * (rule #44/#45 \u2014 a service theme tunes it here instead of forking CSS.)'},{name:"--email-shell-width",value:"480px",description:"\u2500\u2500 Shell \u2014 the 480px transactional-email geometry \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"},{name:"--email-shell-padding",value:"32px",description:"canonical transactional card width"},{name:"--email-shell-page-padding",value:"24px",description:"card inset \u2014 mirrors --space-8 (2rem)"},{name:"--email-shell-border-width",value:"1px",description:"gutter between the client viewport and the card (--space-6)"},{name:"--email-card-radius",value:"10px",description:"card edge \u2014 mirrors the 1px --border hairline"},{name:"--email-card-reference-height",value:"407px",description:"mirrors --card-radius (--radius-xl = 6px \xD7 \u03C6 \u2248 10px)"},{name:"--email-stack-gap",value:"24px",description:"canonical invitation reference measures 480\xD7407"},{name:"--email-stack-gap-sm",value:"12px",description:"block rhythm \u2014 mirrors --space-stack-lg (1.5rem)"},{name:"--email-mark-width",value:"22px",description:'The mark ARTWORK is the same 32\xD732 capsule + glyph `<Logo mark="godx" />` paints; only the * RENDERED BOX differs. The canonical SCR-302 header sets it at 22px next to a 13px/700 "GoDX" * wordmark, which is smaller than the web --logo-godx-size (2rem) \u2014 an email header carries the * identity, not the navigation, so the lockup sits quieter (gh#250).'},{name:"--email-mark-height",value:"22px",description:"canonical SCR-302 header lockup mark box"},{name:"--email-mark-gap",value:"8px",description:"canonical SCR-302 header lockup mark box"},{name:"--email-wordmark-font-size",value:"13px",description:"mark \u2194 wordmark \u2014 mirrors --logo-wordmark-gap (--space-2)"},{name:"--email-wordmark-font-weight",value:"700",description:"canonical wordmark size (\u2248 --font-size-xs 12.5px)"},{name:"--email-font-family-sans",value:'"M PLUS 2", "Hiragino Sans", "Hiragino Kaku Gothic ProN", "Yu Gothic Medium", YuGothic, "Noto Sans JP", Meiryo, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, system-ui, Arial, sans-serif',description:'The DXS canonical face is M PLUS 2 (see src/styles/fonts.css). Email clients honour NO * @font-face, so the stack ships the face NAME first and degrades through the platform JP faces * to a generic \u2014 a client with M PLUS 2 installed (or a webmail that already loaded it) renders * the canonical face, everything else lands on Hiragino (macOS/iOS) \u2192 Yu Gothic (Windows) \u2192 * Noto Sans JP \u2192 Meiryo \u2192 the system UI face \u2192 Arial \u2192 sans-serif. Family names are SINGLE-quoted * on export (the generator normalises) because an inline `style="\u2026"` attribute is double-quoted; * only ASCII aliases are listed, since a non-ASCII family name does not survive every transfer * encoding.'},{name:"--email-font-family-mono",value:"ui-monospace, SFMono-Regular, Menlo, Consolas, monospace",description:"Tabular/reference strings \u2014 invoice ids, masked card numbers, ISO dates, amounts \u2014 are set in * the mono face in the canonical card. Mirrors --font-family-mono."},{name:"--email-body-font-size",value:"14px",description:"Tabular/reference strings \u2014 invoice ids, masked card numbers, ISO dates, amounts \u2014 are set in * the mono face in the canonical card. Mirrors --font-family-mono."},{name:"--email-body-line-height",value:"1.9",description:"1.9, NOT the web --line-height-body (1.7) \u2014 the canonical email card runs its JP body copy * looser than a screen, because a mail client gives the reader no density control."},{name:"--email-body-font-weight",value:"400",description:"1.9, NOT the web --line-height-body (1.7) \u2014 the canonical email card runs its JP body copy * looser than a screen, because a mail client gives the reader no density control."},{name:"--email-heading-font-size",value:"17px",description:"mirrors --font-weight-normal"},{name:"--email-heading-line-height",value:"1.7",description:"canonical card title (\u2248 --heading-h2, base \xD7 \u03C6^\xBC\xB2 \u2248 17.6px)"},{name:"--email-heading-font-weight",value:"500",description:"the title shares the card's body leading, not a tight ramp"},{name:"--email-cta-height",value:"36px",description:"\u2500\u2500 Primary CTA \u2014 the one action button \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"},{name:"--email-cta-line-height",value:"36px",description:"mirrors --control-height-lg (2.25rem) \u2014 the canonical CTA box"},{name:"--email-cta-padding-x",value:"16px",description:"equal to the height: bulletproof vertical centring in Outlook"},{name:"--email-cta-radius",value:"6px",description:"mirrors --space-4"},{name:"--email-cta-font-size",value:"14px",description:"mirrors the --radius base (0.375rem)"},{name:"--email-cta-font-weight",value:"500",description:"mirrors --font-size-base"},{name:"--email-footer-font-size",value:"11px",description:"\u2500\u2500 Legal footer \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"},{name:"--email-footer-line-height",value:"1.8",description:"mirrors --font-size-2xs (base \xF7 \u03C6^\xBC\xB2 \u2248 11.1px)"},{name:"--email-footer-link-gap",value:"12px",description:"the legal band runs looser than the web --line-height-normal"},{name:"--email-footer-padding-top",value:"20px",description:"horizontal separation between footer links (--space-3)"},{name:"--email-footer-border-width",value:"1px",description:"space above the legal band"},{name:"--email-focus-border-width",value:"2px",description:"\u2500\u2500 Focus affordance (webmail preview panes that DO honour :focus) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"},{name:"--email-mobile-max-width",value:"520px",description:"\u2500\u2500 Mobile reflow \u2014 applied under the narrow media query and by inline fallbacks \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"},{name:"--email-mobile-width",value:"100%",description:"the reflow breakpoint (card width + both gutters)"},{name:"--email-mobile-padding",value:"20px",description:"the card goes fluid below the breakpoint"},{name:"--email-mobile-page-padding",value:"12px",description:"reduced card inset \u2014 mirrors --space-5"},{name:"--email-mobile-heading-font-size",value:"16px",description:"One step below the 17px desktop title. The canonical 390px reference raster is unusable (it is * 435px wide and already clips the card \u2014 dxs-platform/platform#496), so this is the library's * documented contract rather than a measured canonical value."},{name:"--email-mobile-cta-width",value:"100%",description:"One step below the 17px desktop title. The canonical 390px reference raster is unusable (it is * 435px wide and already clips the card \u2014 dxs-platform/platform#496), so this is the library's * documented contract rather than a measured canonical value."},{name:"--error-surface-max-width",value:"32rem",description:"Measure of the surface block. Caps the status code + body + metadata column so a long ja/vi * headline wraps at a readable width in BOTH shells, including the application shell where * PageContainer is much wider than the copy wants to be."},{name:"--error-surface-gap",value:"var(--space-3)",description:"Rhythm between the status code, the EmptyState body, the metadata list and the progress meter."},{name:"--error-surface-padding-block",value:"var(--space-10)",description:"Block padding of the surface inside the shell it is placed in. Desktop (1440/1024) default; the * 390 step steps it down so a phone loses no vertical space (see styles/shell-layout.css)."},{name:"--error-surface-padding-block-compact",value:"var(--space-6)",description:"Block padding of the surface inside the shell it is placed in. Desktop (1440/1024) default; the * 390 step steps it down so a phone loses no vertical space (see styles/shell-layout.css)."},{name:"--error-surface-brand-gap",value:"var(--space-2)",description:"Brand slot (a Logo) above the status code, system mode only."},{name:"--error-surface-meta-gap",value:"var(--space-1)",description:"Metadata description list: row rhythm and the label\u2194value gap inside one row."},{name:"--error-surface-meta-row-gap",value:"var(--space-2)",description:"Metadata description list: row rhythm and the label\u2194value gap inside one row."},{name:"--error-surface-meta-border",value:"none",description:"Chrome, default quiet (#44). A service opts in with * `--error-surface-meta-border: 1px solid hsl(var(--border));`."},{name:"--error-surface-meta-padding-block",value:"0",description:"Chrome, default quiet (#44). A service opts in with * `--error-surface-meta-border: 1px solid hsl(var(--border));`."},{name:"--error-surface-progress-max-width",value:"18rem",description:"Width of the maintenance progress meter \u2014 narrower than the measure, so it reads as metadata * rather than as the primary content of the page."},{name:"--sheet-width-default",value:"22.5rem",description:"Feedback primitive tokens: dialog, alert, empty state."},{name:"--sheet-overlay-background",value:"initial",description:"Overlay scrims \u2014 `initial` so the shared semantic --overlay-background re-resolves at the CALL * SITE under a scoped [data-tenant]/.dark theme (a :root binding to it freezes at :root and a * scoped override never reaches the overlay). Each surface takes a SHARE of that one scrim rather * than a private literal, so a service retints every backdrop with one --overlay-background while * the calibrated per-surface depth survives: a slide-in Sheet washes the page more lightly than a * modal Dialog. Defaults resolve to rgb(0 0 0 / 0.2) (sheet) and rgb(0 0 0 / 0.3) (dialog)."},{name:"--sheet-overlay-alpha",value:"40%",description:"Overlay scrims \u2014 `initial` so the shared semantic --overlay-background re-resolves at the CALL * SITE under a scoped [data-tenant]/.dark theme (a :root binding to it freezes at :root and a * scoped override never reaches the overlay). Each surface takes a SHARE of that one scrim rather * than a private literal, so a service retints every backdrop with one --overlay-background while * the calibrated per-surface depth survives: a slide-in Sheet washes the page more lightly than a * modal Dialog. Defaults resolve to rgb(0 0 0 / 0.2) (sheet) and rgb(0 0 0 / 0.3) (dialog)."},{name:"--dialog-width-default",value:"32rem",description:"Overlay scrims \u2014 `initial` so the shared semantic --overlay-background re-resolves at the CALL * SITE under a scoped [data-tenant]/.dark theme (a :root binding to it freezes at :root and a * scoped override never reaches the overlay). Each surface takes a SHARE of that one scrim rather * than a private literal, so a service retints every backdrop with one --overlay-background while * the calibrated per-surface depth survives: a slide-in Sheet washes the page more lightly than a * modal Dialog. Defaults resolve to rgb(0 0 0 / 0.2) (sheet) and rgb(0 0 0 / 0.3) (dialog)."},{name:"--dialog-viewport-inset",value:"1.5rem",description:"Overlay scrims \u2014 `initial` so the shared semantic --overlay-background re-resolves at the CALL * SITE under a scoped [data-tenant]/.dark theme (a :root binding to it freezes at :root and a * scoped override never reaches the overlay). Each surface takes a SHARE of that one scrim rather * than a private literal, so a service retints every backdrop with one --overlay-background while * the calibrated per-surface depth survives: a slide-in Sheet washes the page more lightly than a * modal Dialog. Defaults resolve to rgb(0 0 0 / 0.2) (sheet) and rgb(0 0 0 / 0.3) (dialog)."},{name:"--dialog-overlay-background",value:"initial",description:"Overlay scrims \u2014 `initial` so the shared semantic --overlay-background re-resolves at the CALL * SITE under a scoped [data-tenant]/.dark theme (a :root binding to it freezes at :root and a * scoped override never reaches the overlay). Each surface takes a SHARE of that one scrim rather * than a private literal, so a service retints every backdrop with one --overlay-background while * the calibrated per-surface depth survives: a slide-in Sheet washes the page more lightly than a * modal Dialog. Defaults resolve to rgb(0 0 0 / 0.2) (sheet) and rgb(0 0 0 / 0.3) (dialog)."},{name:"--dialog-overlay-alpha",value:"60%",description:"Overlay scrims \u2014 `initial` so the shared semantic --overlay-background re-resolves at the CALL * SITE under a scoped [data-tenant]/.dark theme (a :root binding to it freezes at :root and a * scoped override never reaches the overlay). Each surface takes a SHARE of that one scrim rather * than a private literal, so a service retints every backdrop with one --overlay-background while * the calibrated per-surface depth survives: a slide-in Sheet washes the page more lightly than a * modal Dialog. Defaults resolve to rgb(0 0 0 / 0.2) (sheet) and rgb(0 0 0 / 0.3) (dialog)."},{name:"--dialog-space-x",value:"var(--space-chrome-x)",description:"Dialog inset defaults to the shared global chrome tokens (override --space-chrome-* once for the * whole system, or --dialog-space-x/-y for dialogs only)."},{name:"--dialog-space-y",value:"var(--space-chrome-y)",description:"Dialog inset defaults to the shared global chrome tokens (override --space-chrome-* once for the * whole system, or --dialog-space-x/-y for dialogs only)."},{name:"--dialog-space-inset",value:"var(--dialog-space-y) var(--dialog-space-x)",description:"Dialog inset defaults to the shared global chrome tokens (override --space-chrome-* once for the * whole system, or --dialog-space-x/-y for dialogs only)."},{name:"--dialog-space-gap",value:"var(--space-stack-md)",description:"Dialog inset defaults to the shared global chrome tokens (override --space-chrome-* once for the * whole system, or --dialog-space-x/-y for dialogs only)."},{name:"--dialog-close-space-offset",value:"var(--space-4)",description:"Dialog inset defaults to the shared global chrome tokens (override --space-chrome-* once for the * whole system, or --dialog-space-x/-y for dialogs only)."},{name:"--alert-space-inset",value:"var(--space-section-active)",description:"Dialog inset defaults to the shared global chrome tokens (override --space-chrome-* once for the * whole system, or --dialog-space-x/-y for dialogs only)."},{name:"--alert-space-gap",value:"var(--space-inline-md)",description:"Dialog inset defaults to the shared global chrome tokens (override --space-chrome-* once for the * whole system, or --dialog-space-x/-y for dialogs only)."},{name:"--alert-inner-space-gap",value:"var(--space-stack-sm)",description:"Dialog inset defaults to the shared global chrome tokens (override --space-chrome-* once for the * whole system, or --dialog-space-x/-y for dialogs only)."},{name:"--alert-dismiss-space-offset",value:"var(--space-3)",description:"Dialog inset defaults to the shared global chrome tokens (override --space-chrome-* once for the * whole system, or --dialog-space-x/-y for dialogs only)."},{name:"--alert-bg-alpha",value:"0.05",description:"Soft (subtle) semantic tint ratios \u2014 themeable so a service can hit its exact spec * (a brand's success-bg/-border are often more present than the faint 5%/30% default)."},{name:"--alert-border-alpha",value:"0.3",description:"Soft (subtle) semantic tint ratios \u2014 themeable so a service can hit its exact spec * (a brand's success-bg/-border are often more present than the faint 5%/30% default)."},{name:"--dialog-content-glow",value:"0 0 0 0 transparent",description:"Brand glow layer for the raised dialog/sheet panel \u2014 invisible no-op at rest (rule #44). * Paired AFTER --shadow-lg in the surface box-shadow so a service can wash the overlay with the * global glow, e.g. --dialog-content-glow: var(--shadow-glow), with no markup change."},{name:"--empty-state-space-y",value:"var(--space-10)",description:"Brand glow layer for the raised dialog/sheet panel \u2014 invisible no-op at rest (rule #44). * Paired AFTER --shadow-lg in the surface box-shadow so a service can wash the overlay with the * global glow, e.g. --dialog-content-glow: var(--shadow-glow), with no markup change."},{name:"--empty-state-space-x",value:"var(--space-6)",description:"Brand glow layer for the raised dialog/sheet panel \u2014 invisible no-op at rest (rule #44). * Paired AFTER --shadow-lg in the surface box-shadow so a service can wash the overlay with the * global glow, e.g. --dialog-content-glow: var(--shadow-glow), with no markup change."},{name:"--empty-state-description-max-width",value:"28rem",description:"Measure of the description paragraph (rule #45 \u2014 a service-tunable constant gets a knob). * 28rem reads ~65 Latin characters per line; a JA/VI service that needs a shorter or longer * measure for its own copy retunes this once instead of forking `.ui-empty-state-description`."},{name:"--empty-state-section-space-y",value:"var(--space-6)",description:"Measure of the description paragraph (rule #45 \u2014 a service-tunable constant gets a knob). * 28rem reads ~65 Latin characters per line; a JA/VI service that needs a shorter or longer * measure for its own copy retunes this once instead of forking `.ui-empty-state-description`."},{name:"--empty-state-section-space-x",value:"var(--space-4)",description:"Measure of the description paragraph (rule #45 \u2014 a service-tunable constant gets a knob). * 28rem reads ~65 Latin characters per line; a JA/VI service that needs a shorter or longer * measure for its own copy retunes this once instead of forking `.ui-empty-state-description`."},{name:"--empty-state-compact-space-y",value:"var(--space-3)",description:"Measure of the description paragraph (rule #45 \u2014 a service-tunable constant gets a knob). * 28rem reads ~65 Latin characters per line; a JA/VI service that needs a shorter or longer * measure for its own copy retunes this once instead of forking `.ui-empty-state-description`."},{name:"--empty-state-compact-space-x",value:"var(--space-2)",description:"Measure of the description paragraph (rule #45 \u2014 a service-tunable constant gets a knob). * 28rem reads ~65 Latin characters per line; a JA/VI service that needs a shorter or longer * measure for its own copy retunes this once instead of forking `.ui-empty-state-description`."},{name:"--empty-state-icon-foreground",value:"initial",description:"EmptyState icon medallion colour \u2014 `initial` so the role defaults re-resolve at the call site * under a scoped theme (rule #44). A service recolours the glyph (--empty-state-icon-foreground) * or washes the medallion fill (--empty-state-icon-tint) without forking. * Defaults = hsl(var(--muted-foreground)) glyph \xB7 hsl(var(--muted)) fill."},{name:"--empty-state-icon-tint",value:"initial",description:"EmptyState icon medallion colour \u2014 `initial` so the role defaults re-resolve at the call site * under a scoped theme (rule #44). A service recolours the glyph (--empty-state-icon-foreground) * or washes the medallion fill (--empty-state-icon-tint) without forking. * Defaults = hsl(var(--muted-foreground)) glyph \xB7 hsl(var(--muted)) fill."},{name:"--skeleton-row-gap",value:"var(--space-stack-sm)",description:"EmptyState icon medallion colour \u2014 `initial` so the role defaults re-resolve at the call site * under a scoped theme (rule #44). A service recolours the glyph (--empty-state-icon-foreground) * or washes the medallion fill (--empty-state-icon-tint) without forking. * Defaults = hsl(var(--muted-foreground)) glyph \xB7 hsl(var(--muted)) fill."},{name:"--skeleton-cell-gap",value:"var(--space-inline-lg)",description:"EmptyState icon medallion colour \u2014 `initial` so the role defaults re-resolve at the call site * under a scoped theme (rule #44). A service recolours the glyph (--empty-state-icon-foreground) * or washes the medallion fill (--empty-state-icon-tint) without forking. * Defaults = hsl(var(--muted-foreground)) glyph \xB7 hsl(var(--muted)) fill."},{name:"--skeleton-card-inset",value:"var(--space-section-active)",description:"EmptyState icon medallion colour \u2014 `initial` so the role defaults re-resolve at the call site * under a scoped theme (rule #44). A service recolours the glyph (--empty-state-icon-foreground) * or washes the medallion fill (--empty-state-icon-tint) without forking. * Defaults = hsl(var(--muted-foreground)) glyph \xB7 hsl(var(--muted)) fill."},{name:"--skeleton-radius",value:"var(--radius)",description:"EmptyState icon medallion colour \u2014 `initial` so the role defaults re-resolve at the call site * under a scoped theme (rule #44). A service recolours the glyph (--empty-state-icon-foreground) * or washes the medallion fill (--empty-state-icon-tint) without forking. * Defaults = hsl(var(--muted-foreground)) glyph \xB7 hsl(var(--muted)) fill."},{name:"--skeleton-background",value:"initial",description:"Skeleton placeholder fill \u2014 `initial` so the --muted default re-resolves at the call site under * a scoped theme (a :root binding to a role var freezes at :root). A service tints the shimmer to * its surface (rule #44) without forking the keyframes. Default = hsl(var(--muted))."},{name:"--form-label-width",value:"max-content",description:"Width of the label column in horizontal/inline layout. A service theme sets * this once (e.g. 110px) to align every form to its design grid; the Form/ * FormField `labelWidth` prop overrides per form/field."},{name:"--form-label-gap",value:"var(--space-4)",description:"Column gap between the label and its control in horizontal/inline layout."},{name:"--legal-document-measure-max-width",value:"46rem",description:"Readable document measure. Caps the header, the section column and the footer so * a 1440px viewport still reads at ~75-90 characters per line."},{name:"--legal-document-column-gap",value:"var(--space-10)",description:"Rail \u2194 document column gutter (two-column mode only)."},{name:"--legal-document-toc-width",value:"15rem",description:"Sticky table-of-contents rail width (two-column mode only)."},{name:"--legal-document-toc-inset-block-start",value:"var(--space-6)",description:"Distance the pinned rail keeps from the top of the scrollport."},{name:"--legal-document-toc-max-height",value:"calc(100dvh - var(--space-12))",description:"Cap for a long contents list so the rail never outgrows the viewport."},{name:"--legal-document-toc-gap",value:"var(--space-1)",description:"Cap for a long contents list so the rail never outgrows the viewport."},{name:"--legal-document-toc-item-padding",value:"var(--space-1) var(--space-2)",description:"Cap for a long contents list so the rail never outgrows the viewport."},{name:"--legal-document-toc-item-radius",value:"var(--radius)",description:"Cap for a long contents list so the rail never outgrows the viewport."},{name:"--legal-document-toc-title-font-size",value:"var(--font-size-2xs)",description:"Cap for a long contents list so the rail never outgrows the viewport."},{name:"--legal-document-toc-font-size",value:"var(--font-size-xs)",description:"Cap for a long contents list so the rail never outgrows the viewport."},{name:"--legal-document-toc-marker-width",value:"2px",description:"Leading marker on the active contents entry \u2014 a non-colour affordance so the * active state is never colour-only (WCAG 1.4.1)."},{name:"--legal-document-nav-gap",value:"var(--space-4)",description:"Gap between the `documentNavigation` slot and the contents list inside the rail."},{name:"--legal-document-header-gap",value:"var(--space-2)",description:"Gap between the `documentNavigation` slot and the contents list inside the rail."},{name:"--legal-document-meta-gap",value:"var(--space-2)",description:"Gap between the `documentNavigation` slot and the contents list inside the rail."},{name:"--legal-document-meta-font-size",value:"var(--font-size-xs)",description:"Gap between the `documentNavigation` slot and the contents list inside the rail."},{name:"--legal-document-section-gap",value:"var(--space-10)",description:"Gap between the `documentNavigation` slot and the contents list inside the rail."},{name:"--legal-document-section-title-gap",value:"var(--space-3)",description:"Gap between the `documentNavigation` slot and the contents list inside the rail."},{name:"--legal-document-body-line-height",value:"1.8",description:"Long-form leading \u2014 legal prose reads looser than UI text."},{name:"--legal-document-scroll-offset",value:"var(--space-6)",description:"`scroll-margin-block-start` on every section: the offset a hash jump / anchor * activation leaves above the heading so it is not hidden under sticky chrome."},{name:"--legal-document-footer-gap",value:"var(--space-4)",description:"`scroll-margin-block-start` on every section: the offset a hash jump / anchor * activation leaves above the heading so it is not hidden under sticky chrome."},{name:"--legal-document-toc-border",value:"none",description:"\u2500\u2500 Chrome \u2014 quiet by default (rule #44) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"},{name:"--legal-document-header-border",value:"none",description:"\u2500\u2500 Chrome \u2014 quiet by default (rule #44) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"},{name:"--legal-document-footer-border",value:"none",description:"\u2500\u2500 Chrome \u2014 quiet by default (rule #44) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"},{name:"--legal-document-meta-foreground",value:"initial",description:"\u2500\u2500 Role-mirror knobs \u2014 `initial` here, role default at the call site \u2500\u2500\u2500\u2500\u2500\u2500\u2500"},{name:"--legal-document-summary-foreground",value:"initial",description:"default = hsl(var(--muted-foreground))"},{name:"--legal-document-toc-foreground",value:"initial",description:"default = hsl(var(--muted-foreground))"},{name:"--legal-document-toc-active-foreground",value:"initial",description:"default = hsl(var(--muted-foreground))"},{name:"--legal-document-toc-active-background",value:"initial",description:"default = hsl(var(--foreground))"},{name:"--legal-document-toc-marker-color",value:"initial",description:"default = hsl(var(--accent))"},{name:"--list-row-padding-y",value:"var(--space-3)",description:"ListRow component tokens \u2014 a single-line entity row for short lists inside a Card * (sessions / API tokens / linked accounts / passkeys / notifications \u2026). Sits in a flush * CardContent; rows separate with a quiet divider (#44 \u2014 chrome defaults to the calm * semantic border)."},{name:"--list-row-padding-x",value:"var(--space-4)",description:"ListRow component tokens \u2014 a single-line entity row for short lists inside a Card * (sessions / API tokens / linked accounts / passkeys / notifications \u2026). Sits in a flush * CardContent; rows separate with a quiet divider (#44 \u2014 chrome defaults to the calm * semantic border)."},{name:"--list-row-gap",value:"var(--space-3)",description:"ListRow component tokens \u2014 a single-line entity row for short lists inside a Card * (sessions / API tokens / linked accounts / passkeys / notifications \u2026). Sits in a flush * CardContent; rows separate with a quiet divider (#44 \u2014 chrome defaults to the calm * semantic border)."},{name:"--list-row-border",value:"initial",description:"Row divider \u2014 `initial` so the --border default re-resolves at the call site under a scoped * theme (a :root binding to a role var freezes at :root). Default = 1px solid hsl(var(--border))."},{name:"--list-row-body-min-width",value:"12rem",description:"Inline size the content column keeps before the trailing actions wrap onto their own line * (#45 \u2014 a service retunes the stack threshold to its grid). Clamped with min(\u2026, 100%) at the * call site, so a container narrower than the knob can never force page-root overflow (#224)."},{name:"--list-row-trailing-gap",value:"var(--space-2)",description:"Gap BETWEEN trailing actions \u2014 they also wrap among themselves at narrow widths (#224)."},{name:"--list-row-compact-padding-y",value:"initial",description:'\u2500\u2500 Compact inline-actions geometry (`density="compact"`, #246) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 * The canonical invitation / history row: a 36px leading avatar, a shrinkable title + * description body and one or two small trailing Buttons, all on ONE line inside the canonical * 358px card (326px content after the row\'s own inline inset) at a 390px viewport. These knobs * only LOWER the geometry \u2014 the #224 wrap contract (row + trailing cluster wrap, body clamped * with min(\u2026, 100%)) is untouched, so a cluster that genuinely cannot fit still wraps instead of * widening the page root. * Declared `initial` so the spacing defaults re-resolve at the CALL SITE \u2014 --space-2 is * density-scaled and re-declared inside a `.ui-density-*` subtree, so a :root binding would * freeze compact rows at the :root density (docs/TOKENS.md \u2014 the :root freeze rule). * Defaults = var(--space-2) block padding \xB7 var(--list-row-padding-x) inline padding \xB7 * var(--space-2) gap.'},{name:"--list-row-compact-padding-x",value:"initial",description:"Inline inset defaults to the default row axis so compact and default rows in the same Card keep * one optical axis; a service retunes it to its own grid (#45)."},{name:"--list-row-compact-gap",value:"initial",description:"Inline inset defaults to the default row axis so compact and default rows in the same Card keep * one optical axis; a service retunes it to its own grid (#45)."},{name:"--list-row-compact-body-min-width",value:"6rem",description:"Compact body threshold \u2014 small enough that avatar + body + two compact Buttons fit the * canonical column (36 + 8 + 96 + 8 + 146 = 294 \u2264 326), and still a readable measure."},{name:"--list-row-read-background",value:"initial",description:"Read (default) row surface \u2014 `initial`, documented default = transparent, i.e. the Card * surface shows through untouched (#44 \u2014 chrome defaults to the quietest state)."},{name:"--list-row-unread-background",value:"initial",description:"Unread emphasis surface (`unread` rows) \u2014 `initial` so the --muted default re-resolves at the * call site under a scoped theme. Documented default = hsl(var(--muted)), the same quiet * emphasis role the table header uses; it keeps the xs muted description line at WCAG AA * (4.63:1 light / 5.47:1 dark), which hsl(var(--accent)) would not (4.23:1 light)."},{name:"--list-row-indicator-color",value:"initial",description:"Unread indicator dot fill \u2014 `initial` so the --primary default re-resolves at the call site * under a scoped theme. Documented default = hsl(var(--primary))."},{name:"--list-row-indicator-size",value:"var(--space-2)",description:"Diameter of the read/unread indicator dot; the gutter is reserved on read rows too so the * titles of a mixed list stay on one optical axis."},{name:"--logo-radius",value:"var(--radius)",description:"Logo component tokens \u2014 the product brand-mark box (a glyph on the primary fill), used in the * app shell header, auth screens, and topbars. Size + radius + per-tier font-size are knobs so a * service theme retunes the mark without forking CSS (rules #44/#45). Colours read the primary * role tokens directly, so a re-themed --primary re-tints the mark automatically."},{name:"--logo-size-xs",value:"1.25rem",description:"Logo component tokens \u2014 the product brand-mark box (a glyph on the primary fill), used in the * app shell header, auth screens, and topbars. Size + radius + per-tier font-size are knobs so a * service theme retunes the mark without forking CSS (rules #44/#45). Colours read the primary * role tokens directly, so a re-themed --primary re-tints the mark automatically."},{name:"--logo-size-sm",value:"1.5rem",description:"Logo component tokens \u2014 the product brand-mark box (a glyph on the primary fill), used in the * app shell header, auth screens, and topbars. Size + radius + per-tier font-size are knobs so a * service theme retunes the mark without forking CSS (rules #44/#45). Colours read the primary * role tokens directly, so a re-themed --primary re-tints the mark automatically."},{name:"--logo-size-md",value:"1.75rem",description:"Logo component tokens \u2014 the product brand-mark box (a glyph on the primary fill), used in the * app shell header, auth screens, and topbars. Size + radius + per-tier font-size are knobs so a * service theme retunes the mark without forking CSS (rules #44/#45). Colours read the primary * role tokens directly, so a re-themed --primary re-tints the mark automatically."},{name:"--logo-size-lg",value:"2.25rem",description:"Logo component tokens \u2014 the product brand-mark box (a glyph on the primary fill), used in the * app shell header, auth screens, and topbars. Size + radius + per-tier font-size are knobs so a * service theme retunes the mark without forking CSS (rules #44/#45). Colours read the primary * role tokens directly, so a re-themed --primary re-tints the mark automatically."},{name:"--logo-font-size-xs",value:"var(--font-size-2xs)",description:"Logo component tokens \u2014 the product brand-mark box (a glyph on the primary fill), used in the * app shell header, auth screens, and topbars. Size + radius + per-tier font-size are knobs so a * service theme retunes the mark without forking CSS (rules #44/#45). Colours read the primary * role tokens directly, so a re-themed --primary re-tints the mark automatically."},{name:"--logo-font-size-sm",value:"var(--font-size-xs)",description:"Logo component tokens \u2014 the product brand-mark box (a glyph on the primary fill), used in the * app shell header, auth screens, and topbars. Size + radius + per-tier font-size are knobs so a * service theme retunes the mark without forking CSS (rules #44/#45). Colours read the primary * role tokens directly, so a re-themed --primary re-tints the mark automatically."},{name:"--logo-font-size-md",value:"var(--font-size-sm)",description:"Logo component tokens \u2014 the product brand-mark box (a glyph on the primary fill), used in the * app shell header, auth screens, and topbars. Size + radius + per-tier font-size are knobs so a * service theme retunes the mark without forking CSS (rules #44/#45). Colours read the primary * role tokens directly, so a re-themed --primary re-tints the mark automatically."},{name:"--logo-font-size-lg",value:"var(--font-size-base)",description:"Logo component tokens \u2014 the product brand-mark box (a glyph on the primary fill), used in the * app shell header, auth screens, and topbars. Size + radius + per-tier font-size are knobs so a * service theme retunes the mark without forking CSS (rules #44/#45). Colours read the primary * role tokens directly, so a re-themed --primary re-tints the mark automatically."},{name:"--logo-success-background",value:"initial",description:'Semantic identity fill reads the --brand IDENTITY role: independent of --primary (a re-themed * action colour never recolours the mark) AND of --success (the \u82E5\u7AF9 STATUS green stays on * badges/progress/"saved" text \u2014 the mark used to borrow it and rendered \u0394E76 \u2248 17.5 off the * canonical emerald, gh#250). Services retune the role once for every Logo. * ROLE-MIRROR KNOBS \u2014 declared `initial` here with the role default at the CALL SITE * (logo-layout.css), so a scoped `.dark` / `[data-tenant]` override of --brand actually * reaches the mark instead of freezing at the :root value (docs/TOKENS.md \xB7 * "Role-mirror knobs MUST be `initial`"). Documented defaults: * --logo-success-background = hsl(var(--brand)) * --logo-success-foreground = hsl(var(--logo-identity-foreground)) * --logo-godx-color = hsl(var(--brand))'},{name:"--logo-success-foreground",value:"initial",description:'Semantic identity fill reads the --brand IDENTITY role: independent of --primary (a re-themed * action colour never recolours the mark) AND of --success (the \u82E5\u7AF9 STATUS green stays on * badges/progress/"saved" text \u2014 the mark used to borrow it and rendered \u0394E76 \u2248 17.5 off the * canonical emerald, gh#250). Services retune the role once for every Logo. * ROLE-MIRROR KNOBS \u2014 declared `initial` here with the role default at the CALL SITE * (logo-layout.css), so a scoped `.dark` / `[data-tenant]` override of --brand actually * reaches the mark instead of freezing at the :root value (docs/TOKENS.md \xB7 * "Role-mirror knobs MUST be `initial`"). Documented defaults: * --logo-success-background = hsl(var(--brand)) * --logo-success-foreground = hsl(var(--logo-identity-foreground)) * --logo-godx-color = hsl(var(--brand))'},{name:"--logo-godx-color",value:"initial",description:'Semantic identity fill reads the --brand IDENTITY role: independent of --primary (a re-themed * action colour never recolours the mark) AND of --success (the \u82E5\u7AF9 STATUS green stays on * badges/progress/"saved" text \u2014 the mark used to borrow it and rendered \u0394E76 \u2248 17.5 off the * canonical emerald, gh#250). Services retune the role once for every Logo. * ROLE-MIRROR KNOBS \u2014 declared `initial` here with the role default at the CALL SITE * (logo-layout.css), so a scoped `.dark` / `[data-tenant]` override of --brand actually * reaches the mark instead of freezing at the :root value (docs/TOKENS.md \xB7 * "Role-mirror knobs MUST be `initial`"). Documented defaults: * --logo-success-background = hsl(var(--brand)) * --logo-success-foreground = hsl(var(--logo-identity-foreground)) * --logo-godx-color = hsl(var(--brand))'},{name:"--logo-identity-foreground",value:"48 9% 9%",description:'Identity-fill INK \u2014 the ink `mark="glyph"` sets its (caller-supplied) TEXT in when it sits on * the --brand identity fill. This is deliberately NOT --brand-foreground. * * --brand-foreground is the identity artwork\'s KNOCKOUT colour, not an ink: it tracks * --background in both themes (light 60 33% 99% = --background; dark 48 9% 9% = --background) * because `<Logo mark="godx">` punches its inner bar as an evenodd HOLE and the email mark has to * paint that hole as a solid fill to match. Negative space only has to clear WCAG 2.2 SC 1.4.11 * (3:1, non-text) \u2014 and at 3.67:1 on the emerald it does. * * The boxed glyph is a different thing: it renders real TEXT, so SC 1.4.3 applies at 4.5:1 (14px * bold is NOT "large text" \u2014 that needs 18.66px bold / 24px), and the knockout white measured * 3.67:1 \u2192 a genuine AA failure. This ink is 48 9% 9% (#191815), the same near-black spine the * DARK theme already inked the glyph with, so it is theme-INVARIANT: dark renders byte-identically * (6.89:1, unchanged) and light rises 3.67 \u2192 4.74:1. A service that re-themes --brand to a dark * fill overrides --logo-success-foreground (the public knob) to re-invert the ink. * * NOT a role-mirror knob: its default is a real value, not a role token, so there is no role to * freeze at :root and the `initial` + call-site rule (docs/TOKENS.md) does not apply to it.'},{name:"--logo-godx-size-xs",value:"1.5rem",description:'\u2500\u2500 Identity-mark box (`<Logo mark="godx" />`) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 * The `size` prop drives the identity mark exactly like it drives the boxed glyph \u2014 a public * prop that renders identically at every tier is a silent no-op, and the godx LOCKUP already * scaled its wordmark per tier, so a fixed mark broke the mark\u2194wordmark proportion at * `size="lg"` (gh#163 follow-up). The godx artwork is a horizontal capsule inside a square * viewBox, so it carries less optical weight than the fully-filled glyph box: each tier sits * +0.25rem above its --logo-size-* sibling. `md` is 2rem \u2014 the historical fixed value \u2014 so * every existing default-size identity surface renders byte-identically. * * --logo-godx-size is the PIN: declared `initial` (guaranteed-invalid) here with the per-tier * default at the CALL SITE (logo-layout.css), so it is inert by default and a service that * sets it once freezes the mark at that box on EVERY tier \u2014 the same call-site pattern the * role-mirror colour knobs use (docs/TOKENS.md). Retune a single tier via --logo-godx-size-*. * Documented default: --logo-godx-size = unset (tiers apply).'},{name:"--logo-godx-size-sm",value:"1.75rem",description:'\u2500\u2500 Identity-mark box (`<Logo mark="godx" />`) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 * The `size` prop drives the identity mark exactly like it drives the boxed glyph \u2014 a public * prop that renders identically at every tier is a silent no-op, and the godx LOCKUP already * scaled its wordmark per tier, so a fixed mark broke the mark\u2194wordmark proportion at * `size="lg"` (gh#163 follow-up). The godx artwork is a horizontal capsule inside a square * viewBox, so it carries less optical weight than the fully-filled glyph box: each tier sits * +0.25rem above its --logo-size-* sibling. `md` is 2rem \u2014 the historical fixed value \u2014 so * every existing default-size identity surface renders byte-identically. * * --logo-godx-size is the PIN: declared `initial` (guaranteed-invalid) here with the per-tier * default at the CALL SITE (logo-layout.css), so it is inert by default and a service that * sets it once freezes the mark at that box on EVERY tier \u2014 the same call-site pattern the * role-mirror colour knobs use (docs/TOKENS.md). Retune a single tier via --logo-godx-size-*. * Documented default: --logo-godx-size = unset (tiers apply).'},{name:"--logo-godx-size-md",value:"2rem",description:'\u2500\u2500 Identity-mark box (`<Logo mark="godx" />`) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 * The `size` prop drives the identity mark exactly like it drives the boxed glyph \u2014 a public * prop that renders identically at every tier is a silent no-op, and the godx LOCKUP already * scaled its wordmark per tier, so a fixed mark broke the mark\u2194wordmark proportion at * `size="lg"` (gh#163 follow-up). The godx artwork is a horizontal capsule inside a square * viewBox, so it carries less optical weight than the fully-filled glyph box: each tier sits * +0.25rem above its --logo-size-* sibling. `md` is 2rem \u2014 the historical fixed value \u2014 so * every existing default-size identity surface renders byte-identically. * * --logo-godx-size is the PIN: declared `initial` (guaranteed-invalid) here with the per-tier * default at the CALL SITE (logo-layout.css), so it is inert by default and a service that * sets it once freezes the mark at that box on EVERY tier \u2014 the same call-site pattern the * role-mirror colour knobs use (docs/TOKENS.md). Retune a single tier via --logo-godx-size-*. * Documented default: --logo-godx-size = unset (tiers apply).'},{name:"--logo-godx-size-lg",value:"2.5rem",description:'\u2500\u2500 Identity-mark box (`<Logo mark="godx" />`) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 * The `size` prop drives the identity mark exactly like it drives the boxed glyph \u2014 a public * prop that renders identically at every tier is a silent no-op, and the godx LOCKUP already * scaled its wordmark per tier, so a fixed mark broke the mark\u2194wordmark proportion at * `size="lg"` (gh#163 follow-up). The godx artwork is a horizontal capsule inside a square * viewBox, so it carries less optical weight than the fully-filled glyph box: each tier sits * +0.25rem above its --logo-size-* sibling. `md` is 2rem \u2014 the historical fixed value \u2014 so * every existing default-size identity surface renders byte-identically. * * --logo-godx-size is the PIN: declared `initial` (guaranteed-invalid) here with the per-tier * default at the CALL SITE (logo-layout.css), so it is inert by default and a service that * sets it once freezes the mark at that box on EVERY tier \u2014 the same call-site pattern the * role-mirror colour knobs use (docs/TOKENS.md). Retune a single tier via --logo-godx-size-*. * Documented default: --logo-godx-size = unset (tiers apply).'},{name:"--logo-godx-size",value:"initial",description:'\u2500\u2500 Identity-mark box (`<Logo mark="godx" />`) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 * The `size` prop drives the identity mark exactly like it drives the boxed glyph \u2014 a public * prop that renders identically at every tier is a silent no-op, and the godx LOCKUP already * scaled its wordmark per tier, so a fixed mark broke the mark\u2194wordmark proportion at * `size="lg"` (gh#163 follow-up). The godx artwork is a horizontal capsule inside a square * viewBox, so it carries less optical weight than the fully-filled glyph box: each tier sits * +0.25rem above its --logo-size-* sibling. `md` is 2rem \u2014 the historical fixed value \u2014 so * every existing default-size identity surface renders byte-identically. * * --logo-godx-size is the PIN: declared `initial` (guaranteed-invalid) here with the per-tier * default at the CALL SITE (logo-layout.css), so it is inert by default and a service that * sets it once freezes the mark at that box on EVERY tier \u2014 the same call-site pattern the * role-mirror colour knobs use (docs/TOKENS.md). Retune a single tier via --logo-godx-size-*. * Documented default: --logo-godx-size = unset (tiers apply).'},{name:"--logo-wordmark-gap",value:"var(--space-2)",description:'\u2500\u2500 Wordmark / lockup (`<Logo wordmark="GoDX" />`) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 * The readable product name set beside the mark. The package ships NO wordmark artwork: the * wordmark is typeset in the design-system display face, so a service retunes face, weight, * tracking, size and the mark\u2194wordmark gap here instead of writing page CSS. The colour is a * role-mirror knob whose default is the IDENTITY role --brand (never --primary, never the * --success status green): the GoDX/`success` lockup is canonical emerald out of the box, and * every other lockup reads --foreground. * Documented defaults: --logo-wordmark-font-family = var(--font-family-display); * --logo-wordmark-color = hsl(var(--foreground)), or hsl(var(--brand)) on the * godx / tone="success" lockup.'},{name:"--logo-wordmark-font-size-xs",value:"var(--font-size-xs)",description:'\u2500\u2500 Wordmark / lockup (`<Logo wordmark="GoDX" />`) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 * The readable product name set beside the mark. The package ships NO wordmark artwork: the * wordmark is typeset in the design-system display face, so a service retunes face, weight, * tracking, size and the mark\u2194wordmark gap here instead of writing page CSS. The colour is a * role-mirror knob whose default is the IDENTITY role --brand (never --primary, never the * --success status green): the GoDX/`success` lockup is canonical emerald out of the box, and * every other lockup reads --foreground. * Documented defaults: --logo-wordmark-font-family = var(--font-family-display); * --logo-wordmark-color = hsl(var(--foreground)), or hsl(var(--brand)) on the * godx / tone="success" lockup.'},{name:"--logo-wordmark-font-size-sm",value:"var(--font-size-sm)",description:'\u2500\u2500 Wordmark / lockup (`<Logo wordmark="GoDX" />`) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 * The readable product name set beside the mark. The package ships NO wordmark artwork: the * wordmark is typeset in the design-system display face, so a service retunes face, weight, * tracking, size and the mark\u2194wordmark gap here instead of writing page CSS. The colour is a * role-mirror knob whose default is the IDENTITY role --brand (never --primary, never the * --success status green): the GoDX/`success` lockup is canonical emerald out of the box, and * every other lockup reads --foreground. * Documented defaults: --logo-wordmark-font-family = var(--font-family-display); * --logo-wordmark-color = hsl(var(--foreground)), or hsl(var(--brand)) on the * godx / tone="success" lockup.'},{name:"--logo-wordmark-font-size-md",value:"var(--font-size-base)",description:'\u2500\u2500 Wordmark / lockup (`<Logo wordmark="GoDX" />`) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 * The readable product name set beside the mark. The package ships NO wordmark artwork: the * wordmark is typeset in the design-system display face, so a service retunes face, weight, * tracking, size and the mark\u2194wordmark gap here instead of writing page CSS. The colour is a * role-mirror knob whose default is the IDENTITY role --brand (never --primary, never the * --success status green): the GoDX/`success` lockup is canonical emerald out of the box, and * every other lockup reads --foreground. * Documented defaults: --logo-wordmark-font-family = var(--font-family-display); * --logo-wordmark-color = hsl(var(--foreground)), or hsl(var(--brand)) on the * godx / tone="success" lockup.'},{name:"--logo-wordmark-font-size-lg",value:"var(--font-size-lg)",description:'\u2500\u2500 Wordmark / lockup (`<Logo wordmark="GoDX" />`) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 * The readable product name set beside the mark. The package ships NO wordmark artwork: the * wordmark is typeset in the design-system display face, so a service retunes face, weight, * tracking, size and the mark\u2194wordmark gap here instead of writing page CSS. The colour is a * role-mirror knob whose default is the IDENTITY role --brand (never --primary, never the * --success status green): the GoDX/`success` lockup is canonical emerald out of the box, and * every other lockup reads --foreground. * Documented defaults: --logo-wordmark-font-family = var(--font-family-display); * --logo-wordmark-color = hsl(var(--foreground)), or hsl(var(--brand)) on the * godx / tone="success" lockup.'},{name:"--logo-wordmark-font-weight",value:"700",description:'\u2500\u2500 Wordmark / lockup (`<Logo wordmark="GoDX" />`) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 * The readable product name set beside the mark. The package ships NO wordmark artwork: the * wordmark is typeset in the design-system display face, so a service retunes face, weight, * tracking, size and the mark\u2194wordmark gap here instead of writing page CSS. The colour is a * role-mirror knob whose default is the IDENTITY role --brand (never --primary, never the * --success status green): the GoDX/`success` lockup is canonical emerald out of the box, and * every other lockup reads --foreground. * Documented defaults: --logo-wordmark-font-family = var(--font-family-display); * --logo-wordmark-color = hsl(var(--foreground)), or hsl(var(--brand)) on the * godx / tone="success" lockup.'},{name:"--logo-wordmark-letter-spacing",value:"-0.01em",description:'\u2500\u2500 Wordmark / lockup (`<Logo wordmark="GoDX" />`) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 * The readable product name set beside the mark. The package ships NO wordmark artwork: the * wordmark is typeset in the design-system display face, so a service retunes face, weight, * tracking, size and the mark\u2194wordmark gap here instead of writing page CSS. The colour is a * role-mirror knob whose default is the IDENTITY role --brand (never --primary, never the * --success status green): the GoDX/`success` lockup is canonical emerald out of the box, and * every other lockup reads --foreground. * Documented defaults: --logo-wordmark-font-family = var(--font-family-display); * --logo-wordmark-color = hsl(var(--foreground)), or hsl(var(--brand)) on the * godx / tone="success" lockup.'},{name:"--logo-wordmark-font-family",value:"initial",description:'\u2500\u2500 Wordmark / lockup (`<Logo wordmark="GoDX" />`) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 * The readable product name set beside the mark. The package ships NO wordmark artwork: the * wordmark is typeset in the design-system display face, so a service retunes face, weight, * tracking, size and the mark\u2194wordmark gap here instead of writing page CSS. The colour is a * role-mirror knob whose default is the IDENTITY role --brand (never --primary, never the * --success status green): the GoDX/`success` lockup is canonical emerald out of the box, and * every other lockup reads --foreground. * Documented defaults: --logo-wordmark-font-family = var(--font-family-display); * --logo-wordmark-color = hsl(var(--foreground)), or hsl(var(--brand)) on the * godx / tone="success" lockup.'},{name:"--logo-wordmark-color",value:"initial",description:'\u2500\u2500 Wordmark / lockup (`<Logo wordmark="GoDX" />`) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 * The readable product name set beside the mark. The package ships NO wordmark artwork: the * wordmark is typeset in the design-system display face, so a service retunes face, weight, * tracking, size and the mark\u2194wordmark gap here instead of writing page CSS. The colour is a * role-mirror knob whose default is the IDENTITY role --brand (never --primary, never the * --success status green): the GoDX/`success` lockup is canonical emerald out of the box, and * every other lockup reads --foreground. * Documented defaults: --logo-wordmark-font-family = var(--font-family-display); * --logo-wordmark-color = hsl(var(--foreground)), or hsl(var(--brand)) on the * godx / tone="success" lockup.'},{name:"--pagination-gap",value:"var(--space-inline-sm)",description:"Navigation primitive tokens: pagination, filters, compact pickers."},{name:"--pagination-item-gap",value:"var(--space-inline-xs)",description:"Navigation primitive tokens: pagination, filters, compact pickers."},{name:"--pagination-size-width",value:"5.5rem",description:"Navigation primitive tokens: pagination, filters, compact pickers."},{name:"--pagination-total-font-size",value:"var(--font-size-sm)",description:"Navigation primitive tokens: pagination, filters, compact pickers."},{name:"--filter-bar-gap",value:"var(--space-3)",description:"Navigation primitive tokens: pagination, filters, compact pickers."},{name:"--filter-bar-padding-y",value:"var(--space-2)",description:"Navigation primitive tokens: pagination, filters, compact pickers."},{name:"--filter-label-font-size",value:"var(--font-size-xs)",description:"Navigation primitive tokens: pagination, filters, compact pickers."},{name:"--filter-picker-width-sm",value:"11rem",description:"Navigation primitive tokens: pagination, filters, compact pickers."},{name:"--filter-picker-width-md",value:"14rem",description:"Navigation primitive tokens: pagination, filters, compact pickers."},{name:"--steps-inline-gap",value:"var(--space-2)",description:"Navigation primitive tokens: pagination, filters, compact pickers."},{name:"--steps-inline-item-gap",value:"var(--space-1)",description:"Navigation primitive tokens: pagination, filters, compact pickers."},{name:"--steps-inline-font-size",value:"var(--font-size-xs)",description:"Navigation primitive tokens: pagination, filters, compact pickers."},{name:"--steps-inline-separator-size",value:"var(--control-icon-size-sm)",description:"Navigation primitive tokens: pagination, filters, compact pickers."},{name:"--app-setting-picker-compact-control-height",value:"var(--control-height-sm)",description:"AppSettingPicker `compact` (gh#217) \u2014 the small, content-hugging labelled trigger used in an * auth/legal footer, where the square icon-only default reads as a stray button and the full * labelled trigger is too tall. Box height comes from the official --control-height-sm tier (never * a literal / ad-hoc calc), and every other knob is themeable (rule #45)."},{name:"--app-setting-picker-compact-padding-x",value:"var(--space-2)",description:"AppSettingPicker `compact` (gh#217) \u2014 the small, content-hugging labelled trigger used in an * auth/legal footer, where the square icon-only default reads as a stray button and the full * labelled trigger is too tall. Box height comes from the official --control-height-sm tier (never * a literal / ad-hoc calc), and every other knob is themeable (rule #45)."},{name:"--app-setting-picker-compact-gap",value:"var(--space-1)",description:"AppSettingPicker `compact` (gh#217) \u2014 the small, content-hugging labelled trigger used in an * auth/legal footer, where the square icon-only default reads as a stray button and the full * labelled trigger is too tall. Box height comes from the official --control-height-sm tier (never * a literal / ad-hoc calc), and every other knob is themeable (rule #45)."},{name:"--app-setting-picker-compact-font-size",value:"var(--font-size-xs)",description:"AppSettingPicker `compact` (gh#217) \u2014 the small, content-hugging labelled trigger used in an * auth/legal footer, where the square icon-only default reads as a stray button and the full * labelled trigger is too tall. Box height comes from the official --control-height-sm tier (never * a literal / ad-hoc calc), and every other knob is themeable (rule #45)."},{name:"--breadcrumb-font-size",value:"var(--font-size-xs)",description:"AppSettingPicker `compact` (gh#217) \u2014 the small, content-hugging labelled trigger used in an * auth/legal footer, where the square icon-only default reads as a stray button and the full * labelled trigger is too tall. Box height comes from the official --control-height-sm tier (never * a literal / ad-hoc calc), and every other knob is themeable (rule #45)."},{name:"--menubar-shortcut-font-size",value:"var(--font-size-xs)",description:"AppSettingPicker `compact` (gh#217) \u2014 the small, content-hugging labelled trigger used in an * auth/legal footer, where the square icon-only default reads as a stray button and the full * labelled trigger is too tall. Box height comes from the official --control-height-sm tier (never * a literal / ad-hoc calc), and every other knob is themeable (rule #45)."},{name:"--tabs-list-max-inline-size",value:"100%",description:"AppSettingPicker `compact` (gh#217) \u2014 the small, content-hugging labelled trigger used in an * auth/legal footer, where the square icon-only default reads as a stray button and the full * labelled trigger is too tall. Box height comes from the official --control-height-sm tier (never * a literal / ad-hoc calc), and every other knob is themeable (rule #45)."},{name:"--tabs-list-overflow",value:"auto",description:"AppSettingPicker `compact` (gh#217) \u2014 the small, content-hugging labelled trigger used in an * auth/legal footer, where the square icon-only default reads as a stray button and the full * labelled trigger is too tall. Box height comes from the official --control-height-sm tier (never * a literal / ad-hoc calc), and every other knob is themeable (rule #45)."},{name:"--tabs-indicator-background",value:"initial",description:"Tabs `line` active indicator (gh#248) \u2014 the ONLY selected-state decoration the line variant * paints. The surrounding active ring belongs to the default/card lists, so `:focus-visible` * keeps its own distinct keyboard ring here (WCAG 2.4.7). `--tabs-indicator-background` is a * role-mirror knob: `initial` so hsl(var(--primary)) re-resolves at the CALL SITE under a scoped * [data-tenant]/.dark theme (a :root binding to a role var would freeze at :root). * Default = hsl(var(--primary)) \xB7 2px thick \xB7 flush with the trigger edge (offset 0 = quietest, * rule #44); a service raises the offset to park the bar on a thicker strip hairline."},{name:"--tabs-indicator-size",value:"2px",description:"Tabs `line` active indicator (gh#248) \u2014 the ONLY selected-state decoration the line variant * paints. The surrounding active ring belongs to the default/card lists, so `:focus-visible` * keeps its own distinct keyboard ring here (WCAG 2.4.7). `--tabs-indicator-background` is a * role-mirror knob: `initial` so hsl(var(--primary)) re-resolves at the CALL SITE under a scoped * [data-tenant]/.dark theme (a :root binding to a role var would freeze at :root). * Default = hsl(var(--primary)) \xB7 2px thick \xB7 flush with the trigger edge (offset 0 = quietest, * rule #44); a service raises the offset to park the bar on a thicker strip hairline."},{name:"--tabs-indicator-offset",value:"0px",description:"Tabs `line` active indicator (gh#248) \u2014 the ONLY selected-state decoration the line variant * paints. The surrounding active ring belongs to the default/card lists, so `:focus-visible` * keeps its own distinct keyboard ring here (WCAG 2.4.7). `--tabs-indicator-background` is a * role-mirror knob: `initial` so hsl(var(--primary)) re-resolves at the CALL SITE under a scoped * [data-tenant]/.dark theme (a :root binding to a role var would freeze at :root). * Default = hsl(var(--primary)) \xB7 2px thick \xB7 flush with the trigger edge (offset 0 = quietest, * rule #44); a service raises the offset to park the bar on a thicker strip hairline."},{name:"--menubar-item-hover-background",value:"initial",description:"Menu item hover/highlight tint \u2014 `initial` so the --accent default re-resolves at the call site * under a scoped theme (a :root binding to a role var freezes at :root). * Defaults = hsl(var(--accent)) fill \xB7 hsl(var(--accent-foreground)) text."},{name:"--menubar-item-hover-foreground",value:"initial",description:"Menu item hover/highlight tint \u2014 `initial` so the --accent default re-resolves at the call site * under a scoped theme (a :root binding to a role var freezes at :root). * Defaults = hsl(var(--accent)) fill \xB7 hsl(var(--accent-foreground)) text."},{name:"--filter-bar-sticky-offset",value:"0px",description:"Sticky offset for the pinned filter strip (Toolbar `sticky`, #197). Default 0 (chrome, * rule #44); raise it so the strip parks below a fixed topbar."},{name:"--filter-bar-sticky-background",value:"initial",description:"Fill painted only when the filter strip is pinned. Role-mirror knob (rule #45): `initial` * so --background re-resolves at the call site under a scoped [data-tenant]/.dark theme."},{name:"--filter-bar-scroll-padding-y",value:"var(--space-1)",description:'Scrollbar gutter reserved under a `overflow="scroll"` filter strip (#216) so the inline * scrollbar never overlaps the controls. Rule #45 \u2014 a service theme retunes it to its grid * (0 on an overlay-scrollbar platform).'},{name:"--sheet-responsive-breakpoint-width",value:"48rem",description:'Viewport width at and below which `SheetContent responsive="auto"` renders the mobile bottom * sheet instead of the desktop side panel. Read at runtime by useSheetResponsiveMode() (a CSS * @media cannot resolve a custom property), so a service moves the drawer breakpoint from ONE * knob for every overlay that opts into the responsive contract \u2014 Sheet and OrgSwitcher alike. * 48rem = 768px mirrors the library\'s canonical mobile line (useIsMobile). Accepts px/rem/em.'},{name:"--sheet-bottom-max-height",value:"85dvh",description:'Block size cap for the responsive BOTTOM presentation only (never for a plain * `side="bottom"` sheet, which stays content-sized). Keeps the sheet below the viewport so the * SheetBody scrolls and the page behind it stays visible/dismissable.'},{name:"--sidebar-section-label-font-size",value:"var(--font-size-2xs)",description:"Shell (sidebar / topbar / kbd) component tokens \u2014 small-by-design text * knobs (rule #45/#46). A service re-tunes chrome text without moving the * global scale."},{name:"--sidebar-product-tenant-font-size",value:"var(--font-size-2xs)",description:"Shell (sidebar / topbar / kbd) component tokens \u2014 small-by-design text * knobs (rule #45/#46). A service re-tunes chrome text without moving the * global scale."},{name:"--sidebar-badge-font-size",value:"var(--font-size-2xs)",description:"Shell (sidebar / topbar / kbd) component tokens \u2014 small-by-design text * knobs (rule #45/#46). A service re-tunes chrome text without moving the * global scale."},{name:"--sidebar-user-role-font-size",value:"var(--font-size-2xs)",description:"Shell (sidebar / topbar / kbd) component tokens \u2014 small-by-design text * knobs (rule #45/#46). A service re-tunes chrome text without moving the * global scale."},{name:"--org-switcher-meta-foreground",value:"var(--muted-foreground)",description:"Shell (sidebar / topbar / kbd) component tokens \u2014 small-by-design text * knobs (rule #45/#46). A service re-tunes chrome text without moving the * global scale."},{name:"--sidebar-nav-sub-font-size",value:"var(--font-size-xs)",description:"Shell (sidebar / topbar / kbd) component tokens \u2014 small-by-design text * knobs (rule #45/#46). A service re-tunes chrome text without moving the * global scale."},{name:"--sidebar-flyout-title-font-size",value:"var(--font-size-xs)",description:"Shell (sidebar / topbar / kbd) component tokens \u2014 small-by-design text * knobs (rule #45/#46). A service re-tunes chrome text without moving the * global scale."},{name:"--topbar-chip-icon-font-size",value:"var(--font-size-2xs)",description:"Shell (sidebar / topbar / kbd) component tokens \u2014 small-by-design text * knobs (rule #45/#46). A service re-tunes chrome text without moving the * global scale."},{name:"--kbd-font-size",value:"var(--font-size-2xs)",description:"Shell (sidebar / topbar / kbd) component tokens \u2014 small-by-design text * knobs (rule #45/#46). A service re-tunes chrome text without moving the * global scale."},{name:"--sidebar-logo-mark-font-size",value:"var(--font-size-xs)",description:"Shell (sidebar / topbar / kbd) component tokens \u2014 small-by-design text * knobs (rule #45/#46). A service re-tunes chrome text without moving the * global scale."},{name:"--sidebar-avatar-font-size",value:"var(--font-size-2xs)",description:"Shell (sidebar / topbar / kbd) component tokens \u2014 small-by-design text * knobs (rule #45/#46). A service re-tunes chrome text without moving the * global scale."},{name:"--sidebar-user-name-font-size",value:"var(--font-size-xs)",description:"Shell (sidebar / topbar / kbd) component tokens \u2014 small-by-design text * knobs (rule #45/#46). A service re-tunes chrome text without moving the * global scale."},{name:"--sidebar-gradient",value:"none",description:"Brand-chrome gradient hooks \u2014 opt-in, invisible by default. A service paints * the sidebar/topbar surface by setting these to a gradient (no-op = none)."},{name:"--topbar-gradient",value:"none",description:"Brand-chrome gradient hooks \u2014 opt-in, invisible by default. A service paints * the sidebar/topbar surface by setting these to a gradient (no-op = none)."},{name:"--app-shell-bar-height",value:"3rem",description:"DXS application-shell geometry. These defaults mirror the checked-in * Admin/Console hi-fi source while remaining themeable by consumers."},{name:"--app-shell-bar-inset",value:"var(--space-4)",description:"Inline inset and slot gap of the AppShell top bar. `-compact` applies below the shell * breakpoint (the docked sidebar is gone, so the bar tightens against the viewport edge)."},{name:"--app-shell-bar-inset-compact",value:"var(--space-3)",description:"Inline inset and slot gap of the AppShell top bar. `-compact` applies below the shell * breakpoint (the docked sidebar is gone, so the bar tightens against the viewport edge)."},{name:"--app-shell-bar-gap",value:"var(--space-3)",description:"Inline inset and slot gap of the AppShell top bar. `-compact` applies below the shell * breakpoint (the docked sidebar is gone, so the bar tightens against the viewport edge)."},{name:"--app-shell-sidebar-width",value:"16rem",description:"Docked navigation rail widths \u2014 the SINGLE most-retuned shell constant (rule #45). A service * that designs on a different grid sets `--app-shell-sidebar-width: 15.9375rem` (255px) once * instead of forking `.app-root`. `-rail-` is the icon-only width used at * `<AppShell sidebarCollapsed>`."},{name:"--app-shell-rail-width",value:"4rem",description:"Docked navigation rail widths \u2014 the SINGLE most-retuned shell constant (rule #45). A service * that designs on a different grid sets `--app-shell-sidebar-width: 15.9375rem` (255px) once * instead of forking `.app-root`. `-rail-` is the icon-only width used at * `<AppShell sidebarCollapsed>`."},{name:"--app-shell-page-max-width",value:"80rem",description:"Docked navigation rail widths \u2014 the SINGLE most-retuned shell constant (rule #45). A service * that designs on a different grid sets `--app-shell-sidebar-width: 15.9375rem` (255px) once * instead of forking `.app-root`. `-rail-` is the icon-only width used at * `<AppShell sidebarCollapsed>`."},{name:"--app-shell-main-background",value:"hsl(var(--muted) / 0.4)",description:"Docked navigation rail widths \u2014 the SINGLE most-retuned shell constant (rule #45). A service * that designs on a different grid sets `--app-shell-sidebar-width: 15.9375rem` (255px) once * instead of forking `.app-root`. `-rail-` is the icon-only width used at * `<AppShell sidebarCollapsed>`."},{name:"--app-shell-mobile-nav-width",value:"22.5rem",description:"Docked navigation rail widths \u2014 the SINGLE most-retuned shell constant (rule #45). A service * that designs on a different grid sets `--app-shell-sidebar-width: 15.9375rem` (255px) once * instead of forking `.app-root`. `-rail-` is the icon-only width used at * `<AppShell sidebarCollapsed>`."},{name:"--app-shell-mobile-nav-background",value:"initial",description:"Mobile-drawer scrim \u2014 `initial` so the shared --overlay-background default re-resolves at the * CALL SITE under a scoped [data-tenant]/.dark theme (a :root binding freezes at :root). Default = * var(--app-shell-mobile-nav-alpha) of the global scrim, i.e. rgb(0 0 0 / 0.2) out of the box."},{name:"--app-shell-mobile-nav-alpha",value:"40%",description:"Share of the global --overlay-background this drawer's scrim uses. A slide-in drawer washes the * page more lightly than a modal dialog; keeping it a RATIO means a service retints the whole * system with one --overlay-background and every overlay keeps its calibrated depth."},{name:"--app-shell-mobile-nav-inset",value:"var(--space-1)",description:"Inline inset of the mobile drawer's scrollable nav body. Near-zero by design: the drawer nav * (the <Sidebar> node by default) owns its own inset via --sidebar-nav-scroll-padding, so the * generic sheet chrome inset must NOT stack on top of it (gh#211). Set it to var(--space-6) for * a custom mobileNav node that wants the full sheet chrome inset."},{name:"--sidebar-brand-mark-size",value:"1.375rem",description:"Inline inset of the mobile drawer's scrollable nav body. Near-zero by design: the drawer nav * (the <Sidebar> node by default) owns its own inset via --sidebar-nav-scroll-padding, so the * generic sheet chrome inset must NOT stack on top of it (gh#211). Set it to var(--space-6) for * a custom mobileNav node that wants the full sheet chrome inset."},{name:"--sidebar-nav-item-height",value:"2rem",description:"Inline inset of the mobile drawer's scrollable nav body. Near-zero by design: the drawer nav * (the <Sidebar> node by default) owns its own inset via --sidebar-nav-scroll-padding, so the * generic sheet chrome inset must NOT stack on top of it (gh#211). Set it to var(--space-6) for * a custom mobileNav node that wants the full sheet chrome inset."},{name:"--sidebar-nav-item-font-size",value:"0.8125rem",description:"Inline inset of the mobile drawer's scrollable nav body. Near-zero by design: the drawer nav * (the <Sidebar> node by default) owns its own inset via --sidebar-nav-scroll-padding, so the * generic sheet chrome inset must NOT stack on top of it (gh#211). Set it to var(--space-6) for * a custom mobileNav node that wants the full sheet chrome inset."},{name:"--sidebar-nav-item-line-height",value:"1.5",description:"Line box of the nav LABEL, not the row (gh#254): `.sb-label` clips, so its line box is its clip * box, and the row's `line-height: 1` shears descenders and Vietnamese tone marks. Keep >= 1.2."},{name:"--auth-account-summary-email-line-height",value:"1.5",description:"Same gh#254 trap, two more clipping boxes. `.ui-auth-account-email` and the truncating last * child of `.ui-topbar-start` both clip (`overflow: hidden`/`clip` + `text-overflow: ellipsis`), * so their line box IS their clip box \u2014 inheriting a tight value shears descenders and Vietnamese * tone marks exactly as the sidebar label did. Keep >= 1.2."},{name:"--topbar-start-truncate-line-height",value:"1.5",description:"Same gh#254 trap, two more clipping boxes. `.ui-auth-account-email` and the truncating last * child of `.ui-topbar-start` both clip (`overflow: hidden`/`clip` + `text-overflow: ellipsis`), * so their line box IS their clip box \u2014 inheriting a tight value shears descenders and Vietnamese * tone marks exactly as the sidebar label did. Keep >= 1.2."},{name:"--sidebar-nav-icon-size",value:"1rem",description:"Same gh#254 trap, two more clipping boxes. `.ui-auth-account-email` and the truncating last * child of `.ui-topbar-start` both clip (`overflow: hidden`/`clip` + `text-overflow: ellipsis`), * so their line box IS their clip box \u2014 inheriting a tight value shears descenders and Vietnamese * tone marks exactly as the sidebar label did. Keep >= 1.2."},{name:"--sidebar-nav-item-gap",value:"0.625rem",description:"Same gh#254 trap, two more clipping boxes. `.ui-auth-account-email` and the truncating last * child of `.ui-topbar-start` both clip (`overflow: hidden`/`clip` + `text-overflow: ellipsis`), * so their line box IS their clip box \u2014 inheriting a tight value shears descenders and Vietnamese * tone marks exactly as the sidebar label did. Keep >= 1.2."},{name:"--sidebar-nav-item-padding-x",value:"0.625rem",description:"Same gh#254 trap, two more clipping boxes. `.ui-auth-account-email` and the truncating last * child of `.ui-topbar-start` both clip (`overflow: hidden`/`clip` + `text-overflow: ellipsis`), * so their line box IS their clip box \u2014 inheriting a tight value shears descenders and Vietnamese * tone marks exactly as the sidebar label did. Keep >= 1.2."},{name:"--sidebar-nav-gap",value:"2px",description:"Same gh#254 trap, two more clipping boxes. `.ui-auth-account-email` and the truncating last * child of `.ui-topbar-start` both clip (`overflow: hidden`/`clip` + `text-overflow: ellipsis`), * so their line box IS their clip box \u2014 inheriting a tight value shears descenders and Vietnamese * tone marks exactly as the sidebar label did. Keep >= 1.2."},{name:"--sidebar-nav-scroll-padding",value:"var(--space-3) var(--space-2)",description:"Same gh#254 trap, two more clipping boxes. `.ui-auth-account-email` and the truncating last * child of `.ui-topbar-start` both clip (`overflow: hidden`/`clip` + `text-overflow: ellipsis`), * so their line box IS their clip box \u2014 inheriting a tight value shears descenders and Vietnamese * tone marks exactly as the sidebar label did. Keep >= 1.2."},{name:"--sidebar-section-gap",value:"var(--space-4)",description:"Same gh#254 trap, two more clipping boxes. `.ui-auth-account-email` and the truncating last * child of `.ui-topbar-start` both clip (`overflow: hidden`/`clip` + `text-overflow: ellipsis`), * so their line box IS their clip box \u2014 inheriting a tight value shears descenders and Vietnamese * tone marks exactly as the sidebar label did. Keep >= 1.2."},{name:"--sidebar-section-label-padding-x",value:"var(--space-2)",description:"Same gh#254 trap, two more clipping boxes. `.ui-auth-account-email` and the truncating last * child of `.ui-topbar-start` both clip (`overflow: hidden`/`clip` + `text-overflow: ellipsis`), * so their line box IS their clip box \u2014 inheriting a tight value shears descenders and Vietnamese * tone marks exactly as the sidebar label did. Keep >= 1.2."},{name:"--sidebar-section-label-padding-bottom",value:"var(--space-1)",description:"Same gh#254 trap, two more clipping boxes. `.ui-auth-account-email` and the truncating last * child of `.ui-topbar-start` both clip (`overflow: hidden`/`clip` + `text-overflow: ellipsis`), * so their line box IS their clip box \u2014 inheriting a tight value shears descenders and Vietnamese * tone marks exactly as the sidebar label did. Keep >= 1.2."},{name:"--topbar-search-max-width",value:"26.25rem",description:"Same gh#254 trap, two more clipping boxes. `.ui-auth-account-email` and the truncating last * child of `.ui-topbar-start` both clip (`overflow: hidden`/`clip` + `text-overflow: ellipsis`), * so their line box IS their clip box \u2014 inheriting a tight value shears descenders and Vietnamese * tone marks exactly as the sidebar label did. Keep >= 1.2."},{name:"--topbar-height",value:"auto",description:"Standalone <Topbar> geometry (rule #45). Topbar is a pure slot bar, so its box is intentionally * QUIET by default: `auto` height and no inline inset, i.e. exactly what it did before these knobs * existed \u2014 inside AppShell the `.app-topbar` grid row still owns the height. A service that * places Topbar directly on a page sets `--topbar-height: 3.5rem` / `--topbar-inset: var(--space-4)` * once instead of writing an app-local class. `--topbar-gap` is the gap BETWEEN the start/center/end * clusters and INSIDE each of them, so one knob re-rhythms the whole bar."},{name:"--topbar-inset",value:"0px",description:"Standalone <Topbar> geometry (rule #45). Topbar is a pure slot bar, so its box is intentionally * QUIET by default: `auto` height and no inline inset, i.e. exactly what it did before these knobs * existed \u2014 inside AppShell the `.app-topbar` grid row still owns the height. A service that * places Topbar directly on a page sets `--topbar-height: 3.5rem` / `--topbar-inset: var(--space-4)` * once instead of writing an app-local class. `--topbar-gap` is the gap BETWEEN the start/center/end * clusters and INSIDE each of them, so one knob re-rhythms the whole bar."},{name:"--topbar-gap",value:"var(--space-2)",description:"Standalone <Topbar> geometry (rule #45). Topbar is a pure slot bar, so its box is intentionally * QUIET by default: `auto` height and no inline inset, i.e. exactly what it did before these knobs * existed \u2014 inside AppShell the `.app-topbar` grid row still owns the height. A service that * places Topbar directly on a page sets `--topbar-height: 3.5rem` / `--topbar-inset: var(--space-4)` * once instead of writing an app-local class. `--topbar-gap` is the gap BETWEEN the start/center/end * clusters and INSIDE each of them, so one knob re-rhythms the whole bar."},{name:"--topbar-center-compact-display",value:"none",description:"At compact desktop/tablet widths the docked sidebar leaves too little inline room for three * intrinsically-sized clusters. The package hides the optional center slot before it can cover * the breadcrumb/title or end utilities. Consumers may opt back into another display strategy * (for example `flex`) once their center content has its own compact presentation."},{name:"--org-switcher-trigger-height",value:"2.75rem",description:"At compact desktop/tablet widths the docked sidebar leaves too little inline room for three * intrinsically-sized clusters. The package hides the optional center slot before it can cover * the breadcrumb/title or end utilities. Consumers may opt back into another display strategy * (for example `flex`) once their center content has its own compact presentation."},{name:"--org-switcher-trigger-padding-x",value:"var(--space-2)",description:"At compact desktop/tablet widths the docked sidebar leaves too little inline room for three * intrinsically-sized clusters. The package hides the optional center slot before it can cover * the breadcrumb/title or end utilities. Consumers may opt back into another display strategy * (for example `flex`) once their center content has its own compact presentation."},{name:"--org-switcher-avatar-size",value:"1.75rem",description:"At compact desktop/tablet widths the docked sidebar leaves too little inline room for three * intrinsically-sized clusters. The package hides the optional center slot before it can cover * the breadcrumb/title or end utilities. Consumers may opt back into another display strategy * (for example `flex`) once their center content has its own compact presentation."},{name:"--org-switcher-menu-width",value:"16rem",description:"At compact desktop/tablet widths the docked sidebar leaves too little inline room for three * intrinsically-sized clusters. The package hides the optional center slot before it can cover * the breadcrumb/title or end utilities. Consumers may opt back into another display strategy * (for example `flex`) once their center content has its own compact presentation."},{name:"--org-switcher-sheet-max-height",value:"75dvh",description:"At compact desktop/tablet widths the docked sidebar leaves too little inline room for three * intrinsically-sized clusters. The package hides the optional center slot before it can cover * the breadcrumb/title or end utilities. Consumers may opt back into another display strategy * (for example `flex`) once their center content has its own compact presentation."},{name:"--org-switcher-state-min-height",value:"8rem",description:"At compact desktop/tablet widths the docked sidebar leaves too little inline room for three * intrinsically-sized clusters. The package hides the optional center slot before it can cover * the breadcrumb/title or end utilities. Consumers may opt back into another display strategy * (for example `flex`) once their center content has its own compact presentation."},{name:"--sidebar-item-active-color",value:"initial",description:"Sidebar active-item tint/marker \u2014 `initial` so the role defaults re-resolve at the call site * under a scoped theme (a :root binding to a role var freezes at :root; a scoped role override * never reaches it). A service re-tunes the active sub-item accent without forking CSS. * Defaults = hsl(var(--primary)) marker/tint."},{name:"--sidebar-item-active-tint",value:"initial",description:"Sidebar active-item tint/marker \u2014 `initial` so the role defaults re-resolve at the call site * under a scoped theme (a :root binding to a role var freezes at :root; a scoped role override * never reaches it). A service re-tunes the active sub-item accent without forking CSS. * Defaults = hsl(var(--primary)) marker/tint."},{name:"--sidebar-item-active-background",value:"initial",description:"Main nav-item active row \u2014 defaults mirror the hover state (accent bg, foreground text); a * service overrides these to brand the selected row (e.g. a gold tint + gold text on a navy * sidebar). `initial` so the defaults re-resolve under a scoped theme. * Defaults = hsl(var(--accent)) fill \xB7 hsl(var(--foreground)) text."},{name:"--sidebar-item-active-foreground",value:"initial",description:"Main nav-item active row \u2014 defaults mirror the hover state (accent bg, foreground text); a * service overrides these to brand the selected row (e.g. a gold tint + gold text on a navy * sidebar). `initial` so the defaults re-resolve under a scoped theme. * Defaults = hsl(var(--accent)) fill \xB7 hsl(var(--foreground)) text."},{name:"--auth-shell-control-height",value:"var(--control-height-comfortable)",description:"AuthShell \u2014 centred auth/login page shell. Comfortable control density (44px, WCAG touch floor) * + a larger auth heading, scoped to the shell; a service re-tunes the auth card width, insets * and heading size without forking."},{name:"--auth-shell-heading-size",value:"var(--font-size-2xl)",description:"AuthShell \u2014 centred auth/login page shell. Comfortable control density (44px, WCAG touch floor) * + a larger auth heading, scoped to the shell; a service re-tunes the auth card width, insets * and heading size without forking."},{name:"--auth-shell-card-max-width",value:"24rem",description:"AuthShell \u2014 centred auth/login page shell. Comfortable control density (44px, WCAG touch floor) * + a larger auth heading, scoped to the shell; a service re-tunes the auth card width, insets * and heading size without forking."},{name:"--auth-shell-bar-padding",value:"var(--space-5) var(--space-6)",description:"AuthShell \u2014 centred auth/login page shell. Comfortable control density (44px, WCAG touch floor) * + a larger auth heading, scoped to the shell; a service re-tunes the auth card width, insets * and heading size without forking."},{name:"--auth-shell-main-padding",value:"var(--space-6)",description:"AuthShell \u2014 centred auth/login page shell. Comfortable control density (44px, WCAG touch floor) * + a larger auth heading, scoped to the shell; a service re-tunes the auth card width, insets * and heading size without forking."},{name:"--auth-shell-footer-padding",value:"var(--space-3) var(--space-6) var(--space-4)",description:"AuthShell \u2014 centred auth/login page shell. Comfortable control density (44px, WCAG touch floor) * + a larger auth heading, scoped to the shell; a service re-tunes the auth card width, insets * and heading size without forking."},{name:"--auth-shell-canonical-control-height",value:"2.25rem",description:"Canonical DXS auth preset. These are public theme knobs: consumers select the preset once on * AuthShell and may retune the product theme here rather than overriding individual fields."},{name:"--auth-shell-canonical-card-max-width",value:"22.5rem",description:"Canonical DXS auth preset. These are public theme knobs: consumers select the preset once on * AuthShell and may retune the product theme here rather than overriding individual fields."},{name:"--auth-shell-canonical-heading-size",value:"var(--font-size-base)",description:"Canonical DXS auth preset. These are public theme knobs: consumers select the preset once on * AuthShell and may retune the product theme here rather than overriding individual fields."},{name:"--auth-shell-canonical-main-padding",value:"1rem",description:"Canonical DXS auth preset. These are public theme knobs: consumers select the preset once on * AuthShell and may retune the product theme here rather than overriding individual fields."},{name:"--auth-shell-canonical-main-padding-mobile",value:"0.9375rem",description:"Canonical DXS auth preset. These are public theme knobs: consumers select the preset once on * AuthShell and may retune the product theme here rather than overriding individual fields."},{name:"--auth-shell-compact-card-inset",value:"var(--space-6)",description:"INLINE (start/end) inset of the compact auth card \u2014 the column the title, fields and actions * align to. Independent of the block axis below (gh#232)."},{name:"--auth-shell-card-padding-block-compact",value:"initial",description:"BLOCK (top/bottom) padding of the compact auth card \u2014 the knob that tunes canonical Login card * HEIGHT without narrowing the column. Bound to the card's --card-space-shell-y, so it really * reaches CardContent's block edges (before gh#232 it was wired only to --card-space-body-y and * the rendered body kept the inset on both block edges \u2014 the documented token did nothing). * Declared `initial` so the default mirrors the LIVE --auth-shell-compact-card-inset resolved at * the CALL SITE: the canonical card renders exactly as before (24px block = 24px inline), and a * service that re-tunes the inset inside a scoped `[data-tenant]` still drags the block default * with it (a :root binding would freeze at the :root inset \u2014 docs/TOKENS.md)."},{name:"--auth-shell-card-body-gap-compact",value:"var(--space-3)",description:"Header\u2194body gap inside the compact auth card (the breathing room under the title before the * first field). Its own knob since gh#232 \u2014 it used to ride on * --auth-shell-card-padding-block-compact, which conflated the section gap with the card's block * padding and made the block knob un-tunable. Default is the pre-gh#232 12px rhythm."},{name:"--auth-shell-card-gap-compact",value:"var(--space-3)",description:"IN-SLOT stack gap of the compact auth card (title \u2195 description inside the header)."},{name:"--auth-shell-main-align",value:"center",description:"Main-axis alignment of the auth column. Default `center` = today's vertically-centred card; * a flow whose intro + card + footnote stack is tall opts into `start` via this knob."},{name:"--auth-shell-card-stack-gap",value:"0px",description:"Gap between the direct sections of the auth column (intro \xB7 card \xB7 remember row). Default 0 * (quiet, rule #44) so an existing single-card consumer is untouched; presets opt in."},{name:"--auth-shell-login-card-max-width",value:"22.5rem",description:"Login preset (gh#237) \u2014 canonical SCR-001 anchors at 1440x900, 1024x900 and 390x844. * The 112px identity slot is deliberately taller than a two-line real requester. Identity * content aligns to the slot end, so missing/one-line/two-line requester data changes only the * empty space above it, never the card or footer position. The flow starts at y=231 desktop and * y=221 mobile: 231/221 + 112 + 20 gap = card y=363/353. Literal artboard lengths belong here, * not in a consumer selector, and do not drift with --scaling."},{name:"--auth-shell-login-flow-offset-block",value:"14.4375rem",description:"Login preset (gh#237) \u2014 canonical SCR-001 anchors at 1440x900, 1024x900 and 390x844. * The 112px identity slot is deliberately taller than a two-line real requester. Identity * content aligns to the slot end, so missing/one-line/two-line requester data changes only the * empty space above it, never the card or footer position. The flow starts at y=231 desktop and * y=221 mobile: 231/221 + 112 + 20 gap = card y=363/353. Literal artboard lengths belong here, * not in a consumer selector, and do not drift with --scaling."},{name:"--auth-shell-login-flow-offset-block-mobile",value:"13.8125rem",description:"Login preset (gh#237) \u2014 canonical SCR-001 anchors at 1440x900, 1024x900 and 390x844. * The 112px identity slot is deliberately taller than a two-line real requester. Identity * content aligns to the slot end, so missing/one-line/two-line requester data changes only the * empty space above it, never the card or footer position. The flow starts at y=231 desktop and * y=221 mobile: 231/221 + 112 + 20 gap = card y=363/353. Literal artboard lengths belong here, * not in a consumer selector, and do not drift with --scaling."},{name:"--auth-shell-login-main-padding-inline",value:"1rem",description:"Login preset (gh#237) \u2014 canonical SCR-001 anchors at 1440x900, 1024x900 and 390x844. * The 112px identity slot is deliberately taller than a two-line real requester. Identity * content aligns to the slot end, so missing/one-line/two-line requester data changes only the * empty space above it, never the card or footer position. The flow starts at y=231 desktop and * y=221 mobile: 231/221 + 112 + 20 gap = card y=363/353. Literal artboard lengths belong here, * not in a consumer selector, and do not drift with --scaling."},{name:"--auth-shell-login-main-padding-inline-mobile",value:"0.9375rem",description:"Login preset (gh#237) \u2014 canonical SCR-001 anchors at 1440x900, 1024x900 and 390x844. * The 112px identity slot is deliberately taller than a two-line real requester. Identity * content aligns to the slot end, so missing/one-line/two-line requester data changes only the * empty space above it, never the card or footer position. The flow starts at y=231 desktop and * y=221 mobile: 231/221 + 112 + 20 gap = card y=363/353. Literal artboard lengths belong here, * not in a consumer selector, and do not drift with --scaling."},{name:"--auth-shell-login-main-padding-block-end",value:"1rem",description:"Login preset (gh#237) \u2014 canonical SCR-001 anchors at 1440x900, 1024x900 and 390x844. * The 112px identity slot is deliberately taller than a two-line real requester. Identity * content aligns to the slot end, so missing/one-line/two-line requester data changes only the * empty space above it, never the card or footer position. The flow starts at y=231 desktop and * y=221 mobile: 231/221 + 112 + 20 gap = card y=363/353. Literal artboard lengths belong here, * not in a consumer selector, and do not drift with --scaling."},{name:"--auth-shell-login-identity-slot-block-size",value:"7rem",description:"Login preset (gh#237) \u2014 canonical SCR-001 anchors at 1440x900, 1024x900 and 390x844. * The 112px identity slot is deliberately taller than a two-line real requester. Identity * content aligns to the slot end, so missing/one-line/two-line requester data changes only the * empty space above it, never the card or footer position. The flow starts at y=231 desktop and * y=221 mobile: 231/221 + 112 + 20 gap = card y=363/353. Literal artboard lengths belong here, * not in a consumer selector, and do not drift with --scaling."},{name:"--auth-shell-login-card-stack-gap",value:"1.25rem",description:"Login preset (gh#237) \u2014 canonical SCR-001 anchors at 1440x900, 1024x900 and 390x844. * The 112px identity slot is deliberately taller than a two-line real requester. Identity * content aligns to the slot end, so missing/one-line/two-line requester data changes only the * empty space above it, never the card or footer position. The flow starts at y=231 desktop and * y=221 mobile: 231/221 + 112 + 20 gap = card y=363/353. Literal artboard lengths belong here, * not in a consumer selector, and do not drift with --scaling."},{name:"--auth-shell-login-card-padding-block-compact",value:"calc( var(--auth-shell-compact-card-inset) + var(--control-border-width) + var(--control-border-width) )",description:"Login preset (gh#237) \u2014 canonical SCR-001 anchors at 1440x900, 1024x900 and 390x844. * The 112px identity slot is deliberately taller than a two-line real requester. Identity * content aligns to the slot end, so missing/one-line/two-line requester data changes only the * empty space above it, never the card or footer position. The flow starts at y=231 desktop and * y=221 mobile: 231/221 + 112 + 20 gap = card y=363/353. Literal artboard lengths belong here, * not in a consumer selector, and do not drift with --scaling."},{name:"--auth-shell-device-card-max-width",value:"23.75rem",description:"Device-authorization preset \u2014 380px card measure, 15px block \xB7 5px inline mobile gutter * (at a 390px viewport the card is x=5px, width=380px)."},{name:"--auth-shell-device-main-padding",value:"1rem",description:"Device-authorization preset \u2014 380px card measure, 15px block \xB7 5px inline mobile gutter * (at a 390px viewport the card is x=5px, width=380px)."},{name:"--auth-shell-device-main-padding-mobile",value:"0.9375rem 0.3125rem",description:"Device-authorization preset \u2014 380px card measure, 15px block \xB7 5px inline mobile gutter * (at a 390px viewport the card is x=5px, width=380px)."},{name:"--auth-shell-context-card-max-width",value:"25rem",description:"Context-selection preset \u2014 25rem card measure on desktop/tablet, edge-to-edge on mobile * (0 inline gutter) with a 16px rhythm between the intro, the card and the remember row."},{name:"--auth-shell-context-main-padding",value:"var(--space-6)",description:"Context-selection preset \u2014 25rem card measure on desktop/tablet, edge-to-edge on mobile * (0 inline gutter) with a 16px rhythm between the intro, the card and the remember row."},{name:"--auth-shell-context-main-padding-mobile",value:"var(--space-6) 0",description:"Context-selection preset \u2014 25rem card measure on desktop/tablet, edge-to-edge on mobile * (0 inline gutter) with a 16px rhythm between the intro, the card and the remember row."},{name:"--auth-shell-context-card-stack-gap",value:"1rem",description:"Context-selection preset \u2014 25rem card measure on desktop/tablet, edge-to-edge on mobile * (0 inline gutter) with a 16px rhythm between the intro, the card and the remember row."},{name:"--auth-shell-recovery-card-max-width",value:"27rem",description:"Account-recovery preset (gh#233) \u2014 the SCR-008 measure shared by the password-recovery panel * (request \xB7 sent \xB7 new-password \xB7 expired) and the sign-in MFA challenge panel (OTP \xB7 * recovery-code \xB7 passkey-failure). Both canonical desktop panels measure w=432 at 1440, so ONE * preset owns them; the 360px canonical Login measure is untouched. * The mobile gutter is a DECIDED contract, not a traced artboard: the supplied 390 reference is a * desktop 2x2 composite that crops horizontally and cannot be measured. 15px inline at 390 \u21D2 the * panel is x=15, width=360 \u2014 the same page rhythm as the canonical Login flow, so a user moving * from Login to Recovery on a phone never sees the surface jump."},{name:"--auth-shell-recovery-main-padding",value:"1rem",description:"Account-recovery preset (gh#233) \u2014 the SCR-008 measure shared by the password-recovery panel * (request \xB7 sent \xB7 new-password \xB7 expired) and the sign-in MFA challenge panel (OTP \xB7 * recovery-code \xB7 passkey-failure). Both canonical desktop panels measure w=432 at 1440, so ONE * preset owns them; the 360px canonical Login measure is untouched. * The mobile gutter is a DECIDED contract, not a traced artboard: the supplied 390 reference is a * desktop 2x2 composite that crops horizontally and cannot be measured. 15px inline at 390 \u21D2 the * panel is x=15, width=360 \u2014 the same page rhythm as the canonical Login flow, so a user moving * from Login to Recovery on a phone never sees the surface jump."},{name:"--auth-shell-recovery-main-padding-mobile",value:"0.9375rem",description:"Account-recovery preset (gh#233) \u2014 the SCR-008 measure shared by the password-recovery panel * (request \xB7 sent \xB7 new-password \xB7 expired) and the sign-in MFA challenge panel (OTP \xB7 * recovery-code \xB7 passkey-failure). Both canonical desktop panels measure w=432 at 1440, so ONE * preset owns them; the 360px canonical Login measure is untouched. * The mobile gutter is a DECIDED contract, not a traced artboard: the supplied 390 reference is a * desktop 2x2 composite that crops horizontally and cannot be measured. 15px inline at 390 \u21D2 the * panel is x=15, width=360 \u2014 the same page rhythm as the canonical Login flow, so a user moving * from Login to Recovery on a phone never sees the surface jump."},{name:"--auth-shell-divider-gap",value:"0.625rem",description:"Labelled auth divider geometry."},{name:"--auth-shell-divider-label-font-size",value:"0.6875rem",description:"Labelled auth divider geometry."},{name:"--auth-shell-divider-rule-color",value:"var(--border)",description:"Labelled auth divider geometry."},{name:"--auth-shell-divider-label-color",value:"var(--muted-foreground)",description:"Labelled auth divider geometry."},{name:"--auth-identity-gap",value:"0.375rem",description:"Labelled auth divider geometry."},{name:"--auth-requester-gap",value:"0.375rem",description:"Labelled auth divider geometry."},{name:"--auth-requester-icon-size",value:"1rem",description:"Labelled auth divider geometry."},{name:"--auth-requester-glyph-size",value:"0.625rem",description:"Labelled auth divider geometry."},{name:"--auth-account-summary-min-height",value:"var(--control-height-comfortable)",description:"Labelled auth divider geometry."},{name:"--auth-account-summary-gap",value:"var(--space-2)",description:"Labelled auth divider geometry."},{name:"--auth-account-summary-padding",value:"var(--space-1) var(--space-2)",description:"Labelled auth divider geometry."},{name:"--auth-account-summary-identity-min-width",value:"12rem",description:"Labelled auth divider geometry."},{name:"--auth-account-summary-avatar-size",value:"var(--control-height-sm)",description:"Labelled auth divider geometry."},{name:"--auth-account-summary-avatar-glyph-size",value:"var(--control-icon-size-sm)",description:"Labelled auth divider geometry."},{name:"--auth-account-summary-email-font-size",value:"var(--font-size-sm)",description:"Labelled auth divider geometry."},{name:"--auth-footer-content-gap",value:"0.375rem",description:"Labelled auth divider geometry."},{name:"--auth-footer-text-font-size",value:"0.6875rem",description:"Labelled auth divider geometry."},{name:"--auth-stack-gap",value:"var(--space-3)",description:"Labelled auth divider geometry."},{name:"--auth-shell-field-label-gap",value:"var(--space-1)",description:"Labelled auth divider geometry."},{name:"--auth-shell-field-label-font-size",value:"var(--font-size-xs)",description:"Labelled auth divider geometry."},{name:"--auth-shell-field-label-line-height",value:"1.25",description:"Labelled auth divider geometry."},{name:"--centered-shell-bar-height",value:"var(--app-shell-bar-height)",description:'CenteredShell \u2014 authenticated, no-sidebar, centred-column page shell (hosted-ID "My Page", * account, standalone settings). The bar mirrors AppShell\'s `.app-topbar` chrome (fixed height + * inline padding); the column max-width has three tiers, all wider than the 24rem auth card. A * service retunes the bar inset, block padding and each width tier without forking CSS.'},{name:"--centered-shell-bar-padding-x",value:"var(--space-4)",description:'CenteredShell \u2014 authenticated, no-sidebar, centred-column page shell (hosted-ID "My Page", * account, standalone settings). The bar mirrors AppShell\'s `.app-topbar` chrome (fixed height + * inline padding); the column max-width has three tiers, all wider than the 24rem auth card. A * service retunes the bar inset, block padding and each width tier without forking CSS.'},{name:"--centered-shell-main-padding",value:"var(--space-6)",description:'CenteredShell \u2014 authenticated, no-sidebar, centred-column page shell (hosted-ID "My Page", * account, standalone settings). The bar mirrors AppShell\'s `.app-topbar` chrome (fixed height + * inline padding); the column max-width has three tiers, all wider than the 24rem auth card. A * service retunes the bar inset, block padding and each width tier without forking CSS.'},{name:"--centered-shell-footer-padding",value:"var(--space-3) var(--space-6) var(--space-4)",description:'CenteredShell \u2014 authenticated, no-sidebar, centred-column page shell (hosted-ID "My Page", * account, standalone settings). The bar mirrors AppShell\'s `.app-topbar` chrome (fixed height + * inline padding); the column max-width has three tiers, all wider than the 24rem auth card. A * service retunes the bar inset, block padding and each width tier without forking CSS.'},{name:"--centered-shell-column-offset-block",value:"0",description:'Block offset of the centred column inside the 100dvh shell. `0` (default, the quietest state \u2014 * rule #44) keeps the top-aligned flowing page; `auto` centres the column in the viewport, which * is what CenteredShell `align="center"` sets for a SYSTEM-level standalone surface (500/503 * error page, maintenance notice). Auto block offsets collapse to 0 when the content is taller * than the viewport, so a long localized message scrolls from the top instead of clipping.'},{name:"--centered-shell-width-sm",value:"32rem",description:'Block offset of the centred column inside the 100dvh shell. `0` (default, the quietest state \u2014 * rule #44) keeps the top-aligned flowing page; `auto` centres the column in the viewport, which * is what CenteredShell `align="center"` sets for a SYSTEM-level standalone surface (500/503 * error page, maintenance notice). Auto block offsets collapse to 0 when the content is taller * than the viewport, so a long localized message scrolls from the top instead of clipping.'},{name:"--centered-shell-width-md",value:"46rem",description:'Block offset of the centred column inside the 100dvh shell. `0` (default, the quietest state \u2014 * rule #44) keeps the top-aligned flowing page; `auto` centres the column in the viewport, which * is what CenteredShell `align="center"` sets for a SYSTEM-level standalone surface (500/503 * error page, maintenance notice). Auto block offsets collapse to 0 when the content is taller * than the viewport, so a long localized message scrolls from the top instead of clipping.'},{name:"--centered-shell-width-lg",value:"64rem",description:'Block offset of the centred column inside the 100dvh shell. `0` (default, the quietest state \u2014 * rule #44) keeps the top-aligned flowing page; `auto` centres the column in the viewport, which * is what CenteredShell `align="center"` sets for a SYSTEM-level standalone surface (500/503 * error page, maintenance notice). Auto block offsets collapse to 0 when the content is taller * than the viewport, so a long localized message scrolls from the top instead of clipping.'},{name:"--centered-shell-landing-max-width",value:"67.5rem",description:'CenteredShell \xB7 public-landing preset (gh#252) \u2014 the token-owned geometry of a PUBLIC marketing * / product landing page: one content measure shared by header, main column and footer, the * section rhythm between page sections, the flat (chrome-quiet) card presentation and the hero * heading tier. Every value is a service-themeable knob (rule #45): a landing composition is a * COMPOSITION of real primitives (docs/COMPOSITION-VS-COMPONENT.md \u2014 Hero/Navbar/Footer FAIL the * Framework-Component Test), so the consumer must never own geometry or a media query. These * knobs are read ONLY under `.ui-centered-shell[data-preset="public-landing"]`, so the default * shell \u2014 which emits no `data-preset` at all \u2014 is byte-identical to before.'},{name:"--centered-shell-landing-inset-inline",value:"var(--space-6)",description:"1080px \u2014 header/main/footer share ONE measure"},{name:"--centered-shell-landing-inset-inline-compact",value:"var(--space-4)",description:"24px page gutter \u2265 40rem"},{name:"--centered-shell-landing-main-padding-block",value:"var(--space-10)",description:"16px page gutter < 40rem"},{name:"--centered-shell-landing-main-padding-block-compact",value:"var(--space-6)",description:"40px"},{name:"--centered-shell-landing-section-gap",value:"var(--space-10)",description:"24px"},{name:"--centered-shell-landing-section-gap-compact",value:"var(--space-6)",description:"rhythm BETWEEN page sections"},{name:"--centered-shell-landing-footer-padding-block",value:"var(--space-8)",description:"rhythm BETWEEN page sections"},{name:"--centered-shell-landing-card-shadow",value:"none",description:"Chrome is a token, default quiet (rule #44): a landing surface is FLAT \u2014 no card elevation \u2014 * so a service opts back IN with `--centered-shell-landing-card-shadow: var(--card-shadow)`."},{name:"--centered-shell-landing-heading-size",value:"var(--font-size-display)",description:"Hero heading tier. The preset re-points `--heading-h1` (same mechanism as * `.ui-auth-shell` \u2192 `--auth-shell-heading-size`), so a hero title is a real `Heading level={1}` * and never a consumer font-size."},{name:"--centered-shell-landing-heading-size-compact",value:"var(--font-size-3xl)",description:"Hero heading tier. The preset re-points `--heading-h1` (same mechanism as * `.ui-auth-shell` \u2192 `--auth-shell-heading-size`), so a hero title is a real `Heading level={1}` * and never a consumer font-size."},{name:"--centered-shell-landing-background",value:"initial",description:'Role-mirror knob \u2014 MUST stay `initial`, with the role default at the CALL SITE * (`var(--centered-shell-landing-background, var(--background))`), so a scoped * `[data-tenant]` / `.dark` override of `--background` still reaches it (docs/TOKENS.md \xB7 * "Role-mirror knobs MUST be `initial`"). Documented default = hsl(var(--background)) \u2014 a public * page is the plain page canvas, not the app\'s `--muted` chrome.'},{name:"--sidebar-nav-item-foreground",value:"initial",description:"Resting nav row + label (also the resting sub-row). Default = hsl(var(--muted-foreground))."},{name:"--sidebar-nav-item-hover-foreground",value:"initial",description:"Hovered nav row + label. Default = hsl(var(--foreground))."},{name:"--sidebar-nav-item-disabled-foreground",value:"initial",description:"`aria-disabled` nav row + label. Default = the resting row colour."},{name:"--sidebar-nav-icon-foreground",value:"initial",description:"Nav ICON (`.sb-icon`, incl. the collapsed rail and group triggers). Default = currentColor, * i.e. the row colour \u2014 the pre-gh#228 look. Set this alone to get canonical dark icons next to * muted labels."},{name:"--sidebar-nav-icon-hover-foreground",value:"initial",description:"Icon on a hovered / active / disabled row. Each falls back to --sidebar-nav-icon-foreground, * then to currentColor, so setting the base icon knob alone themes every state."},{name:"--sidebar-nav-icon-active-foreground",value:"initial",description:"Icon on a hovered / active / disabled row. Each falls back to --sidebar-nav-icon-foreground, * then to currentColor, so setting the base icon knob alone themes every state."},{name:"--sidebar-nav-icon-disabled-foreground",value:"initial",description:"Icon on a hovered / active / disabled row. Each falls back to --sidebar-nav-icon-foreground, * then to currentColor, so setting the base icon knob alone themes every state."},{name:"--table-row-height-compact",value:"1.75rem",description:"Table component tokens: row height, cell padding."},{name:"--table-row-height-default",value:"2rem",description:"Table component tokens: row height, cell padding."},{name:"--table-row-height-comfortable",value:"2.75rem",description:"Table component tokens: row height, cell padding."},{name:"--table-row-height",value:"calc(var(--table-row-height-default) * var(--scaling))",description:"Table component tokens: row height, cell padding."},{name:"--table-cell-padding-y",value:"var(--space-2)",description:"Table component tokens: row height, cell padding."},{name:"--table-cell-space-x",value:"var(--control-padding-x)",description:"Table component tokens: row height, cell padding."},{name:"--table-pagination-padding-y",value:"initial",description:"Pagination footer inset (gh#236). The footer ships as a SELF-CONTAINED slot and is commonly * placed in a flush container (`<Card><CardContent flush><DataTable/>`), where no ancestor * supplies padding \u2014 so it owns its own on BOTH axes instead of declaring block-start only. * Block default = the previous single `padding-top` value, now on both block edges (breathing * room above the footer and before the container's closing border). Inline default = the cell * inline padding, so the \"rows per page\" label sits on the same optical axis as the first * column's text. A service retunes either axis to its own grid (#45). * Both are declared `initial` so the defaults re-resolve at the CALL SITE: --space-stack-sm and * --table-cell-space-x are density-scaled and re-declared inside a `.ui-density-*` subtree, and a * :root binding would freeze the footer at the :root density (docs/TOKENS.md \u2014 the :root freeze * rule). Defaults = var(--space-stack-sm) block \xB7 var(--table-cell-space-x) inline."},{name:"--table-pagination-padding-x",value:"initial",description:"Pagination footer inset (gh#236). The footer ships as a SELF-CONTAINED slot and is commonly * placed in a flush container (`<Card><CardContent flush><DataTable/>`), where no ancestor * supplies padding \u2014 so it owns its own on BOTH axes instead of declaring block-start only. * Block default = the previous single `padding-top` value, now on both block edges (breathing * room above the footer and before the container's closing border). Inline default = the cell * inline padding, so the \"rows per page\" label sits on the same optical axis as the first * column's text. A service retunes either axis to its own grid (#45). * Both are declared `initial` so the defaults re-resolve at the CALL SITE: --space-stack-sm and * --table-cell-space-x are density-scaled and re-declared inside a `.ui-density-*` subtree, and a * :root binding would freeze the footer at the :root density (docs/TOKENS.md \u2014 the :root freeze * rule). Defaults = var(--space-stack-sm) block \xB7 var(--table-cell-space-x) inline."},{name:"--table-surface-min-inline-size",value:"640px",description:'DataTable surface \u2014 narrow-viewport legibility FLOOR (gh#253). Below the `sm` viewport step a * multi-column admin grid whose columns are `white-space: nowrap` would otherwise be crushed, so * the bordered surface keeps a minimum inline size and `.ui-data-table-scroll` scrolls instead. * This shipped as a hard-coded `min-w-[640px]` utility on the surface element, which left a * service that wants a narrower (or no) floor with no route but forking the component (#45). * Default = the previous literal, in px so the floor releases at EXACTLY the same width as the * px-based `sm` media query that clears it (a rem value would drift under a non-16px root and * re-introduce the scroll between the two thresholds). Set `0` to opt out entirely \u2014 which is * what `preset="action-collection"` does, because there the priority measures own the width.'},{name:"--table-head-font-size",value:"var(--font-size-xs)",description:'DataTable surface \u2014 narrow-viewport legibility FLOOR (gh#253). Below the `sm` viewport step a * multi-column admin grid whose columns are `white-space: nowrap` would otherwise be crushed, so * the bordered surface keeps a minimum inline size and `.ui-data-table-scroll` scrolls instead. * This shipped as a hard-coded `min-w-[640px]` utility on the surface element, which left a * service that wants a narrower (or no) floor with no route but forking the component (#45). * Default = the previous literal, in px so the floor releases at EXACTLY the same width as the * px-based `sm` media query that clears it (a rem value would drift under a non-16px root and * re-introduce the scroll between the two thresholds). Set `0` to opt out entirely \u2014 which is * what `preset="action-collection"` does, because there the priority measures own the width.'},{name:"--table-header-background",value:"initial",description:"Header band \u2014 its OWN bg + fg knobs (decoupled from --secondary). Declared `initial` so the * default re-resolves to the LIVE --muted / --muted-foreground roles at the call site: a :root * binding to a role var freezes at the :root value and a scoped [data-tenant] role override never * reaches it. A brand sets both header tokens together to keep band/text contrast. * Defaults = hsl(var(--muted)) band \xB7 hsl(var(--muted-foreground)) text."},{name:"--table-header-foreground",value:"initial",description:"Header band \u2014 its OWN bg + fg knobs (decoupled from --secondary). Declared `initial` so the * default re-resolves to the LIVE --muted / --muted-foreground roles at the call site: a :root * binding to a role var freezes at the :root value and a scoped [data-tenant] role override never * reaches it. A brand sets both header tokens together to keep band/text contrast. * Defaults = hsl(var(--muted)) band \xB7 hsl(var(--muted-foreground)) text."},{name:"--table-pin-shadow",value:"-6px 0 6px -5px hsl(var(--foreground) / 0.12)",description:"Inline-end shadow that lifts a pinned (sticky) action column off the body it scrolls over."},{name:"--table-row-striped-background",value:"initial",description:"Row-state tint washes \u2014 translucent muted over the opaque base. `initial` so the --muted * default re-resolves under a scoped theme; a service retints by reading another role (e.g. * --primary). Defaults = hsl(var(--muted) / 0.4 striped \xB7 0.5 hover \xB7 0.3 selected)."},{name:"--table-row-hover-background",value:"initial",description:"Row-state tint washes \u2014 translucent muted over the opaque base. `initial` so the --muted * default re-resolves under a scoped theme; a service retints by reading another role (e.g. * --primary). Defaults = hsl(var(--muted) / 0.4 striped \xB7 0.5 hover \xB7 0.3 selected)."},{name:"--table-row-selected-background",value:"initial",description:"Row-state tint washes \u2014 translucent muted over the opaque base. `initial` so the --muted * default re-resolves under a scoped theme; a service retints by reading another role (e.g. * --primary). Defaults = hsl(var(--muted) / 0.4 striped \xB7 0.5 hover \xB7 0.3 selected)."},{name:"--table-action-collection-primary-width",value:"18%",description:'Table \xB7 action-collection preset (gh#253) \u2014 the canonical dense approval / action queue. * Column PRIORITY measures replace the desktop intrinsic widths (a nowrap free-text column is * what makes a five-column queue wider than its card and forces a horizontal scroll). Nothing * here is read unless `preset="action-collection"` is set, so every existing table is untouched. * Percentages so the ratio holds at any card width; the action affordance is an absolute measure * because it must never be squeezed below its touch target.'},{name:"--table-action-collection-secondary-width",value:"22%",description:"requester / subject"},{name:"--table-action-collection-meta-width",value:"12%",description:"target / resource"},{name:"--table-action-collection-actions-width",value:"3.5rem",description:"timestamps, ids \u2014 lowest priority"},{name:"--table-action-collection-primary-width-compact",value:"24%",description:"Compact tier, applied below the collapse step (the 390px acceptance frame)."},{name:"--table-action-collection-secondary-width-compact",value:"22%",description:"Compact tier, applied below the collapse step (the 390px acceptance frame)."},{name:"--table-action-collection-meta-width-compact",value:"20%",description:"Compact tier, applied below the collapse step (the 390px acceptance frame)."},{name:"--table-action-collection-actions-width-compact",value:"2.75rem",description:"Compact tier, applied below the collapse step (the 390px acceptance frame)."},{name:"--table-action-collection-font-size-compact",value:"var(--font-size-xs)",description:"Compact tier, applied below the collapse step (the 390px acceptance frame)."},{name:"--table-action-collection-cell-space-x-compact",value:"var(--space-2)",description:"Compact tier, applied below the collapse step (the 390px acceptance frame)."},{name:"--table-action-collection-cell-padding-y-compact",value:"var(--space-2)",description:"Compact tier, applied below the collapse step (the 390px acceptance frame)."},{name:"--table-action-collection-row-height-compact",value:"var(--table-row-height-compact)",description:"Compact tier, applied below the collapse step (the 390px acceptance frame)."}];var p={issue:"https://github.com/godx-jp/godxjp-ui/issues/163",standard:"docs/FRAME-COVERAGE-STANDARD.md",ledger:"preview/frame-coverage.ledger.json",recordedAt:"2026-07-30",warning:"UNTESTED means no executed case demonstrates this dimension. It is NOT a pass. Do not infer support from a happy-path example, a similarly-named frame, or a rendered screenshot.",verdicts:{covered:"An executed case proves this dimension.",untested:"No executed case. NOT a pass \u2014 report it as UNTESTED to the user.","not-applicable":"The dimension cannot exist for this export; the ledger carries the written reason."},requiredViewports:[320,375,390,768,1024,1280,1440,1920],containerWidths:[240,320,480,640,960],totals:{exports:276,dimensionCells:3864,covered:64,untested:2112,notApplicable:1688,exportsWithoutFrame:0},dimensions:[{id:"variants",title:"Variants",axis:"visual"},{id:"tones",title:"Tones",axis:"visual"},{id:"sizes",title:"Sizes",axis:"visual"},{id:"shapes",title:"Shapes",axis:"visual"},{id:"density",title:"Spacing / density",axis:"visual"},{id:"ownership",title:"Controlled / uncontrolled / reset",axis:"ownership"},{id:"states",title:"Disabled / read-only / loading / empty / error / success",axis:"state"},{id:"async",title:"Async: loading / reject / retry / cancel / offline",axis:"async"},{id:"responsive",title:"Responsive viewport matrix",axis:"responsive"},{id:"rtl",title:"RTL / direction",axis:"international"},{id:"contentStress",title:"Long / localized content stress",axis:"contentStress"},{id:"keyboard",title:"Keyboard / focus order / focus return",axis:"accessibility"},{id:"accessibleName",title:"Accessible name / description / error association",axis:"accessibility"},{id:"reducedMotion",title:"Preferences: reduced motion / forced colors / 200% zoom / coarse touch",axis:"preferences"}],knownGaps:[{id:"button-contract-gaps",targets:["Button"],dimensions:["states","contentStress","rtl","keyboard","reducedMotion"],cases:["implicit form `type` inside a form","loading combined with asChild","count / overflowCount / showZero overflow","long Japanese and Vietnamese labels","RTL","coarse touch target"]},{id:"card-contract-gaps",targets:["Card"],dimensions:["variants","density","rtl","accessibleName"],cases:["`variant` \xD7 `accent` \xD7 `size` matrix",'`density="tight"` spacing mode',"nested heading levels","RTL accent rail"]},{id:"card-subcomponent-contract-gaps",targets:["CardBar","CardContent","CardFooter","CardHeader"],dimensions:["rtl","accessibleName","contentStress"],cases:["CardBar `extra` slot","CardContent `tight` / `flush` / `solo`","CardFooter `flush` / `separated`","CardHeader `banded`","RTL"]},{id:"empty-state-contract-gaps",targets:["EmptyState"],dimensions:["variants","contentStress","accessibleName"],cases:["`section` variant","`compact` variant","without icon / description / action","heading level (`titleAs` / `titleLevel`) and live-region semantics"]},{id:"progress-contract-gaps",targets:["Progress"],dimensions:["tones","contentStress"],cases:["destructive tone","`over={true}` (value beyond 100%)"]},{id:"carousel-contract-gaps",targets:["Carousel"],dimensions:["ownership","contentStress","keyboard","reducedMotion","rtl"],cases:["single slide","plugins / setApi","autoplay pause","arrow-key traversal alongside tabs","touch / swipe","RTL"]},{id:"data-table-contract-gaps",targets:["DataTable"],dimensions:["states","async","keyboard","accessibleName","density"],cases:["error / prerequisite / background refresh","server and manual modes (manualPagination / manualSorting / manualFiltering)","zero-page and single-page pagination","accessible table and action-column labels","invalid row ids (getRowId)","keyboard behaviour","virtualization"]},{id:"charts-contract-gaps",targets:["LineChart","BarChart","AreaChart","PieChart"],dimensions:["async","reducedMotion","keyboard","contentStress","responsive","rtl"],cases:["loading / error","reduced motion","keyboard datum navigation","invalid / null / NaN data","mobile axis collision","RTL"]},{id:"locale-stress-gaps",targets:["Timeline","TreeList","PasswordStrength"],dimensions:["contentStress","accessibleName"],cases:["long localized Japanese / Vietnamese content","hard-coded screen-reader strings that never reach `t()`"]}]},z=[{name:"Accordion",group:"data-display",frame:"docs/data-display/accordion.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","ownership","reducedMotion","responsive","rtl","states"],notApplicable:["density","shapes","sizes","tones","variants"]},{name:"AccordionContent",group:"data-display",frame:"docs/data-display/accordion.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl"],notApplicable:["density","ownership","shapes","sizes","states","tones","variants"]},{name:"AccordionItem",group:"data-display",frame:"docs/data-display/accordion.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","ownership","reducedMotion","responsive","rtl","states"],notApplicable:["density","shapes","sizes","tones","variants"]},{name:"AccordionTrigger",group:"data-display",frame:"docs/data-display/accordion.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl"],notApplicable:["density","ownership","shapes","sizes","states","tones","variants"]},{name:"Alert",group:"feedback",frame:"docs/feedback/alert.tsx",covered:["tones","variants"],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl"],notApplicable:["density","ownership","shapes","sizes","states"]},{name:"AlertActions",group:"feedback",frame:"docs/feedback/alert.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl"],notApplicable:["density","ownership","shapes","sizes","states","tones","variants"]},{name:"AlertContent",group:"feedback",frame:"docs/feedback/alert.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl"],notApplicable:["density","ownership","shapes","sizes","states","tones","variants"]},{name:"AlertDescription",group:"feedback",frame:"docs/feedback/alert.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl"],notApplicable:["density","ownership","shapes","sizes","states","tones","variants"]},{name:"AlertDialog",group:"feedback",frame:"docs/feedback/dialog.tsx",covered:["variants"],untested:["accessibleName","async","contentStress","keyboard","ownership","reducedMotion","responsive","rtl","states"],notApplicable:["density","shapes","sizes","tones"]},{name:"AlertDialogAction",group:"feedback",frame:"docs/feedback/dialog.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","ownership","reducedMotion","responsive","rtl","states"],notApplicable:["density","shapes","sizes","tones","variants"]},{name:"AlertDialogCancel",group:"feedback",frame:"docs/feedback/dialog.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","ownership","reducedMotion","responsive","rtl","states"],notApplicable:["density","shapes","sizes","tones","variants"]},{name:"AlertDialogContent",group:"feedback",frame:"docs/feedback/dialog.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl"],notApplicable:["density","ownership","shapes","sizes","states","tones","variants"]},{name:"AlertDialogDescription",group:"feedback",frame:"docs/feedback/dialog.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl"],notApplicable:["density","ownership","shapes","sizes","states","tones","variants"]},{name:"AlertDialogFooter",group:"feedback",frame:"docs/feedback/dialog.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl"],notApplicable:["density","ownership","shapes","sizes","states","tones","variants"]},{name:"AlertDialogHeader",group:"feedback",frame:"docs/feedback/dialog.tsx",covered:["tones"],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl"],notApplicable:["density","ownership","shapes","sizes","states","variants"]},{name:"AlertDialogOverlay",group:"feedback",frame:"docs/feedback/dialog.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl"],notApplicable:["density","ownership","shapes","sizes","states","tones","variants"]},{name:"AlertDialogPortal",group:"feedback",frame:"docs/feedback/dialog.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl"],notApplicable:["density","ownership","shapes","sizes","states","tones","variants"]},{name:"AlertDialogRoot",group:"feedback",frame:"docs/feedback/dialog.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","ownership","reducedMotion","responsive","rtl"],notApplicable:["density","shapes","sizes","states","tones","variants"]},{name:"AlertDialogTitle",group:"feedback",frame:"docs/feedback/dialog.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl"],notApplicable:["density","ownership","shapes","sizes","states","tones","variants"]},{name:"AlertDialogTrigger",group:"feedback",frame:"docs/feedback/dialog.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","ownership","reducedMotion","responsive","rtl","states"],notApplicable:["density","shapes","sizes","tones","variants"]},{name:"AlertMutationFeedback",group:"query",frame:"docs/query/mutation-feedback.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl","states"],notApplicable:["density","ownership","shapes","sizes","tones","variants"]},{name:"AlertQueryError",group:"feedback",frame:"docs/feedback/alert.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl","states"],notApplicable:["density","ownership","shapes","sizes","tones","variants"]},{name:"AlertTitle",group:"feedback",frame:"docs/feedback/alert.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl"],notApplicable:["density","ownership","shapes","sizes","states","tones","variants"]},{name:"AppSettingPicker",group:"navigation",frame:"docs/navigation/app-setting-picker.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","ownership","reducedMotion","responsive","rtl","states"],notApplicable:["density","shapes","sizes","tones","variants"]},{name:"AppShell",group:"layout",frame:"docs/layout/app-shell.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl"],notApplicable:["density","ownership","shapes","sizes","states","tones","variants"]},{name:"AreaChart",group:"charts",frame:"docs/data-display/charts.tsx",covered:["sizes"],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl"],notApplicable:["density","ownership","shapes","states","tones","variants"]},{name:"AspectRatio",group:"layout",frame:"docs/layout/aspect-ratio.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl"],notApplicable:["density","ownership","shapes","sizes","states","tones","variants"]},{name:"AuthAccountSummary",group:"layout",frame:"docs/layout/auth-account-summary.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl","states"],notApplicable:["density","ownership","shapes","sizes","tones","variants"]},{name:"AuthDivider",group:"layout",frame:"docs/layout/auth-divider.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl"],notApplicable:["density","ownership","shapes","sizes","states","tones","variants"]},{name:"AuthFooter",group:"layout",frame:"docs/layout/auth-footer.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl"],notApplicable:["density","ownership","shapes","sizes","states","tones","variants"]},{name:"AuthIdentity",group:"layout",frame:"docs/layout/auth-identity.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl"],notApplicable:["density","ownership","shapes","sizes","states","tones","variants"]},{name:"AuthShell",group:"layout",frame:"docs/layout/auth-shell.tsx",covered:["density","variants"],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl"],notApplicable:["ownership","shapes","sizes","states","tones"]},{name:"AuthStack",group:"layout",frame:"docs/layout/auth-stack.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl"],notApplicable:["density","ownership","shapes","sizes","states","tones","variants"]},{name:"Avatar",group:"data-display",frame:"docs/data-display/avatar.tsx",covered:["shapes"],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl"],notApplicable:["density","ownership","sizes","states","tones","variants"]},{name:"AvatarFallback",group:"data-display",frame:"docs/data-display/avatar.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl"],notApplicable:["density","ownership","shapes","sizes","states","tones","variants"]},{name:"AvatarImage",group:"data-display",frame:"docs/data-display/avatar.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl"],notApplicable:["density","ownership","shapes","sizes","states","tones","variants"]},{name:"Badge",group:"data-display",frame:"docs/data-display/badge.tsx",covered:["shapes","tones","variants"],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl"],notApplicable:["density","ownership","sizes","states"]},{name:"BarChart",group:"charts",frame:"docs/data-display/charts.tsx",covered:["sizes"],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl"],notApplicable:["density","ownership","shapes","states","tones","variants"]},{name:"Breadcrumb",group:"layout",frame:"docs/navigation/breadcrumb.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl"],notApplicable:["density","ownership","shapes","sizes","states","tones","variants"]},{name:"Button",group:"general",frame:"docs/general/button/index.tsx",covered:["shapes","sizes","variants"],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl","states"],notApplicable:["density","ownership","tones"]},{name:"ButtonRefetch",group:"query",frame:"docs/query/button-refetch.tsx",covered:["shapes","sizes","variants"],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl","states"],notApplicable:["density","ownership","tones"]},{name:"Calendar",group:"data-entry",frame:"docs/data-entry/calendar.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl","states"],notApplicable:["density","ownership","shapes","sizes","tones","variants"]},{name:"Card",group:"data-display",frame:"docs/data-display/card/index.tsx",covered:["density","sizes","variants"],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl"],notApplicable:["ownership","shapes","states","tones"]},{name:"CardAction",group:"data-display",frame:"docs/data-display/card/index.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl"],notApplicable:["density","ownership","shapes","sizes","states","tones","variants"]},{name:"CardBar",group:"data-display",frame:"docs/data-display/card/index.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl"],notApplicable:["density","ownership","shapes","sizes","states","tones","variants"]},{name:"CardContent",group:"data-display",frame:"docs/data-display/card/index.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl"],notApplicable:["density","ownership","shapes","sizes","states","tones","variants"]},{name:"CardCover",group:"data-display",frame:"docs/data-display/card/index.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl"],notApplicable:["density","ownership","shapes","sizes","states","tones","variants"]},{name:"CardDescription",group:"data-display",frame:"docs/data-display/card/index.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl"],notApplicable:["density","ownership","shapes","sizes","states","tones","variants"]},{name:"CardFooter",group:"data-display",frame:"docs/data-display/card/index.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl"],notApplicable:["density","ownership","shapes","sizes","states","tones","variants"]},{name:"CardHeader",group:"data-display",frame:"docs/data-display/card/index.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl"],notApplicable:["density","ownership","shapes","sizes","states","tones","variants"]},{name:"CardTitle",group:"data-display",frame:"docs/data-display/card/index.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl"],notApplicable:["density","ownership","shapes","sizes","states","tones","variants"]},{name:"Carousel",group:"data-display",frame:"docs/data-display/carousel.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","ownership","reducedMotion","responsive","rtl"],notApplicable:["density","shapes","sizes","states","tones","variants"]},{name:"CarouselContent",group:"data-display",frame:"docs/data-display/carousel.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl"],notApplicable:["density","ownership","shapes","sizes","states","tones","variants"]},{name:"CarouselDots",group:"data-display",frame:"docs/data-display/carousel.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl"],notApplicable:["density","ownership","shapes","sizes","states","tones","variants"]},{name:"CarouselItem",group:"data-display",frame:"docs/data-display/carousel.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl"],notApplicable:["density","ownership","shapes","sizes","states","tones","variants"]},{name:"CarouselNext",group:"data-display",frame:"docs/data-display/carousel.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","ownership","reducedMotion","responsive","rtl","states"],notApplicable:["density","shapes","sizes","tones","variants"]},{name:"CarouselPrevious",group:"data-display",frame:"docs/data-display/carousel.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","ownership","reducedMotion","responsive","rtl","states"],notApplicable:["density","shapes","sizes","tones","variants"]},{name:"Cascader",group:"data-entry",frame:"docs/data-entry/cascader.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","ownership","reducedMotion","responsive","rtl","states"],notApplicable:["density","shapes","sizes","tones","variants"]},{name:"CenteredShell",group:"layout",frame:"docs/layout/centered-shell.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl"],notApplicable:["density","ownership","shapes","sizes","states","tones","variants"]},{name:"Checkbox",group:"data-entry",frame:"docs/data-entry/checkbox.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","ownership","reducedMotion","responsive","rtl","states"],notApplicable:["density","shapes","sizes","tones","variants"]},{name:"CheckboxGroup",group:"data-entry",frame:"docs/data-entry/checkbox.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","ownership","reducedMotion","responsive","rtl","states"],notApplicable:["density","shapes","sizes","tones","variants"]},{name:"Collapsible",group:"data-display",frame:"docs/data-display/collapsible.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","ownership","reducedMotion","responsive","rtl","states"],notApplicable:["density","shapes","sizes","tones","variants"]},{name:"CollapsibleContent",group:"data-display",frame:"docs/data-display/collapsible.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl"],notApplicable:["density","ownership","shapes","sizes","states","tones","variants"]},{name:"CollapsibleTrigger",group:"data-display",frame:"docs/data-display/collapsible.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl"],notApplicable:["density","ownership","shapes","sizes","states","tones","variants"]},{name:"ColorPicker",group:"data-entry",frame:"docs/data-entry/color-picker.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","ownership","reducedMotion","responsive","rtl","states"],notApplicable:["density","shapes","sizes","tones","variants"]},{name:"Command",group:"data-entry",frame:"docs/data-entry/command.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","ownership","reducedMotion","responsive","rtl"],notApplicable:["density","shapes","sizes","states","tones","variants"]},{name:"CommandEmpty",group:"data-entry",frame:"docs/data-entry/command.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl"],notApplicable:["density","ownership","shapes","sizes","states","tones","variants"]},{name:"CommandGroup",group:"data-entry",frame:"docs/data-entry/command.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","ownership","reducedMotion","responsive","rtl"],notApplicable:["density","shapes","sizes","states","tones","variants"]},{name:"CommandInput",group:"data-entry",frame:"docs/data-entry/command.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","ownership","reducedMotion","responsive","rtl"],notApplicable:["density","shapes","sizes","states","tones","variants"]},{name:"CommandItem",group:"data-entry",frame:"docs/data-entry/command.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","ownership","reducedMotion","responsive","rtl","states"],notApplicable:["density","shapes","sizes","tones","variants"]},{name:"CommandList",group:"data-entry",frame:"docs/data-entry/command.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl"],notApplicable:["density","ownership","shapes","sizes","states","tones","variants"]},{name:"CommandPalette",group:"data-entry",frame:"docs/data-entry/command-palette.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","ownership","reducedMotion","responsive","rtl","states"],notApplicable:["density","shapes","sizes","tones","variants"]},{name:"CompactBarTrend",group:"charts",frame:"docs/charts/compact-bar-trend.tsx",covered:["sizes"],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl"],notApplicable:["density","ownership","shapes","states","tones","variants"]},{name:"ContextMenu",group:"navigation",frame:"docs/navigation/context-menu.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","ownership","reducedMotion","responsive","rtl"],notApplicable:["density","shapes","sizes","states","tones","variants"]},{name:"ContextMenuCheckboxItem",group:"navigation",frame:"docs/navigation/context-menu.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","ownership","reducedMotion","responsive","rtl","states"],notApplicable:["density","shapes","sizes","tones","variants"]},{name:"ContextMenuContent",group:"navigation",frame:"docs/navigation/context-menu.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl"],notApplicable:["density","ownership","shapes","sizes","states","tones","variants"]},{name:"ContextMenuItem",group:"navigation",frame:"docs/navigation/context-menu.tsx",covered:["variants"],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl","states"],notApplicable:["density","ownership","shapes","sizes","tones"]},{name:"ContextMenuLabel",group:"navigation",frame:"docs/navigation/context-menu.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl"],notApplicable:["density","ownership","shapes","sizes","states","tones","variants"]},{name:"ContextMenuPortal",group:"navigation",frame:"docs/navigation/context-menu.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl"],notApplicable:["density","ownership","shapes","sizes","states","tones","variants"]},{name:"ContextMenuRadioGroup",group:"navigation",frame:"docs/navigation/context-menu.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","ownership","reducedMotion","responsive","rtl"],notApplicable:["density","shapes","sizes","states","tones","variants"]},{name:"ContextMenuRadioItem",group:"navigation",frame:"docs/navigation/context-menu.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","ownership","reducedMotion","responsive","rtl","states"],notApplicable:["density","shapes","sizes","tones","variants"]},{name:"ContextMenuSeparator",group:"navigation",frame:"docs/navigation/context-menu.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl"],notApplicable:["density","ownership","shapes","sizes","states","tones","variants"]},{name:"ContextMenuShortcut",group:"navigation",frame:"docs/navigation/context-menu.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl"],notApplicable:["density","ownership","shapes","sizes","states","tones","variants"]},{name:"ContextMenuSub",group:"navigation",frame:"docs/navigation/context-menu.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","ownership","reducedMotion","responsive","rtl"],notApplicable:["density","shapes","sizes","states","tones","variants"]},{name:"ContextMenuSubContent",group:"navigation",frame:"docs/navigation/context-menu.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl"],notApplicable:["density","ownership","shapes","sizes","states","tones","variants"]},{name:"ContextMenuSubTrigger",group:"navigation",frame:"docs/navigation/context-menu.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl","states"],notApplicable:["density","ownership","shapes","sizes","tones","variants"]},{name:"ContextMenuTrigger",group:"navigation",frame:"docs/navigation/context-menu.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl","states"],notApplicable:["density","ownership","shapes","sizes","tones","variants"]},{name:"CredentialReveal",group:"data-display",frame:"docs/data-display/credential-reveal.tsx",covered:["sizes","tones"],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl"],notApplicable:["density","ownership","shapes","states","variants"]},{name:"DataState",group:"query",frame:"docs/query/data-state.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl","states"],notApplicable:["density","ownership","shapes","sizes","tones","variants"]},{name:"DataTable",group:"data-display",frame:"docs/data-display/data-table/index.tsx",covered:["density"],untested:["accessibleName","async","contentStress","keyboard","ownership","reducedMotion","responsive","rtl","states"],notApplicable:["shapes","sizes","tones","variants"]},{name:"DatePicker",group:"data-entry",frame:"docs/data-entry/date-picker.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","ownership","reducedMotion","responsive","rtl","states"],notApplicable:["density","shapes","sizes","tones","variants"]},{name:"DateRangePicker",group:"data-entry",frame:"docs/data-entry/date-range-picker.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","ownership","reducedMotion","responsive","rtl","states"],notApplicable:["density","shapes","sizes","tones","variants"]},{name:"Descriptions",group:"data-display",frame:"docs/data-display/descriptions.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl"],notApplicable:["density","ownership","shapes","sizes","states","tones","variants"]},{name:"Dialog",group:"feedback",frame:"docs/feedback/dialog.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","ownership","reducedMotion","responsive","rtl"],notApplicable:["density","shapes","sizes","states","tones","variants"]},{name:"DialogAction",group:"feedback",frame:"docs/feedback/dialog.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","ownership","reducedMotion","responsive","rtl","states"],notApplicable:["density","shapes","sizes","tones","variants"]},{name:"DialogBody",group:"feedback",frame:"docs/feedback/dialog.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl"],notApplicable:["density","ownership","shapes","sizes","states","tones","variants"]},{name:"DialogCancel",group:"feedback",frame:"docs/feedback/dialog.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","ownership","reducedMotion","responsive","rtl","states"],notApplicable:["density","shapes","sizes","tones","variants"]},{name:"DialogClose",group:"feedback",frame:"docs/feedback/dialog.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","ownership","reducedMotion","responsive","rtl","states"],notApplicable:["density","shapes","sizes","tones","variants"]},{name:"DialogContent",group:"feedback",frame:"docs/feedback/dialog.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl"],notApplicable:["density","ownership","shapes","sizes","states","tones","variants"]},{name:"DialogDescription",group:"feedback",frame:"docs/feedback/dialog.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl"],notApplicable:["density","ownership","shapes","sizes","states","tones","variants"]},{name:"DialogFooter",group:"feedback",frame:"docs/feedback/dialog.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl"],notApplicable:["density","ownership","shapes","sizes","states","tones","variants"]},{name:"DialogHeader",group:"feedback",frame:"docs/feedback/dialog.tsx",covered:["tones"],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl"],notApplicable:["density","ownership","shapes","sizes","states","variants"]},{name:"DialogOverlay",group:"feedback",frame:"docs/feedback/dialog.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl"],notApplicable:["density","ownership","shapes","sizes","states","tones","variants"]},{name:"DialogPortal",group:"feedback",frame:"docs/feedback/dialog.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl"],notApplicable:["density","ownership","shapes","sizes","states","tones","variants"]},{name:"DialogTitle",group:"feedback",frame:"docs/feedback/dialog.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl"],notApplicable:["density","ownership","shapes","sizes","states","tones","variants"]},{name:"DialogTrigger",group:"feedback",frame:"docs/feedback/dialog.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","ownership","reducedMotion","responsive","rtl","states"],notApplicable:["density","shapes","sizes","tones","variants"]},{name:"DropdownMenu",group:"navigation",frame:"docs/navigation/dropdown-menu.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","ownership","reducedMotion","responsive","rtl"],notApplicable:["density","shapes","sizes","states","tones","variants"]},{name:"DropdownMenuCheckboxItem",group:"navigation",frame:"docs/navigation/dropdown-menu.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","ownership","reducedMotion","responsive","rtl","states"],notApplicable:["density","shapes","sizes","tones","variants"]},{name:"DropdownMenuContent",group:"navigation",frame:"docs/navigation/dropdown-menu.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl"],notApplicable:["density","ownership","shapes","sizes","states","tones","variants"]},{name:"DropdownMenuGroup",group:"navigation",frame:"docs/navigation/dropdown-menu.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl"],notApplicable:["density","ownership","shapes","sizes","states","tones","variants"]},{name:"DropdownMenuItem",group:"navigation",frame:"docs/navigation/dropdown-menu.tsx",covered:["variants"],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl","states"],notApplicable:["density","ownership","shapes","sizes","tones"]},{name:"DropdownMenuLabel",group:"navigation",frame:"docs/navigation/dropdown-menu.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl"],notApplicable:["density","ownership","shapes","sizes","states","tones","variants"]},{name:"DropdownMenuPortal",group:"navigation",frame:"docs/navigation/dropdown-menu.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl"],notApplicable:["density","ownership","shapes","sizes","states","tones","variants"]},{name:"DropdownMenuRadioGroup",group:"navigation",frame:"docs/navigation/dropdown-menu.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","ownership","reducedMotion","responsive","rtl"],notApplicable:["density","shapes","sizes","states","tones","variants"]},{name:"DropdownMenuRadioItem",group:"navigation",frame:"docs/navigation/dropdown-menu.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","ownership","reducedMotion","responsive","rtl","states"],notApplicable:["density","shapes","sizes","tones","variants"]},{name:"DropdownMenuSeparator",group:"navigation",frame:"docs/navigation/dropdown-menu.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl"],notApplicable:["density","ownership","shapes","sizes","states","tones","variants"]},{name:"DropdownMenuShortcut",group:"navigation",frame:"docs/navigation/dropdown-menu.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl"],notApplicable:["density","ownership","shapes","sizes","states","tones","variants"]},{name:"DropdownMenuSub",group:"navigation",frame:"docs/navigation/dropdown-menu.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","ownership","reducedMotion","responsive","rtl"],notApplicable:["density","shapes","sizes","states","tones","variants"]},{name:"DropdownMenuSubContent",group:"navigation",frame:"docs/navigation/dropdown-menu.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl"],notApplicable:["density","ownership","shapes","sizes","states","tones","variants"]},{name:"DropdownMenuSubTrigger",group:"navigation",frame:"docs/navigation/dropdown-menu.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl","states"],notApplicable:["density","ownership","shapes","sizes","tones","variants"]},{name:"DropdownMenuTrigger",group:"navigation",frame:"docs/navigation/dropdown-menu.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl","states"],notApplicable:["density","ownership","shapes","sizes","tones","variants"]},{name:"EmptyState",group:"data-display",frame:"docs/data-display/empty-state.tsx",covered:["tones","variants"],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl"],notApplicable:["density","ownership","shapes","sizes","states"]},{name:"ErrorSurface",group:"layout",frame:"docs/layout/error-surface/index.tsx",covered:["tones"],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl"],notApplicable:["density","ownership","shapes","sizes","states","variants"]},{name:"Field",group:"data-entry",frame:"docs/data-entry/checkbox.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl"],notApplicable:["density","ownership","shapes","sizes","states","tones","variants"]},{name:"FilterBar",group:"navigation",frame:"docs/navigation/toolbar.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl"],notApplicable:["density","ownership","shapes","sizes","states","tones","variants"]},{name:"FilterBarGroup",group:"navigation",frame:"docs/navigation/toolbar.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl"],notApplicable:["density","ownership","shapes","sizes","states","tones","variants"]},{name:"Flex",group:"layout",frame:"docs/layout/flex.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl"],notApplicable:["density","ownership","shapes","sizes","states","tones","variants"]},{name:"Form",group:"data-entry",frame:"docs/data-entry/form.tsx",covered:["density"],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl"],notApplicable:["ownership","shapes","sizes","states","tones","variants"]},{name:"FormField",group:"data-entry",frame:"docs/data-entry/form-field/index.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl","states"],notApplicable:["density","ownership","shapes","sizes","tones","variants"]},{name:"Heading",group:"general",frame:"docs/general/typography.tsx",covered:["tones"],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl"],notApplicable:["density","ownership","shapes","sizes","states","variants"]},{name:"HoverCard",group:"data-display",frame:"docs/data-display/hover-card.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","ownership","reducedMotion","responsive","rtl"],notApplicable:["density","shapes","sizes","states","tones","variants"]},{name:"HoverCardContent",group:"data-display",frame:"docs/data-display/hover-card.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl"],notApplicable:["density","ownership","shapes","sizes","states","tones","variants"]},{name:"HoverCardTrigger",group:"data-display",frame:"docs/data-display/hover-card.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl"],notApplicable:["density","ownership","shapes","sizes","states","tones","variants"]},{name:"InfiniteQueryState",group:"query",frame:"docs/query/infinite-query-state.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl","states"],notApplicable:["density","ownership","shapes","sizes","tones","variants"]},{name:"Input",group:"data-entry",frame:"docs/data-entry/input.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","ownership","reducedMotion","responsive","rtl","states"],notApplicable:["density","shapes","sizes","tones","variants"]},{name:"InputOTP",group:"data-entry",frame:"docs/data-entry/input-otp.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl"],notApplicable:["density","ownership","shapes","sizes","states","tones","variants"]},{name:"InputOTPGroup",group:"data-entry",frame:"docs/data-entry/input-otp.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl"],notApplicable:["density","ownership","shapes","sizes","states","tones","variants"]},{name:"InputOTPSeparator",group:"data-entry",frame:"docs/data-entry/input-otp.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl"],notApplicable:["density","ownership","shapes","sizes","states","tones","variants"]},{name:"InputOTPSlot",group:"data-entry",frame:"docs/data-entry/input-otp.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl"],notApplicable:["density","ownership","shapes","sizes","states","tones","variants"]},{name:"Label",group:"data-entry",frame:"docs/data-entry/label.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl"],notApplicable:["density","ownership","shapes","sizes","states","tones","variants"]},{name:"LegalDocumentShell",group:"layout",frame:"docs/layout/legal-document-shell/index.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl"],notApplicable:["density","ownership","shapes","sizes","states","tones","variants"]},{name:"LineChart",group:"charts",frame:"docs/data-display/charts.tsx",covered:["sizes"],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl"],notApplicable:["density","ownership","shapes","states","tones","variants"]},{name:"ListRow",group:"data-display",frame:"docs/data-display/list-row.tsx",covered:["density"],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl"],notApplicable:["ownership","shapes","sizes","states","tones","variants"]},{name:"Logo",group:"general",frame:"docs/general/logo.tsx",covered:["sizes","tones"],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl"],notApplicable:["density","ownership","shapes","states","variants"]},{name:"MasterDetail",group:"layout",frame:"docs/layout/master-detail.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl"],notApplicable:["density","ownership","shapes","sizes","states","tones","variants"]},{name:"Menubar",group:"navigation",frame:"docs/navigation/menubar.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","ownership","reducedMotion","responsive","rtl"],notApplicable:["density","shapes","sizes","states","tones","variants"]},{name:"MenubarCheckboxItem",group:"navigation",frame:"docs/navigation/menubar.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","ownership","reducedMotion","responsive","rtl","states"],notApplicable:["density","shapes","sizes","tones","variants"]},{name:"MenubarContent",group:"navigation",frame:"docs/navigation/menubar.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl"],notApplicable:["density","ownership","shapes","sizes","states","tones","variants"]},{name:"MenubarItem",group:"navigation",frame:"docs/navigation/menubar.tsx",covered:["variants"],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl","states"],notApplicable:["density","ownership","shapes","sizes","tones"]},{name:"MenubarLabel",group:"navigation",frame:"docs/navigation/menubar.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl"],notApplicable:["density","ownership","shapes","sizes","states","tones","variants"]},{name:"MenubarMenu",group:"navigation",frame:"docs/navigation/menubar.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","ownership","reducedMotion","responsive","rtl"],notApplicable:["density","shapes","sizes","states","tones","variants"]},{name:"MenubarRadioGroup",group:"navigation",frame:"docs/navigation/menubar.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","ownership","reducedMotion","responsive","rtl"],notApplicable:["density","shapes","sizes","states","tones","variants"]},{name:"MenubarRadioItem",group:"navigation",frame:"docs/navigation/menubar.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","ownership","reducedMotion","responsive","rtl","states"],notApplicable:["density","shapes","sizes","tones","variants"]},{name:"MenubarSeparator",group:"navigation",frame:"docs/navigation/menubar.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl"],notApplicable:["density","ownership","shapes","sizes","states","tones","variants"]},{name:"MenubarShortcut",group:"navigation",frame:"docs/navigation/menubar.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl"],notApplicable:["density","ownership","shapes","sizes","states","tones","variants"]},{name:"MenubarSub",group:"navigation",frame:"docs/navigation/menubar.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","ownership","reducedMotion","responsive","rtl"],notApplicable:["density","shapes","sizes","states","tones","variants"]},{name:"MenubarSubContent",group:"navigation",frame:"docs/navigation/menubar.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl"],notApplicable:["density","ownership","shapes","sizes","states","tones","variants"]},{name:"MenubarSubTrigger",group:"navigation",frame:"docs/navigation/menubar.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl","states"],notApplicable:["density","ownership","shapes","sizes","tones","variants"]},{name:"MenubarTrigger",group:"navigation",frame:"docs/navigation/menubar.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl"],notApplicable:["density","ownership","shapes","sizes","states","tones","variants"]},{name:"MonthPicker",group:"data-entry",frame:"docs/data-entry/month-picker.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","ownership","reducedMotion","responsive","rtl","states"],notApplicable:["density","shapes","sizes","tones","variants"]},{name:"MonthRangePicker",group:"data-entry",frame:"docs/data-entry/month-range-picker.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","ownership","reducedMotion","responsive","rtl","states"],notApplicable:["density","shapes","sizes","tones","variants"]},{name:"NavigationMenu",group:"navigation",frame:"docs/navigation/navigation-menu.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","ownership","reducedMotion","responsive","rtl"],notApplicable:["density","shapes","sizes","states","tones","variants"]},{name:"NavigationMenuContent",group:"navigation",frame:"docs/navigation/navigation-menu.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl"],notApplicable:["density","ownership","shapes","sizes","states","tones","variants"]},{name:"NavigationMenuIndicator",group:"navigation",frame:"docs/navigation/navigation-menu.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl"],notApplicable:["density","ownership","shapes","sizes","states","tones","variants"]},{name:"NavigationMenuItem",group:"navigation",frame:"docs/navigation/navigation-menu.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","ownership","reducedMotion","responsive","rtl"],notApplicable:["density","shapes","sizes","states","tones","variants"]},{name:"NavigationMenuLink",group:"navigation",frame:"docs/navigation/navigation-menu.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl"],notApplicable:["density","ownership","shapes","sizes","states","tones","variants"]},{name:"NavigationMenuList",group:"navigation",frame:"docs/navigation/navigation-menu.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl"],notApplicable:["density","ownership","shapes","sizes","states","tones","variants"]},{name:"NavigationMenuTrigger",group:"navigation",frame:"docs/navigation/navigation-menu.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl"],notApplicable:["density","ownership","shapes","sizes","states","tones","variants"]},{name:"NavigationMenuViewport",group:"navigation",frame:"docs/navigation/navigation-menu.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl"],notApplicable:["density","ownership","shapes","sizes","states","tones","variants"]},{name:"NumberInput",group:"data-entry",frame:"docs/data-entry/number-input.tsx",covered:["sizes"],untested:["accessibleName","async","contentStress","keyboard","ownership","reducedMotion","responsive","rtl","states"],notApplicable:["density","shapes","tones","variants"]},{name:"OrgSwitcher",group:"layout",frame:"docs/layout/org-switcher.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","ownership","reducedMotion","responsive","rtl","states"],notApplicable:["density","shapes","sizes","tones","variants"]},{name:"PageContainer",group:"layout",frame:"docs/layout/page-container.tsx",covered:["density","variants"],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl"],notApplicable:["ownership","shapes","sizes","states","tones"]},{name:"Pagination",group:"navigation",frame:"docs/navigation/pagination.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","ownership","reducedMotion","responsive","rtl","states"],notApplicable:["density","shapes","sizes","tones","variants"]},{name:"PaginationContent",group:"navigation",frame:"docs/navigation/pagination.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl"],notApplicable:["density","ownership","shapes","sizes","states","tones","variants"]},{name:"PaginationEllipsis",group:"navigation",frame:"docs/navigation/pagination.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl"],notApplicable:["density","ownership","shapes","sizes","states","tones","variants"]},{name:"PaginationItem",group:"navigation",frame:"docs/navigation/pagination.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","ownership","reducedMotion","responsive","rtl"],notApplicable:["density","shapes","sizes","states","tones","variants"]},{name:"PaginationLink",group:"navigation",frame:"docs/navigation/pagination.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","ownership","reducedMotion","responsive","rtl","states"],notApplicable:["density","shapes","sizes","tones","variants"]},{name:"PaginationNext",group:"navigation",frame:"docs/navigation/pagination.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","ownership","reducedMotion","responsive","rtl","states"],notApplicable:["density","shapes","sizes","tones","variants"]},{name:"PaginationPrevious",group:"navigation",frame:"docs/navigation/pagination.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","ownership","reducedMotion","responsive","rtl","states"],notApplicable:["density","shapes","sizes","tones","variants"]},{name:"PasswordInput",group:"data-entry",frame:"docs/data-entry/password-input.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl"],notApplicable:["density","ownership","shapes","sizes","states","tones","variants"]},{name:"PasswordStrength",group:"data-entry",frame:"docs/data-entry/password-strength.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","ownership","reducedMotion","responsive","rtl"],notApplicable:["density","shapes","sizes","states","tones","variants"]},{name:"PieChart",group:"charts",frame:"docs/data-display/charts.tsx",covered:["sizes"],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl"],notApplicable:["density","ownership","shapes","states","tones","variants"]},{name:"Popover",group:"data-display",frame:"docs/data-display/popover.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","ownership","reducedMotion","responsive","rtl"],notApplicable:["density","shapes","sizes","states","tones","variants"]},{name:"PopoverAnchor",group:"data-display",frame:"docs/data-display/popover.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl"],notApplicable:["density","ownership","shapes","sizes","states","tones","variants"]},{name:"PopoverContent",group:"data-display",frame:"docs/data-display/popover.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl"],notApplicable:["density","ownership","shapes","sizes","states","tones","variants"]},{name:"PopoverDescription",group:"data-display",frame:"docs/data-display/popover.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl"],notApplicable:["density","ownership","shapes","sizes","states","tones","variants"]},{name:"PopoverHeader",group:"data-display",frame:"docs/data-display/popover.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl"],notApplicable:["density","ownership","shapes","sizes","states","tones","variants"]},{name:"PopoverTitle",group:"data-display",frame:"docs/data-display/popover.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl"],notApplicable:["density","ownership","shapes","sizes","states","tones","variants"]},{name:"PopoverTrigger",group:"data-display",frame:"docs/data-display/popover.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl"],notApplicable:["density","ownership","shapes","sizes","states","tones","variants"]},{name:"PrefetchLink",group:"query",frame:"docs/query/prefetch-link.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl"],notApplicable:["density","ownership","shapes","sizes","states","tones","variants"]},{name:"Progress",group:"data-display",frame:"docs/data-display/progress.tsx",covered:["tones"],untested:["accessibleName","async","contentStress","keyboard","ownership","reducedMotion","responsive","rtl"],notApplicable:["density","shapes","sizes","states","variants"]},{name:"QrCode",group:"data-display",frame:"docs/data-display/qr-code.tsx",covered:["sizes"],untested:["accessibleName","async","contentStress","keyboard","ownership","reducedMotion","responsive","rtl"],notApplicable:["density","shapes","states","tones","variants"]},{name:"Radio",group:"data-entry",frame:"docs/data-entry/radio-group.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","ownership","reducedMotion","responsive","rtl","states"],notApplicable:["density","shapes","sizes","tones","variants"]},{name:"RadioGroup",group:"data-entry",frame:"docs/data-entry/radio-group.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","ownership","reducedMotion","responsive","rtl","states"],notApplicable:["density","shapes","sizes","tones","variants"]},{name:"RadioGroupRoot",group:"data-entry",frame:"docs/data-entry/radio-group.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","ownership","reducedMotion","responsive","rtl","states"],notApplicable:["density","shapes","sizes","tones","variants"]},{name:"RadioItem",group:"data-entry",frame:"docs/data-entry/radio-group.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","ownership","reducedMotion","responsive","rtl","states"],notApplicable:["density","shapes","sizes","tones","variants"]},{name:"Rating",group:"data-entry",frame:"docs/data-entry/rating.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","ownership","reducedMotion","responsive","rtl","states"],notApplicable:["density","shapes","sizes","tones","variants"]},{name:"ResizableHandle",group:"layout",frame:"docs/layout/resizable-panel.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl","states"],notApplicable:["density","ownership","shapes","sizes","tones","variants"]},{name:"ResizablePanel",group:"layout",frame:"docs/layout/resizable-panel.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl","states"],notApplicable:["density","ownership","shapes","sizes","tones","variants"]},{name:"ResizablePanelGroup",group:"layout",frame:"docs/layout/resizable-panel.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl","states"],notApplicable:["density","ownership","shapes","sizes","tones","variants"]},{name:"ResponsiveGrid",group:"layout",frame:"docs/layout/responsive-grid.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl"],notApplicable:["density","ownership","shapes","sizes","states","tones","variants"]},{name:"Reveal",group:"general",frame:"docs/general/reveal.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl"],notApplicable:["density","ownership","shapes","sizes","states","tones","variants"]},{name:"ScrollArea",group:"data-display",frame:"docs/data-display/scroll-area.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl"],notApplicable:["density","ownership","shapes","sizes","states","tones","variants"]},{name:"ScrollBar",group:"data-display",frame:"docs/data-display/scroll-area.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl"],notApplicable:["density","ownership","shapes","sizes","states","tones","variants"]},{name:"SearchInput",group:"data-entry",frame:"docs/data-entry/search-input.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","ownership","reducedMotion","responsive","rtl","states"],notApplicable:["density","shapes","sizes","tones","variants"]},{name:"Select",group:"data-entry",frame:"docs/data-entry/select.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","ownership","reducedMotion","responsive","rtl","states"],notApplicable:["density","shapes","sizes","tones","variants"]},{name:"SelectContent",group:"data-entry",frame:"docs/data-entry/select.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","ownership","reducedMotion","responsive","rtl"],notApplicable:["density","shapes","sizes","states","tones","variants"]},{name:"SelectGroup",group:"data-entry",frame:"docs/data-entry/select.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","ownership","reducedMotion","responsive","rtl"],notApplicable:["density","shapes","sizes","states","tones","variants"]},{name:"SelectItem",group:"data-entry",frame:"docs/data-entry/select.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","ownership","reducedMotion","responsive","rtl","states"],notApplicable:["density","shapes","sizes","tones","variants"]},{name:"SelectLabel",group:"data-entry",frame:"docs/data-entry/select.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","ownership","reducedMotion","responsive","rtl"],notApplicable:["density","shapes","sizes","states","tones","variants"]},{name:"SelectScrollDownButton",group:"data-entry",frame:"docs/data-entry/select.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","ownership","reducedMotion","responsive","rtl"],notApplicable:["density","shapes","sizes","states","tones","variants"]},{name:"SelectScrollUpButton",group:"data-entry",frame:"docs/data-entry/select.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","ownership","reducedMotion","responsive","rtl"],notApplicable:["density","shapes","sizes","states","tones","variants"]},{name:"SelectSeparator",group:"data-entry",frame:"docs/data-entry/select.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","ownership","reducedMotion","responsive","rtl"],notApplicable:["density","shapes","sizes","states","tones","variants"]},{name:"SelectTrigger",group:"data-entry",frame:"docs/data-entry/select.tsx",covered:["sizes"],untested:["accessibleName","async","contentStress","keyboard","ownership","reducedMotion","responsive","rtl","states"],notApplicable:["density","shapes","tones","variants"]},{name:"SelectValue",group:"data-entry",frame:"docs/data-entry/select.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","ownership","reducedMotion","responsive","rtl"],notApplicable:["density","shapes","sizes","states","tones","variants"]},{name:"Separator",group:"layout",frame:"docs/layout/separator.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl"],notApplicable:["density","ownership","shapes","sizes","states","tones","variants"]},{name:"ServiceCatalogCta",group:"data-display",frame:"docs/data-display/service-launcher-card.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl"],notApplicable:["density","ownership","shapes","sizes","states","tones","variants"]},{name:"ServiceLauncherCard",group:"data-display",frame:"docs/data-display/service-launcher-card.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl"],notApplicable:["density","ownership","shapes","sizes","states","tones","variants"]},{name:"ServiceLauncherCardSkeleton",group:"data-display",frame:"docs/data-display/service-launcher-card.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl"],notApplicable:["density","ownership","shapes","sizes","states","tones","variants"]},{name:"Sheet",group:"feedback",frame:"docs/feedback/sheet.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","ownership","reducedMotion","responsive","rtl"],notApplicable:["density","shapes","sizes","states","tones","variants"]},{name:"SheetBody",group:"feedback",frame:"docs/feedback/sheet.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl"],notApplicable:["density","ownership","shapes","sizes","states","tones","variants"]},{name:"SheetClose",group:"feedback",frame:"docs/feedback/sheet.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","ownership","reducedMotion","responsive","rtl","states"],notApplicable:["density","shapes","sizes","tones","variants"]},{name:"SheetContent",group:"feedback",frame:"docs/feedback/sheet.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl"],notApplicable:["density","ownership","shapes","sizes","states","tones","variants"]},{name:"SheetDescription",group:"feedback",frame:"docs/feedback/sheet.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl"],notApplicable:["density","ownership","shapes","sizes","states","tones","variants"]},{name:"SheetFooter",group:"feedback",frame:"docs/feedback/sheet.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl"],notApplicable:["density","ownership","shapes","sizes","states","tones","variants"]},{name:"SheetHeader",group:"feedback",frame:"docs/feedback/sheet.tsx",covered:["tones"],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl"],notApplicable:["density","ownership","shapes","sizes","states","variants"]},{name:"SheetOverlay",group:"feedback",frame:"docs/feedback/sheet.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl"],notApplicable:["density","ownership","shapes","sizes","states","tones","variants"]},{name:"SheetPortal",group:"feedback",frame:"docs/feedback/sheet.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl"],notApplicable:["density","ownership","shapes","sizes","states","tones","variants"]},{name:"SheetTitle",group:"feedback",frame:"docs/feedback/sheet.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl"],notApplicable:["density","ownership","shapes","sizes","states","tones","variants"]},{name:"SheetTrigger",group:"feedback",frame:"docs/feedback/sheet.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","ownership","reducedMotion","responsive","rtl","states"],notApplicable:["density","shapes","sizes","tones","variants"]},{name:"Sidebar",group:"layout",frame:"docs/layout/sidebar.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl"],notApplicable:["density","ownership","shapes","sizes","states","tones","variants"]},{name:"SidebarHeader",group:"layout",frame:"docs/layout/sidebar.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl"],notApplicable:["density","ownership","shapes","sizes","states","tones","variants"]},{name:"SidebarItem",group:"layout",frame:"docs/layout/sidebar.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl","states"],notApplicable:["density","ownership","shapes","sizes","tones","variants"]},{name:"SidebarSection",group:"layout",frame:"docs/layout/sidebar.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl"],notApplicable:["density","ownership","shapes","sizes","states","tones","variants"]},{name:"Skeleton",group:"feedback",frame:"docs/feedback/skeleton.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl"],notApplicable:["density","ownership","shapes","sizes","states","tones","variants"]},{name:"SkeletonDetail",group:"feedback",frame:"docs/feedback/skeleton.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl"],notApplicable:["density","ownership","shapes","sizes","states","tones","variants"]},{name:"SkeletonRows",group:"feedback",frame:"docs/feedback/skeleton.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl"],notApplicable:["density","ownership","shapes","sizes","states","tones","variants"]},{name:"SkeletonStat",group:"feedback",frame:"docs/feedback/skeleton.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl"],notApplicable:["density","ownership","shapes","sizes","states","tones","variants"]},{name:"SkeletonTable",group:"feedback",frame:"docs/feedback/skeleton.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl"],notApplicable:["density","ownership","shapes","sizes","states","tones","variants"]},{name:"Slider",group:"data-entry",frame:"docs/data-entry/slider.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","ownership","reducedMotion","responsive","rtl","states"],notApplicable:["density","shapes","sizes","tones","variants"]},{name:"SplitPane",group:"layout",frame:"docs/layout/split-pane.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl"],notApplicable:["density","ownership","shapes","sizes","states","tones","variants"]},{name:"StatCard",group:"data-display",frame:"docs/data-display/card/index.tsx",covered:["sizes"],untested:["accessibleName","async","contentStress","keyboard","ownership","reducedMotion","responsive","rtl"],notApplicable:["density","shapes","states","tones","variants"]},{name:"StatusBadge",group:"data-display",frame:"docs/data-display/badge.tsx",covered:["shapes","tones","variants"],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl"],notApplicable:["density","ownership","sizes","states"]},{name:"Steps",group:"navigation",frame:"docs/navigation/steps.tsx",covered:["sizes"],untested:["accessibleName","async","contentStress","keyboard","ownership","reducedMotion","responsive","rtl"],notApplicable:["density","shapes","states","tones","variants"]},{name:"Switch",group:"data-entry",frame:"docs/data-entry/switch.tsx",covered:["sizes"],untested:["accessibleName","async","contentStress","keyboard","ownership","reducedMotion","responsive","rtl","states"],notApplicable:["density","shapes","tones","variants"]},{name:"Table",group:"data-display",frame:"docs/data-display/table.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl"],notApplicable:["density","ownership","shapes","sizes","states","tones","variants"]},{name:"TableBody",group:"data-display",frame:"docs/data-display/table.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl"],notApplicable:["density","ownership","shapes","sizes","states","tones","variants"]},{name:"TableCell",group:"data-display",frame:"docs/data-display/table.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl"],notApplicable:["density","ownership","shapes","sizes","states","tones","variants"]},{name:"TableHead",group:"data-display",frame:"docs/data-display/table.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl"],notApplicable:["density","ownership","shapes","sizes","states","tones","variants"]},{name:"TableHeader",group:"data-display",frame:"docs/data-display/table.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl"],notApplicable:["density","ownership","shapes","sizes","states","tones","variants"]},{name:"TableRow",group:"data-display",frame:"docs/data-display/table.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl"],notApplicable:["density","ownership","shapes","sizes","states","tones","variants"]},{name:"Tabs",group:"navigation",frame:"docs/navigation/tabs.tsx",covered:["variants"],untested:["accessibleName","async","contentStress","keyboard","ownership","reducedMotion","responsive","rtl"],notApplicable:["density","shapes","sizes","states","tones"]},{name:"TabsContent",group:"navigation",frame:"docs/navigation/tabs.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","ownership","reducedMotion","responsive","rtl"],notApplicable:["density","shapes","sizes","states","tones","variants"]},{name:"TabsList",group:"navigation",frame:"docs/navigation/tabs.tsx",covered:["variants"],untested:["accessibleName","async","contentStress","keyboard","ownership","reducedMotion","responsive","rtl"],notApplicable:["density","shapes","sizes","states","tones"]},{name:"TabsTrigger",group:"navigation",frame:"docs/navigation/tabs.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","ownership","reducedMotion","responsive","rtl","states"],notApplicable:["density","shapes","sizes","tones","variants"]},{name:"TagInput",group:"data-entry",frame:"docs/data-entry/tag-input.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","ownership","reducedMotion","responsive","rtl","states"],notApplicable:["density","shapes","sizes","tones","variants"]},{name:"Text",group:"general",frame:"docs/general/typography.tsx",covered:["sizes","tones"],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl"],notApplicable:["density","ownership","shapes","states","variants"]},{name:"Textarea",group:"data-entry",frame:"docs/data-entry/textarea.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","ownership","reducedMotion","responsive","rtl","states"],notApplicable:["density","shapes","sizes","tones","variants"]},{name:"Timeline",group:"data-display",frame:"docs/data-display/timeline.tsx",covered:["variants"],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl"],notApplicable:["density","ownership","shapes","sizes","states","tones"]},{name:"TimePicker",group:"data-entry",frame:"docs/data-entry/time-picker.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","ownership","reducedMotion","responsive","rtl","states"],notApplicable:["density","shapes","sizes","tones","variants"]},{name:"Toaster",group:"feedback",frame:"docs/feedback/toast.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl"],notApplicable:["density","ownership","shapes","sizes","states","tones","variants"]},{name:"Toggle",group:"data-entry",frame:"docs/data-entry/toggle.tsx",covered:["sizes","variants"],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl"],notApplicable:["density","ownership","shapes","states","tones"]},{name:"ToggleGroup",group:"data-entry",frame:"docs/data-entry/toggle-group.tsx",covered:["sizes","variants"],untested:["accessibleName","async","contentStress","keyboard","ownership","reducedMotion","responsive","rtl","states"],notApplicable:["density","shapes","tones"]},{name:"ToggleGroupItem",group:"data-entry",frame:"docs/data-entry/toggle-group.tsx",covered:["sizes","variants"],untested:["accessibleName","async","contentStress","keyboard","ownership","reducedMotion","responsive","rtl"],notApplicable:["density","shapes","states","tones"]},{name:"Toolbar",group:"navigation",frame:"docs/navigation/toolbar.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl"],notApplicable:["density","ownership","shapes","sizes","states","tones","variants"]},{name:"ToolbarGroup",group:"navigation",frame:"docs/navigation/toolbar.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl"],notApplicable:["density","ownership","shapes","sizes","states","tones","variants"]},{name:"Tooltip",group:"feedback",frame:"docs/feedback/tooltip.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","ownership","reducedMotion","responsive","rtl"],notApplicable:["density","shapes","sizes","states","tones","variants"]},{name:"TooltipContent",group:"feedback",frame:"docs/feedback/tooltip.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl"],notApplicable:["density","ownership","shapes","sizes","states","tones","variants"]},{name:"TooltipProvider",group:"feedback",frame:"docs/feedback/tooltip.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl"],notApplicable:["density","ownership","shapes","sizes","states","tones","variants"]},{name:"TooltipTrigger",group:"feedback",frame:"docs/feedback/tooltip.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl"],notApplicable:["density","ownership","shapes","sizes","states","tones","variants"]},{name:"Topbar",group:"layout",frame:"docs/layout/topbar.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl"],notApplicable:["density","ownership","shapes","sizes","states","tones","variants"]},{name:"Transfer",group:"data-entry",frame:"docs/data-entry/transfer.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","ownership","reducedMotion","responsive","rtl","states"],notApplicable:["density","shapes","sizes","tones","variants"]},{name:"TreeList",group:"data-display",frame:"docs/data-display/tree-list.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl"],notApplicable:["density","ownership","shapes","sizes","states","tones","variants"]},{name:"TreeSelect",group:"data-entry",frame:"docs/data-entry/tree-select.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","ownership","reducedMotion","responsive","rtl","states"],notApplicable:["density","shapes","sizes","tones","variants"]},{name:"TwoFactorSetup",group:"feedback",frame:"docs/feedback/two-factor-setup.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","ownership","reducedMotion","responsive","rtl","states"],notApplicable:["density","shapes","sizes","tones","variants"]},{name:"Upload",group:"data-entry",frame:"docs/data-entry/upload.tsx",covered:["variants"],untested:["accessibleName","async","contentStress","keyboard","ownership","reducedMotion","responsive","rtl","states"],notApplicable:["density","shapes","sizes","tones"]}],le=new Map(z.map(t=>[t.name.toLowerCase(),t]));function L(t){return le.get(String(t??"").trim().toLowerCase())}var u=[{number:1,title:"Storybook is mandatory",body:"Every primitive / shell / composite has a paired story under `src/stories/<group>/<Name>.stories.tsx` covering every variant + state on light + dark."},{number:2,title:"Tokens, not utilities",body:"Visual values come from CSS custom properties in `src/tokens/` + `src/styles/theme.css`. Token-named Tailwind utilities (`bg-background`) are fine; raw value utilities (`bg-blue-500`) are forbidden. (ADR-0003)"},{number:3,title:"Radix for interactive primitives",body:"Anything with keyboard / ARIA / portal wraps the relevant Radix primitive. (ADR-0001)"},{number:4,title:"shadcn-style ownership",body:"Primitives are thin wrappers; consumers can fork the source in place. (ADR-0002)"},{number:5,title:"One i18next singleton",body:"`initI18n()` in `src/i18n/index.ts` is THE instance; consumers extend via `addResourceBundle`. (ADR-0004)"},{number:6,title:"WCAG 2.1 AA baseline",body:"Every interactive primitive passes axe-core (keyboard nav, ARIA, focus-visible, 4.5:1 contrast, `prefers-reduced-motion`). Stories double as a11y test surfaces."},{number:7,title:"SemVer 2.0 + Keep a Changelog 1.1",body:"Every release-worthy change updates `CHANGELOG.md` under `## Unreleased` in the same PR."},{number:8,title:"Inclusive naming",body:"`allowlist` / `denylist`, `main` / `primary` / `replica` / `secondary`, `they/them`. Never `whitelist` / `blacklist` / `master` / `slave`. Lint-enforced."},{number:9,title:"No marketing speak",body:'Banned: "powerful", "robust", "blazing fast", "best-in-class", "seamless", "enterprise-grade". State what it does.'},{number:10,title:"English is canonical for docs",body:"Localised docs at `docs/i18n/<bcp47>/`; front-matter tracks staleness."},{number:11,title:"Submodule discipline",body:"Two-PR workflow: (1) submodule PR \u2192 `main`, (2) downstream PR \u2192 bump pin. Never push a pin to a SHA not on the submodule remote."},{number:12,title:"Branch + PR workflow",body:"`feat/<scope>` / `fix/<scope>` \u2192 submodule `main`. CI green + squash-merge. No direct push to `main`. `--no-verify` forbidden."},{number:13,title:"TypeScript strict",body:"Explicit types on every export. `forwardRef` for components; `ComponentPropsWithoutRef` for extension. No `any`. No `@ts-ignore` without comment + issue link."},{number:14,title:"Every third-party library is shadcn / Radix-recommended",body:"Locked stack: Radix UI, cmdk, sonner, lucide-react, react-aria-components + `@internationalized/date`, i18next + react-i18next, class-variance-authority + clsx + tailwind-merge. New peer \u2192 ADR documenting why it's the canonical choice."},{number:15,title:"No `@apply` re-encoding tokens",body:"Inside a primitive `.tsx`, don't `@apply` a Tailwind utility that re-encodes a token \u2014 reference the canonical CSS class from `tokens.css` instead. Composite token-named utilities remain fine."},{number:16,title:"CSS source-of-truth is `src/tokens/` + `src/styles/theme.css`",body:"A primitive that needs a new color / spacing / radius adds it there FIRST, then references it."},{number:17,title:"`src/stories/` \u2194 `src/components/` parity",body:"Story set matches primitive set under each group. CI-checked via `scripts/check-stories-parity.mjs`."},{number:18,title:"`docs/reference/<group>/` \u2194 `src/components/<group>/` parity",body:"Every primitive has a reference page; every page maps to a primitive. CI-checked via `scripts/check-docs-parity.mjs`."},{number:19,title:"No service-specific anything",body:'`me-service`, `forge-service`, `admin-service` never appear in source / comments / prop names. Per-deployment brand color lives at `[data-accent="<palette>"]`.'},{number:20,title:'No "platform-only" exports',body:"Every primitive ships via `package.json::exports`. Internal-only helpers stay un-exported."},{number:21,title:"Every component honours every theme axis",body:"`data-theme` (light / dark), `data-accent` (6 palettes), `data-density` (compact / default / comfortable), `data-font-size` (sm / base / lg / xl). Read from tokens, never hardcode values. Verify every PR via the Storybook toolbar sweep."},{number:22,title:"100% match to the design canon",body:'Every visual literal comes from `design-handoff/ui-system/<latest-bundle>/`. Token-pin canon literals; never substitute "close enough". If the bundle doesn\'t cover a case \u2014 STOP, ask the user to mock it.'},{number:23,title:"Concept-first prop API",body:"One concept per prop. Reuse shared vocabulary (`size`, `variant`, `color`, `tone`, `accent`, `padding`, `density`, `orientation`, `placement`, `current`, `value` / `defaultValue` / `onValueChange`, `open` / `defaultOpen` / `onOpenChange`, `justify`, `sticky`, `offset`). Before adding a new prop or token: grep for an existing one."},{number:24,title:"Mobile-first",body:"Defaults target `xs` (\u22650px); progressive enhancement via `sm:` / `md:` / `lg:` / `xl:` / `2xl:`. Touch targets \u2265 44 \xD7 44 px (`--touch-target-min`, does NOT scale with density). Runtime viewport via `useBreakpoint`, never `window.innerWidth`. Stories render at narrow viewport first."},{number:25,title:"Stories are docs; UI is the primitive",body:"When a story looks wrong, fix the primitive / CSS / token. Never paper over with a story tweak. Story-only diff without a paired primitive / CSS / token diff is rejected."},{number:26,title:"Library isolation",body:"`dist/` ships only the consumer surface. Storybook, tests, scripts, design-handoff, `dev-probe/` stay out of npm. Every `dependencies` entry is `external` in `tsup`. Verification via `pnpm pack` + grep of `dist/`."},{number:27,title:"Per-group folder structure",body:"Primitives at `src/components/<group>/<Name>.tsx`; six canonical groups (general, layout, data-display, data-entry, feedback, navigation). Barrel = `src/components/primitives.ts` (single file). Stories + reference docs mirror the same group hierarchy."},{number:28,title:"`src/` folder taxonomy",body:"Three classes: consumer surface (matched by `tsup` entry + `package.json::exports`), Storybook-only (`src/stories/`), build-input-only (`cn.ts`, per-group sources consumed via the barrel). No `src/lib/`, `src/utils/`, `src/internal/`, `src/clients/`, `src/screens/`. Service clients live with the composite that uses them."},{number:29,title:"Stories consume framework primitives only",body:"No raw `<button>` / `<input>` / hand-rolled chips when a primitive exists. HTML semantics (`<section>`, `<article>`, \u2026) for structure are fine. Inline `style={{}}` limited to layout / positioning; no colour / radius / typography overrides."},{number:30,title:"Story `render` returns JSX directly",body:"No opaque `<XyzDemo />` wrapper components, no zero-arg `Demo` helpers. Use `render: function StoryName() { \u2026 }` so Storybook's source panel shows runnable JSX, not `<XyzDemo />`."},{number:31,title:"No nested wrapper / convenience primitives",body:"One Radix base = one framework primitive. `<SimpleX>` over `<X>` is forbidden; add a prop to `<X>` instead. Composites under `src/components/composites/` that combine multiple primitives are NOT wrappers."},{number:32,title:"No redundant props",body:"Before adding a prop / item field / variant, grep the existing surface; if a field already covers the concept, use it. Top-level prop that re-expresses an item field (Timeline `pending` \u2194 `items[i].animate`) is rejected."},{number:33,title:"Stories / source / docs name-synchronized",body:"No two names for the same export across the framework surface; no legacy aliases in stories / docs (source may keep an alias for a deprecation cycle, but the marketing surfaces use the canonical name only). Rename PR runs `grep -rn '<oldName>' src docs` and clears it."},{number:34,title:"Storybook source panel = real, copy-paste-ready code",body:'Storybook\'s react-docgen serializer strips every function value (`cell: ({row}) => <JSX/>`, `render: ({field}) => <Input/>`, `rowClassName`, `renderItem`, \u2026) to `() => {}`. Any story whose `render` passes a function-valued prop, references a module-level helper (`Badge`, `EMPLOYEE_COLUMNS`, etc.), or uses a render-prop pattern MUST override `parameters.docs.source.code` with the literal copy-paste-ready snippet \u2014 type aliases, helper functions spelled out, column definitions with cell JSX visible, inline data array. The `render()` callback stays as-is (module-level constants are fine for runtime performance); `source.code` is the marketing surface. Skip ONLY for stories whose JSX is purely static primitives Storybook can serialize verbatim (`<Button variant="primary">Click</Button>`). The exemplar is `Table.Default` in `src/stories/data-display/Table.stories.tsx`.'},{number:35,title:"Status chips never wrap",body:"A `Badge` / `Badge` reads as one atomic unit. Its label must never break across lines \u2014 pin `white-space: nowrap` on the chip (done in `badge-layout.css`), especially inside narrow `DataTable` cells (\u30B9\u30B3\u30FC\u30D7 / \u30B9\u30C6\u30FC\u30BF\u30B9 columns). If a cell is too tight, widen the column or shorten the label; never let the chip wrap."},{number:36,title:"Badge tone/icon are the colour escape hatch",body:"`Badge` auto-maps a fixed set of English lifecycle keys (active, draft, pending, scheduled, cancelled, failed, \u2026) to tone + icon. For ANY other value \u2014 localized labels (\u516C\u958B\u4E2D, \u30A2\u30AF\u30C6\u30A3\u30D6) or categorical tiers (\u4F1A\u54E1\u30E9\u30F3\u30AF, \u5951\u7D04\u30D7\u30E9\u30F3) \u2014 pass `tone` explicitly (success | warning | destructive | info | neutral) and, for non-lifecycle tiers, `icon={null}` to drop the misleading glyph. Don't let domain labels fall back to neutral grey + \u25CB. Map domain\u2192tone in the CONSUMER layer; the framework only provides the props."},{number:37,title:"DataTable is full-width \u2014 never inside a narrow grid column",body:"A multi-column `DataTable` occupies its OWN row at the page's full width: `<Card><CardContent flush><DataTable \u2026/></CardContent></Card>`. Never nest it in a `lg:col-span-2` of a `ResponsiveGrid columns={3}` beside a chart \u2014 the columns get squeezed until CJK text collapses to one character per line. Charts / KPI cards go in their own row ABOVE the table. (See the `inertia-list-page` pattern.)"},{number:38,title:"FilterBar stays OUT of CardContent flush",body:"`CardContent flush` strips horizontal padding for edge-to-edge tables. A `FilterBar` placed inside it loses all padding and sticks to the card edge. Render `FilterBar` as a STANDALONE block above the table card; wrap ONLY the `DataTable` / `EmptyState` in the `Card` + `CardContent flush`. Order on a list page: KPIs \u2192 FilterBar \u2192 table card."},{number:39,title:"Long text columns get an explicit width",body:"For columns whose value can be long (name / title / segment / address), set `col.width` to a Tailwind width class (e.g. `w-64`, `w-48`) so the column reserves space instead of shrinking and wrapping to many lines; leave numeric / status columns auto. Table cells default to `white-space: nowrap`, so an over-tight table scrolls horizontally rather than crushing \u2014 give the important columns real widths so the default layout reads well before any scroll."},{number:40,title:"Pages are mobile-first",body:'Author and verify every page at 320\u2013390px FIRST. Spacing comes only from `Flex` `gap` (vertical rhythm = `Flex direction="col"`, control rows = the default `direction="row"`) + `ResponsiveGrid columns={2|3|4}` (which collapse to a single column on narrow screens) \u2014 never raw `p-*` / `gap-*` / `space-*` utilities for page layout. Wide tables scroll horizontally on small screens (don\'t force-fit them); dialogs and sheets are full-height on mobile. Touch targets \u2265 44\xD744px.'},{number:41,title:"Drawer & dialog footer layout",body:'Sheet/Dialog/AlertDialog footers are a pinned action bar (Ant Design Drawer footer): the footer sticks to the bottom, SheetFooter draws a full-bleed top border, and actions are RIGHT-aligned with the PRIMARY button rightmost (Cancel/secondary to its left). A destructive / clear / reset action goes far-LEFT \u2014 give that button `className="mr-auto"`. NEVER stack footer buttons full-width or center them.'},{number:42,title:"Props & Tokens Before Customization",body:"Before reaching for a Tailwind class, inline `style`, or extra CSS, you MUST first check whether the component already supports the need via a PROP, a design TOKEN, or a layout/typography PRIMITIVE. godx-ui is meant to be enough on its own (Ant-Design-style): `className` is for genuine one-offs only \u2014 never to redo what an API already does. Specifically: (1) NEVER hand-roll typography \u2014 no `text-[13px]`/`text-[11px]` arbitrary px (bypasses the golden type scale), no `font-medium`/`font-semibold`/`text-muted-foreground` on a raw `<span>`; use `<Text size tone weight tabular mono>` / `<Heading level>`. (2) NEVER hand-roll a trivial flex/grid wrapper; use `<Flex>` / `<ResponsiveGrid>` / `<PageContainer>`. (3) NEVER set a control's radius/height/colour with a utility when a `shape`/`size`/`tone`/token exists. If a real need has NO prop/token/primitive, that is a library GAP \u2014 file it (draft_bug_report), don't paper over it with ad-hoc Tailwind."},{number:43,title:"Every form control goes through FormField",body:"Consumers MUST wrap every labelled form control (Input, Select, DatePicker, DateRangePicker, NumberInput, Radio.Group, Checkbox groups, range pairs, ...) in FormField \u2014 it owns the label (aria-labelledby, never a dangling <label for>), auto-generates/injects the control id, and wires aria-describedby/aria-errormessage/aria-invalid. Bare controls are the rare exception (e.g. a toolbar quick-filter with its own aria-label) and must carry id/name + aria-label themselves. Never hand-roll a label+control stack with Text/Label."},{number:44,title:"Chrome is a token, default quiet",body:"Any decorative chrome a component draws \u2014 dividers, separator borders, and the padding that exists only to space that chrome \u2014 MUST read a token; never hard-code it in `src/styles/*.css` (a hard-coded `border-bottom: 1px solid hsl(var(--border))` leaves consumers no off-switch short of a variant fork). The DEFAULT is the quietest state (`none` / balanced rhythm); a service theme opts IN, e.g. `--page-header-divider: 1px solid hsl(var(--border))`. Born from real consumption: PageContainer's header divider was undisableable until tokenised."},{number:45,title:"Every service-tunable constant gets a knob",body:'When component CSS encodes a geometry choice that a service plausibly re-tunes to match its design handoff \u2014 form label column width, label\u2194control gap, header insets \u2014 it MUST be a documented component token (current value as the default). The theme sets it ONCE globally; props (`labelWidth`) override per instance; Form\u2192FormField priority stays intact. The test: "would a service theme.css want to change this to match its design grid?" If yes and the only route is forking CSS, that is a library gap \u2014 fix the library, don\'t patch the app. Born from real consumption: `--form-label-width` / `--form-label-gap` (design spec said 110px/8px; the values were prop-only and hard-coded `--space-4`).'},{number:46,title:"Typography is tokens, default is base",body:"A UI framework gives consumers knobs: every font-size in `src/styles/*.css` MUST reference a token \u2014 the global modular scale `var(--font-size-{2xs|xs|sm|base|lg|xl|2xl})` or a per-component `var(--{component}-\u2026-font-size)` knob (rule #45) \u2014 never a hard-coded literal (`font-size: 12px` can't be re-themed). The DEFAULT body size is `--font-size-base`; components render body/UI text at `base`, not at the `sm` alias. Smaller-by-design text (badge, section label, caption) is a component token defaulting to a small step (`--badge-font-size: var(--font-size-xs)`), so a service re-tunes that part without moving the global scale. The `sm`/`xs` tokens stay for the explicit `<Text size>` API. Every component token is surfaced in the MCP `get_component` output (check:mcp-token-sync). Enforced by `check:typography`."}];function T(t){return u.find(a=>a.number===t)}var b=[{name:"common-fixes",tagline:"Fix the most common @godxjp/ui consumer mistakes & visual bugs (StatCard double-border, grey Badge, crushed/empty table headers, washed-out sidebar footer, Inertia layout crash, SSR hydration). Before \u2192 after.",tags:["fixes","migration","bug","cardstat","statusbadge","datatable","sidebar","gotcha","review"],code:`// \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
|
|
1634
1900
|
// 0) \u2605 MOST COMMON: <Card> body has NO padding (content is flush against the edges)
|
|
1635
1901
|
// Cause: the bare <Card> has ZERO inner padding \u2014 it MUST contain <CardContent>.
|
|
1636
1902
|
// Don't hand-roll padding with className="p-4" on the Card either.
|
|
@@ -1818,7 +2084,132 @@ export function GeneralSettingsSection({
|
|
|
1818
2084
|
// use route-backed local navigation so every area is deep-linkable and back/forward works.
|
|
1819
2085
|
// \u26D4 Do NOT wrap the whole settings page in a Card, and do NOT make desktop a horizontal tab bar
|
|
1820
2086
|
// \u2014 persistent local nav scales to many sections; a tab strip does not.
|
|
1821
|
-
// \u26D4 Do NOT invent CSS class names for the shell/nav/content \u2014 compose Flex + semantic tokens.`},{name:"
|
|
2087
|
+
// \u26D4 Do NOT invent CSS class names for the shell/nav/content \u2014 compose Flex + semantic tokens.`},{name:"settings-section-rows",aliases:["settings-section","settings-row","danger-zone","billing-handoff","preferences-rows","account-identity"],tagline:"The four canonical settings BLOCKS \u2014 identity \xB7 preference rows \xB7 billing handoff \xB7 danger zone \u2014 composed from Card + ListRow + Descriptions + StatusBadge + AlertDialog. A COMPOSITION pattern: godx-ui ships no SettingsSection/SettingsRow/DangerZone component and never will.",tags:["settings","account","billing","danger","preferences","listrow","card","admin"],code:`// gh#216. Gate 0 (docs/COMPOSITION-VS-COMPONENT.md): a "SettingsSection"/"SettingsRow"/
|
|
2088
|
+
// "DangerZone" FAILS the Framework-Component Test \u2014 it owns no behavior (C2), is fully
|
|
2089
|
+
// expressible from existing primitives + tokens (C3), and its API would be screen-shaped (C4).
|
|
2090
|
+
// So compose it. Live reference screen: the "settings account sections" page in the preview
|
|
2091
|
+
// showcase gallery (docs/showcase/, served at /showcase/<id>).
|
|
2092
|
+
import { ExternalLink, ShieldAlert } from "lucide-react";
|
|
2093
|
+
import { Button, Text } from "@godxjp/ui/general";
|
|
2094
|
+
import {
|
|
2095
|
+
Card, CardAction, CardContent, CardDescription, CardFooter, CardHeader, CardTitle,
|
|
2096
|
+
Descriptions, ListRow, StatusBadge,
|
|
2097
|
+
} from "@godxjp/ui/data-display";
|
|
2098
|
+
import { Switch } from "@godxjp/ui/data-entry";
|
|
2099
|
+
import { AlertDialog } from "@godxjp/ui/feedback";
|
|
2100
|
+
import { Flex } from "@godxjp/ui/layout";
|
|
2101
|
+
|
|
2102
|
+
// 1) IDENTITY \u2014 metadata is a description list, so it is Descriptions (a real <dl>), never a
|
|
2103
|
+
// two-column flex of styled divs. One header action goes in CardAction.
|
|
2104
|
+
<Card>
|
|
2105
|
+
<CardHeader>
|
|
2106
|
+
<CardTitle level={2}>\u30A2\u30AB\u30A6\u30F3\u30C8\u60C5\u5831</CardTitle>
|
|
2107
|
+
<CardDescription>\u8ACB\u6C42\u66F8\u30FB\u76E3\u67FB\u30ED\u30B0\u306B\u8868\u793A\u3055\u308C\u308B\u6C0F\u540D\u3068\u30E1\u30FC\u30EB\u30A2\u30C9\u30EC\u30B9\u3067\u3059\u3002</CardDescription>
|
|
2108
|
+
<CardAction><Button variant="outline" size="sm">\u7DE8\u96C6</Button></CardAction>
|
|
2109
|
+
</CardHeader>
|
|
2110
|
+
<CardContent>
|
|
2111
|
+
<Descriptions columns={2}>
|
|
2112
|
+
<Descriptions.Item label="\u8868\u793A\u540D">\u85E4\u539F \u6167</Descriptions.Item>
|
|
2113
|
+
<Descriptions.Item label="\u30E1\u30FC\u30EB\u30A2\u30C9\u30EC\u30B9">satoshi@example.co.jp</Descriptions.Item>
|
|
2114
|
+
<Descriptions.Item label="\u5F93\u696D\u54E1\u756A\u53F7" mono>EMP-1042-JP</Descriptions.Item>
|
|
2115
|
+
<Descriptions.Item label="\u5229\u7528\u958B\u59CB\u65E5">
|
|
2116
|
+
{/* ISO-8601 in the machine attribute, Intl for the human text. */}
|
|
2117
|
+
<time dateTime="2023-04-01">
|
|
2118
|
+
{new Intl.DateTimeFormat(locale, { dateStyle: "long", timeZone: "UTC" }).format(since)}
|
|
2119
|
+
</time>
|
|
2120
|
+
</Descriptions.Item>
|
|
2121
|
+
</Descriptions>
|
|
2122
|
+
</CardContent>
|
|
2123
|
+
</Card>
|
|
2124
|
+
|
|
2125
|
+
// 2) PREFERENCE ROWS \u2014 THE setting row pattern: CardContent flush + ListRow, whose \`trailing\`
|
|
2126
|
+
// slot IS the control. align="start" + overflow="wrap" keep long JA/VI titles readable and let
|
|
2127
|
+
// the control drop to its own line at 390px (--list-row-body-min-width).
|
|
2128
|
+
// \u26D4 NEVER: <div className="flex items-center justify-between border-b py-3">\u2026</div>
|
|
2129
|
+
<Card>
|
|
2130
|
+
<CardHeader><CardTitle level={2}>\u901A\u77E5\u306E\u74B0\u5883\u8A2D\u5B9A</CardTitle></CardHeader>
|
|
2131
|
+
<CardContent flush>
|
|
2132
|
+
<ListRow
|
|
2133
|
+
align="start"
|
|
2134
|
+
overflow="wrap"
|
|
2135
|
+
title={<label htmlFor="pref-digest">\u9031\u6B21\u30B5\u30DE\u30EA\u30FC\u3092\u30E1\u30FC\u30EB\u3067\u53D7\u3051\u53D6\u308B</label>}
|
|
2136
|
+
description="\u6BCE\u9031\u6708\u66DC\u65E5\u306E\u671D\u306B\u672A\u51E6\u7406\u306E\u627F\u8A8D\u3092\u307E\u3068\u3081\u3066\u9001\u4FE1\u3057\u307E\u3059\u3002"
|
|
2137
|
+
trailing={<Switch id="pref-digest" checked={digest} onCheckedChange={setDigest} />}
|
|
2138
|
+
/>
|
|
2139
|
+
</CardContent>
|
|
2140
|
+
</Card>
|
|
2141
|
+
|
|
2142
|
+
// 3) BILLING HANDOFF \u2014 the subscription state is a CANONICAL StatusBadge; the tone + icon come
|
|
2143
|
+
// from the library's shared domain-status map, so a page NEVER keeps its own status\u2192colour
|
|
2144
|
+
// table. Money/seats/dates go through Intl (ISO 4217 minor units come from the formatter).
|
|
2145
|
+
// \u26D4 NEVER: const STATUS_COLORS = { past_due: "red", trialing: "blue" }
|
|
2146
|
+
<Card>
|
|
2147
|
+
<CardHeader>
|
|
2148
|
+
<CardTitle level={2}>\u8ACB\u6C42\u3068\u30B5\u30D6\u30B9\u30AF\u30EA\u30D7\u30B7\u30E7\u30F3</CardTitle>
|
|
2149
|
+
<CardDescription>\u652F\u6255\u3044\u65B9\u6CD5\u3068\u8ACB\u6C42\u66F8\u306F\u5916\u90E8\u306E\u8ACB\u6C42\u30DD\u30FC\u30BF\u30EB\u3067\u7BA1\u7406\u3057\u307E\u3059\u3002</CardDescription>
|
|
2150
|
+
<CardAction><StatusBadge status="past_due" variant="outline" /></CardAction>
|
|
2151
|
+
</CardHeader>
|
|
2152
|
+
<CardContent>
|
|
2153
|
+
<Descriptions columns={2}>
|
|
2154
|
+
<Descriptions.Item label="\u5951\u7D04\u30D7\u30E9\u30F3">\u30D3\u30B8\u30CD\u30B9\uFF08\u5E74\u984D\uFF09</Descriptions.Item>
|
|
2155
|
+
<Descriptions.Item label="\u6B21\u56DE\u8ACB\u6C42\u4E88\u5B9A\u984D">
|
|
2156
|
+
{new Intl.NumberFormat(locale, { style: "currency", currency: "JPY" }).format(1536000)}
|
|
2157
|
+
</Descriptions.Item>
|
|
2158
|
+
</Descriptions>
|
|
2159
|
+
</CardContent>
|
|
2160
|
+
<CardFooter separated>
|
|
2161
|
+
<Button variant="outline" asChild>
|
|
2162
|
+
<a href={portalUrl} target="_blank" rel="noreferrer">
|
|
2163
|
+
<ExternalLink aria-hidden="true" />
|
|
2164
|
+
\u8ACB\u6C42\u30DD\u30FC\u30BF\u30EB\u3092\u958B\u304F
|
|
2165
|
+
{/* Never signal "leaves the app" with the icon alone (WCAG 1.4.1 / 2.4.4). */}
|
|
2166
|
+
<span className="sr-only">\uFF08\u65B0\u3057\u3044\u30BF\u30D6\u3067\u958B\u304D\u307E\u3059\uFF09</span>
|
|
2167
|
+
</a>
|
|
2168
|
+
</Button>
|
|
2169
|
+
</CardFooter>
|
|
2170
|
+
</Card>
|
|
2171
|
+
|
|
2172
|
+
// 4) DANGER ZONE \u2014 Card accent="destructive" paints the semantic leading rail from --destructive
|
|
2173
|
+
// through the existing --card-accent-rail-width token. NO new component, NO new token, and no
|
|
2174
|
+
// baked red. Colour never carries the meaning alone: the heading says "irreversible", each row
|
|
2175
|
+
// says what is lost, and delete is gated behind type-to-confirm friction.
|
|
2176
|
+
<Card accent="destructive">
|
|
2177
|
+
<CardHeader>
|
|
2178
|
+
<CardTitle level={2} className="text-destructive">\u53D6\u308A\u6D88\u305B\u306A\u3044\u64CD\u4F5C</CardTitle>
|
|
2179
|
+
<CardDescription>\u4EE5\u4E0B\u306E\u64CD\u4F5C\u306F\u5143\u306B\u623B\u305B\u307E\u305B\u3093\u3002</CardDescription>
|
|
2180
|
+
</CardHeader>
|
|
2181
|
+
<CardContent flush>
|
|
2182
|
+
<ListRow
|
|
2183
|
+
align="start"
|
|
2184
|
+
overflow="wrap"
|
|
2185
|
+
leading={<ShieldAlert className="text-destructive size-4" aria-hidden="true" />}
|
|
2186
|
+
title="\u30EF\u30FC\u30AF\u30B9\u30DA\u30FC\u30B9\u3068\u5168\u30C7\u30FC\u30BF\u3092\u5B8C\u5168\u306B\u524A\u9664\u3059\u308B"
|
|
2187
|
+
description="\u52E4\u6020\u8A18\u9332\u30FB\u7533\u8ACB\u5C65\u6B74\u30FB\u76E3\u67FB\u30ED\u30B0\u304C\u3059\u3079\u3066\u524A\u9664\u3055\u308C\u307E\u3059\u3002"
|
|
2188
|
+
trailing={
|
|
2189
|
+
<Button variant="destructive" size="sm" onClick={() => setConfirmOpen(true)}>\u524A\u9664</Button>
|
|
2190
|
+
}
|
|
2191
|
+
/>
|
|
2192
|
+
</CardContent>
|
|
2193
|
+
</Card>
|
|
2194
|
+
|
|
2195
|
+
<AlertDialog
|
|
2196
|
+
open={confirmOpen}
|
|
2197
|
+
onOpenChange={setConfirmOpen}
|
|
2198
|
+
title="\u30EF\u30FC\u30AF\u30B9\u30DA\u30FC\u30B9\u3092\u5B8C\u5168\u306B\u524A\u9664\u3057\u307E\u3059\u304B\uFF1F"
|
|
2199
|
+
description="\u3053\u306E\u64CD\u4F5C\u306F\u53D6\u308A\u6D88\u305B\u307E\u305B\u3093\u3002\u78BA\u8A8D\u306E\u305F\u3081\u8B58\u5225\u5B50\u3092\u5165\u529B\u3057\u3066\u304F\u3060\u3055\u3044\u3002"
|
|
2200
|
+
challenge={workspaceSlug}
|
|
2201
|
+
variant="destructive"
|
|
2202
|
+
onConfirm={destroy}
|
|
2203
|
+
/>
|
|
2204
|
+
|
|
2205
|
+
// \u2500\u2500 DO / DON'T \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
|
|
2206
|
+
// \u2705 Sections stack in a bounded column: <Flex direction="col" gap="lg" className="max-w-3xl">.
|
|
2207
|
+
// \u2705 Every setting row is a ListRow; its control lives in \`trailing\`, and the visible row title is
|
|
2208
|
+
// a <label htmlFor> pointing at that control so the switch/select is named by what you can read.
|
|
2209
|
+
// \u26D4 Do NOT add SettingsSection / SettingsRow / DangerZone to src/components \u2014 they fail Gate 0.
|
|
2210
|
+
// \u26D4 Do NOT wrap the WHOLE settings page in one Card (route-backed local nav owns the IA \u2014 see the
|
|
2211
|
+
// responsive settings page pattern); each block is its own Card.
|
|
2212
|
+
// \u26D4 Do NOT hand-roll padding (p-4) on a Card, a status colour map, or a <dl> replacement.`},{name:"confirm-destructive",tagline:'Type-to-confirm destructive dialog \u2014 Dialog mode="confirm" + Input gate + toast (real @godxjp/ui API).',tags:["dialog","confirm","destructive","delete"],code:`import { useState } from "react";
|
|
1822
2213
|
import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogDescription, DialogFooter } from "@godxjp/ui/feedback";
|
|
1823
2214
|
import { Input } from "@godxjp/ui/data-entry";
|
|
1824
2215
|
import { Button } from "@godxjp/ui/general";
|
|
@@ -2027,7 +2418,106 @@ export const withCrmLayout = [CrmLayout] // \u2705 array \u2192 Inertia passes
|
|
|
2027
2418
|
// argless new Date() during render (e.g. fabricating chart/demo numbers) \u2192 React
|
|
2028
2419
|
// hydration mismatch ("server rendered text didn't match the client"). Seed
|
|
2029
2420
|
// deterministically by index, or compute inside an event handler:
|
|
2030
|
-
const seeded = (n: number) => { const x = Math.sin((n + 1) * 99.71) * 1e4; return x - Math.floor(x) }`},{name:"
|
|
2421
|
+
const seeded = (n: number) => { const x = Math.sin((n + 1) * 99.71) * 1e4; return x - Math.floor(x) }`},{name:"error-pages",aliases:["error-surface","errorsurface","403","404","500","503","exception-page"],tagline:'403 / 404 / 500 / 503 exception pages. USE THE COMPONENT: import { ErrorSurface } from "@godxjp/ui/layout". `mode="application"` (403/404) is the body you put inside the AppShell the route already renders; `mode="system"` (500/503) owns the page via CenteredShell align="center".',tags:["error","errorsurface","403","404","500","503","maintenance","inertia","ssr","layout","emptystate","centeredshell"],code:`// @godxjp/ui SHIPS <ErrorSurface> (gh#221 \u2192 gh#251). Do NOT hand-compose an error page from
|
|
2422
|
+
// AuthShell + a generic Card + a local ".canonical-auth-card" \u2014 that workaround IS the regression.
|
|
2423
|
+
//
|
|
2424
|
+
// 403 / 404 \u2192 mode="application": the surface is the BODY of the AppShell you ALREADY render
|
|
2425
|
+
// (sidebar + topbar + breadcrumb are PRESERVED, never reconstructed)
|
|
2426
|
+
// 500 / 503 \u2192 mode="system": the surface OWNS the page (CenteredShell align="center",
|
|
2427
|
+
// package-owned geometry at 1440 / 1024 / 390 \u2014 no min-h-dvh, no media query)
|
|
2428
|
+
// Docs page: docs/layout/error-surface/
|
|
2429
|
+
|
|
2430
|
+
import { Button, Logo, Text } from "@godxjp/ui/general";
|
|
2431
|
+
import { AppShell, ErrorSurface, PageContainer, Sidebar } from "@godxjp/ui/layout";
|
|
2432
|
+
|
|
2433
|
+
// \u2500\u2500 WHAT THE COMPONENT OWNS \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
|
|
2434
|
+
// \u2022 status \u2192 icon + tone (ShieldAlert/warning \xB7 SearchX/muted \xB7 ServerCrash/destructive \xB7
|
|
2435
|
+
// Wrench/warning). You pass the NUMBER; you never write the lookup.
|
|
2436
|
+
// \u2022 EXACTLY ONE recovery action \u2014 a single slot; a 2nd element is dropped with a dev error.
|
|
2437
|
+
// \u2022 semantic metadata slots as real <dt>/<dd> pairs, with LIBRARY-owned localized labels:
|
|
2438
|
+
// requestId \xB7 permission \xB7 organization \xB7 maintenance{start,end,timeZone,progress}
|
|
2439
|
+
// \u2022 heading order (h2 in application mode under the PageContainer h1, h1 in system mode)
|
|
2440
|
+
// \u2022 the status code announced as "HTTP status 403", not the cardinal number "403"
|
|
2441
|
+
// PRODUCT COPY stays yours: title / description / action come from the APP's own t().
|
|
2442
|
+
|
|
2443
|
+
// \u2500\u2500 403 / 404 \u2014 APPLICATION mode: PRESERVE the authenticated shell \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
|
|
2444
|
+
export function ForbiddenPage() {
|
|
2445
|
+
return (
|
|
2446
|
+
<AppShell sidebar={<Sidebar activeId="reports" sections={sections} />}>
|
|
2447
|
+
<PageContainer title={t("reports.title")} breadcrumb={[{ label: t("nav.home"), to: "/" }, { label: t("reports.title") }]}>
|
|
2448
|
+
<ErrorSurface
|
|
2449
|
+
mode="application"
|
|
2450
|
+
status={403}
|
|
2451
|
+
title={t("errors.403.title")}
|
|
2452
|
+
description={t("errors.403.description")}
|
|
2453
|
+
permission="reports.view" {/* WHY: the missing role \u2026 */}
|
|
2454
|
+
organization="Acme KK" {/* \u2026 vs the wrong workspace */}
|
|
2455
|
+
action={<Button onClick={goHome}>{t("errors.backHome")}</Button>}
|
|
2456
|
+
/>
|
|
2457
|
+
</PageContainer>
|
|
2458
|
+
</AppShell>
|
|
2459
|
+
);
|
|
2460
|
+
}
|
|
2461
|
+
|
|
2462
|
+
// \u2500\u2500 500 / 503 \u2014 SYSTEM mode: package-owned viewport-centred geometry \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
|
|
2463
|
+
// The component renders CenteredShell align="center" itself \u2192 the column centres in the 100dvh
|
|
2464
|
+
// shell at 1440 / 1024 / 390. NO className, NO min-h-dvh, NO media query. Tall copy still scrolls
|
|
2465
|
+
// from the top (auto offsets collapse), so a long JA/VI message is never clipped.
|
|
2466
|
+
export function MaintenancePage() {
|
|
2467
|
+
return (
|
|
2468
|
+
<ErrorSurface
|
|
2469
|
+
mode="system"
|
|
2470
|
+
status={503}
|
|
2471
|
+
brand={<Logo glyph="G" />}
|
|
2472
|
+
title={t("errors.503.title")}
|
|
2473
|
+
description={t("errors.503.description")}
|
|
2474
|
+
maintenance={{
|
|
2475
|
+
start: "2026-08-02T18:00:00Z", // ISO-8601 instants + an IANA zone \u2192 Intl.formatRange
|
|
2476
|
+
end: "2026-08-02T20:00:00Z",
|
|
2477
|
+
timeZone: "Asia/Tokyo",
|
|
2478
|
+
progress: 40, // 0-100, SERVER-SENT (a client clock breaks hydration)
|
|
2479
|
+
}}
|
|
2480
|
+
footer={<Text size="xs" tone="muted">2026 GodX</Text>}
|
|
2481
|
+
action={<Button onClick={reload}>{t("errors.reload")}</Button>}
|
|
2482
|
+
/>
|
|
2483
|
+
);
|
|
2484
|
+
}
|
|
2485
|
+
|
|
2486
|
+
// \u2500\u2500 INERTIA / SSR \u2014 one Error page component, one expression \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
|
|
2487
|
+
// Laravel: Handler::render() \u2192 Inertia::render('Error', ['status' => $response->getStatusCode()])
|
|
2488
|
+
//
|
|
2489
|
+
// export default function Error({ status, requestId }) {
|
|
2490
|
+
// const isSystem = status >= 500
|
|
2491
|
+
// return (
|
|
2492
|
+
// <ErrorSurface
|
|
2493
|
+
// mode={isSystem ? "system" : "application"}
|
|
2494
|
+
// status={status}
|
|
2495
|
+
// requestId={requestId}
|
|
2496
|
+
// title={t(\`errors.\${status}.title\`)}
|
|
2497
|
+
// description={t(\`errors.\${status}.description\`)}
|
|
2498
|
+
// action={<Button asChild><Link href="/">{t("errors.backHome")}</Link></Button>}
|
|
2499
|
+
// />
|
|
2500
|
+
// )
|
|
2501
|
+
// }
|
|
2502
|
+
// // persistent layout \u21D2 403/404 keep the authenticated shell; 500/503 own the page
|
|
2503
|
+
// Error.layout = (page) => (page.props.status < 500 ? <CrmLayout>{page}</CrmLayout> : page)
|
|
2504
|
+
//
|
|
2505
|
+
// SSR rules:
|
|
2506
|
+
// \u2022 ErrorSurface has NO client state / effects / portals \u2192 it renders fully server-side. An
|
|
2507
|
+
// exception page must be readable without hydration.
|
|
2508
|
+
// \u2022 Pass the maintenance window as a SERVER-SENT ISO-8601 string + an explicit IANA timeZone.
|
|
2509
|
+
// An argless new Date() or the server's local zone \u21D2 hydration mismatch.
|
|
2510
|
+
// \u2022 A 500 page must NOT import the app's query client / data providers \u2014 the failure may be
|
|
2511
|
+
// inside them. ErrorSurface needs only the AppProvider that supplies the locale.
|
|
2512
|
+
//
|
|
2513
|
+
// THEMING: retune with tokens, never a className \u2014
|
|
2514
|
+
// --error-surface-max-width | -gap | -padding-block(-compact) | -meta-gap | -meta-row-gap |
|
|
2515
|
+
// --error-surface-meta-border (default 'none', rule #44) | --error-surface-progress-max-width
|
|
2516
|
+
//
|
|
2517
|
+
// ANTI-PATTERNS: AuthShell + a generic Card (the gh#251 workaround; AuthShell is the
|
|
2518
|
+
// UNAUTHENTICATED root with auth-card geometry) \xB7 rebuilding nav on the 403 page \xB7 two CTAs \xB7
|
|
2519
|
+
// className="min-h-dvh flex \u2026" \xB7 a hand-built "18:00 - 20:00 JST" string \xB7 writing the request id
|
|
2520
|
+
// into 'description' as prose instead of using the semantic slot.`},{name:"badge-coloring",tagline:"Colour a Badge for localized labels and tiers via tone + icon (escape-hatch props).",tags:["statusbadge","badge","tone","color","status","tier","table"],code:`import { Badge } from "@godxjp/ui/data-display"
|
|
2031
2521
|
|
|
2032
2522
|
// Badge auto-colours a fixed set of English LIFECYCLE keys:
|
|
2033
2523
|
// active/completed (success \u2713) \xB7 draft (neutral \u25CB) \xB7 pending/temporary (warning \u23F1)
|
|
@@ -2277,7 +2767,108 @@ export function OrganizationMemberships({
|
|
|
2277
2767
|
);
|
|
2278
2768
|
}
|
|
2279
2769
|
// DON'T: passive rows with no action \xB7 identical decorative icons \xB7 unlabeled timestamps
|
|
2280
|
-
// (label them: "2024\u5E74\u306B\u53C2\u52A0") \xB7 a large empty invitation Card shown permanently.`},{name:"
|
|
2770
|
+
// (label them: "2024\u5E74\u306B\u53C2\u52A0") \xB7 a large empty invitation Card shown permanently.`},{name:"auth-recovery-panels",aliases:["password-recovery","forgot-password","reset-password","mfa-challenge","two-factor-challenge","2fa-challenge","otp-challenge","passkey-failure","scr-008"],tagline:'Signed-OUT password recovery + sign-in MFA challenge panels (SCR-008). There is NO PasswordRecoveryPanel and NO MfaChallengePanel \u2014 compose Card + AuthStack + FormField/InputOTP + Button inside AuthShell preset="account-recovery" (the 432px token-owned measure).',tags:["auth","authshell","recovery","password","mfa","2fa","otp","inputotp","passkey","layout","presentation"],code:`// \u26A0\uFE0F There is NO <PasswordRecoveryPanel state=\u2026/> and NO <MfaChallengePanel state=\u2026/> in
|
|
2771
|
+
// @godxjp/ui (gh#233 \u2014 both fail the Framework-Component Test: they own no behaviour, and a
|
|
2772
|
+
// \`state\` prop that swaps five incompatible bodies is the screen-shaped grab-bag anti-pattern,
|
|
2773
|
+
// the same call as ErrorSurface's \`mode\`). What the package owns is the MEASURE:
|
|
2774
|
+
// <AuthShell variant="canonical" preset="account-recovery"> \u2190 432px panel, 15px gutter at 390
|
|
2775
|
+
// Docs page: docs/layout/auth-recovery/
|
|
2776
|
+
//
|
|
2777
|
+
// PRESENTATION ONLY. No route, no reset semantics, no OTP verification, no recovery-code
|
|
2778
|
+
// consumption, no passkey authentication, no permissions. The consumer owns all logic and copy.
|
|
2779
|
+
|
|
2780
|
+
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@godxjp/ui/data-display";
|
|
2781
|
+
import { FormField, Input, InputOTP, InputOTPGroup, InputOTPSlot, PasswordInput, PasswordStrength } from "@godxjp/ui/data-entry";
|
|
2782
|
+
import { Alert, AlertDescription, AlertTitle } from "@godxjp/ui/feedback";
|
|
2783
|
+
import { Button, Reveal } from "@godxjp/ui/general";
|
|
2784
|
+
import { AuthFooter, AuthShell, AuthStack, Flex } from "@godxjp/ui/layout";
|
|
2785
|
+
|
|
2786
|
+
// \u2500\u2500 THE CANONICAL PANEL ANATOMY \u2014 identical for ALL SEVEN states \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
|
|
2787
|
+
// Card > CardHeader(CardTitle + CardDescription) \u2190 title/description INSIDE the surface
|
|
2788
|
+
// > CardContent > AuthStack
|
|
2789
|
+
// [notice] Alert tone="success|destructive" (only when the state has one)
|
|
2790
|
+
// [fields] FormField(s): email \xB7 password pair \xB7 ONE 6-slot InputOTP \xB7 one Input
|
|
2791
|
+
// [primary] Button fullWidth (spans the panel; exactly one)
|
|
2792
|
+
// [fallback] Flex justify="between" gap="sm" wrap (two ghost actions share ONE row)
|
|
2793
|
+
//
|
|
2794
|
+
// DON'T put <AuthIdentity> above the panel here \u2014 it always renders the hosted mark, and the
|
|
2795
|
+
// SCR-008 hierarchy puts the heading INSIDE the bordered surface.
|
|
2796
|
+
// DON'T reuse <TwoFactorSetup> \u2014 that is the ENROLLMENT Dialog (QR / manual key / recovery
|
|
2797
|
+
// output). A sign-in challenge has no secret to reveal and no dialog to dismiss: it IS the page.
|
|
2798
|
+
|
|
2799
|
+
// \u2500\u2500 SIGN-IN MFA CHALLENGE (otp) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
|
|
2800
|
+
// Focus order IS DOM order: code field \u2192 verify \u2192 fallback A \u2192 fallback B. No tabindex.
|
|
2801
|
+
// The 6-slot row is ONE field: InputOTP owns paste/arrows/backspace/caret and is a single tab
|
|
2802
|
+
// stop. Six <Input>s would put six stops in the ring and break paste of a 6-digit code.
|
|
2803
|
+
export function MfaChallengePage({ code, onCodeChange, error, pending, onVerify }) {
|
|
2804
|
+
return (
|
|
2805
|
+
<AuthShell
|
|
2806
|
+
variant="canonical"
|
|
2807
|
+
preset="account-recovery"
|
|
2808
|
+
brand={brand}
|
|
2809
|
+
footer={<AuthFooter product="GoDX ID" terms={termsLink} privacy={privacyLink} locale={localePicker} />}
|
|
2810
|
+
>
|
|
2811
|
+
<Reveal>
|
|
2812
|
+
<Card>
|
|
2813
|
+
<CardHeader>
|
|
2814
|
+
<CardTitle level={1}>{t("mfa.title")}</CardTitle>
|
|
2815
|
+
<CardDescription>{t("mfa.description")}</CardDescription>
|
|
2816
|
+
</CardHeader>
|
|
2817
|
+
<CardContent>
|
|
2818
|
+
<AuthStack>
|
|
2819
|
+
<FormField id="mfa-code" label={t("mfa.codeLabel")} error={error} required>
|
|
2820
|
+
<InputOTP maxLength={6} pattern="^[0-9]+$" value={code} onChange={onCodeChange}>
|
|
2821
|
+
<InputOTPGroup>
|
|
2822
|
+
{/* six <InputOTPSlot index={0..5} /> */}
|
|
2823
|
+
</InputOTPGroup>
|
|
2824
|
+
</InputOTP>
|
|
2825
|
+
</FormField>
|
|
2826
|
+
<Button fullWidth loading={pending} loadingText={t("common.verifying")} onClick={onVerify}>
|
|
2827
|
+
{t("mfa.verify")}
|
|
2828
|
+
</Button>
|
|
2829
|
+
<Flex justify="between" gap="sm" wrap>
|
|
2830
|
+
<Button variant="ghost" size="sm">{t("mfa.useRecoveryCode")}</Button>
|
|
2831
|
+
<Button variant="ghost" size="sm">{t("mfa.retryPasskey")}</Button>
|
|
2832
|
+
</Flex>
|
|
2833
|
+
</AuthStack>
|
|
2834
|
+
</CardContent>
|
|
2835
|
+
</Card>
|
|
2836
|
+
</Reveal>
|
|
2837
|
+
</AuthShell>
|
|
2838
|
+
);
|
|
2839
|
+
}
|
|
2840
|
+
|
|
2841
|
+
// \u2500\u2500 THE OTHER SIX STATES \u2014 same anatomy, only the notice/fields/copy change \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
|
|
2842
|
+
// The STATE IS NOT A PROP: the server answers, and the route renders the matching body in the
|
|
2843
|
+
// SAME 432px panel. Nothing is reconstructed, because nothing was torn down.
|
|
2844
|
+
//
|
|
2845
|
+
// password recovery \xB7 request \u2014 fields: email (autoComplete="username")
|
|
2846
|
+
// password recovery \xB7 sent \u2014 <Alert tone="success"> + <Button fullWidth disabled> (cooldown)
|
|
2847
|
+
// password recovery \xB7 new-password \u2014 two <PasswordInput autoComplete="new-password"> + <PasswordStrength>
|
|
2848
|
+
// password recovery \xB7 expired \u2014 <Alert tone="destructive">, primary = request a new link
|
|
2849
|
+
// MFA \xB7 recovery-code \u2014 ONE <Input autoComplete="one-time-code">
|
|
2850
|
+
// MFA \xB7 passkey-failure \u2014 <Alert tone="destructive">, primary = retry passkey
|
|
2851
|
+
//
|
|
2852
|
+
// The masked address in \`sent\` ("h*****@example.co.jp") is produced by the SERVER \u2014 the panel
|
|
2853
|
+
// never reconstructs an identifier, it renders the string it is given.
|
|
2854
|
+
|
|
2855
|
+
// \u2500\u2500 STATES \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
|
|
2856
|
+
// error FormField error \u21D2 aria-invalid + aria-errormessage + role="alert". Never colour-only.
|
|
2857
|
+
// loading Button loading + loadingText \u21D2 aria-busy, the box does not resize, fallbacks disabled.
|
|
2858
|
+
// disabled a resend cooldown is <Button disabled> \u2014 keep it VISIBLE so the affordance is known.
|
|
2859
|
+
|
|
2860
|
+
// \u2500\u2500 RESPONSIVE CONTRACT (1440 / 1024 / 390) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
|
|
2861
|
+
// 1440 \xB7 1024 panel 432px, centred, 16px page gutter; OTP one row (6 \xD7 36px = 216px)
|
|
2862
|
+
// 390 panel 360px at x=15 (15px inline gutter, --auth-shell-recovery-main-padding-mobile),
|
|
2863
|
+
// 310px content column, OTP still ONE row, primary still full-width, the fallback
|
|
2864
|
+
// row wraps to a stack only when the localized labels exceed the column.
|
|
2865
|
+
// \u26A0\uFE0F The 390 canonical reference supplied with gh#233 is a desktop 2\xD72 COMPOSITE that overflows
|
|
2866
|
+
// and crops horizontally. It is NOT a valid mobile source and was not traced; the row above is a
|
|
2867
|
+
// DECIDED contract, documented in docs/layout/auth-recovery/ and pinned by tests.
|
|
2868
|
+
|
|
2869
|
+
// ANTI-PATTERNS: a page-local --auth-shell-card-max-width override or a .recovery-panel{width:432px}
|
|
2870
|
+
// class \xB7 AuthIdentity above the panel \xB7 TwoFactorSetup as a challenge \xB7 six Inputs for the OTP row \xB7
|
|
2871
|
+
// re-scoping --control-height to widen the slots (use --otp-slot-size) \xB7 two primary actions.`},{name:"account-recovery-settings",aliases:["recovery-email","backup-codes","security-settings","recovery-settings"],tagline:"Signed-in recovery: compact method status/action rows (email \xB7 phone \xB7 backup codes) + a separate password-change destination \u2014 capability-aware, risk-appropriate, NO stacked Info Alerts.",tags:["account","recovery","security","backup-codes","password","settings"],code:`// Research basis: Google Account recovery (email/phone + backup codes), Microsoft Security info,
|
|
2281
2872
|
// GitHub recovery codes. Signed-IN recovery SETTINGS (manage your methods) is a DIFFERENT surface
|
|
2282
2873
|
// from the signed-OUT forgot-password JOURNEY \u2014 do not mix them on one page.
|
|
2283
2874
|
import { Card, CardContent, CardHeader, CardTitle, ListRow, Badge } from "@godxjp/ui/data-display";
|
|
@@ -2347,7 +2938,125 @@ export function AccountRecoverySettings({ email, phone, backupCodesSupported, ba
|
|
|
2347
2938
|
);
|
|
2348
2939
|
}
|
|
2349
2940
|
// The signed-OUT recovery journey (forgot password \u2192 email link \u2192 reset) is a separate flow on the
|
|
2350
|
-
// auth screens (AuthShell), NOT part of these signed-in settings. Do not surface it here.`}
|
|
2941
|
+
// auth screens (AuthShell), NOT part of these signed-in settings. Do not surface it here.`},{name:"transactional-email",aliases:["email-template","email-tokens","blade-email","html-email"],tagline:"Build a transactional HTML email (Blade/Twig/MJML/React) from @godxjp/ui/email \u2014 literal hex + px derived from the web tokens, the canonical GoDX brand mark, a bulletproof CTA, legal footer and mobile reflow. NEVER hand-copy hex or hand-draw the logo in a mail template.",tags:["email","transactional","blade","laravel","tokens","brand","mark","mjml","cta"],code:`// \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
|
|
2942
|
+
// WHY a separate export: an email is rendered by Blade/Twig/MJML and by clients that strip
|
|
2943
|
+
// <style>, ignore CSS custom properties and block remote images. So NEITHER the React components
|
|
2944
|
+
// NOR @godxjp/ui/tokens are reachable \u2014 every value must be a LITERAL inline style.
|
|
2945
|
+
// @godxjp/ui/email is that bridge. It is React-free and dependency-free, and every value is
|
|
2946
|
+
// DERIVED from src/tokens/ (HSL\u2192hex at module load), so it can never drift from the web palette.
|
|
2947
|
+
//
|
|
2948
|
+
// \u274C style="background:#0077c7" // hand-copied hex \u2192 drifts the first re-theme
|
|
2949
|
+
// \u274C style="padding: var(--space-8)" // no email client resolves var()
|
|
2950
|
+
// \u274C style="width: 30rem" // rem is unreliable; use literal px
|
|
2951
|
+
// \u274C <img src="https://cdn\u2026/logo.png"> // blocked by default in most clients
|
|
2952
|
+
// \u274C an emerald pill with no internal glyph // that is the INCOMPLETE mark
|
|
2953
|
+
// \u2705 every value from the token export + the embedded brand mark below
|
|
2954
|
+
|
|
2955
|
+
import {
|
|
2956
|
+
EMAIL_BRAND_MARK,
|
|
2957
|
+
EMAIL_COLORS,
|
|
2958
|
+
EMAIL_CTA,
|
|
2959
|
+
EMAIL_FOOTER,
|
|
2960
|
+
EMAIL_MOBILE,
|
|
2961
|
+
EMAIL_SHELL,
|
|
2962
|
+
EMAIL_TYPOGRAPHY,
|
|
2963
|
+
emailBrandMarkSvg,
|
|
2964
|
+
emailInlineStyle,
|
|
2965
|
+
} from "@godxjp/ui/email";
|
|
2966
|
+
|
|
2967
|
+
const c = EMAIL_COLORS;
|
|
2968
|
+
|
|
2969
|
+
// 1) THE 480px SHELL \u2014 a table, not a div: Outlook's Word renderer has no flex/grid.
|
|
2970
|
+
const page = emailInlineStyle({ padding: EMAIL_SHELL.pagePadding, backgroundColor: c.background });
|
|
2971
|
+
const card = emailInlineStyle({
|
|
2972
|
+
width: EMAIL_SHELL.width, // "480px" \u2014 EMAIL_SHELL.widthPx (480) for width="480"
|
|
2973
|
+
maxWidth: "100%",
|
|
2974
|
+
backgroundColor: c.surface, // \u2190 --card
|
|
2975
|
+
border: \`\${EMAIL_SHELL.borderWidth} solid \${c.border}\`,
|
|
2976
|
+
borderRadius: EMAIL_SHELL.radius,
|
|
2977
|
+
});
|
|
2978
|
+
const body = emailInlineStyle({
|
|
2979
|
+
padding: EMAIL_SHELL.padding, // content column = EMAIL_SHELL.contentWidth (416px)
|
|
2980
|
+
fontFamily: EMAIL_TYPOGRAPHY.fontFamily,
|
|
2981
|
+
fontSize: EMAIL_TYPOGRAPHY.bodyFontSize,
|
|
2982
|
+
lineHeight: EMAIL_TYPOGRAPHY.bodyLineHeight,
|
|
2983
|
+
color: c.foreground,
|
|
2984
|
+
});
|
|
2985
|
+
|
|
2986
|
+
// 2) THE BRAND MARK \u2014 emerald capsule + internal glyph, embedded, zero network fetch.
|
|
2987
|
+
// EMAIL_BRAND_MARK.svg inline <svg> (Apple Mail, iOS, Thunderbird)
|
|
2988
|
+
// EMAIL_BRAND_MARK.dataUri data: URL for <img src> (no request)
|
|
2989
|
+
// EMAIL_BRAND_MARK.tableHtml <table> fallback (renders everywhere)
|
|
2990
|
+
// The header is a LOCKUP: mark (22px) + EMAIL_BRAND_MARK.gap + the wordmark at
|
|
2991
|
+
// EMAIL_BRAND_MARK.wordmarkFontSize/wordmarkFontWeight. The mark is DECORATIVE there \u2014
|
|
2992
|
+
// emailBrandMarkSvg({ label: "" }) \u2014 so the wordmark carries the name and it is announced once.
|
|
2993
|
+
const mark = emailBrandMarkSvg({ label: "" });
|
|
2994
|
+
|
|
2995
|
+
// 3) THE CTA \u2014 line-height EQUALS height, so the label centres with no flexbox.
|
|
2996
|
+
const ctaCell = emailInlineStyle({
|
|
2997
|
+
height: EMAIL_CTA.height, // 36px \u2014 the canonical box (mirrors --control-height-lg)
|
|
2998
|
+
borderRadius: EMAIL_CTA.radius,
|
|
2999
|
+
backgroundColor: c.primary,
|
|
3000
|
+
});
|
|
3001
|
+
const ctaLink = emailInlineStyle({
|
|
3002
|
+
display: "inline-block",
|
|
3003
|
+
padding: \`0 \${EMAIL_CTA.paddingX}\`,
|
|
3004
|
+
lineHeight: EMAIL_CTA.lineHeight,
|
|
3005
|
+
fontSize: EMAIL_CTA.fontSize,
|
|
3006
|
+
fontWeight: EMAIL_CTA.fontWeight,
|
|
3007
|
+
color: c.primaryForeground,
|
|
3008
|
+
textDecoration: "none",
|
|
3009
|
+
});
|
|
3010
|
+
|
|
3011
|
+
// 4) LEGAL FOOTER \u2014 quiet type over a hairline; links separated by a fixed-width spacer span
|
|
3012
|
+
// (margins between inline links are dropped by several clients).
|
|
3013
|
+
const footer = emailInlineStyle({
|
|
3014
|
+
borderTop: \`\${EMAIL_FOOTER.borderWidth} solid \${c.border}\`,
|
|
3015
|
+
paddingTop: EMAIL_FOOTER.paddingTop,
|
|
3016
|
+
fontSize: EMAIL_FOOTER.fontSize,
|
|
3017
|
+
lineHeight: EMAIL_FOOTER.lineHeight,
|
|
3018
|
+
color: c.mutedForeground,
|
|
3019
|
+
});
|
|
3020
|
+
const linkSpacer = emailInlineStyle({ display: "inline-block", width: EMAIL_FOOTER.linkGap });
|
|
3021
|
+
|
|
3022
|
+
export const html = \`
|
|
3023
|
+
<table role="presentation" border="0" cellpadding="0" cellspacing="0" width="100%">
|
|
3024
|
+
<tr><td align="center" style="\${page}">
|
|
3025
|
+
<table role="presentation" border="0" cellpadding="0" cellspacing="0"
|
|
3026
|
+
width="\${EMAIL_SHELL.widthPx}" class="email-card" style="\${card}">
|
|
3027
|
+
<tr><td class="email-body" style="\${body}">
|
|
3028
|
+
\${mark}
|
|
3029
|
+
<h1 class="email-heading">Your subject line</h1>
|
|
3030
|
+
<p>Your body copy.</p>
|
|
3031
|
+
<table role="presentation" border="0" cellpadding="0" cellspacing="0">
|
|
3032
|
+
<tr><td class="email-cta" align="center" style="\${ctaCell}">
|
|
3033
|
+
<a class="email-cta-link" href="https://your.app/action" style="\${ctaLink}">Continue</a>
|
|
3034
|
+
</td></tr>
|
|
3035
|
+
</table>
|
|
3036
|
+
<table role="presentation" border="0" cellpadding="0" cellspacing="0" width="100%">
|
|
3037
|
+
<tr><td style="\${footer}">
|
|
3038
|
+
<p>This mailbox is not monitored.</p>
|
|
3039
|
+
<p><a href="#">Help</a><span style="\${linkSpacer}">​</span><a href="#">Privacy</a></p>
|
|
3040
|
+
</td></tr>
|
|
3041
|
+
</table>
|
|
3042
|
+
</td></tr>
|
|
3043
|
+
</table>
|
|
3044
|
+
</td></tr>
|
|
3045
|
+
</table>\`;
|
|
3046
|
+
|
|
3047
|
+
// 5) MOBILE REFLOW \u2014 progressive enhancement ONLY. Every rule below has an inline equivalent
|
|
3048
|
+
// above, so a client that strips <style> still renders the desktop layout, never a broken one.
|
|
3049
|
+
export const mediaQuery = \`
|
|
3050
|
+
@media (max-width: \${EMAIL_MOBILE.maxWidth}) {
|
|
3051
|
+
.email-card { width: \${EMAIL_MOBILE.width} !important; }
|
|
3052
|
+
.email-body { padding: \${EMAIL_MOBILE.padding} !important; }
|
|
3053
|
+
.email-heading { font-size: \${EMAIL_MOBILE.headingFontSize} !important; }
|
|
3054
|
+
.email-cta, .email-cta-link { width: \${EMAIL_MOBILE.ctaWidth} !important; }
|
|
3055
|
+
}\`;
|
|
3056
|
+
|
|
3057
|
+
// 6) BLADE / TWIG / any non-JS engine \u2014 dump the same values once in the build step:
|
|
3058
|
+
// node --input-type=module -e 'import("@godxjp/ui/email").then(m => process.stdout.write(m.EMAIL_TOKENS_JSON))' > resources/design/email-tokens.json
|
|
3059
|
+
// then read the JSON from PHP. Same numbers, same source, no copy-paste.`}];function P(t){let a=t.trim().toLowerCase();return b.find(e=>e.name===a||e.aliases?.includes(a))}function q(t){let a=t.trim().toLowerCase();return a===""?b:b.filter(e=>e.name.includes(a)||e.aliases?.some(o=>o.includes(a))||e.tagline.toLowerCase().includes(a)||e.tags.some(o=>o.includes(a)))}function M(t){return t.audience!=="core"}var x=[{id:"taste",audience:"both",name:"Taste baseline \u2014 Senior UI/UX engineering",whenToUse:"Default for any production app screen. Metric-based rules, strict component architecture, CSS hardware acceleration, balanced design engineering.",source:"Leonxlnx/taste-skill (root) + @godxjp/ui design-thinking.ts",sections:[{id:"mobile-first",title:"Mobile-first non-negotiable",tagline:"Defaults target xs (\u22650px); enhance via sm: / md: / lg: / xl: / 2xl:",body:`Cardinal rule 24. Touch targets \u2265 44\xD744 px. NEVER read
|
|
2351
3060
|
window.innerWidth \u2014 use useBreakpoint(). Stories render at narrow
|
|
2352
3061
|
viewport first. Multi-column layouts: grid grid-cols-1 sm:grid-cols-N.
|
|
2353
3062
|
EXCEPTION: name pairs (\u59D3+\u540D) use grid-cols-2 always.`},{id:"one-intent-per-screen",title:"One intent per screen",tagline:"Pick the ONE primary question this page answers. 60-80% visual weight to it.",body:`Wall-of-cards dashboards are AI slop. Show 1-2 hero metrics
|
|
@@ -2871,7 +3580,7 @@ Only after sign-off do you move to code. A wireframe skipped = a redesign reliti
|
|
|
2871
3580
|
6. Hand-rolled UI instead of @godxjp/ui primitives (raw nav buttons, styled-div "Card", raw table).
|
|
2872
3581
|
Every run of this skill produces, in order: (1) a stakeholder-confirmed concept/entity artifact \u2192
|
|
2873
3582
|
(2) a layered IA doc \u2192 (3) an approved wireframe Artifact \u2192 (4) a per-route implementation checklist.
|
|
2874
|
-
Do not advance a step until the prior artifact is signed off.`}]}];function
|
|
3583
|
+
Do not advance a step until the prior artifact is signed off.`}]}];function A(t){return x.find(a=>a.id===t)}function U(t,a){return A(t)?.sections.find(e=>e.id===a)}function j(t,a){let e=t.toLowerCase(),o=[],r=(n,s,g,h,d)=>{n.some(y=>e.includes(y))&&o.push({skill:s,section:g,why:h,alsoSee:d})};r(["premium","awwwards","agency","linear","apple","high-end","luxury"],"soft","vibe-archetypes","Premium tier \u2014 pick a Vibe + Layout archetype + apply Double-Bezel.",["soft/double-bezel","soft/magnetic-hover"]),r(["landing page","marketing","hero","long scroll"],"imagegen-web","hero-composition-bias","Landing pages benefit from hero composition variety + per-section image generation.",["gpt-tasteskill/principles","soft/layout-archetypes"]),r(["mobile app","ios","android","phone screen","onboarding flow"],"imagegen-mobile","principles","Mobile app design \u2014 generate screens first, avoid phone-shaped-website.",["taste/mobile-first"]),r(["workspace","notion","document","editorial","knowledge base"],"minimalist","palette","Editorial workspace = warm monochrome + spot pastels + serif headings.",["minimalist/typography","minimalist/bento-grids"]),r(["dashboard","data heavy","tabular","ops table"],"brutalist","principles","Data-heavy dashboards work with Brutalist (rigid grids, utilitarian color).",["taste/one-intent-per-screen"]),r(["brand","identity","logo","guidelines"],"brandkit","principles","Brand identity work \u2014 boards before screens."),r(["refactor","redesign","upgrade existing","audit"],"redesign","fix-priority","Existing project = run audit first, fix in priority order (font \u2192 palette \u2192 states \u2192 ...).",["redesign/audit-checklist"]),r(["form","validation","submit","sign up","registration"],"taste","form-discipline","Form must have explicit label + help + error wired via FormField (rule 34)."),r(["loading","saving","skeleton","spinner"],"taste","loading-states","Skeleton for INIT fetch, Spinner for active work. Never mix."),r(["mobile first","responsive","breakpoint"],"taste","mobile-first","Default styles target xs. Touch targets \u2265 44px. Use useBreakpoint()."),r(["complete code","full implementation","no placeholder"],"output","banned","Banned: // ..., // TODO, 'for brevity'. Ship complete runnable code."),r(["gsap","scrolltrigger","scroll choreography","pinning"],"gpt-tasteskill","principles","GSAP ScrollTrigger \u2014 pinning, stacking, scrubbing."),r(["from image","image to code","design first"],"image-to-code","workflow","Generate design image first \u2192 analyze \u2192 implement."),r(["handoff","design bundle","claude design","prototype","build the page","implement the design","build this screen","mockup"],"design-to-page","map-to-primitives","Map every block to a real @godxjp/ui primitive (MCP-first), consume existing tokens, apply the reference-design DNA, tables central, gaps \u2192 extend-or-ask, verify.",["design-to-page/read-intent","design-to-page/dna","design-to-page/tables-central"]),r(["compose a screen","new screen","new page","create a page","create a screen","build a view","build a screen","from scratch","screen from a brief"],"compose-a-screen","pick-primitives","Build a new app screen from real @godxjp/ui primitives (MCP-first): one-intent-per-screen, real chrome, every state + a11y + i18n, verify.",["compose-a-screen/assemble-screen","compose-a-screen/state-and-a11y","taste/one-intent-per-screen"]),r(["bug in godx","godx-ui bug","report a bug","file an issue","gh issue","can't follow the rule","library is broken","primitive is broken"],"compose-a-screen","report-bug","If @godxjp/ui itself is at fault, don't fake a workaround \u2014 file a detailed gh issue (use draft_bug_report).",["design-to-page/report-bug"]),r(["slow","performance","perf","violation","long task","lag","janky","re-render","rerender","bundle size","code splitting","tree-shak","ch\u1EADm","l\u1ED7i hi\u1EC7u n\u0103ng","\u91CD\u3044"],"app-performance","measure-first","Measure FIRST (longtask + temporary Profiler), then apply the matching proven fix \u2014 page architecture, not the library, is almost always the culprit.",["app-performance/filter-pane-memo","app-performance/heavy-panels","app-performance/bundle-budget"]),r(["admin console","admin panel","control panel","back-office","back office","management console","management ui","multi-tenant","multi tenant","platform admin","workspace admin","console qu\u1EA3n tr\u1ECB","layered ia","information architecture"],"design-complex-admin","entity-model-first","A complex multi-tier admin console: lock the entity model \u2192 study exemplars \u2192 design a LAYERED IA \u2192 wireframe (Artifact) \u2192 validate \u2192 build route-by-route. Kills the flat-console anti-pattern.",["design-complex-admin/layered-ia","design-complex-admin/wireframe-first","compose-a-screen/pick-primitives"]);let i=a?.consumerOnly?o.filter(n=>{let s=A(n.skill);return!s||s.audience!=="core"}):o;return i.length===0?a?.consumerOnly?[{skill:"compose-a-screen",section:"pick-primitives",why:`No keyword match for "${t}". Default consumer path: compose the screen from real primitives via the MCP.`,alsoSee:["design-to-page/map-to-primitives","taste/one-intent-per-screen"]}]:[{skill:"taste",section:"<see whenToUse>",why:`No keyword match for "${t}". Default to the "taste" baseline \u2014 see whenToUse for sections.`}]:i}var D=[{category:"visual",name:"Purple-blue gradient hero",body:`The default LLM color palette \u2014 purple \u2192 blue \u2192 cyan radial /
|
|
2875
3584
|
linear gradient as hero background. Looks like every AI-generated
|
|
2876
3585
|
SaaS landing page from 2023.`,fix:`Use the framework's accent palette (\`data-accent="blue"\` /
|
|
2877
3586
|
"violet" / "cyan" / "green" / "orange" / "rose"). Solid surface
|
|
@@ -2979,169 +3688,169 @@ arrives.`,fix:`Use Skeleton placeholders matching the eventual content shape.
|
|
|
2979
3688
|
The framework's \`<Form loading={{ kind: "skeleton" }}>\` cascades
|
|
2980
3689
|
to every field; \`<Skeleton className="h-9 w-full rounded-md" />\`
|
|
2981
3690
|
for individual blocks. Layout stays stable, perceived speed
|
|
2982
|
-
improves.`}];function V(a){return D.filter(t=>t.category===a)}var R=[{category:"typography",symptom:"Inter / Roboto / Open Sans everywhere \u2014 the AI default.",fix:"Pick a font with character: Geist, Outfit, Cabinet Grotesk, Satoshi for sans. For editorial / creative \u2014 pair a serif heading (Newsreader, Lyon, Playfair) with a sans body.",uiNote:"Override --font-sans + --font-serif at the consumer's root CSS. Framework reads from these tokens."},{category:"typography",symptom:"Headlines lack presence \u2014 small + thin + default tracking.",fix:"Increase display size, tighten letter-spacing (-0.02em to -0.04em), reduce line-height (1.1). Headlines should feel HEAVY and INTENTIONAL.",uiNote:"Typography.Title size={1} for hero; override fontFamily + letterSpacing inline."},{category:"typography",symptom:"Body paragraphs full-width \u2014 hard to read.",fix:"Limit paragraph max-width to ~65ch. Increase line-height to 1.6+.",uiNote:"Wrap Typography.Paragraph in `<div style={{ maxWidth: '65ch' }}>`."},{category:"typography",symptom:"Only Regular (400) + Bold (700) weights \u2014 flat hierarchy.",fix:"Introduce Medium (500) + SemiBold (600) for subtle weight contrasts."},{category:"typography",symptom:"Numbers in proportional font \u2014 columns jitter in tables.",fix:"`font-variant-numeric: tabular-nums` for data, or a monospace font like Geist Mono.",uiNote:"Table primitive already uses `tabular-nums` on `.num` cells. For ad-hoc numeric labels, add the CSS prop manually."},{category:"typography",symptom:"Orphaned words \u2014 single word on the last line of a heading.",fix:"`text-wrap: balance` (h1/h2/h3) or `text-wrap: pretty` (body)."},{category:"typography",symptom:"Title Case On Every Header.",fix:"Use sentence case instead. More modern, easier to read."},{category:"color-surface",symptom:"Pure #000000 background.",fix:"Replace with off-black (#0A0A0A) / dark charcoal (#121212) / tinted dark (deep navy).",uiNote:"Framework dark theme already uses tinted dark values \u2014 verify the consumer's override didn't force pure black."},{category:"color-surface",symptom:"Oversaturated accent colors.",fix:"Keep saturation below 80%. Desaturate so accents BLEND with neutrals rather than scream."},{category:"color-surface",symptom:"More than one accent color competing.",fix:"Pick ONE. Remove the rest. Consistency beats variety in palette.",uiNote:"Set ONE `data-accent` at `<html>` root. Use semantic colors (success / warning / destructive) only for genuinely semantic content."},{category:"color-surface",symptom:"Purple/blue 'AI gradient' aesthetic \u2014 most common AI fingerprint.",fix:"Replace with neutral base + ONE considered accent. Drop the gradient entirely if it has no narrative purpose."},{category:"color-surface",symptom:"Generic black `box-shadow` everywhere.",fix:"Tint shadow to match background hue (e.g. cool gray bg \u2192 cool gray shadow). Colored shadows over pure black."},{category:"color-surface",symptom:"Random dark section breaking an otherwise light page.",fix:"Either commit to full dark mode OR keep light consistently. If contrast needed, use a SLIGHTLY darker shade of the same palette \u2014 not a sudden jump to #111."},{category:"color-surface",symptom:"Empty flat sections with no visual depth.",fix:"Add subtle background imagery at low opacity (`/picsum.photos/seed/{name}/1920/1080`) OR ambient gradient at 0.02-0.05 opacity. Empty flat = unfinished."},{category:"layout",symptom:"Everything centered + symmetric.",fix:"Break symmetry: offset margins, mixed aspect ratios, left-aligned header over centered body."},{category:"layout",symptom:"Three equal card columns as feature row \u2014 the most generic AI layout.",fix:"Replace with 2-column zig-zag, asymmetric grid, horizontal scroll, or masonry. The 3-equal-cols pattern is RED FLAG #1.",uiNote:"Use Bento Grid (custom CSS grid with `gridColumn: 'span N'`) instead of `<Grid cols={3}>` for hero sections."},{category:"layout",symptom:"`height: 100vh` causing iOS Safari jump.",fix:"Use `min-height: 100dvh` (dynamic viewport) instead."},{category:"layout",symptom:"No max-width container \u2014 content stretches edge-to-edge.",fix:"Add a container constraint (1200-1440px) with `margin: auto`. Or use `max-w-4xl / max-w-5xl` for content-heavy pages.",uiNote:"Framework's PageContent constrains via `var(--container-max-width)`. Consumer may override."},{category:"layout",symptom:"Cards forced to same height by flexbox.",fix:"Allow variable heights or use masonry when content varies.",uiNote:"Use Masonry primitive \u2014 handles variable heights without flexbox stretch."},{category:"layout",symptom:"Buttons at random vertical positions in card rows.",fix:"Pin CTAs to card bottom \u2014 same Y-position across the row regardless of content above.",uiNote:"Card's `actions` footer slot bottom-aligns automatically."},{category:"layout",symptom:"Feature lists starting at different vertical positions in pricing tables.",fix:"Fixed-height title/price block + consistent spacing above the feature list. Cards align across columns."},{category:"layout",symptom:"Dashboard ALWAYS has a left sidebar.",fix:"Consider top navigation, floating command menu, or collapsible panel. Sidebar isn't the only chrome.",uiNote:"Framework supports both \u2014 AppShell with sidebar slot is optional; can use Topbar-only for some flows."},{category:"interactivity",symptom:"No hover states on buttons.",fix:"Background shift, scale, or translate on hover \u2014 150-200ms ease.",uiNote:"Framework Button has built-in hover. If overridden \u2014 restore."},{category:"interactivity",symptom:"No active/pressed feedback.",fix:"`scale(0.98)` or `translateY(1px)` on `:active`. Simulates a physical click."},{category:"interactivity",symptom:"No focus ring (`outline: none`).",fix:"Restore visible `:focus-visible` ring. Accessibility requirement, not optional."},{category:"interactivity",symptom:"Generic circular spinner for page-level loading.",fix:"Replace with Skeleton placeholders matching the eventual content shape.",uiNote:"Framework Skeleton + Form `loading={{ kind: 'skeleton' }}` handles cascading initial-fetch state."},{category:"interactivity",symptom:"No empty states \u2014 empty dashboard shows nothing.",fix:"Design a composed 'getting started' view: Empty primitive with title + description + next-action button."},{category:"interactivity",symptom:"`window.alert()` for errors.",fix:"Inline error in the relevant Field, OR toast for non-form errors, OR Dialog for blocking errors."},{category:"interactivity",symptom:"Dead links (`href='#'`).",fix:"Either link to real destinations or visually disable the button."},{category:"interactivity",symptom:"No indication of current page in navigation.",fix:"Style the active nav link distinctly.",uiNote:"Sidebar handles via `activeId` \u2014 pass it."},{category:"content",symptom:"Generic names \u2014 'John Doe', 'Jane Smith'.",fix:"Diverse, realistic names. For Japanese apps: \u7530\u4E2D \u592A\u90CE, \u4F50\u85E4 \u7F8E\u54B2, Nguy\u1EC5n Lan, Maria Cruz."},{category:"content",symptom:"Fake round numbers \u2014 '99.99%', '50%', '$100.00'.",fix:"Organic data: '47.2%', '$99.00', '+1 (312) 847-1928'."},{category:"content",symptom:"Placeholder brand names \u2014 Acme, Nexus, SmartFlow.",fix:"Invent contextual believable brands or use the consumer's real brand."},{category:"content",symptom:"AI copy clich\xE9s \u2014 'elevate', 'seamless', 'unleash', 'next-gen', 'game-changer', 'delve', 'tapestry', 'in the world of'.",fix:"Plain specific language. Numbers, nouns, verbs.",uiNote:"Framework's cardinal rule 9 bans this in framework docs; same discipline applies to consumer copy."},{category:"content",symptom:"Exclamation marks in success messages.",fix:"Remove. Be confident, not loud."},{category:"content",symptom:"'Oops!' or apologetic error messages.",fix:"Direct + specific: 'Connection failed. Please try again.' / '\u30E1\u30FC\u30EB\u30A2\u30C9\u30EC\u30B9\u306E\u5F62\u5F0F\u304C\u6B63\u3057\u304F\u3042\u308A\u307E\u305B\u3093'."},{category:"content",symptom:"Lorem Ipsum.",fix:"Real draft copy. Even rough placeholder beats Latin."},{category:"components",symptom:"Generic card look (border + shadow + white).",fix:"Remove border OR shadow OR background \u2014 keep ONE. Cards exist only when elevation communicates hierarchy."},{category:"components",symptom:"Always one filled + one ghost button.",fix:"Add text links / tertiary styles for variety.",uiNote:"Button has `variant='link'` for tertiary actions."},{category:"components",symptom:"3-card carousel testimonials with dots.",fix:"Replace with masonry wall of quotes, embedded social posts, or single rotating quote."},{category:"components",symptom:"Pricing table with 3 equal towers.",fix:"Highlight recommended tier with COLOR and emphasis, not just extra height."},{category:"components",symptom:"Modals for everything.",fix:"Use inline editing, Sheet (slide-over), or expandable Collapse for simple actions. Reserve Dialog for true blocking decisions."},{category:"components",symptom:"Footer link farm with 4 columns.",fix:"Simplify. Main nav paths + legally required links. No marketing kitchen sink."},{category:"iconography",symptom:"Lucide or Feather icons exclusively.",fix:"Use Phosphor (Bold / Fill), Heroicons, or a custom set. AI default tell.",uiNote:"Framework ships with lucide as locked dependency (rule 14). For editorial differentiation, layer Phosphor on top."},{category:"iconography",symptom:"Cliche icon metaphors \u2014 rocketship 'launch', shield 'security'.",fix:"Less obvious: bolt, fingerprint, spark, vault, gem."},{category:"iconography",symptom:"Stock 'diverse team in office' photo.",fix:"Real team photos, candid shots, or a consistent illustration style. Avatar initials fallback > generic stock person."},{category:"code-quality",symptom:"Div soup \u2014 no semantic HTML.",fix:"`<nav>`, `<main>`, `<article>`, `<aside>`, `<section>` for landmarks.",uiNote:"AppShell renders the canonical landmark structure automatically."},{category:"code-quality",symptom:"Inline styles mixed with CSS classes haphazardly.",fix:"Move styling into the project's system. Inline `style={{}}` only for layout / positioning (rule 29)."},{category:"code-quality",symptom:"Missing alt text on images.",fix:"Describe content for SR. Never leave `alt=''` or `alt='image'` on meaningful images."},{category:"code-quality",symptom:"Arbitrary z-index values like `9999`.",fix:"Establish a clean z-index scale in CSS variables."},{category:"omissions",symptom:"No legal links in footer.",fix:"Add Privacy Policy + Terms of Service."},{category:"omissions",symptom:"Dead ends in user flows \u2014 no 'back'.",fix:"Every page has a way back. Breadcrumb, back button, OR clear nav state."},{category:"omissions",symptom:"No custom 404 page.",fix:"Design a helpful branded 404 with a way home and search."},{category:"omissions",symptom:"No form validation.",fix:"Client-side validation via zod schema. Framework's Form + FormField handle field-level errors automatically."},{category:"omissions",symptom:"No 'skip to content' link.",fix:"Hidden skip-link, first focusable element. Essential for keyboard users.",uiNote:"AppShell renders one automatically."}],U=["1. Font swap \u2014 biggest instant improvement, lowest risk","2. Color palette cleanup \u2014 remove clashing / oversaturated colors","3. Hover + active states \u2014 makes the interface feel alive","4. Layout + spacing \u2014 proper grid, max-width, consistent padding","5. Replace generic components \u2014 swap cliche patterns for modern alternatives","6. Add loading, empty, error states \u2014 makes it feel finished","7. Polish typography scale + spacing \u2014 the premium final touch"],q=["Work with the existing tech stack. Do NOT migrate frameworks or styling libraries.","Do NOT break existing functionality. Test after every change.","Before importing any new library, check `package.json` first.","Keep changes reviewable + focused. Small targeted improvements over big rewrites.","Run the audit before fixing \u2014 listing issues first prevents accidental scope creep."];function H(a){return R.filter(t=>t.category===a)}var _="node node_modules/@godxjp/ui/scripts/ui-audit.mjs (add --format json for machine output, --rules to print this catalog)",G=[{id:"no-raw-palette-color",severity:"error",category:"tokens",standard:null,fix:"Use semantic tokens (bg-primary, text-muted-foreground), never raw palette (bg-blue-500)."},{id:"no-arbitrary-hex",severity:"error",category:"tokens",standard:null,fix:"No hardcoded hex in className; read design-system color tokens."},{id:"no-arbitrary-spacing",severity:"error",category:"tokens",standard:null,fix:"No p-[13px]/gap-[7px]; use the token scale / <Flex gap> / <PageContainer>."},{id:"no-arbitrary-size",severity:"error",category:"tokens",standard:null,fix:"No w-[37px]/h-[260px]; use token sizes or a sizing prop (min-w-[\u2026] allowed)."},{id:"no-arbitrary-typography",severity:"error",category:"tokens",standard:null,fix:"No text-[20px]/leading-[1.7]; use the golden-ratio type-scale tokens."},{id:"no-arbitrary-radius",severity:"error",category:"tokens",standard:null,fix:"No rounded-[6px]; use rounded-sm/md/lg radius tokens."},{id:"no-dark-color-override",severity:"warn",category:"tokens",standard:null,fix:"Drop dark: color overrides \u2014 semantic tokens already adapt."},{id:"raw-white-black",severity:"warn",category:"tokens",standard:null,fix:"Prefer semantic tokens (text-primary-foreground, bg-background) over raw white/black."},{id:"no-domain-tracking-token",severity:"error",category:"tokens",standard:null,fix:"No package-tracking/domain tokens; use semantic tokens or app theme overrides."},{id:"no-space-xy",severity:"error",category:"tokens",standard:null,fix:"Use <Flex gap> instead of space-x/y-*."},{id:"no-raw-select",severity:"error",category:"composition",standard:"HTML Living Standard (WHATWG)",fix:"Use <Select> from @godxjp/ui, not a raw <select>."},{id:"no-raw-table",severity:"error",category:"composition",standard:"HTML Living Standard (WHATWG)",fix:"Use the <Table>/<DataTable> family, not a raw <table>."},{id:"no-raw-input",severity:"error",category:"composition",standard:"HTML Living Standard (WHATWG)",fix:"Use <Input> from @godxjp/ui, not a raw <input>."},{id:"no-raw-textarea",severity:"warn",category:"composition",standard:"HTML Living Standard (WHATWG)",fix:"Use <Textarea> from @godxjp/ui, not a raw <textarea>."},{id:"no-raw-button",severity:"error",category:"composition",standard:"HTML Living Standard (WHATWG)",fix:"Use <Button> from @godxjp/ui, not a raw <button>."},{id:"card-manual-padding",severity:"error",category:"composition",standard:null,fix:"Wrap the body in <CardContent>; don't hand-roll padding on <Card>."},{id:"card-needs-content",severity:"error",category:"composition",standard:null,fix:"<Card> body must be in <CardContent> (no padding otherwise); flush only for a full-bleed table."},{id:"bare-control-needs-formfield",severity:"warn",category:"composition",standard:"WCAG 2.2 SC 1.3.1 \xB7 3.3.2 \xB7 @godxjp/ui FormField (cardinal rule 227)",fix:"Wrap a labelled control in <FormField label=\u2026> \u2014 it owns label\u2194control id wiring, aria/error, AND the field rhythm; never pair a bare <Label> with an <Input>."},{id:"manual-field-error",severity:"warn",category:"composition",standard:"WCAG 2.2 SC 3.3.1",fix:"Use <FormField error=\u2026>, not a hand-rolled <p class='text-destructive'>."},{id:"manual-field-helper",severity:"warn",category:"composition",standard:null,fix:"Use <FormField helper=\u2026>, not a hand-rolled helper <p>."},{id:"status-tone-not-variant",severity:"error",category:"api",standard:null,fix:"Badge/Tag/StatCard status uses tone, not variant (variant is structural)."},{id:"value-callback-on-value-change",severity:"error",category:"api",standard:null,fix:"Abstract value components use onValueChange, not onChange."},{id:"icon-button-needs-name",severity:"warn",category:"a11y",standard:"WCAG 2.2 SC 4.1.2 \xB7 1.1.1 \xB7 WAI-ARIA 1.2",fix:"Add aria-label={t('\u2026')} to <Button size='icon'>; the glyph is aria-hidden."},{id:"img-needs-alt",severity:"warn",category:"a11y",standard:"WCAG 2.2 SC 1.1.1 \xB7 HTML Living Standard",fix:"Add alt to every <img> (alt='' if decorative); prefer <Avatar>/<AspectRatio>."},{id:"no-positive-tabindex",severity:"warn",category:"a11y",standard:"WCAG 2.2 SC 2.4.3 \xB7 WAI-ARIA APG",fix:"Use tabIndex 0 or -1 only; never positive \u2014 it breaks focus order."},{id:"hand-rolled-close-glyph",severity:"warn",category:"a11y",standard:"WAI-ARIA 1.2 (dialog) \xB7 WCAG 2.2 SC 4.1.2",fix:"Pass onDismiss to <Alert>, or use <Dialog>/<Sheet>'s built-in labelled close \u2014 not a bare \u2715."},{id:"no-emoji-in-ui",severity:"warn",category:"i18n",standard:"Unicode UTS #51 \xB7 WCAG 2.2 SC 1.1.1",fix:"No emoji in product UI; quiet i18n copy + Lucide icon + Badge tone."},{id:"no-emoji-flag",severity:"warn",category:"i18n",standard:"ISO 3166-1 \xB7 ECMA-402 Intl.DisplayNames \xB7 Unicode UTS #51",fix:"Derive country names from Intl.DisplayNames; no emoji flags."},{id:"hardcoded-currency",severity:"warn",category:"i18n",standard:"ISO 4217 \xB7 ECMA-402 Intl.NumberFormat",fix:"Format money with Intl.NumberFormat({ style: 'currency', currency }), not \xA5{amount}."},{id:"raw-intl-date",severity:"warn",category:"i18n",standard:"ISO 8601 \xB7 IANA tz \xB7 ECMA-402 Intl.DateTimeFormat",fix:"Use formatDate from @godxjp/ui/datetime, not hand-built or locale-default dates."},{id:"no-physical-direction",severity:"warn",category:"rtl",standard:"W3C CSS Logical Properties L1 \xB7 WCAG 2.2 (1.3.2)",fix:"Use logical utilities (ms-/me-/ps-/pe-, start-/end-, text-start/end, border-s/e, rounded-s/e)."},{id:"no-em-dash-in-copy",severity:"warn",category:"copy",standard:"@godxjp/ui reference-design typography",fix:"No em-dash (\u2014) in copy; use a middot \xB7 or two calm sentences."}];function W(a){return a?G.filter(t=>t.category===a):G}var K="node node_modules/@godxjp/ui/scripts/visual-audit.mjs <baseUrl> [route \u2026] (optional peers, TESTED range: playwright >=1.55 <2 [1.61.1] + @axe-core/playwright >=4.10 <5 [4.12.1] + axe-core >=4.10 <5 [4.12.1] + a chromium via `playwright install chromium`; --strict for a CI gate, --format json ALWAYS emits valid JSON with a status of ok|partial|error separating infra errors[] from product findings[], --rules to print this catalog)",$=[{id:"axe-violations",severity:"warn",category:"a11y",standard:"WCAG 2.2 A/AA \xB7 WAI-ARIA 1.2 (axe-core engine)",fix:"Fix each axe node \u2014 contrast (1.4.3), name/role/value (4.1.2), ARIA, landmarks. Runs on the REAL DOM, catching what static analysis cannot."},{id:"target-size-min",severity:"warn",category:"a11y",standard:"WCAG 2.2 SC 2.5.8 (24\xD724 AA) \xB7 2.5.5 (44\xD744 AAA)",fix:"Interactive targets must be \u226524\xD724 CSS px; size from the --control-height tier."},{id:"oversaturated-accent",severity:"warn",category:"color",standard:"@godxjp/ui reference-design \u6E0B\u307F (OKLCH chroma \u2264 0.18)",fix:"Desaturate brand/primary surfaces (OKLCH chroma \u2264 0.18); read --primary tokens, no raw vivid bars."},{id:"emoji-rendered",severity:"warn",category:"i18n",standard:"Unicode UTS #51 \xB7 WCAG 2.2 SC 1.1.1",fix:"Remove emoji from rendered product text; quiet i18n copy + Lucide icon + Badge tone."},{id:"alert-controls-misplaced",severity:"warn",category:"layout",standard:"@godxjp/ui Alert anatomy \xB7 WAI-ARIA 1.2 \xB7 WCAG 2.2 SC 4.1.2",fix:"Use <Alert>: one leading tone icon, <Alert.Actions> trailing-right normal width, onDismiss \xD7 top-right, one horizontal row."}];function Y(a){return a?$.filter(t=>t.category===a):$}var h={name:"@godxjp/ui-mcp",version:"18.4.0",godxUiCompatibility:"18.4.x",description:"Model Context Protocol server for @godxjp/ui \u2014 gives Claude Code / Codex CLI / Cursor / any MCP-aware agent live access to the component catalog, prop vocabulary, design tokens, 45 cardinal rules, copy-paste-ready patterns, 12 design / taste skills synthesised from Leonxlnx/taste-skill, 20+ anti-AI-tell patterns, and a 50-check redesign audit \u2014 token-efficient (list \u2192 drill-down).",type:"module",main:"./dist/index.js",module:"./dist/index.js",types:"./dist/index.d.ts",bin:{"godx-ui-mcp":"./dist/index.js"},files:["dist","README.md"],publishConfig:{registry:"https://registry.npmjs.org/",access:"public"},repository:{type:"git",url:"git+https://github.com/godx-jp/godxjp-ui.git",directory:"mcp"},homepage:"https://github.com/godx-jp/godxjp-ui/tree/main/mcp#readme",license:"Apache-2.0",scripts:{build:"tsup",dev:"tsup --watch",start:"node dist/index.js",inspect:"npx @modelcontextprotocol/inspector node dist/index.js","type-check":"tsc --noEmit",test:"vitest run",prepublishOnly:"npm run build"},dependencies:{"@modelcontextprotocol/sdk":"^1.29.0",zod:"^4.4.3"},devDependencies:{"@types/node":"^22.10.0",tsup:"^8.5.1",typescript:"^6.0.3",vitest:"^4.1.6"},keywords:["mcp","model-context-protocol","godxjp","ui","design-system","react","claude","cursor"]};var J=[{name:"list_skills",description:"List every design/taste skill bundled by this MCP (id + name + whenToUse + section ids). Use FIRST to discover skills; then `get_skill_section` to drill in.",inputSchema:{type:"object",properties:{}}},{name:"list_primitives",description:"List every @godxjp/ui primitive/composite/shell (group + tagline per entry). Optionally filter by group. Then `get_component` for one's full API.",inputSchema:{type:"object",properties:{group:{type:"string",enum:["general","layout","data-display","data-entry","feedback","navigation","composites","shell","providers"]}}}},{name:"list_patterns",description:"List every canonical copy-paste code pattern (signup-form, settings-page, data-table-page, async-data-state, confirm-destructive, \u2026); common aliases resolve too. Use before `get_pattern`.",inputSchema:{type:"object",properties:{}}},{name:"list_anti_ai_tells",description:"List every AI-tell pattern to AVOID (optionally by category). Use to self-audit a design before shipping; then `get_anti_ai_tell` for the fix.",inputSchema:{type:"object",properties:{category:{type:"string",enum:["visual","layout","copy","interaction","imagery","structure"]}}}},{name:"list_redesign_checks",description:"List the redesign audit checklist (50+ checks; optionally by category). Use when auditing an existing project; then `get_redesign_check` for a symptom's fix.",inputSchema:{type:"object",properties:{category:{type:"string",enum:["typography","color-surface","layout","interactivity","content","components","iconography","code-quality","omissions"]}}}},{name:"list_audit_rules",description:"List the LOCAL static ui-audit rules (scripts/ui-audit.mjs) to run BEFORE any visual review \u2014 each cites the standard it enforces (WCAG/WAI-ARIA/Intl/ISO/IANA/CSS-Logical) + a fix + the run command. Optionally by category.",inputSchema:{type:"object",properties:{category:{type:"string",enum:["tokens","composition","api","a11y","i18n","rtl","copy"]}}}},{name:"list_visual_checks",description:"List the RUNTIME visual-audit checks (scripts/visual-audit.mjs \u2014 Playwright + axe-core) to run against the RUNNING app: contrast/ARIA (axe), target size, rendered-accent chroma, DOM emoji, banner layout. Needs a browser (vs list_audit_rules, static). Optionally by category.",inputSchema:{type:"object",properties:{category:{type:"string",enum:["a11y","color","i18n","layout"]}}}},{name:"get_anti_ai_tell",description:"Fetch ONE anti-AI-tell \u2014 full body + concrete fix. Use after `list_anti_ai_tells`.",inputSchema:{type:"object",properties:{name:{type:"string",description:"Exact tell name from list_anti_ai_tells."}},required:["name"]}},{name:"get_redesign_check",description:"Fetch redesign check(s) matching a symptom snippet. Returns full fix + UI note. Use after `list_redesign_checks`.",inputSchema:{type:"object",properties:{symptom:{type:"string",description:"Fragment of the symptom text (e.g. 'Inter everywhere' / '100vh')."}},required:["symptom"]}},{name:"get_skill_section",description:"Fetch ONE section of ONE skill \u2014 token-efficient. E.g. `skill='soft', section='double-bezel'`. Use after `list_skills` narrowed the relevant skill + section.",inputSchema:{type:"object",properties:{skill:{type:"string",description:"Skill id (e.g. 'soft', 'minimalist', 'taste')."},section:{type:"string",description:"Section id within that skill."}},required:["skill","section"]}},{name:"get_component",description:"Full guide for one @godxjp/ui component \u2014 import path, props/types/defaults, HOW to use it (DO/DON'T), WHEN to reach for it (use cases), related components (don't reinvent/confuse), a copy-paste example, story path, and cardinal rules. Use this before hand-rolling anything. Design-token knobs are listed compactly (name+default); pass `verbose:true` for what each token controls.",inputSchema:{type:"object",properties:{name:{type:"string",description:"Component name (e.g. 'Button', 'DataTable')."},verbose:{type:"boolean",description:"Include the full design-token table with a 'what it controls' description per token. Default false (compact token+default only) to save context."}},required:["name"]}},{name:"get_pattern",description:"Full code snippet for one canonical pattern \u2014 copy-paste-ready.",inputSchema:{type:"object",properties:{name:{type:"string",description:"Pattern slug (use list_patterns first)."}},required:["name"]}},{name:"get_rule",description:"Read one cardinal rule from CLAUDE.md (by number) OR all if no number.",inputSchema:{type:"object",properties:{number:{type:"number",description:"Rule number (1-N)."}}}},{name:"get_vocab",description:"Read shared prop-vocabulary type (`SizeProp`, `StatusProp`, `ColorProp`, `LoadingProp`, etc.) OR all if no name.",inputSchema:{type:"object",properties:{name:{type:"string",description:"Vocab type name."}}}},{name:"get_tokens",description:"Read design tokens, optionally filtered by tier category (primitive / semantic / component).",inputSchema:{type:"object",properties:{category:{type:"string",enum:["primitive","semantic","component"]}}}},{name:"list_consumer_skills",description:"List the design skills relevant to an app-dev BUILDING WITH @godxjp/ui (audience consumer/both). Hides core library-maintenance skills. START HERE if you import @godxjp/ui and want guidance (design-to-page, compose-a-screen, taste, \u2026). Returns id + name + whenToUse + section ids.",inputSchema:{type:"object",properties:{}}},{name:"get_consumer_skill",description:"Fetch ONE section of ONE consumer-facing skill. Same as get_skill_section but refuses core-only skills (steers app-devs away from library-maintenance material). Use after list_consumer_skills / route_consumer_task.",inputSchema:{type:"object",properties:{skill:{type:"string",description:"Consumer skill id (e.g. 'design-to-page', 'compose-a-screen')."},section:{type:"string",description:"Section id within that skill."}},required:["skill"]}},{name:"route_consumer_task",description:"Natural-language task \u2192 consumer skill+section pointer. Like route_task but only points to consumer-facing skills (never core library-maintenance). Use FIRST when you're building an app with @godxjp/ui.",inputSchema:{type:"object",properties:{task:{type:"string",description:"Describe what you want to build."}},required:["task"]}},{name:"draft_bug_report",description:"When @godxjp/ui ITSELF is at fault (missing token, a primitive lacking the controlled-vocabulary prop, a real a11y/behaviour bug, a wrong catalog example) and you cannot follow a rule \u2014 DON'T fake a workaround. This drafts a detailed GitHub issue body + a copy-paste `gh issue create` command so you can report it. Prints the command only; never runs gh.",inputSchema:{type:"object",properties:{summary:{type:"string",description:"One-line title of the bug / blocked rule."},repro:{type:"string",description:"Minimal steps or code to reproduce."},expected:{type:"string",description:"What SHOULD happen (per the rule/spec)."},actual:{type:"string",description:"What actually happens."},component:{type:"string",description:"Affected component name, if any (links to get_component)."},rule:{type:"number",description:"Cardinal rule number that can't be followed, if any."},version:{type:"string",description:"Installed @godxjp/ui version (e.g. '12.1.0')."},env:{type:"string",description:"Environment (browser/OS/framework), if relevant."}},required:["summary"]}},{name:"check_compatibility",description:"Report whether the @godxjp/ui version installed in the target project matches THIS catalog (which describes one release train). A mismatched minor means the props/tokens/patterns may describe a build they never installed (#140). Pass the installed version (`npm ls @godxjp/ui`); call it at the START of a consumer session.",inputSchema:{type:"object",properties:{version:{type:"string",description:"Installed @godxjp/ui version in the target project, e.g. '16.10.0'. Omit to just read the catalog's own version + compatible range."}}}},{name:"route_task",description:"Natural-language task \u2192 skill+section pointer (e.g. 'design a premium agency hero' \u2192 soft/vibe-archetypes). Use FIRST when you don't know which skill applies.",inputSchema:{type:"object",properties:{task:{type:"string",description:"Describe what you want to build."}},required:["task"]}},{name:"suggest_primitive",description:"Use case \u2192 primitive recommendation. E.g. 'confirm a destructive delete' \u2192 DangerZone pattern + Dialog suggestion.",inputSchema:{type:"object",properties:{use_case:{type:"string"}},required:["use_case"]}},{name:"search_components",description:"Fuzzy-search primitives by name / tagline / prop. Returns ranked matches.",inputSchema:{type:"object",properties:{query:{type:"string"}},required:["query"]}},{name:"lint_jsx",description:"Heuristic check of a JSX snippet for common violations \u2014 raw `<button>` / `<input>`, `color='error'` on Tag/Badge, missing aria-label, missing source.code override on stories with cell renderers (rule 34), etc.",inputSchema:{type:"object",properties:{jsx:{type:"string"}},required:["jsx"]}}];async function X(a,t){switch(a){case"list_skills":return ie();case"list_primitives":return Z(t.group);case"list_patterns":return ce();case"list_anti_ai_tells":return he(t.category);case"list_redesign_checks":return ge(t.category);case"list_audit_rules":return ue(t.category);case"list_visual_checks":return pe(t.category);case"get_anti_ai_tell":return me(String(t.name??""));case"get_redesign_check":return fe(String(t.symptom??""));case"get_skill_section":return ee(String(t.skill??""),String(t.section??""));case"get_component":return ve(String(t.name??""),t.verbose===!0);case"get_pattern":return we(String(t.name??""));case"get_rule":return ke(typeof t.number=="number"?t.number:void 0);case"get_vocab":return Ce(t.name==null?void 0:String(t.name));case"get_tokens":return xe(t.category);case"list_consumer_skills":return re();case"get_consumer_skill":return se(String(t.skill??""),String(t.section??""));case"route_consumer_task":return Q(String(t.task??""),{consumerOnly:!0});case"draft_bug_report":return le(t);case"check_compatibility":return de(t.version==null?void 0:String(t.version));case"route_task":return Q(String(t.task??""));case"suggest_primitive":return Se(String(t.use_case??""));case"search_components":return Te(String(t.query??""));case"lint_jsx":return De(String(t.jsx??""));default:return`Unknown tool: ${a}`}}function ie(){let a=`# Available skills (${k.length})
|
|
3691
|
+
improves.`}];function H(t){return D.filter(a=>a.category===t)}var R=[{category:"typography",symptom:"Inter / Roboto / Open Sans everywhere \u2014 the AI default.",fix:"Pick a font with character: Geist, Outfit, Cabinet Grotesk, Satoshi for sans. For editorial / creative \u2014 pair a serif heading (Newsreader, Lyon, Playfair) with a sans body.",uiNote:"Override --font-sans + --font-serif at the consumer's root CSS. Framework reads from these tokens."},{category:"typography",symptom:"Headlines lack presence \u2014 small + thin + default tracking.",fix:"Increase display size, tighten letter-spacing (-0.02em to -0.04em), reduce line-height (1.1). Headlines should feel HEAVY and INTENTIONAL.",uiNote:"Typography.Title size={1} for hero; override fontFamily + letterSpacing inline."},{category:"typography",symptom:"Body paragraphs full-width \u2014 hard to read.",fix:"Limit paragraph max-width to ~65ch. Increase line-height to 1.6+.",uiNote:"Wrap Typography.Paragraph in `<div style={{ maxWidth: '65ch' }}>`."},{category:"typography",symptom:"Only Regular (400) + Bold (700) weights \u2014 flat hierarchy.",fix:"Introduce Medium (500) + SemiBold (600) for subtle weight contrasts."},{category:"typography",symptom:"Numbers in proportional font \u2014 columns jitter in tables.",fix:"`font-variant-numeric: tabular-nums` for data, or a monospace font like Geist Mono.",uiNote:"Table primitive already uses `tabular-nums` on `.num` cells. For ad-hoc numeric labels, add the CSS prop manually."},{category:"typography",symptom:"Orphaned words \u2014 single word on the last line of a heading.",fix:"`text-wrap: balance` (h1/h2/h3) or `text-wrap: pretty` (body)."},{category:"typography",symptom:"Title Case On Every Header.",fix:"Use sentence case instead. More modern, easier to read."},{category:"color-surface",symptom:"Pure #000000 background.",fix:"Replace with off-black (#0A0A0A) / dark charcoal (#121212) / tinted dark (deep navy).",uiNote:"Framework dark theme already uses tinted dark values \u2014 verify the consumer's override didn't force pure black."},{category:"color-surface",symptom:"Oversaturated accent colors.",fix:"Keep saturation below 80%. Desaturate so accents BLEND with neutrals rather than scream."},{category:"color-surface",symptom:"More than one accent color competing.",fix:"Pick ONE. Remove the rest. Consistency beats variety in palette.",uiNote:"Set ONE `data-accent` at `<html>` root. Use semantic colors (success / warning / destructive) only for genuinely semantic content."},{category:"color-surface",symptom:"Purple/blue 'AI gradient' aesthetic \u2014 most common AI fingerprint.",fix:"Replace with neutral base + ONE considered accent. Drop the gradient entirely if it has no narrative purpose."},{category:"color-surface",symptom:"Generic black `box-shadow` everywhere.",fix:"Tint shadow to match background hue (e.g. cool gray bg \u2192 cool gray shadow). Colored shadows over pure black."},{category:"color-surface",symptom:"Random dark section breaking an otherwise light page.",fix:"Either commit to full dark mode OR keep light consistently. If contrast needed, use a SLIGHTLY darker shade of the same palette \u2014 not a sudden jump to #111."},{category:"color-surface",symptom:"Empty flat sections with no visual depth.",fix:"Add subtle background imagery at low opacity (`/picsum.photos/seed/{name}/1920/1080`) OR ambient gradient at 0.02-0.05 opacity. Empty flat = unfinished."},{category:"layout",symptom:"Everything centered + symmetric.",fix:"Break symmetry: offset margins, mixed aspect ratios, left-aligned header over centered body."},{category:"layout",symptom:"Three equal card columns as feature row \u2014 the most generic AI layout.",fix:"Replace with 2-column zig-zag, asymmetric grid, horizontal scroll, or masonry. The 3-equal-cols pattern is RED FLAG #1.",uiNote:"Use Bento Grid (custom CSS grid with `gridColumn: 'span N'`) instead of `<Grid cols={3}>` for hero sections."},{category:"layout",symptom:"`height: 100vh` causing iOS Safari jump.",fix:"Use `min-height: 100dvh` (dynamic viewport) instead."},{category:"layout",symptom:"No max-width container \u2014 content stretches edge-to-edge.",fix:"Add a container constraint (1200-1440px) with `margin: auto`. Or use `max-w-4xl / max-w-5xl` for content-heavy pages.",uiNote:"Framework's PageContent constrains via `var(--container-max-width)`. Consumer may override."},{category:"layout",symptom:"Cards forced to same height by flexbox.",fix:"Allow variable heights or use masonry when content varies.",uiNote:"Use Masonry primitive \u2014 handles variable heights without flexbox stretch."},{category:"layout",symptom:"Buttons at random vertical positions in card rows.",fix:"Pin CTAs to card bottom \u2014 same Y-position across the row regardless of content above.",uiNote:"Card's `actions` footer slot bottom-aligns automatically."},{category:"layout",symptom:"Feature lists starting at different vertical positions in pricing tables.",fix:"Fixed-height title/price block + consistent spacing above the feature list. Cards align across columns."},{category:"layout",symptom:"Dashboard ALWAYS has a left sidebar.",fix:"Consider top navigation, floating command menu, or collapsible panel. Sidebar isn't the only chrome.",uiNote:"Framework supports both \u2014 AppShell with sidebar slot is optional; can use Topbar-only for some flows."},{category:"interactivity",symptom:"No hover states on buttons.",fix:"Background shift, scale, or translate on hover \u2014 150-200ms ease.",uiNote:"Framework Button has built-in hover. If overridden \u2014 restore."},{category:"interactivity",symptom:"No active/pressed feedback.",fix:"`scale(0.98)` or `translateY(1px)` on `:active`. Simulates a physical click."},{category:"interactivity",symptom:"No focus ring (`outline: none`).",fix:"Restore visible `:focus-visible` ring. Accessibility requirement, not optional."},{category:"interactivity",symptom:"Generic circular spinner for page-level loading.",fix:"Replace with Skeleton placeholders matching the eventual content shape.",uiNote:"Framework Skeleton + Form `loading={{ kind: 'skeleton' }}` handles cascading initial-fetch state."},{category:"interactivity",symptom:"No empty states \u2014 empty dashboard shows nothing.",fix:"Design a composed 'getting started' view: Empty primitive with title + description + next-action button."},{category:"interactivity",symptom:"`window.alert()` for errors.",fix:"Inline error in the relevant Field, OR toast for non-form errors, OR Dialog for blocking errors."},{category:"interactivity",symptom:"Dead links (`href='#'`).",fix:"Either link to real destinations or visually disable the button."},{category:"interactivity",symptom:"No indication of current page in navigation.",fix:"Style the active nav link distinctly.",uiNote:"Sidebar handles via `activeId` \u2014 pass it."},{category:"content",symptom:"Generic names \u2014 'John Doe', 'Jane Smith'.",fix:"Diverse, realistic names. For Japanese apps: \u7530\u4E2D \u592A\u90CE, \u4F50\u85E4 \u7F8E\u54B2, Nguy\u1EC5n Lan, Maria Cruz."},{category:"content",symptom:"Fake round numbers \u2014 '99.99%', '50%', '$100.00'.",fix:"Organic data: '47.2%', '$99.00', '+1 (312) 847-1928'."},{category:"content",symptom:"Placeholder brand names \u2014 Acme, Nexus, SmartFlow.",fix:"Invent contextual believable brands or use the consumer's real brand."},{category:"content",symptom:"AI copy clich\xE9s \u2014 'elevate', 'seamless', 'unleash', 'next-gen', 'game-changer', 'delve', 'tapestry', 'in the world of'.",fix:"Plain specific language. Numbers, nouns, verbs.",uiNote:"Framework's cardinal rule 9 bans this in framework docs; same discipline applies to consumer copy."},{category:"content",symptom:"Exclamation marks in success messages.",fix:"Remove. Be confident, not loud."},{category:"content",symptom:"'Oops!' or apologetic error messages.",fix:"Direct + specific: 'Connection failed. Please try again.' / '\u30E1\u30FC\u30EB\u30A2\u30C9\u30EC\u30B9\u306E\u5F62\u5F0F\u304C\u6B63\u3057\u304F\u3042\u308A\u307E\u305B\u3093'."},{category:"content",symptom:"Lorem Ipsum.",fix:"Real draft copy. Even rough placeholder beats Latin."},{category:"components",symptom:"Generic card look (border + shadow + white).",fix:"Remove border OR shadow OR background \u2014 keep ONE. Cards exist only when elevation communicates hierarchy."},{category:"components",symptom:"Always one filled + one ghost button.",fix:"Add text links / tertiary styles for variety.",uiNote:"Button has `variant='link'` for tertiary actions."},{category:"components",symptom:"3-card carousel testimonials with dots.",fix:"Replace with masonry wall of quotes, embedded social posts, or single rotating quote."},{category:"components",symptom:"Pricing table with 3 equal towers.",fix:"Highlight recommended tier with COLOR and emphasis, not just extra height."},{category:"components",symptom:"Modals for everything.",fix:"Use inline editing, Sheet (slide-over), or expandable Collapse for simple actions. Reserve Dialog for true blocking decisions."},{category:"components",symptom:"Footer link farm with 4 columns.",fix:"Simplify. Main nav paths + legally required links. No marketing kitchen sink."},{category:"iconography",symptom:"Lucide or Feather icons exclusively.",fix:"Use Phosphor (Bold / Fill), Heroicons, or a custom set. AI default tell.",uiNote:"Framework ships with lucide as locked dependency (rule 14). For editorial differentiation, layer Phosphor on top."},{category:"iconography",symptom:"Cliche icon metaphors \u2014 rocketship 'launch', shield 'security'.",fix:"Less obvious: bolt, fingerprint, spark, vault, gem."},{category:"iconography",symptom:"Stock 'diverse team in office' photo.",fix:"Real team photos, candid shots, or a consistent illustration style. Avatar initials fallback > generic stock person."},{category:"code-quality",symptom:"Div soup \u2014 no semantic HTML.",fix:"`<nav>`, `<main>`, `<article>`, `<aside>`, `<section>` for landmarks.",uiNote:"AppShell renders the canonical landmark structure automatically."},{category:"code-quality",symptom:"Inline styles mixed with CSS classes haphazardly.",fix:"Move styling into the project's system. Inline `style={{}}` only for layout / positioning (rule 29)."},{category:"code-quality",symptom:"Missing alt text on images.",fix:"Describe content for SR. Never leave `alt=''` or `alt='image'` on meaningful images."},{category:"code-quality",symptom:"Arbitrary z-index values like `9999`.",fix:"Establish a clean z-index scale in CSS variables."},{category:"omissions",symptom:"No legal links in footer.",fix:"Add Privacy Policy + Terms of Service."},{category:"omissions",symptom:"Dead ends in user flows \u2014 no 'back'.",fix:"Every page has a way back. Breadcrumb, back button, OR clear nav state."},{category:"omissions",symptom:"No custom 404 page.",fix:"Design a helpful branded 404 with a way home and search."},{category:"omissions",symptom:"No form validation.",fix:"Client-side validation via zod schema. Framework's Form + FormField handle field-level errors automatically."},{category:"omissions",symptom:"No 'skip to content' link.",fix:"Hidden skip-link, first focusable element. Essential for keyboard users.",uiNote:"AppShell renders one automatically."}],G=["1. Font swap \u2014 biggest instant improvement, lowest risk","2. Color palette cleanup \u2014 remove clashing / oversaturated colors","3. Hover + active states \u2014 makes the interface feel alive","4. Layout + spacing \u2014 proper grid, max-width, consistent padding","5. Replace generic components \u2014 swap cliche patterns for modern alternatives","6. Add loading, empty, error states \u2014 makes it feel finished","7. Polish typography scale + spacing \u2014 the premium final touch"],W=["Work with the existing tech stack. Do NOT migrate frameworks or styling libraries.","Do NOT break existing functionality. Test after every change.","Before importing any new library, check `package.json` first.","Keep changes reviewable + focused. Small targeted improvements over big rewrites.","Run the audit before fixing \u2014 listing issues first prevents accidental scope creep."];function _(t){return R.filter(a=>a.category===t)}var K="node node_modules/@godxjp/ui/scripts/ui-audit.mjs (add --format json for machine output, --rules to print this catalog)",$=[{id:"no-raw-palette-color",severity:"error",category:"tokens",standard:null,fix:"Use semantic tokens (bg-primary, text-muted-foreground), never raw palette (bg-blue-500)."},{id:"no-arbitrary-hex",severity:"error",category:"tokens",standard:null,fix:"No hardcoded hex in className; read design-system color tokens."},{id:"no-arbitrary-spacing",severity:"error",category:"tokens",standard:null,fix:"No p-[13px]/gap-[7px]; use the token scale / <Flex gap> / <PageContainer>."},{id:"no-arbitrary-size",severity:"error",category:"tokens",standard:null,fix:"No w-[37px]/h-[260px]; use token sizes or a sizing prop (min-w-[\u2026] allowed)."},{id:"no-arbitrary-typography",severity:"error",category:"tokens",standard:null,fix:"No text-[20px]/leading-[1.7]; use the golden-ratio type-scale tokens."},{id:"no-arbitrary-radius",severity:"error",category:"tokens",standard:null,fix:"No rounded-[6px]; use rounded-sm/md/lg radius tokens."},{id:"no-dark-color-override",severity:"warn",category:"tokens",standard:null,fix:"Drop dark: color overrides \u2014 semantic tokens already adapt."},{id:"raw-white-black",severity:"warn",category:"tokens",standard:null,fix:"Prefer semantic tokens (text-primary-foreground, bg-background) over raw white/black."},{id:"no-domain-tracking-token",severity:"error",category:"tokens",standard:null,fix:"No package-tracking/domain tokens; use semantic tokens or app theme overrides."},{id:"no-space-xy",severity:"error",category:"tokens",standard:null,fix:"Use <Flex gap> instead of space-x/y-*."},{id:"no-raw-select",severity:"error",category:"composition",standard:"HTML Living Standard (WHATWG)",fix:"Use <Select> from @godxjp/ui, not a raw <select>."},{id:"no-raw-table",severity:"error",category:"composition",standard:"HTML Living Standard (WHATWG)",fix:"Use the <Table>/<DataTable> family, not a raw <table>."},{id:"no-raw-input",severity:"error",category:"composition",standard:"HTML Living Standard (WHATWG)",fix:"Use <Input> from @godxjp/ui, not a raw <input>."},{id:"no-raw-textarea",severity:"warn",category:"composition",standard:"HTML Living Standard (WHATWG)",fix:"Use <Textarea> from @godxjp/ui, not a raw <textarea>."},{id:"no-raw-button",severity:"error",category:"composition",standard:"HTML Living Standard (WHATWG)",fix:"Use <Button> from @godxjp/ui, not a raw <button>."},{id:"card-manual-padding",severity:"error",category:"composition",standard:null,fix:"Wrap the body in <CardContent>; don't hand-roll padding on <Card>."},{id:"card-needs-content",severity:"error",category:"composition",standard:null,fix:"<Card> body must be in <CardContent> (no padding otherwise); flush only for a full-bleed table."},{id:"bare-control-needs-formfield",severity:"warn",category:"composition",standard:"WCAG 2.2 SC 1.3.1 \xB7 3.3.2 \xB7 @godxjp/ui FormField (cardinal rule 227)",fix:"Wrap a labelled control in <FormField label=\u2026> \u2014 it owns label\u2194control id wiring, aria/error, AND the field rhythm; never pair a bare <Label> with an <Input>."},{id:"manual-field-error",severity:"warn",category:"composition",standard:"WCAG 2.2 SC 3.3.1",fix:"Use <FormField error=\u2026>, not a hand-rolled <p class='text-destructive'>."},{id:"manual-field-helper",severity:"warn",category:"composition",standard:null,fix:"Use <FormField helper=\u2026>, not a hand-rolled helper <p>."},{id:"status-tone-not-variant",severity:"error",category:"api",standard:null,fix:"Badge/Tag/StatCard status uses tone, not variant (variant is structural)."},{id:"value-callback-on-value-change",severity:"error",category:"api",standard:null,fix:"Abstract value components use onValueChange, not onChange."},{id:"icon-button-needs-name",severity:"warn",category:"a11y",standard:"WCAG 2.2 SC 4.1.2 \xB7 1.1.1 \xB7 WAI-ARIA 1.2",fix:"Add aria-label={t('\u2026')} to <Button size='icon'>; the glyph is aria-hidden."},{id:"img-needs-alt",severity:"warn",category:"a11y",standard:"WCAG 2.2 SC 1.1.1 \xB7 HTML Living Standard",fix:"Add alt to every <img> (alt='' if decorative); prefer <Avatar>/<AspectRatio>."},{id:"no-positive-tabindex",severity:"warn",category:"a11y",standard:"WCAG 2.2 SC 2.4.3 \xB7 WAI-ARIA APG",fix:"Use tabIndex 0 or -1 only; never positive \u2014 it breaks focus order."},{id:"hand-rolled-close-glyph",severity:"warn",category:"a11y",standard:"WAI-ARIA 1.2 (dialog) \xB7 WCAG 2.2 SC 4.1.2",fix:"Pass onDismiss to <Alert>, or use <Dialog>/<Sheet>'s built-in labelled close \u2014 not a bare \u2715."},{id:"no-emoji-in-ui",severity:"warn",category:"i18n",standard:"Unicode UTS #51 \xB7 WCAG 2.2 SC 1.1.1",fix:"No emoji in product UI; quiet i18n copy + Lucide icon + Badge tone."},{id:"no-emoji-flag",severity:"warn",category:"i18n",standard:"ISO 3166-1 \xB7 ECMA-402 Intl.DisplayNames \xB7 Unicode UTS #51",fix:"Derive country names from Intl.DisplayNames; no emoji flags."},{id:"hardcoded-currency",severity:"warn",category:"i18n",standard:"ISO 4217 \xB7 ECMA-402 Intl.NumberFormat",fix:"Format money with Intl.NumberFormat({ style: 'currency', currency }), not \xA5{amount}."},{id:"raw-intl-date",severity:"warn",category:"i18n",standard:"ISO 8601 \xB7 IANA tz \xB7 ECMA-402 Intl.DateTimeFormat",fix:"Use formatDate from @godxjp/ui/datetime, not hand-built or locale-default dates."},{id:"no-physical-direction",severity:"warn",category:"rtl",standard:"W3C CSS Logical Properties L1 \xB7 WCAG 2.2 (1.3.2)",fix:"Use logical utilities (ms-/me-/ps-/pe-, start-/end-, text-start/end, border-s/e, rounded-s/e)."},{id:"no-em-dash-in-copy",severity:"warn",category:"copy",standard:"@godxjp/ui reference-design typography",fix:"No em-dash (\u2014) in copy; use a middot \xB7 or two calm sentences."}];function Y(t){return t?$.filter(a=>a.category===t):$}var X="node node_modules/@godxjp/ui/scripts/visual-audit.mjs <baseUrl> [route \u2026] (optional peers, TESTED range: playwright >=1.55 <2 [1.61.1] + @axe-core/playwright >=4.10 <5 [4.12.1] + axe-core >=4.10 <5 [4.12.1] + a chromium via `playwright install chromium`; --strict for a CI gate, --format json ALWAYS emits valid JSON with a status of ok|partial|error separating infra errors[] from product findings[], --rules to print this catalog)",Q=[{id:"axe-violations",severity:"warn",category:"a11y",standard:"WCAG 2.2 A/AA \xB7 WAI-ARIA 1.2 (axe-core engine)",fix:"Fix each axe node \u2014 contrast (1.4.3), name/role/value (4.1.2), ARIA, landmarks. Runs on the REAL DOM, catching what static analysis cannot."},{id:"target-size-min",severity:"warn",category:"a11y",standard:"WCAG 2.2 SC 2.5.8 (24\xD724 AA) \xB7 2.5.5 (44\xD744 AAA)",fix:"Interactive targets must be \u226524\xD724 CSS px; size from the --control-height tier."},{id:"oversaturated-accent",severity:"warn",category:"color",standard:"@godxjp/ui reference-design \u6E0B\u307F (OKLCH chroma \u2264 0.18)",fix:"Desaturate brand/primary surfaces (OKLCH chroma \u2264 0.18); read --primary tokens, no raw vivid bars."},{id:"emoji-rendered",severity:"warn",category:"i18n",standard:"Unicode UTS #51 \xB7 WCAG 2.2 SC 1.1.1",fix:"Remove emoji from rendered product text; quiet i18n copy + Lucide icon + Badge tone."},{id:"alert-controls-misplaced",severity:"warn",category:"layout",standard:"@godxjp/ui Alert anatomy \xB7 WAI-ARIA 1.2 \xB7 WCAG 2.2 SC 4.1.2",fix:"Use <Alert>: one leading tone icon, <Alert.Actions> trailing-right normal width, onDismiss \xD7 top-right, one horizontal row."}];function J(t){return t?Q.filter(a=>a.category===t):Q}var m={name:"@godxjp/ui-mcp",version:"18.6.0",godxUiCompatibility:"18.6.x",description:"Model Context Protocol server for @godxjp/ui \u2014 gives Claude Code / Codex CLI / Cursor / any MCP-aware agent live access to the component catalog, prop vocabulary, design tokens, 45 cardinal rules, copy-paste-ready patterns, 12 design / taste skills synthesised from Leonxlnx/taste-skill, 20+ anti-AI-tell patterns, and a 50-check redesign audit \u2014 token-efficient (list \u2192 drill-down).",type:"module",main:"./dist/index.js",module:"./dist/index.js",types:"./dist/index.d.ts",bin:{"godx-ui-mcp":"./dist/index.js"},files:["dist","README.md"],publishConfig:{registry:"https://registry.npmjs.org/",access:"public"},repository:{type:"git",url:"git+https://github.com/godx-jp/godxjp-ui.git",directory:"mcp"},homepage:"https://github.com/godx-jp/godxjp-ui/tree/main/mcp#readme",license:"Apache-2.0",scripts:{build:"tsup",dev:"tsup --watch",start:"node dist/index.js",inspect:"npx @modelcontextprotocol/inspector node dist/index.js","type-check":"tsc --noEmit",test:"vitest run",prepublishOnly:"npm run build"},dependencies:{"@modelcontextprotocol/sdk":"^1.29.0",zod:"^4.4.3"},devDependencies:{"@types/node":"^22.10.0",tsup:"^8.5.1",typescript:"^6.0.3",vitest:"^4.1.6"},keywords:["mcp","model-context-protocol","godxjp","ui","design-system","react","claude","cursor"]};var ee=[{name:"list_skills",description:"List every design/taste skill bundled by this MCP (id + name + whenToUse + section ids). Use FIRST to discover skills; then `get_skill_section` to drill in.",inputSchema:{type:"object",properties:{}}},{name:"list_primitives",description:"List every @godxjp/ui primitive/composite/shell (group + tagline per entry). Optionally filter by group. Then `get_component` for one's full API.",inputSchema:{type:"object",properties:{group:{type:"string",enum:["general","layout","data-display","data-entry","feedback","navigation","composites","shell","providers"]}}}},{name:"list_patterns",description:"List every canonical copy-paste code pattern (signup-form, settings-page, data-table-page, async-data-state, confirm-destructive, \u2026); common aliases resolve too. Use before `get_pattern`.",inputSchema:{type:"object",properties:{}}},{name:"list_anti_ai_tells",description:"List every AI-tell pattern to AVOID (optionally by category). Use to self-audit a design before shipping; then `get_anti_ai_tell` for the fix.",inputSchema:{type:"object",properties:{category:{type:"string",enum:["visual","layout","copy","interaction","imagery","structure"]}}}},{name:"list_redesign_checks",description:"List the redesign audit checklist (50+ checks; optionally by category). Use when auditing an existing project; then `get_redesign_check` for a symptom's fix.",inputSchema:{type:"object",properties:{category:{type:"string",enum:["typography","color-surface","layout","interactivity","content","components","iconography","code-quality","omissions"]}}}},{name:"list_audit_rules",description:"List the LOCAL static ui-audit rules (scripts/ui-audit.mjs) to run BEFORE any visual review \u2014 each cites the standard it enforces (WCAG/WAI-ARIA/Intl/ISO/IANA/CSS-Logical) + a fix + the run command. Optionally by category.",inputSchema:{type:"object",properties:{category:{type:"string",enum:["tokens","composition","api","a11y","i18n","rtl","copy"]}}}},{name:"list_visual_checks",description:"List the RUNTIME visual-audit checks (scripts/visual-audit.mjs \u2014 Playwright + axe-core) to run against the RUNNING app: contrast/ARIA (axe), target size, rendered-accent chroma, DOM emoji, banner layout. Needs a browser (vs list_audit_rules, static). Optionally by category.",inputSchema:{type:"object",properties:{category:{type:"string",enum:["a11y","color","i18n","layout"]}}}},{name:"get_anti_ai_tell",description:"Fetch ONE anti-AI-tell \u2014 full body + concrete fix. Use after `list_anti_ai_tells`.",inputSchema:{type:"object",properties:{name:{type:"string",description:"Exact tell name from list_anti_ai_tells."}},required:["name"]}},{name:"get_redesign_check",description:"Fetch redesign check(s) matching a symptom snippet. Returns full fix + UI note. Use after `list_redesign_checks`.",inputSchema:{type:"object",properties:{symptom:{type:"string",description:"Fragment of the symptom text (e.g. 'Inter everywhere' / '100vh')."}},required:["symptom"]}},{name:"get_skill_section",description:"Fetch ONE section of ONE skill \u2014 token-efficient. E.g. `skill='soft', section='double-bezel'`. Use after `list_skills` narrowed the relevant skill + section.",inputSchema:{type:"object",properties:{skill:{type:"string",description:"Skill id (e.g. 'soft', 'minimalist', 'taste')."},section:{type:"string",description:"Section id within that skill."}},required:["skill","section"]}},{name:"get_component",description:"Full guide for one @godxjp/ui component \u2014 import path, props/types/defaults, HOW to use it (DO/DON'T), WHEN to reach for it (use cases), related components (don't reinvent/confuse), a copy-paste example, story path, and cardinal rules. Use this before hand-rolling anything. Design-token knobs are listed compactly (name+default); pass `verbose:true` for what each token controls.",inputSchema:{type:"object",properties:{name:{type:"string",description:"Component name (e.g. 'Button', 'DataTable')."},verbose:{type:"boolean",description:"Include the full design-token table with a 'what it controls' description per token. Default false (compact token+default only) to save context."}},required:["name"]}},{name:"get_pattern",description:"Full code snippet for one canonical pattern \u2014 copy-paste-ready.",inputSchema:{type:"object",properties:{name:{type:"string",description:"Pattern slug (use list_patterns first)."}},required:["name"]}},{name:"get_rule",description:"Read one cardinal rule from CLAUDE.md (by number) OR all if no number.",inputSchema:{type:"object",properties:{number:{type:"number",description:"Rule number (1-N)."}}}},{name:"get_vocab",description:"Read shared prop-vocabulary type (`SizeProp`, `StatusProp`, `ColorProp`, `LoadingProp`, etc.) OR all if no name.",inputSchema:{type:"object",properties:{name:{type:"string",description:"Vocab type name."}}}},{name:"get_tokens",description:"Read design tokens, optionally filtered by tier category (primitive / semantic / component).",inputSchema:{type:"object",properties:{category:{type:"string",enum:["primitive","semantic","component"]}}}},{name:"list_consumer_skills",description:"List the design skills relevant to an app-dev BUILDING WITH @godxjp/ui (audience consumer/both). Hides core library-maintenance skills. START HERE if you import @godxjp/ui and want guidance (design-to-page, compose-a-screen, taste, \u2026). Returns id + name + whenToUse + section ids.",inputSchema:{type:"object",properties:{}}},{name:"get_consumer_skill",description:"Fetch ONE section of ONE consumer-facing skill. Same as get_skill_section but refuses core-only skills (steers app-devs away from library-maintenance material). Use after list_consumer_skills / route_consumer_task.",inputSchema:{type:"object",properties:{skill:{type:"string",description:"Consumer skill id (e.g. 'design-to-page', 'compose-a-screen')."},section:{type:"string",description:"Section id within that skill."}},required:["skill"]}},{name:"route_consumer_task",description:"Natural-language task \u2192 consumer skill+section pointer. Like route_task but only points to consumer-facing skills (never core library-maintenance). Use FIRST when you're building an app with @godxjp/ui.",inputSchema:{type:"object",properties:{task:{type:"string",description:"Describe what you want to build."}},required:["task"]}},{name:"draft_bug_report",description:"When @godxjp/ui ITSELF is at fault (missing token, a primitive lacking the controlled-vocabulary prop, a real a11y/behaviour bug, a wrong catalog example) and you cannot follow a rule \u2014 DON'T fake a workaround. This drafts a detailed GitHub issue body + a copy-paste `gh issue create` command so you can report it. Prints the command only; never runs gh.",inputSchema:{type:"object",properties:{summary:{type:"string",description:"One-line title of the bug / blocked rule."},repro:{type:"string",description:"Minimal steps or code to reproduce."},expected:{type:"string",description:"What SHOULD happen (per the rule/spec)."},actual:{type:"string",description:"What actually happens."},component:{type:"string",description:"Affected component name, if any (links to get_component)."},rule:{type:"number",description:"Cardinal rule number that can't be followed, if any."},version:{type:"string",description:"Installed @godxjp/ui version (e.g. '12.1.0')."},env:{type:"string",description:"Environment (browser/OS/framework), if relevant."}},required:["summary"]}},{name:"check_compatibility",description:"Report whether the @godxjp/ui version installed in the target project matches THIS catalog (which describes one release train). A mismatched minor means the props/tokens/patterns may describe a build they never installed (#140). Pass the installed version (`npm ls @godxjp/ui`); call it at the START of a consumer session.",inputSchema:{type:"object",properties:{version:{type:"string",description:"Installed @godxjp/ui version in the target project, e.g. '16.10.0'. Omit to just read the catalog's own version + compatible range."}}}},{name:"route_task",description:"Natural-language task \u2192 skill+section pointer (e.g. 'design a premium agency hero' \u2192 soft/vibe-archetypes). Use FIRST when you don't know which skill applies.",inputSchema:{type:"object",properties:{task:{type:"string",description:"Describe what you want to build."}},required:["task"]}},{name:"suggest_primitive",description:"Use case \u2192 primitive recommendation. E.g. 'confirm a destructive delete' \u2192 DangerZone pattern + Dialog suggestion.",inputSchema:{type:"object",properties:{use_case:{type:"string"}},required:["use_case"]}},{name:"search_components",description:"Fuzzy-search primitives by name / tagline / prop. Returns ranked matches.",inputSchema:{type:"object",properties:{query:{type:"string"}},required:["query"]}},{name:"get_frame_coverage",description:"Verified preview-contract coverage for a component (issue #163). Answers 'is this state actually PROVEN?' \u2014 returns, per contract dimension (variants, tones, sizes, shapes, density, controlled/uncontrolled ownership, disabled/read-only/loading/empty/error/success, async retry/cancel/offline, responsive viewport matrix, RTL, long/localized content, keyboard/focus, accessible name/description/error, reduced motion / coarse touch), whether an EXECUTED case proves it (covered), whether nothing proves it (UNTESTED), or whether it cannot exist (not-applicable, with a reason). UNTESTED IS NOT A PASS: never infer that a component supports a state because an example renders. Call this before telling a user a component 'supports' anything. Omit `name` for the repo-wide summary and the tracked known gaps.",inputSchema:{type:"object",properties:{name:{type:"string",description:"Public export name (e.g. 'Button', 'DataTable', 'CardFooter'). Omit for the repo-wide coverage summary."}}}},{name:"lint_jsx",description:"Heuristic check of a JSX snippet for common violations \u2014 raw `<button>` / `<input>`, `color='error'` on Tag/Badge, missing aria-label, missing source.code override on stories with cell renderers (rule 34), etc.",inputSchema:{type:"object",properties:{jsx:{type:"string"}},required:["jsx"]}}];async function te(t,a){switch(t){case"list_skills":return ce();case"list_primitives":return ae(a.group);case"list_patterns":return ge();case"list_anti_ai_tells":return fe(a.category);case"list_redesign_checks":return we(a.category);case"list_audit_rules":return ye(a.category);case"list_visual_checks":return be(a.category);case"get_anti_ai_tell":return ve(String(a.name??""));case"get_redesign_check":return ke(String(a.symptom??""));case"get_skill_section":return oe(String(a.skill??""),String(a.section??""));case"get_component":return Ce(String(a.name??""),a.verbose===!0);case"get_pattern":return De(String(a.name??""));case"get_rule":return Ne(typeof a.number=="number"?a.number:void 0);case"get_vocab":return Oe(a.name==null?void 0:String(a.name));case"get_tokens":return Ie(a.category);case"list_consumer_skills":return pe();case"get_consumer_skill":return he(String(a.skill??""),String(a.section??""));case"route_consumer_task":return Z(String(a.task??""),{consumerOnly:!0});case"draft_bug_report":return ue(a);case"check_compatibility":return me(a.version==null?void 0:String(a.version));case"route_task":return Z(String(a.task??""));case"suggest_primitive":return Pe(String(a.use_case??""));case"search_components":return Re(String(a.query??""));case"get_frame_coverage":return Ae(a.name===void 0?void 0:String(a.name));case"lint_jsx":return Ee(String(a.jsx??""));default:return`Unknown tool: ${t}`}}function ce(){let t=`# Available skills (${x.length})
|
|
2983
3692
|
|
|
2984
|
-
`;
|
|
2985
|
-
`,
|
|
3693
|
+
`;t+="Each is tagged `[audience]` \u2014 `core` = building @godxjp/ui itself, `consumer` = building an app with it, `both`. App-devs: use `list_consumer_skills` to hide core material.\n\n",t+='Use `get_skill_section skill="..." section="..."` to drill in.\n\n';for(let a of x)t+=`## ${a.id} \u2014 ${a.name} \`[${a.audience}]\`
|
|
3694
|
+
`,t+=`**When to use:** ${a.whenToUse}
|
|
2986
3695
|
|
|
2987
|
-
`,
|
|
3696
|
+
`,t+=`**Sections:** ${a.sections.map(e=>`\`${e.id}\``).join(", ")}
|
|
2988
3697
|
|
|
2989
|
-
`;return
|
|
2990
|
-
_Source: ${
|
|
3698
|
+
`;return t+=`
|
|
3699
|
+
_Source: ${x.map(a=>a.source).filter((a,e,o)=>o.indexOf(a)===e).slice(0,3).join("; ")}, \u2026_`,t}function pe(){let t=x.filter(M),a=`# Consumer skills (${t.length}) \u2014 building an app WITH @godxjp/ui
|
|
2991
3700
|
|
|
2992
|
-
`;
|
|
2993
|
-
`,
|
|
3701
|
+
`;a+='Core library-maintenance skills are hidden. Drill in with `get_consumer_skill skill="..." section="..."`, or route a task with `route_consumer_task`.\n\n';for(let e of t)a+=`## ${e.id} \u2014 ${e.name} \`[${e.audience}]\`
|
|
3702
|
+
`,a+=`**When to use:** ${e.whenToUse}
|
|
2994
3703
|
|
|
2995
|
-
`,
|
|
3704
|
+
`,a+=`**Sections:** ${e.sections.map(o=>`\`${o.id}\``).join(", ")}
|
|
2996
3705
|
|
|
2997
|
-
`;return
|
|
3706
|
+
`;return a}function he(t,a){let e=A(t);return e&&!M(e)?`Skill "${t}" is CORE-only (building @godxjp/ui itself) and isn't served to app-devs. Use \`list_consumer_skills\` for consumer-facing guidance${t==="component-discipline"?" \u2014 the standards you need when composing/extending are folded into `compose-a-screen/state-and-a11y` and `design-to-page/verify`.":"."}`:oe(t,a)}function ue(t){let a=f=>{let S=t[f];return typeof S=="string"?S.trim():""},e=a("summary");if(!e)return"Pass at least `summary` (one-line title). For a useful report also pass `repro`, `expected`, `actual`, and ideally `component` / `rule` / `version` / `env`. A vague report a maintainer can't reproduce is not enough.";let o=a("repro"),r=a("expected"),i=a("actual"),n=a("component"),s=typeof t.rule=="number"?t.rule:void 0,g=a("version"),h=a("env"),d=[];o||d.push("repro"),r||d.push("expected"),i||d.push("actual");let y=(f,S)=>S||`_(TODO: ${f} \u2014 required for a reproducible report)_`,N=`[bug] ${e}`,l=`## Summary
|
|
2998
3707
|
|
|
2999
3708
|
${e}
|
|
3000
3709
|
|
|
3001
3710
|
`;l+=`## Affected
|
|
3002
3711
|
|
|
3003
|
-
`,l+=
|
|
3712
|
+
`,l+=n?`- Component: \`${n}\` (see \`get_component name="${n}"\`)
|
|
3004
3713
|
`:`- Component: _(n/a)_
|
|
3005
3714
|
`,l+=s!==void 0?`- Cardinal rule: #${s} (see \`get_rule number=${s}\`)
|
|
3006
3715
|
`:"",l+=`
|
|
3007
3716
|
## Reproduction (minimal)
|
|
3008
3717
|
|
|
3009
|
-
${
|
|
3718
|
+
${y("repro",o)}
|
|
3010
3719
|
|
|
3011
3720
|
`,l+=`## Expected
|
|
3012
3721
|
|
|
3013
|
-
${
|
|
3722
|
+
${y("expected",r)}
|
|
3014
3723
|
|
|
3015
3724
|
`,l+=`## Actual
|
|
3016
3725
|
|
|
3017
|
-
${
|
|
3726
|
+
${y("actual",i)}
|
|
3018
3727
|
|
|
3019
3728
|
`,l+=`## Environment
|
|
3020
3729
|
|
|
3021
|
-
`,l+=`- @godxjp/ui version: ${
|
|
3022
|
-
`,l+=`- Env: ${
|
|
3730
|
+
`,l+=`- @godxjp/ui version: ${g||"_(TODO: e.g. 12.1.0)_"}
|
|
3731
|
+
`,l+=`- Env: ${h||"_(TODO: browser / OS / framework)_"}
|
|
3023
3732
|
|
|
3024
3733
|
`,l+=`## Proposed fix
|
|
3025
3734
|
|
|
3026
3735
|
_(optional \u2014 what the library should do instead)_
|
|
3027
|
-
`;let c=
|
|
3736
|
+
`;let c=f=>`'${f.replace(/'/g,"'\\''")}'`,E=`gh issue create --repo godx-jp/godxjp-ui --label bug --title ${c(N)} --body ${c(l)}`,w=`# Draft bug report
|
|
3028
3737
|
|
|
3029
|
-
`;return d.length&&(
|
|
3738
|
+
`;return d.length&&(w+=`> \u26A0\uFE0F Incomplete \u2014 fill ${d.map(f=>`\`${f}\``).join(", ")} before filing (a report a maintainer can't reproduce will bounce).
|
|
3030
3739
|
|
|
3031
|
-
`),
|
|
3740
|
+
`),w+=`**Title:** ${N}
|
|
3032
3741
|
|
|
3033
|
-
`,
|
|
3742
|
+
`,w+=`## Issue body (Markdown)
|
|
3034
3743
|
|
|
3035
3744
|
${l}
|
|
3036
|
-
`,
|
|
3745
|
+
`,w+=`## File it (copy-paste \u2014 this tool does NOT run gh)
|
|
3037
3746
|
|
|
3038
3747
|
\`\`\`sh
|
|
3039
|
-
${
|
|
3748
|
+
${E}
|
|
3040
3749
|
\`\`\`
|
|
3041
3750
|
|
|
3042
|
-
`,
|
|
3751
|
+
`,w+="_Reminder: don't hand-roll a fake workaround to hide the bug \u2014 file this, then mark any minimal local workaround with `// TODO(godxui#<n>)`._\n",w}function me(t){let a=m.version,e=m.godxUiCompatibility,o=`# @godxjp/ui compatibility
|
|
3043
3752
|
|
|
3044
|
-
- MCP / catalog version (serverInfo.version): **${
|
|
3753
|
+
- MCP / catalog version (serverInfo.version): **${a}**
|
|
3045
3754
|
- Compatible @godxjp/ui range: **${e??"(unset)"}**
|
|
3046
|
-
`,
|
|
3047
|
-
\u26A0\uFE0F Could not compare \u2014 installed="${
|
|
3048
|
-
`:(
|
|
3049
|
-
\u2705 In lockstep \u2014 installed @godxjp/ui@${
|
|
3755
|
+
`,r=t?.trim();if(!r)return o+'\nProvide the installed version \u2014 `check_compatibility version="16.10.0"` \u2014 to get a match/mismatch verdict. Read it from the target project\'s `node_modules/@godxjp/ui/package.json` or `npm ls @godxjp/ui`.\n';let i=h=>{let d=/^(\d+)\.(\d+)\.(\d+)/.exec(h);return d?{major:d[1],minor:d[2]}:null},n=e?/^(\d+)\.(\d+)\.x$/.exec(e):null,s=i(r);return!s||e&&!n?o+`
|
|
3756
|
+
\u26A0\uFE0F Could not compare \u2014 installed="${r}" is not a plain x.y.z version, or the catalog range is malformed. Verify with \`npm ls @godxjp/ui\`.
|
|
3757
|
+
`:(n?s.major===n[1]&&s.minor===n[2]:r===a)?o+`
|
|
3758
|
+
\u2705 In lockstep \u2014 installed @godxjp/ui@${r} is described by this catalog. Prop / token / pattern guidance is safe to trust.
|
|
3050
3759
|
`:o+`
|
|
3051
|
-
\u{1F534} MISMATCH \u2014 the target project runs @godxjp/ui@${
|
|
3052
|
-
- upgrade the app: \`npm i @godxjp/ui@${
|
|
3760
|
+
\u{1F534} MISMATCH \u2014 the target project runs @godxjp/ui@${r}, but this catalog describes ${e??a}. Props, defaults, tokens, and patterns it returns may not match the installed build. Align them before trusting output:
|
|
3761
|
+
- upgrade the app: \`npm i @godxjp/ui@${a}\`, or
|
|
3053
3762
|
- point your agent at the matching MCP release: \`@godxjp/ui-mcp@${s.major}.${s.minor}\` (same minor as the app).
|
|
3054
|
-
`}function
|
|
3763
|
+
`}function ae(t){let a=t?F(t):v;if(a.length===0)return`No components${t?` in group "${t}"`:""}.`;let e=a.reduce((r,i)=>((r[i.group]??=[]).push(i),r),{}),o=`# @godxjp/ui primitives${t?` \u2014 ${t}`:""}
|
|
3055
3764
|
|
|
3056
|
-
${
|
|
3765
|
+
${a.length} components.
|
|
3057
3766
|
|
|
3058
|
-
`;for(let[
|
|
3767
|
+
`;for(let[r,i]of Object.entries(e)){o+=`## ${r}
|
|
3059
3768
|
|
|
3060
|
-
`;for(let
|
|
3769
|
+
`;for(let n of i)o+=`- **${n.name}** \u2014 ${n.tagline}
|
|
3061
3770
|
`;o+=`
|
|
3062
|
-
`}return o}function
|
|
3771
|
+
`}return o}function ge(){let t=`# Canonical patterns (${b.length})
|
|
3063
3772
|
|
|
3064
|
-
`;for(let
|
|
3065
|
-
_tags: ${
|
|
3066
|
-
`;return
|
|
3773
|
+
`;for(let a of b)t+=`- **${a.name}** \u2014 ${a.tagline}
|
|
3774
|
+
_tags: ${a.tags.join(", ")}_
|
|
3775
|
+
`;return t}function be(t){let a=J(t),e=`# Runtime visual-audit checks${t?` \u2014 ${t}`:""} (${a.length})
|
|
3067
3776
|
|
|
3068
3777
|
`;e+=`_Run against the RUNNING app BEFORE a visual review (warnings, non-blocking). Needs a browser._
|
|
3069
3778
|
`,e+=`\`\`\`
|
|
3070
|
-
${
|
|
3779
|
+
${X}
|
|
3071
3780
|
\`\`\`
|
|
3072
3781
|
|
|
3073
|
-
`,e+="_Static `list_audit_rules` first (cheap, every save) \u2192 THIS (before review) \u2192 human eyes for taste._\n\n";for(let o of
|
|
3782
|
+
`,e+="_Static `list_audit_rules` first (cheap, every save) \u2192 THIS (before review) \u2192 human eyes for taste._\n\n";for(let o of a)e+=`- **${o.id}** (${o.severity}, ${o.category}) \u2014 _${o.standard}_
|
|
3074
3783
|
${o.fix}
|
|
3075
|
-
`;return e}function
|
|
3784
|
+
`;return e}function ye(t){let a=Y(t),e=`# Local UI-audit rules${t?` \u2014 ${t}`:""} (${a.length})
|
|
3076
3785
|
|
|
3077
3786
|
`;e+=`_Run BEFORE any visual review (warnings are agent guidance, non-blocking):_
|
|
3078
3787
|
`,e+=`\`\`\`
|
|
3079
|
-
${
|
|
3788
|
+
${K}
|
|
3080
3789
|
\`\`\`
|
|
3081
3790
|
|
|
3082
|
-
`;let o=
|
|
3083
|
-
`;for(let
|
|
3084
|
-
${
|
|
3791
|
+
`;let o=a.reduce((r,i)=>((r[i.category]??=[]).push(i),r),{});for(let[r,i]of Object.entries(o)){e+=`## ${r}
|
|
3792
|
+
`;for(let n of i)e+=`- **${n.id}** (${n.severity})${n.standard?` \u2014 _${n.standard}_`:""}
|
|
3793
|
+
${n.fix}
|
|
3085
3794
|
`;e+=`
|
|
3086
|
-
`}return e}function
|
|
3795
|
+
`}return e}function fe(t){let a=t?H(t):D,e=`# AI tells to AVOID${t?` \u2014 ${t}`:""} (${a.length})
|
|
3087
3796
|
|
|
3088
|
-
`;e+='_Compact list. Use `get_anti_ai_tell name="<name>"` for the full body + fix._\n\n';let o=
|
|
3089
|
-
`;for(let
|
|
3797
|
+
`;e+='_Compact list. Use `get_anti_ai_tell name="<name>"` for the full body + fix._\n\n';let o=a.reduce((r,i)=>((r[i.category]??=[]).push(i),r),{});for(let[r,i]of Object.entries(o)){e+=`## ${r}
|
|
3798
|
+
`;for(let n of i)e+=`- ${n.name}
|
|
3090
3799
|
`;e+=`
|
|
3091
|
-
`}return e}function
|
|
3800
|
+
`}return e}function ve(t){let a=D.find(e=>e.name.toLowerCase()===t.trim().toLowerCase());if(!a){let e=`Anti-AI-tell "${t}" not found. Use \`list_anti_ai_tells\` to discover. Closest:
|
|
3092
3801
|
|
|
3093
3802
|
`;for(let o of D.slice(0,8))e+=`- ${o.name} (${o.category})
|
|
3094
|
-
`;return e}return`# ${
|
|
3803
|
+
`;return e}return`# ${a.name}
|
|
3095
3804
|
|
|
3096
|
-
**Category:** ${
|
|
3805
|
+
**Category:** ${a.category}
|
|
3097
3806
|
|
|
3098
3807
|
## Symptom
|
|
3099
3808
|
|
|
3100
|
-
${
|
|
3809
|
+
${a.body}
|
|
3101
3810
|
|
|
3102
3811
|
## Fix
|
|
3103
3812
|
|
|
3104
|
-
${
|
|
3105
|
-
`}function
|
|
3813
|
+
${a.fix}
|
|
3814
|
+
`}function we(t){let a=t?_(t):R,e=`# Redesign audit${t?` \u2014 ${t}`:""} (${a.length} checks)
|
|
3106
3815
|
|
|
3107
|
-
`;
|
|
3108
|
-
${
|
|
3816
|
+
`;t||(e+=`## Fix priority
|
|
3817
|
+
${G.map(r=>r).join(`
|
|
3109
3818
|
`)}
|
|
3110
3819
|
|
|
3111
3820
|
`,e+=`## Rules
|
|
3112
|
-
${
|
|
3821
|
+
${W.map(r=>`- ${r}`).join(`
|
|
3113
3822
|
`)}
|
|
3114
3823
|
|
|
3115
|
-
`),e+='_Compact list of symptoms. Use `get_redesign_check symptom="<text snippet>"` for the full fix + UI note._\n\n';let o=
|
|
3116
|
-
`;for(let
|
|
3824
|
+
`),e+='_Compact list of symptoms. Use `get_redesign_check symptom="<text snippet>"` for the full fix + UI note._\n\n';let o=a.reduce((r,i)=>((r[i.category]??=[]).push(i),r),{});for(let[r,i]of Object.entries(o)){e+=`## ${r}
|
|
3825
|
+
`;for(let n of i)e+=`- ${n.symptom}
|
|
3117
3826
|
`;e+=`
|
|
3118
|
-
`}return e}function
|
|
3827
|
+
`}return e}function ke(t){let a=t.trim().toLowerCase();if(!a)return"Pass `symptom` \u2014 a fragment matching the audit check symptom (e.g. 'Inter everywhere' / '100vh' / 'Acme').";let e=R.filter(r=>r.symptom.toLowerCase().includes(a)||r.fix.toLowerCase().includes(a));if(!e.length)return`No redesign check matches "${t}". Use \`list_redesign_checks\` to see all.`;let o=`# Redesign checks matching "${t}" (${e.length})
|
|
3119
3828
|
|
|
3120
|
-
`;for(let
|
|
3829
|
+
`;for(let r of e)o+=`## ${r.category}
|
|
3121
3830
|
|
|
3122
|
-
**Symptom:** ${
|
|
3831
|
+
**Symptom:** ${r.symptom}
|
|
3123
3832
|
|
|
3124
|
-
**Fix:** ${
|
|
3125
|
-
${
|
|
3126
|
-
_UI note:_ ${
|
|
3833
|
+
**Fix:** ${r.fix}
|
|
3834
|
+
${r.uiNote?`
|
|
3835
|
+
_UI note:_ ${r.uiNote}
|
|
3127
3836
|
`:""}
|
|
3128
|
-
`;return o}function
|
|
3837
|
+
`;return o}function oe(t,a){let e=A(t);if(!e)return`Skill "${t}" not found. Use \`list_skills\` for available ids.`;if(!a){let r=`# ${e.name}
|
|
3129
3838
|
|
|
3130
3839
|
${e.whenToUse}
|
|
3131
3840
|
|
|
3132
3841
|
## Sections
|
|
3133
|
-
`;for(let
|
|
3134
|
-
`;return
|
|
3135
|
-
`;for(let
|
|
3136
|
-
`;return
|
|
3842
|
+
`;for(let i of e.sections)r+=`- \`${i.id}\` \u2014 ${i.tagline}
|
|
3843
|
+
`;return r}let o=U(t,a);if(!o){let r=`Section "${a}" not in skill "${t}". Available:
|
|
3844
|
+
`;for(let i of e.sections)r+=`- \`${i.id}\` \u2014 ${i.tagline}
|
|
3845
|
+
`;return r}return`# ${e.name} \u2192 ${o.title}
|
|
3137
3846
|
|
|
3138
3847
|
${o.tagline}
|
|
3139
3848
|
|
|
3140
3849
|
${o.body}
|
|
3141
3850
|
|
|
3142
|
-
_Source: ${e.source}_`}var
|
|
3851
|
+
_Source: ${e.source}_`}var xe={Badge:["badge"],Button:["button"],Toggle:["toggle"],TagInput:["tag-input"],Card:["card"],StatCard:["stat-card"],Table:["table"],DataTable:["table"],Dialog:["dialog"],AlertDialog:["dialog"],Sheet:["dialog"],Drawer:["dialog"],Alert:["alert"],EmptyState:["empty-state"],Skeleton:["skeleton"],Pagination:["pagination"],Toolbar:["filter"],Breadcrumb:["breadcrumb"],Menubar:["menubar"],Progress:["progress"],TreeSelect:["tree"],Timeline:["timeline"],PasswordStrength:["password-strength"],PasswordInput:["password-strength"],Checkbox:["checkbox"],Switch:["switch"],Slider:["slider"],ColorPicker:["color-picker"],Command:["command"],Radio:["choice"],RadioGroup:["choice"],Field:["choice"],AppShell:["app-shell","sidebar","topbar"],Sidebar:["sidebar"],Topbar:["topbar"],Form:["form"],FormField:["form"],Input:["control"],Textarea:["control"],NumberInput:["control"],Select:["control","search-input"],Cascader:["control"],DatePicker:["control"],TimePicker:["control"],InputOTP:["control"]};function Se(t){let a=xe[t]??[t.replace(/([a-z0-9])([A-Z])/g,"$1-$2").toLowerCase()];return V.filter(e=>a.some(o=>e.name.startsWith(`--${o}-`)))}function Ce(t,a=!1){let e=O(t);if(!e)return`Component "${t}" not found. Use \`list_primitives\` to discover.`;let o=`# ${e.name}
|
|
3143
3852
|
|
|
3144
|
-
**Group:** ${e.group}`,
|
|
3853
|
+
**Group:** ${e.group}`,r=e.importPath??`@godxjp/ui/${e.group==="providers"?"app":e.group}`;o+=` \xB7 **Import:** \`import { ${e.name} } from "${r}"\`
|
|
3145
3854
|
|
|
3146
3855
|
`,e.deprecated&&(o+=`> \u26A0\uFE0F **DEPRECATED.** Kept catalogued so you're steered to the replacement \u2014 see the tagline / Related below. Do not use in new code.
|
|
3147
3856
|
|
|
@@ -3151,28 +3860,28 @@ _Source: ${e.source}_`}var be={Badge:["badge"],Button:["button"],Toggle:["toggle
|
|
|
3151
3860
|
|
|
3152
3861
|
`,o+=`| Name | Type | Required | Default | Description |
|
|
3153
3862
|
|---|---|---|---|---|
|
|
3154
|
-
`;for(let
|
|
3155
|
-
`;let
|
|
3863
|
+
`;for(let n of e.props)o+=`| \`${n.name}\` | \`${n.type}\` | ${n.required?"\u2713":""} | ${n.defaultValue?`\`${n.defaultValue}\``:""} | ${n.description} |
|
|
3864
|
+
`;let i=Se(e.name);if(i.length)if(o+=`
|
|
3156
3865
|
## Design tokens (theme knobs)
|
|
3157
3866
|
|
|
3158
3867
|
Override these in a service \`theme.css\` to re-tune ONLY this component (never hard-code or fork CSS \u2014 rules #44/#45/#46):
|
|
3159
3868
|
|
|
3160
|
-
`,
|
|
3869
|
+
`,a){o+=`| Token | Default | What it controls |
|
|
3161
3870
|
|---|---|---|
|
|
3162
|
-
`;for(let
|
|
3871
|
+
`;for(let n of i)o+=`| \`${n.name}\` | \`${n.value}\` | ${n.description} |
|
|
3163
3872
|
`}else{o+=`| Token | Default |
|
|
3164
3873
|
|---|---|
|
|
3165
|
-
`;for(let
|
|
3874
|
+
`;for(let n of i)o+=`| \`${n.name}\` | \`${n.value}\` |
|
|
3166
3875
|
`;o+=`
|
|
3167
|
-
_${
|
|
3876
|
+
_${i.length} knob${i.length>1?"s":""}. G\u1ECDi \`get_component name="${e.name}" verbose=true\` \u0111\u1EC3 xem m\u1ED7i token \u0111i\u1EC1u khi\u1EC3n g\xEC._
|
|
3168
3877
|
`}if(e.usage&&e.usage.length){o+=`
|
|
3169
3878
|
## How to use it
|
|
3170
3879
|
|
|
3171
|
-
`;for(let
|
|
3880
|
+
`;for(let n of e.usage)o+=`- ${n}
|
|
3172
3881
|
`}if(e.useCases&&e.useCases.length){o+=`
|
|
3173
3882
|
## When to reach for it (use cases)
|
|
3174
3883
|
|
|
3175
|
-
`;for(let
|
|
3884
|
+
`;for(let n of e.useCases)o+=`- ${n}
|
|
3176
3885
|
`}if(o+=`
|
|
3177
3886
|
## Example
|
|
3178
3887
|
|
|
@@ -3180,89 +3889,128 @@ _${r.length} knob${r.length>1?"s":""}. G\u1ECDi \`get_component name="${e.name}"
|
|
|
3180
3889
|
${e.example}
|
|
3181
3890
|
\`\`\`
|
|
3182
3891
|
|
|
3183
|
-
`,e.related&&e.related.length){o+=`## Related \u2014 don't confuse / don't reinvent
|
|
3892
|
+
`,o+=re(L(e.name),e.name),e.related&&e.related.length){o+=`## Related \u2014 don't confuse / don't reinvent
|
|
3184
3893
|
|
|
3185
|
-
`;for(let
|
|
3894
|
+
`;for(let n of e.related)o+=`- ${n}
|
|
3186
3895
|
`;o+=`
|
|
3187
3896
|
`}if(e.docPath&&(o+=`**Reference doc:** \`docs/reference/${e.docPath}\`
|
|
3188
3897
|
|
|
3189
3898
|
`),o+=`**Storybook:** \`src/stories/${e.storyPath}\`
|
|
3190
3899
|
|
|
3191
3900
|
`,e.rules.length){o+=`**Cardinal rules:**
|
|
3192
|
-
`;for(let
|
|
3193
|
-
`:`- #${
|
|
3194
|
-
`}}return o}
|
|
3195
|
-
|
|
3196
|
-
`;return o}return`# Pattern: ${t.name}
|
|
3901
|
+
`;for(let n of e.rules){let s=T(n);o+=s?`- #${n} \u2014 ${s.title}
|
|
3902
|
+
`:`- #${n}
|
|
3903
|
+
`}}return o}var Te=new Map(p.dimensions.map(t=>[t.id,t.title]));function re(t,a){if(!t)return`
|
|
3904
|
+
## Verified contract coverage (issue #163)
|
|
3197
3905
|
|
|
3198
|
-
|
|
3906
|
+
**UNTESTED.** \`${a}\` has no entry in the frame-coverage ledger, so NOTHING about its props, states, responsive behaviour, RTL, keyboard or async lifecycle is proven by an executed case. Treat the example above as an illustration, never as evidence of support.
|
|
3907
|
+
|
|
3908
|
+
`;let e=i=>i.map(n=>Te.get(n)??n).join(", "),o=`
|
|
3909
|
+
## Verified contract coverage (issue #163)
|
|
3910
|
+
|
|
3911
|
+
`;o+=`> ${p.warning}
|
|
3912
|
+
|
|
3913
|
+
`,o+=t.frame?`Frame: \`${t.frame}\`
|
|
3914
|
+
|
|
3915
|
+
`:"**No `/frame/**` route exists for this export yet.**\n\n",o+=t.covered.length?`- \u2705 **covered** (an executed case proves it): ${e(t.covered)}
|
|
3916
|
+
`:`- \u2705 **covered:** none
|
|
3917
|
+
`,o+=t.untested.length?`- \u26A0\uFE0F **UNTESTED \u2014 not a pass, no executed case:** ${e(t.untested)}
|
|
3918
|
+
`:`- \u26A0\uFE0F **UNTESTED:** none
|
|
3919
|
+
`,t.notApplicable.length&&(o+=`- \u2796 **not applicable** (no such axis in this export's public API): ${e(t.notApplicable)}
|
|
3920
|
+
`);let r=p.knownGaps.filter(i=>i.targets.includes(t.name));for(let i of r)o+=`- \u{1F50E} **tracked known gap** \`${i.id}\`: ${i.cases.join(" \xB7 ")}
|
|
3921
|
+
`;return o+="\nSee `preview/frame-coverage.ledger.json` and `docs/FRAME-COVERAGE-LEDGER.md` for the written reason behind every cell.\n\n",o}function Ae(t){let a=p.totals;if(!t||!t.trim()){let o=`# Preview contract coverage (issue #163)
|
|
3922
|
+
|
|
3923
|
+
`;o+=`> ${p.warning}
|
|
3924
|
+
|
|
3925
|
+
`,o+=`- Public exports tracked: **${a.exports}** (${a.exportsWithoutFrame} with no frame at all)
|
|
3926
|
+
`,o+=`- Dimension cells: **${a.dimensionCells}** \u2014 \u2705 ${a.covered} covered \xB7 \u26A0\uFE0F ${a.untested} UNTESTED \xB7 \u2796 ${a.notApplicable} reasoned not-applicable
|
|
3927
|
+
`,o+=`- Required viewport matrix: ${p.requiredViewports.join(", ")} (container widths ${p.containerWidths.join(", ")})
|
|
3928
|
+
`,o+=`- Ledger recorded: ${p.recordedAt} \xB7 \`${p.ledger}\`
|
|
3929
|
+
|
|
3930
|
+
`,o+=`## Contract dimensions
|
|
3199
3931
|
|
|
3200
|
-
|
|
3932
|
+
`;for(let r of p.dimensions)o+=`- \`${r.id}\` \u2014 ${r.title}
|
|
3933
|
+
`;o+=`
|
|
3934
|
+
## Tracked known gaps
|
|
3935
|
+
|
|
3936
|
+
`;for(let r of p.knownGaps)o+=`- \`${r.id}\` \u2014 ${r.targets.join(", ")}: ${r.cases.join(" \xB7 ")}
|
|
3937
|
+
`;return o+='\nCall `get_frame_coverage name="Button"` for one export. **Never** report a dimension as supported because an example renders it.\n',o}let e=L(t);if(!e){let o=z.filter(r=>r.name.toLowerCase().includes(t.trim().toLowerCase())).slice(0,8).map(r=>r.name);return`No frame-coverage entry for "${t}" \u2014 its contract is **UNTESTED** by definition.
|
|
3938
|
+
`+(o.length?`Did you mean: ${o.join(", ")}?
|
|
3939
|
+
`:"")+"Call `get_frame_coverage` with no name for the repo-wide summary.\n"}return`# ${e.name} \u2014 contract coverage
|
|
3940
|
+
|
|
3941
|
+
**Group:** ${e.group}
|
|
3942
|
+
${re(e,e.name)}`}function De(t){let a=P(t);if(!a){let e=q(t);if(e.length===0)return`Pattern "${t}" not found.`;let o=`Pattern "${t}" not found. Closest:
|
|
3943
|
+
`;for(let r of e)o+=`- ${r.name} \u2014 ${r.tagline}
|
|
3944
|
+
`;return o}return`# Pattern: ${a.name}
|
|
3945
|
+
|
|
3946
|
+
${a.tagline}
|
|
3947
|
+
|
|
3948
|
+
**Tags:** ${a.tags.join(", ")}
|
|
3201
3949
|
|
|
3202
3950
|
\`\`\`tsx
|
|
3203
|
-
${
|
|
3951
|
+
${a.code}
|
|
3204
3952
|
\`\`\`
|
|
3205
|
-
`}function
|
|
3953
|
+
`}function Ne(t){if(t!==void 0){let e=T(t);return e?`# Rule ${e.number} \u2014 ${e.title}
|
|
3206
3954
|
|
|
3207
3955
|
${e.body}
|
|
3208
|
-
`:`Rule ${
|
|
3956
|
+
`:`Rule ${t} not found. Valid: 1-${u.length}.`}let a=`# Cardinal rules (${u.length})
|
|
3209
3957
|
|
|
3210
|
-
`;for(let e of u)
|
|
3958
|
+
`;for(let e of u)a+=`## ${e.number}. ${e.title}
|
|
3211
3959
|
|
|
3212
3960
|
${e.body}
|
|
3213
3961
|
|
|
3214
|
-
`;return
|
|
3962
|
+
`;return a}function Oe(t){if(t){let e=B(t);if(!e)return`Vocab "${t}" not found.`;let o=`# ${e.name}
|
|
3215
3963
|
|
|
3216
3964
|
${e.concept}
|
|
3217
3965
|
|
|
3218
|
-
`;return o+=`**Values:** ${e.values.map(
|
|
3966
|
+
`;return o+=`**Values:** ${e.values.map(r=>`\`${r}\``).join(" | ")}
|
|
3219
3967
|
|
|
3220
|
-
`,o+=`**Used by:** ${e.usedBy.map(
|
|
3968
|
+
`,o+=`**Used by:** ${e.usedBy.map(r=>`\`${r}\``).join(", ")}
|
|
3221
3969
|
|
|
3222
3970
|
`,e.notes&&(o+=`**Notes:** ${e.notes}
|
|
3223
|
-
`),o}let
|
|
3971
|
+
`),o}let a=`# Prop vocabulary
|
|
3224
3972
|
|
|
3225
|
-
${
|
|
3973
|
+
${k.length} shared types.
|
|
3226
3974
|
|
|
3227
|
-
`;for(let e of
|
|
3975
|
+
`;for(let e of k)a+=`## ${e.name}
|
|
3228
3976
|
${e.concept}
|
|
3229
3977
|
|
|
3230
3978
|
Values: ${e.values.map(o=>`\`${o}\``).join(" | ")}
|
|
3231
3979
|
|
|
3232
|
-
`;return
|
|
3980
|
+
`;return a}function Ie(t){let a=t?I(t):C;if(a.length===0)return`No tokens${t?` in "${t}"`:""}.`;let e=`# Design tokens${t?` \u2014 ${t}`:""}
|
|
3233
3981
|
|
|
3234
|
-
`,o=
|
|
3982
|
+
`,o=a.reduce((r,i)=>((r[i.category]??=[]).push(i),r),{});for(let[r,i]of Object.entries(o)){e+=`## ${r}
|
|
3235
3983
|
|
|
3236
3984
|
| Name | Role | Tier |
|
|
3237
3985
|
|---|---|---|
|
|
3238
|
-
`;for(let
|
|
3986
|
+
`;for(let n of i)e+=`| \`${n.name}\` | ${n.role} | ${n.tier} |
|
|
3239
3987
|
`;e+=`
|
|
3240
|
-
`}return e}function
|
|
3988
|
+
`}return e}function Z(t,a){if(!t.trim())return"Describe the task (e.g. 'design a premium agency hero', 'audit existing settings page').";let e=j(t,a),o=`# Routing "${t}"${a?.consumerOnly?" (consumer)":""}
|
|
3241
3989
|
|
|
3242
|
-
`;for(let
|
|
3243
|
-
${
|
|
3244
|
-
`,
|
|
3245
|
-
`);return o+='\nFetch with: `get_skill_section skill="X" section="Y"`',o}function
|
|
3990
|
+
`;for(let r of e)o+=`- **skill:** \`${r.skill}\`, **section:** \`${r.section}\`
|
|
3991
|
+
${r.why}
|
|
3992
|
+
`,r.alsoSee?.length&&(o+=` _Also see:_ ${r.alsoSee.map(i=>`\`${i}\``).join(", ")}
|
|
3993
|
+
`);return o+='\nFetch with: `get_skill_section skill="X" section="Y"`',o}function Pe(t){let a=t.trim().toLowerCase();if(!a)return"Describe your use case.";let e=[],o=(i,n,s,g=2)=>{i.some(h=>a.includes(h))&&e.push({component:n,rationale:s,score:g})};if(o(["form","submit","validation","register","sign up"],"Form + FormField","RHF + zod composition.",5),o(["table","rows","columns"],"DataTable / Table","DataTable for chrome (toolbar+pagination+batch). Table for slim primitive.",5),o(["modal","dialog","confirm"],"Dialog / AlertDialog","Radix Dialog. AlertDialog for destructive.",4),o(["drawer","side panel","sheet"],"Sheet","Side panel for filters/settings.",4),o(["toast","notification"],"toast / Toaster","Sonner-backed.",4),o(["loading","saving","spinner"],"Spinner / Form loading prop","Spinner=active work, Skeleton=init fetch.",3),o(["alert","banner"],"Alert","5 semantic colors \xD7 outlined/banner.",3),o(["select","dropdown"],"Select / AutoComplete","Select=discrete options, AutoComplete=free-text+suggestions.",3),o(["filter"],"Toolbar/ToolbarGroup + pattern 'filter-bar' (\u2192 data-table-page)","Standalone Toolbar filter bar above a table; see the data-table-page pattern.",4),o(["delete","destructive"],"Pattern 'confirm-destructive'","Card accent='destructive' + typed-name confirm.",4),!e.length)return`No direct match for "${t}". Try \`list_primitives\` or \`search_components\`.`;e.sort((i,n)=>n.score-i.score);let r=`# Suggestions for "${t}"
|
|
3246
3994
|
|
|
3247
|
-
`;for(let
|
|
3248
|
-
`;return
|
|
3995
|
+
`;for(let i of e)r+=`- **${i.component}** \u2014 ${i.rationale}
|
|
3996
|
+
`;return r}function Re(t){let a=t.trim().toLowerCase();if(!a)return ae();let e=a.split(/\s+/).filter(n=>n.length>=2),o=e.length?e:[a],r=v.map(n=>{let s=n.name.toLowerCase(),g=n.tagline.toLowerCase(),h=(n.useCases??[]).join(" ").toLowerCase(),d=(n.usage??[]).join(" ").toLowerCase(),y=(n.related??[]).join(" ").toLowerCase(),N=n.props.map(c=>c.name.toLowerCase()),l=0;s===a&&(l+=100);for(let c of o)s.includes(c)&&(l+=5),g.includes(c)&&(l+=3),h.includes(c)&&(l+=2),d.includes(c)&&(l+=1),y.includes(c)&&(l+=1),n.group.includes(c)&&(l+=1),N.some(E=>E.includes(c))&&(l+=1);return{c:n,score:l}}).filter(n=>n.score>0).sort((n,s)=>s.score-n.score).slice(0,12);if(!r.length)return`No matches for "${t}". Try \`list_primitives\` or a broader term (e.g. a use-case word like "date", "select", "confirm").`;let i=`# Search "${t}" \u2014 ${r.length} match${r.length>1?"es":""}
|
|
3249
3997
|
|
|
3250
|
-
`;for(let{c:
|
|
3251
|
-
`;return
|
|
3998
|
+
`;for(let{c:n,score:s}of r)i+=`- **${n.name}** (${n.group}, ${s}) \u2014 ${n.tagline}
|
|
3999
|
+
`;return i}function Ee(t){let a=[],e=(r,i)=>{r.test(t)&&a.push(i)};if(e(/<button[\s>]/,"Use `<Button>` instead of raw `<button>` (rule 29)."),e(/<input[\s>]/,"Use `<Input>` instead of raw `<input>` (rule 29)."),e(/<select[\s>]/,"Use `<Select>` instead of raw `<select>` (rule 29)."),e(/<textarea[\s>]/,"Use `<Textarea>` instead of raw `<textarea>` (rule 29)."),e(/<(table|thead|tbody)[\s>]/,"Use `<DataTable>` instead of a hand-rolled `<table>` (rule 29)."),e(/bg-(red|blue|green|yellow|gray|slate|zinc|neutral|stone|orange|amber|lime|emerald|teal|cyan|sky|indigo|violet|purple|fuchsia|pink|rose)-\d{2,3}\b/,"Use semantic token utilities (`bg-primary`/`bg-destructive`) not raw color scales (rule 2)."),e(/\b(?:ml|mr|pl|pr|left|right)-(?:\d|\[|auto|px|full|screen)|\b(?:rounded-[lr]|border-[lr]|text-(?:left|right))\b/,"Physical direction class \u2014 use logical CSS (`ms-/me-`, `ps-/pe-`, `start-/end-`, `rounded-s/e`, `text-start/end`) so the UI flips correctly under RTL (rule: logical CSS)."),e(/size=["']default["']/,'`size="default"` is not in the controlled vocabulary \u2014 use `size` \u2208 xs|sm|md|lg.'),e(/\btext-\[[0-9.]+px\]/,"Arbitrary text size `text-[Npx]` bypasses the golden type scale \u2014 use `<Text size>` / `<Heading level>` (rule 42)."),e(/<Tag[\s\S]*?color=["']error["']/i,'Tag `color="error"` \u2192 `"destructive"` (v5.0, PR #60).'),e(/<Badge[\s\S]*?variant=["']error["']/i,'Badge `variant="error"` \u2192 `"destructive"` (v5.0, PR #63).'),e(/(Flex|Space|Grid|Masonry)[\s\S]*?(gap|size)=["']middle["']/i,'`"middle"` \u2192 `"default"` for Flex/Space/Grid/Masonry (v5.0).'),e(/<IconButton[\s\S]*?size=["']default["']/i,'IconButton `size="default"` \u2192 `"md"` (v5.0).'),e(/<SegmentedControl[\s\S]*?size=["']sm["']/i,'SegmentedControl `size="sm"` \u2192 `"small"` (v5.0).'),e(/<PageContent[\s\S]*?padding=["'](compact|comfortable)["']/i,'PageContent `padding="compact"/"comfortable"` \u2192 `"tight"/"cozy"` (v5.0).'),e(/<Pagination[\s\S]*?justify=["']between["']/i,'Pagination `justify="between"` \u2192 `"space-between"` (v5.0).'),/<IconButton(?![^>]*aria-label)/i.test(t)&&!/asChild/i.test(t)&&a.push("`<IconButton>` should have `aria-label` (rule 6 \u2014 WCAG)."),/cell:\s*\(\{?\s*row\s*\}?\)\s*=>/i.test(t)&&/export\s+const\s+\w+\s*:\s*Story/i.test(t)&&(/parameters[\s\S]{0,200}source[\s\S]{0,100}code:/i.test(t)||a.push("Stories with function-valued cell renderers MUST override `parameters.docs.source.code` (rule 34).")),/text-(red|blue|green|yellow)-\d{2,3}\b/.test(t)&&a.push("Hard-coded color scales \u2014 use semantic tokens. Tells AI-slop palette (rule 2 + anti-AI-tells.visual.rainbow-chip-wall)."),/h-\[?100vh\]?/.test(t)&&a.push("`100vh` causes iOS Safari viewport jump \u2014 use `min-h-[100dvh]` (redesign.layout / soft.absolute-zero)."),/className=["'][^"']*(?:shadow-md|shadow-lg|shadow-xl)["']/.test(t)&&a.push("Tailwind heavy shadows are an AI tell \u2014 use ultra-diffuse low-opacity (< 0.05) or tinted shadows (soft.absolute-zero, minimalist)."),/\b(?:Inter|Roboto|Helvetica|Open\s+Sans)\b/i.test(t)&&a.push("Banned default fonts (Inter/Roboto/Helvetica/Open Sans). Use Geist/Clash Display/PP Editorial New (soft.absolute-zero, minimalist.negative-constraints)."),/Acme|NovaCore|Flowbit|Quantix|VeloPay|John\s+Doe|Jane\s+Smith|Lorem\s+Ipsum/i.test(t)&&a.push("Generic placeholder content (Acme/NovaCore/John Doe/Lorem Ipsum). Use believable real-sounding names (anti-AI-tells.copy)."),a.length===0)return"\u2705 No issues found against the heuristic checks.";let o=`# Lint findings \u2014 ${a.length} issue${a.length===1?"":"s"}
|
|
3252
4000
|
|
|
3253
|
-
`;for(let
|
|
4001
|
+
`;for(let r of a)o+=`- ${r}
|
|
3254
4002
|
`;return o+=`
|
|
3255
4003
|
Note: heuristic only \u2014 not a substitute for the full CI gate.
|
|
3256
|
-
`,o}var
|
|
4004
|
+
`,o}var ne=u.length,ie=[{uri:"godx-ui://compatibility",name:"Package compatibility",description:"MCP package/server version and the compatible @godxjp/ui release range.",mimeType:"application/json"},{uri:"godx-ui://components",name:"All components",description:"Full component catalog as JSON \u2014 name, group, tagline, props, example, rules.",mimeType:"application/json"},{uri:"godx-ui://prop-vocabulary",name:"Shared prop vocabulary",description:"Cross-cutting prop types (SizeProp, StatusProp, ColorProp, LoadingProp, \u2026) as JSON.",mimeType:"application/json"},{uri:"godx-ui://tokens",name:"All design tokens",description:"Every CSS variable + role + value + axis as JSON.",mimeType:"application/json"},{uri:"godx-ui://rules",name:`Cardinal rules (${ne})`,description:`The ${ne} binding rules from CLAUDE.md as Markdown.`,mimeType:"text/markdown"},{uri:"godx-ui://patterns",name:"Code patterns",description:"Canonical pattern catalog (registration-form, settings-page, data-table, \u2026) as JSON.",mimeType:"application/json"}];async function se(t){if(t==="godx-ui://compatibility")return JSON.stringify({mcpVersion:m.version,serverVersion:m.version,compatibleUi:m.godxUiCompatibility,policy:"UI and MCP are released from the same source commit and minor release train."},null,2);if(t==="godx-ui://components")return JSON.stringify(v,null,2);if(t.startsWith("godx-ui://components/")){let a=t.slice(21),e=O(a);if(!e)throw new Error(`Component not found: ${a}`);return ze(e)}if(t==="godx-ui://prop-vocabulary")return JSON.stringify(k,null,2);if(t==="godx-ui://tokens")return JSON.stringify(C,null,2);if(t.startsWith("godx-ui://tokens/")){let a=t.slice(17);return JSON.stringify(I(a),null,2)}if(t==="godx-ui://rules"){let a=`# Cardinal rules (${u.length})
|
|
3257
4005
|
|
|
3258
|
-
`;for(let e of u)
|
|
4006
|
+
`;for(let e of u)a+=`## ${e.number}. ${e.title}
|
|
3259
4007
|
|
|
3260
4008
|
${e.body}
|
|
3261
4009
|
|
|
3262
|
-
`;return
|
|
4010
|
+
`;return a}if(t.startsWith("godx-ui://rules/")){let a=Number(t.slice(16)),e=T(a);if(!e)throw new Error(`Rule not found: ${a}`);return`# Rule ${e.number} \u2014 ${e.title}
|
|
3263
4011
|
|
|
3264
4012
|
${e.body}
|
|
3265
|
-
`}if(
|
|
4013
|
+
`}if(t==="godx-ui://patterns")return JSON.stringify(b.map(({name:a,tagline:e,tags:o})=>({name:a,tagline:e,tags:o})),null,2);if(t.startsWith("godx-ui://patterns/")){let a=t.slice(19),e=P(a);if(!e)throw new Error(`Pattern not found: ${a}`);return`# ${e.name}
|
|
3266
4014
|
|
|
3267
4015
|
${e.tagline}
|
|
3268
4016
|
|
|
@@ -3271,21 +4019,21 @@ ${e.tagline}
|
|
|
3271
4019
|
\`\`\`tsx
|
|
3272
4020
|
${e.code}
|
|
3273
4021
|
\`\`\`
|
|
3274
|
-
`}throw new Error(`Unknown resource: ${
|
|
4022
|
+
`}throw new Error(`Unknown resource: ${t}`)}function ze(t){let a=`# ${t.name}
|
|
3275
4023
|
|
|
3276
|
-
**Group:** ${
|
|
4024
|
+
**Group:** ${t.group}
|
|
3277
4025
|
|
|
3278
|
-
${
|
|
4026
|
+
${t.tagline}
|
|
3279
4027
|
|
|
3280
|
-
`;
|
|
4028
|
+
`;a+=`## Props
|
|
3281
4029
|
|
|
3282
|
-
`,
|
|
4030
|
+
`,a+=`| Name | Type | Required | Default | Description |
|
|
3283
4031
|
|---|---|---|---|---|
|
|
3284
|
-
`;for(let e of
|
|
3285
|
-
`;return
|
|
4032
|
+
`;for(let e of t.props)a+=`| \`${e.name}\` | \`${e.type}\` | ${e.required?"\u2713":""} | ${e.defaultValue?`\`${e.defaultValue}\``:""} | ${e.description} |
|
|
4033
|
+
`;return a+=`
|
|
3286
4034
|
## Example
|
|
3287
4035
|
|
|
3288
4036
|
\`\`\`tsx
|
|
3289
|
-
${
|
|
4037
|
+
${t.example}
|
|
3290
4038
|
\`\`\`
|
|
3291
|
-
`,
|
|
4039
|
+
`,a}async function Ue(){let t=new Le({name:"godx-ui-mcp",version:m.version},{capabilities:{tools:{},resources:{}}});t.setRequestHandler(Ve,async()=>({tools:ee})),t.setRequestHandler(Fe,async e=>{let{name:o,arguments:r}=e.params;return{content:[{type:"text",text:await te(o,r??{})}]}}),t.setRequestHandler(Be,async()=>({resources:ie})),t.setRequestHandler(qe,async e=>{let{uri:o}=e.params,r=await se(o);return{contents:[{uri:o,mimeType:o.endsWith(".json")?"application/json":"text/markdown",text:r}]}});let a=new Me;await t.connect(a),console.error("[godx-ui-mcp] connected (stdio)")}Ue().catch(t=>{console.error("[godx-ui-mcp] fatal:",t),process.exit(1)});
|