@godxjp/ui 28.9.0 → 28.12.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 (244) hide show
  1. package/agent/START-HERE.md +193 -0
  2. package/agent/anti-ai-tells.json +158 -0
  3. package/agent/components/Accordion.json +60 -0
  4. package/agent/components/AccountChip.json +59 -0
  5. package/agent/components/Actions.json +78 -0
  6. package/agent/components/Activity.json +80 -0
  7. package/agent/components/Affix.json +78 -0
  8. package/agent/components/Alert.json +65 -0
  9. package/agent/components/AlertDialog.json +109 -0
  10. package/agent/components/AlertDialogRoot.json +52 -0
  11. package/agent/components/Anchor.json +119 -0
  12. package/agent/components/AppLauncher.json +95 -0
  13. package/agent/components/AppProvider.json +105 -0
  14. package/agent/components/AppSettingPicker.json +88 -0
  15. package/agent/components/AppSettingToggle.json +70 -0
  16. package/agent/components/AppShell.json +159 -0
  17. package/agent/components/AreaChart.json +105 -0
  18. package/agent/components/AspectRatio.json +38 -0
  19. package/agent/components/Attachments.json +77 -0
  20. package/agent/components/AuthAccountSummary.json +64 -0
  21. package/agent/components/AuthDivider.json +35 -0
  22. package/agent/components/AuthFooter.json +48 -0
  23. package/agent/components/AuthIdentity.json +42 -0
  24. package/agent/components/AuthShell.json +108 -0
  25. package/agent/components/AuthStack.json +21 -0
  26. package/agent/components/Avatar.json +89 -0
  27. package/agent/components/Badge.json +96 -0
  28. package/agent/components/Banner.json +48 -0
  29. package/agent/components/BarChart.json +108 -0
  30. package/agent/components/BranchScopePicker.json +89 -0
  31. package/agent/components/Breadcrumb.json +54 -0
  32. package/agent/components/Button.json +133 -0
  33. package/agent/components/Calendar.json +259 -0
  34. package/agent/components/Callout.json +46 -0
  35. package/agent/components/Card.json +112 -0
  36. package/agent/components/CardBar.json +49 -0
  37. package/agent/components/CardContent.json +51 -0
  38. package/agent/components/Carousel.json +50 -0
  39. package/agent/components/Cascader.json +209 -0
  40. package/agent/components/CenteredShell.json +66 -0
  41. package/agent/components/ChatBubble.json +100 -0
  42. package/agent/components/ChatBubbleList.json +64 -0
  43. package/agent/components/ChatComposer.json +160 -0
  44. package/agent/components/ChatSuggestion.json +86 -0
  45. package/agent/components/Checkbox.json +68 -0
  46. package/agent/components/CheckboxGroup.json +96 -0
  47. package/agent/components/CodeBlock.json +64 -0
  48. package/agent/components/Collapsible.json +74 -0
  49. package/agent/components/ColorPicker.json +87 -0
  50. package/agent/components/Command.json +168 -0
  51. package/agent/components/CommandPalette.json +84 -0
  52. package/agent/components/CompactBarTrend.json +101 -0
  53. package/agent/components/Conversations.json +82 -0
  54. package/agent/components/CredentialReveal.json +93 -0
  55. package/agent/components/DataState.json +79 -0
  56. package/agent/components/DataTable.json +268 -0
  57. package/agent/components/DatePicker.json +275 -0
  58. package/agent/components/Descriptions.json +67 -0
  59. package/agent/components/Dialog.json +78 -0
  60. package/agent/components/DraggablePanel.json +106 -0
  61. package/agent/components/DropdownMenu.json +102 -0
  62. package/agent/components/EmptyState.json +83 -0
  63. package/agent/components/ErrorSurface.json +128 -0
  64. package/agent/components/FeatureList.json +43 -0
  65. package/agent/components/Field.json +64 -0
  66. package/agent/components/FilterBar.json +99 -0
  67. package/agent/components/Flex.json +153 -0
  68. package/agent/components/FloatButton.json +91 -0
  69. package/agent/components/Form.json +87 -0
  70. package/agent/components/FormErrors.json +51 -0
  71. package/agent/components/FormField.json +137 -0
  72. package/agent/components/FormFieldArray.json +39 -0
  73. package/agent/components/FormFieldControl.json +129 -0
  74. package/agent/components/FormRoot.json +122 -0
  75. package/agent/components/Heading.json +61 -0
  76. package/agent/components/HoverCard.json +55 -0
  77. package/agent/components/Icon.json +60 -0
  78. package/agent/components/InfiniteQueryState.json +58 -0
  79. package/agent/components/Input.json +122 -0
  80. package/agent/components/InputOTP.json +106 -0
  81. package/agent/components/Label.json +43 -0
  82. package/agent/components/LegalDocumentShell.json +102 -0
  83. package/agent/components/Legend.json +42 -0
  84. package/agent/components/LineChart.json +103 -0
  85. package/agent/components/Link.json +41 -0
  86. package/agent/components/ListRow.json +92 -0
  87. package/agent/components/Logo.json +85 -0
  88. package/agent/components/Marquee.json +91 -0
  89. package/agent/components/Masonry.json +82 -0
  90. package/agent/components/MasterDetail.json +95 -0
  91. package/agent/components/MegaMenu.json +120 -0
  92. package/agent/components/MobileShell.json +73 -0
  93. package/agent/components/NavList.json +63 -0
  94. package/agent/components/NumberInput.json +158 -0
  95. package/agent/components/OrgSwitcher.json +89 -0
  96. package/agent/components/OverlayPortalProvider.json +42 -0
  97. package/agent/components/PageContainer.json +181 -0
  98. package/agent/components/Pagination.json +132 -0
  99. package/agent/components/Paragraph.json +40 -0
  100. package/agent/components/PasswordInput.json +79 -0
  101. package/agent/components/PasswordStrength.json +51 -0
  102. package/agent/components/PermissionMatrix.json +81 -0
  103. package/agent/components/PieChart.json +99 -0
  104. package/agent/components/Popover.json +110 -0
  105. package/agent/components/PrefetchLink.json +65 -0
  106. package/agent/components/Progress.json +79 -0
  107. package/agent/components/Prose.json +57 -0
  108. package/agent/components/QrCode.json +62 -0
  109. package/agent/components/Radio.json +98 -0
  110. package/agent/components/RadioGroup.json +91 -0
  111. package/agent/components/RangeTimeline.json +80 -0
  112. package/agent/components/Rating.json +92 -0
  113. package/agent/components/ResizablePanel.json +69 -0
  114. package/agent/components/ResponsiveGrid.json +77 -0
  115. package/agent/components/Reveal.json +70 -0
  116. package/agent/components/ScrollArea.json +104 -0
  117. package/agent/components/SearchInput.json +98 -0
  118. package/agent/components/Segmented.json +96 -0
  119. package/agent/components/Select.json +397 -0
  120. package/agent/components/Separator.json +86 -0
  121. package/agent/components/ServiceCatalogCta.json +46 -0
  122. package/agent/components/ServiceLauncherCard.json +93 -0
  123. package/agent/components/ServiceRolePanel.json +83 -0
  124. package/agent/components/Sheet.json +85 -0
  125. package/agent/components/Sidebar.json +118 -0
  126. package/agent/components/Skeleton.json +57 -0
  127. package/agent/components/SkeletonArticle.json +71 -0
  128. package/agent/components/SkeletonAvatar.json +50 -0
  129. package/agent/components/SkeletonButton.json +57 -0
  130. package/agent/components/SkeletonForm.json +52 -0
  131. package/agent/components/SkeletonImage.json +37 -0
  132. package/agent/components/SkeletonInput.json +51 -0
  133. package/agent/components/SkeletonNode.json +42 -0
  134. package/agent/components/SkeletonRows.json +49 -0
  135. package/agent/components/SkeletonTable.json +45 -0
  136. package/agent/components/Slider.json +160 -0
  137. package/agent/components/SplitPane.json +66 -0
  138. package/agent/components/StatCard.json +83 -0
  139. package/agent/components/Steps.json +95 -0
  140. package/agent/components/Swatch.json +41 -0
  141. package/agent/components/Switch.json +81 -0
  142. package/agent/components/Table.json +112 -0
  143. package/agent/components/Tabs.json +158 -0
  144. package/agent/components/TagInput.json +105 -0
  145. package/agent/components/Text.json +201 -0
  146. package/agent/components/Textarea.json +126 -0
  147. package/agent/components/ThoughtChain.json +76 -0
  148. package/agent/components/Thumbnail.json +70 -0
  149. package/agent/components/TimePicker.json +200 -0
  150. package/agent/components/TimeRangePicker.json +90 -0
  151. package/agent/components/Timeline.json +47 -0
  152. package/agent/components/TimelineGrid.json +92 -0
  153. package/agent/components/Title.json +67 -0
  154. package/agent/components/Toaster.json +42 -0
  155. package/agent/components/Toggle.json +90 -0
  156. package/agent/components/ToggleGroup.json +102 -0
  157. package/agent/components/Toolbar.json +120 -0
  158. package/agent/components/Tooltip.json +110 -0
  159. package/agent/components/Topbar.json +83 -0
  160. package/agent/components/TopbarItem.json +79 -0
  161. package/agent/components/Transfer.json +141 -0
  162. package/agent/components/Tree.json +185 -0
  163. package/agent/components/TreeSelect.json +232 -0
  164. package/agent/components/TwoFactorSetup.json +79 -0
  165. package/agent/components/Typography.json +42 -0
  166. package/agent/components/Upload.json +221 -0
  167. package/agent/components/UploadCropDialog.json +60 -0
  168. package/agent/components/VisuallyHidden.json +20 -0
  169. package/agent/components/Welcome.json +65 -0
  170. package/agent/components/formatDate.json +46 -0
  171. package/agent/components/inertiaUpload.json +32 -0
  172. package/agent/components/useZodForm.json +39 -0
  173. package/agent/components-index.json +884 -0
  174. package/agent/components.json +15515 -0
  175. package/agent/index.json +56 -0
  176. package/agent/llms.txt +32 -0
  177. package/agent/patterns/account-recovery-settings.json +19 -0
  178. package/agent/patterns/async-data-state.json +20 -0
  179. package/agent/patterns/auth-recovery-panels.json +29 -0
  180. package/agent/patterns/badge-coloring.json +14 -0
  181. package/agent/patterns/common-fixes.json +16 -0
  182. package/agent/patterns/confirm-destructive.json +11 -0
  183. package/agent/patterns/data-table-page.json +18 -0
  184. package/agent/patterns/deferred-loading.json +12 -0
  185. package/agent/patterns/error-pages.json +28 -0
  186. package/agent/patterns/inertia-detail-page.json +13 -0
  187. package/agent/patterns/inertia-list-page.json +15 -0
  188. package/agent/patterns/inertia-persistent-layout.json +14 -0
  189. package/agent/patterns/organization-memberships.json +19 -0
  190. package/agent/patterns/page-sections.json +18 -0
  191. package/agent/patterns/settings-page-responsive.json +18 -0
  192. package/agent/patterns/settings-section-rows.json +23 -0
  193. package/agent/patterns/signup-form.json +13 -0
  194. package/agent/patterns/topbar-account-chip.json +18 -0
  195. package/agent/patterns/transactional-email.json +22 -0
  196. package/agent/patterns-index.json +323 -0
  197. package/agent/patterns.json +342 -0
  198. package/agent/rules.json +237 -0
  199. package/agent/tokens.json +8427 -0
  200. package/agent/vocabulary.json +198 -0
  201. package/dist/components/data-display/service-launcher-card.d.ts +19 -0
  202. package/dist/components/data-display/service-launcher-card.js +14 -1
  203. package/dist/components/data-entry/attachments.js +77 -33
  204. package/dist/components/data-entry/input.js +8 -1
  205. package/dist/components/layout/flex.d.ts +2 -2
  206. package/dist/components/layout/flex.js +2 -0
  207. package/dist/components/ui/avatar.d.ts +1 -18
  208. package/dist/components/ui/avatar.js +1 -36
  209. package/dist/components/ui/tag-input.d.ts +10 -0
  210. package/dist/components/ui/tag-input.js +35 -2
  211. package/dist/contracts/measurement.json +1 -1
  212. package/dist/i18n/messages/en.json +190 -1
  213. package/dist/i18n/messages/ja.json +188 -1
  214. package/dist/i18n/messages/vi.json +188 -1
  215. package/dist/lib/image-loading-status.d.ts +25 -0
  216. package/dist/lib/image-loading-status.js +41 -0
  217. package/dist/props/components/data-entry.prop.d.ts +21 -2
  218. package/dist/props/components/layout.prop.d.ts +42 -0
  219. package/dist/props/registry.d.ts +14 -1
  220. package/dist/props/registry.js +18 -1
  221. package/dist/styles/card-layout.css +10 -4
  222. package/dist/styles/control.css +33 -4
  223. package/dist/styles/data-display-layout.css +1 -1
  224. package/dist/styles/data-entry-layout.css +245 -2
  225. package/dist/styles/layout.css +17 -0
  226. package/dist/styles/navigation-layout.css +3 -1
  227. package/dist/styles/shell-layout.css +2 -0
  228. package/dist/styles/table-layout.css +50 -9
  229. package/dist/tokens/components/attachments.css +18 -9
  230. package/dist/tokens/components/segmented.css +7 -3
  231. package/dist/tokens/components/table.css +2 -1
  232. package/docs/COMPOSITION-VS-COMPONENT.md +41 -0
  233. package/docs/DESIGN-AUTHORITY.md +52 -0
  234. package/docs/DEVELOPMENT.md +81 -6
  235. package/docs/assets/service-mark-rose.svg +6 -0
  236. package/docs/assets/service-mark-teal.svg +5 -0
  237. package/docs/data-display/service-launcher-card.tsx +232 -92
  238. package/docs/data-entry/tag-input.tsx +37 -0
  239. package/docs/layout/flex.tsx +40 -0
  240. package/docs/roadmap/website-components.md +34 -0
  241. package/docs/showcase/marketing-page.tsx +54 -45
  242. package/docs/showcase/table-pagination.tsx +99 -18
  243. package/docs/showcase/theme-customization.tsx +25 -2
  244. package/package.json +8 -5
