@godxjp/ui 33.22.2 → 33.25.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.
Files changed (130) hide show
  1. package/DESIGN.md +1 -1
  2. package/README.md +16 -5
  3. package/agent/DESIGN.md +1 -1
  4. package/agent/START-HERE.md +8 -8
  5. package/agent/components/AgentBadge.json +43 -0
  6. package/agent/components/AppShell.json +6 -0
  7. package/agent/components/Avatar.json +7 -0
  8. package/agent/components/CatalogDetail.json +7 -3
  9. package/agent/components/DatePicker.json +1 -1
  10. package/agent/components/DropdownMenu.json +1 -0
  11. package/agent/components/EditorialCard.json +2 -1
  12. package/agent/components/Image.json +6 -1
  13. package/agent/components/PageContainer.json +5 -0
  14. package/agent/components/PageErrorBoundary.json +48 -0
  15. package/agent/components/Pagination.json +5 -0
  16. package/agent/components/Popover.json +1 -0
  17. package/agent/components/Select.json +2 -1
  18. package/agent/components/Toaster.json +2 -2
  19. package/agent/components/Toolbar.json +1 -1
  20. package/agent/components/Topbar.json +1 -1
  21. package/agent/components-index.json +10 -0
  22. package/agent/components.json +138 -11
  23. package/agent/index.json +8 -8
  24. package/agent/llms.txt +7 -7
  25. package/agent/patterns/notice-bar.json +16 -0
  26. package/agent/patterns-index.json +15 -0
  27. package/agent/patterns.json +16 -0
  28. package/agent/rules.json +2 -2
  29. package/agent/tokens.json +121 -7
  30. package/dist/components/charts/chart-cartesian.js +5 -1
  31. package/dist/components/charts/chart-summary.d.ts +1 -1
  32. package/dist/components/charts/chart-summary.js +17 -6
  33. package/dist/components/charts/compact-bar-trend.js +4 -3
  34. package/dist/components/charts/pie-chart.js +2 -1
  35. package/dist/components/data-display/avatar.d.ts +1 -1
  36. package/dist/components/data-display/avatar.js +7 -1
  37. package/dist/components/data-display/badge.d.ts +11 -0
  38. package/dist/components/data-display/badge.js +20 -0
  39. package/dist/components/data-display/image.d.ts +1 -0
  40. package/dist/components/data-display/image.js +10 -2
  41. package/dist/components/data-display/index.d.ts +2 -2
  42. package/dist/components/data-display/index.js +2 -1
  43. package/dist/components/data-display/popover.js +11 -0
  44. package/dist/components/data-entry/calendar.js +1 -1
  45. package/dist/components/data-entry/date-picker.js +22 -5
  46. package/dist/components/data-entry/record-picker.js +122 -98
  47. package/dist/components/data-entry/select.js +13 -3
  48. package/dist/components/general/button.js +2 -2
  49. package/dist/components/layout/affix.js +1 -1
  50. package/dist/components/layout/app-shell.d.ts +1 -1
  51. package/dist/components/layout/app-shell.js +3 -1
  52. package/dist/components/layout/editorial-catalog.d.ts +1 -1
  53. package/dist/components/layout/editorial-catalog.js +53 -15
  54. package/dist/components/layout/index.d.ts +2 -0
  55. package/dist/components/layout/index.js +2 -0
  56. package/dist/components/layout/page-container.d.ts +1 -1
  57. package/dist/components/layout/page-container.js +6 -3
  58. package/dist/components/layout/page-error-boundary.d.ts +32 -0
  59. package/dist/components/layout/page-error-boundary.js +57 -0
  60. package/dist/components/layout/sidebar.d.ts +1 -1
  61. package/dist/components/layout/sidebar.js +100 -49
  62. package/dist/components/layout/topbar.js +108 -21
  63. package/dist/components/navigation/dropdown-menu.d.ts +13 -3
  64. package/dist/components/navigation/dropdown-menu.js +36 -6
  65. package/dist/components/navigation/pagination.d.ts +1 -1
  66. package/dist/components/navigation/pagination.js +13 -1
  67. package/dist/components/navigation/toolbar.js +2 -1
  68. package/dist/contracts/measurement.json +1 -1
  69. package/dist/i18n/messages/en.json +7 -0
  70. package/dist/i18n/messages/ja.json +7 -0
  71. package/dist/i18n/messages/vi.json +7 -0
  72. package/dist/props/components/data-display.prop.d.ts +29 -0
  73. package/dist/props/components/layout.prop.d.ts +52 -2
  74. package/dist/props/components/navigation.prop.d.ts +15 -1
  75. package/dist/props/registry.d.ts +65 -5
  76. package/dist/props/registry.js +90 -4
  77. package/dist/styles/alert-layout.css +4 -0
  78. package/dist/styles/application.css +24 -0
  79. package/dist/styles/badge-layout.css +14 -0
  80. package/dist/styles/base.css +11 -0
  81. package/dist/styles/control.css +32 -19
  82. package/dist/styles/core.css +1 -0
  83. package/dist/styles/data-display-layout.css +51 -0
  84. package/dist/styles/dialog-layout.css +9 -0
  85. package/dist/styles/editorial-layout.css +2076 -0
  86. package/dist/styles/editorial.css +3 -0
  87. package/dist/styles/icon-layout.css +3 -0
  88. package/dist/styles/index.css +1 -0
  89. package/dist/styles/layers.json +60 -9
  90. package/dist/styles/layout.css +9 -1985
  91. package/dist/styles/precompiled/alert-layout.css +2 -0
  92. package/dist/styles/precompiled/badge-layout.css +2 -0
  93. package/dist/styles/precompiled/base.css +2 -0
  94. package/dist/styles/precompiled/card-layout.css +2 -0
  95. package/dist/styles/precompiled/chart-layout.css +2 -0
  96. package/dist/styles/precompiled/control.css +2 -0
  97. package/dist/styles/precompiled/data-display-layout.css +2 -0
  98. package/dist/styles/precompiled/data-entry-layout.css +2 -0
  99. package/dist/styles/precompiled/dialog-layout.css +2 -0
  100. package/dist/styles/precompiled/editorial-account-layout.css +2 -0
  101. package/dist/styles/precompiled/editorial-layout.css +2 -0
  102. package/dist/styles/precompiled/float-button-layout.css +2 -0
  103. package/dist/styles/precompiled/form-layout.css +2 -0
  104. package/dist/styles/precompiled/icon-layout.css +2 -0
  105. package/dist/styles/precompiled/layout.css +2 -0
  106. package/dist/styles/precompiled/logo-layout.css +2 -0
  107. package/dist/styles/precompiled/motion.css +2 -0
  108. package/dist/styles/precompiled/navigation-layout.css +2 -0
  109. package/dist/styles/precompiled/shell-layout.css +2 -0
  110. package/dist/styles/precompiled/table-layout.css +2 -0
  111. package/dist/styles/precompiled/text-layout.css +2 -0
  112. package/dist/styles/precompiled/toggle.css +2 -0
  113. package/dist/styles/precompiled/vendor-day-picker.css +2 -0
  114. package/dist/styles/precompiled/vendor-sonner.css +2 -0
  115. package/dist/styles/shell-layout.css +17 -1
  116. package/dist/styles/table-layout.css +1 -1
  117. package/dist/styles.css +1 -1
  118. package/dist/tokens/components/badge.css +3 -0
  119. package/dist/tokens/components/control.css +3 -0
  120. package/dist/tokens/components/data-display.css +9 -0
  121. package/dist/tokens/components/editorial.css +9 -6
  122. package/dist/tokens/components/shell.css +2 -0
  123. package/dist/tokens/foundation.css +8 -0
  124. package/docs/CONSUMER-RULES.md +36 -1
  125. package/package.json +4 -2
  126. package/scripts/consumer-rule.md +1 -1
  127. package/scripts/prune-css.mjs +56 -8
  128. package/scripts/ui-audit.mjs +36 -0
  129. package/skills/godxjp-ui-navigation/SKILL.md +14 -9
  130. package/skills/godxjp-ui-navigation/references/principles.md +2 -2
@@ -692,6 +692,11 @@
692
692
  "name": "extra",
693
693
  "type": "ReactNode | { start?: ReactNode; end?: ReactNode }"
694
694
  },
695
+ {
696
+ "description": "The page's Help control (gh#1494) — a ghost Button opening a help Sheet, or a link to the manual. ONE fixed place on every page: the last item at the header row's inline end, after the `extra` actions, and still flush with that end when the actions wrap or are absent (measured 1440/768/390, 0/1/5 actions). A page passing only `help` still renders a header. Do not put Help in `extra`, in the body, or in a second header band.",
697
+ "name": "help",
698
+ "type": "ReactNode"
699
+ },
695
700
  {
696
701
  "description": "FIXED chrome band between the page header and the body — a filter strip, a status bar, a \"channel workflow\" rail. It is a SIBLING of the body, not content inside it: with `fill` the body is the scroll viewport, so the band is `flex: none` OUTSIDE the scroller and never scrolls away or gets slid under. Shares the page gutters and the `measure` cap with the header and the body (the three bands line up on both edges), goes full-bleed under variant=\"flush\" (wrap padded strips in PageContainer.Inset), and renders NOTHING when omitted — no wrapper, no gap. Its BOX sits FLUSH against the header above and the body below (chrome is attached, not a third page section floating between two voids, so the band cancels the container gap from itself), and by default its inset gives the band's CONTENT that gap back: a plain filter strip (a Segmented, a row of filters) sits one band gap from the header and from the body (gh#1323 — it used to touch both, 0px). A theme that paints or rules the band sets a tighter inset with `--page-toolbar-pad-block`.",
697
702
  "name": "toolbar",
@@ -1680,7 +1685,8 @@
1680
1685
  ],
1681
1686
  "tagline": "A whole-card link (gh#1435). `variant=\"journal\"`: an edge-to-edge cover, category, title, two lines of description and a footer with a circled arrow; the link is named by its title (aria-labelledby). `variant=\"spotlight\"`: kicker, title and a line beside a tilted picture. Pass the link element (router Link or `<a href>`) as `link`; the card content is rendered into it. Geometry: `--editorial-journal-*`, `--editorial-spotlight-*`.",
1682
1687
  "usage": [
1683
- "DO lay journal cards out with `ResponsiveGrid preset=\"trio\"`."
1688
+ "DO lay journal cards out with `ResponsiveGrid preset=\"trio\"`.",
1689
+ "DO use kit Image with preview={false} for media inside the whole-card link. Spotlight sizes the Image frame to fill its slot, including placeholder and fallback (gh#1474)."
1684
1690
  ]
1685
1691
  },
1686
1692
  {
@@ -1978,7 +1984,7 @@
1978
1984
  ]
1979
1985
  },
1980
1986
  {
1981
- "example": "import { CatalogDetail, CatalogDetailTag, Eyebrow } from \"@godxjp/ui/layout\";\n\n<CatalogDetail media={<img src={src} alt={name} />} overline={<Eyebrow>Pokémon</Eyebrow>} title={name} meta={<CatalogDetailTag>SAR</CatalogDetailTag>} summary={{ label: \"Market\", price: \"¥32,800\" }} facts={[{ key: \"set\", label: \"Expansion\", value: \"151\" }]} />",
1987
+ "example": "import { CatalogDetail, CatalogDetailTag, Eyebrow } from \"@godxjp/ui/layout\";\n\n<CatalogDetail image={{ src: \"/reference.svg\", alt: \"Reference\", priority: true }} overline={<Eyebrow>Pokémon</Eyebrow>} title={name} meta={<CatalogDetailTag>SAR</CatalogDetailTag>} summary={{ label: \"Market\", price: \"¥32,800\" }} facts={[{ key: \"set\", label: \"Expansion\", value: \"151\" }]} />",
1982
1988
  "group": "layout",
1983
1989
  "importPath": "@godxjp/ui/layout",
1984
1990
  "name": "CatalogDetail",
@@ -1990,11 +1996,15 @@
1990
1996
  "type": "\"card\" | \"sealed\""
1991
1997
  },
1992
1998
  {
1993
- "description": "The picture.",
1999
+ "description": "Custom picture; used when image is omitted.",
1994
2000
  "name": "media",
1995
- "required": true,
1996
2001
  "type": "ReactNode"
1997
2002
  },
2003
+ {
2004
+ "description": "Kit Image keyed per src. Art frame exposes data-status loading|loaded|error and aria-busy; token-driven skeleton respects reduced motion. priority requests eager/high priority. Takes precedence over media.",
2005
+ "name": "image",
2006
+ "type": "{ src: string; alt: string; width?: number | string; height?: number | string; fallback?: string; priority?: boolean }"
2007
+ },
1998
2008
  {
1999
2009
  "description": "Save toggle over the frame.",
2000
2010
  "name": "favorite",
@@ -3244,6 +3254,12 @@
3244
3254
  "required": true,
3245
3255
  "type": "ReactNode"
3246
3256
  },
3257
+ {
3258
+ "defaultValue": "true",
3259
+ "description": "Page-level error boundary around `children` (gh#1494). A page that throws while rendering is replaced by an application ErrorSurface inside <main> (localized title + one Reload action); the sidebar, topbar and breadcrumb stay, so the user can navigate away. It resets when the URL changes, so the next visit — or Back — draws the page again. Pass PageErrorBoundary's props as an object to configure it, or `false` when the app already wraps pages in its own boundary.",
3260
+ "name": "errorBoundary",
3261
+ "type": "boolean | { fallback?; resetKey?; onError? }"
3262
+ },
3247
3263
  {
3248
3264
  "description": "Full topbar override; else a rail is built from topbarLeft/topbarRight/logo. Omit ALL FOUR bar slots (topbar, topbarLeft, topbarRight, logo) and there is NO `<header class=\"app-topbar\">` in the DOM at all: the shell publishes `data-topbar=\"none\"` on its root and the bar's grid row collapses to zero, for a shell whose PAGE owns the top row. The one exception is the AppShell-owned drawer trigger — at or below 900px the header comes back carrying the hamburger alone, so navigation is never unreachable.",
3249
3265
  "name": "topbar",
@@ -3767,7 +3783,7 @@
3767
3783
  },
3768
3784
  {
3769
3785
  "defaultValue": "\"scroll\"",
3770
- "description": "What a bar that does not fit does with the cells that do not fit (gh#728). scroll (default) lets the BAR scroll on the inline axis while the start and end clusters keep their cells whole — every cell stays in the DOM and in the accessibility tree, Tab reaches each one in order, and the browser scrolls a focused cell into view; the scrollbar is suppressed (a horizontal bar inside a 48px chrome row would eat the height that caused the deficit), so the pointer affordance is drag/wheel. clip is the pre-gh#728 behaviour: a cell past the cluster's edge is simply not painted — still focusable, still announced, invisible and unreachable by pointer. Independent of TopbarItem hideBelow, which REMOVES a cell at a step; this is only about what happens once there is no budget left.",
3786
+ "description": "What a bar that does not fit does with the cells that do not fit (gh#728). scroll (default) lets the BAR scroll on the inline axis while the start and end clusters keep their cells whole — every cell stays in the DOM and in the accessibility tree, Tab reaches each one in order, and the browser scrolls a focused cell into view; the scrollbar is suppressed (a horizontal bar inside a 48px chrome row would eat the height that caused the deficit), so the pointer affordance is drag/wheel. clip is the pre-gh#728 behaviour: a cell past the cluster's edge is simply not painted — still focusable, still announced, invisible and unreachable by pointer. Independent of TopbarItem hideBelow, which REMOVES a cell at a step; this is only about what happens once there is no budget left. menu (gh#914) folds every cluster into ONE \"more\" cell when the bar does not fit; folding MOVES the controls (into the open menu, or a hidden holder while it is closed) and never unmounts them, so a CommandPalette in `start` keeps its ⌘K shortcut and a control keeps its state across folds (gh#1478).",
3771
3787
  "name": "overflow",
3772
3788
  "type": "\"scroll\" | \"clip\" | \"menu\""
3773
3789
  },
@@ -4426,6 +4442,54 @@
4426
4442
  "Unexpected failure with a support correlation id: <ErrorSurface mode=\"system\" status={500} requestId=\"01J9Z0…\" action={<Button onClick={reload}>Reload</Button>} />."
4427
4443
  ]
4428
4444
  },