@@ -0,0 +1,92 @@
1
+ {
2
+ "example": "import { Card, CardContent, CardHeader, CardTitle, ListRow, Badge } from \"@godxjp/ui/data-display\";\nimport { Button } from \"@godxjp/ui/general\";\nimport { Smartphone } from \"lucide-react\";\nimport { Flex } from \"@godxjp/ui/layout\";\n\n// The three scenarios below are three CARDS on one page, so they are wrapped in a Flex — the gap\n// between sibling cards belongs to the stack, never to the cards (audit: sibling-cards-need-flex).\n<Flex direction=\"col\" gap=\"lg\">\n<Card>\n <CardHeader>\n <CardTitle>アクティブなセッション</CardTitle>\n </CardHeader>\n <CardContent flush>\n <ListRow\n leading={<Smartphone aria-hidden=\"true\" className=\"size-4\" />}\n title=\"iPhone 15 · Tokyo\"\n description=\"最終アクセス 2分前\"\n trailing={<Badge status=\"active\" />}\n />\n <ListRow\n leading={<Smartphone aria-hidden=\"true\" className=\"size-4\" />}\n title=\"MacBook Pro · Osaka\"\n description=\"最終アクセス 3日前\"\n trailing={<Button size=\"xs\" variant=\"outline\">ログアウト</Button>}\n />\n </CardContent>\n</Card>\n\n// Notifications — unread dot + emphasized surface, wrapping title, two inline actions\n<Card>\n <CardContent flush>\n <ListRow\n unread\n align=\"start\"\n overflow=\"wrap\"\n title=\"組織「グローバル・トランスフォーメーション推進本部」への招待が届いています\"\n description=\"2026-07-30 09:12 JST\"\n trailing={\n <>\n <Button size=\"xs\" variant=\"ghost\">既読にする</Button>\n <Button size=\"xs\" variant=\"outline\">開く</Button>\n </>\n }\n />\n <ListRow unread={false} align=\"start\" overflow=\"wrap\" title=\"請求書が発行されました\" description=\"2026-07-28 18:40 JST\" />\n </CardContent>\n</Card>\n\n// A list of LINKS — `as=\"li\"` gives the list item, `asChild` gives the whole-row link,\n// and the item carries the divider. Never wrap the row in your own <li>, and never reach for a\n// raw <ul>: `marker=\"none\"` is the semantic list container (no bullet, no indent, gap token).\n<Card>\n <CardContent flush>\n <Flex as=\"ul\" marker=\"none\" direction=\"col\" gap=\"none\">\n {projects.map((project) => (\n <ListRow key={project.key} as=\"li\" asChild title={project.name} description={project.key}>\n <Link href={`/projects/${project.key}`} />\n </ListRow>\n ))}\n </Flex>\n </CardContent>\n</Card>\n</Flex>",
3
+ "group": "data-display",
4
+ "importPath": "@godxjp/ui/data-display",
5
+ "name": "ListRow",
6
+ "props": [
7
+ {
8
+ "defaultValue": "false",
9
+ "description": "Supply a link child to make the entire row a native link. Use aria-current=page for the current destination; never nest interactive trailing controls. Combine with `as=\"li\"` inside a `<Flex as=\"ul\" marker=\"none\">` — the row stays the link and `as` supplies the list item around it.",
10
+ "name": "asChild",
11
+ "type": "boolean"
12
+ },
13
+ {
14
+ "description": "Primary line — rendered in medium weight; truncates to one line.",
15
+ "name": "title",
16
+ "required": true,
17
+ "type": "ReactNode"
18
+ },
19
+ {
20
+ "description": "Secondary line under the title (muted, xs); truncates to one line. ONE line, deliberately: a row that wants two (an endpoint, then its events) is asking for a different surface — antd `List.Item.Meta` has the same single `description`, so this is NOT an antd parity gap. Put the second line in `trailing` (a Badge, a count) or move the row to `Descriptions` / a detail panel (gh#714 §3).",
21
+ "name": "description",
22
+ "type": "ReactNode"
23
+ },
24
+ {
25
+ "description": "Leading slot — a decorative icon or an Avatar. Mark a purely decorative icon `aria-hidden`.",
26
+ "name": "leading",
27
+ "type": "ReactNode"
28
+ },
29
+ {
30
+ "description": "Trailing slot — the row action(s): a Button / DropdownMenu trigger, a Badge, or a Switch.",
31
+ "name": "trailing",
32
+ "type": "ReactNode"
33
+ },
34
+ {
35
+ "defaultValue": "\"center\"",
36
+ "description": "Cross-axis alignment of the columns; `start` for multi-line content.",
37
+ "name": "align",
38
+ "type": "\"center\" | \"start\""
39
+ },
40
+ {
41
+ "defaultValue": "\"div\"",
42
+ "description": "Render element — `li` when the parent is a semantic list (`<Flex as=\"ul\" marker=\"none\">` — a raw `<ul>` carries no gap token and, without `marker=\"none\"`, the bullet rule `.ui-flex[data-list] > li { display: list-item }` outranks `display: flex` on the row itself). WITH `asChild` the child owns the row element, so `as` becomes the list ITEM around it: `<li data-slot=\"list-row-item\"><a data-slot=\"list-row\">`. Use both for a list of links — do not wrap the row in an `<li>` (or a `role=\"listitem\"` div) yourself, because that makes every row an only child and the row-to-row divider, keyed on `:not(:last-child)` among siblings, stops matching on every row.",
43
+ "name": "as",
44
+ "type": "\"div\" | \"li\""
45
+ },
46
+ {
47
+ "defaultValue": "\"truncate\"",
48
+ "description": "How title/description resolve content longer than the row — `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.",
49
+ "name": "overflow",
50
+ "type": "\"truncate\" | \"wrap\""
51
+ },
52
+ {
53
+ "defaultValue": "\"default\"",
54
+ "description": "Row geometry. It only lowers thresholds — 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.",
55
+ "name": "density",
56
+ "type": "\"default\" | \"compact\""
57
+ },
58
+ {
59
+ "description": "Read/unread state for notification rows — 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.",
60
+ "name": "unread",
61
+ "type": "boolean"
62
+ }
63
+ ],
64
+ "related": [
65
+ "DataTable — use instead when the list is long or needs sorting/selection/pagination; ListRow is for short, chrome-light lists.",
66
+ "Card — ListRow is designed to live inside `<CardContent flush>`; the Card supplies the outer surface and the closing border.",
67
+ "Descriptions — for a key/value metadata grid on a detail page (no per-row action); ListRow is for actionable entity rows."
68
+ ],
69
+ "rules": [
70
+ 42,
71
+ 44
72
+ ],
73
+ "storyPath": "data-display/ListRow.stories.tsx",
74
+ "tagline": "Single-line entity row (leading · title/description · trailing action) for SHORT lists inside a Card — sessions, API tokens, linked accounts, passkeys, MFA factors, invitations.",
75
+ "usage": [
76
+ "DO use ListRow for a SHORT (≈2–8 item) list of entities inside a Card where each row is one line with an action — 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.",
77
+ "DON'T reach for DataTable here — 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.",
78
+ "DO write a list of LINKS as `<Flex as=\"ul\" marker=\"none\" direction=\"col\" gap=\"none\">` + `<ListRow as=\"li\" asChild><Link/></ListRow>` — one call gives the list item, the whole-row link and the divider. DON'T wrap the row in your own `<li>` or `role=\"listitem\"` element to get list semantics back: the divider rule reads `:not(:last-child)` among the rows themselves, so a wrapper per row makes each one an only child and EVERY divider disappears — silently, which is why `ui-audit` now flags it as `no-hand-rolled-list`.",
79
+ "DON'T hand-roll `<div className=\"flex items-center justify-between border-b py-3\">` — that is exactly the repeated pattern ListRow replaces (border/radius/padding are tokenized via `--list-row-*`).",
80
+ "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 list, and build that list as `<Flex as=\"ul\" marker=\"none\">` — `ui-audit` flags a raw `<ul>`/`role=\"list\"` as `no-hand-rolled-list`.",
81
+ "DO use `unread` for a notification list — 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))` — 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` — that renders a labelled pill, not a compact status dot.",
82
+ "DO pass `density=\"compact\"` for the canonical invitation / history row — an Avatar, a title (+ description) and one or two small trailing Buttons that must read as ONE line inside a narrow card (≈326px 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 — the default density is the entity-row measure.",
83
+ "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."
84
+ ],
85
+ "useCases": [
86
+ "Account security page — a list of active sessions (device + last-seen as title/description, a destructive 'Revoke' Button in trailing).",
87
+ "Developer settings — API tokens or passkeys, each row showing the name + created date and a DropdownMenu of actions.",
88
+ "Linked accounts / SSO — an IdP icon in leading, the provider name + connected email, and a Switch or 'Disconnect' Button trailing.",
89
+ "Notifications inbox — `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.",
90
+ "Pending invitations — 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."
91
+ ]
92
+ }
@@ -0,0 +1,85 @@
1
+ {
2
+ "example": "import { Logo } from \"@godxjp/ui/general\";\n\n// Full lockup — mark + wordmark as ONE element (no wrapper, no page CSS).\n<Logo glyph=\"c\" wordmark=\"CoreBooks\" />\n\n// Canonical brand-green GoDX lockup — identity role, independent of --primary.\n<Logo mark=\"godx\" tone=\"success\" wordmark=\"GoDX\" />\n\n// Bare mark standing alone → give it an accessible name.\n<Logo label=\"CoreBooks\" size=\"lg\" />\n\n// Product lockup — \"GoDX | ID\", one master, package-owned divider and gaps.\n<Logo mark=\"godx-lockup\" productSuffix=\"ID\" />\n\n// The whole lockup as a link — the <a> IS the lockup, no wrapper to align.\n<Logo asChild mark=\"godx\" tone=\"success\" wordmark=\"GoDX\">\n <a href=\"/\" />\n</Logo>",
3
+ "group": "general",
4
+ "importPath": "@godxjp/ui/general",
5
+ "name": "Logo",
6
+ "props": [
7
+ {
8
+ "defaultValue": "\"g\"",
9
+ "description": "The brand glyph — a short mark (letter/initials) or a custom inline <svg>. Only read when mark=\"glyph\".",
10
+ "name": "glyph",
11
+ "type": "React.ReactNode"
12
+ },
13
+ {
14
+ "defaultValue": "\"glyph\"",
15
+ "description": "\"godx-lockup\" renders the FULL master lockup — identity mark + the drawn \"GoDX\" logotype as one artwork (4.787:1, so the `size` tier drives HEIGHT and width follows); pair it with `productSuffix` for \"GoDX | ID\". \"godx\" renders THE CANONICAL GoDX IDENTITY MARK as an inline vector owned by the package — 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.",
16
+ "name": "mark",
17
+ "type": "\"glyph\" | \"godx\" | \"godx-lockup\""
18
+ },
19
+ {
20
+ "defaultValue": "\"md\"",
21
+ "description": "Box size tier (tokenised) — 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 — 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).",
22
+ "name": "size",
23
+ "type": "\"xs\" | \"sm\" | \"md\" | \"lg\""
24
+ },
25
+ {
26
+ "defaultValue": "\"primary\"",
27
+ "description": "Semantic fill role. 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.",
28
+ "name": "tone",
29
+ "type": "\"primary\" | \"success\""
30
+ },
31
+ {
32
+ "description": "Readable product name rendered BESIDE the mark as ONE lockup — 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/…props; 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↔wordmark 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.",
33
+ "name": "wordmark",
34
+ "type": "React.ReactNode"
35
+ },
36
+ {
37
+ "description": "The PRODUCT name that follows the brand, set off by the lockup's own rule — `<Logo mark=\"godx-lockup\" productSuffix=\"ID\" />` renders \"GoDX | ID\" (later \"GoDX | Console\", \"GoDX | Admin\"). It renders after `wordmark` when both are set, and it turns Logo into a lockup on its own. The suffix is TEXT in the lockup's own type scale, NEVER a second artwork file: the rule's colour/width/height (--logo-divider-color / -width / -height / -alpha, the logotype ink at 0.25 alpha so it has a dark theme the kit's hardcoded #C5C8D6 does not) and the gap around it (--logo-product-suffix-gap) are package tokens. With mark=\"godx-lockup\" and no `wordmark` the brand half is DRAWN (paths, unreachable by AT), so the package restores \"GoDX\" as sr-only text and the lockup is announced \"GoDX <suffix>\" — as TEXT rather than role=\"img\"+aria-label, so an asChild link keeps its link role.",
38
+ "name": "productSuffix",
39
+ "type": "React.ReactNode"
40
+ },
41
+ {
42
+ "description": "Accessible name. Set → exposed as a named image (role img); omitted → 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). It also overrides the \"GoDX <productSuffix>\" name a drawn-logotype lockup composes for itself (role=\"img\" makes the sr-only brand word presentational).",
43
+ "name": "label",
44
+ "type": "string"
45
+ },
46
+ {
47
+ "description": "Borrow the single child element as the logo ROOT instead of rendering a <span> — the way to make the whole logo a LINK (<a>, or a router <Link>). The borrowed tag itself carries data-slot=\"logo-lockup\"/.ui-logo-lockup and the mark + wordmark become its children, so there is no wrapper element between the link and the lockup. Use it instead of wrapping Logo in your own <a>: .ui-logo-lockup is display:inline-flex, so a plain <a> (display:inline) puts the lockup on a line box and the strut's descender nudges the mark up in a topbar — the only fix left to the consumer is className=\"flex\" on the <a>, which ui-audit rejects as no-utility-layout.",
48
+ "name": "asChild",
49
+ "type": "boolean"
50
+ }
51
+ ],
52
+ "related": [
53
+ "AuthIdentity (@godxjp/ui/layout) — the canonical auth heading block; it already renders the GoDX mark, so don't add a second Logo above it.",
54
+ "Text / Heading — 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).",
55
+ "Avatar — 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."
56
+ ],
57
+ "rules": [
58
+ 45,
59
+ 46
60
+ ],
61
+ "storyPath": "general/Logo.stories.tsx",
62
+ "tagline": "The product brand mark — 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.",
63
+ "usage": [
64
+ "DO import from `@godxjp/ui/general`: `import { Logo } from \"@godxjp/ui/general\";`",
65
+ "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>` — that repeats literal size/radius and puts type utilities on a bare span (rules #45/#46).",
66
+ "DO leave `label` unset when a readable wordmark sits beside the mark (shell header, topbar) — the mark stays decorative and the wordmark carries the accessible name. Set `label` only when the mark stands alone.",
67
+ "DON'T pass more than 1–2 glyphs — the box is square and centres its content; a long string overflows. The product NAME goes in `wordmark`, never in `glyph`.",
68
+ "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>` — 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.",
69
+ "NOTE: the package ships NO wordmark ARTWORK — `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.",
70
+ "DO use `<Logo mark=\"godx-lockup\" productSuffix=\"ID\" />` for a product surface branded \"GoDX | ID\" — the divider, the gaps, the `size` scale and the light/dark master are the package's. DON'T inline the kit's flattened \"GoDX | ID\" SVG in the app: it is a second master in its own coordinate system, with global `id=\"title\"`/`id=\"desc\"` that collide across instances, a hardcoded `#0B0F3B` ink with no dark variant, and a hardcoded `#C5C8D6` rule (gh#649).",
71
+ "DON'T typeset the suffix yourself as `<Logo mark=\"godx-lockup\" /><Text>ID</Text>` — that has no divider and the spacing is not the lockup's token.",
72
+ "DON'T re-tint via `className=\"bg-*\"` — the fill reads the `--primary` role token; retune it through a service theme (`--primary`, `--logo-radius`, `--logo-size-*`), not utilities.",
73
+ "DO use `mark=\"godx\"` for the canonical GoDX identity mark on hosted-identity screens — 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.",
74
+ "DO make the brand a link with `asChild`, not with a wrapper: `<Logo asChild mark=\"godx\" wordmark=\"GoDX\"><Link href=\"/\" /></Logo>`. Writing `<a className=\"flex\"><Logo …/></a>` instead is the exact shape ui-audit rejects (no-utility-layout), and dropping the `flex` misaligns the mark."
75
+ ],
76
+ "useCases": [
77
+ "App-shell header brand lockup — `<Logo glyph=\"c\" wordmark=\"CoreBooks\" />` in the sidebar/topbar: mark decorative, wordmark readable, spacing tokenized.",
78
+ "Auth screen — a standalone labelled mark above the sign-in form: `<Logo label=\"CoreBooks\" size=\"lg\" />`.",
79
+ "Tenant/workspace switcher row — a small `size=\"sm\"` mark as the leading slot of a ListRow or menu item.",
80
+ "Custom SVG brand — pass an inline `<svg>` as `glyph` to render a real logomark on the primary fill instead of a letter.",
81
+ "Hosted GoDX identity surface — `<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.",
82
+ "Brand lockup in a shell header / auth brand bar — `<Logo mark=\"godx\" tone=\"success\" wordmark=\"GoDX\" />`: one element, brand-green, no wrapper div and no page CSS.",
83
+ "Product-branded surface (\"GoDX ID\" legal pages, a Console/Admin topbar) — `<Logo mark=\"godx-lockup\" productSuffix=\"ID\" />`: the master lockup, the package's divider, and one accessible name, \"GoDX ID\"."
84
+ ]
85
+ }
@@ -0,0 +1,91 @@
1
+ {
2
+ "docPath": "data-display/marquee.tsx",
3
+ "example": "import { Marquee } from \"@godxjp/ui/data-display\";\nimport { Text } from \"@godxjp/ui/general\";\n\n<Marquee fade pauseOnHover label={t(\"partners.pauseLogos\")}>\n {partners.map((partner) => (\n <Text key={partner.id} size=\"sm\" tone=\"muted\">\n {partner.name}\n </Text>\n ))}\n</Marquee>",
4
+ "group": "data-display",
5
+ "importPath": "@godxjp/ui/data-display",
6
+ "name": "Marquee",
7
+ "props": [
8
+ {
9
+ "description": "The row of content. Rendered ONCE for real; every further copy that fills the track is an aria-hidden + inert clone, so the accessibility tree and the tab order see it exactly once however wide the viewport is.",
10
+ "name": "children",
11
+ "type": "ReactNode"
12
+ },
13
+ {
14
+ "description": "Controlled motion state; pass with `onPlayChange`. Exists so one page-level 'stop all motion' switch can halt every track at once. The built-in pause control stays either way.",
15
+ "name": "play",
16
+ "type": "boolean"
17
+ },
18
+ {
19
+ "description": "Uncontrolled initial state. Default true. Set false where the motion is not the point of the screen: WebAIM recommends animated content be paused by default.",
20
+ "name": "defaultPlay",
21
+ "type": "boolean"
22
+ },
23
+ {
24
+ "description": "Fires on every transition, from the built-in control or a controlled write.",
25
+ "name": "onPlayChange",
26
+ "type": "(play: boolean) => void"
27
+ },
28
+ {
29
+ "description": "The edge the content travels TOWARDS, spelled logically. Default `start`, which follows the reading direction; both members mirror under dir=\"rtl\" with no prop change.",
30
+ "name": "direction",
31
+ "type": "\"start\" | \"end\""
32
+ },
33
+ {
34
+ "description": "Pace ordinal over the `--marquee-interval` motion token (a duration per SCREENFUL, so the pace is the same with one item or forty). Default `base`. Not px/s: a raw number is neither themeable nor width-aware.",
35
+ "name": "speed",
36
+ "type": "\"slow\" | \"base\" | \"fast\""
37
+ },
38
+ {
39
+ "description": "Space between items AND between copies, as a token step. Defaults to the `--marquee-gap-inline` token.",
40
+ "name": "gap",
41
+ "type": "GapProp"
42
+ },
43
+ {
44
+ "description": "Also pause under the pointer. Default false. An ADDITION to the pause control, never the mechanism. Focus inside the track always pauses it regardless.",
45
+ "name": "pauseOnHover",
46
+ "type": "boolean"
47
+ },
48
+ {
49
+ "description": "Mask both edges over `--marquee-mask-width`. Default false. A mask, not an opaque gradient colour, so it follows a themed or dark background.",
50
+ "name": "fade",
51
+ "type": "boolean"
52
+ },
53
+ {
54
+ "description": "Names the CONTENT (\"partner logos\"), not the button. The control's accessible name is composed from it and the state verb ('Pause scrolling: partner logos'), so the name still flips when the state does. A non-string node is ignored.",
55
+ "name": "label",
56
+ "type": "ReactNode"
57
+ },
58
+ {
59
+ "description": "Structural class on the root.",
60
+ "name": "className",
61
+ "type": "string"
62
+ }
63
+ ],
64
+ "related": [
65
+ "Carousel — the answer whenever the reader should STEP through discrete items. It is user-driven, has prev/next and dots, and does not move on its own.",
66
+ "Activity — ambient motion with no travel: a pulse or three dots that say something is happening, with no content to read.",
67
+ "ScrollArea — what a Marquee becomes under prefers-reduced-motion, and what to reach for directly when the row should simply be scrollable.",
68
+ "Flex / ResponsiveGrid — the static logo wall. Start here; add motion only when something asks for it."
69
+ ],
70
+ "rules": [
71
+ 2,
72
+ 44,
73
+ 45
74
+ ],
75
+ "storyPath": "data-display/Marquee.stories.tsx",
76
+ "tagline": "A track of content that travels continuously, carrying the WCAG 2.2.2 pause control that makes it conformant. The clone count is MEASURED against the viewport and re-measured on resize; under prefers-reduced-motion it does not move at all.",
77
+ "usage": [
78
+ "DO leave the pause control alone. WCAG 2.2 SC 2.2.2 makes it a conformance requirement for anything that moves automatically for more than five seconds beside other content, and technique F16 names 'a scrolling news ticker without a mechanism to pause it' as the failure by example.",
79
+ "DO ask first whether it should move at all. Nielsen Norman Group and WebAIM both discourage auto-moving content, and no major design system ships a ticker. A static `Flex wrap` or `ResponsiveGrid` logo wall is usually the better screen; `defaultPlay={false}` is the middle ground.",
80
+ "DO use `play` / `onPlayChange` for a page-level 'reduce motion on this screen' switch across several tracks.",
81
+ "DON'T reach for `pauseOnHover` as the pause mechanism. A keyboard user never hovers; that is exactly the gap in `react-fast-marquee`, whose clones also carry no `aria-hidden`.",
82
+ "DON'T put essential, non-duplicated information in it. A reader who looks away misses it, and under prefers-reduced-motion the row becomes a scrollable strip instead.",
83
+ "DON'T wrap it in your own overflow/animation CSS. The clone count and the lap distance are measured; a second overflow box breaks the measurement and the seam.",
84
+ "DON'T add a role. `role=\"marquee\"` is a LIVE REGION for frequently CHANGING content; here nothing changes, only its position. Wrap it in a labelled <section> when the collection needs a name."
85
+ ],
86
+ "useCases": [
87
+ "A partner or customer logo wall on a marketing page, where the row is wider than the viewport.",
88
+ "An announcement or status strip above an application shell (maintenance windows, release notes) that must stay pausable.",
89
+ "A 'now processing' rail of job identifiers beside a dashboard, where the motion signals activity rather than carrying the data."
90
+ ]
91
+ }
@@ -0,0 +1,82 @@
1
+ {
2
+ "docPath": "layout/masonry.tsx",
3
+ "example": "import { Masonry } from \"@godxjp/ui/layout\";\nimport { Card, CardContent } from \"@godxjp/ui/data-display\";\nimport { Text } from \"@godxjp/ui/general\";\n\n<Masonry\n columns={{ base: 1, sm: 2, lg: 3 }}\n gap=\"md\"\n items={notes.map((note) => ({ key: note.id, data: note }))}\n itemRender={({ data }) => (\n <Card>\n <CardContent>\n <Text>{data.body}</Text>\n </CardContent>\n </Card>\n )}\n onLayoutChange={(layout) => console.log(layout.length, \"tiles placed\")}\n/>",
4
+ "group": "layout",
5
+ "importPath": "@godxjp/ui/layout",
6
+ "name": "Masonry",
7
+ "props": [
8
+ {
9
+ "description": "The tiles, in READING order — which is also DOM order. Each is { key, children?, column?, height?, data? }, antd's MasonryItem field for field.",
10
+ "name": "items",
11
+ "type": "MasonryItemProp<TData>[]"
12
+ },
13
+ {
14
+ "description": "Renders a tile that carries no `children`. antd's rule is ported exactly: `children` wins over `itemRender`, and the callback receives the live index and column.",
15
+ "name": "itemRender",
16
+ "type": "(item: MasonryItemProp<TData> & { index: number; column: number }) => ReactNode"
17
+ },
18
+ {
19
+ "defaultValue": "3",
20
+ "description": "Column count, fixed or per VIEWPORT breakpoint (sm 40rem · md 48rem · lg 64rem · xl 80rem), taking the widest matching declared step and falling back to `base`. antd's 3 is kept. antd spells the steps xs..xxl; `xs` IS `base` here and there is no `xxl` — both are a TypeScript error and a dev-time warning, never a silent drop.",
21
+ "name": "columns",
22
+ "type": "number | { base?: number; sm?: number; md?: number; lg?: number; xl?: number }"
23
+ },
24
+ {
25
+ "defaultValue": "(none — the --masonry-gap-* tokens, which default to 0 like antd)",
26
+ "description": "Spacing between tiles. THIS IS ANT DESIGN'S `gutter`, renamed: this package already owns the axis as `gap` (Flex, ResponsiveGrid, AuthStack) and takes a GapProp token step rather than a pixel number, so a masonry breathes with --scaling. The tuple is antd's [Gap, Gap] = [inline, block]. Passing `gutter` is a compile error that names `gap`.",
27
+ "name": "gap",
28
+ "type": "GapProp | [GapProp, GapProp]"
29
+ },
30
+ {
31
+ "defaultValue": "false",
32
+ "description": "antd `fresh`. Wraps EVERY tile in its own ResizeObserver so a tile that changes height in place — a chart settling, a 'show more', a late image — re-packs the columns. Off, only the container's own resize and a descendant load/error re-measure.",
33
+ "name": "fresh",
34
+ "type": "boolean"
35
+ },
36
+ {
37
+ "description": "Fires once every tile has a resolved position and the count matches `items`, and is deduped on an unchanged assignment. antd DOCUMENTS `{ key, column }[]` but its implementation spreads the whole item; the implementation is what is ported, so `data` comes back with it.",
38
+ "name": "onLayoutChange",
39
+ "type": "(layout: (MasonryItemProp<TData> & { column: number })[]) => void"
40
+ },
41
+ {
42
+ "description": "DOM id of the container.",
43
+ "name": "id",
44
+ "type": "string"
45
+ },
46
+ {
47
+ "description": "Structural class on the container.",
48
+ "name": "className",
49
+ "type": "string"
50
+ }
51
+ ],
52
+ "related": [
53
+ "ResponsiveGrid — the answer whenever the tiles are or can be EQUAL height. It is pure CSS grid with container queries, needs no measurement, and keeps DOM order and visual order identical. Reach for Masonry only when the heights genuinely differ.",
54
+ "Flex — a single row or column of mixed-width content; no columns, no packing.",
55
+ "Card / CardContent — the usual tile. Masonry positions the tile box and never styles what is inside it.",
56
+ "AspectRatio — pair it with `height` when the tiles are images of a known ratio; the layout then settles with no measurement pass at all."
57
+ ],
58
+ "rules": [
59
+ 2,
60
+ 40,
61
+ 44,
62
+ 45
63
+ ],
64
+ "storyPath": "layout/Masonry.stories.tsx",
65
+ "tagline": "Ant Design `Masonry` (6.0.0): tiles of UNEQUAL height packed into columns, each tile dropped into whichever column is shortest when its turn comes. DOM order stays `items` order at every width, so the reading order never follows the packing.",
66
+ "usage": [
67
+ "DO order `items` by IMPORTANCE, never by height. The tiles are absolutely positioned, so DOM order — what a screen reader reads and what Tab visits — is always the `items` order, while the visual order is the packing. A later tile can sit visually above an earlier one; that is the form, not a bug.",
68
+ "DO give every tile a stable `key`. It is the identity the measurement cache and `onLayoutChange` are keyed on, not React's element key alone.",
69
+ "DO pass `height` when you already know it (a fixed-ratio thumbnail, a server-rendered feed). It skips the measurement pass, so the first paint lands in the right place instead of settling a frame later.",
70
+ "DO reach for `fresh` only when tiles change height ON THEIR OWN. It is one ResizeObserver per tile; a static feed does not need it.",
71
+ "DON'T use `gutter` — that is antd's name for `gap` here, and it is typed `never` so the compiler says so. `gap` takes a token step: gap=\"md\", gap={4}, gap={[\"sm\", \"md\"]}.",
72
+ "DON'T write `columns={{ xs: 1 }}`. The mobile-first floor is `base`, and `xxl` does not exist here; both spellings are rejected at compile time and warned about at runtime.",
73
+ "DON'T add a role. A masonry is a layout and WAI-ARIA 1.2 has no role for one; `region` would collide with landmark-unique, `list` would announce structure the tiles already carry. Wrap it in a labelled <section> when the COLLECTION needs a name.",
74
+ "DON'T use it for equal-height tiles — that is ResponsiveGrid, which needs no measurement and no JavaScript."
75
+ ],
76
+ "useCases": [
77
+ "A media or document gallery where thumbnails have different aspect ratios and a fixed-row grid would leave a ragged band of whitespace under every row.",
78
+ "A feed of notes, comments or activity cards whose text length varies wildly — a one-line entry beside a twelve-line one.",
79
+ "A dashboard of report cards of different depth (a two-row summary beside a ten-row table) that should still fill the page evenly.",
80
+ "A pinned-column layout: one tile held in the first column with `column: 0` (a filter panel, a promoted card) while the rest of the feed packs around it."
81
+ ]
82
+ }
@@ -0,0 +1,95 @@
1
+ {
2
+ "example": "import { MasterDetail } from \"@godxjp/ui/layout\";\n\n// Canonical: fluid list + fixed 320px detail rail (stacks below 40rem).\n<MasterDetail\n masterLabel=\"Teams\"\n detailLabel=\"Selected team\"\n detailId=\"team-detail\"\n master={<TeamTable onRowClick={select} detailId=\"team-detail\" />}\n>\n <TeamDetail team={selected} />\n</MasterDetail>\n\n// Leading navigator rail instead.\n<MasterDetail rail=\"master\" railWidth=\"compact\" masterLabel=\"Categories\">\n <SettingsForm />\n</MasterDetail>\n\n// A long real collection: bound the master so it scrolls in place and the detail\n// stays near the top of a stacked mobile page.\n<MasterDetail masterViewport=\"compact\" masterLabel=\"Members\" detailLabel=\"Selected member\">\n <MemberDetail member={selected} />\n</MasterDetail>",
3
+ "group": "layout",
4
+ "importPath": "@godxjp/ui/layout",
5
+ "name": "MasterDetail",
6
+ "props": [
7
+ {
8
+ "description": "One-pane navigation below the token-owned collapse threshold; omit to retain stacking. The consumer owns URL/history selection.",
9
+ "name": "mobilePane",
10
+ "type": "\"master\" | \"detail\""
11
+ },
12
+ {
13
+ "description": "Back link shown above detail in mobile navigation mode.",
14
+ "name": "detailBack",
15
+ "type": "ReactNode"
16
+ },
17
+ {
18
+ "description": "Selectable collection. Always FIRST in DOM order, so the stacked (mobile) order is list-then-detail whichever region owns the rail.",
19
+ "name": "master",
20
+ "required": true,
21
+ "type": "ReactNode"
22
+ },
23
+ {
24
+ "description": "Detail surface for the current master selection.",
25
+ "name": "children",
26
+ "required": true,
27
+ "type": "ReactNode"
28
+ },
29
+ {
30
+ "defaultValue": "\"detail\"",
31
+ "description": "`master` = leading category/navigator rail beside a fluid detail surface.",
32
+ "name": "rail",
33
+ "type": "\"master\" | \"detail\""
34
+ },
35
+ {
36
+ "description": "Token rail steps: 12rem, 18.75rem, 20rem, 24rem.",
37
+ "name": "railWidth",
38
+ "type": "\"narrow\" | \"compact\" | \"standard\" | \"wide\""
39
+ },
40
+ {
41
+ "defaultValue": "\"auto\"",
42
+ "description": "Bound the master collection to a scrollable viewport. `auto` (default) never bounds it — 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 — retune the tokens in a service theme.",
43
+ "name": "masterViewport",
44
+ "type": "\"auto\" | \"compact\" | \"standard\""
45
+ },
46
+ {
47
+ "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) — that is the global knob a service theme retunes once.",
48
+ "name": "collapseBelow",
49
+ "type": "\"sm\" | \"md\" | \"lg\" | \"xl\" | false"
50
+ },
51
+ {
52
+ "description": "Accessible name for the master region landmark.",
53
+ "name": "masterLabel",
54
+ "type": "string"
55
+ },
56
+ {
57
+ "description": "Accessible name for the detail region landmark.",
58
+ "name": "detailLabel",
59
+ "type": "string"
60
+ },
61
+ {
62
+ "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).",
63
+ "name": "detailId",
64
+ "type": "string"
65
+ }
66
+ ],
67
+ "related": [
68
+ "SplitPane — 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.",
69
+ "ResponsiveGrid — use for equal-width independent cards, never for a master-detail relationship.",
70
+ "PageContainer — outer page scaffold that supplies page insets and title hierarchy around MasterDetail."
71
+ ],
72
+ "rules": [
73
+ 24,
74
+ 40
75
+ ],
76
+ "storyPath": "layout/MasterDetail.stories.tsx",
77
+ "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 — default `detail` (the canonical 1fr/320px list + detail rail); `master` for a leading navigator rail.",
78
+ "usage": [
79
+ "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.",
80
+ "DO keep selection and keyboard semantics on the controls inside `master` (`aria-pressed`, roving focus, or listbox semantics as appropriate) — only the caller knows which APG pattern applies; MasterDetail preserves whatever you render.",
81
+ "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).",
82
+ "DO choose `compact` for the 300px rail and `standard` for 320px. Never reproduce these tracks with consumer CSS.",
83
+ "DO provide `masterLabel` and `detailLabel` when the surrounding headings do not already identify both regions.",
84
+ "Use ResponsiveGrid.Item span for proportional tracks, or MasterDetail when the rail needs a bounded width.",
85
+ "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.",
86
+ "DON'T reproduce that bound with a consumer `max-height`/`overflow` rule or a pixel prop — 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.",
87
+ "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().",
88
+ "Measured geometry with the defaults: 1440 → fluid master + 320px rail; 1024 → fluid master + 320px rail; 390 → stacked full-width master then detail. Same JSX at every width — no consumer-local CSS, grid tracks or per-screen spacing."
89
+ ],
90
+ "useCases": [
91
+ "Teams screen: the team DataTable stays fluid while the selected team's detail keeps the 320px rail.",
92
+ "Organization service access: services in the compact leading rail (`rail=\"master\"`) and selected service roles in the fluid detail surface.",
93
+ "Settings navigator: categories in the leading rail and the selected configuration form in the detail surface."
94
+ ]
95
+ }
@@ -0,0 +1,120 @@
1
+ {
2
+ "docPath": "navigation/mega-menu.tsx",
3
+ "example": "import { MegaMenu } from \"@godxjp/ui/navigation\";\n\n<MegaMenu\n label=\"メインナビゲーション\"\n value={route}\n onValueChange={setRoute}\n triggerAction=\"hover\"\n items={[\n {\n key: \"products\",\n label: \"製品\",\n panel: {\n groups: [\n {\n key: \"core\",\n label: \"コア\",\n description: \"毎日使う業務アプリ\",\n links: [\n { key: \"hr\", label: \"人事管理\", href: \"/hr\", description: \"従業員台帳と異動\" },\n { key: \"payroll\", label: \"給与計算\", href: \"/payroll\" },\n ],\n },\n ],\n footer: <a href=\"/products\">すべての製品を見る</a>,\n },\n },\n { key: \"pricing\", label: \"料金\", href: \"/pricing\" },\n ]}\n/>",
4
+ "group": "navigation",
5
+ "importPath": "@godxjp/ui/navigation",
6
+ "name": "MegaMenu",
7
+ "props": [
8
+ {
9
+ "description": "The bar. An item WITH a `panel` is a disclosure button (antd SubMenuType); an item WITHOUT one is a plain link (antd MenuItemType). { key, label, href?, icon?, disabled?, panel? } where panel is { groups: [{ key, label?, description?, icon?, links: [{ key, label, href?, description?, icon?, disabled? }] }], footer? }. Ant Design `items`.",
10
+ "name": "items",
11
+ "type": "MegaMenuItemProp[]"
12
+ },
13
+ {
14
+ "description": "Key of the OPEN PANEL, or null for none. Ant Design `openKeys` collapsed to one level — a megamenu bar is one level deep, so at most one panel is open and the array would only ever hold zero or one key.",
15
+ "name": "open",
16
+ "type": "string | null"
17
+ },
18
+ {
19
+ "defaultValue": "null",
20
+ "description": "Initial uncontrolled open panel. Ant Design `defaultOpenKeys`.",
21
+ "name": "defaultOpen",
22
+ "type": "string | null"
23
+ },
24
+ {
25
+ "description": "Fires with the newly open panel's key, or null when everything closed. Ant Design `onOpenChange`.",
26
+ "name": "onOpenChange",
27
+ "type": "(key: string | null) => void"
28
+ },
29
+ {
30
+ "description": "Key of the item for the CURRENT ROUTE — renders aria-current=\"page\" on the bar item and on the matching panel link. Ant Design `selectedKeys`, singular because a route is singular.",
31
+ "name": "value",
32
+ "type": "string"
33
+ },
34
+ {
35
+ "description": "Uncontrolled initial current route. Ant Design `defaultSelectedKeys`.",
36
+ "name": "defaultValue",
37
+ "type": "string"
38
+ },
39
+ {
40
+ "description": "Fires with the activated key (a top-level link or a panel link). Activation always closes the open panel. Ant Design `onClick`.",
41
+ "name": "onValueChange",
42
+ "type": "(key: string) => void"
43
+ },
44
+ {
45
+ "defaultValue": "md",
46
+ "description": "Bar density. The trigger box tracks the matching --control-height tier.",
47
+ "name": "size",
48
+ "type": "xs | sm | md | lg"
49
+ },
50
+ {
51
+ "defaultValue": "click",
52
+ "description": "Ant Design `triggerSubMenuAction`. Default is `click` here where antd defaults to `hover`, because a hover-only trigger has no equivalent on a touch screen; `hover` still accepts click, so touch is never stranded. antd's third value `contextMenu` is not ported.",
53
+ "name": "triggerAction",
54
+ "type": "click | hover"
55
+ },
56
+ {
57
+ "defaultValue": "0",
58
+ "description": "Ant Design `subMenuOpenDelay`, in MILLISECONDS (antd uses seconds). `hover` only.",
59
+ "name": "openDelay",
60
+ "type": "number"
61
+ },
62
+ {
63
+ "defaultValue": "100",
64
+ "description": "Ant Design `subMenuCloseDelay`, in MILLISECONDS. The hover-intent grace period: the pointer may cross a diagonal toward the panel for this long before anything closes. `hover` only.",
65
+ "name": "closeDelay",
66
+ "type": "number"
67
+ },
68
+ {
69
+ "description": "Ant Design `expandIcon`, verbatim including its `false` to remove the chevron.",
70
+ "name": "expandIcon",
71
+ "type": "React.ReactNode | false"
72
+ },
73
+ {
74
+ "description": "Router link component for every href in the bar and the panels — same contract and same spelling as Sidebar.linkComponent / NavList.linkComponent.",
75
+ "name": "linkComponent",
76
+ "type": "React.ComponentType<AnchorHTMLAttributes & { href?: string }>"
77
+ },
78
+ {
79
+ "description": "Accessible name of the <nav> landmark (a plain string — it lands on aria-label). Localized default otherwise.",
80
+ "name": "label",
81
+ "type": "string"
82
+ },
83
+ {
84
+ "description": "DOM id of the nav root.",
85
+ "name": "id",
86
+ "type": "string"
87
+ }
88
+ ],
89
+ "related": [
90
+ "DropdownMenu — a menu of COMMANDS on a trigger (role=\"menu\"). Use it for actions; use MegaMenu for navigation to places.",
91
+ "NavList — the same idea laid out vertically inside a page (a settings nav). antd's `Menu mode=\"inline\"` is Sidebar; `mode=\"vertical\"` is NavList.",
92
+ "Topbar / TopbarItem — the APP shell's bar, and NOT where this goes. Measured, both slots break it: `topbar-center` is `display: none` below roughly 1280px (flex at 1440, none at 1024) so the nav vanishes on a laptop, and `topbar-start` is one `overflow: clip` / `flex-wrap: nowrap` row, so the narrow accordion runs out of it (58 elements past the viewport at 375). Put MegaMenu in the site header's own row beside the logo, and give phone width a `Sheet` behind a trigger — which is what real sites do anyway.",
93
+ "Tabs — switches which panel of the SAME page is shown. A nav goes somewhere else.",
94
+ "Breadcrumb — where you are in the hierarchy, not where you can go."
95
+ ],
96
+ "rules": [
97
+ 2,
98
+ 6,
99
+ 23,
100
+ 44,
101
+ 45
102
+ ],
103
+ "storyPath": "navigation/MegaMenu.stories.tsx",
104
+ "tagline": "A primary site navigation whose top-level items disclose a full-width panel of grouped links (Ant Design `Menu mode=\"horizontal\"` whose SubMenu renders through popupRender). Implements the WAI-ARIA APG Disclosure Navigation pattern — NOT menu/menubar roles — with a roving tabindex across the bar, hover intent, Escape-to-trigger, and a narrow layout where the same disclosure lays out in flow.",
105
+ "usage": [
106
+ "DO reach for this INSTEAD of DropdownMenu for a site nav. DropdownMenu is react-aria-components Menu, i.e. role=\"menu\" / role=\"menuitem\": a row of them announces a desktop application menubar for what is actually a set of links, and Tab then leaves the whole widget instead of walking the links. That is the classic megamenu a11y defect and it is why this component exists.",
107
+ "DO give the bar its landmark name through `label` when a page has more than one nav (a primary bar plus a footer nav): two unnamed <nav> landmarks are indistinguishable in a landmark list.",
108
+ "DO drive `value` from your router. A change to it CLOSES the open panel, which is the close-on-route-change half of the contract and needs no router dependency here.",
109
+ "DO use `linkComponent` for a client-side router; the library composes the row and your component renders only the <a>.",
110
+ "DON'T nest a second level inside a panel. A panel is exactly one level deep by construction (`groups[].links[]`) — antd's arbitrary SubMenu nesting is `Sidebar`/`NavList` territory, not a bar.",
111
+ "DON'T set `triggerAction=\"hover\"` and then also hide the trigger's own affordance: hover still opens on click here precisely so a touch user is not stranded, and WCAG 1.4.13 applies to anything hover-revealed.",
112
+ "DON'T add a `theme=\"dark\"` prop expecting antd's. This library inverts by role scoping ([data-tenant] / per-region), and every surface here is a --mega-menu-* token (rules #44/#45).",
113
+ "KNOW that the open panel is `position: fixed` and its geometry is MEASURED from the bar, not inherited. That is not a preference: an absolutely-positioned panel is clipped away by both surfaces a megamenu lives in (`Topbar`'s slots are `overflow: clip`, `Card` is `overflow: hidden` — measured at 331px of 348px gone, and not hit-testable). The consequence for you is that an open panel OVERLAYS what is beneath it, so do not leave one open by default in the middle of a scrolling page."
114
+ ],
115
+ "useCases": [
116
+ "A marketing or product site's primary navigation, where Products / Solutions / Resources each open a panel of grouped links rather than a narrow list.",
117
+ "An admin console with several product areas: a top bar where a section opens a panel of its screens, grouped with headings and one-line descriptions.",
118
+ "A documentation site's top bar, where the current page is marked with aria-current in both the bar and the open panel."
119
+ ]
120
+ }