4445
+ {
4446
+ "example": "import { AppShell, PageErrorBoundary } from \"@godxjp/ui/layout\";\n\n// Inside AppShell nothing is needed: a throwing page keeps the shell.\n<AppShell sidebar={sidebar} topbar={topbar} errorBoundary={{ onError: report }}>\n {page}\n</AppShell>;\n\n// Outside AppShell, wrap the page yourself.\n<PageErrorBoundary onError={report}>{page}</PageErrorBoundary>;",
4447
+ "group": "layout",
4448
+ "importPath": "@godxjp/ui/layout",
4449
+ "name": "PageErrorBoundary",
4450
+ "props": [
4451
+ {
4452
+ "description": "The page.",
4453
+ "name": "children",
4454
+ "type": "ReactNode"
4455
+ },
4456
+ {
4457
+ "description": "What replaces the page that threw. Default: <ErrorSurface mode=\"application\" status={500} titleLevel={1}> with the kit's localized title/description and ONE 'Reload page' action. The function form gets the error and a `reset()` that draws the page again without a reload.",
4458
+ "name": "fallback",
4459
+ "type": "ReactNode | ({ error, reset }) => ReactNode"
4460
+ },
4461
+ {
4462
+ "description": "Draw the page again when this value changes (Object.is). The boundary ALREADY resets when the URL changes — it records the URL it failed on and resets on the next render under a different one, so it needs no router integration; pass a key only for a change the URL does not show.",
4463
+ "name": "resetKey",
4464
+ "type": "unknown"
4465
+ },
4466
+ {
4467
+ "description": "Report the error (an error tracker). Replaces the development-time console.error.",
4468
+ "name": "onError",
4469
+ "type": "(error: unknown, info: React.ErrorInfo) => void"
4470
+ }
4471
+ ],
4472
+ "related": [
4473
+ "AppShell — wraps its children in a PageErrorBoundary by default (`errorBoundary`).",
4474
+ "ErrorSurface — the default fallback; use it directly for an HTTP 400/403/404/500/503 page the server reports."
4475
+ ],
4476
+ "rules": [
4477
+ 23,
4478
+ 24
4479
+ ],
4480
+ "storyPath": "layout/page-error-boundary.tsx",
4481
+ "tagline": "Page-level React error boundary (gh#1494): a page that throws while rendering is replaced by an application ErrorSurface and the shell around it stays. AppShell wraps its children in one by default; use this directly only around a page outside AppShell.",
4482
+ "usage": [
4483
+ "DO rely on AppShell's built-in boundary: every page inside AppShell is already covered. Configure it with AppShell `errorBoundary={{ onError: report }}`.",
4484
+ "DO wrap a page that renders OUTSIDE AppShell (a CenteredShell page, an embedded view) in <PageErrorBoundary> yourself.",
4485
+ "DO NOT wrap every Card in a boundary: it is PAGE-level. An in-page query failure is DataState / Alert.QueryError, not a render error.",
4486
+ "DO NOT key the boundary on the URL yourself to make it reset — it already resets on navigation."
4487
+ ],
4488
+ "useCases": [
4489
+ "A list page whose row renderer hits an unexpected null: the page area shows the ErrorSurface, the sidebar still works, and navigating to another page draws that page normally.",
4490
+ "Error reporting: <AppShell errorBoundary={{ onError: (error) => Sentry.captureException(error) }} …>."
4491
+ ]
4492
+ },
4429
4493
  {
4430
4494
  "example": "import { CenteredShell, Flex, Topbar } from \"@godxjp/ui/layout\";\nimport { LegalDocumentShell } from \"@godxjp/ui/lab\";\nimport { Button, Text } from \"@godxjp/ui/general\";\nimport { useState } from \"react\";\n\nexport function TermsPage() {\n const [activeSection, setActiveSection] = useState(\"acceptance\");\n\n return (\n <CenteredShell width=\"lg\" topbar={<Topbar start={<Brand />} />}>\n <LegalDocumentShell\n title=\"利用規約\"\n version=\"2.4\"\n effectiveDate=\"2026-04-01\"\n summary=\"本規約は、当社が提供するサービスの利用条件を定めるものです。\"\n contentsLabel=\"目次\"\n activeSection={activeSection}\n onActiveSectionChange={setActiveSection}\n sections={[\n { id: \"acceptance\", title: \"第1条(本規約への同意)\", content: <Text as=\"p\">…</Text> },\n { id: \"accounts\", title: \"第2条(アカウントの管理)\", content: <Text as=\"p\">…</Text> },\n ]}\n documentNavigation={\n <Flex direction=\"col\" gap=\"xs\" role=\"group\" aria-label=\"法的文書\">\n <Button variant=\"secondary\" size=\"sm\" fullWidth aria-current=\"page\">利用規約</Button>\n <Button variant=\"ghost\" size=\"sm\" fullWidth>プライバシーポリシー</Button>\n </Flex>\n }\n footerAction={<Button size=\"sm\">同意する</Button>}\n />\n </CenteredShell>\n );\n}",
4431
4495
  "group": "layout",
@@ -6195,6 +6259,49 @@
6195
6259
  "A status or category an administrator coloured themselves, in an issue tracker or a CRM — `<Badge shape=\"pill\" color={status.color}>{status.name}</Badge>`. The colour comes out of a picker and is stored on the row, so it cannot be mapped to a tone."
6196
6260
  ]
6197
6261
  },
6262
+ {
6263
+ "example": "import { AgentBadge, Avatar, AvatarFallback, AvatarImage } from \"@godxjp/ui/data-display\";\nimport { Text } from \"@godxjp/ui/general\";\nimport { Flex } from \"@godxjp/ui/layout\";\n\n<Flex align=\"center\" gap=\"sm\">\n <Avatar kind=\"agent\">\n <AvatarImage src={agent.avatarUrl} alt=\"\" />\n <AvatarFallback>ハ</AvatarFallback>\n </Avatar>\n <Text>ハルカ</Text>\n <AgentBadge />\n</Flex>",
6264
+ "group": "data-display",
6265
+ "importPath": "@godxjp/ui/data-display",
6266
+ "name": "AgentBadge",
6267
+ "props": [
6268
+ {
6269
+ "defaultValue": "\"span\"",
6270
+ "description": "Render element. `span` (default) because the label sits in a name's text line, often inside a link or a <button>.",
6271
+ "name": "as",
6272
+ "type": "\"div\" | \"span\""
6273
+ },
6274
+ {
6275
+ "description": "Overrides the localized default label (`dataDisplay.avatar.agent`: ja \"AIエージェント\", en \"AI agent\", vi \"Tác tử AI\"). Omit it — the default follows the locale.",
6276
+ "name": "children",
6277
+ "type": "ReactNode"
6278
+ },
6279
+ {
6280
+ "description": "Extra classes on the label root.",
6281
+ "name": "className",
6282
+ "type": "string"
6283
+ }
6284
+ ],
6285
+ "related": [
6286
+ "Avatar — `kind=\"agent\"` puts the \"AI\" tag on the mark itself; use it everywhere an agent's mark appears, and add AgentBadge where its name is printed.",
6287
+ "Badge — the general chip; AgentBadge is a fixed-meaning label built on the same chip geometry."
6288
+ ],
6289
+ "rules": [
6290
+ 35
6291
+ ],
6292
+ "storyPath": "data-display/avatar.tsx",
6293
+ "tagline": "The \"AI agent\" label beside a NAME — violet outline on a transparent ground, pill, never wraps. The text-row half of telling an AI agent apart from a person; `Avatar kind=\"agent\"` is the mark half. A list that shows names uses BOTH; a place with only a mark (assignee icon column, dense cell) uses the Avatar alone.",
6294
+ "usage": [
6295
+ "DO put it right after the agent's name, in the same text line: `<Flex align=\"center\" gap=\"xs\"><Text>{name}</Text><AgentBadge /></Flex>`, and pair it with `<Avatar kind=\"agent\">` on the same row.",
6296
+ "DO leave `children` empty so the label follows the locale; it is real text, so it is announced as written and never colour-only (WCAG 1.4.1).",
6297
+ "DON'T hand-roll it as `<Badge variant=\"outline\" className=\"text-violet-600\">AI</Badge>` — the colour reads the `--agent` role through `--badge-agent-border` / `--badge-agent-foreground`, which a theme retunes once.",
6298
+ "DON'T add it where there is no name (an icon-only column, a 24px cell) — there the Avatar's own AI tag is the marker."
6299
+ ],
6300
+ "useCases": [
6301
+ "A member list, a people picker, an assignee dropdown option — any row that prints a principal's name.",
6302
+ "A comment, an activity entry or a chat message authored by an AI agent."
6303
+ ]
6304
+ },
6198
6305
  {
6199
6306
  "example": "import { Legend } from \"@godxjp/ui/data-display\";\n\n<CardAction>\n <Legend\n items={[\n { tone: \"destructive\", label: \"期限超過\" },\n { tone: \"warning\", label: \"期限間近\" },\n { tone: \"success\", label: \"対応済\" },\n ]}\n />\n</CardAction>",
6200
6307
  "group": "data-display",
@@ -9070,7 +9177,7 @@
9070
9177
  "type": "React.ReactNode"
9071
9178
  },
9072
9179
  {
9073
- "description": "antd `popupMatchSelectWidth`. true (default) pins the popup to the trigger width, false lets it hug its rows, a number pins it to that many pixels.",
9180
+ "description": "antd `popupMatchSelectWidth`. true (default): the trigger width is the floor and the popup grows to its longest row, up to --select-content-max-width (32rem) and the viewport less --select-content-viewport-inset, then wraps rows. false lets it hug its rows below the trigger width; a number pins it to that many pixels. The width never depends on where the popup is placed, so it cannot change while open.",
9074
9181
  "name": "popupMatchSelectWidth",
9075
9182
  "type": "boolean | number"
9076
9183
  },
@@ -9200,6 +9307,7 @@
9200
9307
  ],
9201
9308
  "tagline": "Polymorphic single-select: pass options/loadOptions for the data-driven (Ant-style) API, or compose sub-parts manually — never use a raw <select>.",
9202
9309
  "usage": [
9310
+ "DO: Searchable popup widths, including numeric popupMatchSelectWidth, are capped by --search-select-panel-max-width and --search-select-panel-viewport-inset, using the viewport width excluding classic scrollbars.",
9203
9311
  "Coarse pointers: affix actions use --control-affix-touch-target-size (44px by default); text space is reserved separately. Fine-pointer geometry is unchanged.",
9204
9312
  "FREE-TEXT TAGS WITH NO LIST (keywords, email recipients, labels typed from scratch): `<Select mode=\"tags\" open={false} value={tags} onValueChange={setTags} />` — the inline chip field. Use plain `mode=\"tags\"` (popup) only when there is a list of existing tags to pick from. There is no `TagInput` component.",
9205
9313
  "DO use the data-driven API (options/loadOptions) for straightforward selects — it handles grouping, search, async, and custom rendering automatically. Only reach for the compound API when you need to inject arbitrary content into the trigger or listbox.",
@@ -10027,7 +10135,7 @@
10027
10135
  "type": "\"date\" | \"week\" | \"month\" | \"quarter\" | \"year\""
10028
10136
  },
10029
10137
  {
10030
- "description": "CARDINALITY: two endpoints in one control (antd `DatePicker.RangePicker`). value/defaultValue and the callback become `DateRange`; the pair submits as `${name}_from` / `${name}_to`. Composes with `picker`, so a month range is `<DatePicker range picker=\"month\" />`. Never place two DatePickers side by side to fake this.",
10138
+ "description": "With no confirmation, holds the first endpoint internally while open, emits a complete range or clear, and discards half-ranges on close. CARDINALITY: two endpoints in one control (antd `DatePicker.RangePicker`). value/defaultValue and the callback become `DateRange`; the pair submits as `${name}_from` / `${name}_to`. Composes with `picker`, so a month range is `<DatePicker range picker=\"month\" />`. Never place two DatePickers side by side to fake this.",
10031
10139
  "name": "range",
10032
10140
  "type": "boolean"
10033
10141
  },
@@ -10578,7 +10686,7 @@
10578
10686
  },
10579
10687
  {
10580
10688
  "defaultValue": "true",
10581
- "description": "Draw every visible toast whole, stacked with a gap. Default `true` (gh#1387): Sonner's collapsed deck (`expand={false}`) keeps earlier toasts as cards peeking behind the front one with their text hidden, which reads as an empty box. Pass `expand={false}` to opt back into the deck.",
10689
+ "description": "Draw every visible toast whole, stacked with a gap. Default `true` (gh#1387): Sonner's collapsed deck (`expand={false}`) keeps earlier toasts as cards peeking behind the front one with their text hidden, which reads as an empty box. Pass `expand={false}` to opt back into the deck. While a MODAL Dialog or Sheet is open the toaster takes no pointer events (gh#1477): a toast drawn over the modal's footer stays visible and announced, and the click reaches the footer action — do not work around an overlap with `expand={false}`.",
10582
10690
  "name": "expand",
10583
10691
  "type": "boolean"
10584
10692
  },
@@ -10603,7 +10711,7 @@
10603
10711
  "usage": [
10604
10712
  "DO: Mount exactly ONE `<Toaster richColors />` at the app root (e.g. inside your layout or AppShell children). Multiple mounts create duplicate toast stacks — there is no provider context, only DOM portals.",
10605
10713
  "DO: Import `toast` from `\"@godxjp/ui/feedback\"` to fire toasts anywhere: `toast.success(…)`, `toast.error(…)`, `toast.warning(…)`, `toast.info(…)`, `toast.loading(…)`, `toast.promise(…)`. It is Sonner's own API with one house default: `toast.error` and `toast.warning` carry a close button (opt out per call with `{ closeButton: false }`), success/info stay auto-dismiss only (gh#1253).",
10606
- "DON'T: Import `toast` from `\"sonner\"` directly — it works, but it skips the close-button default on error/warning. Also note `toast.promise` settling into an error does not gain the button; pass `closeButton` there yourself if the failure must stay dismissible.",
10714
+ "DON'T: Import `toast` from `\"sonner\"` directly — a consumer-installed version can own a different toast store from the kit Toaster, silently hiding toasts. Use `import { toast } from \"@godxjp/ui\"` (also available from `@godxjp/ui/feedback`); this also preserves close-button defaults. Also note `toast.promise` settling into an error does not gain the button; pass `closeButton` there yourself if the failure must stay dismissible.",
10607
10715
  "DO: Tune how long toasts stay with the `--toast-duration` token in the theme (default 4000ms) rather than a `duration` prop on every mount. Never truncate a toast's body — it wraps.",
10608
10716
  "DO: Let the wrapper handle theming — it uses `useDocumentTheme()` to sync with the document `dark` class and `prefers-color-scheme` automatically. Never pass a hardcoded `theme` prop unless you are deliberately overriding.",
10609
10717
  "DON'T: Use `Toaster` for persistent errors or blocking confirmations. Toasts auto-dismiss; they are not a substitute for `Alert` (inline persistent warnings) or `Dialog` (decisions requiring user input).",
@@ -10805,6 +10913,11 @@
10805
10913
  "importPath": "@godxjp/ui/navigation",
10806
10914
  "name": "Pagination",
10807
10915
  "props": [
10916
+ {
10917
+ "description": "Pointer enter, focus or touch start on enabled page/previous/next controls, including simple and responsive forms. May repeat: preload/cache the target without changing value. Disabled or out-of-range targets do not emit.",
10918
+ "name": "onPageIntent",
10919
+ "type": "(page: number) => void"
10920
+ },
10808
10921
  {
10809
10922
  "description": "Tên khả truy cập của landmark phân trang. Bắt buộc khi một trang có nhiều bộ phân trang.",
10810
10923
  "name": "ariaLabel",
@@ -11022,6 +11135,7 @@
11022
11135
  ],
11023
11136
  "tagline": "Dropdown menu (react-aria). Compose DropdownMenu/DropdownMenuTrigger/DropdownMenuContent/DropdownMenuItem/DropdownMenuSeparator. `trigger` picks the gestures — click (default), hover, or contextMenu, which is what replaced the deleted ContextMenu component.",
11024
11137
  "usage": [
11138
+ "DO: Use only menu collection nodes inside DropdownMenuContent. Arbitrary headers, notification lists and links belong in Popover + ListRow; development diagnostics and the dropdown-menu-non-menu-children audit rule name this fix.",
11025
11139
  "DO compose the full sub-part tree: DropdownMenu (root) → DropdownMenuTrigger (with asChild to delegate to your Button/icon) → DropdownMenuContent → DropdownMenuItem / DropdownMenuSeparator / DropdownMenuLabel / DropdownMenuGroup. Omitting any level (e.g. rendering DropdownMenuContent without DropdownMenu as ancestor) breaks Radix context and the menu will not open.",
11026
11140
  "DO use DropdownMenuTrigger with asChild and pass a godx-ui Button or icon Button as the child — never render a raw <button> or <div> as the trigger, and never omit asChild when the child is already a button-like element (double-button nesting breaks a11y).",
11027
11141
  "DO use variant='destructive' on DropdownMenuItem for irreversible actions (delete, revoke, void) — this applies the semantic destructive colour token automatically without any className override.",
@@ -11174,7 +11288,7 @@
11174
11288
  "type": "FilterBarSearchProp"
11175
11289
  },
11176
11290
  {
11177
- "description": "Typed model — labelled domain-neutral Select filters rendered after the search slot in array order. Each: { value (stable identity), label (rendered as the control's REAL <label htmlFor> — WCAG 2.5.3), options, selected/defaultSelected/onSelectedChange, placeholder, disabled }. Width is token-owned (--filter-bar-filter-width).",
11291
+ "description": "Typed model — labelled domain-neutral Select filters rendered after the search slot in array order. Each: { value (stable identity), label (rendered as the control's REAL <label htmlFor> — WCAG 2.5.3), options, selected/defaultSelected/onSelectedChange, placeholder, disabled, required, allowClear }. Width is token-owned (--filter-bar-filter-width). A filter shows a ✕ by default (no filter is its resting state); a SCOPE filter that is never empty (branch, month) passes `required` — no ✕ and `aria-required` on the control — and `allowClear: false` hides the ✕ alone (gh#1495). Never rebuild a filter by hand on the bar's internal class to drop the ✕.",
11178
11292
  "name": "filters",
11179
11293
  "type": "FilterBarFilterProp[]"
11180
11294
  },
@@ -13739,6 +13853,7 @@
13739
13853
  ],
13740
13854
  "tagline": "React Aria floating panel anchored to a trigger — always compose with PopoverTrigger + PopoverContent; never use a raw div overlay.",
13741
13855
  "usage": [
13856
+ "DO: PopoverContent scrolls automatically when its available height caps content. Focus rings remain contained at scroll edges, including flush panels.",
13742
13857
  "HOVER CARD: `<Popover openOn=\"hover\"><PopoverTrigger asChild><a href=…>@yamada</a></PopoverTrigger><PopoverContent>…</PopoverContent></Popover>` for RICH preview content (an avatar + bio, an entity summary). For a short plain-text hint use Tooltip. DON'T put essential information only in a hover card — touch never opens it; make the same content reachable from the trigger's destination. There is no `HoverCard` component.",
13743
13858
  "DO compose: <Popover> → <PopoverTrigger asChild> → <Button/> and <PopoverContent>. All four parts are required for any popover to function; omitting PopoverTrigger or PopoverContent produces nothing.",
13744
13859
  "DO set `<PopoverContent width=\"auto\">` when the child brings its own measure — a Calendar, a chart, a fixed-width preview. The panel's own 18rem clips them, and a `w-*` utility on className is a per-call-site constant no service theme can retune, which is exactly what `flush` exists to avoid on the padding axis. `width=\"trigger\"` matches the anchor, the shape every select-like control wants.",
@@ -14378,6 +14493,12 @@
14378
14493
  "name": "presenceLabel",
14379
14494
  "type": "string"
14380
14495
  },
14496
+ {
14497
+ "defaultValue": "\"person\"",
14498
+ "description": "Who the mark stands for. `person` (inert default) emits no attribute and no node. `agent` marks an AI AGENT wherever a person could be mistaken for one — a member list, an assignee picker, a comment author: a white-on-violet \"AI\" pill at the TOP/inline-end corner, spilling slightly past the mark behind a --background ring (presence owns the bottom corner, so the two compose), legible at every size xs..lg, plus a localized sr-only \"AI agent\" (t(\"dataDisplay.avatar.agent\")), so the difference is never the colour alone (WCAG 1.4.1). Same two values as OrgChart `variant`. Retune with --avatar-agent-* (colour from the --agent role). Where the NAME is printed too, add `<AgentBadge />` beside it; where only the mark appears (icon column, dense cell), the Avatar alone.",
14499
+ "name": "kind",
14500
+ "type": "\"person\" | \"agent\""
14501
+ },
14381
14502
  {
14382
14503
  "description": "Compose AvatarImage and AvatarFallback.",
14383
14504
  "name": "children",
@@ -14405,6 +14526,7 @@
14405
14526
  "tagline": "Avatar mark with image and fallback slots for users, teams, and entities.",
14406
14527
  "usage": [
14407
14528
  "DO compose Avatar > AvatarImage + AvatarFallback so broken or missing images still show a readable fallback.",
14529
+ "DO mark an AI agent with `kind=\"agent\"` everywhere its mark appears. A list that shows NAMES uses BOTH `<Avatar kind=\"agent\">` and `<AgentBadge />` beside the name; a place with only the mark (an assignee icon column, a dense 24px cell) uses the Avatar alone — its AI tag is the marker there.",
14408
14530
  "DO pass `fit=\"contain\"` on AvatarImage for a logo or app icon that is wide, transparent or off-centre — the default `cover` crops to fill, right for a face (gh#1176). A full-bleed square icon (the GoDX service icon set) keeps the default.",
14409
14531
  "DO use `presence` for who is reachable RIGHT NOW — a chat member list, a DM row, a message-stream author, the topbar account mark. It is the only supported way to put a status dot on an avatar: the dot's inset is a function of the mark's own --avatar-* radius/size and of the root's clip, neither of which a page can read, which is why the hand-rolled `<span className=\"relative\"><Avatar/><span className=\"absolute -end-0.5 -bottom-0.5 size-2.5 rounded-full bg-green-500 ring-2 ring-background\"/></span>` wrapper can never be got right from outside (and fails the DS audit on the raw palette).",
14410
14532
  "DON'T announce a presence CHANGE from the avatar — it carries no aria-live on purpose. A roster of 40 marks resyncing over a socket would flood a screen reader; announce the change in the consumer's own live region if the product wants it.",
@@ -18417,6 +18539,11 @@
18417
18539
  "importPath": "@godxjp/ui/data-display",
18418
18540
  "name": "Image",
18419
18541
  "props": [
18542
+ {
18543
+ "description": "Reports resolved status, including cached images; error remains final while showing fallback.",
18544
+ "name": "onStatusChange",
18545
+ "type": "(status: \"loading\" | \"loaded\" | \"error\") => void"
18546
+ },
18420
18547
  {
18421
18548
  "description": "Image URL.",
18422
18549
  "name": "src",
@@ -18438,7 +18565,7 @@
18438
18565
  "type": "string"
18439
18566
  },
18440
18567
  {
18441
- "description": "Painted over the frame until the picture loads; `true` = a muted block.",
18568
+ "description": "After hydration, painted over the frame while the picture loads; SSR leaves pictures uncovered. `true` = a muted block. Child content can shrink to fit the frame.",
18442
18569
  "name": "placeholder",
18443
18570
  "type": "ReactNode | boolean"
18444
18571
  },
package/agent/index.json CHANGED
@@ -1,10 +1,10 @@
1
1
  {
2
2
  "counts": {
3
3
  "anti-ai-tells": 35,
4
- "components": 210,
5
- "patterns": 28,
4
+ "components": 212,
5
+ "patterns": 29,
6
6
  "rules": 44,
7
- "tokens": 3184,
7
+ "tokens": 3203,
8
8
  "vocabulary": 16
9
9
  },
10
10
  "design": "https://raw.githubusercontent.com/godx-jp/godxjp-ui/main/agent/DESIGN.md",
@@ -16,7 +16,7 @@
16
16
  },
17
17
  {
18
18
  "file": "components-index.json",
19
- "note": "46 KB — name + group + tagline for all 210. FETCH THIS FIRST, then fetch only the components you chose.",
19
+ "note": "47 KB — name + group + tagline for all 212. FETCH THIS FIRST, then fetch only the components you chose.",
20
20
  "url": "https://raw.githubusercontent.com/godx-jp/godxjp-ui/main/agent/components-index.json"
21
21
  },
22
22
  {
@@ -54,19 +54,19 @@
54
54
  "note": "Pin to the tag that matches the @godxjp/ui version you installed. A catalog newer than your package describes props you do not have; older, and it hides props you do.",
55
55
  "read": {
56
56
  "live": "https://raw.githubusercontent.com/godx-jp/godxjp-ui/main/agent/index.json",
57
- "pinned": "https://raw.githubusercontent.com/godx-jp/godxjp-ui/v33.22.2/agent/index.json"
57
+ "pinned": "https://raw.githubusercontent.com/godx-jp/godxjp-ui/v33.25.0/agent/index.json"
58
58
  },
59
59
  "source": "mcp/src/data — the same data @godxjp/ui-mcp serves — plus the foundation and semantic token tiers, read from src/tokens/*.css",
60
60
  "start": "https://raw.githubusercontent.com/godx-jp/godxjp-ui/main/agent/START-HERE.md",
61
61
  "tokenTiers": {
62
62
  "component": "per-part knobs, --{component}-{part}-{property}; usually leave these alone",
63
63
  "counts": {
64
- "component": 2851,
65
- "foundation": 220,
64
+ "component": 2867,
65
+ "foundation": 223,
66
66
  "semantic": 113
67
67
  },
68
68
  "foundation": "the seeds a consumer is invited to set — --primary, --background, --radius",
69
69
  "semantic": "named roles that follow the seeds — --ring, --text-link, --overlay-background"
70
70
  },
71
- "version": "33.22.2"
71
+ "version": "33.25.0"
72
72
  }
package/agent/llms.txt CHANGED
@@ -1,10 +1,10 @@
1
1
  # @godxjp/ui
2
2
 
3
- > A Japanese-enterprise React design system: 210 components, 3184 design tokens,
4
- > 44 cardinal rules. This file is the entry point for AI agents. Catalog version 33.22.2.
3
+ > A Japanese-enterprise React design system: 212 components, 3203 design tokens,
4
+ > 44 cardinal rules. This file is the entry point for AI agents. Catalog version 33.25.0.
5
5
 
6
6
  If your client can run a process, do not read these files — run the MCP server instead
7
- (`npx @godxjp/ui-mcp@33.22.2`). It is searchable and version-locked. These files exist for agents
7
+ (`npx @godxjp/ui-mcp@33.25.0`). It is searchable and version-locked. These files exist for agents
8
8
  that can only fetch URLs.
9
9
 
10
10
  ## Start
@@ -15,11 +15,11 @@ that can only fetch URLs.
15
15
 
16
16
  ## Catalog
17
17
 
18
- - [patterns-index.json](https://raw.githubusercontent.com/godx-jp/godxjp-ui/main/agent/patterns-index.json): 28 whole-task patterns (name, tagline, tags). Start here when the task is a TASK — "build a settings page" — then fetch `patterns/<name>.json` for complete code.
19
- - [components-index.json](https://raw.githubusercontent.com/godx-jp/godxjp-ui/main/agent/components-index.json): 46 KB — all 210 components as name, group, tagline, plus `absorbed`: the names that do NOT exist and map to it (`Combobox` → `Select`).
18
+ - [patterns-index.json](https://raw.githubusercontent.com/godx-jp/godxjp-ui/main/agent/patterns-index.json): 29 whole-task patterns (name, tagline, tags). Start here when the task is a TASK — "build a settings page" — then fetch `patterns/<name>.json` for complete code.
19
+ - [components-index.json](https://raw.githubusercontent.com/godx-jp/godxjp-ui/main/agent/components-index.json): 47 KB — all 212 components as name, group, tagline, plus `absorbed`: the names that do NOT exist and map to it (`Combobox` → `Select`).
20
20
  - [components/&lt;Name&gt;.json](https://raw.githubusercontent.com/godx-jp/godxjp-ui/main/agent/components/Select.json): one file per component (1 KB–38 KB, median 5 KB). Read the index, then fetch only the ones you chose — this is the selective route, and the reason you do not need the blob.
21
21
  - [components.json](https://raw.githubusercontent.com/godx-jp/godxjp-ui/main/agent/components.json): 1.4 MB — every entry in one file. Most URL fetchers truncate a response this size without saying so; prefer the per-component files.
22
- - [tokens.json](https://raw.githubusercontent.com/godx-jp/godxjp-ui/main/agent/tokens.json): every design token with its value, the reason it exists, and its `tier` — 220 `foundation` seeds (`--primary`, `--background`, `--radius`: set these when you are handed a brand), 113 `semantic` roles, 2851 `component` knobs.
22
+ - [tokens.json](https://raw.githubusercontent.com/godx-jp/godxjp-ui/main/agent/tokens.json): every design token with its value, the reason it exists, and its `tier` — 223 `foundation` seeds (`--primary`, `--background`, `--radius`: set these when you are handed a brand), 113 `semantic` roles, 2867 `component` knobs.
23
23
  - [vocabulary.json](https://raw.githubusercontent.com/godx-jp/godxjp-ui/main/agent/vocabulary.json): the controlled prop vocabulary — which prop name means what, across every component.
24
24
  - [rules.json](https://raw.githubusercontent.com/godx-jp/godxjp-ui/main/agent/rules.json): 44 cardinal rules.
25
25
  - [anti-ai-tells.json](https://raw.githubusercontent.com/godx-jp/godxjp-ui/main/agent/anti-ai-tells.json): 35 shapes that make generated UI look generated, each with its fix.
@@ -27,7 +27,7 @@ that can only fetch URLs.
27
27
  ## Pinning
28
28
 
29
29
  Every URL above tracks `main`. To pin to the release a project actually installed, swap `main` for
30
- the tag: `.../godx-jp/godxjp-ui/v33.22.2/agent/...`. A catalog that does not match the installed
30
+ the tag: `.../godx-jp/godxjp-ui/v33.25.0/agent/...`. A catalog that does not match the installed
31
31
  package describes props that are absent, or hides props that are present, and says nothing either way.
32
32
 
33
33
  Pinned catalogs only exist for releases whose tag actually contains `agent/`. If `…/v<version>/agent/index.json` returns 404, that release predates this catalog: read `…/main/…` instead and compare `index.json` → `version` against the package you have, so you at least know which way it drifted.
@@ -0,0 +1,16 @@
1
+ {
2
+ "aliases": [
3
+ "consent-bar",
4
+ "cookie-consent"
5
+ ],
6
+ "code": "// Register noticeBarDocs copy in your application message catalogue (en/ja/vi recipe: docs/feedback/notice-bar.tsx).\n// Framework-Component Test: C3 FAIL; compose existing primitives.\nimport { useEffect, useRef, useState } from \"react\";\nimport { createPortal } from \"react-dom\";\nimport { Card, CardContent } from \"@godxjp/ui/data-display\";\nimport { Button, Text, Heading } from \"@godxjp/ui/general\";\nimport { Flex, PageContainer } from \"@godxjp/ui/layout\";\nimport { useTranslation } from \"@godxjp/ui/i18n\";\n\n/**\n * Composition, gh#1463. C1 PASS; C2 FAIL (ordinary region + native Tab order);\n * C3 FAIL (Card + Flex + Button + fixed section); C4/C5/C6 PASS; C7 FAIL\n * (persistent non-modal consent is not a named primitive in two reference libraries).\n * Consent policy/storage belongs to the application. No focus trap, live role or auto focus.\n * Portal to body avoids paint/transform ancestors turning fixed into a local pin.\n */\nexport default function Demo() {\n const { t } = useTranslation();\n const [decision, setDecision] = useState<string | null>(null);\n const [mounted, setMounted] = useState(false);\n const region = useRef<HTMLElement>(null);\n const host = useRef<HTMLDivElement>(null);\n const [direction, setDirection] = useState(\"ltr\");\n useEffect(() => {\n setDirection(host.current ? getComputedStyle(host.current).direction : \"ltr\");\n setMounted(true);\n }, []);\n useEffect(() => {\n const node = region.current;\n if (!mounted || decision !== null || !node) return;\n const root = document.documentElement;\n const previous = root.style.getPropertyValue(\"--toast-offset-bottom\");\n const priority = root.style.getPropertyPriority(\"--toast-offset-bottom\");\n const base = getComputedStyle(root).getPropertyValue(\"--toast-offset-bottom\").trim() || \"0px\";\n const publish = () => root.style.setProperty(\"--toast-offset-bottom\", `calc(${base} + ${node.offsetHeight}px)`);\n publish();\n const observer = new ResizeObserver(publish);\n observer.observe(node);\n return () => {\n observer.disconnect();\n if (previous) root.style.setProperty(\"--toast-offset-bottom\", previous, priority);\n else root.style.removeProperty(\"--toast-offset-bottom\");\n };\n }, [mounted, decision]);\n return <>\n <div ref={host}><PageContainer title={t(\"noticeBarDocs.page\")}>\n <Text as=\"p\">{t(\"noticeBarDocs.body\")}</Text>\n <Button variant=\"outline\" onClick={() => setDecision(null)}>{t(\"noticeBarDocs.reopen\")}</Button>\n {decision !== null && <Text as=\"p\">{t(decision)}</Text>}\n <section id=\"privacy\"><Heading level={2}>{t(\"noticeBarDocs.privacy\")}</Heading><Text as=\"p\">{t(\"noticeBarDocs.policy\")}</Text></section>\n </PageContainer></div>\n {mounted && decision === null && createPortal(\n <section ref={region} dir={direction} style={{position: \"fixed\", insetInline: 0, insetBlockEnd: 0, zIndex: \"var(--sticky-z-index)\", paddingBlockEnd: \"env(safe-area-inset-bottom, 0px)\", paddingInlineStart: direction === \"rtl\" ? \"env(safe-area-inset-right, 0px)\" : \"env(safe-area-inset-left, 0px)\", paddingInlineEnd: direction === \"rtl\" ? \"env(safe-area-inset-left, 0px)\" : \"env(safe-area-inset-right, 0px)\", background: \"hsl(var(--card))\", color: \"hsl(var(--card-foreground))\"}} role=\"region\" aria-label={t(\"noticeBarDocs.title\")}>\n <Card><CardContent>\n <Flex align=\"center\" justify=\"between\" gap=\"lg\" wrap>\n <Flex direction=\"col\" gap=\"xs\" style={{flex: \"1 1 var(--page-measure-narrow)\", minInlineSize: 0, overflowWrap: \"anywhere\"}}>\n <Text weight=\"medium\">{t(\"noticeBarDocs.title\")}</Text>\n <Text as=\"p\">{t(\"noticeBarDocs.description\")} <a href=\"#privacy\" style={{textDecorationLine: \"underline\"}}>{t(\"noticeBarDocs.privacy\")}</a></Text>\n </Flex>\n <Flex gap=\"sm\" wrap>\n <Button variant=\"outline\" onClick={() => setDecision(\"noticeBarDocs.declined\")}>{t(\"noticeBarDocs.decline\")}</Button>\n <Button onClick={() => setDecision(\"noticeBarDocs.accepted\")}>{t(\"noticeBarDocs.accept\")}</Button>\n </Flex>\n </Flex>\n </CardContent></Card>\n </section>, document.body)}\n </>;\n}\n",
7
+ "name": "notice-bar",
8
+ "tagline": "Fixed non-modal consent region: opaque Card, wrapping actions, safe-area padding, measured Toaster clearance; application owns consent policy. Recipe: docs/feedback/notice-bar.tsx, isolate feedback-notice-bar.",
9
+ "tags": [
10
+ "feedback",
11
+ "consent",
12
+ "cookie",
13
+ "notice",
14
+ "fixed"
15
+ ]
16
+ }
@@ -1,4 +1,19 @@
1
1
  [
2
+ {
3
+ "aliases": [
4
+ "consent-bar",
5
+ "cookie-consent"
6
+ ],
7
+ "name": "notice-bar",
8
+ "tagline": "Fixed non-modal consent region: opaque Card, wrapping actions, safe-area padding, measured Toaster clearance; application owns consent policy. Recipe: docs/feedback/notice-bar.tsx, isolate feedback-notice-bar.",
9
+ "tags": [
10
+ "feedback",
11
+ "consent",
12
+ "cookie",
13
+ "notice",
14
+ "fixed"
15
+ ]
16
+ },
2
17
  {
3
18
  "name": "common-fixes",
4
19
  "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 → after.",
@@ -1,4 +1,20 @@
1
1
  [
2
+ {
3
+ "aliases": [
4
+ "consent-bar",
5
+ "cookie-consent"
6
+ ],
7
+ "code": "// Register noticeBarDocs copy in your application message catalogue (en/ja/vi recipe: docs/feedback/notice-bar.tsx).\n// Framework-Component Test: C3 FAIL; compose existing primitives.\nimport { useEffect, useRef, useState } from \"react\";\nimport { createPortal } from \"react-dom\";\nimport { Card, CardContent } from \"@godxjp/ui/data-display\";\nimport { Button, Text, Heading } from \"@godxjp/ui/general\";\nimport { Flex, PageContainer } from \"@godxjp/ui/layout\";\nimport { useTranslation } from \"@godxjp/ui/i18n\";\n\n/**\n * Composition, gh#1463. C1 PASS; C2 FAIL (ordinary region + native Tab order);\n * C3 FAIL (Card + Flex + Button + fixed section); C4/C5/C6 PASS; C7 FAIL\n * (persistent non-modal consent is not a named primitive in two reference libraries).\n * Consent policy/storage belongs to the application. No focus trap, live role or auto focus.\n * Portal to body avoids paint/transform ancestors turning fixed into a local pin.\n */\nexport default function Demo() {\n const { t } = useTranslation();\n const [decision, setDecision] = useState<string | null>(null);\n const [mounted, setMounted] = useState(false);\n const region = useRef<HTMLElement>(null);\n const host = useRef<HTMLDivElement>(null);\n const [direction, setDirection] = useState(\"ltr\");\n useEffect(() => {\n setDirection(host.current ? getComputedStyle(host.current).direction : \"ltr\");\n setMounted(true);\n }, []);\n useEffect(() => {\n const node = region.current;\n if (!mounted || decision !== null || !node) return;\n const root = document.documentElement;\n const previous = root.style.getPropertyValue(\"--toast-offset-bottom\");\n const priority = root.style.getPropertyPriority(\"--toast-offset-bottom\");\n const base = getComputedStyle(root).getPropertyValue(\"--toast-offset-bottom\").trim() || \"0px\";\n const publish = () => root.style.setProperty(\"--toast-offset-bottom\", `calc(${base} + ${node.offsetHeight}px)`);\n publish();\n const observer = new ResizeObserver(publish);\n observer.observe(node);\n return () => {\n observer.disconnect();\n if (previous) root.style.setProperty(\"--toast-offset-bottom\", previous, priority);\n else root.style.removeProperty(\"--toast-offset-bottom\");\n };\n }, [mounted, decision]);\n return <>\n <div ref={host}><PageContainer title={t(\"noticeBarDocs.page\")}>\n <Text as=\"p\">{t(\"noticeBarDocs.body\")}</Text>\n <Button variant=\"outline\" onClick={() => setDecision(null)}>{t(\"noticeBarDocs.reopen\")}</Button>\n {decision !== null && <Text as=\"p\">{t(decision)}</Text>}\n <section id=\"privacy\"><Heading level={2}>{t(\"noticeBarDocs.privacy\")}</Heading><Text as=\"p\">{t(\"noticeBarDocs.policy\")}</Text></section>\n </PageContainer></div>\n {mounted && decision === null && createPortal(\n <section ref={region} dir={direction} style={{position: \"fixed\", insetInline: 0, insetBlockEnd: 0, zIndex: \"var(--sticky-z-index)\", paddingBlockEnd: \"env(safe-area-inset-bottom, 0px)\", paddingInlineStart: direction === \"rtl\" ? \"env(safe-area-inset-right, 0px)\" : \"env(safe-area-inset-left, 0px)\", paddingInlineEnd: direction === \"rtl\" ? \"env(safe-area-inset-left, 0px)\" : \"env(safe-area-inset-right, 0px)\", background: \"hsl(var(--card))\", color: \"hsl(var(--card-foreground))\"}} role=\"region\" aria-label={t(\"noticeBarDocs.title\")}>\n <Card><CardContent>\n <Flex align=\"center\" justify=\"between\" gap=\"lg\" wrap>\n <Flex direction=\"col\" gap=\"xs\" style={{flex: \"1 1 var(--page-measure-narrow)\", minInlineSize: 0, overflowWrap: \"anywhere\"}}>\n <Text weight=\"medium\">{t(\"noticeBarDocs.title\")}</Text>\n <Text as=\"p\">{t(\"noticeBarDocs.description\")} <a href=\"#privacy\" style={{textDecorationLine: \"underline\"}}>{t(\"noticeBarDocs.privacy\")}</a></Text>\n </Flex>\n <Flex gap=\"sm\" wrap>\n <Button variant=\"outline\" onClick={() => setDecision(\"noticeBarDocs.declined\")}>{t(\"noticeBarDocs.decline\")}</Button>\n <Button onClick={() => setDecision(\"noticeBarDocs.accepted\")}>{t(\"noticeBarDocs.accept\")}</Button>\n </Flex>\n </Flex>\n </CardContent></Card>\n </section>, document.body)}\n </>;\n}\n",
8
+ "name": "notice-bar",
9
+ "tagline": "Fixed non-modal consent region: opaque Card, wrapping actions, safe-area padding, measured Toaster clearance; application owns consent policy. Recipe: docs/feedback/notice-bar.tsx, isolate feedback-notice-bar.",
10
+ "tags": [
11
+ "feedback",
12
+ "consent",
13
+ "cookie",
14
+ "notice",
15
+ "fixed"
16
+ ]
17
+ },
2
18
  {
3
19
  "code": "// ───────────────────────────────────────────────────────────────────────\n// 0) ★ MOST COMMON: <Card> body has NO padding (content is flush against the edges)\n// Cause: the bare <Card> has ZERO inner padding — it MUST contain <CardContent>.\n// Don't hand-roll padding with className=\"p-4\" on the Card either.\n// ❌ <Card><Flex direction=\"col\" gap=\"md\">…fields…</Flex></Card> // flush, no padding\n// ❌ <Card className=\"p-4\">…fields…</Card> // hand-rolled padding\n// ✅ <Card><CardContent><Flex direction=\"col\" gap=\"md\">…fields…</Flex></CardContent></Card>\n// Titles → <CardHeader><CardTitle>. Only go flush deliberately for a full-bleed table:\n// ✅ <Card><CardContent flush><DataTable/></CardContent></Card>\n// GENERAL RULE — compose godx-ui primitives FULLY; never hand-roll what one ships:\n// padding → CardContent (not p-4) · controls → Input/Select/Button (not raw <input>/<select>/<button>)\n// vertical spacing → Flex direction=\"col\" gap (not space-y-*) · empty rows → DataTable's built-in\n// empty / <EmptyState> (not a custom data.length===0 guard). If a primitive exists, USE it.\n\n// 1) StatCard shows a DOUBLE border (too thick)\n// Cause: StatCard IS already a bordered Card. Don't wrap it.\n// ❌ <Card><CardContent><StatCard label=\"x\" value=\"1\" /></CardContent></Card>\n// ✅ dashboard only, with comparison/trend:\n// <ResponsiveGrid columns={4}><StatCard label=\"売上\" value=\"¥8.2M\" delta=\"+12%\" /></ResponsiveGrid>\n// A list or detail page has NO StatCard row — a row count is not a KPI (Pagination showTotal).\n\n// 2) Badge renders grey with a ○ (no colour) for localized/tier labels\n// Cause: it auto-maps only English lifecycle keys.\n// ✅ <Badge status=\"active\">公開中</Badge> // lifecycle → keep icon\n// ✅ <Badge status=\"プレミアム\" tone=\"neutral\" icon={null} /> // tier → ONE neutral tone\n// Colour is for status only; a tier/category/scope is plain text or a neutral pill.\n\n// 3) Table text collapses to one char per line, or a chip wraps\n// ✅ give long columns a width: { key: \"name\", header: \"氏名\", width: \"w-64\" }\n// (cells + chips are pinned white-space: nowrap by the library.)\n\n// 4) Empty (icon/action) column header shows a blank grey block\n// ✅ header: \"\" renders a transparent cell — leave it empty, don't inject a space.\n\n// 5) DataTable columns are crushed / squeezed\n// Cause: the table is nested in a narrow grid column.\n// ❌ <ResponsiveGrid columns={3}><div className=\"lg:col-span-2\"><Card><DataTable/></Card></div></ResponsiveGrid>\n// ✅ Table gets its OWN full-width row: <Card><CardContent flush><DataTable/></CardContent></Card>\n\n// 6) Toolbar (filter bar) has no padding (sticks to the edge)\n// Cause: it's inside CardContent flush (flush strips padding — that's for tables).\n// ❌ <Card><CardContent flush><Toolbar/><DataTable/></CardContent></Card>\n// ✅ <Toolbar/> then <Card><CardContent flush><DataTable/></CardContent></Card>\n\n// 7) Sidebar footer looks washed-out / off-design\n// Cause: raw opacity-*/text-[11px]. Use semantic tokens.\n// ✅ <div className=\"text-muted-foreground text-xs\">\n// <div className=\"text-foreground font-medium\">{name}</div><div>{role}</div></div>\n\n// 8) Inertia: \"Objects are not valid as a React child {errors, auth, …}\"\n// Cause: persistent layout passed as a render fn. Use the ARRAY form.\n// ❌ Page.layout = (page) => <Layout>{page}</Layout>\n// ✅ Page.layout = [Layout] // Layout is ({children}) => ...\n\n// 9) Inertia v3 hydration mismatch (\"server rendered text didn't match the client\")\n// Cause: Math.random()/argless new Date() during render (SSR ≠ client).\n// ✅ seed deterministically by index, or compute in an event handler.\n\n// 10) Hide a column on mobile / sign-aware KPI delta\n// ✅ columns: [{ key: \"email\", header: \"メール\", hiddenOnMobile: true }]\n// ✅ <StatCard label=\"売上\" value=\"¥8.2M\" delta=\"+12%\" /> // + green / - red; inverse flips",
4
20
  "name": "common-fixes",
package/agent/rules.json CHANGED
@@ -55,7 +55,7 @@
55
55
  "title": "TypeScript strict"
56
56
  },
57
57
  {
58
- "body": "The runtime stack is what `package.json` declares: react-aria-components / react-aria / react-stately, cmdk, sonner, lucide-react, @tanstack/react-table, react-day-picker, class-variance-authority + clsx + tailwind-merge. A new dependency needs an ADR saying why it is the canonical choice and why an existing one cannot do it.",
58
+ "body": "The runtime stack is what `package.json` declares: react-aria-components / react-aria / react-stately, cmdk, sonner, lucide-react, @tanstack/react-table, react-day-picker, class-variance-authority + clsx + tailwind-merge. Consumers must import toast from @godxjp/ui (or @godxjp/ui/feedback), never directly from sonner: two installed versions own separate toast stores and silently miss the kit Toaster. The audit rule no-direct-sonner-import enforces this. A new dependency needs an ADR saying why it is the canonical choice and why an existing one cannot do it.",
59
59
  "number": 14,
60
60
  "title": "Every third-party library is a deliberate, declared choice"
61
61
  },
@@ -160,7 +160,7 @@
160
160
  "title": "Long text columns get an explicit width"
161
161
  },
162
162
  {
163
- "body": "Author and verify every page at 320–390px FIRST. Page sections are spaced by `PageContainer` itself (`--page-body-gap` between its direct children). Inside a section 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) — never raw `p-*` / `gap-*` / `space-*` utilities for page layout (the static audit rejects them). Styles are loaded whole: `@godxjp/ui/styles` or `styles/core`, never a hand-picked set of layers. They differ ONLY in the 729 bundled CJK `@font-face` declarations, and that difference is most of the CSS: 367 KB vs 86 KB gzip for a 15-component app (gh#971) — pick `styles/core` whenever the app loads fonts another way.",
163
+ "body": "Author and verify every page at 320–390px FIRST. Page sections are spaced by `PageContainer` itself (`--page-body-gap` between its direct children). Inside a section 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) — never raw `p-*` / `gap-*` / `space-*` utilities for page layout (the static audit rejects them). Load `@godxjp/ui/styles` (Tailwind v4) or `styles.css` (precompiled) for all families, or `styles/application` to omit editorial/catalog/holding layouts; `styles/editorial` adds that family after the application entry. Existing `styles/core` still carries all families. Fonts are opt-in. For a content-aware subset run `npx @godxjp/ui prune-css src --out <your stylesheet>` and import its generated file instead; add `--precompiled` without Tailwind, or `--exclude-family editorial` to reject accidental use of the omitted family. Both paths use the package-owned layer dependency graph. Re-run after upgrades/import changes; never hand-pick layout files.",
164
164
  "number": 40,
165
165
  "title": "Pages are mobile-first"
166
166
  },