@godxjp/ui 31.31.8 → 32.0.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 (727) hide show
  1. package/README.md +102 -121
  2. package/agent/START-HERE.md +10 -10
  3. package/agent/components/Accordion.json +2 -2
  4. package/agent/components/AccountChip.json +1 -1
  5. package/agent/components/Actions.json +1 -1
  6. package/agent/components/Activity.json +2 -2
  7. package/agent/components/Affix.json +1 -1
  8. package/agent/components/Alert.json +22 -6
  9. package/agent/components/AlertDialog.json +8 -1
  10. package/agent/components/Anchor.json +4 -3
  11. package/agent/components/AppLauncher.json +2 -2
  12. package/agent/components/AppProvider.json +19 -11
  13. package/agent/components/AppSettingPicker.json +18 -2
  14. package/agent/components/AppShell.json +4 -5
  15. package/agent/components/AreaChart.json +0 -1
  16. package/agent/components/AspectRatio.json +2 -2
  17. package/agent/components/Attachments.json +3 -3
  18. package/agent/components/AuthAccountSummary.json +1 -1
  19. package/agent/components/AuthDivider.json +1 -1
  20. package/agent/components/AuthExpiryProvider.json +0 -1
  21. package/agent/components/AuthFooter.json +2 -2
  22. package/agent/components/AuthIdentity.json +3 -3
  23. package/agent/components/AuthStack.json +1 -1
  24. package/agent/components/Avatar.json +2 -12
  25. package/agent/components/Badge.json +1 -1
  26. package/agent/components/BarChart.json +0 -1
  27. package/agent/components/BranchScopePicker.json +1 -1
  28. package/agent/components/Breadcrumb.json +1 -1
  29. package/agent/components/Button.json +3 -3
  30. package/agent/components/Calendar.json +1 -1
  31. package/agent/components/Card.json +2 -2
  32. package/agent/components/CardBar.json +0 -1
  33. package/agent/components/CardContent.json +0 -1
  34. package/agent/components/Carousel.json +4 -3
  35. package/agent/components/Cascader.json +1 -1
  36. package/agent/components/CenteredShell.json +78 -17
  37. package/agent/components/ChatBubble.json +3 -3
  38. package/agent/components/ChatBubbleList.json +3 -3
  39. package/agent/components/ChatComposer.json +3 -3
  40. package/agent/components/ChatSuggestion.json +3 -3
  41. package/agent/components/Checkbox.json +1 -1
  42. package/agent/components/CheckboxGroup.json +0 -1
  43. package/agent/components/CodeBlock.json +1 -1
  44. package/agent/components/Collapsible.json +4 -4
  45. package/agent/components/ColorPicker.json +1 -1
  46. package/agent/components/Command.json +5 -5
  47. package/agent/components/CommandPalette.json +1 -1
  48. package/agent/components/CompactBarTrend.json +1 -1
  49. package/agent/components/Conversations.json +3 -3
  50. package/agent/components/CredentialReveal.json +1 -1
  51. package/agent/components/DataState.json +2 -2
  52. package/agent/components/DataTable.json +1 -1
  53. package/agent/components/DatePicker.json +1 -1
  54. package/agent/components/Descriptions.json +1 -1
  55. package/agent/components/Dialog.json +2 -2
  56. package/agent/components/DraggablePanel.json +5 -4
  57. package/agent/components/DropdownMenu.json +1 -1
  58. package/agent/components/EmojiPicker.json +3 -2
  59. package/agent/components/EmptyState.json +1 -1
  60. package/agent/components/ErrorSurface.json +3 -3
  61. package/agent/components/FeatureList.json +1 -1
  62. package/agent/components/Field.json +0 -1
  63. package/agent/components/Flex.json +34 -4
  64. package/agent/components/FloatButton.json +6 -5
  65. package/agent/components/Form.json +1 -1
  66. package/agent/components/FormErrors.json +0 -1
  67. package/agent/components/FormField.json +6 -6
  68. package/agent/components/FormFieldArray.json +0 -1
  69. package/agent/components/FormFieldControl.json +0 -1
  70. package/agent/components/FormRoot.json +1 -1
  71. package/agent/components/Heading.json +3 -0
  72. package/agent/components/Icon.json +1 -1
  73. package/agent/components/Image.json +7 -4
  74. package/agent/components/InfiniteQueryState.json +1 -1
  75. package/agent/components/Input.json +1 -1
  76. package/agent/components/InputOTP.json +2 -2
  77. package/agent/components/Label.json +3 -3
  78. package/agent/components/LegalDocumentShell.json +5 -4
  79. package/agent/components/Legend.json +1 -1
  80. package/agent/components/LineChart.json +0 -1
  81. package/agent/components/Link.json +0 -1
  82. package/agent/components/ListRow.json +1 -1
  83. package/agent/components/Logo.json +5 -4
  84. package/agent/components/Marquee.json +4 -3
  85. package/agent/components/Masonry.json +4 -3
  86. package/agent/components/MasterDetail.json +1 -1
  87. package/agent/components/MegaMenu.json +4 -3
  88. package/agent/components/MobileShell.json +4 -4
  89. package/agent/components/NavList.json +1 -1
  90. package/agent/components/NumberInput.json +1 -1
  91. package/agent/components/OrgChart.json +4 -3
  92. package/agent/components/OrgSwitcher.json +1 -1
  93. package/agent/components/OverlayPortalProvider.json +0 -1
  94. package/agent/components/PageContainer.json +3 -3
  95. package/agent/components/PageCover.json +3 -2
  96. package/agent/components/Pagination.json +1 -1
  97. package/agent/components/PasswordInput.json +1 -1
  98. package/agent/components/PasswordStrength.json +1 -1
  99. package/agent/components/PermissionMatrix.json +1 -1
  100. package/agent/components/PieChart.json +0 -1
  101. package/agent/components/Popover.json +31 -6
  102. package/agent/components/PrefetchLink.json +1 -1
  103. package/agent/components/Progress.json +1 -1
  104. package/agent/components/Prose.json +1 -1
  105. package/agent/components/QrCode.json +1 -1
  106. package/agent/components/Radio.json +0 -1
  107. package/agent/components/RadioGroup.json +2 -2
  108. package/agent/components/RangeTimeline.json +2 -1
  109. package/agent/components/Rating.json +1 -1
  110. package/agent/components/RecordPicker.json +1 -1
  111. package/agent/components/ResizablePanel.json +1 -1
  112. package/agent/components/ResponsiveGrid.json +2 -2
  113. package/agent/components/Reveal.json +3 -3
  114. package/agent/components/ScrollArea.json +2 -2
  115. package/agent/components/SearchInput.json +1 -1
  116. package/agent/components/Segmented.json +1 -1
  117. package/agent/components/Select.json +7 -5
  118. package/agent/components/Separator.json +1 -1
  119. package/agent/components/ServiceCatalogCta.json +0 -1
  120. package/agent/components/ServiceLauncherCard.json +1 -1
  121. package/agent/components/ServiceRolePanel.json +1 -1
  122. package/agent/components/Sheet.json +5 -5
  123. package/agent/components/Sidebar.json +1 -1
  124. package/agent/components/Skeleton.json +26 -12
  125. package/agent/components/Slider.json +2 -2
  126. package/agent/components/SortableList.json +3 -2
  127. package/agent/components/SplitPane.json +1 -1
  128. package/agent/components/StatCard.json +1 -1
  129. package/agent/components/Steps.json +1 -1
  130. package/agent/components/Swatch.json +1 -1
  131. package/agent/components/Switch.json +1 -1
  132. package/agent/components/Table.json +1 -1
  133. package/agent/components/Tabs.json +3 -3
  134. package/agent/components/Text.json +5 -1
  135. package/agent/components/TextDiff.json +5 -4
  136. package/agent/components/Textarea.json +1 -1
  137. package/agent/components/ThemeScope.json +1 -1
  138. package/agent/components/ThoughtChain.json +3 -3
  139. package/agent/components/TimePicker.json +1 -1
  140. package/agent/components/Timeline.json +1 -1
  141. package/agent/components/TimelineGrid.json +5 -4
  142. package/agent/components/Toaster.json +0 -1
  143. package/agent/components/Toggle.json +3 -3
  144. package/agent/components/ToggleGroup.json +1 -1
  145. package/agent/components/Toolbar.json +10 -3
  146. package/agent/components/Tooltip.json +9 -9
  147. package/agent/components/Topbar.json +1 -1
  148. package/agent/components/TopbarItem.json +1 -1
  149. package/agent/components/Transfer.json +2 -2
  150. package/agent/components/Tree.json +1 -1
  151. package/agent/components/TreeSelect.json +1 -1
  152. package/agent/components/TwoFactorSetup.json +1 -1
  153. package/agent/components/Upload.json +1 -1
  154. package/agent/components/UploadCropDialog.json +1 -1
  155. package/agent/components/Welcome.json +3 -3
  156. package/agent/components/formatDate.json +2 -2
  157. package/agent/components/inertiaUpload.json +0 -1
  158. package/agent/components/useZodForm.json +0 -1
  159. package/agent/components-index.json +80 -154
  160. package/agent/components.json +541 -1737
  161. package/agent/index.json +10 -10
  162. package/agent/llms.txt +9 -9
  163. package/agent/patterns/account-recovery-settings.json +1 -1
  164. package/agent/patterns/auth-recovery-panels.json +2 -2
  165. package/agent/patterns/error-pages.json +1 -1
  166. package/agent/patterns/pricing-plans.json +17 -0
  167. package/agent/patterns-index.json +17 -1
  168. package/agent/patterns.json +21 -4
  169. package/agent/rules.json +30 -65
  170. package/agent/tokens.json +27 -57
  171. package/agent/vocabulary.json +3 -3
  172. package/dist/app/app-provider.d.ts +5 -3
  173. package/dist/app/app-provider.js +47 -12
  174. package/dist/app/date-format-labels.d.ts +3 -1
  175. package/dist/app/date-format-labels.js +2 -4
  176. package/dist/app/date-formats.d.ts +21 -2
  177. package/dist/app/date-formats.js +51 -2
  178. package/dist/app/index.d.ts +3 -1
  179. package/dist/app/index.js +5 -0
  180. package/dist/app/locales.d.ts +58 -3
  181. package/dist/app/locales.js +134 -8
  182. package/dist/app/preset.d.ts +25 -0
  183. package/dist/app/preset.js +10 -0
  184. package/dist/app/request-headers.d.ts +3 -3
  185. package/dist/app/request-headers.js +19 -10
  186. package/dist/app/settings-scope.d.ts +54 -0
  187. package/dist/app/settings-scope.js +46 -0
  188. package/dist/app/tenant-theme.js +8 -3
  189. package/dist/app/time-format-labels.d.ts +4 -1
  190. package/dist/app/time-format-labels.js +7 -1
  191. package/dist/app/types.d.ts +10 -2
  192. package/dist/app/use-formatting.d.ts +1 -1
  193. package/dist/components/admin/index.d.ts +1 -1
  194. package/dist/components/admin/index.js +1 -1
  195. package/dist/components/data-display/accordion.d.ts +51 -1
  196. package/dist/components/data-display/accordion.js +247 -1
  197. package/dist/components/data-display/avatar.d.ts +16 -1
  198. package/dist/components/data-display/avatar.js +109 -1
  199. package/dist/components/data-display/credential-reveal.d.ts +9 -2
  200. package/dist/components/data-display/credential-reveal.js +156 -1
  201. package/dist/components/data-display/image.js +5 -3
  202. package/dist/components/data-display/index.d.ts +0 -21
  203. package/dist/components/data-display/index.js +1 -43
  204. package/dist/components/data-display/popover.d.ts +15 -3
  205. package/dist/components/data-display/popover.js +168 -2
  206. package/dist/components/data-entry/branch-scope-picker.d.ts +1 -1
  207. package/dist/components/data-entry/index.d.ts +5 -12
  208. package/dist/components/data-entry/index.js +11 -12
  209. package/dist/components/data-entry/input-otp.d.ts +48 -2
  210. package/dist/components/data-entry/input-otp.js +94 -1
  211. package/dist/components/data-entry/number-input.d.ts +1 -1
  212. package/dist/components/data-entry/password-input.d.ts +38 -2
  213. package/dist/components/data-entry/password-input.js +56 -1
  214. package/dist/components/data-entry/rating.d.ts +38 -2
  215. package/dist/components/data-entry/rating.js +127 -1
  216. package/dist/components/data-entry/segmented.d.ts +94 -2
  217. package/dist/components/data-entry/segmented.js +104 -1
  218. package/dist/components/{ui/tag-input.d.ts → data-entry/select-tags-inline.d.ts} +7 -4
  219. package/dist/components/{ui/tag-input.js → data-entry/select-tags-inline.js} +4 -4
  220. package/dist/components/data-entry/select.js +4 -0
  221. package/dist/components/data-entry/toggle-group.d.ts +108 -2
  222. package/dist/components/data-entry/toggle-group.js +189 -1
  223. package/dist/components/data-entry/toggle.d.ts +86 -2
  224. package/dist/components/data-entry/toggle.js +153 -2
  225. package/dist/components/feedback/alert.d.ts +5 -3
  226. package/dist/components/feedback/alert.js +14 -2
  227. package/dist/components/feedback/index.d.ts +2 -6
  228. package/dist/components/feedback/index.js +1 -13
  229. package/dist/components/feedback/skeleton.d.ts +4 -17
  230. package/dist/components/feedback/skeleton.js +3 -40
  231. package/dist/components/general/index.d.ts +2 -4
  232. package/dist/components/general/index.js +1 -6
  233. package/dist/components/general/typography.d.ts +4 -58
  234. package/dist/components/general/typography.js +7 -141
  235. package/dist/components/layout/aspect-ratio.d.ts +24 -1
  236. package/dist/components/layout/aspect-ratio.js +18 -1
  237. package/dist/components/layout/auth-divider.js +1 -1
  238. package/dist/components/layout/auth-identity.js +6 -2
  239. package/dist/components/layout/centered-shell.d.ts +13 -8
  240. package/dist/components/layout/centered-shell.js +43 -1
  241. package/dist/components/layout/flex.d.ts +1 -1
  242. package/dist/components/layout/flex.js +73 -1
  243. package/dist/components/layout/index.d.ts +1 -13
  244. package/dist/components/layout/index.js +0 -12
  245. package/dist/components/layout/separator.d.ts +25 -2
  246. package/dist/components/layout/separator.js +71 -1
  247. package/dist/components/navigation/{app-setting-toggle.d.ts → app-setting-cycle.d.ts} +3 -4
  248. package/dist/components/navigation/{app-setting-toggle.js → app-setting-cycle.js} +3 -3
  249. package/dist/components/navigation/app-setting-picker.d.ts +6 -2
  250. package/dist/components/navigation/app-setting-picker.js +20 -2
  251. package/dist/components/navigation/index.d.ts +3 -11
  252. package/dist/components/navigation/index.js +1 -9
  253. package/dist/components/navigation/tabs.js +1 -1
  254. package/dist/components/ui/carousel.d.ts +1 -1
  255. package/dist/components/ui/carousel.js +1 -1
  256. package/dist/components/ui/index.d.ts +11 -5
  257. package/dist/components/ui/index.js +5 -5
  258. package/dist/contracts/measurement.json +2 -2
  259. package/dist/email/brand-mark.d.ts +3 -2
  260. package/dist/email/brand-mark.js +1 -1
  261. package/dist/email/tokens.generated.d.ts +6 -6
  262. package/dist/email/tokens.generated.js +6 -6
  263. package/dist/i18n/locale-tags.d.ts +25 -0
  264. package/dist/i18n/locale-tags.js +59 -0
  265. package/dist/i18n/translate.d.ts +20 -3
  266. package/dist/i18n/translate.js +25 -14
  267. package/dist/i18n/use-translation.d.ts +3 -3
  268. package/dist/i18n/use-translation.js +4 -2
  269. package/dist/{components/navigation → lab}/anchor.d.ts +2 -2
  270. package/dist/{components/navigation → lab}/anchor.js +5 -5
  271. package/dist/{components/data-display → lab}/carousel.js +2 -2
  272. package/dist/{components/layout → lab}/draggable-panel.d.ts +8 -8
  273. package/dist/{components/layout → lab}/draggable-panel.js +2 -2
  274. package/dist/{components/data-entry → lab}/emoji-picker.d.ts +2 -2
  275. package/dist/{components/data-entry → lab}/emoji-picker.js +6 -6
  276. package/dist/{components/general → lab}/float-button.d.ts +8 -8
  277. package/dist/{components/general → lab}/float-button.js +7 -7
  278. package/dist/lab/index.d.ts +39 -0
  279. package/dist/lab/index.js +48 -0
  280. package/dist/{components/layout → lab}/legal-document-shell.d.ts +2 -2
  281. package/dist/{components/layout → lab}/legal-document-shell.js +4 -4
  282. package/dist/{components/data-display → lab}/marquee.d.ts +5 -5
  283. package/dist/{components/data-display → lab}/marquee.js +5 -5
  284. package/dist/{components/layout → lab}/masonry.d.ts +2 -2
  285. package/dist/{components/layout → lab}/masonry.js +3 -3
  286. package/dist/{components/navigation → lab}/mega-menu.d.ts +4 -4
  287. package/dist/{components/navigation → lab}/mega-menu.js +2 -2
  288. package/dist/{components/data-display → lab}/org-chart.d.ts +2 -2
  289. package/dist/{components/data-display → lab}/org-chart.js +4 -4
  290. package/dist/{components/layout → lab}/page-cover.d.ts +2 -2
  291. package/dist/{components/layout → lab}/page-cover.js +2 -2
  292. package/dist/{components/data-display → lab}/range-timeline.d.ts +1 -1
  293. package/dist/{components/data-display → lab}/range-timeline.js +6 -6
  294. package/dist/{components/data-entry → lab}/sortable-list.d.ts +2 -2
  295. package/dist/{components/data-entry → lab}/sortable-list.js +3 -3
  296. package/dist/{components/data-display → lab}/text-diff.d.ts +3 -3
  297. package/dist/{components/data-display → lab}/text-diff.js +4 -4
  298. package/dist/{components/data-display → lab}/timeline-grid.d.ts +2 -2
  299. package/dist/{components/data-display → lab}/timeline-grid.js +1 -1
  300. package/dist/lib/control-styles.js +1 -1
  301. package/dist/lib/datetime/format.js +20 -6
  302. package/dist/lib/datetime/sync.d.ts +6 -2
  303. package/dist/lib/datetime/sync.js +14 -18
  304. package/dist/lib/platform.d.ts +1 -1
  305. package/dist/props/components/app.prop.d.ts +28 -13
  306. package/dist/props/components/data-display.prop.d.ts +19 -29
  307. package/dist/props/components/feedback.prop.d.ts +15 -44
  308. package/dist/props/components/general.prop.d.ts +2 -64
  309. package/dist/props/components/index.d.ts +9 -1
  310. package/dist/props/components/layout.prop.d.ts +41 -57
  311. package/dist/props/registry.d.ts +48 -101
  312. package/dist/props/registry.js +45 -129
  313. package/dist/props/vocabulary/index.d.ts +1 -1
  314. package/dist/props/vocabulary/interaction.prop.d.ts +3 -12
  315. package/dist/styles/alert-layout.css +0 -29
  316. package/dist/styles/data-display-layout.css +14 -22
  317. package/dist/styles/fonts/OFL.txt +93 -0
  318. package/dist/styles/form-layout.css +4 -0
  319. package/dist/styles/index.css +0 -2
  320. package/dist/styles/layers.json +44 -179
  321. package/dist/styles/text-layout.css +0 -15
  322. package/dist/styles.css +2 -0
  323. package/dist/themes/godx.css +43 -0
  324. package/dist/themes/godx.d.ts +13 -0
  325. package/dist/themes/godx.js +12 -0
  326. package/dist/tokens/components/feedback.css +0 -6
  327. package/dist/tokens/derived.css +23 -22
  328. package/dist/tokens/foundation.css +4 -6
  329. package/docs/COMPOSITION-VS-COMPONENT.md +58 -33
  330. package/docs/CONSUMER-RULES.md +1 -1
  331. package/docs/CUSTOMER-THEMING.md +17 -53
  332. package/docs/migrations/v32.md +98 -0
  333. package/package.json +58 -19
  334. package/scripts/_agent-setup.mjs +54 -3
  335. package/scripts/brand-accent.generated.mjs +15 -2
  336. package/scripts/cli.mjs +36 -11
  337. package/scripts/codemod.mjs +522 -0
  338. package/scripts/consumer-rule.md +21 -43
  339. package/scripts/postinstall.mjs +10 -47
  340. package/scripts/prune-css.mjs +1 -1
  341. package/scripts/ui-audit.mjs +5 -2
  342. package/dist/components/data-display/chat-bubble.d.ts +0 -30
  343. package/dist/components/data-display/chat-bubble.js +0 -229
  344. package/dist/components/data-display/hover-card.d.ts +0 -1
  345. package/dist/components/data-display/hover-card.js +0 -7
  346. package/dist/components/data-display/thought-chain.d.ts +0 -42
  347. package/dist/components/data-display/thought-chain.js +0 -177
  348. package/dist/components/data-display/thumbnail.d.ts +0 -37
  349. package/dist/components/data-display/thumbnail.js +0 -20
  350. package/dist/components/data-display/welcome.d.ts +0 -26
  351. package/dist/components/data-display/welcome.js +0 -37
  352. package/dist/components/data-entry/attachments.d.ts +0 -19
  353. package/dist/components/data-entry/attachments.js +0 -366
  354. package/dist/components/data-entry/chat-composer.d.ts +0 -51
  355. package/dist/components/data-entry/chat-composer.js +0 -200
  356. package/dist/components/data-entry/chat-suggestion.d.ts +0 -28
  357. package/dist/components/data-entry/chat-suggestion.js +0 -309
  358. package/dist/components/data-entry/tag-input.d.ts +0 -2
  359. package/dist/components/data-entry/tag-input.js +0 -5
  360. package/dist/components/feedback/banner.d.ts +0 -21
  361. package/dist/components/feedback/banner.js +0 -19
  362. package/dist/components/feedback/callout.d.ts +0 -23
  363. package/dist/components/feedback/callout.js +0 -42
  364. package/dist/components/layout/auth-shell.d.ts +0 -10
  365. package/dist/components/layout/auth-shell.js +0 -42
  366. package/dist/components/layout/space-compact.d.ts +0 -65
  367. package/dist/components/layout/space-compact.js +0 -52
  368. package/dist/components/navigation/conversations.d.ts +0 -39
  369. package/dist/components/navigation/conversations.js +0 -371
  370. package/dist/components/ui/accordion.d.ts +0 -51
  371. package/dist/components/ui/accordion.js +0 -254
  372. package/dist/components/ui/aspect-ratio.d.ts +0 -24
  373. package/dist/components/ui/aspect-ratio.js +0 -21
  374. package/dist/components/ui/avatar.d.ts +0 -16
  375. package/dist/components/ui/avatar.js +0 -115
  376. package/dist/components/ui/credential-reveal.d.ts +0 -9
  377. package/dist/components/ui/credential-reveal.js +0 -160
  378. package/dist/components/ui/hover-card.d.ts +0 -47
  379. package/dist/components/ui/hover-card.js +0 -205
  380. package/dist/components/ui/input-otp.d.ts +0 -48
  381. package/dist/components/ui/input-otp.js +0 -101
  382. package/dist/components/ui/password-input.d.ts +0 -38
  383. package/dist/components/ui/password-input.js +0 -60
  384. package/dist/components/ui/rating.d.ts +0 -38
  385. package/dist/components/ui/rating.js +0 -131
  386. package/dist/components/ui/segmented.d.ts +0 -94
  387. package/dist/components/ui/segmented.js +0 -108
  388. package/dist/components/ui/separator.d.ts +0 -25
  389. package/dist/components/ui/separator.js +0 -74
  390. package/dist/components/ui/toggle-group.d.ts +0 -108
  391. package/dist/components/ui/toggle-group.js +0 -194
  392. package/dist/components/ui/toggle.d.ts +0 -86
  393. package/dist/components/ui/toggle.js +0 -156
  394. package/docs/CANONICAL-CONTRACTS.md +0 -206
  395. package/docs/COMPONENTS.md +0 -163
  396. package/docs/DATETIME.md +0 -78
  397. package/docs/DEVELOPMENT.md +0 -269
  398. package/docs/FRAME-A11Y-CI.md +0 -287
  399. package/docs/FRAME-COVERAGE-LEDGER.md +0 -123
  400. package/docs/FRAME-COVERAGE-REPORT.md +0 -228
  401. package/docs/FRAME-COVERAGE-STANDARD.md +0 -65
  402. package/docs/FRAME-COVERAGE.md +0 -29
  403. package/docs/MEASUREMENT-CONTRACT.md +0 -109
  404. package/docs/PROPS-REGISTRY.md +0 -61
  405. package/docs/PROPS-VOCABULARY.md +0 -100
  406. package/docs/REGISTRY.md +0 -70
  407. package/docs/TESTING.md +0 -107
  408. package/docs/THEME-API-COVERAGE.md +0 -538
  409. package/docs/WHAT-BELONGS-HERE.md +0 -179
  410. package/docs/asset-modules.d.ts +0 -53
  411. package/docs/assets/cover-terrain.svg +0 -6
  412. package/docs/assets/portrait-amber.svg +0 -5
  413. package/docs/assets/portrait-clay.svg +0 -5
  414. package/docs/assets/portrait-indigo.svg +0 -5
  415. package/docs/assets/portrait-iris.svg +0 -5
  416. package/docs/assets/portrait-moss.svg +0 -5
  417. package/docs/assets/portrait-plum.svg +0 -5
  418. package/docs/assets/portrait-slate.svg +0 -5
  419. package/docs/assets/service-mark-rose.svg +0 -6
  420. package/docs/assets/service-mark-teal.svg +0 -5
  421. package/docs/assets/shot-landscape.svg +0 -12
  422. package/docs/assets/shot-portrait.svg +0 -10
  423. package/docs/assets/tcgm/card-1.svg +0 -1
  424. package/docs/assets/tcgm/card-2.svg +0 -1
  425. package/docs/assets/tcgm/card-3.svg +0 -1
  426. package/docs/assets/tcgm/card-4.svg +0 -1
  427. package/docs/assets/tcgm/card-5.svg +0 -1
  428. package/docs/assets/tcgm/card-6.svg +0 -1
  429. package/docs/assets/tcgm/hero-prism.svg +0 -1
  430. package/docs/assets/tcgm/scene-1.svg +0 -1
  431. package/docs/assets/tcgm/scene-2.svg +0 -1
  432. package/docs/assets/tcgm/scene-3.svg +0 -1
  433. package/docs/assets/tcgm/scene-4.svg +0 -1
  434. package/docs/assets/tcgm/scene-5.svg +0 -1
  435. package/docs/assets/tcgm/tcgm-lockup-color.svg +0 -1
  436. package/docs/assets/tcgm/tcgm-symbol-color.svg +0 -1
  437. package/docs/charts/cjk-category-axis.tsx +0 -81
  438. package/docs/charts/compact-bar-trend.tsx +0 -327
  439. package/docs/charts/size-tiers.tsx +0 -192
  440. package/docs/data-display/accordion.tsx +0 -99
  441. package/docs/data-display/avatar.tsx +0 -502
  442. package/docs/data-display/badge.tsx +0 -335
  443. package/docs/data-display/card/examples/detail-panel.tsx +0 -106
  444. package/docs/data-display/card/examples/tab-list.tsx +0 -97
  445. package/docs/data-display/card/index.md +0 -3
  446. package/docs/data-display/card/index.tsx +0 -390
  447. package/docs/data-display/carousel.tsx +0 -160
  448. package/docs/data-display/charts.tsx +0 -278
  449. package/docs/data-display/chat-bubble.tsx +0 -397
  450. package/docs/data-display/code-block.tsx +0 -188
  451. package/docs/data-display/collapsible.tsx +0 -162
  452. package/docs/data-display/credential-reveal.tsx +0 -148
  453. package/docs/data-display/data-table/examples/antd-parity.tsx +0 -257
  454. package/docs/data-display/data-table/examples/approval-queue.tsx +0 -212
  455. package/docs/data-display/data-table/examples/grid-features.tsx +0 -158
  456. package/docs/data-display/data-table/examples/list-page.tsx +0 -175
  457. package/docs/data-display/data-table/examples/selectable-inbox.tsx +0 -113
  458. package/docs/data-display/data-table/examples/server-paged.tsx +0 -179
  459. package/docs/data-display/data-table/examples/striped-list.tsx +0 -173
  460. package/docs/data-display/data-table/index.md +0 -9
  461. package/docs/data-display/data-table/index.tsx +0 -407
  462. package/docs/data-display/descriptions.tsx +0 -263
  463. package/docs/data-display/empty-state.tsx +0 -164
  464. package/docs/data-display/feature-list.tsx +0 -159
  465. package/docs/data-display/hover-card.tsx +0 -181
  466. package/docs/data-display/image.tsx +0 -170
  467. package/docs/data-display/legend.tsx +0 -158
  468. package/docs/data-display/list-row.tsx +0 -441
  469. package/docs/data-display/markdown.tsx +0 -86
  470. package/docs/data-display/marquee.tsx +0 -254
  471. package/docs/data-display/org-chart.tsx +0 -165
  472. package/docs/data-display/permission-matrix.tsx +0 -188
  473. package/docs/data-display/popover-touch.tsx +0 -27
  474. package/docs/data-display/popover.tsx +0 -331
  475. package/docs/data-display/progress.tsx +0 -162
  476. package/docs/data-display/prose.tsx +0 -169
  477. package/docs/data-display/qr-code.tsx +0 -68
  478. package/docs/data-display/scroll-area.tsx +0 -420
  479. package/docs/data-display/service-launcher-card.tsx +0 -445
  480. package/docs/data-display/stat-card.tsx +0 -354
  481. package/docs/data-display/swatch.tsx +0 -107
  482. package/docs/data-display/table.tsx +0 -362
  483. package/docs/data-display/text-diff.tsx +0 -89
  484. package/docs/data-display/thought-chain.tsx +0 -176
  485. package/docs/data-display/thumbnail.tsx +0 -128
  486. package/docs/data-display/timeline-grid.tsx +0 -119
  487. package/docs/data-display/timeline.tsx +0 -671
  488. package/docs/data-display/touch-actions.tsx +0 -36
  489. package/docs/data-display/tree.tsx +0 -724
  490. package/docs/data-display/welcome.tsx +0 -115
  491. package/docs/data-entry/attachments.tsx +0 -160
  492. package/docs/data-entry/block-editor.tsx +0 -105
  493. package/docs/data-entry/branch-scope-picker.tsx +0 -128
  494. package/docs/data-entry/calendar.tsx +0 -226
  495. package/docs/data-entry/cascader.tsx +0 -380
  496. package/docs/data-entry/chat-composer.tsx +0 -619
  497. package/docs/data-entry/chat-suggestion.tsx +0 -301
  498. package/docs/data-entry/checkbox.tsx +0 -256
  499. package/docs/data-entry/color-picker.tsx +0 -189
  500. package/docs/data-entry/command-palette.tsx +0 -179
  501. package/docs/data-entry/command.tsx +0 -341
  502. package/docs/data-entry/country-picker-recipe.tsx +0 -175
  503. package/docs/data-entry/date-picker.tsx +0 -452
  504. package/docs/data-entry/emoji-picker.tsx +0 -35
  505. package/docs/data-entry/form/examples/employee-settings.tsx +0 -267
  506. package/docs/data-entry/form/examples/invoice-form.tsx +0 -387
  507. package/docs/data-entry/form-dynamic-fields.tsx +0 -199
  508. package/docs/data-entry/form-field/examples/a11y-contract.tsx +0 -242
  509. package/docs/data-entry/form-field/examples/create-form.tsx +0 -107
  510. package/docs/data-entry/form-field/index.md +0 -8
  511. package/docs/data-entry/form-field/index.tsx +0 -337
  512. package/docs/data-entry/form-inertia.tsx +0 -80
  513. package/docs/data-entry/form-root.tsx +0 -112
  514. package/docs/data-entry/form.tsx +0 -1498
  515. package/docs/data-entry/input-otp.tsx +0 -258
  516. package/docs/data-entry/input.tsx +0 -796
  517. package/docs/data-entry/label.tsx +0 -102
  518. package/docs/data-entry/markdown-editor.tsx +0 -69
  519. package/docs/data-entry/number-input.tsx +0 -279
  520. package/docs/data-entry/password-input.tsx +0 -102
  521. package/docs/data-entry/password-strength.tsx +0 -99
  522. package/docs/data-entry/radio-group.tsx +0 -316
  523. package/docs/data-entry/rating.tsx +0 -91
  524. package/docs/data-entry/record-picker.tsx +0 -209
  525. package/docs/data-entry/search-input.tsx +0 -121
  526. package/docs/data-entry/segmented-in-filter-row.tsx +0 -60
  527. package/docs/data-entry/segmented.tsx +0 -300
  528. package/docs/data-entry/select-async-contracts.tsx +0 -108
  529. package/docs/data-entry/select-matrix.tsx +0 -209
  530. package/docs/data-entry/select.tsx +0 -547
  531. package/docs/data-entry/slider.tsx +0 -212
  532. package/docs/data-entry/sortable-list.tsx +0 -76
  533. package/docs/data-entry/switch.tsx +0 -314
  534. package/docs/data-entry/tag-input.tsx +0 -145
  535. package/docs/data-entry/textarea.tsx +0 -249
  536. package/docs/data-entry/time-picker.tsx +0 -187
  537. package/docs/data-entry/time-range-picker.tsx +0 -55
  538. package/docs/data-entry/toggle-count.tsx +0 -348
  539. package/docs/data-entry/toggle-group.tsx +0 -438
  540. package/docs/data-entry/toggle.tsx +0 -217
  541. package/docs/data-entry/transfer.tsx +0 -115
  542. package/docs/data-entry/tree-select.tsx +0 -204
  543. package/docs/data-entry/upload-crop-dialog.tsx +0 -109
  544. package/docs/data-entry/upload.tsx +0 -310
  545. package/docs/feedback/alert-dialog.tsx +0 -270
  546. package/docs/feedback/alert.tsx +0 -248
  547. package/docs/feedback/auth-expiry.tsx +0 -104
  548. package/docs/feedback/banner.tsx +0 -183
  549. package/docs/feedback/callout.tsx +0 -220
  550. package/docs/feedback/danger-confirm.tsx +0 -91
  551. package/docs/feedback/dialog-touch.tsx +0 -24
  552. package/docs/feedback/dialog.tsx +0 -373
  553. package/docs/feedback/sheet.tsx +0 -510
  554. package/docs/feedback/skeleton.tsx +0 -168
  555. package/docs/feedback/toast.tsx +0 -135
  556. package/docs/feedback/tooltip.tsx +0 -171
  557. package/docs/feedback/two-factor-setup.tsx +0 -68
  558. package/docs/foundation/_email-specimen.ts +0 -274
  559. package/docs/foundation/_theme-editor-scope.ts +0 -222
  560. package/docs/foundation/colors.tsx +0 -207
  561. package/docs/foundation/density.tsx +0 -242
  562. package/docs/foundation/email-tokens.tsx +0 -323
  563. package/docs/foundation/radius-shadow.tsx +0 -154
  564. package/docs/foundation/spacing.tsx +0 -216
  565. package/docs/foundation/theme-editor.tsx +0 -654
  566. package/docs/foundation/typography.tsx +0 -147
  567. package/docs/general/actions.tsx +0 -175
  568. package/docs/general/activity.tsx +0 -289
  569. package/docs/general/button/examples/destructive-confirm.tsx +0 -90
  570. package/docs/general/button/examples/form-actions.tsx +0 -87
  571. package/docs/general/button/examples/toolbar.tsx +0 -160
  572. package/docs/general/button/index.md +0 -16
  573. package/docs/general/button/index.tsx +0 -236
  574. package/docs/general/float-button.tsx +0 -133
  575. package/docs/general/icon.tsx +0 -217
  576. package/docs/general/logo.tsx +0 -194
  577. package/docs/general/reveal.tsx +0 -309
  578. package/docs/general/typography.tsx +0 -542
  579. package/docs/i18n/messages/en.json +0 -1715
  580. package/docs/i18n/messages/ja.json +0 -1708
  581. package/docs/i18n/messages/vi.json +0 -1708
  582. package/docs/layout/account-chip.tsx +0 -72
  583. package/docs/layout/admin-collection.tsx +0 -122
  584. package/docs/layout/affix.tsx +0 -209
  585. package/docs/layout/app-launcher.tsx +0 -211
  586. package/docs/layout/app-shell-arrangements.tsx +0 -225
  587. package/docs/layout/app-shell-narrow-bar.tsx +0 -205
  588. package/docs/layout/app-shell-states.tsx +0 -171
  589. package/docs/layout/app-shell.tsx +0 -395
  590. package/docs/layout/aspect-ratio.tsx +0 -135
  591. package/docs/layout/auth-account-summary.tsx +0 -103
  592. package/docs/layout/auth-divider.tsx +0 -28
  593. package/docs/layout/auth-footer.tsx +0 -37
  594. package/docs/layout/auth-identity.tsx +0 -70
  595. package/docs/layout/auth-recovery/examples/mfa-challenge.md +0 -41
  596. package/docs/layout/auth-recovery/examples/mfa-challenge.tsx +0 -254
  597. package/docs/layout/auth-recovery/examples/mobile-390.md +0 -25
  598. package/docs/layout/auth-recovery/examples/mobile-390.tsx +0 -82
  599. package/docs/layout/auth-recovery/examples/password-recovery.md +0 -24
  600. package/docs/layout/auth-recovery/examples/password-recovery.tsx +0 -197
  601. package/docs/layout/auth-recovery/examples/wrapping-ja-en-vi.md +0 -27
  602. package/docs/layout/auth-recovery/examples/wrapping-ja-en-vi.tsx +0 -156
  603. package/docs/layout/auth-recovery/index.md +0 -140
  604. package/docs/layout/auth-recovery/index.tsx +0 -96
  605. package/docs/layout/auth-shell-context.tsx +0 -103
  606. package/docs/layout/auth-shell-device.tsx +0 -112
  607. package/docs/layout/auth-shell-registration.tsx +0 -338
  608. package/docs/layout/auth-shell-variants.tsx +0 -143
  609. package/docs/layout/auth-shell.tsx +0 -59
  610. package/docs/layout/auth-stack.tsx +0 -40
  611. package/docs/layout/centered-shell.tsx +0 -102
  612. package/docs/layout/draggable-panel.tsx +0 -127
  613. package/docs/layout/error-surface/examples/application-403.tsx +0 -84
  614. package/docs/layout/error-surface/examples/application-404.tsx +0 -77
  615. package/docs/layout/error-surface/examples/system-500.tsx +0 -47
  616. package/docs/layout/error-surface/examples/system-503.tsx +0 -48
  617. package/docs/layout/error-surface/index.md +0 -153
  618. package/docs/layout/error-surface/index.tsx +0 -239
  619. package/docs/layout/flex.tsx +0 -444
  620. package/docs/layout/legal-document-shell/_data.ts +0 -110
  621. package/docs/layout/legal-document-shell/examples/desktop-1440.md +0 -12
  622. package/docs/layout/legal-document-shell/examples/desktop-1440.tsx +0 -83
  623. package/docs/layout/legal-document-shell/examples/mobile-390.md +0 -22
  624. package/docs/layout/legal-document-shell/examples/mobile-390.tsx +0 -70
  625. package/docs/layout/legal-document-shell/examples/tablet-1024.md +0 -3
  626. package/docs/layout/legal-document-shell/examples/tablet-1024.tsx +0 -84
  627. package/docs/layout/legal-document-shell/examples/wrapping-ja-en-vi.md +0 -18
  628. package/docs/layout/legal-document-shell/examples/wrapping-ja-en-vi.tsx +0 -61
  629. package/docs/layout/legal-document-shell/index.md +0 -51
  630. package/docs/layout/legal-document-shell/index.tsx +0 -101
  631. package/docs/layout/masonry.tsx +0 -291
  632. package/docs/layout/master-detail.tsx +0 -390
  633. package/docs/layout/mobile-shell.tsx +0 -106
  634. package/docs/layout/nav-list.tsx +0 -584
  635. package/docs/layout/org-switcher.tsx +0 -131
  636. package/docs/layout/page-container.tsx +0 -1020
  637. package/docs/layout/page-cover.tsx +0 -56
  638. package/docs/layout/resizable-panel.tsx +0 -419
  639. package/docs/layout/responsive-grid.tsx +0 -403
  640. package/docs/layout/separator.tsx +0 -327
  641. package/docs/layout/service-role-panel.tsx +0 -144
  642. package/docs/layout/sidebar.tsx +0 -689
  643. package/docs/layout/space-compact.tsx +0 -202
  644. package/docs/layout/split-pane.tsx +0 -532
  645. package/docs/layout/topbar-item.tsx +0 -233
  646. package/docs/layout/topbar.tsx +0 -360
  647. package/docs/navigation/anchor.tsx +0 -295
  648. package/docs/navigation/app-setting-picker.tsx +0 -178
  649. package/docs/navigation/app-setting-toggle.tsx +0 -111
  650. package/docs/navigation/breadcrumb.tsx +0 -207
  651. package/docs/navigation/card-tabs.tsx +0 -344
  652. package/docs/navigation/conversations.tsx +0 -176
  653. package/docs/navigation/dropdown-menu.tsx +0 -345
  654. package/docs/navigation/filter-bar.tsx +0 -247
  655. package/docs/navigation/mega-menu-panel.tsx +0 -86
  656. package/docs/navigation/mega-menu.tsx +0 -254
  657. package/docs/navigation/pagination.tsx +0 -293
  658. package/docs/navigation/steps.tsx +0 -315
  659. package/docs/navigation/tabs-rtl.tsx +0 -28
  660. package/docs/navigation/tabs.tsx +0 -755
  661. package/docs/navigation/toolbar.tsx +0 -347
  662. package/docs/providers/app-provider.tsx +0 -191
  663. package/docs/providers/format-date.tsx +0 -230
  664. package/docs/providers/theme-scope.tsx +0 -186
  665. package/docs/query/button-refetch.tsx +0 -161
  666. package/docs/query/data-state.tsx +0 -262
  667. package/docs/query/infinite-query-state.tsx +0 -97
  668. package/docs/query/mutation-feedback.tsx +0 -128
  669. package/docs/query/touch-actions.tsx +0 -68
  670. package/docs/react-router/prefetch-link.tsx +0 -262
  671. package/docs/roadmap/ai-chat-components.md +0 -206
  672. package/docs/roadmap/antd-parity.md +0 -155
  673. package/docs/roadmap/badge-tag-chip-count.md +0 -172
  674. package/docs/roadmap/list-masonry.md +0 -159
  675. package/docs/roadmap/parity-audit-data-display-feedback.md +0 -567
  676. package/docs/roadmap/parity-audit-data-entry.md +0 -368
  677. package/docs/roadmap/parity-audit-layout-navigation-general.md +0 -459
  678. package/docs/roadmap/parity-backlog.md +0 -77
  679. package/docs/roadmap/tree-components.md +0 -151
  680. package/docs/roadmap/website-components.md +0 -813
  681. package/docs/showcase/acme-portal.tsx +0 -461
  682. package/docs/showcase/acme-website.tsx +0 -499
  683. package/docs/showcase/caimono-price-comparison.tsx +0 -919
  684. package/docs/showcase/case1-warehouse-dashboard.tsx +0 -358
  685. package/docs/showcase/case2-employee-me.tsx +0 -438
  686. package/docs/showcase/case3-approval-workflow.tsx +0 -531
  687. package/docs/showcase/case4-login.tsx +0 -312
  688. package/docs/showcase/case5-shift-calendar.tsx +0 -702
  689. package/docs/showcase/case6-agency-handy.tsx +0 -1120
  690. package/docs/showcase/futurelastic-web.tsx +0 -441
  691. package/docs/showcase/marketing-page.tsx +0 -895
  692. package/docs/showcase/org-switcher.tsx +0 -320
  693. package/docs/showcase/permission-matrix.tsx +0 -354
  694. package/docs/showcase/public-landing.tsx +0 -210
  695. package/docs/showcase/settings-account-sections.tsx +0 -559
  696. package/docs/showcase/settings-security-mfa.tsx +0 -145
  697. package/docs/showcase/table-approval-queue.tsx +0 -199
  698. package/docs/showcase/table-bulk-actions.tsx +0 -468
  699. package/docs/showcase/table-compact-kintone.tsx +0 -438
  700. package/docs/showcase/table-conditional-format.tsx +0 -367
  701. package/docs/showcase/table-crud-list.tsx +0 -544
  702. package/docs/showcase/table-density.tsx +0 -349
  703. package/docs/showcase/table-expandable-rows.tsx +0 -496
  704. package/docs/showcase/table-filter-chips.tsx +0 -468
  705. package/docs/showcase/table-footer-totals.tsx +0 -328
  706. package/docs/showcase/table-grouped-subtotals.tsx +0 -398
  707. package/docs/showcase/table-master-detail.tsx +0 -482
  708. package/docs/showcase/table-pagination.tsx +0 -409
  709. package/docs/showcase/table-states.tsx +0 -371
  710. package/docs/showcase/table-sticky-columns.tsx +0 -377
  711. package/docs/showcase/table-tree-rows.tsx +0 -451
  712. package/docs/showcase/table-view-tabs.tsx +0 -488
  713. package/docs/showcase/tcgm-website.tsx +0 -1047
  714. package/docs/showcase/tenant-brand-color.tsx +0 -338
  715. package/docs/showcase/theme-customization.tsx +0 -1292
  716. package/docs/showcase/theme-lab.tsx +0 -2125
  717. package/docs/themes/flat.css +0 -466
  718. package/docs/themes/glassmorphism.css +0 -960
  719. package/docs/themes/index.ts +0 -219
  720. package/docs/themes/neubrutalism.css +0 -481
  721. /package/dist/components/navigation/{filter-bar.d.ts → toolbar.d.ts} +0 -0
  722. /package/dist/components/navigation/{filter-bar.js → toolbar.js} +0 -0
  723. /package/dist/{components/data-display → lab}/carousel.d.ts +0 -0
  724. /package/dist/{components/data-entry → lab}/emoji-data.d.ts +0 -0
  725. /package/dist/{components/data-entry → lab}/emoji-data.js +0 -0
  726. /package/dist/{components/data-display → lab}/marquee-reveal.d.ts +0 -0
  727. /package/dist/{components/data-display → lab}/marquee-reveal.js +0 -0
@@ -24,7 +24,6 @@
24
24
  "rules": [
25
25
  1
26
26
  ],
27
- "storyPath": "data-entry/Upload.stories.tsx",
28
27
  "tagline": "Bridge Inertia multipart visits to Upload progress, cancellation, retry and error recovery.",
29
28
  "usage": [
30
29
  "Pass the returned callback to Upload.onUpload. Spread callbacks into router.post options; they enable multipart and independent async requests.",
@@ -139,7 +138,7 @@
139
138
  23,
140
139
  31
141
140
  ],
142
- "storyPath": "data-entry/Form.stories.tsx",
141
+ "storyPath": "data-entry/form-root.tsx",
143
142
  "tagline": "Client or server-adapted forms with shared layout, validation, submission, reset and error recovery.",
144
143
  "usage": [
145
144
  "Use inside the documented form composition; do not nest native form elements.",
@@ -267,7 +266,6 @@
267
266
  23,
268
267
  31
269
268
  ],
270
- "storyPath": "data-entry/Form.stories.tsx",
271
269
  "tagline": "Bind a typed field to RHF or a server adapter, with shared FormField presentation and validation.",
272
270
  "usage": [
273
271
  "Use inside the documented form composition; do not nest native form elements.",
@@ -310,7 +308,6 @@
310
308
  23,
311
309
  31
312
310
  ],
313
- "storyPath": "data-entry/Form.stories.tsx",
314
311
  "tagline": "Dynamic typed field collections with stable keys, nested validation and append/remove/reorder operations.",
315
312
  "usage": [
316
313
  "Use inside the documented form composition; do not nest native form elements.",
@@ -349,7 +346,6 @@
349
346
  23,
350
347
  31
351
348
  ],
352
- "storyPath": "data-entry/Form.stories.tsx",
353
349
  "tagline": "Create a typed React Hook Form instance with Zod validation; reuse reset, setValue, trigger, formState and field errors.",
354
350
  "usage": [
355
351
  "Use inside the documented form composition; do not nest native form elements.",
@@ -400,7 +396,6 @@
400
396
  "rules": [
401
397
  9
402
398
  ],
403
- "storyPath": "data-display/Card.stories.tsx",
404
399
  "tagline": "Inline card toolbar with scoped inset and divider edges.",
405
400
  "usage": [
406
401
  "Compose inside Card. Unset border follows its position; explicit border prevents double rules in stacked bars."
@@ -528,7 +523,7 @@
528
523
  "docPath": "docs/data-display/timeline.tsx",
529
524
  "example": "<RangeTimeline label=\"Schedule\" columns={[{ label: \"Week\", units: 7 }]} rows={[{ id: \"task\", label: \"Task\", start: 0, end: 6, startLabel: \"Start: day 1\", endLabel: \"End: day 7\" }]} />",
530
525
  "group": "data-display",
531
- "importPath": "@godxjp/ui/data-display",
526
+ "importPath": "@godxjp/ui/lab",
532
527
  "name": "RangeTimeline",
533
528
  "props": [
534
529
  {
@@ -638,6 +633,7 @@
638
633
  "rules": [],
639
634
  "storyPath": "data-display/timeline.tsx",
640
635
  "tagline": "Horizontal intervals with token-owned geometry and optional endpoint editing.",
636
+ "tier": "lab",
641
637
  "usage": [
642
638
  "Provide a precise non-drag editor in each row label when enabling changes. Clipped endpoints and short intervals omit grips; labels and their editors remain available.",
643
639
  "An interval wholly outside the columns shows a localized direction indicator at that edge of its row: a chevron plus \"before\" at the inline-start edge, \"after\" plus a chevron at the inline-end edge; the chevrons mirror under RTL.",
@@ -804,7 +800,7 @@
804
800
  "rules": [
805
801
  23
806
802
  ],
807
- "storyPath": "layout/PageContainer.stories.tsx",
803
+ "storyPath": "layout/page-container.tsx",
808
804
  "tagline": "Mandatory page shell — EVERY page wraps its content in PageContainer (title/subtitle/extra/footer/breadcrumb).",
809
805
  "usage": [
810
806
  "DO: Always wrap every page's content in PageContainer — it is the mandatory page shell. Pass `title` (required, rendered as `<h1>`) for every page; omitting it leaves the page without an accessible heading.",
@@ -838,16 +834,16 @@
838
834
  "A dashboard page with KPI cards and chart sections — use `variant='default'` with `children={<Flex direction='col' gap='lg'>…</Flex>}` to vertically stack multiple Card/StatCard sections beneath the page title.",
839
835
  "Any deep-nav page in a multi-level admin (e.g. Accounting > Ledger > Journal Entry #42) where a 3-segment breadcrumb trail provides back-navigation without browser history dependence.",
840
836
  "A high-density data reconciliation page where an analyst needs to see maximum rows — use `density='compact'` to tighten all spacing across the DataTable, Toolbar, and controls in a single prop.",
841
- "A chat / messaging detail page where the message list should scroll inside the page and the composer stays pinned at the bottom — use `fill` so the body occupies the full shell height, with `footer={<Composer/>}` + `stickyFooter`. Without `fill` the page would top-pack and the composer would float mid-screen on a tall viewport.",
837
+ "A chat / messaging detail page where the message list should scroll inside the page and the composer stays pinned at the bottom — use `fill` so the body occupies the full shell height, with `footer={<ChatComposer />}` (@godxjp/chat) + `stickyFooter`. Without `fill` the page would top-pack and the composer would float mid-screen on a tall viewport.",
842
838
  "A Slack-like chat channel, a mail thread, or an IDE-style tab view whose top row is the SURFACE's name rather than a document title — `headerScale='chrome'` (usually with `variant='ghost'`) puts the `<h1>` on the body type step so the header reads as a channel label and the band collapses to roughly the height of one control row, leaving the vertical space to the conversation.",
843
- "A chat channel page where a fixed band (channel workflow / pinned-message / connection status) must sit between the page header and the scrolling transcript — `toolbar={<Toolbar>…</Toolbar>}` with `fill` + `footer={<Composer/>}` + `stickyFooter`. The band is outside the scroller, so the transcript never travels under it and the composer stays pinned; a collection page uses the same slot for its filter strip above a full-bleed DataTable (`variant='flush'`)."
839
+ "A chat channel page where a fixed band (channel workflow / pinned-message / connection status) must sit between the page header and the scrolling transcript — `toolbar={<Toolbar>…</Toolbar>}` with `fill` + `footer={<ChatComposer />}` (@godxjp/chat) + `stickyFooter`. The band is outside the scroller, so the transcript never travels under it and the composer stays pinned; a collection page uses the same slot for its filter strip above a full-bleed DataTable (`variant='flush'`)."
844
840
  ]
845
841
  },
846
842
  {
847
843
  "docPath": "docs/layout/page-cover.tsx",
848
- "example": "import { PageContainer, PageCover } from \"@godxjp/ui/layout\";\nimport { Button } from \"@godxjp/ui/general\";\n\n<PageContainer\n title={page.title}\n headerScale=\"display\"\n icon={<span aria-hidden=\"true\">{page.icon}</span>}\n cover={\n <PageCover\n src={page.cover}\n alt=\"\"\n positionY={y}\n onPositionChange={setY}\n repositioning={moving}\n onRepositioningChange={setMoving}\n actions={<Button size=\"sm\" variant=\"secondary\" onClick={() => setMoving(true)}>Reposition</Button>}\n />\n }\n>\n {body}\n</PageContainer>",
844
+ "example": "import { PageContainer } from \"@godxjp/ui/layout\";\nimport { PageCover } from \"@godxjp/ui/lab\";\nimport { Button } from \"@godxjp/ui/general\";\n\n<PageContainer\n title={page.title}\n headerScale=\"display\"\n icon={<span aria-hidden=\"true\">{page.icon}</span>}\n cover={\n <PageCover\n src={page.cover}\n alt=\"\"\n positionY={y}\n onPositionChange={setY}\n repositioning={moving}\n onRepositioningChange={setMoving}\n actions={<Button size=\"sm\" variant=\"secondary\" onClick={() => setMoving(true)}>Reposition</Button>}\n />\n }\n>\n {body}\n</PageContainer>",
849
845
  "group": "layout",
850
- "importPath": "@godxjp/ui/layout",
846
+ "importPath": "@godxjp/ui/lab",
851
847
  "name": "PageCover",
852
848
  "props": [
853
849
  {
@@ -913,17 +909,38 @@
913
909
  "rules": [],
914
910
  "storyPath": "layout/page-cover.tsx",
915
911
  "tagline": "A page's cover image (gh#1160): Notion's banner across the top, or note.com's eyecatch (~1.91:1, rounded) in the reading column. Put it in PageContainer `cover`. The focal point is positionY (object-position); while `repositioning` the image IS a vertical WAI-ARIA slider — drag or arrows / PageUp / PageDown / Home / End, Enter / Escape to finish.",
912
+ "tier": "lab",
916
913
  "usage": [
917
914
  "DO pass it as PageContainer `cover` with the page `icon` — the container places it and overlaps the icon.",
918
915
  "DO wire a Reposition button in `actions` that sets `repositioning`, and persist positionY from onPositionChange."
919
916
  ]
920
917
  },
921
918
  {
919
+ "absorbed": [
920
+ "SpaceCompact"
921
+ ],
922
922
  "example": "import { Flex } from \"@godxjp/ui/layout\";\nimport { Button } from \"@godxjp/ui/general\";\n\n<Flex direction=\"row\" gap=\"sm\" align=\"center\" justify=\"between\" wrap>\n <SearchSummary />\n <Flex direction=\"row\" gap=\"xs\" align=\"center\" wrap>\n <Button variant=\"outline\">リセット</Button>\n <Button>適用</Button>\n </Flex>\n</Flex>",
923
923
  "group": "layout",
924
924
  "importPath": "@godxjp/ui/layout",
925
925
  "name": "Flex",
926
926
  "props": [
927
+ {
928
+ "defaultValue": "false",
929
+ "description": "Weld the children into one control — antd Space.Compact (v32 folded `SpaceCompact` in here). Each child gets its own flex-item wrapper, inner corner radii are zeroed and the shared border collapses to one hairline; each child keeps its own focus ring, name and `size`. `direction` picks the axis (\"row\" default, \"col\"); `gap`/`wrap`/`pad` and the other layout knobs do not apply (a seam has no gap). Named (aria-label/-labelledby, e.g. by a wrapping FormField) it becomes role=\"group\".",
930
+ "name": "attached",
931
+ "type": "boolean"
932
+ },
933
+ {
934
+ "defaultValue": "false",
935
+ "description": "`attached` only. The joined row fills its parent's inline size (antd `block`, renamed to match Button.fullWidth). Field children grow to fill it; a Button keeps its content width (gh#1062).",
936
+ "name": "fullWidth",
937
+ "type": "boolean"
938
+ },
939
+ {
940
+ "description": "`attached` only. Scoped control density for the whole joined row (antd Space.Compact `size`). Each child still carries its OWN `size` prop.",
941
+ "name": "density",
942
+ "type": "\"compact\" | \"default\" | \"comfortable\""
943
+ },
927
944
  {
928
945
  "description": "Floating child actions shown on parent hover/focus-within; always visible on touch.",
929
946
  "name": "reveal",
@@ -1049,8 +1066,8 @@
1049
1066
  2,
1050
1067
  40
1051
1068
  ],
1052
- "storyPath": "layout/Flex.stories.tsx",
1053
- "tagline": "Token-spaced flex primitive with explicit direction, alignment, justification, and wrapping controls.",
1069
+ "storyPath": "layout/flex.tsx",
1070
+ "tagline": "Token-spaced flex primitive with explicit direction, alignment, justification, and wrapping controls — and, with `attached`, a row of controls welded into ONE visual unit (antd Space.Compact).",
1054
1071
  "usage": [
1055
1072
  "DO import from `@godxjp/ui/layout` and reach for Flex when the axis, alignment, justification, or wrap behavior is part of the component contract: `import { Flex } from \"@godxjp/ui/layout\"`.",
1056
1073
  "DO keep spacing on the `gap` prop instead of raw `gap-*`, `space-*`, or padding utilities. Flex uses the same token scale as other layout primitives, so spacing remains tied to the design system.",
@@ -1060,7 +1077,14 @@
1060
1077
  "FULL-BLEED BAND: a marketing/landing section is a `<section>` carrying the edge-to-edge paint (background, border, halo) around ONE `<Flex direction=\"col\" measure=\"wide\" pad={{ inline: 6, block: 20 }}>`. The measure centres and caps the column, `pad` owns the gutter and the band rhythm (`block: 20` = 80px, `block: 24` = 96px — the two band steps). Never a page-local `max-width: 1200px` + `margin-inline: auto` class, and never an inline style saying the same thing: that four-declaration idiom is what this prop replaced (gh#839).",
1061
1078
  "Flex is a plain div with React.HTMLAttributes<HTMLDivElement>; pass `id`, `role`, `aria-*`, `data-*`, and structural className values as needed, but do not use it as a semantic form or button wrapper. When the parent only accepts phrasing content — a TabsTrigger, PopoverTrigger or Button, all of which render a <button> — pass `as=\"span\"` rather than reaching for a raw `<span className=\"flex …\">`.",
1062
1079
  "SEMANTIC LIST: a list of rows is `<Flex as=\"ul\" marker=\"none\" direction=\"col\" gap=\"none\">` with `<ListRow as=\"li\">` children — never a raw `<ul>` (no gap token), never `<div role=\"list\">` + `<div role=\"listitem\">` (ARIA re-describing markup HTML already has), and never a wrapper around each row: the divider is `:not(:last-child)` among SIBLINGS, so a row alone in its own wrapper loses it silently. `ui-audit` flags all three as `no-hand-rolled-list`.",
1063
- "NAMED FLEX = GROUP: a role-less div may not carry a naming attribute (axe aria-allowed-attr), so a Flex given `aria-label`/`aria-labelledby` — e.g. by FormField wrapping a composite range/年月 field — automatically renders `role='group'`, folds `aria-errormessage` into `aria-describedby`, and drops the widget-only `aria-required`/`aria-invalid`. Passing an explicit `role` opts out of all of this and the caller owns the attribute set."
1080
+ "NAMED FLEX = GROUP: a role-less div may not carry a naming attribute (axe aria-allowed-attr), so a Flex given `aria-label`/`aria-labelledby` — e.g. by FormField wrapping a composite range/年月 field — automatically renders `role='group'`, folds `aria-errormessage` into `aria-describedby`, and drops the widget-only `aria-required`/`aria-invalid`. Passing an explicit `role` opts out of all of this and the caller owns the attribute set.",
1081
+ "ATTACHED: DO reach for it when two or more controls must read as ONE field — a number + a unit (\"毎 [2] [週 ▾] ごと\"), a currency Select welded to an amount Input, a search field with an attached filter Select. `ResponsiveGrid columns={2}` puts them in separate cells (which collapses to ONE column on a narrow form card); `Flex attached` keeps them fused on one line regardless of the grid.",
1082
+ "ATTACHED: ONE label for the pair vs per-control: wrap the WHOLE `Flex attached` in a single `FormField label=\"…\"` when the pair reads as one field (\"繰り返し間隔\" over a number+unit row) — a role-less `<div>` cannot carry a cloned `aria-label`/`aria-labelledby` (axe `aria-allowed-attr`), so a NAMED `Flex attached` with no explicit `role` promotes itself to `role=\"group\"` automatically, the same contract `Flex` already honours for a range/年月 pair. Give each CHILD its own `aria-label` instead only when the two controls are independently meaningful outside the row (rare — prefer the one-label pattern for a welded control).",
1083
+ "ATTACHED: DON'T reach for `className=\"rounded-none\"` / `[&>*:not(:first-child)]` utilities to join controls yourself — `ui-audit`'s `no-utility-layout` rule blocks exactly that, and it is precisely the corner-radius seam this component owns.",
1084
+ "ATTACHED: DO set `size` on each CHILD individually, not on `Flex attached` — the row has no size prop of its own by design (see `density`); every control already owns its own `size` axis (`xs|sm|md|lg`).",
1085
+ "ATTACHED: DO use `fullWidth` inside a narrow form card so the joined row spans the field column, exactly like a lone Input would.",
1086
+ "ATTACHED: `fullWidth` is antd `Space.Compact block` (gh#1062): in a row, a FIELD child (Input and its affix/Password/Search forms, NumberInput, Textarea, TagInput, the Date/Time pickers, and the Select/Cascader/TreeSelect/SearchSelect trigger) grows to fill the row, and every other child — a Button, an icon button — keeps its content width. So `[Input][+ Add]` is a full-width field with its submit at content width, and two fields (NumberInput + Select) share the row evenly between them. Deviation from antd, written down: antd grows only its Input family (`width: 100%`); Select/InputNumber/DatePicker keep their own width there. Here every field grows, so two fields never leave an empty tail. `direction=\"col\"` + `fullWidth` stacks full-width children as before.",
1087
+ "ATTACHED: DON'T expect corner-radius welding on `direction=\"col\"` yet — the shared border still collapses, but each child keeps all four of its own corners rounded until a block-axis radius knob exists on the Input/trigger families (documented gap, not a silent one)."
1064
1088
  ],
1065
1089
  "useCases": [
1066
1090
  "Toolbar internals where controls should sit in a row, wrap on narrow widths, and stay vertically centered.",
@@ -1068,68 +1092,10 @@
1068
1092
  "Empty-state or loading blocks that center content on both axes using `align='center'` and `justify='center'`.",
1069
1093
  "Form sub-sections where a vertical group needs stretched children or centered helper content beyond what a plain column Flex provides.",
1070
1094
  "Badge, chip, or tag clusters where wrapping is required but the caller also needs explicit gap control.",
1071
- "Low-level layout composition inside custom components where raw flex classes would duplicate the primitive."
1072
- ]
1073
- },
1074
- {
1075
- "docPath": "layout/space-compact.tsx",
1076
- "example": "import { SpaceCompact } from \"@godxjp/ui/layout\";\nimport { FormField, NumberInput, Select } from \"@godxjp/ui/data-entry\";\n\n<FormField label=\"繰り返し間隔\">\n <SpaceCompact>\n <NumberInput aria-label=\"間隔の数\" min={1} defaultValue={2} />\n <Select\n aria-label=\"単位\"\n defaultValue=\"week\"\n options={[\n { value: \"day\", label: \"日\" },\n { value: \"week\", label: \"週\" },\n { value: \"month\", label: \"月\" },\n ]}\n />\n </SpaceCompact>\n</FormField>",
1077
- "group": "layout",
1078
- "importPath": "@godxjp/ui/layout",
1079
- "name": "SpaceCompact",
1080
- "props": [
1081
- {
1082
- "defaultValue": "\"horizontal\"",
1083
- "description": "Layout axis (antd `orientation`; the installed antd `direction` prop is itself @deprecated in favour of this, so only `orientation` is ported).",
1084
- "name": "orientation",
1085
- "type": "\"horizontal\" | \"vertical\""
1086
- },
1087
- {
1088
- "description": "antd's boolean spelling of orientation=\"vertical\". `orientation` wins when both are given.",
1089
- "name": "vertical",
1090
- "type": "boolean"
1091
- },
1092
- {
1093
- "defaultValue": "false",
1094
- "description": "The row fills its parent's inline size (antd `block`, renamed to match Button.fullWidth — the same rename, the same reason: this library's controlled vocabulary wins on values). Field children grow to fill it; a Button keeps its content width.",
1095
- "name": "fullWidth",
1096
- "type": "boolean"
1097
- },
1098
- {
1099
- "description": "Scoped control density for the whole row (antd Space.Compact `size`, small|middle|large — the ConfigProvider ambient-size cascade this library already owns under `density`; see `Form.density`). Each child still carries its OWN `size` prop.",
1100
- "name": "density",
1101
- "type": "\"compact\" | \"default\" | \"comfortable\""
1102
- },
1103
- {
1104
- "description": "DOM id of the row.",
1105
- "name": "id",
1106
- "type": "string"
1107
- }
1108
- ],
1109
- "related": [
1110
- "Flex `gap` — siblings that stay visually SEPARATE (a normal control row with spacing between items). SpaceCompact is the opposite case: controls that must read as ONE box.",
1111
- "ResponsiveGrid — equal-width form CELLS; SpaceCompact welds controls WITHIN one cell (or one row) into a single unit.",
1112
- "FormField — wraps the whole SpaceCompact with one label when the pair is one field.",
1113
- "NumberInput, Select — the two controls the shipping use case welds; either composes cleanly inside SpaceCompact because both already expose the radius knob it reads (`--input-radius-*`, `--control-trigger-radius-*`)."
1114
- ],
1115
- "rules": [
1116
- 2
1117
- ],
1118
- "storyPath": "layout/SpaceCompact.stories.tsx",
1119
- "tagline": "Weld a row (or column) of controls into one visual unit — antd Space.Compact. Inner corner radii are zeroed and the shared border collapses to one hairline; each child keeps its own focus ring and its own size.",
1120
- "usage": [
1121
- "DO reach for it when two or more controls must read as ONE field — a number + a unit (\"毎 [2] [週 ▾] ごと\"), a currency Select welded to an amount Input, a search field with an attached filter Select. `ResponsiveGrid columns={2}` puts them in separate cells (which collapses to ONE column on a narrow form card); SpaceCompact keeps them fused on one line regardless of the grid.",
1122
- "ONE label for the pair vs per-control: wrap the WHOLE `SpaceCompact` in a single `FormField label=\"…\"` when the pair reads as one field (\"繰り返し間隔\" over a number+unit row) — a role-less `<div>` cannot carry a cloned `aria-label`/`aria-labelledby` (axe `aria-allowed-attr`), so a NAMED SpaceCompact with no explicit `role` promotes itself to `role=\"group\"` automatically, the same contract `Flex` already honours for a range/年月 pair. Give each CHILD its own `aria-label` instead only when the two controls are independently meaningful outside the row (rare — prefer the one-label pattern for a welded control).",
1123
- "DON'T reach for `className=\"rounded-none\"` / `[&>*:not(:first-child)]` utilities to join controls yourself — `ui-audit`'s `no-utility-layout` rule blocks exactly that, and it is precisely the corner-radius seam this component owns.",
1124
- "DO set `size` on each CHILD individually, not on SpaceCompact — the row has no size prop of its own by design (see `density`); every control already owns its own `size` axis (`xs|sm|md|lg`).",
1125
- "DO use `fullWidth` inside a narrow form card so the joined row spans the field column, exactly like a lone Input would.",
1126
- "`fullWidth` is antd `Space.Compact block` (gh#1062): in a row, a FIELD child (Input and its affix/Password/Search forms, NumberInput, Textarea, TagInput, the Date/Time pickers, and the Select/Cascader/TreeSelect/SearchSelect trigger) grows to fill the row, and every other child — a Button, an icon button — keeps its content width. So `[Input][+ Add]` is a full-width field with its submit at content width, and two fields (NumberInput + Select) share the row evenly between them. Deviation from antd, written down: antd grows only its Input family (`width: 100%`); Select/InputNumber/DatePicker keep their own width there. Here every field grows, so two fields never leave an empty tail. `orientation=\"vertical\"` + `fullWidth` stacks full-width children as before.",
1127
- "DON'T expect corner-radius welding on `orientation=\"vertical\"` yet — the shared border still collapses, but each child keeps all four of its own corners rounded until a block-axis radius knob exists on the Input/trigger families (documented gap, not a silent one)."
1128
- ],
1129
- "useCases": [
1130
- "定期課題 (recurring issue) interval row: 毎 [NumberInput] [Select 週/月/日 ▾] ごと, fused on one line inside a FormField, inside a 2-column ResponsiveGrid form card that collapses to one column.",
1131
- "A currency amount: [Select 通貨 ▾][NumberInput 金額] welded so the currency reads as part of the amount field, not a separate control beside it.",
1132
- "A filtered search bar: [Select scope][SearchInput query] as one visual field, `fullWidth` inside a page toolbar."
1095
+ "Low-level layout composition inside custom components where raw flex classes would duplicate the primitive.",
1096
+ "ATTACHED: a recurring-issue interval row, \"毎 [NumberInput] [Select 週/月/日 ▾] ごと\", fused on one line inside a FormField, inside a 2-column ResponsiveGrid form card that collapses to one column.",
1097
+ "ATTACHED: A currency amount: [Select 通貨 ▾][NumberInput 金額] welded so the currency reads as part of the amount field, not a separate control beside it.",
1098
+ "ATTACHED: A filtered search bar: [Select scope][SearchInput query] as one visual field, `fullWidth` inside a page toolbar."
1133
1099
  ]
1134
1100
  },
1135
1101
  {
@@ -1186,7 +1152,7 @@
1186
1152
  ],
1187
1153
  "related": [
1188
1154
  "Flex — use Flex (direction col or row) for sequential blocks of mixed-width content (forms, description lists, button rows). Use ResponsiveGrid only when you want equal-width, auto-reflowing tile columns.",
1189
- "SplitPane — use SplitPane for a fixed two-panel side-by-side layout with a defined primary/secondary ratio that does NOT collapse to stacked tiles. Use ResponsiveGrid when you want automatic column count collapse on narrow screens.",
1155
+ "SplitPane — a main column beside a FIXED-rem aside that splits at the pane's own 48rem and stacks below it (a user-draggable ratio is ResizablePanel). Use ResponsiveGrid when you want automatic column count collapse on narrow screens.",
1190
1156
  "StatCard — the canonical direct child of ResponsiveGrid for KPI tiles. StatCard is self-contained (draws its own bordered card); never wrap it in Card/CardContent when placing it inside ResponsiveGrid.",
1191
1157
  "SkeletonStat — the loading-state sibling of StatCard, used as a drop-in placeholder child of ResponsiveGrid with the same columns count while KPI data is in flight."
1192
1158
  ],
@@ -1194,7 +1160,7 @@
1194
1160
  24,
1195
1161
  40
1196
1162
  ],
1197
- "storyPath": "layout/ResponsiveGrid.stories.tsx",
1163
+ "storyPath": "layout/responsive-grid.tsx",
1198
1164
  "tagline": "Container-responsive card grid with configurable base, sm, md and lg columns.",
1199
1165
  "usage": [
1200
1166
  "ResponsiveGrid.Item span={2} owns a responsive column span; an object {base:1,lg:2} sets explicit steps, clamped to the parent columns.",
@@ -1347,15 +1313,14 @@
1347
1313
  }
1348
1314
  ],
1349
1315
  "related": [
1350
- "AppShell — opinionated wrapper that composes AppShell + a frozen default Topbar in three props (menu, children, breadcrumb). Use AppShell for quick scaffolding when the default GodX product chip and no-op search/notification handlers are acceptable; switch to AppShell directly the moment you need a custom entity switcher, real onSearchOpen, user slot, or any topbar configuration.",
1351
1316
  "Sidebar — the canonical node to pass as AppShell's `sidebar` prop; owns activeId, collapsible submenu groups, collapsed icon-only mode, and section labels. Never hand-roll a nav list inside the sidebar slot.",
1352
- "Topbar — the structured topbar component to pass to AppShell's `topbar` prop when you need live product/project chip switchers, search, notifications, sidebar toggle, user avatar, or rightSlot extras. A custom `topbar` replaces the DEFAULT bar layout, so `topbarLeft`/`topbarRight` are ignored — but `logo` is not: the shell always renders it, and `topbarSpan` decides whether it lands in the bar or at the sidebar's head.",
1317
+ "Topbar — the bar to pass to AppShell's `topbar` prop when it needs live controls: compose a switcher, search, notifications or the user avatar into its `start` / `center` / `end` slots. A custom `topbar` replaces the DEFAULT bar layout, so `topbarLeft`/`topbarRight` are ignored — but `logo` is not: the shell always renders it, and `topbarSpan` decides whether it lands in the bar or at the sidebar's head.",
1353
1318
  "PageContainer — the mandatory direct child inside AppShell's `children` for every page; provides title, subtitle, extra actions, breadcrumb, footer, variant (flush/narrow/ghost), and density. Never render raw content directly as AppShell's child without a PageContainer wrapper."
1354
1319
  ],
1355
1320
  "rules": [
1356
1321
  23
1357
1322
  ],
1358
- "storyPath": "layout/AppShell.stories.tsx",
1323
+ "storyPath": "layout/app-shell.tsx",
1359
1324
  "tagline": "Root application shell — composes sidebar, topbar rail, main content area, and optional footer.",
1360
1325
  "usage": [
1361
1326
  "useAppShellNavigationMode() returns drawer/docked at the same 56.25rem boundary as AppShell. The root also publishes data-navigation. Use this instead of a consumer media query.",
@@ -1363,7 +1328,7 @@
1363
1328
  "DO rely on AppShell's OWNED mobile drawer at or below 900px (NOT the Tailwind `lg` 1024px step — the shipped media query is `width <= 56.25rem`): it renders a hamburger trigger in the topbar and a focus-trapped Sheet (Esc + overlay close, focus returns to the trigger). `mobileNav` defaults to the `sidebar` node, so the same nav is reachable on mobile with no wiring — never hide the sidebar without providing this. Pass a tailored `mobileNav`, or `mobileNav={null}` only when navigation lives elsewhere (e.g. a bottom bar).",
1364
1329
  "DO set `responsiveNavigation=\"docked\"` only when the approved product contract retains its sidebar below 900px. AppShell keeps the same sidebar/footer/active navigation in a token-sized grid track and removes the redundant drawer trigger; never reproduce this with consumer media queries.",
1365
1330
  "DO let the drawer nav own its own inset: AppShell renders `mobileNav` in a Sheet body whose inline padding is the `--app-shell-mobile-nav-inset` token (near-zero by default) instead of the generic 24px sheet chrome inset, so a <Sidebar> in the drawer is not double-padded (its own --sidebar-nav-scroll-padding already insets each row). If a custom `mobileNav` node needs the full chrome inset, set `--app-shell-mobile-nav-inset: var(--space-6)` in the service theme — never patch the drawer with a `[data-slot='sheet-body']` selector in app CSS.",
1366
- "DO use the auto-built topbar rail (logo / topbarLeft / topbarRight) for simple shells. Pass a fully configured <Topbar> to the `topbar` prop when you need live handlers (entity switcher via productMenu, search, notifications, user avatar) — `topbarLeft`/`topbarRight` are then ignored (they are slots of the DEFAULT bar layout, and a custom `topbar` IS replacing that layout; a dev-mode warning names them). `logo` is NOT one of them: it is always rendered, because brand identity belongs to the frame rather than to the bar's contents. Two repos passed both for months and got no logo at all — no error, no warning, and a bar that still looked right because it had other content.",
1331
+ "DO use the auto-built topbar rail (logo / topbarLeft / topbarRight) for simple shells. Pass a fully configured <Topbar start={…} center={…} end={…}> to the `topbar` prop when you need live controls (an entity switcher, search, notifications, the user avatar) — Topbar has those three slots and no per-control props — `topbarLeft`/`topbarRight` are then ignored (they are slots of the DEFAULT bar layout, and a custom `topbar` IS replacing that layout; a dev-mode warning names them). `logo` is NOT one of them: it is always rendered, because brand identity belongs to the frame rather than to the bar's contents. Two repos passed both for months and got no logo at all — no error, no warning, and a bar that still looked right because it had other content.",
1367
1332
  "DO pass `logo` and let the shell place it — do NOT put the lockup in `Sidebar`'s `brand` slot or hand-position it in a `Topbar` slot. THE TOP-LEFT CORNER BELONGS TO WHOEVER `topbarSpan` SAYS: under `content` the rail runs the full window height and the brand sits at the sidebar's head, aligned to that track; under `full` the bar runs edge to edge and the brand goes in the bar with the rest of the space-level chrome. Placing it yourself pins it to one of those answers, and the axis then moves the rest of the shell out from under it (measured on a shipped consumer: the logo floating in the content column at x=280, indented 24px past the rail it should have sat above).",
1368
1333
  "DO give a wide brand lockup a narrow-bar answer with `logoCompact` instead of cropping your own artwork in the app: `<AppShell topbarSpan=\"full\" logo={<FullLockup />} logoCompact={<MarkOnly />} />` swaps the NODE below the `sm` step, which is the only way to change a `viewBox` (no stylesheet can set an attribute). Without it the brand cell still gives room back — it is capped at --app-shell-brand-compact-max-inline-size below that step and cropped from the inline-end — where before it kept its intrinsic width at every viewport: measured with a 6:1 lockup at 390px, brand 143.7px, `.ui-topbar` 150.3px, `.ui-topbar-start` width 0 and the end cluster's last cell painting at x=384.8 on a 390px viewport (gh#728).",
1369
1334
  "DO build a chat / mail / IDE shell by omitting ALL FOUR bar slots (`topbar`, `topbarLeft`, `topbarRight`, `logo`) — a shell whose PAGE owns the top row. AppShell then renders no `<header class='app-topbar'>` and marks its root `data-topbar='none'`, so the bar's grid row collapses to zero and the page header IS the first row of chrome. Keeping an empty bar instead costs a fixed `--app-shell-bar-height` band plus its border and card background, stacking a second row of chrome (~48px + the page header) over exactly the region a transcript or an editor needs most. The mobile drawer survives: at or below 900px the header returns carrying the hamburger alone.",
@@ -1384,125 +1349,27 @@
1384
1349
  "App-level footer (e.g. version/build info, compliance notice): pass a <footer> node to AppShell's `footer` prop — it renders outside `<main>` so it stays pinned below the scroll area.",
1385
1350
  "Rapid prototype or internal tool where you want a branded shell with minimal topbar: skip the `topbar` prop entirely and use `logo`, `topbarLeft`, `topbarRight` to build the rail declaratively without instantiating <Topbar>.",
1386
1351
  "Breadcrumb-aware shell: pass a <Breadcrumb items={…}> node to AppShell's `breadcrumb` prop so the breadcrumb strip appears above all page content without each page having to render it separately.",
1387
- "Chat / mail / IDE shell: a channel rail in `sidebar`, no bar slots at all, and a <PageContainer fill toolbar={…} footer={<Composer/>} stickyFooter> as children — the page's own header is the top row and the shell reserves no band above it, while the 900px drawer still reaches the channel list."
1352
+ "Chat / mail / IDE shell: a channel rail in `sidebar`, no bar slots at all, and a <PageContainer fill toolbar={…} footer={<ChatComposer />} stickyFooter> (ChatComposer from @godxjp/chat) as children — the page's own header is the top row and the shell reserves no band above it, while the 900px drawer still reaches the channel list."
1388
1353
  ]
1389
1354
  },
1390
1355
  {
1391
1356
  "absorbed": [
1357
+ "AuthShell",
1392
1358
  "OrganizationChoiceList"
1393
1359
  ],
1394
- "example": "import { AuthShell, AuthIdentity, AuthFooter } from \"@godxjp/ui/layout\";\nimport { Reveal, Logo, Button } from \"@godxjp/ui/general\";\nimport { Card, CardContent, CardHeader, CardTitle } from \"@godxjp/ui/data-display\";\nimport { AppSettingPicker } from \"@godxjp/ui/navigation\";\n\nexport function DeviceAuthorizationPage() {\n return (\n // 380px card at 1440/1024 · 5px inline gutter at 390 — all token-owned, no page CSS.\n <AuthShell\n variant=\"canonical\"\n preset=\"device-authorization\"\n brand={<Logo mark=\"godx\" tone=\"success\" />}\n footer={\n <AuthFooter\n product=\"GoDX ID\"\n terms=\"Terms\"\n privacy=\"Privacy\"\n locale={<AppSettingPicker kind=\"locale\" appearance=\"labeled\" compact />}\n />\n }\n >\n <Reveal>\n <Card>\n <CardHeader>\n <AuthIdentity title=\"デバイスを認証\" requester=\"勤怠管理が認証を要求しています\" />\n <CardTitle level={2}>確認コードを入力</CardTitle>\n </CardHeader>\n <CardContent>\n {/* InputOTP + actions */}\n <Button fullWidth>デバイスを承認</Button>\n </CardContent>\n </Card>\n </Reveal>\n </AuthShell>\n );\n}",
1360
+ "example": "import { CenteredShell, Topbar, Flex } from \"@godxjp/ui/layout\";\nimport { AppSettingPicker } from \"@godxjp/ui/navigation\";\nimport { Avatar, AvatarFallback, Card, CardContent, CardHeader, CardTitle } from \"@godxjp/ui/data-display\";\nimport { Button, Text } from \"@godxjp/ui/general\";\n\nexport function MyPage() {\n return (\n <CenteredShell\n width=\"md\"\n topbar={\n <Topbar\n start={\n <Avatar className=\"rounded-md\">\n <AvatarFallback className=\"bg-primary text-primary-foreground font-bold\">G</AvatarFallback>\n </Avatar>\n }\n end={\n <>\n <AppSettingPicker kind=\"locale\" />\n <Button variant=\"ghost\" size=\"sm\">田中 太郎</Button>\n </>\n }\n />\n }\n footer={<Text size=\"xs\" tone=\"muted\">© 2026 GodX</Text>}\n >\n <Flex direction=\"col\" gap=\"lg\">\n <Card>\n <CardHeader><CardTitle level={1}>マイページ</CardTitle></CardHeader>\n <CardContent>アカウントの概要。</CardContent>\n </Card>\n </Flex>\n </CenteredShell>\n );\n}",
1395
1361
  "group": "layout",
1396
1362
  "importPath": "@godxjp/ui/layout",
1397
- "name": "AuthShell",
1363
+ "name": "CenteredShell",
1398
1364
  "props": [
1399
1365
  {
1400
- "description": "Centred content — typically a single auth <Card> holding the form.",
1401
- "name": "children",
1402
- "required": true,
1403
- "type": "ReactNode"
1404
- },
1405
- {
1406
- "description": "Brand bar slot pinned to the top (e.g. a <Logo> / product mark).",
1407
- "name": "brand",
1408
- "type": "ReactNode"
1409
- },
1410
- {
1411
- "description": "Page-level controls pinned to the TOP-RIGHT of the same banner row as `brand` — a locale <Select>/<AppSettingPicker>, a theme <ToggleGroup>, a \"need help?\" link. They belong to the PAGE, not the auth form, so they sit in the bar, not in the card. The banner renders as soon as `brand` OR `actions` is present.",
1412
- "name": "actions",
1413
- "type": "ReactNode"
1414
- },
1415
- {
1416
- "description": "Footer slot pinned to the bottom (legal links, locale switch, support).",
1417
- "name": "footer",
1418
- "type": "ReactNode"
1419
- },
1420
- {
1421
- "defaultValue": "\"default\"",
1422
- "description": "Visual contract for the auth surface. \"canonical\" applies the shared DXS compact geometry (36px controls, 22.5rem/360px card measure, responsive page insets, tighter field labels) through component tokens. \"default\" keeps the comfortable 44px shell with the 24rem card.",
1366
+ "defaultValue": "\"page\"",
1367
+ "description": "Which shape. \"page\": the authenticated centred column (topbar/width/align start|center/preset default|public-landing). \"auth\": the unauthenticated auth shell (brand/actions/footer, a vertically centred ~24rem card at the comfortable 44px control tier, preset = a hosted-identity flow, align anchored|center, measure, density). \"auth-canonical\": the auth shell on the shared DXS compact geometry (36px controls, 22.5rem card, responsive insets). The variant decides which props type-check. v32 folded the former `AuthShell` in here (`AuthShell` → `variant=\"auth\"`, `AuthShell variant=\"canonical\"` → `variant=\"auth-canonical\"`).",
1423
1368
  "name": "variant",
1424
- "type": "\"default\" | \"canonical\""
1425
- },
1426
- {
1427
- "defaultValue": "\"default\"",
1428
- "description": "Named flow GEOMETRY — the package-owned layout contract for a canonical hosted-identity flow. \"device-authorization\" = a 380px card at 1440/1024 with a 5px inline page gutter at 390, AND the code field itself: the preset hands --otp-slot-{inline,block}-size the canonical 27.5x52 device-grant slot, so two 4-slot grouped boxes measure 112x54 instead of the 146x38 the square --control-height tier produced. \"registration\" = the 360px sign-up measure with a 15px inline gutter at 390 (the same page rhythm as \"login\", so sign-in to sign-up never jumps on a phone). START-aligned like login: a sign-up card is the tallest surface in the set (name/email/password/confirm/strength/consent/submit/providers) and a vertically centred tall card overflows ABOVE the scroll origin on a short viewport, putting its first field out of reach.",
1429
- "name": "preset",
1430
- "type": "\"default\" | \"login\" | \"registration\" | \"device-authorization\" | \"context-selection\" | \"account-recovery\""
1431
- },
1432
- {
1433
- "defaultValue": "\"default\"",
1434
- "description": "Inline MEASURE of the shell content slot. \"default\" is the single auth card (24rem, or 22.5rem under variant=\"canonical\"). \"wide\" opens the slot to --auth-shell-wide-card-max-width (64rem) for a SPLIT login: a brand/marketing panel beside the auth card, laid out with <ResponsiveGrid columns={{ sm: 1, lg: 2 }}>. The wide slot centres with auto margins, so a tall two-column layout starts at the top instead of overflowing above the scroll origin. Ignored under a `preset` — a preset already owns its flow geometry.",
1435
- "name": "measure",
1436
- "type": "\"default\" | \"wide\""
1437
- },
1438
- {
1439
- "description": "Block-axis placement of the auth column — ORTHOGONAL to `preset` the way `variant` is: the preset owns the page MEASURE (card width, inline gutters, section rhythm), `align` owns where that column sits vertically. Omit it to keep the preset's own choice: \"login\" and \"registration\" anchor so a requester/identity line that wraps to two lines cannot move the card (gh#237), every other preset centres. Pass \"center\" for a vertically centred column — the block-start inset collapses to the preset's block-end one on desktop AND mobile, so the padding is symmetric and justify-content has nothing to fight — or \"anchored\" for a top-anchored one. This is what replaces re-declaring a preset's offset tokens from consumer CSS, the page-local-vertical-offset anti-pattern presets exist to remove. HAZARD on a tall flow: a vertically centred tall card overflows ABOVE the scroll origin on a short viewport, putting its first field out of reach — which is why \"registration\" anchors by default; \"center\" is legal there but is the caller's judgement.",
1440
- "name": "align",
1441
- "type": "\"anchored\" | \"center\""
1442
- },
1443
- {
1444
- "description": "Vertical density scoped to auth-card descendants. Defaults to \"compact\" under variant=\"canonical\" and \"comfortable\" otherwise.",
1445
- "name": "density",
1446
- "type": "\"comfortable\" | \"compact\""
1369
+ "type": "\"page\" | \"auth\" | \"auth-canonical\""
1447
1370
  },
1448
1371
  {
1449
- "description": "Extra CSS classes merged onto the shell root.",
1450
- "name": "className",
1451
- "type": "string"
1452
- }
1453
- ],
1454
- "related": [
1455
- "AppShell — the shell for AUTHENTICATED app pages (sidebar + topbar + main). AuthShell is its unauthenticated counterpart (brand bar + centred card + footer). Never nest the two.",
1456
- "Reveal — wrap the auth <Card> in <Reveal> for the entrance animation; AuthShell delegates motion (and prefers-reduced-motion handling) to it rather than baking an animation in.",
1457
- "Card — the canonical container for the auth form; place the form inside <CardContent>. AuthShell centres and width-constrains it.",
1458
- "EmptyState — with `tone=\"success\"` for a confirmation card inside the shell (e.g. device approved).",
1459
- "AuthIdentity / AuthFooter / AuthStack / AuthDivider — the auth composites that fill the shell: the GoDX identity mark + heading + requesting-client line, the mono legal footer (its `locale` slot takes an <AppSettingPicker kind=\"locale\" appearance=\"labeled\" compact />), the 12px section rhythm, and the labelled \"or\" rule.",
1460
- "ListRow — compose the organisation choice list for `preset=\"context-selection\"` as <Card><CardContent flush><Flex as=\"ul\" marker=\"none\"> of <ListRow as=\"li\">: shared row dividers, no per-row card outline. There is no separate OrganizationChoiceList component — that is a composition pattern (rule #46), not a framework component."
1461
- ],
1462
- "rules": [
1463
- 23
1464
- ],
1465
- "storyPath": "layout/AuthShell.stories.tsx",
1466
- "tagline": "Centred auth/login page shell — brand bar (top) + centred card (main) + footer, over min-h-dvh, at comfortable control density.",
1467
- "usage": [
1468
- "DO pass a single <Card> (with the form inside <CardContent>) as `children` — AuthShell centres it and constrains its width via `--auth-shell-card-max-width`; do NOT hand-roll a `.auth-shell-main` / `.ui-auth-scope` wrapper.",
1469
- "DO use `variant=\"canonical\" preset=\"login\"` for SCR-001 and pass <AuthIdentity>, <Card>, <AuthFooter> as direct children in that order (an anchor may wrap AuthIdentity). The preset owns the identity slot, card anchor, 20px section rhythm and compact card block inset for standalone and real requester states. Do not wrap the three sections in a consumer Flex/Stack or the semantic grid cannot anchor them.",
1470
- "DO select a `preset` instead of overriding geometry: `preset=\"login\"` for the stable SCR-001 identity/card/footer anchor, `preset=\"registration\"` for the sign-up form and its pending-email state, `preset=\"device-authorization\"` for the 380px OAuth device-grant measure, `preset=\"context-selection\"` for the 25rem organisation/context picker, `preset=\"account-recovery\"` for the 432px SCR-008 recovery/MFA panel. Page-local width/inset/vertical-offset variables are the exact anti-pattern these presets replace.",
1471
- "DO build the SOCIAL / PROVIDER ACTION row as a COMPOSITION — there is NO SocialLinks component: `<AuthDivider label=\"or\" />` followed by a `Flex direction=\"col\" gap=\"sm\"` of real `Button variant=\"outline\"` with the provider glyph as an aria-hidden icon. The package deliberately does not own it: which providers a product offers, in what order, and what consent they imply are product decisions, and a component would have to invent them. `disabled` / `loading` are the Button's own props — do not add a provider-specific API.",
1472
- "DO build the ORGANIZATION CHOICE LIST as a COMPOSITION — there is NO OrganizationChoiceList component: `Card` > `CardContent flush` > a `<Flex as=\"ul\" marker=\"none\">` of `ListRow as=\"li\"` (leading Avatar, title, description, trailing Button). `CardContent flush` is what gives shared row dividers instead of a card outline per row. Its states are existing exports, never bespoke markup: Skeleton rows for loading, `EmptyState` for no invitations, `Alert tone=\"destructive\"` for a failed fetch and `Alert tone=\"warning\"` for permission-denied. See the auth-shell-context and auth-shell-registration frames.",
1473
- "DO build the password-recovery and sign-in MFA CHALLENGE panels as a COMPOSITION inside `preset=\"account-recovery\"` — there is NO PasswordRecoveryPanel and NO MfaChallengePanel component: Card > CardHeader(CardTitle + CardDescription, INSIDE the bordered surface) > CardContent > AuthStack[ Alert notice · FormField fields · Button fullWidth · Flex justify=\"between\" wrap fallback row ]. Do NOT put AuthIdentity above the panel there (it always renders the hosted mark), and NEVER reuse TwoFactorSetup — that is the ENROLLMENT dialog, not a sign-in challenge. See the `auth-recovery-panels` pattern.",
1474
- "DO combine `variant` and `preset` — they are orthogonal: `variant` owns control density + heading size, `preset` owns the page measure. `variant=\"canonical\" preset=\"device-authorization\"` is the canonical device screen.",
1475
- "DO let `preset=\"context-selection\"` space the auth column: it turns the card slot into a flex column with a tokenized `--auth-shell-card-stack-gap`, so an intro (<AuthIdentity>), the choice <Card> and a trailing \"remember\" row pass as three siblings with NO page-local spacing.",
1476
- "DO NOT add a page-local width, inset or colour to hit an artboard — if a measure is missing, it is a library gap: a new preset or token, never consumer CSS (rules #44/#45).",
1477
- "DO state the block placement with `align` when a product wants the opposite of its preset's default — align=\"center\" on preset=\"login\" for a vertically centred SCR-001, align=\"anchored\" to top-anchor a preset that centres. It collapses the preset's block-start inset to its block-end one on desktop AND mobile, so justify-content has nothing to fight. Re-declaring --auth-shell-login-flow-offset-block (or any preset offset token) from a consumer stylesheet is the anti-pattern it replaces.",
1478
- "DO put the product/brand mark in `brand` (a <Logo> or an <Avatar>) — it renders as the top banner landmark; omit it and the banner is not rendered.",
1479
- "DO put page-level controls in `actions` — the locale picker, the theme <ToggleGroup>, a \"need help?\" link. They land at the banner's inline end at a tokenized gap (`--auth-shell-bar-gap`), and the banner appears even with no `brand`. Do NOT hand-roll a top-right row with `ms-auto` on the `brand` content, and do NOT reach for CenteredShell just to get a topbar with actions: CenteredShell is the AUTHENTICATED shell.",
1480
- "DO use `measure=\"wide\"` for the SPLIT login — a brand/marketing panel beside the auth card. The content slot opens to 64rem (`--auth-shell-wide-card-max-width`) and centres with auto margins, so the tall two-column layout starts at the top instead of overflowing above the scroll origin; lay the two halves out with <ResponsiveGrid columns={{ sm: 1, lg: 2 }}> and hide the panel below `lg`. It is IGNORED under a `preset` (a preset owns its flow geometry), so pick one or the other.",
1481
- "DO use `footer` for compliance/legal/support links or a locale switch — it renders as the contentinfo landmark below the card.",
1482
- "DO wrap the card in <Reveal> for the entrance animation (`<AuthShell><Reveal><Card/></Reveal></AuthShell>`) — Reveal honours prefers-reduced-motion; AuthShell itself stays layout-only.",
1483
- "DO NOT re-scope control height or heading size in the app — AuthShell already sets the comfortable control tier (44px, WCAG touch floor) and the larger auth heading via `--auth-shell-control-height` / `--auth-shell-heading-size`; a service retunes those tokens, not a bespoke class.",
1484
- "DO tune the COMPACT auth card through its three independent knobs, never a consumer selector on `[data-slot=\"card-content\"]`: `--auth-shell-compact-card-inset` = the inline column, `--auth-shell-card-padding-block-compact` = the card's block (top/bottom) padding — it is wired to the card's `--card-space-shell-y` and really moves CardContent's block edges — and `--auth-shell-card-body-gap-compact` = the header↔body gap. Each defaults to today's canonical rhythm, so overriding one moves ONLY that axis.",
1485
- "DO NOT nest AuthShell inside AppShell (or vice-versa) — AuthShell is the ROOT shell for unauthenticated pages (login/mfa/passkey/device/reset); AppShell is for the authenticated app."
1486
- ],
1487
- "useCases": [
1488
- "Canonical Login (SCR-001): <AuthShell variant=\"canonical\" preset=\"login\"> with direct AuthIdentity · Card · AuthFooter children; the card remains anchored when requester is absent, one line or wraps to two lines.",
1489
- "Login page: <AuthShell brand={<Logo/>} footer={<AuthFooter/>}> wrapping a <Card> with the email/password form and a primary <Button fullWidth>.",
1490
- "MFA / passkey / device-authorisation step: same shell, a <Card> with the one-time-code <InputOTP> or a passkey prompt.",
1491
- "OAuth device-grant screen (SCR-004): <AuthShell variant=\"canonical\" preset=\"device-authorization\"> — a 380px card at 1440/1024 and a 5px inline gutter at 390, with zero page-local CSS.",
1492
- "Organisation / context selection (/select-context): <AuthShell variant=\"canonical\" preset=\"context-selection\" brand={<Logo mark=\"godx\" />}> with an <AuthIdentity> intro, a <Card><CardContent flush> list of <ListRow as=\"li\"> organisations, and a trailing \"remember this choice\" <Checkbox> — the preset spaces the three sections.",
1493
- "Split product login: <AuthShell measure=\"wide\" brand={<Logo/>} actions={<><Select locale/><ToggleGroup theme/></>}> around a <ResponsiveGrid columns={{ sm: 1, lg: 2 }}> whose first cell is the brand/value panel (hidden below lg) and whose second is the auth <Card>. See docs/showcase/case4-login.",
1494
- "Password reset / forgot-password / accept-invite: the centred single-card flow with a brand bar and a legal footer.",
1495
- "SSO landing / success confirmation: pair with an <EmptyState tone=\"success\"> inside the card for an approved-device confirmation."
1496
- ]
1497
- },
1498
- {
1499
- "example": "import { CenteredShell, Topbar, Flex } from \"@godxjp/ui/layout\";\nimport { AppSettingPicker } from \"@godxjp/ui/navigation\";\nimport { Avatar, AvatarFallback, Card, CardContent, CardHeader, CardTitle } from \"@godxjp/ui/data-display\";\nimport { Button, Text } from \"@godxjp/ui/general\";\n\nexport function MyPage() {\n return (\n <CenteredShell\n width=\"md\"\n topbar={\n <Topbar\n start={\n <Avatar className=\"rounded-md\">\n <AvatarFallback className=\"bg-primary text-primary-foreground font-bold\">G</AvatarFallback>\n </Avatar>\n }\n end={\n <>\n <AppSettingPicker kind=\"locale\" />\n <Button variant=\"ghost\" size=\"sm\">田中 太郎</Button>\n </>\n }\n />\n }\n footer={<Text size=\"xs\" tone=\"muted\">© 2026 GodX</Text>}\n >\n <Flex direction=\"col\" gap=\"lg\">\n <Card>\n <CardHeader><CardTitle level={1}>マイページ</CardTitle></CardHeader>\n <CardContent>アカウントの概要。</CardContent>\n </Card>\n </Flex>\n </CenteredShell>\n );\n}",
1500
- "group": "layout",
1501
- "importPath": "@godxjp/ui/layout",
1502
- "name": "CenteredShell",
1503
- "props": [
1504
- {
1505
- "description": "Centred column content — page sections (identity hero, org picker, service grid, team list). Top-aligned and scrolls; NOT vertically centred like AuthShell's card.",
1372
+ "description": "Centred column content — page sections (identity hero, org picker, service grid, team list). Top-aligned and scrolls; NOT vertically centred like the auth CenteredShell's card.",
1506
1373
  "name": "children",
1507
1374
  "required": true,
1508
1375
  "type": "ReactNode"
@@ -1518,47 +1385,98 @@
1518
1385
  "type": "ReactNode"
1519
1386
  },
1520
1387
  {
1521
- "description": "Max-width of the centred column: sm ~32rem, md (default) ~46rem, lg ~64rem — all wider than AuthShell's 24rem auth card. A service retunes each tier via --centered-shell-width-*.",
1388
+ "description": "Max-width of the centred column: sm ~32rem, md (default) ~46rem, lg ~64rem — all wider than the auth CenteredShell's 24rem auth card. A service retunes each tier via --centered-shell-width-*.",
1522
1389
  "name": "width",
1523
1390
  "type": "\"sm\" | \"md\" | \"lg\""
1524
1391
  },
1525
1392
  {
1526
- "description": "Block alignment of the centred column inside the 100dvh shell. \"start\" (default) keeps the top-aligned flowing/scrolling page shape. Overflowing content still scrolls from the top, so a long localized message is never clipped.",
1393
+ "description": "AUTH: Block-axis placement of the auth column — ORTHOGONAL to `preset` the way `variant` is: the preset owns the page MEASURE (card width, inline gutters, section rhythm), `align` owns where that column sits vertically. Omit it to keep the preset's own choice: \"login\" and \"registration\" anchor so a requester/identity line that wraps to two lines cannot move the card (gh#237), every other preset centres. Pass \"center\" for a vertically centred column — the block-start inset collapses to the preset's block-end one on desktop AND mobile, so the padding is symmetric and justify-content has nothing to fight — or \"anchored\" for a top-anchored one. This is what replaces re-declaring a preset's offset tokens from consumer CSS, the page-local-vertical-offset anti-pattern presets exist to remove. HAZARD on a tall flow: a vertically centred tall card overflows ABOVE the scroll origin on a short viewport, putting its first field out of reach — which is why \"registration\" anchors by default; \"center\" is legal there but is the caller's judgement. PAGE: Block alignment of the centred column inside the 100dvh shell. \"start\" (default) keeps the top-aligned flowing/scrolling page shape. Overflowing content still scrolls from the top, so a long localized message is never clipped.",
1527
1394
  "name": "align",
1528
- "type": "\"start\" | \"center\""
1395
+ "type": "\"start\" | \"center\" (page) | \"anchored\" | \"center\" (auth)"
1529
1396
  },
1530
1397
  {
1531
1398
  "defaultValue": "\"default\"",
1532
- "description": "Whole-page shell contract. \"default\" emits no attribute and keeps the exact box. \"public-landing\" owns the PUBLIC landing geometry: ONE content measure shared by the header bar, the centred column and the footer (--centered-shell-landing-max-width, 67.5rem), the section rhythm between page sections, the flat elevation-free card chrome (--centered-shell-landing-card-shadow: none) and the hero h1 tier — plus the compact step at 40rem. A landing composition therefore needs no page-local CSS, no max-width wrapper and no descendant selector against shell internals.",
1399
+ "description": "Whole-page shell contract. \"default\" emits no attribute and keeps the exact box. \"public-landing\" owns the PUBLIC landing geometry: ONE content measure shared by the header bar, the centred column and the footer (--centered-shell-landing-max-width, 67.5rem), the section rhythm between page sections, the flat elevation-free card chrome (--centered-shell-landing-card-shadow: none) and the hero h1 tier — plus the compact step at 40rem. A landing composition therefore needs no page-local CSS, no max-width wrapper and no descendant selector against shell internals. AUTH: Named flow GEOMETRY — the package-owned layout contract for a canonical hosted-identity flow. \"device-authorization\" = a 380px card at 1440/1024 with a 5px inline page gutter at 390, AND the code field itself: the preset hands --otp-slot-{inline,block}-size the canonical 27.5x52 device-grant slot, so two 4-slot grouped boxes measure 112x54 instead of the 146x38 the square --control-height tier produced. \"registration\" = the 360px sign-up measure with a 15px inline gutter at 390 (the same page rhythm as \"login\", so sign-in to sign-up never jumps on a phone). START-aligned like login: a sign-up card is the tallest surface in the set (name/email/password/confirm/strength/consent/submit/providers) and a vertically centred tall card overflows ABOVE the scroll origin on a short viewport, putting its first field out of reach.",
1533
1400
  "name": "preset",
1534
- "type": "\"default\" | \"public-landing\""
1401
+ "type": "\"default\" | \"public-landing\" (page) | \"login\" | \"registration\" | \"device-authorization\" | \"context-selection\" | \"account-recovery\" (auth)"
1402
+ },
1403
+ {
1404
+ "description": "the auth shape only. Brand bar slot pinned to the top (e.g. a <Logo> / product mark).",
1405
+ "name": "brand",
1406
+ "type": "ReactNode"
1407
+ },
1408
+ {
1409
+ "description": "the auth shape only. Page-level controls pinned to the TOP-RIGHT of the same banner row as `brand` — a locale <Select>/<AppSettingPicker>, a theme <ToggleGroup>, a \"need help?\" link. They belong to the PAGE, not the auth form, so they sit in the bar, not in the card. The banner renders as soon as `brand` OR `actions` is present.",
1410
+ "name": "actions",
1411
+ "type": "ReactNode"
1412
+ },
1413
+ {
1414
+ "defaultValue": "\"default\"",
1415
+ "description": "the auth shape only. Inline MEASURE of the shell content slot. \"default\" is the single auth card (24rem, or 22.5rem under variant=\"canonical\"). \"wide\" opens the slot to --auth-shell-wide-card-max-width (64rem) for a SPLIT login: a brand/marketing panel beside the auth card, laid out with <ResponsiveGrid columns={{ sm: 1, lg: 2 }}>. The wide slot centres with auto margins, so a tall two-column layout starts at the top instead of overflowing above the scroll origin. Ignored under a `preset` — a preset already owns its flow geometry.",
1416
+ "name": "measure",
1417
+ "type": "\"default\" | \"wide\""
1418
+ },
1419
+ {
1420
+ "description": "the auth shape only. Vertical density scoped to auth-card descendants. Defaults to \"compact\" under variant=\"canonical\" and \"comfortable\" otherwise.",
1421
+ "name": "density",
1422
+ "type": "\"comfortable\" | \"compact\""
1535
1423
  }
1536
1424
  ],
1537
1425
  "related": [
1538
1426
  "AppShell — the shell for authenticated app pages WITH a sidebar nav rail. CenteredShell is its no-sidebar sibling (same padded topbar chrome, a centred column instead of a full-bleed main).",
1539
- "AuthShell — the UNAUTHENTICATED root shell (login/mfa/reset): a ~24rem card centred vertically, with its own banner `actions` slot and a `measure=\"wide\"` split-login measure. CenteredShell is the AUTHENTICATED centred-page counterpart. Never nest the two.",
1540
1427
  "Topbar — compose it into `topbar`; CenteredShell supplies the padded chrome the bare Topbar lacks.",
1541
- "PageContainer — for a titled section INSIDE the column; or compose <Card>/<ResponsiveGrid> sections directly."
1428
+ "PageContainer — for a titled section INSIDE the column; or compose <Card>/<ResponsiveGrid> sections directly.",
1429
+ "Reveal — wrap the auth <Card> in <Reveal> for the entrance animation; the auth CenteredShell delegates motion (and prefers-reduced-motion handling) to it rather than baking an animation in.",
1430
+ "Card — the canonical container for the auth form; place the form inside <CardContent>. the auth CenteredShell centres and width-constrains it.",
1431
+ "EmptyState — with `tone=\"success\"` for a confirmation card inside the shell (e.g. device approved).",
1432
+ "AuthIdentity / AuthFooter / AuthStack / AuthDivider — the auth composites that fill the shell: the GoDX identity mark + heading + requesting-client line, the mono legal footer (its `locale` slot takes an <AppSettingPicker kind=\"locale\" appearance=\"labeled\" compact />), the 12px section rhythm, and the labelled \"or\" rule.",
1433
+ "ListRow — compose the organisation choice list for `preset=\"context-selection\"` as <Card><CardContent flush><Flex as=\"ul\" marker=\"none\"> of <ListRow as=\"li\">: shared row dividers, no per-row card outline. There is no separate OrganizationChoiceList component — that is a composition pattern (rule #46), not a framework component."
1542
1434
  ],
1543
1435
  "rules": [
1544
1436
  23
1545
1437
  ],
1546
- "storyPath": "layout/CenteredShell.stories.tsx",
1547
- "tagline": "Authenticated, no-sidebar, centred-column page shell (hosted-ID My Page / account / standalone settings) — padded topbar with real actions + a width-tiered centred column, zero custom CSS.",
1438
+ "storyPath": "layout/centered-shell.tsx",
1439
+ "tagline": "The centred root shell, two shapes: `variant=\"page\"` (default) — authenticated, no-sidebar, centred-column page (hosted-ID My Page / account / standalone settings) with a padded topbar; `variant=\"auth\"` / `\"auth-canonical\"` — the unauthenticated login/mfa/reset shell (brand bar + vertically centred auth card + footer). Zero custom CSS either way.",
1548
1440
  "usage": [
1549
- "DO use CenteredShell for an AUTHENTICATED page that has a topbar with actions but NO sidebar — the hosted-ID 'My Page', an account / self-service surface, a standalone settings page. It is the third shell: AppShell (needs a sidebar) · AuthShell (unauthenticated narrow card) · CenteredShell (authenticated centred column).",
1441
+ "DO use CenteredShell for an AUTHENTICATED page that has a topbar with actions but NO sidebar — the hosted-ID 'My Page', an account / self-service surface, a standalone settings page. It is the third shell: AppShell (needs a sidebar) · the auth CenteredShell (unauthenticated narrow card) · CenteredShell (authenticated centred column).",
1550
1442
  "DO put a <Topbar start={<brand/>} end={<actions/>}/> in `topbar` — CenteredShell wraps it in the padded `.app-topbar` chrome, so you get inline padding + border + backdrop with zero custom CSS. Do NOT hand-roll a bar with raw `padding-inline` — the bare Topbar primitive ships no inset (the .ui-topbar zero-padding footgun) and content sits flush to the edge.",
1551
- "DO pick `width` by content: `sm` (~32rem) for a single settings form, `md` (default, ~46rem) for a My Page of stacked sections, `lg` (~64rem) for a service-launcher grid. All are wider than AuthShell's 24rem card.",
1552
- "DO wrap an individual section in <Reveal> for entrance motion — CenteredShell stays layout-only and delegates prefers-reduced-motion handling to Reveal (same as AuthShell).",
1553
- "DO NOT use AuthShell for an authenticated page (it is the UNAUTHENTICATED root and imposes auth-card geometry), and DO NOT force AppShell with an empty sidebar — use CenteredShell. Conversely, do NOT reach for CenteredShell to build a login page: AuthShell has its own `actions` slot for the locale/theme controls and a `measure=\"wide\"` for the split brand-panel login. Never nest it inside AppShell/AuthShell (or vice-versa); it is a ROOT shell.",
1554
- "DO use `align=\"center\"` (+ `width=\"sm\"`) for a SYSTEM-level standalone page — a 500/503 error surface, a maintenance notice. It centres the column in the 100dvh shell at 1440/1024/390 with no consumer `min-h-dvh` / flex CSS and no className; the knob is --centered-shell-column-offset-block. For an actual 403/404/500/503 page do NOT wire this by hand — use `ErrorSurface`, which renders this shell itself in `mode=\"system\"`."
1555
- ],
1556
- "useCases": [
1557
- "Hosted GoDX ID 'My Page': <CenteredShell topbar={<Topbar start={<Brand/>} end={<><AppSettingPicker kind=\"locale\"/><UserMenu/></>}/>} footer={<Footer/>} width=\"md\"> with an identity hero, an org picker, a service-launcher grid and a team list.",
1443
+ "DO pick `width` by content: `sm` (~32rem) for a single settings form, `md` (default, ~46rem) for a My Page of stacked sections, `lg` (~64rem) for a service-launcher grid. All are wider than the auth CenteredShell's 24rem card.",
1444
+ "DO wrap an individual section in <Reveal> for entrance motion — CenteredShell stays layout-only and delegates prefers-reduced-motion handling to Reveal (same as the auth CenteredShell).",
1445
+ "DO NOT use the auth shape for an authenticated page (it is the UNAUTHENTICATED root and imposes auth-card geometry), and DO NOT force AppShell with an empty sidebar — use CenteredShell. Conversely, do NOT reach for CenteredShell to build a login page: the auth CenteredShell has its own `actions` slot for the locale/theme controls and a `measure=\"wide\"` for the split brand-panel login. Never nest it inside AppShell/auth CenteredShell (or vice-versa); it is a ROOT shell.",
1446
+ "DO use `align=\"center\"` (+ `width=\"sm\"`) for a SYSTEM-level standalone page — a 500/503 error surface, a maintenance notice. It centres the column in the 100dvh shell at 1440/1024/390 with no consumer `min-h-dvh` / flex CSS and no className; the knob is --centered-shell-column-offset-block. For an actual 403/404/500/503 page do NOT wire this by hand — use `ErrorSurface`, which renders this shell itself in `mode=\"system\"`.",
1447
+ "AUTH SHAPE: DO pass a single <Card> (with the form inside <CardContent>) as `children` — the auth CenteredShell centres it and constrains its width via `--auth-shell-card-max-width`; do NOT hand-roll a `.auth-shell-main` / `.ui-auth-scope` wrapper.",
1448
+ "AUTH SHAPE: DO use `variant=\"auth-canonical\" preset=\"login\"` for SCR-001 and pass <AuthIdentity>, <Card>, <AuthFooter> as direct children in that order (an anchor may wrap AuthIdentity). The preset owns the identity slot, card anchor, 20px section rhythm and compact card block inset for standalone and real requester states. Do not wrap the three sections in a consumer Flex/Stack or the semantic grid cannot anchor them.",
1449
+ "AUTH SHAPE: DO select a `preset` instead of overriding geometry: `preset=\"login\"` for the stable SCR-001 identity/card/footer anchor, `preset=\"registration\"` for the sign-up form and its pending-email state, `preset=\"device-authorization\"` for the 380px OAuth device-grant measure, `preset=\"context-selection\"` for the 25rem organisation/context picker, `preset=\"account-recovery\"` for the 432px SCR-008 recovery/MFA panel. Page-local width/inset/vertical-offset variables are the exact anti-pattern these presets replace.",
1450
+ "AUTH SHAPE: DO build the SOCIAL / PROVIDER ACTION row as a COMPOSITION — there is NO SocialLinks component: `<AuthDivider label=\"or\" />` followed by a `Flex direction=\"col\" gap=\"sm\"` of real `Button variant=\"outline\"` with the provider glyph as an aria-hidden icon. The package deliberately does not own it: which providers a product offers, in what order, and what consent they imply are product decisions, and a component would have to invent them. `disabled` / `loading` are the Button's own props — do not add a provider-specific API.",
1451
+ "AUTH SHAPE: DO build the ORGANIZATION CHOICE LIST as a COMPOSITION — there is NO OrganizationChoiceList component: `Card` > `CardContent flush` > a `<Flex as=\"ul\" marker=\"none\">` of `ListRow as=\"li\"` (leading Avatar, title, description, trailing Button). `CardContent flush` is what gives shared row dividers instead of a card outline per row. Its states are existing exports, never bespoke markup: Skeleton rows for loading, `EmptyState` for no invitations, `Alert tone=\"destructive\"` for a failed fetch and `Alert tone=\"warning\"` for permission-denied. See the auth-shell-context and auth-shell-registration frames.",
1452
+ "AUTH SHAPE: DO build the password-recovery and sign-in MFA CHALLENGE panels as a COMPOSITION inside `preset=\"account-recovery\"` — there is NO PasswordRecoveryPanel and NO MfaChallengePanel component: Card > CardHeader(CardTitle + CardDescription, INSIDE the bordered surface) > CardContent > AuthStack[ Alert notice · FormField fields · Button fullWidth · Flex justify=\"between\" wrap fallback row ]. Do NOT put AuthIdentity above the panel there (it always renders the hosted mark), and NEVER reuse TwoFactorSetup — that is the ENROLLMENT dialog, not a sign-in challenge. See the `auth-recovery-panels` pattern.",
1453
+ "AUTH SHAPE: DO combine `variant` and `preset` — they are orthogonal: `variant` owns control density + heading size, `preset` owns the page measure. `variant=\"auth-canonical\" preset=\"device-authorization\"` is the canonical device screen.",
1454
+ "AUTH SHAPE: DO let `preset=\"context-selection\"` space the auth column: it turns the card slot into a flex column with a tokenized `--auth-shell-card-stack-gap`, so an intro (<AuthIdentity>), the choice <Card> and a trailing \"remember\" row pass as three siblings with NO page-local spacing.",
1455
+ "AUTH SHAPE: DO NOT add a page-local width, inset or colour to hit an artboard — if a measure is missing, it is a library gap: a new preset or token, never consumer CSS (rules #44/#45).",
1456
+ "AUTH SHAPE: DO state the block placement with `align` when a product wants the opposite of its preset's default — align=\"center\" on preset=\"login\" for a vertically centred SCR-001, align=\"anchored\" to top-anchor a preset that centres. It collapses the preset's block-start inset to its block-end one on desktop AND mobile, so justify-content has nothing to fight. Re-declaring --auth-shell-login-flow-offset-block (or any preset offset token) from a consumer stylesheet is the anti-pattern it replaces.",
1457
+ "AUTH SHAPE: DO put the product/brand mark in `brand` (a <Logo> or an <Avatar>) — it renders as the top banner landmark; omit it and the banner is not rendered.",
1458
+ "AUTH SHAPE: DO put page-level controls in `actions` — the locale picker, the theme <ToggleGroup>, a \"need help?\" link. They land at the banner's inline end at a tokenized gap (`--auth-shell-bar-gap`), and the banner appears even with no `brand`. Do NOT hand-roll a top-right row with `ms-auto` on the `brand` content, and do NOT reach for CenteredShell just to get a topbar with actions: CenteredShell is the AUTHENTICATED shell.",
1459
+ "AUTH SHAPE: DO use `measure=\"wide\"` for the SPLIT login — a brand/marketing panel beside the auth card. The content slot opens to 64rem (`--auth-shell-wide-card-max-width`) and centres with auto margins, so the tall two-column layout starts at the top instead of overflowing above the scroll origin; lay the two halves out with <ResponsiveGrid columns={{ sm: 1, lg: 2 }}> and hide the panel below `lg`. It is IGNORED under a `preset` (a preset owns its flow geometry), so pick one or the other.",
1460
+ "AUTH SHAPE: DO use `footer` for compliance/legal/support links or a locale switch — it renders as the contentinfo landmark below the card.",
1461
+ "AUTH SHAPE: DO wrap the card in <Reveal> for the entrance animation (`<CenteredShell variant=\"auth\"><Reveal><Card/></Reveal></CenteredShell>`) — Reveal honours prefers-reduced-motion; the auth CenteredShell itself stays layout-only.",
1462
+ "AUTH SHAPE: DO NOT re-scope control height or heading size in the app — the auth CenteredShell already sets the comfortable control tier (44px, WCAG touch floor) and the larger auth heading via `--auth-shell-control-height` / `--auth-shell-heading-size`; a service retunes those tokens, not a bespoke class.",
1463
+ "AUTH SHAPE: DO tune the COMPACT auth card through its three independent knobs, never a consumer selector on `[data-slot=\"card-content\"]`: `--auth-shell-compact-card-inset` = the inline column, `--auth-shell-card-padding-block-compact` = the card's block (top/bottom) padding — it is wired to the card's `--card-space-shell-y` and really moves CardContent's block edges — and `--auth-shell-card-body-gap-compact` = the header↔body gap. Each defaults to today's canonical rhythm, so overriding one moves ONLY that axis.",
1464
+ "AUTH SHAPE: DO NOT nest the auth CenteredShell inside AppShell (or vice-versa) — the auth CenteredShell is the ROOT shell for unauthenticated pages (login/mfa/passkey/device/reset); AppShell is for the authenticated app."
1465
+ ],
1466
+ "useCases": [
1467
+ "Hosted GoDX ID 'My Page': <CenteredShell topbar={<Topbar start={<Logo />} end={<><AppSettingPicker kind=\"locale\"/>{yourUserMenu}</>}/>} footer={<AuthFooter … />} width=\"md\"> with an identity hero, an org picker, a service-launcher grid and a team list.",
1558
1468
  "Account / self-service settings surface (no admin sidebar): stacked <Card> sections (profile, security, sessions) in a centred `md` column under a topbar with a user menu.",
1559
1469
  "Standalone single settings page: `width=\"sm\"` with one <Card> of <Field>s and a save action.",
1560
1470
  "System error / maintenance page (500 · 503): `<CenteredShell align=\"center\" width=\"sm\">` around the canonical error body (status code <Text mono tabular> + <EmptyState icon tone title description action> + optional request-ID / maintenance line). See the `error-pages` pattern.",
1561
- "Service launcher / app picker after sign-in: `width=\"lg\"` with a <ResponsiveGrid> of app cards under the brand topbar."
1471
+ "Service launcher / app picker after sign-in: `width=\"lg\"` with a <ResponsiveGrid> of app cards under the brand topbar.",
1472
+ "AUTH SHAPE: Canonical Login (SCR-001): <CenteredShell variant=\"auth-canonical\" preset=\"login\"> with direct AuthIdentity · Card · AuthFooter children; the card remains anchored when requester is absent, one line or wraps to two lines.",
1473
+ "AUTH SHAPE: Login page: <CenteredShell variant=\"auth\" brand={<Logo/>} footer={<AuthFooter/>}> wrapping a <Card> with the email/password form and a primary <Button fullWidth>.",
1474
+ "AUTH SHAPE: MFA / passkey / device-authorisation step: same shell, a <Card> with the one-time-code <InputOTP> or a passkey prompt.",
1475
+ "AUTH SHAPE: OAuth device-grant screen (SCR-004): <CenteredShell variant=\"auth-canonical\" preset=\"device-authorization\"> — a 380px card at 1440/1024 and a 5px inline gutter at 390, with zero page-local CSS.",
1476
+ "AUTH SHAPE: Organisation / context selection (/select-context): <CenteredShell variant=\"auth-canonical\" preset=\"context-selection\" brand={<Logo mark=\"godx\" />}> with an <AuthIdentity> intro, a <Card><CardContent flush> list of <ListRow as=\"li\"> organisations, and a trailing \"remember this choice\" <Checkbox> — the preset spaces the three sections.",
1477
+ "AUTH SHAPE: Split product login: <CenteredShell variant=\"auth\" measure=\"wide\" brand={<Logo/>} actions={<><Select locale/><ToggleGroup theme/></>}> around a <ResponsiveGrid columns={{ sm: 1, lg: 2 }}> whose first cell is the brand/value panel (hidden below lg) and whose second is the auth <Card>. See docs/showcase/case4-login.",
1478
+ "AUTH SHAPE: Password reset / forgot-password / accept-invite: the centred single-card flow with a brand bar and a legal footer.",
1479
+ "AUTH SHAPE: SSO landing / success confirmation: pair with an <EmptyState tone=\"success\"> inside the card for an approved-device confirmation."
1562
1480
  ]
1563
1481
  },
1564
1482
  {
@@ -1609,7 +1527,7 @@
1609
1527
  "related": [
1610
1528
  "AppShell — the authenticated shell WITH a sidebar rail and a mobile drawer at the 900px step. Use it for an admin console that happens to be viewed on a phone; use MobileShell when the phone IS the product.",
1611
1529
  "CenteredShell — the authenticated no-sidebar shell whose main scrolls the PAGE. MobileShell is its handheld counterpart: same 'no rail' shape, opposite scroll contract.",
1612
- "AuthShell — the unauthenticated root. A login screen inside a handheld app still belongs to AuthShell, not MobileShell.",
1530
+ "CenteredShell (auth shape) — the unauthenticated root. A login screen inside a handheld app still belongs to the auth CenteredShell, not MobileShell.",
1613
1531
  "Sheet — `side=\"bottom\"` is the handheld modal: scanners, pickers and forms open from the bottom over MobileShell rather than navigating away."
1614
1532
  ],
1615
1533
  "rules": [
@@ -1617,15 +1535,15 @@
1617
1535
  24,
1618
1536
  45
1619
1537
  ],
1620
- "storyPath": "layout/MobileShell.stories.tsx",
1538
+ "storyPath": "layout/mobile-shell.tsx",
1621
1539
  "tagline": "Handheld app shell (gh#354) — status band · app bar · the ONE scroll region · sticky action bar · bottom tab bar, with device safe-area insets and a document that never scrolls.",
1622
1540
  "usage": [
1623
- "DO use MobileShell for a HANDHELD app screen — a warehouse/handy terminal, a driver app, a field-work PWA. It is the fourth root shell: AppShell (needs a sidebar) · AuthShell (unauthenticated card) · CenteredShell (authenticated scrolling document) · MobileShell (a phone app that does NOT scroll its document).",
1541
+ "DO use MobileShell for a HANDHELD app screen — a warehouse/handy terminal, a driver app, a field-work PWA. It is the fourth root shell: AppShell (needs a sidebar) · the auth CenteredShell (unauthenticated card) · CenteredShell (authenticated scrolling document) · MobileShell (a phone app that does NOT scroll its document).",
1624
1542
  "DO let `children` be the only thing that scrolls. Put the primary verb in `actions` and navigation in `tabBar` — both sit outside the scroll region, so neither needs `position: sticky`, a z-index, or bottom padding to clear the other.",
1625
1543
  "DO NOT compose one out of <Card> + `ui-card-inset*` + `overflow-y-auto` (what docs/showcase/case6 did before gh#354). That reproduces the look and neither behaviour that matters on a device: the document still scrolls, and nothing pads out of env(safe-area-inset-*), so the notch covers the app bar and the home indicator covers the primary button.",
1626
1544
  "DO swap the `header` node for a screen MODE (select mode, search mode) instead of stacking a second contextual strip below it — one bar to read, and the platform pattern on both iOS and Android.",
1627
1545
  "DO use `height=\"fill\"` ONLY when the shell is inside a bounded parent (a device-frame preview). In a real app leave it at \"viewport\": that is what keeps the document from scrolling.",
1628
- "DO NOT nest MobileShell inside AppShell / AuthShell / CenteredShell (or the reverse) — it is a ROOT shell. Retune the page gutter and the three band heights from the theme (--mobile-shell-inset-inline, --mobile-shell-header-bar-height, --mobile-shell-tab-bar-height, --mobile-shell-status-bar-height); never fork .ui-mobile-shell-* CSS."
1546
+ "DO NOT nest MobileShell inside AppShell / CenteredShell (or the reverse) — it is a ROOT shell. Retune the page gutter and the three band heights from the theme (--mobile-shell-inset-inline, --mobile-shell-header-bar-height, --mobile-shell-tab-bar-height, --mobile-shell-status-bar-height); never fork .ui-mobile-shell-* CSS."
1629
1547
  ],
1630
1548
  "useCases": [
1631
1549
  "Warehouse handheld (代理店ハンディ): a status band, an app bar with a select-mode text action, a scrolling item list, a dominant Scan button in `actions`, and a three-destination `tabBar` (inbound · packing · outbound). See the case6-agency-handy showcase.",
@@ -1725,7 +1643,7 @@
1725
1643
  23,
1726
1644
  31
1727
1645
  ],
1728
- "storyPath": "layout/Sidebar.preview.tsx",
1646
+ "storyPath": "layout/sidebar.tsx",
1729
1647
  "subParts": [
1730
1648
  "SidebarHeader",
1731
1649
  "SidebarItem",
@@ -1826,7 +1744,7 @@
1826
1744
  5,
1827
1745
  6
1828
1746
  ],
1829
- "storyPath": "layout/Topbar.stories.tsx",
1747
+ "storyPath": "layout/topbar.tsx",
1830
1748
  "tagline": "A PURE SLOT bar for the app shell — positions three clusters (start / center / end) and owns ONLY the bar layout. It bakes NO chrome: no product switcher, no search box, no notification bell, no language picker. The CONSUMER composes those from real primitives and drops them into a slot; whether a control is icon-only / labelled / bordered is that control's own config, never the shell's.",
1831
1749
  "usage": [
1832
1750
  "DO compose the bar yourself: a brand mark (an `Avatar`) + sidebar toggle in `start`, a search trigger in `center`, settings pickers + notifications + user menu in `end`. The shell only positions; it never decides WHICH controls exist.",
@@ -1910,7 +1828,7 @@
1910
1828
  5,
1911
1829
  6
1912
1830
  ],
1913
- "storyPath": "layout/TopbarItem.stories.tsx",
1831
+ "storyPath": "layout/topbar-item.tsx",
1914
1832
  "tagline": "ONE interactive cell of a Topbar slot — the account button, a settings or notifications trigger. Full bar height, the bar's own hover surface, and the focus mark hosted INSIDE the cell. Use it INSTEAD OF a Button in a Topbar slot: a Button there is a --control-height pill floating in a taller bar, with its own hover fill and a ring drawn around the pill.",
1915
1833
  "usage": [
1916
1834
  "DO put bar triggers in a Topbar slot as TopbarItem, not as Button — that is the difference between chrome and a control that landed in the chrome.",
@@ -1971,7 +1889,7 @@
1971
1889
  5,
1972
1890
  6
1973
1891
  ],
1974
- "storyPath": "layout/NavList.stories.tsx",
1892
+ "storyPath": "layout/nav-list.tsx",
1975
1893
  "tagline": "Vertical route navigation for INSIDE a page — the shape a settings nav takes. Renders the same `.sb-nav-item` rows as the Sidebar rail (icon column, label, badge, active tokens, `aria-current=\"page\"`) in a `<nav>` landmark, without the AppShell grid the rail depends on.",
1976
1894
  "usage": [
1977
1895
  "DO use NavList for a settings / account / preferences sub-navigation beside the pane it drives — typically as MasterDetail's `master`.",
@@ -2064,7 +1982,7 @@
2064
1982
  24,
2065
1983
  40
2066
1984
  ],
2067
- "storyPath": "layout/MasterDetail.stories.tsx",
1985
+ "storyPath": "layout/master-detail.tsx",
2068
1986
  "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.",
2069
1987
  "usage": [
2070
1988
  "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.",
@@ -2086,9 +2004,9 @@
2086
2004
  },
2087
2005
  {
2088
2006
  "docPath": "layout/draggable-panel.tsx",
2089
- "example": "import { useState } from \"react\";\nimport { DraggablePanel, Flex } from \"@godxjp/ui/layout\";\nimport { Text } from \"@godxjp/ui/general\";\n\nfunction Assistant() {\n const [position, setPosition] = useState({ x: 0, y: 0 });\n return (\n <DraggablePanel\n title=\"アシスタント\"\n placement=\"bottom-end\"\n position={position}\n onPositionChange={setPosition}\n >\n <Flex direction=\"col\" gap=\"sm\">\n <Text size=\"sm\">この請求書の消費税区分について質問できます。</Text>\n </Flex>\n </DraggablePanel>\n );\n}",
2007
+ "example": "import { useState } from \"react\";\nimport { Flex } from \"@godxjp/ui/layout\";\nimport { DraggablePanel } from \"@godxjp/ui/lab\";\nimport { Text } from \"@godxjp/ui/general\";\n\nfunction Assistant() {\n const [position, setPosition] = useState({ x: 0, y: 0 });\n return (\n <DraggablePanel\n title=\"アシスタント\"\n placement=\"bottom-end\"\n position={position}\n onPositionChange={setPosition}\n >\n <Flex direction=\"col\" gap=\"sm\">\n <Text size=\"sm\">この請求書の消費税区分について質問できます。</Text>\n </Flex>\n </DraggablePanel>\n );\n}",
2090
2008
  "group": "layout",
2091
- "importPath": "@godxjp/ui/layout",
2009
+ "importPath": "@godxjp/ui/lab",
2092
2010
  "name": "DraggablePanel",
2093
2011
  "props": [
2094
2012
  {
@@ -2173,10 +2091,11 @@
2173
2091
  23,
2174
2092
  44
2175
2093
  ],
2176
- "storyPath": "layout/DraggablePanel.stories.tsx",
2094
+ "storyPath": "layout/draggable-panel.tsx",
2177
2095
  "tagline": "A floating surface the person using it can MOVE — drag it by the title-bar handle, or focus the handle and nudge it with the arrow keys. Bounded to the viewport, position reported through onPositionChange and never stored by the library.",
2096
+ "tier": "lab",
2178
2097
  "usage": [
2179
- "DO import from `@godxjp/ui/layout`: `import { DraggablePanel } from \"@godxjp/ui/layout\";`",
2098
+ "DO import from `@godxjp/ui/lab` (the opt-in lab subpath): `import { DraggablePanel } from \"@godxjp/ui/lab\";`",
2180
2099
  "DO treat the position as yours. The panel reports it and stores nothing, so persisting it per user, per workspace or not at all is a decision you make.",
2181
2100
  "DO keep the body text short enough to read at 22rem, or let it scroll — the body is its own scroll region and the panel is capped at 70vh.",
2182
2101
  "DON'T hand-roll this with a fixed-position div and pointer maths: that is page-local CSS ui-audit blocks, and every consumer that floats anything would write it again.",
@@ -2241,7 +2160,7 @@
2241
2160
  "rules": [
2242
2161
  24
2243
2162
  ],
2244
- "storyPath": "layout/SplitPane.stories.tsx",
2163
+ "storyPath": "layout/split-pane.tsx",
2245
2164
  "tagline": "Two-column layout with a main content area and a fixed-width aside panel.",
2246
2165
  "usage": [
2247
2166
  "DO: pass all right-panel content via the `aside` prop — it renders inside a semantic `<aside>` element at a fixed rem width (sm=20rem, md=22rem). The `children` prop fills the main `1fr` column. Both accept any ReactNode.",
@@ -2371,10 +2290,10 @@
2371
2290
  23,
2372
2291
  24
2373
2292
  ],
2374
- "storyPath": "layout/ErrorSurface.stories.tsx",
2293
+ "storyPath": "layout/error-surface/index.tsx",
2375
2294
  "tagline": "Package-owned semantic exception surface for 400 / 403 / 404 / 500 / 503. `mode` is the SHELL CONTRACT: \"application\" renders the body you put inside the AppShell the route already provides (chrome PRESERVED, never reconstructed); \"system\" owns the whole page via CenteredShell align=\"center\" (package-owned 1440/1024/390 geometry). Exactly one recovery action, plus semantic request-id / permission / organization / maintenance metadata slots.",
2376
2295
  "usage": [
2377
- "DO use ErrorSurface for ANY 400 / 403 / 404 / 500 / 503 page. It is a real import from @godxjp/ui/layout — never hand-compose an error page from AuthShell + a generic Card, and never add a consumer-local `.canonical-auth-card`-style class (that workaround IS the regression).",
2296
+ "DO use ErrorSurface for ANY 400 / 403 / 404 / 500 / 503 page. It is a real import from @godxjp/ui/layout — never hand-compose an error page from the auth CenteredShell + a generic Card, and never add a consumer-local `.canonical-auth-card`-style class (that workaround IS the regression).",
2378
2297
  "DO put mode=\"application\" INSIDE the shell the route already renders: <AppShell …><PageContainer …><ErrorSurface mode=\"application\" …/></PageContainer></AppShell>. The surface returns only its own block on purpose, so the sidebar/topbar/breadcrumb survive and the user can navigate away.",
2379
2298
  "DO use mode=\"system\" for 500/503 and pass NOTHING for geometry — the surface renders CenteredShell align=\"center\" itself. A className=\"min-h-dvh flex items-center\" is always wrong.",
2380
2299
  "DO pass ONE action. A second CTA is dropped with a development error; put 'contact support' in `description`.",
@@ -2382,7 +2301,7 @@
2382
2301
  "DO let `status` pick the icon and tone. Override them only for a truer product glyph — never to recolour a status into a different severity.",
2383
2302
  "DO NOT retune the geometry with a className: --error-surface-max-width, --error-surface-gap, --error-surface-padding-block(-compact), --error-surface-meta-* and --error-surface-progress-max-width are the knobs. The metadata divider defaults to `none` (rule #44); opt in with --error-surface-meta-border.",
2384
2303
  "DO NOT write the request id, the missing permission or the maintenance window into `description` as prose — each is a semantic <dt>/<dd> slot, and prose loses the label↔value relationship for a screen reader.",
2385
- "DO NOT use AuthShell for an error page: it is the UNAUTHENTICATED root and imposes auth-card geometry."
2304
+ "DO NOT use the auth shape for an error page: it is the UNAUTHENTICATED root and imposes auth-card geometry."
2386
2305
  ],
2387
2306
  "useCases": [
2388
2307
  "Inertia/Laravel exception page (SCR-006): one Error.tsx receives `status` from Handler::render() and renders <ErrorSurface mode={status >= 500 ? \"system\" : \"application\"} status={status} … />, with Error.layout keeping the authenticated shell for 403/404 only.",
@@ -2392,9 +2311,9 @@
2392
2311
  ]
2393
2312
  },
2394
2313
  {
2395
- "example": "import { LegalDocumentShell, CenteredShell, Flex, Topbar } from \"@godxjp/ui/layout\";\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}",
2314
+ "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}",
2396
2315
  "group": "layout",
2397
- "importPath": "@godxjp/ui/layout",
2316
+ "importPath": "@godxjp/ui/lab",
2398
2317
  "name": "LegalDocumentShell",
2399
2318
  "props": [
2400
2319
  {
@@ -2475,8 +2394,9 @@
2475
2394
  23,
2476
2395
  24
2477
2396
  ],
2478
- "storyPath": "layout/LegalDocumentShell.stories.tsx",
2397
+ "storyPath": "layout/legal-document-shell/index.tsx",
2479
2398
  "tagline": "Long-form legal/policy document surface (terms, privacy, DPA, cookie policy, SLA) — semantic article/nav/section landmarks, real anchors, a sticky table-of-contents rail, scroll-spy aria-current, hash deep links with a token-driven scroll offset and focus handoff. All legal text stays consumer-owned.",
2399
+ "tier": "lab",
2480
2400
  "usage": [
2481
2401
  "DO use LegalDocumentShell for ANY long-form legal/policy document — terms of service, privacy policy, DPA, cookie policy, SLA, EULA, security policy. It is the only primitive that owns the document behaviour: scroll-spy active section, hash deep links, scroll offset, focus handoff and reduced-motion scrolling.",
2482
2402
  "DO pass `effectiveDate` as an ISO 8601 string ('2026-04-01'). The shell formats it with Intl.DateTimeFormat in the active locale and keeps the ISO value in <time dateTime>. A pre-formatted string ('April 1, 2026') is a bug — it will not localize.",
@@ -2487,7 +2407,7 @@
2487
2407
  "DO NOT expect a viewport media query: the shell owns its query container, so the one-column ⇄ two-column split (56rem) is decided by the SHELL's own width. Below it the contents are a STATIC compact block (never pinned on a phone); above it they are a sticky rail."
2488
2408
  ],
2489
2409
  "useCases": [
2490
- "Hosted legal terms/privacy screen (SCR-005): <CenteredShell width=\"lg\" topbar={<Topbar …/>}><LegalDocumentShell title=\"利用規約\" version=\"2.4\" effectiveDate=\"2026-04-01\" contentsLabel=\"目次\" sections={sections} activeSection={active} onActiveSectionChange={setActive} documentNavigation={<DocumentSwitcher/>} footerAction={<Button>同意する</Button>} /></CenteredShell>",
2410
+ "Hosted legal terms/privacy screen (SCR-005): <CenteredShell width=\"lg\" topbar={<Topbar …/>}><LegalDocumentShell title=\"利用規約\" version=\"2.4\" effectiveDate=\"2026-04-01\" contentsLabel=\"目次\" sections={sections} activeSection={active} onActiveSectionChange={setActive} documentNavigation={yourDocumentSwitcher} footerAction={<Button>同意する</Button>} /></CenteredShell>",
2491
2411
  "In-app policy viewer inside a Dialog/Sheet during onboarding: the same `sections` with `footerAction` carrying the accept button; the shell stays single-column because its container is narrow.",
2492
2412
  "Deep-linkable DPA / sub-processor document: link to /legal/dpa#data-retention — the shell selects, scrolls to (with the scroll offset) and focuses that section on arrival.",
2493
2413
  "Multi-document legal set (Terms · Privacy · Cookies): render the switcher in `documentNavigation` so it sits above the contents in the sticky rail."
@@ -2528,7 +2448,7 @@
2528
2448
  "PrefetchLink — if ancestor breadcrumb segments should prefetch their destination query on hover/focus, consider pairing the `to` values with `PrefetchLink` in a custom breadcrumb or pre-warming the cache on mount; Breadcrumb's internal links are plain react-router-dom `<Link>` with no prefetch behaviour."
2529
2449
  ],
2530
2450
  "rules": [],
2531
- "storyPath": "layout/Breadcrumb.stories.tsx",
2451
+ "storyPath": "navigation/breadcrumb.tsx",
2532
2452
  "tagline": "Standalone breadcrumb nav rendering an ordered trail of page segments.",
2533
2453
  "usage": [
2534
2454
  "DO import from `@godxjp/ui/layout` (not from a navigation or general sub-path) and pass a single `items` prop — an ordered array of `{ label, to? }` objects. No children, no sub-components, no render-prop API.",
@@ -2549,9 +2469,9 @@
2549
2469
  },
2550
2470
  {
2551
2471
  "docPath": "general/float-button.tsx",
2552
- "example": "import { FloatButton } from \"@godxjp/ui/general\";\nimport { MessageCircle, Share2 } from \"lucide-react\";\n\n<FloatButton.Group trigger=\"click\" type=\"primary\" icon={<MessageCircle />} aria-label=\"Trợ lý\">\n <FloatButton tooltip=\"Hỏi trợ lý\" badge={{ count: 3 }} />\n <FloatButton tooltip=\"Chia sẻ\" icon={<Share2 />} />\n <FloatButton.BackTop />\n</FloatButton.Group>",
2472
+ "example": "import { FloatButton } from \"@godxjp/ui/lab\";\nimport { MessageCircle, Share2 } from \"lucide-react\";\n\n<FloatButton.Group trigger=\"click\" type=\"primary\" icon={<MessageCircle />} aria-label=\"Trợ lý\">\n <FloatButton tooltip=\"Hỏi trợ lý\" badge={{ count: 3 }} />\n <FloatButton tooltip=\"Chia sẻ\" icon={<Share2 />} />\n <FloatButton.BackTop />\n</FloatButton.Group>",
2553
2473
  "group": "general",
2554
- "importPath": "@godxjp/ui/general",
2474
+ "importPath": "@godxjp/ui/lab",
2555
2475
  "name": "FloatButton",
2556
2476
  "props": [
2557
2477
  {
@@ -2616,14 +2536,15 @@
2616
2536
  "related": [
2617
2537
  "Button — the same control IN the layout flow. A float button is a Button that gave up its place in the page and took a corner instead.",
2618
2538
  "Popover — anchors a panel to a trigger; it does not place the trigger.",
2619
- "Banner — a full-bleed strip, not a corner mark.",
2539
+ "Alert — `variant=\"banner\"` is a full-bleed strip, not a corner mark.",
2620
2540
  "Toolbar / PageContainer extra — where an action belongs when it IS part of this page's content."
2621
2541
  ],
2622
2542
  "rules": [],
2623
- "storyPath": "general/FloatButton.stories.tsx",
2543
+ "storyPath": "general/float-button.tsx",
2624
2544
  "tagline": "Ant Design's corner action, ported whole — a control pinned to the viewport above the page, with FloatButton.Group (a stack, or a menu behind one trigger) and FloatButton.BackTop. The corner insets are tokens, so a service moves the mark without a media query.",
2545
+ "tier": "lab",
2625
2546
  "usage": [
2626
- "DO import from `@godxjp/ui/general`: `import { FloatButton } from \"@godxjp/ui/general\";`",
2547
+ "DO import from `@godxjp/ui/lab` (the opt-in lab subpath): `import { FloatButton } from \"@godxjp/ui/lab\";`",
2627
2548
  "DO give every float button a name — `tooltip` as a plain string is enough, since it becomes the aria-label too.",
2628
2549
  "DO move the mark with the `--float-button-offset-block-end` / `--float-button-offset-inline-end` tokens when a sticky action bar or a mobile tab bar is already in that corner.",
2629
2550
  "DO pass `target={() => element}` to FloatButton.BackTop inside a shell that owns its own scroll region, so it watches that region rather than the document.",
@@ -2688,7 +2609,7 @@
2688
2609
  },
2689
2610
  {
2690
2611
  "defaultValue": "false",
2691
- "description": "Render as Radix Slot — merge props onto the child (<a>/<Link>).",
2612
+ "description": "Render as the kit's Slot — merge props onto the child (<a>/<Link>).",
2692
2613
  "name": "asChild",
2693
2614
  "type": "boolean"
2694
2615
  },
@@ -2746,11 +2667,11 @@
2746
2667
  "rules": [
2747
2668
  23
2748
2669
  ],
2749
- "storyPath": "general/Button.stories.tsx",
2670
+ "storyPath": "general/button/index.tsx",
2750
2671
  "subParts": [
2751
2672
  "ButtonRefetch"
2752
2673
  ],
2753
- "tagline": "Core button with variant + size presets, built on cva and Radix Slot (asChild).",
2674
+ "tagline": "Core button with variant + size presets, built on cva with the kit's own Slot (asChild).",
2754
2675
  "usage": [
2755
2676
  "DO pick the right variant for intent: `default` (primary CTA, one per section), `destructive` (irreversible actions like delete/revoke), `outline` (secondary actions alongside a primary), `secondary` (less prominent actions), `ghost` (toolbar icon-only actions), `link` (inline text-style navigation without an underline by default).",
2756
2677
  "DO use icon-only sizes (`icon`, `icon-xs`, `icon-sm`, `icon-lg`) exclusively for buttons that contain only an SVG — these sizes set equal width/height. For text+icon buttons use `default|sm|lg|xs` sizes; icons inside are auto-sized to 1rem via `[&_svg:not([class*='size-'])]:size-4`.",
@@ -2772,6 +2693,10 @@
2772
2693
  ]
2773
2694
  },
2774
2695
  {
2696
+ "absorbed": [
2697
+ "Paragraph",
2698
+ "Typography"
2699
+ ],
2775
2700
  "example": "import { Text } from \"@godxjp/ui/general\";\n\n<Text size=\"xs\" tone=\"muted\">補助テキスト</Text>\n<Text weight=\"medium\" tabular>¥1,240,000</Text>\n<Text size=\"xs\" mono tone=\"muted\">RC-204881</Text>\n\n// A link in running content — the affordance, composed onto a router link.\n<Text asChild link><Link href=\"/view/PKG-12\">ログイン画面の余白</Link></Text>",
2776
2701
  "group": "general",
2777
2702
  "importPath": "@godxjp/ui/general",
@@ -2905,7 +2830,7 @@
2905
2830
  "type": "boolean | { text, editing, icon, tooltip, onStart, onChange, onCancel, onEnd, maxLength, autoSize, triggerType, enterIcon, tabIndex }"
2906
2831
  },
2907
2832
  {
2908
- "description": "Single-line truncation, antd's spelling. antd drops `rows` / `expandable` / `onExpand` on `Text` — an inline run has no second line to expand into — and that omission is ported; reach for `Paragraph` when you want them. It is the SAME axis as `truncate` / `clamp` and OUTRANKS both (dev builds warn), because it is the only spelling that can also carry a suffix or a tooltip. `tooltip: true` (the children) or a node shows the full text in a Tooltip ONLY while the run is actually clipped — on pointer hover, and on keyboard focus of the Text itself (`asChild` link) or of the nearest focusable control around it (a Tree `treeitem`, a DataTable sort button, a link); the Text never becomes a tab stop of its own and its accessible name stays the full text. With `asChild` the child element is the truncated box (one `<a>`, not `<a><a>`). A `<Text ellipsis>` in `ColumnDef.header` truncates inside its column like a cell.",
2833
+ "description": "Single-line truncation, antd's spelling. antd drops `rows` / `expandable` / `onExpand` on `Text` — an inline run has no second line to expand into — and that omission is ported; for a multi-line run use `clamp` (antd's `Paragraph` was retired in v32). It is the SAME axis as `truncate` / `clamp` and OUTRANKS both (dev builds warn), because it is the only spelling that can also carry a suffix or a tooltip. `tooltip: true` (the children) or a node shows the full text in a Tooltip ONLY while the run is actually clipped — on pointer hover, and on keyboard focus of the Text itself (`asChild` link) or of the nearest focusable control around it (a Tree `treeitem`, a DataTable sort button, a link); the Text never becomes a tab stop of its own and its accessible name stays the full text. With `asChild` the child element is the truncated box (one `<a>`, not `<a><a>`). A `<Text ellipsis>` in `ColumnDef.header` truncates inside its column like a cell.",
2909
2834
  "name": "ellipsis",
2910
2835
  "type": "boolean | { suffix, symbol, defaultExpanded, expanded, onEllipsis, tooltip }"
2911
2836
  },
@@ -2981,6 +2906,9 @@
2981
2906
  ]
2982
2907
  },
2983
2908
  {
2909
+ "absorbed": [
2910
+ "Title"
2911
+ ],
2984
2912
  "example": "import { Heading } from \"@godxjp/ui/general\";\n\n<Heading level={2}>請求書一覧</Heading>\n<Heading level={3} tone=\"muted\">補足セクション</Heading>\n<Heading level={1} size=\"5xl\" align=\"center\">Ship it everywhere</Heading>",
2985
2913
  "group": "general",
2986
2914
  "importPath": "@godxjp/ui/general",
@@ -3041,155 +2969,6 @@
3041
2969
  "A marketing hero headline: `<Heading level={1} size=\"5xl\">` — a real <h1> on the display ramp, instead of a page-local `.display` class."
3042
2970
  ]
3043
2971
  },
3044
- {
3045
- "example": "import { Typography } from \"@godxjp/ui/general\";\n\n<Typography>\n <Typography.Title level={3}>リリースノート</Typography.Title>\n <Typography.Paragraph>請求書の一括ダウンロードに対応しました。</Typography.Paragraph>\n <Typography.Link href=\"/changelog\">変更履歴</Typography.Link>\n</Typography>",
3046
- "group": "general",
3047
- "importPath": "@godxjp/ui/general",
3048
- "name": "Typography",
3049
- "props": [
3050
- {
3051
- "defaultValue": "\"article\"",
3052
- "description": "Rendered element.",
3053
- "name": "as",
3054
- "type": "string"
3055
- },
3056
- {
3057
- "description": "antd's private alias for `as`. Accepted so antd code pastes in unchanged; `as` wins when both are passed.",
3058
- "name": "component",
3059
- "type": "string"
3060
- }
3061
- ],
3062
- "related": [
3063
- "Text",
3064
- "Title",
3065
- "Paragraph",
3066
- "Link",
3067
- "Heading",
3068
- "Prose"
3069
- ],
3070
- "rules": [
3071
- 2,
3072
- 23
3073
- ],
3074
- "storyPath": "general/typography.tsx",
3075
- "tagline": "antd's prose container — a plain <article> that Title / Paragraph / Text / Link sit inside, and the compound root so `<Typography.Text>` from an antd codebase compiles here unchanged.",
3076
- "usage": [
3077
- "DO reach for it when you have a RUN of prose — a heading, some paragraphs, a link — rather than one label. A single caption is just `<Text>`.",
3078
- "DO use the compound spelling when porting from antd: `Typography.Text`, `Typography.Title`, `Typography.Paragraph` and `Typography.Link` ARE the flat `Text` / `Title` / `Paragraph` / `Link` exports, not poorer copies of them.",
3079
- "DON'T use it as a layout box. It carries type, not spacing — sections are spaced by `PageContainer` and groups by `Flex` / `ResponsiveGrid`."
3080
- ],
3081
- "useCases": [
3082
- "A release-note body: a `Title`, two `Paragraph`s and a `Link`, wrapped so the block rhythm is owned in one place.",
3083
- "Pasting an antd screen in unchanged, `Typography.Paragraph` and all."
3084
- ]
3085
- },
3086
- {
3087
- "example": "import { Title } from \"@godxjp/ui/general\";\n\n<Title level={2}>請求書一覧</Title>\n<Title level={3} editable={{ onChange: rename }}>{projectName}</Title>",
3088
- "group": "general",
3089
- "importPath": "@godxjp/ui/general",
3090
- "name": "Title",
3091
- "props": [
3092
- {
3093
- "defaultValue": "1",
3094
- "description": "Sets BOTH the --heading-h{1..5} size token and the semantic <h1>..<h5>. Level 5 reads --heading-h5, which is bound to the existing --font-size-2xs step rather than being a new number. A value outside 1..5 falls back to h1, the way antd does — an <h7> from a runtime value would have no heading semantics at all. NOTE the default is antd's 1, while `Heading` defaults to 2.",
3095
- "name": "level",
3096
- "type": "1 | 2 | 3 | 4 | 5"
3097
- },
3098
- {
3099
- "description": "Override the rendered element — a visual h2 that is a real <h1>.",
3100
- "name": "as",
3101
- "type": "\"h1\" | \"h2\" | \"h3\" | \"h4\" | \"h5\" | \"div\""
3102
- },
3103
- {
3104
- "defaultValue": "\"default\"",
3105
- "description": "Semantic foreground colour. Outranks antd's `type`.",
3106
- "name": "tone",
3107
- "type": "\"default\" | \"muted\" | \"primary\" | \"success\" | \"warning\" | \"destructive\" | \"info\" | \"inherit\""
3108
- },
3109
- {
3110
- "description": "Logical text alignment.",
3111
- "name": "align",
3112
- "type": "\"start\" | \"center\" | \"end\""
3113
- },
3114
- {
3115
- "description": "Single-line ellipsis. `ellipsis` outranks it when both are passed.",
3116
- "name": "truncate",
3117
- "type": "boolean"
3118
- },
3119
- {
3120
- "defaultValue": "\"medium\"",
3121
- "description": "Font weight, from the three-weight canon.",
3122
- "name": "weight",
3123
- "type": "\"regular\" | \"medium\" | \"semibold\" | \"bold\""
3124
- },
3125
- {
3126
- "description": "Truncation. `true` is one line; `rows` clamps to N. `expandable` adds an expand control, `symbol` relabels it, `onEllipsis` fires when the measured overflow flips.",
3127
- "name": "ellipsis",
3128
- "type": "boolean | { rows, expandable, suffix, symbol, defaultExpanded, expanded, onExpand, onEllipsis, tooltip }"
3129
- }
3130
- ],
3131
- "related": [
3132
- "Heading",
3133
- "Typography",
3134
- "Text",
3135
- "CardTitle"
3136
- ],
3137
- "rules": [
3138
- 2,
3139
- 23
3140
- ],
3141
- "storyPath": "general/typography.tsx",
3142
- "tagline": "antd Typography.Title — a heading with five levels and the antd block behaviours (copyable, editable, ellipsis, the decorations). A SIBLING of `Heading`, which is this library's own four-level heading and is unchanged.",
3143
- "usage": [
3144
- "DO use `Title` when you want antd's spelling, a fifth level, or a heading that is copyable / editable.",
3145
- "DO use `Heading` for everything else. It is the four-level heading the rest of this library is written in, and `CardTitle` / `PageContainer` already render one for you.",
3146
- "DON'T set `level` to pick a SIZE. It sets the semantic element too, so skipping from h1 to h4 for looks breaks the document outline — override the element with `as` instead."
3147
- ],
3148
- "useCases": [
3149
- "A project name a user can rename in place: `<Title level={2} editable={{ onChange: rename }}>{name}</Title>`.",
3150
- "A dense sub-label below the body step, where `Heading` has no level left: `<Title level={5}>内訳</Title>`."
3151
- ]
3152
- },
3153
- {
3154
- "example": "import { Paragraph } from \"@godxjp/ui/general\";\n\n<Paragraph ellipsis={{ rows: 3, expandable: true }}>{description}</Paragraph>\n<Paragraph copyable>{ticketBody}</Paragraph>",
3155
- "group": "general",
3156
- "importPath": "@godxjp/ui/general",
3157
- "name": "Paragraph",
3158
- "props": [
3159
- {
3160
- "defaultValue": "\"div\"",
3161
- "description": "Rendered element. The default is antd's <div>, not <p>, because the editing textarea and the action cluster are BLOCK content: inside a <p> the parser would split the paragraph and leave the actions outside it. Pass `as=\"p\"` when the content is known to be phrasing-only.",
3162
- "name": "as",
3163
- "type": "\"div\" | \"p\" | \"span\""
3164
- },
3165
- {
3166
- "description": "Truncation. `rows` clamps to N lines; `expandable` adds an expand control (\"collapsible\" keeps a collapse one); `symbol` relabels it; `suffix` pins text after the ellipsis; `onEllipsis` fires when the measured overflow flips. This is the antd spelling of `clamp` and outranks it. One deviation from antd: the expand control is a sibling AFTER the clamped box rather than inline at the end of the last line, because the truncation is done by CSS line-clamping here (antd re-slices the text in JavaScript, which drops any element after the cut).",
3167
- "name": "ellipsis",
3168
- "type": "boolean | { rows, expandable, suffix, symbol, defaultExpanded, expanded, onExpand, onEllipsis, tooltip }"
3169
- }
3170
- ],
3171
- "related": [
3172
- "Text",
3173
- "Typography",
3174
- "Prose",
3175
- "Title"
3176
- ],
3177
- "rules": [
3178
- 2,
3179
- 23
3180
- ],
3181
- "storyPath": "general/typography.tsx",
3182
- "tagline": "antd Typography.Paragraph — a block of body text with the full ellipsis contract (rows + an expand control) and the antd block behaviours. Renders a <div>, matching antd.",
3183
- "usage": [
3184
- "DO use `Paragraph` for a block of body copy that may need to be clamped with a way to read the rest. `Text clamp={n}` clamps but has no expand control.",
3185
- "DO keep the default <div>. Change it to a <p> only when you know the content holds no interactive parts.",
3186
- "DON'T stack Paragraphs and then space them by hand — put them in a `<Flex direction=\"col\" gap>`; never write `mb-4` on your own element."
3187
- ],
3188
- "useCases": [
3189
- "An issue description clamped to three lines with a 続きを読む control: `<Paragraph ellipsis={{ rows: 3, expandable: true }}>{body}</Paragraph>`.",
3190
- "A release note whose text can be edited in place by an admin."
3191
- ]
3192
- },
3193
2972
  {
3194
2973
  "example": "import { Link } from \"@godxjp/ui/general\";\n\n<Link href=\"/docs/billing\">請求の設定</Link>\n<Link href=\"https://example.com\" target=\"_blank\">外部サイト</Link>",
3195
2974
  "group": "general",
@@ -3210,7 +2989,6 @@
3210
2989
  ],
3211
2990
  "related": [
3212
2991
  "Text",
3213
- "Typography",
3214
2992
  "Button",
3215
2993
  "PrefetchLink"
3216
2994
  ],
@@ -3245,7 +3023,7 @@
3245
3023
  },
3246
3024
  {
3247
3025
  "defaultValue": "\"glyph\"",
3248
- "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.",
3026
+ "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 (the auth CenteredShell 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.",
3249
3027
  "name": "mark",
3250
3028
  "type": "\"glyph\" | \"godx\" | \"godx-lockup\""
3251
3029
  },
@@ -3283,7 +3061,7 @@
3283
3061
  }
3284
3062
  ],
3285
3063
  "related": [
3286
- "AuthIdentity (@godxjp/ui/layout) — the canonical auth heading block; it already renders the GoDX mark, so don't add a second Logo above it.",
3064
+ "AuthIdentity (@godxjp/ui/layout) — the auth heading block. It draws `brand`, else the active preset's `brandMark` (the GoDX mark under `<AppProvider preset={godxPreset}>`), else no mark — so don't add a second Logo above it.",
3287
3065
  "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).",
3288
3066
  "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."
3289
3067
  ],
@@ -3291,7 +3069,7 @@
3291
3069
  45,
3292
3070
  46
3293
3071
  ],
3294
- "storyPath": "general/Logo.stories.tsx",
3072
+ "storyPath": "general/logo.tsx",
3295
3073
  "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.",
3296
3074
  "usage": [
3297
3075
  "DO import from `@godxjp/ui/general`: `import { Logo } from \"@godxjp/ui/general\";`",
@@ -3303,6 +3081,7 @@
3303
3081
  "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).",
3304
3082
  "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.",
3305
3083
  "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.",
3084
+ "GoDX LOOK = THE PRESET (v32): the package defaults are neutral (ink `--primary`, no product mark). A GoDX surface gets the violet identity, the fonts and the GoDX mark in AuthIdentity from `import \"@godxjp/ui/themes/godx.css\"` (after the base stylesheet) plus `import { godxPreset } from \"@godxjp/ui/themes/godx\"` and `<AppProvider preset={godxPreset}>`. `mark=\"godx\"` / `\"godx-lockup\"` still render the GoDX artwork wherever you place them explicitly.",
3306
3085
  "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.",
3307
3086
  "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."
3308
3087
  ],
@@ -3311,7 +3090,7 @@
3311
3090
  "Auth screen — a standalone labelled mark above the sign-in form: `<Logo label=\"CoreBooks\" size=\"lg\" />`.",
3312
3091
  "Tenant/workspace switcher row — a small `size=\"sm\"` mark as the leading slot of a ListRow or menu item.",
3313
3092
  "Custom SVG brand — pass an inline `<svg>` as `glyph` to render a real logomark on the primary fill instead of a letter.",
3314
- "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.",
3093
+ "Hosted GoDX identity surface — `<Logo mark=\"godx\" tone=\"success\" />` in an auth CenteredShell `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.",
3315
3094
  "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.",
3316
3095
  "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\"."
3317
3096
  ]
@@ -3360,14 +3139,14 @@
3360
3139
  }
3361
3140
  ],
3362
3141
  "related": [
3363
- "AuthShell — pairs with Reveal for the auth card entrance; AuthShell delegates all motion to Reveal.",
3142
+ "CenteredShell (auth shape) — pairs with Reveal for the auth card entrance; the auth shape delegates all motion to Reveal.",
3364
3143
  "Card — the most common thing to wrap in <Reveal> (dashboard cards, auth card, settings sections).",
3365
3144
  "ResponsiveGrid — combine with `<Reveal delay={n}>` (or `asChild`) per grid item for a staggered grid reveal.",
3366
3145
  "Carousel — wrap the WHOLE Carousel in one `<Reveal on=\"view\">`, never each CarouselItem: the rail clips its slides, so a per-slide observer never fires for the ones outside the window.",
3367
3146
  "ScrollArea — same rule as Carousel: reveal the scroller, not the children it clips."
3368
3147
  ],
3369
3148
  "rules": [],
3370
- "storyPath": "general/Reveal.stories.tsx",
3149
+ "storyPath": "general/reveal.tsx",
3371
3150
  "tagline": "The official entrance-motion primitive (staggered fade-up) — reads DS motion tokens and honours prefers-reduced-motion, replacing hand-rolled @keyframes + .app-reveal/.d1..d6 classes.",
3372
3151
  "usage": [
3373
3152
  "DO use <Reveal> INSTEAD of hand-rolling `@keyframes auth-fade-up` + `.app-reveal` + `.d1..d6` in a consumer global.css — that repeats literal durations/delays and violates the tokens-only rule. Reveal reads `--duration-slow` / `--ease-emphasized` / `--reveal-distance` / `--reveal-stagger-step`.",
@@ -3383,7 +3162,7 @@
3383
3162
  "DO NOT set a raw ms delay or a literal translate distance — the whole point is that `delay` is a controlled ordinal and the distance/duration come from tokens."
3384
3163
  ],
3385
3164
  "useCases": [
3386
- "Auth card entrance: `<AuthShell><Reveal><Card/></Reveal></AuthShell>` — the sign-in card fades up on load, respecting reduced-motion.",
3165
+ "Auth card entrance: `<CenteredShell variant=\"auth\"><Reveal><Card/></Reveal></CenteredShell>` — the sign-in card fades up on load, respecting reduced-motion.",
3387
3166
  "Staggered dashboard: map stat cards with `<Reveal delay={i + 1}>` so the row cascades in.",
3388
3167
  "Section reveal on a settings/detail page — wrap each Card in <Reveal> for a calm entrance without hand-written CSS.",
3389
3168
  "asChild on a grid item: `<Reveal asChild delay={2}><ResponsiveGrid.Item/></Reveal>` keeps the grid cell intact while animating it in.",
@@ -3444,10 +3223,10 @@
3444
3223
  "Button (loading) — THIS action is in flight, on a control. Not an ambient state.",
3445
3224
  "Progress — a DETERMINATE amount is done. Activity's `bar` variant is the indeterminate case, where no percentage exists.",
3446
3225
  "DataState — antd `Spin`'s wrapper form (`<Spin spinning>{children}</Spin>`) over a query: skeleton → prerequisite → empty → error, with cause-aware retry. This is what to reach for when the REGION is loading.",
3447
- "antd `Spin` — no such component here, by a ruling recorded twice in docs/roadmap/parity-backlog.md and docs/roadmap/parity-audit-data-display-feedback.md §2.20. Its four jobs are Activity / Skeleton / DataState / Button `loading`. Its `delay` (flicker guard) is tracked separately against Button as `loadingDelay`; its `fullscreen` is a Dialog/app-shell concern, not an indicator one."
3226
+ "antd `Spin` — no such component here, by a standing parity ruling (antd Spin is not ported). Its four jobs are Activity / Skeleton / DataState / Button `loading`. Its `delay` (flicker guard) is tracked separately against Button as `loadingDelay`; its `fullscreen` is a Dialog/app-shell concern, not an indicator one."
3448
3227
  ],
3449
3228
  "rules": [],
3450
- "storyPath": "general/Activity.stories.tsx",
3229
+ "storyPath": "general/activity.tsx",
3451
3230
  "tagline": "The official AMBIENT-motion primitive and the standalone indeterminate indicator (antd `Spin`) — a continuous, unbounded 'something is happening right now, elsewhere' mark (someone typing, a sync running, a response streaming, a recording live). Loading a REGION is Skeleton; an in-flight ACTION is Button loading; a known percentage is Progress.",
3452
3231
  "usage": [
3453
3232
  "DO read this first if you came looking for a Spin/Spinner (gh#830). antd's `Spin` is ONE component with a `spinning` boolean covering four different situations; here those are four components, because the accessible semantics of each are genuinely different: (1) something is happening ELSEWHERE, indefinitely → Activity, no live region by default; (2) the CONTENT of this region is loading → Skeleton (aria-busy + aria-live, shaped placeholders) or DataState (the whole skeleton → prerequisite → empty → error lifecycle); (3) THIS action is in flight → Button `loading` (aria-busy + activation blocked on the control itself); (4) a percentage is known → Progress. Picking by shape ('I want the round one') is how a persistent indicator ends up telling a screen reader the page is busy forever.",
@@ -3512,7 +3291,7 @@
3512
3291
  "Avatar — a person or an entity, not an icon; it brings its own box and must not be squeezed into an icon measure."
3513
3292
  ],
3514
3293
  "rules": [],
3515
- "storyPath": "general/Icon.stories.tsx",
3294
+ "storyPath": "general/icon.tsx",
3516
3295
  "tagline": "A glyph on the --icon-size-* scale, and the ONLY supported way to render a standalone one. A lucide component ships width=\"24\" height=\"24\"; only four rules in this library override that (.ui-button svg, the menu row, the topbar cell, the ListRow leading slot), so everywhere else a glyph draws at 24px beside 14px text.",
3517
3296
  "usage": [
3518
3297
  "DO use <Icon> for ANY glyph that is not already inside Button / DropdownMenuItem / TopbarItem / a ListRow leading slot — in a Text, a table cell, an <a>, a Flex. Those four are the only contexts in the library that size a glyph for you; everywhere else a bare lucide icon renders at 24px.",
@@ -3768,7 +3547,7 @@
3768
3547
  35,
3769
3548
  37
3770
3549
  ],
3771
- "storyPath": "data-display/DataTable.stories.tsx",
3550
+ "storyPath": "data-display/data-table/index.tsx",
3772
3551
  "tagline": "The one TanStack-powered compound admin list — sticky header, sorting, global search, column visibility ('set view'), bulk selection, BOTH cursor and numbered pagination, density, and built-in empty/loading states. Keep the SIMPLE `data` + lean `columns` (ColumnDef) API for the common case; opt into the full grid chrome via the compound parts. Internally driven by @tanstack/react-table (a real dependency). Lives on @godxjp/ui/data-display only (it is NOT on the runtime-neutral root/admin barrel because it pulls TanStack).",
3773
3552
  "usage": [
3774
3553
  "DO use `striped` on dense list tables (many columns, a row the eye must follow across the width). To stripe EVERY Table and DataTable in a service, set it ONCE in the theme — `:root { --table-row-striped-alpha: 100%; }` — instead of passing `striped` at each call site; `striped={false}` then opts one table out. Retint with `--table-row-striped-background`, never with a `rowClassName` utility or `:nth-child` page CSS (those count DOM rows, so an expanded detail row shifts every stripe after it).",
@@ -3876,7 +3655,7 @@
3876
3655
  "DataState / InfiniteQueryState — use instead of Card when the content is a TanStack Query-driven list that needs automatic skeleton, empty, and error states; Card does not manage loading lifecycle."
3877
3656
  ],
3878
3657
  "rules": [],
3879
- "storyPath": "data-display/Card.stories.tsx",
3658
+ "storyPath": "data-display/card/index.tsx",
3880
3659
  "subParts": [
3881
3660
  "CardAction",
3882
3661
  "CardCover",
@@ -3899,7 +3678,7 @@
3899
3678
  "DO use `tabList` for a tab strip that belongs to the CARD — antd's card-head tabs, ported name for name (gh#570): `tabList={[{ key, tab, disabled? }]}` plus `activeTabKey`/`defaultActiveTabKey`/`onTabChange`, with `extra` for antd's `tabBarExtraContent` and `tabProps` for everything else on the Tabs underneath. The strip renders INSIDE the card head, under the title, on the same surface and inside the same border, and the Card's children become the selected tab's body (wrap them in <CardContent>, or <CardContent flush> for an edge-to-edge DataTable). DON'T hand-roll it as a <Tabs> parked on the page above the card (the strip floats off the card and the two read as two objects) or as a <Card> repeated inside each tab (the shell is copied per view). A <Tabs> INSIDE <CardContent tight flush> is still correct for a strip that belongs to the BODY rather than to the card head.",
3900
3679
  "DO reach for `accentPlacement=\"perimeter\"` when the whole card needs attention, not one edge: `<Card accent=\"attention\" accentPlacement=\"perimeter\">` is the semantic-tone equivalent of `variant=\"featured\"` (which is brand-toned by definition). Never hand-roll it with `className=\"border-2 border-[--attention]\"` or a page-local `.card--attention` rule — the placement owns the border weight, the outer ring AND the slot-padding compensation, so text stays on the same column as an unaccented sibling.",
3901
3680
  "DON'T hand-roll a stat/KPI tile with <Card> + raw divs — use <StatCard> (label, value, hint, delta, layout, inverse props) which is already a Card internally with correct token-driven layout.",
3902
- "SPACING IS BORDER-AWARE & token-driven (theme via src/tokens/components/card.css, never hard-code padding on slots): `--card-space-inset` is the shared horizontal column every slot (header/content/footer) aligns to. A DIVIDED section — a `banded` header or a `separated` footer, i.e. one carrying a divider border — pads SYMMETRICALLY top+bottom from `--card-space-divided-y` (a band reads as its own region). A PLAIN header flows into the body instead: top `--card-space-shell-y`, no bottom, and the body supplies the gap via `--card-space-body-y`. THE TWO AXES ARE INDEPENDENT: `--card-space-inset` is inline-only, while `--card-space-shell-y` owns the BLOCK shell edges (plain-header top, `solo` body top, terminal slot bottom) and defaults to the inset — so a shell/theme can make a card SHORTER without narrowing its column by overriding `--card-space-shell-y` alone (this is how AuthShell's `--auth-shell-card-padding-block-compact` reaches CardContent). Never bridge it with a consumer selector on the card-content slot. Special case: `<CardContent flush>` zeroes BOTH of its block edges — for ANY full-bleed body, not only one containing a <Table>` gate left a flush file LIST floating 18px off its header while the flush table beside it sat at 0) — so the plain header above it supplies the gap from its own `--card-space-body-y` bottom padding instead. `tight` and `solo` still own that axis themselves. `--card-space-gap` is the in-slot stack gap (title↕description). Tune the band rhythm once at `--card-space-divided-y`; tune the accent stripe width at `--card-accent-rail-width` (default 6px)."
3681
+ "SPACING IS BORDER-AWARE & token-driven (theme via src/tokens/components/card.css, never hard-code padding on slots): `--card-space-inset` is the shared horizontal column every slot (header/content/footer) aligns to. A DIVIDED section — a `banded` header or a `separated` footer, i.e. one carrying a divider border — pads SYMMETRICALLY top+bottom from `--card-space-divided-y` (a band reads as its own region). A PLAIN header flows into the body instead: top `--card-space-shell-y`, no bottom, and the body supplies the gap via `--card-space-body-y`. THE TWO AXES ARE INDEPENDENT: `--card-space-inset` is inline-only, while `--card-space-shell-y` owns the BLOCK shell edges (plain-header top, `solo` body top, terminal slot bottom) and defaults to the inset — so a shell/theme can make a card SHORTER without narrowing its column by overriding `--card-space-shell-y` alone (this is how the auth CenteredShell's `--auth-shell-card-padding-block-compact` reaches CardContent). Never bridge it with a consumer selector on the card-content slot. Special case: `<CardContent flush>` zeroes BOTH of its block edges — for ANY full-bleed body, not only one containing a <Table>` gate left a flush file LIST floating 18px off its header while the flush table beside it sat at 0) — so the plain header above it supplies the gap from its own `--card-space-body-y` bottom padding instead. `tight` and `solo` still own that axis themselves. `--card-space-gap` is the in-slot stack gap (title↕description). Tune the band rhythm once at `--card-space-divided-y`; tune the accent stripe width at `--card-accent-rail-width` (default 6px)."
3903
3682
  ],
3904
3683
  "useCases": [
3905
3684
  "Dashboard KPI summary row: wrap each metric in <StatCard> (or a plain <Card density=\"tight\"> with <CardContent>) to render a uniform grid of labeled value tiles with optional trend deltas.",
@@ -3943,7 +3722,6 @@
3943
3722
  37,
3944
3723
  38
3945
3724
  ],
3946
- "storyPath": "data-display/Card.stories.tsx",
3947
3725
  "tagline": "Card body. flush = edge-to-edge (for DataTable/tabs); tight = no top gap; solo = no header above. NEVER put a Toolbar inside flush (it loses padding).",
3948
3726
  "usage": [
3949
3727
  "DO: Always wrap body content in <CardContent> — a bare <Card> has no internal padding, so any child placed directly inside it renders flush against the card edges.",
@@ -4026,7 +3804,7 @@
4026
3804
  "LineChart / BarChart / PieChart (@godxjp/ui/charts) — when the trend or composition BEHIND a KPI matters, pair the StatCard headline number with a chart in the same dashboard grid: StatCard states the figure, the chart shows its shape over time or its breakdown."
4027
3805
  ],
4028
3806
  "rules": [],
4029
- "storyPath": "data-display/StatCard.stories.tsx",
3807
+ "storyPath": "data-display/stat-card.tsx",
4030
3808
  "tagline": "KPI tile. ⚠️ StatCard IS ALREADY a bordered Card — render it DIRECTLY in ResponsiveGrid. NEVER wrap it in <Card>/<CardContent> (that double-borders it → looks too thick). Use `accent` for a semantic leading-edge rail on a KPI needing attention.",
4031
3809
  "usage": [
4032
3810
  "DO place StatCard directly as a child of ResponsiveGrid — it renders its own bordered Card shell internally, so no wrapping <Card> or <CardContent> is needed or allowed. Wrapping creates a double border.",
@@ -4116,7 +3894,7 @@
4116
3894
  "rules": [
4117
3895
  40
4118
3896
  ],
4119
- "storyPath": "data-display/ServiceLauncherCard.stories.tsx",
3897
+ "storyPath": "data-display/service-launcher-card.tsx",
4120
3898
  "subParts": [
4121
3899
  "ServiceLauncherCardSkeleton"
4122
3900
  ],
@@ -4171,7 +3949,6 @@
4171
3949
  "rules": [
4172
3950
  40
4173
3951
  ],
4174
- "storyPath": "data-display/ServiceLauncherCard.stories.tsx",
4175
3952
  "tagline": "Dashed companion tile that closes a launcher grid with the real catalog/add route — same Card geometry as ServiceLauncherCard, no status or metadata.",
4176
3953
  "usage": [
4177
3954
  "DO render it as the LAST child of the same ResponsiveGrid that holds the ServiceLauncherCards, so it inherits the 3→2→1 ladder and lines up with the tiles it follows.",
@@ -4256,7 +4033,7 @@
4256
4033
  "rules": [
4257
4034
  35
4258
4035
  ],
4259
- "storyPath": "data-display/Badge.stories.tsx",
4036
+ "storyPath": "data-display/badge.tsx",
4260
4037
  "subParts": [
4261
4038
  "StatusBadge"
4262
4039
  ],
@@ -4305,7 +4082,7 @@
4305
4082
  "CardAction — the header slot a Legend usually sits in, so the key lands opposite the CardTitle."
4306
4083
  ],
4307
4084
  "rules": [],
4308
- "storyPath": "data-display/Legend.stories.tsx",
4085
+ "storyPath": "data-display/legend.tsx",
4309
4086
  "tagline": "The KEY for a colour-coded surface: which tone means what, spelled out in words once — a square swatch per tone with its label.",
4310
4087
  "usage": [
4311
4088
  "DO import from `@godxjp/ui/data-display`: `import { Legend } from \"@godxjp/ui/data-display\";`",
@@ -4346,7 +4123,7 @@
4346
4123
  "Badge — a chip that labels a thing (and can carry the entity's own `color` as a wash). Swatch is the colour itself, with no chip around it."
4347
4124
  ],
4348
4125
  "rules": [],
4349
- "storyPath": "data-display/Swatch.stories.tsx",
4126
+ "storyPath": "data-display/swatch.tsx",
4350
4127
  "tagline": "A READ-ONLY sample of ONE colour a person chose — a brand's primary_color, a calendar category, a tag tint. The colour is a VALUE on the element, never a token in a stylesheet.",
4351
4128
  "usage": [
4352
4129
  "DO import from `@godxjp/ui/data-display`: `import { Swatch } from \"@godxjp/ui/data-display\";`",
@@ -4364,9 +4141,9 @@
4364
4141
  ]
4365
4142
  },
4366
4143
  {
4367
- "example": "import { TextDiff } from \"@godxjp/ui/data-display\";\n\n<TextDiff before={row.previous_original} after={original} lang=\"ja\" />\n\n<TextDiff mode=\"split\" granularity=\"line\" before={previousBody} after={body} />",
4144
+ "example": "import { TextDiff } from \"@godxjp/ui/lab\";\n\n<TextDiff before={row.previous_original} after={original} lang=\"ja\" />\n\n<TextDiff mode=\"split\" granularity=\"line\" before={previousBody} after={body} />",
4368
4145
  "group": "data-display",
4369
- "importPath": "@godxjp/ui/data-display",
4146
+ "importPath": "@godxjp/ui/lab",
4370
4147
  "name": "TextDiff",
4371
4148
  "props": [
4372
4149
  {
@@ -4433,10 +4210,11 @@
4433
4210
  "Timeline — the list of revisions; TextDiff is what one revision changed."
4434
4211
  ],
4435
4212
  "rules": [],
4436
- "storyPath": "data-display/TextDiff.stories.tsx",
4213
+ "storyPath": "data-display/text-diff.tsx",
4437
4214
  "tagline": "What changed between two versions of a text — removed runs in <del>, added runs in <ins>, inline or side by side, per line / word / character, with CJK cut per character and long unchanged runs folded.",
4215
+ "tier": "lab",
4438
4216
  "usage": [
4439
- "DO import from `@godxjp/ui/data-display`: `import { TextDiff } from \"@godxjp/ui/data-display\";`",
4217
+ "DO import from `@godxjp/ui/lab` (the opt-in lab subpath): `import { TextDiff } from \"@godxjp/ui/lab\";`",
4440
4218
  "DO pass the two raw strings — the component tokenizes, diffs (LCS) and renders. Use the exported `diffText(before, after, { granularity, lang })` only when you need the runs themselves (a change count, a plain-text export).",
4441
4219
  "DO pass `lang` for the text's language, not the UI's: a Japanese original in an English UI still needs lang=\"ja\".",
4442
4220
  "DO use mode=\"split\" when there is room for two columns and the texts are long; inline for a drawer, a list row or a narrow panel.",
@@ -4475,7 +4253,7 @@
4475
4253
  "Legend — the key that says what a COLOUR means across other marks; a FeatureList makes statements of its own."
4476
4254
  ],
4477
4255
  "rules": [],
4478
- "storyPath": "data-display/FeatureList.stories.tsx",
4256
+ "storyPath": "data-display/feature-list.tsx",
4479
4257
  "tagline": "A list of STATEMENTS, each with a leading state glyph (included ✓ / limited − / excluded ✗), an optional wrapping description, and the glyph aligned to the first text line.",
4480
4258
  "usage": [
4481
4259
  "DO import from `@godxjp/ui/data-display`: `import { FeatureList } from \"@godxjp/ui/data-display\";`",
@@ -4493,80 +4271,10 @@
4493
4271
  ]
4494
4272
  },
4495
4273
  {
4496
- "docPath": "data-display/thumbnail.tsx",
4497
- "example": "import { Thumbnail } from \"@godxjp/ui/data-display\";\n\n<Flex gap=\"sm\" wrap align=\"start\">\n <Thumbnail src=\"/shot-portrait.png\" width={360} height={640} alt=\"モバイル版の一覧画面\" size=\"lg\" />\n <Thumbnail src=\"/shot-desktop.png\" width={960} height={540} alt=\"デスクトップ版のダッシュボード\" size=\"lg\" />\n</Flex>",
4274
+ "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, and the\n item carries the divider. Never wrap the row in your own li, and never reach for a raw ul:\n `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>",
4498
4275
  "group": "data-display",
4499
4276
  "importPath": "@godxjp/ui/data-display",
4500
- "name": "Thumbnail",
4501
- "props": [
4502
- {
4503
- "description": "Image URL.",
4504
- "name": "src",
4505
- "required": true,
4506
- "type": "string"
4507
- },
4508
- {
4509
- "description": "Required by the type. Pass \"\" for a picture that carries no information the page does not already say — the empty string is a decision, a missing attribute is an omission (WCAG 1.1.1).",
4510
- "name": "alt",
4511
- "required": true,
4512
- "type": "string"
4513
- },
4514
- {
4515
- "defaultValue": "\"md\"",
4516
- "description": "Frame height — sm 64px (an attachment strip), md 96px (a gallery row), lg 160px (a review screen). The width follows the picture's own ratio.",
4517
- "name": "size",
4518
- "type": "\"sm\" | \"md\" | \"lg\""
4519
- },
4520
- {
4521
- "description": "The file's real pixel width. Pass it with `height` and the browser knows the ratio before the bytes land, so the frame takes its final width on the first paint instead of reflowing the row.",
4522
- "name": "width",
4523
- "type": "number"
4524
- },
4525
- {
4526
- "description": "The file's real pixel height. See `width`.",
4527
- "name": "height",
4528
- "type": "number"
4529
- },
4530
- {
4531
- "description": "Native img attribute, passed through. Every other img attribute passes through too.",
4532
- "name": "loading",
4533
- "type": "\"lazy\" | \"eager\""
4534
- },
4535
- {
4536
- "description": "Root class. The three heights, the hairline and the radius live in the --thumbnail-* tokens.",
4537
- "name": "className",
4538
- "type": "string"
4539
- }
4540
- ],
4541
- "related": [
4542
- "AspectRatio — a ratio-constrained slot at full width; use it when YOU choose the ratio, not when the picture does.",
4543
- "Avatar — an identity mark for a person, team or entity, with an initials fallback.",
4544
- "CardCover — the full-bleed media slot at the top of a Card.",
4545
- "Carousel — for a set too large to lay out at once; a wrap row of 2–5 thumbnails is not a carousel."
4546
- ],
4547
- "rules": [],
4548
- "storyPath": "data-display/Thumbnail.stories.tsx",
4549
- "tagline": "A framed picture at a FIXED height with its own INTRINSIC width — the shape a wrapping row of screenshots with different aspect ratios needs.",
4550
- "usage": [
4551
- "DO import from `@godxjp/ui/data-display`: `import { Thumbnail } from \"@godxjp/ui/data-display\";`",
4552
- "DO wrap a row of them in `<Flex gap=\"sm\" wrap align=\"start\">` — equal heights make the strip read as one row while each width stays natural.",
4553
- "DO pass the file's real `width` and `height` so the row does not reflow while the pictures load.",
4554
- "DON'T use AspectRatio for this: it spans `width: 100%` and pins ONE ratio, so a row of mixed-ratio screenshots comes out letterboxed or cropped. AspectRatio is for a slot whose ratio you choose.",
4555
- "DON'T use Avatar (an identity mark with an initials fallback) or Card (it pads the frame away from the picture). CardCover is the full-bleed media slot inside a Card, not a standalone thumbnail.",
4556
- "DON'T hand-roll `<img className=\"h-40 w-auto rounded-md border\" />` — `no-hand-rolled-surface` flags it, correctly: border + radius is a surface."
4557
- ],
4558
- "useCases": [
4559
- "A review screen showing a submission's screenshots, portrait and landscape in the same wrapping row.",
4560
- "An attachment strip beside a message or a ticket, at `size=\"sm\"`.",
4561
- "A gallery row of uploaded images where each picture must stay uncropped.",
4562
- "Any picture that needs a hairline so a light image is distinguishable from the card behind it."
4563
- ]
4564
- },
4565
- {
4566
- "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, and the\n item carries the divider. Never wrap the row in your own li, and never reach for a raw ul:\n `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>",
4567
- "group": "data-display",
4568
- "importPath": "@godxjp/ui/data-display",
4569
- "name": "ListRow",
4277
+ "name": "ListRow",
4570
4278
  "props": [
4571
4279
  {
4572
4280
  "defaultValue": "false",
@@ -4634,7 +4342,7 @@
4634
4342
  42,
4635
4343
  44
4636
4344
  ],
4637
- "storyPath": "data-display/ListRow.stories.tsx",
4345
+ "storyPath": "data-display/list-row.tsx",
4638
4346
  "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.",
4639
4347
  "usage": [
4640
4348
  "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.",
@@ -4753,7 +4461,7 @@
4753
4461
  6,
4754
4462
  23
4755
4463
  ],
4756
- "storyPath": "data-display/CredentialReveal.stories.tsx",
4464
+ "storyPath": "data-display/credential-reveal.tsx",
4757
4465
  "tagline": "One-time secret surface — masked-by-default value with a show/hide toggle, a copy button that confirms the copy, optional download, and an optional acknowledge action to pair with Dialog. The GitHub/Stripe token-reveal pattern as a real primitive so consumers stop hand-rolling it.",
4758
4466
  "usage": [
4759
4467
  "DO use for a secret shown exactly once after creation (device credential, API key, service-account secret) — it masks by default and confirms the copy.",
@@ -4815,7 +4523,7 @@
4815
4523
  23,
4816
4524
  24
4817
4525
  ],
4818
- "storyPath": "data-display/QrCode.stories.tsx",
4526
+ "storyPath": "data-display/qr-code.tsx",
4819
4527
  "tagline": "Local-only SVG QR renderer for enrollment links, device pairing and other values that must never be sent to a third-party image service.",
4820
4528
  "usage": [
4821
4529
  "DO use QrCode when the encoded payload must remain inside the browser, especially an otpauth enrollment URI containing a TOTP secret.",
@@ -4879,7 +4587,7 @@
4879
4587
  "Flex — use Flex for arbitrary vertical/horizontal layout of heterogeneous UI elements. Use Descriptions when every item follows the label-on-top / value-below pattern and you want responsive multi-column alignment for free."
4880
4588
  ],
4881
4589
  "rules": [],
4882
- "storyPath": "data-display/Descriptions.stories.tsx",
4590
+ "storyPath": "data-display/descriptions.tsx",
4883
4591
  "tagline": "Responsive definition grid for detail-page metadata. COMPOUND — value goes in Descriptions.Item children.",
4884
4592
  "usage": [
4885
4593
  "DO use Descriptions.Item as the ONLY direct child — never raw <div>, <dt>/<dd>, or plain text nodes. Every label/value pair must be wrapped in <Descriptions.Item label=\"…\">value</Descriptions.Item>.",
@@ -4956,7 +4664,7 @@
4956
4664
  "SkeletonTable — use for the loading skeleton before data arrives (pass to DataState's `skeleton=` or DataTable's `loading=`). EmptyState is for after data arrives and is empty, not while loading."
4957
4665
  ],
4958
4666
  "rules": [],
4959
- "storyPath": "data-display/EmptyState.stories.tsx",
4667
+ "storyPath": "data-display/empty-state.tsx",
4960
4668
  "tagline": "Centred empty placeholder with icon, title, description, and optional CTA.",
4961
4669
  "usage": [
4962
4670
  "DO always pass `title` — it is the only required prop and renders a heading (`<h3>` by default); omitting it causes a blank silent render with no visible error.",
@@ -5047,7 +4755,7 @@
5047
4755
  "BarChart / PieChart / LineChart (@godxjp/ui/charts) — a Progress breakdown handles ONE total split into a few named states, inline and without a charting runtime; move up to a chart when you have several series, many categories, or a value changing over time."
5048
4756
  ],
5049
4757
  "rules": [],
5050
- "storyPath": "data-display/Progress.stories.tsx",
4758
+ "storyPath": "data-display/progress.tsx",
5051
4759
  "tagline": "Progress in two modes: a METER (`value` 0–100, optional tone, over-capacity striped state, drawn as a bar or — with `shape='ring'` — as an arc with the readout inside it) and a BREAKDOWN (`segments` — one total split into tone-coloured slices on a taller track).",
5052
4760
  "usage": [
5053
4761
  "DO import from `@godxjp/ui/data-display`, not from a generic UI path: `import { Progress } from \"@godxjp/ui/data-display\";`",
@@ -5122,7 +4830,7 @@
5122
4830
  "CredentialReveal - a masked secret with copy; not for arbitrary text."
5123
4831
  ],
5124
4832
  "rules": [],
5125
- "storyPath": "data-display/CodeBlock.stories.tsx",
4833
+ "storyPath": "data-display/code-block.tsx",
5126
4834
  "tagline": "A block of preformatted text (request and response bodies, console output, snippets): mono, muted surface, soft-wrapped long lines by default, and an optional height cap that scrolls inside the block instead of pushing the page.",
5127
4835
  "usage": [
5128
4836
  "DO import from `@godxjp/ui/data-display`: `import { CodeBlock } from \"@godxjp/ui/data-display\";`",
@@ -5436,7 +5144,7 @@
5436
5144
  "ScrollArea - if a very long document needs its own scroll viewport, wrap Prose in ScrollArea."
5437
5145
  ],
5438
5146
  "rules": [],
5439
- "storyPath": "data-display/Prose.stories.tsx",
5147
+ "storyPath": "data-display/prose.tsx",
5440
5148
  "tagline": "Typography for rendered content (Markdown, CMS bodies, issue descriptions): styles the semantic HTML inside it from the tokens, with no opinion about where the HTML comes from. To RENDER Markdown, use the sibling package `@godxjp/markdown` inside it — `<Prose><Markdown>{body}</Markdown></Prose>` — the one GFM renderer + sanitiser + Mermaid gate shared by every GoDX app; do not assemble react-markdown + rehype-sanitize per app (gh#1108). Tables: `@godxjp/markdown` wraps each in `.ui-prose-table-scroll`, which Prose scrolls horizontally (focusable only while it overflows), and table cells keep their words whole — a short code like `F02` never breaks (gh#1131).",
5441
5149
  "usage": [
5442
5150
  "DO import from `@godxjp/ui/data-display`: `import { Prose } from \"@godxjp/ui/data-display\";`",
@@ -5487,7 +5195,7 @@
5487
5195
  "Badge — Badge is a single-item inline indicator; Timeline sequences multiple statuses with connectors. Compose Badge inside a Timeline `title` or `note` field for richer per-event context, but do not replace Timeline with a stack of Badges."
5488
5196
  ],
5489
5197
  "rules": [],
5490
- "storyPath": "data-display/Timeline.stories.tsx",
5198
+ "storyPath": "data-display/timeline.tsx",
5491
5199
  "tagline": "Vertical event list with an icon rail. Current item gets a highlighted glyph. `variant` switches the rail to numbered (ordinal) or status-driven glyphs, and each item carries a `status` (done/current/pending, or `log` for an activity-log entry with no status semantics) plus an optional per-item `icon`. The whole progress column is ONE hue: done dot, current dot and travelled line all paint `--primary`, exactly as `Steps` paints `finish` and `process`.",
5492
5200
  "usage": [
5493
5201
  "DO pass an array of `TimelineItem` objects to `items`. Each item is `{ title, location?, time?, note?, current?, status?, icon? }`. All fields except `title` are optional. The only other props are `variant` and `density`.",
@@ -5512,9 +5220,9 @@
5512
5220
  ]
5513
5221
  },
5514
5222
  {
5515
- "example": "import { TimelineGrid } from \"@godxjp/ui/data-display\";\n\n<TimelineGrid\n label=\"週シフト 5月11日〜17日\"\n start=\"06:00\"\n end=\"24:00\"\n interval={2}\n now=\"14:35\"\n columns={[\n { id: \"05-13\", label: \"水 13\" },\n { id: \"05-14\", label: \"木 14\", current: true },\n ]}\n events={[\n { id: \"a\", columnId: \"05-14\", start: \"09:00\", end: \"17:30\", title: \"早番\", description: \"田中\" },\n { id: \"b\", columnId: \"05-14\", start: \"13:00\", end: \"22:00\", title: \"遅番\", description: \"高橋\" },\n { id: \"c\", columnId: \"05-14\", start: \"22:00\", end: \"06:00\", title: \"夜勤\", description: \"伊藤\" },\n ]}\n onEventSelect={(event) => openShift(event.id)}\n/>",
5223
+ "example": "import { TimelineGrid } from \"@godxjp/ui/lab\";\n\n<TimelineGrid\n label=\"週シフト 5月11日〜17日\"\n start=\"06:00\"\n end=\"24:00\"\n interval={2}\n now=\"14:35\"\n columns={[\n { id: \"05-13\", label: \"水 13\" },\n { id: \"05-14\", label: \"木 14\", current: true },\n ]}\n events={[\n { id: \"a\", columnId: \"05-14\", start: \"09:00\", end: \"17:30\", title: \"早番\", description: \"田中\" },\n { id: \"b\", columnId: \"05-14\", start: \"13:00\", end: \"22:00\", title: \"遅番\", description: \"高橋\" },\n { id: \"c\", columnId: \"05-14\", start: \"22:00\", end: \"06:00\", title: \"夜勤\", description: \"伊藤\" },\n ]}\n onEventSelect={(event) => openShift(event.id)}\n/>",
5516
5224
  "group": "data-display",
5517
- "importPath": "@godxjp/ui/data-display",
5225
+ "importPath": "@godxjp/ui/lab",
5518
5226
  "name": "TimelineGrid",
5519
5227
  "props": [
5520
5228
  {
@@ -5585,10 +5293,11 @@
5585
5293
  42,
5586
5294
  45
5587
5295
  ],
5588
- "storyPath": "data-display/TimelineGrid.stories.tsx",
5296
+ "storyPath": "data-display/timeline-grid.tsx",
5589
5297
  "tagline": "The time-axis half of the Timeline family: a vertical hour axis, one column per day (or room, or machine) and event blocks placed by start time and duration. Overlapping events are laid out side by side automatically. NOT a calendar — no month view, no navigation, no drag-to-create, no recurrence, no timezone conversion.",
5298
+ "tier": "lab",
5590
5299
  "usage": [
5591
- "DO import from `@godxjp/ui/data-display`: `import { TimelineGrid } from \"@godxjp/ui/data-display\";`",
5300
+ "DO import from `@godxjp/ui/lab` (the opt-in lab subpath): `import { TimelineGrid } from \"@godxjp/ui/lab\";`",
5592
5301
  "DO give a shift/booking board one column per day and let the grid place the blocks. DON'T hand-roll a `relative` container with absolutely-positioned divs and percentage offsets — that is the exact shape this primitive replaces (#354 item 7), and it hides one of two overlapping shifts.",
5593
5302
  "DO re-tune the rhythm with the tokens instead of a className: `--timeline-grid-hour-height` is the height of one hour (the knob that fits a 24-hour axis on one screen), `--timeline-grid-column-min-width` is the floor below which the grid scrolls instead of collapsing, `--timeline-grid-axis-width` is the hour rail, and `--timeline-grid-event-color` / `--timeline-grid-now-color` / `--timeline-grid-current-tint` are role-mirror colour knobs.",
5594
5303
  "DO express a shift that crosses midnight as `start: \"22:00\", end: \"06:00\"` — an `end` at or before `start` means the next day. The block is drawn to the end of the window and marked `data-clipped`, and the text still reads 22:00–06:00.",
@@ -5697,7 +5406,7 @@
5697
5406
  "DataState — when your Table's data comes from `useQuery`, wrap it in DataState to handle loading/error/empty states declaratively instead of writing conditional logic around the Table yourself."
5698
5407
  ],
5699
5408
  "rules": [],
5700
- "storyPath": "data-display/Table.stories.tsx",
5409
+ "storyPath": "data-display/table.tsx",
5701
5410
  "subParts": [
5702
5411
  "TableBody",
5703
5412
  "TableCell",
@@ -5784,7 +5493,7 @@
5784
5493
  "AlertMutationFeedback — sibling widget for mutation (not query) lifecycle; use it below a form submit button to surface `useMutation` errors, not DataState which only handles `useQuery`."
5785
5494
  ],
5786
5495
  "rules": [],
5787
- "storyPath": "query/DataState.stories.tsx",
5496
+ "storyPath": "query/data-state.tsx",
5788
5497
  "tagline": "TanStack Query lifecycle widget — skeleton / error / empty / success for one useQuery block. Import from @godxjp/ui/query.",
5789
5498
  "usage": [
5790
5499
  "DO: pass a `UseQueryResult<T>` directly from `useQuery` — DataState reads `isPending`, `isError`, `isFetching`, `data`, and `error` off it; never destructure those fields manually and branch yourself.",
@@ -5800,7 +5509,7 @@
5800
5509
  "DO: expect a background refetch over existing data to keep the content on screen with a polite sr-only busy status — it does not flash the skeleton. Only the initial fetch (isPending) shows the skeleton."
5801
5510
  ],
5802
5511
  "useCases": [
5803
- "A detail page that loads a single invoice/journal entry via `useQuery` — DataState renders the skeleton row while fetching, an error alert with retry if the API fails, and the `<InvoiceCard>` only when data is confirmed non-null.",
5512
+ "A detail page that loads a single invoice/journal entry via `useQuery` — DataState renders the skeleton row while fetching, an error alert with retry if the API fails, and your invoice card only when data is confirmed non-null.",
5804
5513
  "A list page that shows a `DataTable` of members/partners — wrap the table in DataState so the skeleton matches the column count while loading and `EmptyState` appears when the filtered result set is empty.",
5805
5514
  "A sidebar panel that lazily loads related transactions for the selected entity — DataState keeps the panel in skeleton state during the background fetch without any manual `isPending` branching in the parent.",
5806
5515
  "A dashboard stat card that calls a summary API — DataState handles the loading/error/empty lifecycle so `<StatCard>` is only rendered with fully resolved numbers, preventing NaN or undefined rendering.",
@@ -5831,7 +5540,6 @@
5831
5540
  "ErrorSurface — the whole-page error surface; a session expiry is neither a page error nor an inline one."
5832
5541
  ],
5833
5542
  "rules": [],
5834
- "storyPath": "query/DataState.stories.tsx",
5835
5543
  "tagline": "App-root handler for an expired session (401 / invalid or expired token): every DataState / InfiniteQueryState / AlertMutationFeedback / AlertQueryError calls it automatically, once, and shows a neutral pending state instead of an error alert. Also exported from @godxjp/ui.",
5836
5544
  "usage": [
5837
5545
  "DO: mount it ONCE near the root, inside the QueryClientProvider/AppProvider, and redirect to the IdP with the current URL as the return target. The IdP returns immediately while its own session is alive — the SSO norm (Google/Microsoft apps, OIDC SPA guidance): no page-level error for an expired session.",
@@ -5890,7 +5598,7 @@
5890
5598
  "ButtonRefetch — companion component for the page header refresh action wired to `query.refetch()`. Use alongside InfiniteQueryState when you want an explicit refresh control in addition to the built-in load-more footer."
5891
5599
  ],
5892
5600
  "rules": [],
5893
- "storyPath": "query/InfiniteQueryState.stories.tsx",
5601
+ "storyPath": "query/infinite-query-state.tsx",
5894
5602
  "tagline": "useInfiniteQuery widget — flatten pages, skeleton/empty/error, load-more footer. Import from @godxjp/ui/query.",
5895
5603
  "usage": [
5896
5604
  "DO: Import from `@godxjp/ui/query` (not `@godxjp/ui`). Use the bundled `flattenItemPages` helper for any API that returns `{ items: T[] }` pages — it handles `undefined` data safely. Custom page shapes require a custom `flatten` function.",
@@ -5979,7 +5687,7 @@
5979
5687
  23,
5980
5688
  24
5981
5689
  ],
5982
- "storyPath": "data-entry/Form.stories.tsx",
5690
+ "storyPath": "data-entry/form.tsx",
5983
5691
  "tagline": "Ant-style layout container — renders <form> and pushes layout (vertical/horizontal), labelWidth/controlWidth, label alignment, responsive collapse, and multi-column grid down to every FormField (overridable per field).",
5984
5692
  "usage": [
5985
5693
  "DO set `layout`, `labelWidth`, `controlWidth` ONCE on `<Form>` — every `<FormField>` inside inherits them. Override a single field by passing the same prop on that `<FormField>` (Form → FormField priority).",
@@ -6099,7 +5807,7 @@
6099
5807
  }
6100
5808
  ],
6101
5809
  "related": [
6102
- "Label — the bare Radix label component. Use directly only when you are building a fully custom layout that cannot accept FormField's stack wrapper, and you will manage aria-describedby/aria-invalid yourself. FormField is always preferred for standard form controls.",
5810
+ "Label — the bare label component. Use directly only when you are building a fully custom layout that cannot accept FormField's stack wrapper, and you will manage aria-describedby/aria-invalid yourself. FormField is always preferred for standard form controls.",
6103
5811
  "Field — the inline row for a boolean control: label and control side by side, with `labelAddon`, `description` and `error` (gh#812 added the last two slots; `error` wires aria-invalid / aria-errormessage / aria-describedby onto the control, so a validated boolean no longer has to be hand-wired). It still renders NO hidden `<input name>` — `Switch name=\"…\"` submits itself — and it has no `required`, `helper`, error-bag `name` binding or layout/width knobs. Still: never wrap a bare `Switch` in FormField.",
6104
5812
  "Field — pairs a single checkbox or radio with a label and optional description in a horizontal layout (control beside text). Use Field instead of FormField when the control and its label sit side-by-side rather than stacked.",
6105
5813
  "CheckboxGroup / RadioGroup — for groups of options where FormField is not needed per-item; the group component handles its own legend/label and option layout."
@@ -6107,22 +5815,22 @@
6107
5815
  "rules": [
6108
5816
  23
6109
5817
  ],
6110
- "storyPath": "data-entry/FormField.stories.tsx",
5818
+ "storyPath": "data-entry/form-field/index.tsx",
6111
5819
  "tagline": "Wraps a control with label, helper, and error; injects the accessible name (aria-labelledby), description (aria-describedby) and validation (aria-errormessage/aria-invalid/aria-required) contract onto the child, which forwards it to its real semantic focus target. Reads the parent Form's layout (vertical/horizontal) — overridable per field.",
6112
5820
  "usage": [
6113
5821
  "DO pass the same string to both `id` on `<FormField>` and `id` on the child control — the component wires `<Label htmlFor={id}>`, and builds `{id}-helper` / `{id}-error` ids for `aria-describedby`. If the ids diverge the label click and screen-reader announcements break.",
6114
5822
  "DO pass a SINGLE React element as `children`. FormField calls `React.cloneElement` on it to inject `aria-describedby`, `aria-required`, and `aria-invalid` — if you pass a fragment or multiple nodes, cloneElement silently skips the injection and a11y attributes are lost.",
6115
- "COMPOSITE CHILD: when the single child is a layout wrapper — a `Flex` holding a range from/to pair or a 年/月 input+select combo — the label still reaches every control inside. FormField publishes its label through FieldNameContext and each control's semantic focus target (Input's `<input>`, Select/SearchSelect's `role=combobox` trigger, and everything composed on them) adopts it as a LAST-RESORT accessible name; a control's own `aria-label`/`aria-labelledby` always wins, so set a per-control `aria-label` (e.g. 開始日/終了日) when the two halves should announce distinct names. The wrapper itself renders as a named `role='group'` (see Flex).",
5823
+ "COMPOSITE CHILD: when the single child is a layout wrapper — a `Flex` holding a range from/to pair or a 年/月 input+select combo — the label still reaches every control inside. FormField publishes its label through FieldNameContext and each control's semantic focus target (Input's `<input>`, Select's `role=combobox` trigger, and everything composed on them) adopts it as a LAST-RESORT accessible name; a control's own `aria-label`/`aria-labelledby` always wins, so set a per-control `aria-label` (e.g. 開始日/終了日) when the two halves should announce distinct names. The wrapper itself renders as a named `role='group'` (see Flex).",
6116
5824
  "DO reach for `staticText` (not `children` with a bare string/span) for a read-only field mixed into an otherwise-editable Form — e.g. an immutable name/email row above an editable role Select on the same Members-edit card. It renders with the exact typography `Descriptions.Item`'s value uses, and — because it IS a FormField reading the same Form context — it lines up with every other field's label column, `labelAlign`, and row-to-row gap automatically. A bare string as `children` instead triggers the dev-mode 'expected a single React element child' warning and has no typography contract at all.",
6117
5825
  "WIDTH: a FormField FILLS its container in vertical/horizontal layout — like the conventional Form.Item (vertical → width:100%). It works full-width inside `<Form>`, a `ResponsiveGrid` cell, a bare `<Flex direction='col'>`, or a plain block; you do NOT need to wrap it in a grid to get full width. `layout='inline'` is the only content-width exception (compact, side-by-side). To narrow just the control (keeping the label row full-width), set `controlWidth` — never constrain the FormField itself.",
6118
5826
  "DO use the `error` prop (not a hand-rolled `<p>`) for validation messages — it renders with `role='alert'` and `text-destructive` styling and overrides `helper` automatically. Never render an error paragraph alongside FormField.",
6119
5827
  "DO use `labelAddon` (a ReactNode rendered after the label text, in the label row) for supplementary controls such as a tooltip trigger, a 'copy' icon button or a short text action ('Assign to myself'); in a horizontal/inline layout the label row wraps, so an addon that does not fit beside the label drops under it inside the label column rather than overlapping the control — never insert such controls as siblings outside FormField, which breaks layout.",
6120
5828
  "DON'T wrap `Switch` in FormField — use `Field` instead. Field owns the inline row: `label`, `labelAddon`, `description`, and `error` (which wires aria-invalid / aria-errormessage / aria-describedby onto the control, gh#812). It renders no hidden input of its own — `Switch name=\"…\"` is what submits natively — and it has no `required`, `helper` or layout knobs.",
6121
5829
  "DON'T use FormField for checkbox-beside-label or radio-beside-label patterns — use `Field` (single checkbox/radio with description) or `CheckboxGroup` / `RadioGroup` (multiple options), which have their own integrated labelling.",
6122
- "CONTRACT (which element owns each ARIA relationship): every data-entry control accepts and FORWARDS the injected props to its real semantic focus target, not a wrapper div — Input/Textarea/NumberInput → the `<input>/<textarea>`; Select/SearchSelect/Cascader/TreeSelect → the `role=combobox` trigger (with aria-expanded + aria-haspopup + aria-controls per the WAI-ARIA APG combobox pattern); DatePicker/TimePicker → the typeable `role=combobox` input (aria-haspopup=dialog); ColorPicker → the `<input type=color>` swatch; SearchInput → the `role=searchbox` input. GROUP controls own the relationship on their container: RadioGroup → `role=radiogroup` (full validation incl. aria-invalid/-errormessage/-required); CheckboxGroup, `DatePicker range` (two inputs), and Transfer → `role=group` — per ARIA 1.2 a group is not a widget, so the error id is folded into aria-describedby instead of aria-invalid/-errormessage. Upload forwards the label/description onto its native `<input type=file>`; its visible dropzone/button keeps its own action label. This forwarding is implemented once in `src/lib/field-a11y.ts` (`pickFieldA11y` / `pickGroupFieldA11y` / `resolveFieldA11y`) — do not reinvent it per control.",
5830
+ "CONTRACT (which element owns each ARIA relationship): every data-entry control accepts and FORWARDS the injected props to its real semantic focus target, not a wrapper div — Input/Textarea/NumberInput → the `<input>/<textarea>`; Select/Cascader/TreeSelect → the `role=combobox` trigger (with aria-expanded + aria-haspopup + aria-controls per the WAI-ARIA APG combobox pattern); DatePicker/TimePicker → the typeable `role=combobox` input (aria-haspopup=dialog); ColorPicker → the `<input type=color>` swatch; SearchInput → the `role=searchbox` input. GROUP controls own the relationship on their container: RadioGroup → `role=radiogroup` (full validation incl. aria-invalid/-errormessage/-required); CheckboxGroup, `DatePicker range` (two inputs), and Transfer → `role=group` — per ARIA 1.2 a group is not a widget, so the error id is folded into aria-describedby instead of aria-invalid/-errormessage. Upload forwards the label/description onto its native `<input type=file>`; its visible dropzone/button keeps its own action label. This forwarding is implemented once in `src/lib/field-a11y.ts` (`pickFieldA11y` / `pickGroupFieldA11y` / `resolveFieldA11y`) — do not reinvent it per control.",
6123
5831
  "FIELD IDENTITY / AUTOMATION: FormField also injects a `data-field` — the field's stable MACHINE key, resolved as `field` → `name` → `id` — onto the same semantic focus target the ARIA relationships land on, and onto every option of a RadioGroup/CheckboxGroup. Use it (not a generated id, and never the visible Japanese label) as the selector in e2e tests and screen automation. It reaches NESTED controls too: when the direct child is a layout wrapper (a Flex holding a from/to pair, a 年/月 combo, a value beside a 「不明」 checkbox) cloneElement stops on that wrapper, so FormField also publishes the field through context and each control inside resolves its own key from its OWN `id` — which is what keeps `search_billing_date_from` and `..._to` distinct instead of collapsing onto one shared key. A nested control with NO id of its own deliberately gets nothing: a fabricated key is worse than a missing one, because automation binds to it and breaks silently. Two companion pieces: a `Select`'s trigger also carries `data-value` = the selected CODE (the trigger shows the option LABEL, and Radix keeps the value in an aria-hidden 1x1px native `<select>`), and each RadioGroup/CheckboxGroup option gets a deterministic `{groupId}-{optionValue}` id instead of a per-mount `React.useId()` token. Nothing here is opt-in and no DOM structure changed. A `data-field` written on the control itself always wins.",
6124
5832
  "NATIVE `name` IS OPT-IN: FormField emits the same key as a real `name` attribute ONLY when the app set `<AppProvider emitFieldNames>`. It is off by default because `name` decides what a native `<form>` submit sends — turning it on globally in a shared package would make every consumer start posting new keys to its backend on an upgrade. Turn it on in apps that need native form posts or a screen-automation contract; a `name` written on the control itself always wins.",
6125
- "NATIVE FORM PARTICIPATION: pass `name` to a control for HTML form submission — Input/Textarea/NumberInput/Select submit natively; SearchSelect submits via a hidden input; DatePicker/TimePicker emit ISO strings (`yyyy-MM-dd` / 24h `HH:mm`); the range pickers emit `${name}_from` / `${name}_to`. `required`/`readOnly`/`disabled` map to the underlying control. Cascader/TreeSelect/Transfer submit named values via hidden inputs; Upload appends staged local files to FormData when named. Disabled controls are excluded.",
5833
+ "NATIVE FORM PARTICIPATION: pass `name` to a control for HTML form submission — Input/Textarea/NumberInput/Select submit natively; a searchable Select submits via a hidden input; DatePicker/TimePicker emit ISO strings (`yyyy-MM-dd` / 24h `HH:mm`); the range pickers emit `${name}_from` / `${name}_to`. `required`/`readOnly`/`disabled` map to the underlying control. Cascader/TreeSelect/Transfer submit named values via hidden inputs; Upload appends staged local files to FormData when named. Disabled controls are excluded.",
6126
5834
  "ERROR TIMING & RECOVERY: pass `error` only after a field is dirty or the form is submitted (don't show errors on pristine mount). The error node renders with `role='alert'` so it is announced live the moment it appears; clearing `error` (e.g. after the user corrects the value or a server round-trip succeeds) removes aria-invalid and restores the helper. On submit, focus the first invalid control and/or render an error summary that links to each field by `id`."
6127
5835
  ],
6128
5836
  "useCases": [
@@ -6130,7 +5838,7 @@
6130
5838
  "Wrapping a `Select` or `DatePicker` inside a multi-field filter panel where each control needs a visible label, helper hint (e.g. 'YYYY/MM/DD'), and inline error state.",
6131
5839
  "Adding a `labelAddon` tooltip button next to a 'Tax rate' label in an accounting form to explain when different rates apply, without breaking the label–control association.",
6132
5840
  "Enclosing a `DatePicker range` or `TimePicker` in an admin settings page where the field needs a label, a muted hint ('Inclusive of start and end date'), and conditional error display.",
6133
- "Wrapping a `SearchSelect` or `Select` (with `showSearch`) control for vendor/account lookup in a journal-entry form where the `id` must be kept consistent for programmatic focus management.",
5841
+ "Wrapping a `Select` (with `showSearch`) control for vendor/account lookup in a journal-entry form where the `id` must be kept consistent for programmatic focus management.",
6134
5842
  "Providing structured error feedback for a `Cascader` or `TreeSelect` in a multi-level category assignment screen, replacing ad-hoc error rendering with the standardised `role='alert'` pattern."
6135
5843
  ]
6136
5844
  },
@@ -6165,7 +5873,6 @@
6165
5873
  "rules": [
6166
5874
  23
6167
5875
  ],
6168
- "storyPath": "data-entry/FormErrors.stories.tsx",
6169
5876
  "subParts": [
6170
5877
  "FormErrorsProvider"
6171
5878
  ],
@@ -6289,7 +5996,7 @@
6289
5996
  "Select — use instead of Input when the value must come from a fixed or async option list; never render a plain Input and parse free text when the set of valid values is enumerable."
6290
5997
  ],
6291
5998
  "rules": [],
6292
- "storyPath": "data-entry/Input.stories.tsx",
5999
+ "storyPath": "data-entry/input.tsx",
6293
6000
  "tagline": "Styled wrapper around native <input>; accepts all HTML input attributes. Pair with FormField for labelled fields.",
6294
6001
  "usage": [
6295
6002
  "DO always wrap Input in FormField when the field needs a label, helper text, or validation error — FormField injects aria-describedby and aria-invalid onto Input automatically; never wire these attributes by hand.",
@@ -6446,7 +6153,7 @@
6446
6153
  3,
6447
6154
  6
6448
6155
  ],
6449
- "storyPath": "data-entry/NumberInput.stories.tsx",
6156
+ "storyPath": "data-entry/number-input.tsx",
6450
6157
  "tagline": "WAI-ARIA spinbutton for localized numeric entry — composes the real Input (role=spinbutton, inputMode=decimal) with stacked increment/decrement step Buttons. Type freely, Arrow/Shift-Arrow step, value commits clamped to min/max + rounded to precision.",
6451
6158
  "usage": [
6452
6159
  "DO use NumberInput (not `<Input type='number'>`) whenever numeric entry wants steppers, min/max clamping, precision rounding, or a ¥/% affix — it is the canonical numeric primitive. Plain Input has no stepper and no clamp.",
@@ -6550,7 +6257,7 @@
6550
6257
  "rules": [
6551
6258
  23
6552
6259
  ],
6553
- "storyPath": "data-entry/SearchInput.stories.tsx",
6260
+ "storyPath": "data-entry/search-input.tsx",
6554
6261
  "tagline": "Debounced search box with a clear button. Fires onSearch (NOT onChange) after the debounce. Controlled (value) or uncontrolled (defaultValue).",
6555
6262
  "usage": [
6556
6263
  "DO: listen to `onSearch`, not `onChange`. The component debounces internally (default 250 ms) and fires `onSearch(q)` after the delay — never wire your filter logic to `onChange` on SearchInput because it does not expose one.",
@@ -6675,7 +6382,7 @@
6675
6382
  "Dialog — the over-threshold branch; `RecordPicker` opens one for you."
6676
6383
  ],
6677
6384
  "rules": [],
6678
- "storyPath": "data-entry/RecordPicker.stories.tsx",
6385
+ "storyPath": "data-entry/record-picker.tsx",
6679
6386
  "tagline": "ONE picker whose SHAPE follows the size of the set behind it: at or under `threshold` it IS a Select (dropdown with search); over it, a Dialog with search + consumer-declared filters, chips, and a confirm step for `multiple`. Reach for it when the same field may hold eight records on one tenant and eight thousand on another.",
6680
6387
  "usage": [
6681
6388
  "DO pass `count` from the server whenever the list is server-backed. It is the only honest input to the threshold decision — `rows.length` describes the page you fetched, not the set behind it, so a picker over 9000 records would render as a dropdown showing 20.",
@@ -6699,7 +6406,8 @@
6699
6406
  "CountrySelect",
6700
6407
  "SearchSelect",
6701
6408
  "Typeahead",
6702
- "AsyncSelect"
6409
+ "AsyncSelect",
6410
+ "TagInput"
6703
6411
  ],
6704
6412
  "example": "import {\n Select,\n SelectContent,\n SelectGroup,\n SelectItem,\n SelectLabel,\n SelectSeparator,\n SelectTrigger,\n SelectValue,\n} from \"@godxjp/ui/data-entry\";\nimport { Text } from \"@godxjp/ui/general\";\n// as=\"span\" on both: an option row is a phrasing context, so a <div> would be invalid HTML.\nimport { Flex } from \"@godxjp/ui/layout\";\n\n// ── 1. Data-driven (Ant-style) — static list, no search ──────────────────────\nexport function StatusSelect({ value, onChange }) {\n return (\n <Select\n value={value}\n onValueChange={onChange}\n options={[\n { value: \"draft\", label: \"Draft\" },\n { value: \"sent\", label: \"Sent\" },\n { value: \"paid\", label: \"Paid\" },\n { value: \"overdue\", label: \"Overdue\" },\n ]}\n placeholder=\"Select status\"\n name=\"status\"\n id=\"status\"\n />\n );\n}\n\n// ── 2. Data-driven, searchable static list with groups ────────────────────────\nexport function CurrencySelect({ value, onChange }) {\n return (\n <Select\n value={value}\n onValueChange={onChange}\n showSearch\n options={[\n { value: \"JPY\", label: \"Japanese Yen\", group: \"Asia\" },\n { value: \"VND\", label: \"Vietnamese Dong\", group: \"Asia\" },\n { value: \"EUR\", label: \"Euro\", group: \"Europe\" },\n { value: \"GBP\", label: \"Pound Sterling\", group: \"Europe\" },\n ]}\n placeholder=\"Select currency\"\n searchPlaceholder=\"Search currencies…\"\n name=\"currency\"\n />\n );\n}\n\n// ── 3. Data-driven, async (loadOptions) ──────────────────────────────────────\nexport function AccountSelect({ value, onChange, selectedLabel }) {\n async function loadOptions({ query, page }) {\n const res = await fetch(`/api/accounts?q=${query}&page=${page}`);\n const json = await res.json();\n return { options: json.data, hasMore: json.hasMore };\n }\n return (\n <Select\n value={value}\n onValueChange={onChange}\n loadOptions={loadOptions}\n selectedLabel={selectedLabel}\n placeholder=\"Search accounts…\"\n renderOption={(opt) => (\n <Flex as=\"span\" align=\"center\" gap=\"xs\">\n <Text as=\"span\" tone=\"muted\" mono>\n {opt.value}\n </Text>\n {opt.label}\n </Flex>\n )}\n name=\"account_id\"\n />\n );\n}\n\n// ── 4. Compound API — custom trigger content ──────────────────────────────────\nexport function PrioritySelect({ value, onValueChange }) {\n return (\n <Select value={value} onValueChange={onValueChange}>\n <SelectTrigger size=\"sm\" id=\"priority\">\n <SelectValue placeholder=\"Priority\" />\n </SelectTrigger>\n <SelectContent>\n <SelectGroup>\n <SelectLabel>Urgency</SelectLabel>\n <SelectItem value=\"high\">High</SelectItem>\n <SelectItem value=\"medium\">Medium</SelectItem>\n </SelectGroup>\n <SelectSeparator />\n <SelectItem value=\"low\">Low</SelectItem>\n </SelectContent>\n </Select>\n );\n}",
6705
6413
  "group": "data-entry",
@@ -6832,7 +6540,7 @@
6832
6540
  "type": "\"full\" | \"auto\" | \"bounded\""
6833
6541
  },
6834
6542
  {
6835
- "description": "Searchable mode only. Controlled popover open state (uncontrolled by default). Pair with onOpenChange.",
6543
+ "description": "Searchable mode only. Controlled popover open state (uncontrolled by default). Pair with onOpenChange. With `mode=\"tags\"`, `open={false}` (antd's spelling of \"a tags field whose list never opens\") renders the INLINE chip field instead — no popup, no combobox: type + Enter or a `tokenSeparators` character adds a tag, Backspace on an empty draft drops the last, a pasted \"a, b, c\" becomes three tags, the draft commits on blur, and `name` submits the tags comma-joined in one hidden input. `onValueChange` then receives the tag array only. This is what the retired `TagInput` was (v32 #1223).",
6836
6544
  "name": "open",
6837
6545
  "type": "boolean"
6838
6546
  },
@@ -7060,18 +6768,18 @@
7060
6768
  ],
7061
6769
  "related": [
7062
6770
  "Segmented — the same choice when the option set is small and worth showing at once. Select hides its options behind a trigger; Segmented lays them out, which reads better for 2-4 mutually exclusive options.",
7063
- "SearchSelect — the combobox engine Select delegates to when showSearch=true or loadOptions is set. Prefer Select with showSearch instead of reaching for SearchSelect directly (SearchSelect is now deprecated as a public API).",
6771
+ "Select showSearch / loadOptions — the searchable combobox is a MODE of this component (an internal engine, not a public SearchSelect export); never build your own.",
7064
6772
  "TreeSelect — use when options are hierarchical (parent/child tree). Not a drop-in for Select; has expand/collapse and a separate treeData prop.",
7065
6773
  "Select with showSearch — use Select (with the `showSearch` prop) for typeahead/autocomplete lookup patterns instead of the removed Autocomplete component.",
7066
6774
  "RadioGroup — use instead of Select when there are 2-4 mutually exclusive choices that must all be visible at once without opening a popover.",
7067
- "Combobox (if present) — compound cmdk-powered combobox for free-text + suggestion; Select is for strict value lists only."
6775
+ "Command — the cmdk-powered list for free-text command search (a palette, a slash menu); a value-picking combobox is Select showSearch — there is no Combobox component."
7068
6776
  ],
7069
6777
  "rules": [
7070
6778
  3,
7071
6779
  6,
7072
6780
  23
7073
6781
  ],
7074
- "storyPath": "data-entry/Select.stories.tsx",
6782
+ "storyPath": "data-entry/select.tsx",
7075
6783
  "subParts": [
7076
6784
  "SelectContent",
7077
6785
  "SelectGroup",
@@ -7085,6 +6793,7 @@
7085
6793
  ],
7086
6794
  "tagline": "Polymorphic single-select: pass options/loadOptions for the data-driven (Ant-style) API, or compose sub-parts manually — never use a raw <select>.",
7087
6795
  "usage": [
6796
+ "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.",
7088
6797
  "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.",
7089
6798
  "DO pass name= on the data-driven Select so the value is submitted with a native form or Inertia useForm. Without name= the value is React-only and will not appear in form data.",
7090
6799
  "NAMING A SELECT WITH NO VISIBLE LABEL: put `aria-label` on `<Select>`, NOT on `<SelectTrigger>`. Both render the identical button attribute — Select forwards its name down through SelectFieldA11yContext and the trigger writes last — but only the root spelling also names the react-aria field, and a root that cannot see a name warns once per render (gh#869). Inside a FormField or Field, pass nothing: the label id reaches both levels on its own. `aria-labelledby` pointing at your own element works on either level and is the right spelling when a visible heading already names the control.",
@@ -7181,7 +6890,7 @@
7181
6890
  "RadioGroup — use when the user must choose exactly one option from 2–4 mutually exclusive values; Switch is only appropriate for a single on/off boolean."
7182
6891
  ],
7183
6892
  "rules": [],
7184
- "storyPath": "data-entry/Switch.stories.tsx",
6893
+ "storyPath": "data-entry/switch.tsx",
7185
6894
  "tagline": "Toggle switch (bare), on react-aria-components. For a labelled row use Field. `role=\"switch\"` is the real `<input>`; the painted track is the `<label>` around it, and that is where `data-state`/`data-size` live.",
7186
6895
  "usage": [
7187
6896
  "DO use Switch (bare) only when you are building a custom inline toggle without a visible label — e.g., a DataTable row action column. Always pair it with a <Label htmlFor={id}> placed adjacent in the DOM; never leave it label-less for screen readers.",
@@ -7304,7 +7013,7 @@
7304
7013
  "Select with showSearch or SearchSelect — if the multi-line field is actually a tag/token input or a constrained lookup, prefer Select (showSearch) or SearchSelect over a Textarea that the user types into freely."
7305
7014
  ],
7306
7015
  "rules": [],
7307
- "storyPath": "data-entry/Textarea.stories.tsx",
7016
+ "storyPath": "data-entry/textarea.tsx",
7308
7017
  "tagline": "Styled wrapper around native <textarea>. Pair with FormField for labelled fields. For a Markdown body (wiki page, issue description, mail) use the sibling package `@godxjp/editor` `MarkdownEditor` — this Textarea plus a formatting toolbar, preview through `@godxjp/markdown`, and paste/drop upload through a host `upload` function (gh#1109).",
7309
7018
  "usage": [
7310
7019
  "DO reach for `variant=\"ghost\"` ONLY when a parent surface already draws the box and owns focus — the composer Card, an inline edit cell. A standalone field keeps the default: without its own border it reads as plain text, not something you can type into.",
@@ -7349,11 +7058,11 @@
7349
7058
  "Checkbox — the most common bare-Label partner; pair with Label via shared useId() id/htmlFor when Field's layout is too heavy."
7350
7059
  ],
7351
7060
  "rules": [],
7352
- "storyPath": "data-entry/Label.stories.tsx",
7353
- "tagline": "Styled Radix Label; use htmlFor to associate with a control.",
7061
+ "storyPath": "data-entry/label.tsx",
7062
+ "tagline": "Styled React Aria Label; use htmlFor to associate with a control.",
7354
7063
  "usage": [
7355
7064
  "DO: always pass `htmlFor` matching the `id` of the associated control — this is the entire purpose of the component. Without it, clicking the label text does NOT focus or toggle the control, breaking a11y and UX.",
7356
- "DO: import from `@godxjp/ui/data-entry` (not shadcn or Radix directly). The godx-ui Label extends Radix's LabelPrimitive with `data-slot=\"label\"`, `select-none`, and `group-data-[disabled]` opacity-50 — hand-rolling a `<label>` loses all of these.",
7065
+ "DO: import from `@godxjp/ui/data-entry` (not shadcn or a primitive library directly). The godx-ui Label extends React Aria's Label with `data-slot=\"label\"`, `select-none`, and `group-data-[disabled]` opacity-50 — hand-rolling a `<label>` loses all of these.",
7357
7066
  "DON'T: use Label as a standalone visible heading or section title. It is a form-control association primitive. For page/section headings use semantic HTML (`<h2>`, etc.) or a typography class instead.",
7358
7067
  "DON'T: wrap Label around a control that is already labelled internally. FormField, Field, and CheckboxGroup all render Label internally — adding a second Label creates a duplicate association and redundant screen-reader announcement.",
7359
7068
  "DO: pair Label with Checkbox or Switch when NOT using the compound wrapper (Field). In that case generate the shared id with `React.useId()` and pass it to both `id` on the control and `htmlFor` on Label.",
@@ -7418,7 +7127,7 @@
7418
7127
  "Field — the internal layout primitive (control slot + Label + description) that Checkbox.Group renders per item. Use it directly only when you need a one-off labelled checkbox or radio item outside of a group, and you want the consistent indent/description layout without the group's value-management overhead."
7419
7128
  ],
7420
7129
  "rules": [],
7421
- "storyPath": "data-entry/Checkbox.stories.tsx",
7130
+ "storyPath": "data-entry/checkbox.tsx",
7422
7131
  "subParts": [
7423
7132
  "CheckboxVisual"
7424
7133
  ],
@@ -7511,14 +7220,14 @@
7511
7220
  "rules": [
7512
7221
  23
7513
7222
  ],
7514
- "storyPath": "data-entry/RadioGroup.stories.tsx",
7223
+ "storyPath": "data-entry/radio-group.tsx",
7515
7224
  "subParts": [
7516
7225
  "RadioGroupRoot"
7517
7226
  ],
7518
7227
  "tagline": "Radio group accepting an options array or RadioItem children.",
7519
7228
  "usage": [
7520
7229
  "DO use the `options` prop for the data-driven path: pass `{ label, value, disabled?, description? }[]` and RadioGroup renders every option as a correctly labelled Field automatically — never hand-roll Radio.Item + Label pairs in a loop yourself.",
7521
- "DO provide `name` whenever the group lives inside an HTML form: Radix renders a hidden `<input name={name}>` carrying the selected string value, making the field natively form-submittable without a separate hidden input.",
7230
+ "DO provide `name` whenever the group lives inside an HTML form: the group renders a hidden `<input name={name}>` carrying the selected string value, making the field natively form-submittable without a separate hidden input.",
7522
7231
  "DO use controlled mode (`value` + `onValueChange`) for any form managed by useForm or a state manager. Use `defaultValue` only for truly uncontrolled UI where you never need to read the value in code.",
7523
7232
  "DO NOT reach for children / manual composition unless the options list is dynamic-JSX (e.g. each item needs a custom rendered label with an icon). When you do compose children manually, wrap each Radio.Item in a Field — rendering a bare Radio.Item without Field skips the label and breaks a11y.",
7524
7233
  "DO NOT use RadioGroup when the user may select zero or multiple items — that is CheckboxGroup. RadioGroup enforces exactly one selection at all times (or none before first interaction when uncontrolled).",
@@ -7785,7 +7494,7 @@
7785
7494
  13,
7786
7495
  31
7787
7496
  ],
7788
- "storyPath": "data-entry/DatePicker.stories.tsx",
7497
+ "storyPath": "data-entry/date-picker.tsx",
7789
7498
  "tagline": "ONE date control: `picker` sets the granularity (day · week · month · quarter · year), `range` makes it a two-endpoint field, `multiple` a set. A real typeable input holds the value and submits ISO-8601 at the picker's own precision; the panel is the visual-only affordance.",
7790
7499
  "usage": [
7791
7500
  "ONE component covers every date-shaped field. `picker` is the granularity axis, `range` and `multiple` are the cardinality axis, and they compose: `<DatePicker range picker=\"month\" />` is a month range.",
@@ -7853,7 +7562,7 @@
7853
7562
  23,
7854
7563
  3
7855
7564
  ],
7856
- "storyPath": "feedback/Dialog.stories.tsx",
7565
+ "storyPath": "feedback/dialog.tsx",
7857
7566
  "subParts": [
7858
7567
  "DialogAction",
7859
7568
  "DialogBody",
@@ -7875,7 +7584,7 @@
7875
7584
  "DO reach for `variant=\"destructive\"` for a dangerous confirmation, INCLUDING one that needs a form inside it (a required reason, a typed challenge). It gives the alertdialog role and the non-dismissable scrim without giving up the freeform body. The separate `AlertDialog*` parts remain for existing code and still work, but they are the older way in.",
7876
7585
  "DO leave `variant` alone for everything that is not destructive. The prop is a danger level, not a colour knob: to tint only the header band use `DialogHeader tone`, which is a separate axis with seven values.",
7877
7586
  "DO always control open state via `open` + `onOpenChange`. Dialog has no uncontrolled shortcut — omitting `open` means the trigger alone drives state, which is fine for simple trigger-only cases, but any async submission flow must use controlled state so you can hold the dialog open while `pending=true` and close it only on success.",
7878
- "DO include `DialogHeader` with `DialogTitle` (and optionally `DialogDescription`) inside every `DialogContent`. Radix requires an accessible title for screen readers; omitting it triggers a console warning and breaks a11y.",
7587
+ "DO include `DialogHeader` with `DialogTitle` (and optionally `DialogDescription`) inside every `DialogContent`. a dialog needs an accessible title for screen readers; omitting it triggers a console warning and breaks a11y.",
7879
7588
  "DO wrap tall/scrolling content in `DialogBody` (the ring-safe scroll slot, max-height ~60vh). It insets the content to match the dialog padding so a full-width control's focus ring never clips against the scroll container — mirror of SheetBody.",
7880
7589
  "DO set `modal={false}` when the user must keep working on the page behind an open dialog (edit a list while a payment or detail dialog stays open). Control `open` yourself: an outside press no longer closes it, so give it a visible close action. Escape closes it only while focus is inside the dialog.",
7881
7590
  "DO pass `width` on `DialogContent` for a wide dialog (an overview grid, a comparison): `<DialogContent width=\"60rem\">` (number = px, string = any CSS length; antd Modal `width`, same vocabulary as SheetContent `width`). It is capped at the viewport minus --dialog-viewport-inset, so it never leaves a phone screen. Default 32rem (--dialog-width-default). Do NOT switch to a full-width Sheet to get room (gh#1174).",
@@ -7970,8 +7679,9 @@
7970
7679
  23,
7971
7680
  3
7972
7681
  ],
7973
- "storyPath": "feedback/AlertDialog.stories.tsx",
7682
+ "storyPath": "feedback/alert-dialog.tsx",
7974
7683
  "subParts": [
7684
+ "AlertDialogRoot",
7975
7685
  "AlertDialogAction",
7976
7686
  "AlertDialogCancel",
7977
7687
  "AlertDialogContent",
@@ -7985,6 +7695,12 @@
7985
7695
  ],
7986
7696
  "tagline": "Canonical modal confirmation flow (destructive / high-stakes decisions). Preserves confirm semantics with `role=\"alertdialog\"` and built-in cancel/confirm handling.",
7987
7697
  "usage": [
7698
+ "COMPOUND (AlertDialogRoot): Reach for the flat `AlertDialog` preset FIRST — it already covers title/description/confirm/cancel, the typed `challenge`, `stepUp` re-auth and `pending`. Use `AlertDialogRoot` only when the confirm body needs content the preset does not model (an impact summary, a diff, a nested list).",
7699
+ "COMPOUND (AlertDialogRoot): DO name it `AlertDialogRoot`, not `AlertDialog` — the `AlertDialog` export is the flat preset and takes a completely different (non-compound) prop API.",
7700
+ "COMPOUND (AlertDialogRoot): DO include `AlertDialogHeader` with `AlertDialogTitle` inside every `AlertDialogContent` — `role=alertdialog` needs an accessible title; omitting it warns in the console and breaks screen-reader announcement. `AlertDialogHeader` also takes the prop-driven `title`/`subtitle`/`extra`/`tone` form, where `subtitle` renders the `AlertDialogDescription`.",
7701
+ "COMPOUND (AlertDialogRoot): DO portal explicitly: `AlertDialogContent` does NOT self-portal (unlike `DialogContent`). Wrap it in `AlertDialogPortal` with a sibling `AlertDialogOverlay`, or the scrim and stacking context are wrong.",
7702
+ "COMPOUND (AlertDialogRoot): DO use `AlertDialogAction` / `AlertDialogCancel` for the footer buttons — they carry the button styling AND the close semantics. Do not wrap them in `asChild` `<Button variant=…>`: the Root's button classes and the child's would both land on the element and the variant would not win.",
7703
+ "COMPOUND (AlertDialogRoot): DO NOT import `@radix-ui/react-alert-dialog` directly in a consumer app. Everything the compound needs is exported from `@godxjp/ui/feedback`.",
7988
7704
  "Use `AlertDialog` for destructive/irreversible actions (delete, void, unpublish, archive, etc.).",
7989
7705
  "Use `confirmPhrase`/`challenge` for high-friction operations (e.g. typing an org slug) to reduce accidental confirmation — both force the destructive shape: the destructive confirm button plus a leading status glyph beside the title, which is Ant Design `Modal.confirm` parity. The header surface stays UNTINTED — antd signals danger with the glyph and never tints a modal header (its soft `colorErrorBg` belongs to Alert/Tag/message). To tint the band anyway, set `DialogHeader tone` yourself; it is a separate seven-value axis the preset no longer imposes.",
7990
7706
  "Pass `stepUp` for a passkey/2FA re-auth gate that must resolve truthy before `onConfirm` runs (refunds, org deletion).",
@@ -7997,58 +7713,6 @@
7997
7713
  "Destructive batch operations that should remain modal and explicit until action is intentionally confirmed."
7998
7714
  ]
7999
7715
  },
8000
- {
8001
- "example": "import {\n AlertDialogRoot, AlertDialogTrigger, AlertDialogPortal, AlertDialogOverlay,\n AlertDialogContent, AlertDialogHeader, AlertDialogTitle, AlertDialogDescription,\n AlertDialogFooter, AlertDialogAction, AlertDialogCancel,\n} from \"@godxjp/ui/feedback\";\nimport { Button } from \"@godxjp/ui/general\";\n\nfunction ConfirmSettlement() {\n return (\n <AlertDialogRoot>\n <AlertDialogTrigger asChild><Button variant=\"outline\" size=\"sm\">支払を確定</Button></AlertDialogTrigger>\n <AlertDialogPortal>\n <AlertDialogOverlay />\n <AlertDialogContent className=\"max-w-md\">\n <AlertDialogHeader>\n <AlertDialogTitle>2026年7月分の支払を確定しますか?</AlertDialogTitle>\n <AlertDialogDescription>確定すると振込データが生成されます。</AlertDialogDescription>\n </AlertDialogHeader>\n {/* freeform impact summary goes here */}\n <AlertDialogFooter>\n <AlertDialogCancel>戻る</AlertDialogCancel>\n <AlertDialogAction>確定する</AlertDialogAction>\n </AlertDialogFooter>\n </AlertDialogContent>\n </AlertDialogPortal>\n </AlertDialogRoot>\n );\n}",
8002
- "group": "feedback",
8003
- "importPath": "@godxjp/ui/feedback",
8004
- "name": "AlertDialogRoot",
8005
- "props": [
8006
- {
8007
- "description": "Controlled open state.",
8008
- "name": "open",
8009
- "type": "boolean"
8010
- },
8011
- {
8012
- "description": "Uncontrolled initial open state (use with AlertDialogTrigger).",
8013
- "name": "defaultOpen",
8014
- "type": "boolean"
8015
- },
8016
- {
8017
- "description": "Open-state change handler.",
8018
- "name": "onOpenChange",
8019
- "type": "(open: boolean) => void"
8020
- },
8021
- {
8022
- "description": "The trigger and the portalled alertdialog parts.",
8023
- "name": "children",
8024
- "type": "React.ReactNode"
8025
- }
8026
- ],
8027
- "related": [
8028
- "AlertDialog — the flat preset. Prefer it; it is the canonical destructive-confirm recipe and needs no compound markup.",
8029
- "Dialog — compound modal for form-style, non-destructive flows. `role=dialog`, dismissible on outside click.",
8030
- "AlertDialogHeader — the header band; `tone` tints only the background (default | success | warning | destructive | info | muted | neutral)."
8031
- ],
8032
- "rules": [
8033
- 23,
8034
- 3
8035
- ],
8036
- "storyPath": "feedback/AlertDialog.stories.tsx",
8037
- "tagline": "Compound alertdialog Root — the role=\"alertdialog\" mirror of Dialog's root. Wraps Radix AlertDialog.Root and supplies the context AlertDialogTitle/AlertDialogDescription/AlertDialogAction/AlertDialogCancel read. Parts: AlertDialogTrigger/AlertDialogPortal/AlertDialogOverlay/AlertDialogContent/AlertDialogHeader/AlertDialogFooter/AlertDialogTitle/AlertDialogDescription/AlertDialogAction/AlertDialogCancel.",
8038
- "usage": [
8039
- "Reach for the flat `AlertDialog` preset FIRST — it already covers title/description/confirm/cancel, the typed `challenge`, `stepUp` re-auth and `pending`. Use `AlertDialogRoot` only when the confirm body needs content the preset does not model (an impact summary, a diff, a nested list).",
8040
- "DO name it `AlertDialogRoot`, not `AlertDialog` — the `AlertDialog` export is the flat preset and takes a completely different (non-compound) prop API.",
8041
- "DO include `AlertDialogHeader` with `AlertDialogTitle` inside every `AlertDialogContent` — Radix requires an accessible title for `role=alertdialog`; omitting it warns in the console and breaks screen-reader announcement. `AlertDialogHeader` also takes the prop-driven `title`/`subtitle`/`extra`/`tone` form, where `subtitle` renders the `AlertDialogDescription`.",
8042
- "DO portal explicitly: `AlertDialogContent` does NOT self-portal (unlike `DialogContent`). Wrap it in `AlertDialogPortal` with a sibling `AlertDialogOverlay`, or the scrim and stacking context are wrong.",
8043
- "DO use `AlertDialogAction` / `AlertDialogCancel` for the footer buttons — they carry the button styling AND the Radix close semantics. Do not wrap them in `asChild` `<Button variant=…>`: the Root's button classes and the child's would both land on the element and the variant would not win.",
8044
- "DO NOT import `@radix-ui/react-alert-dialog` directly in a consumer app. Everything the compound needs is exported from `@godxjp/ui/feedback`."
8045
- ],
8046
- "useCases": [
8047
- "Confirm-with-impact-summary — a destructive confirmation that must show what will be affected (a list of 3 downstream jobs, a table of invoices) before the user commits. The flat preset only takes a string `description`.",
8048
- "Batch-close confirmation whose body renders a rich breakdown (counts, totals, per-tenant rows) alongside the standard confirm/cancel pair.",
8049
- "Any confirmation that must keep `role=alertdialog` semantics (focus trap, no dismiss-on-outside-click) but needs freeform children — use Dialog only when the flow is non-destructive."
8050
- ]
8051
- },
8052
7716
  {
8053
7717
  "example": "import { Sheet, SheetTrigger, SheetContent, SheetHeader, SheetTitle } from \"@godxjp/ui/feedback\";\nimport { Button } from \"@godxjp/ui/general\";\n\n<Sheet open={open} onOpenChange={setOpen}>\n <SheetTrigger asChild><Button variant=\"outline\" size=\"sm\">絞り込み</Button></SheetTrigger>\n <SheetContent side=\"right\">\n <SheetHeader><SheetTitle>フィルター設定</SheetTitle></SheetHeader>\n {/* filter fields */}\n </SheetContent>\n</Sheet>",
8054
7718
  "group": "feedback",
@@ -8077,7 +7741,7 @@
8077
7741
  "type": "number | string"
8078
7742
  },
8079
7743
  {
8080
- "description": "On SheetContent (Radix name, same as DialogContent): fires on every close before focus returns to the trigger. Call event.preventDefault() and focus your own target (e.g. the new page's heading after a drawer navigation) to send focus there instead (gh#1134).",
7744
+ "description": "On SheetContent (same name as on DialogContent): fires on every close before focus returns to the trigger. Call event.preventDefault() and focus your own target (e.g. the new page's heading after a drawer navigation) to send focus there instead (gh#1134).",
8081
7745
  "name": "onCloseAutoFocus",
8082
7746
  "type": "(event: Event) => void"
8083
7747
  },
@@ -8102,7 +7766,7 @@
8102
7766
  "rules": [
8103
7767
  3
8104
7768
  ],
8105
- "storyPath": "feedback/Sheet.stories.tsx",
7769
+ "storyPath": "feedback/sheet.tsx",
8106
7770
  "subParts": [
8107
7771
  "SheetBody",
8108
7772
  "SheetClose",
@@ -8115,20 +7779,20 @@
8115
7779
  "SheetTitle",
8116
7780
  "SheetTrigger"
8117
7781
  ],
8118
- "tagline": "Side-panel drawer / responsive detail panel (Radix Dialog). Parts: Sheet/SheetTrigger/SheetContent(side=right|left|top|bottom, responsive=auto|side|bottom)/SheetHeader/SheetBody/SheetTitle/SheetFooter.",
7782
+ "tagline": "Side-panel drawer / responsive detail panel (React Aria modal dialog). Parts: Sheet/SheetTrigger/SheetContent(side=right|left|top|bottom, responsive=auto|side|bottom)/SheetHeader/SheetBody/SheetTitle/SheetFooter.",
8119
7783
  "usage": [
8120
7784
  "DO build the panel with SheetHeader (pass `title`/`subtitle`/`extra`/`tone` OR children) > SheetBody (scrollable, ring-safe) > SheetFooter (pinned). SheetTitle is required for a11y — the `title` prop renders it for you. Never skip the title.",
8121
7785
  "DO set `width` on SheetContent for a wider/narrower panel (e.g. width={480}); it caps at the viewport so small screens still get a full-width panel.",
8122
7786
  "DO use responsive=\"auto\" for a record detail panel / drawer that must be a desktop side panel and a mobile bottom sheet — ONE <Sheet>, no page-local media query. The breakpoint is the --sheet-responsive-breakpoint-width token, so a service moves the line once for every overlay. `width` is ignored while the bottom presentation is active (a bottom sheet is full-bleed).",
8123
7787
  "DON'T hardcode an overlay breakpoint in app code (useMediaQuery(\"(max-width: 390px)\")). If a composite must swap a desktop surface for a mobile sheet (a Popover→Sheet switcher, for example), call the exported useSheetResponsiveMode(\"auto\") hook so it reads the same themeable token.",
8124
- "DO use all named sub-parts in order: Sheet (root) > SheetTrigger (opener) > SheetContent (panel) > SheetHeader > SheetTitle (required for a11y — maps to Radix DialogPrimitive.Title, announced as the accessible name) > optional SheetDescription > body content > SheetFooter. Never skip SheetTitle inside an open SheetContent.",
7788
+ "DO use all named sub-parts in order: Sheet (root) > SheetTrigger (opener) > SheetContent (panel) > SheetHeader > SheetTitle (required for a11y — it is the dialog's heading, announced as the accessible name) > optional SheetDescription > body content > SheetFooter. Never skip SheetTitle inside an open SheetContent.",
8125
7789
  "DO control state explicitly with open + onOpenChange on Sheet root when you need to close programmatically (e.g. after form submit). Uncontrolled (no props) works for simple trigger-only cases but gives you no hook to reset form state on close.",
8126
7790
  "DO use SheetTrigger asChild to wrap a Button or other interactive element — this avoids a nested <button> in the DOM. Never render a raw <button> as a direct child of SheetTrigger.",
8127
7791
  "DO wrap a long/scrolling body in SheetBody (between SheetHeader and a pinned SheetFooter). It is the ring-safe scroll slot: a hand-rolled <div className='overflow-y-auto'> clips the 3px focus ring of a full-width Input/Select at the scroll edges — SheetBody insets the content so the ring never clips.",
8128
7792
  "DO use SheetFooter (renders at the bottom via mt-auto, symmetric 16/24 padding, full-bleed top border) for primary/cancel action Buttons. Never float action Buttons inside the body — they will not stick to the panel bottom.",
8129
7793
  "DON'T set showCloseButton={false} on SheetContent unless you provide your own SheetClose element; omitting both leaves users with no keyboard-accessible close path and breaks a11y.",
8130
7794
  "DO set `modal={false}` on Sheet when the user must keep working on the page behind the open panel (edit a list while a detail panel stays open). Control `open` yourself: an outside press no longer closes it, so keep the ✕ or a footer close action. Escape closes it only while focus is inside the panel.",
8131
- "DON'T put a Sheet inside a Dialog (nested Radix portals conflict). If you need a slide-over triggered from within a modal, close the Dialog first, then open the Sheet."
7795
+ "DON'T put a Sheet inside a Dialog (nested modal overlays conflict). If you need a slide-over triggered from within a modal, close the Dialog first, then open the Sheet."
8132
7796
  ],
8133
7797
  "useCases": [
8134
7798
  "Filter/search panel: slide in from the right with filter FormFields (Select, `DatePicker range`, CheckboxGroup) that affect a DataTable — preferred over a Dialog because filters do not require confirmation and benefit from seeing the table behind the overlay.",
@@ -8140,6 +7804,10 @@
8140
7804
  ]
8141
7805
  },
8142
7806
  {
7807
+ "absorbed": [
7808
+ "Banner",
7809
+ "Callout"
7810
+ ],
8143
7811
  "example": "import { Alert, AlertTitle, AlertDescription } from \"@godxjp/ui/feedback\";\n\n<Alert tone=\"warning\">\n <AlertTitle>3 件の打刻漏れがあります</AlertTitle>\n <AlertDescription>本日中に確認してください。</AlertDescription>\n</Alert>",
8144
7812
  "group": "feedback",
8145
7813
  "importPath": "@godxjp/ui/feedback",
@@ -8147,9 +7815,15 @@
8147
7815
  "props": [
8148
7816
  {
8149
7817
  "defaultValue": "\"default\"",
8150
- "description": "STRUCTURAL axis, orthogonal to `tone` (which owns colour + icon): \"default\" is the inline rounded card; \"banner\" is the full-bleed attention strip — prefer the `Banner` export, which fixes this axis.",
7818
+ "description": "STRUCTURAL axis, orthogonal to `tone` (which owns colour + icon): \"default\" is the inline rounded card; \"banner\" is the full-bleed page/shell attention strip (square corners, page-gutter inset, actions wrap below 640px); \"callout\" is the aside INSIDE a document body — `role=\"note\"`, never a live region, `kind` picks the admonition. v32 folded the former `Banner` and `Callout` exports into these values.",
8151
7819
  "name": "variant",
8152
- "type": "\"default\" | \"banner\""
7820
+ "type": "\"default\" | \"banner\" | \"callout\""
7821
+ },
7822
+ {
7823
+ "defaultValue": "\"note\"",
7824
+ "description": "`variant=\"callout\"` only. GitHub/Obsidian admonition preset; resolves the tone AND the leading glyph. A preset, not a second colour axis — `tone` and `icon` still override per instance. Ignored by the other variants.",
7825
+ "name": "kind",
7826
+ "type": "\"note\" | \"tip\" | \"important\" | \"warning\" | \"caution\""
8153
7827
  },
8154
7828
  {
8155
7829
  "description": "Renders an × dismiss button when provided.",
@@ -8162,19 +7836,21 @@
8162
7836
  "type": "LucideIcon | false"
8163
7837
  },
8164
7838
  {
8165
- "description": "Semantic tone driving the colour + leading icon.",
7839
+ "description": "Semantic tone driving the colour + leading icon AND live-region politeness (\"destructive\"/\"warning\" announce assertively via role=\"alert\"; every other tone politely via role=\"status\"). Under `variant=\"callout\"` tone drives colour only — a callout is never a live region.",
8166
7840
  "name": "tone",
8167
- "type": "\"success\" | \"warning\" | \"destructive\" | \"info\" | \"neutral\""
7841
+ "type": "\"default\" | \"success\" | \"warning\" | \"destructive\" | \"info\" | \"muted\" | \"neutral\""
8168
7842
  }
8169
7843
  ],
8170
7844
  "related": [
8171
7845
  "Toaster — use for transient, auto-dismissing feedback ('Record saved', 'Deleted'). Alert is for persistent page-scoped banners; Toaster is for fire-and-forget notifications triggered by toast() from sonner.",
8172
7846
  "AlertMutationFeedback — use when you want inline success/error feedback tightly coupled to a form mutation's state (renders inline below the submit button). Alert requires you to manage show/hide state yourself.",
8173
7847
  "DataState — use for full query lifecycle (loading skeleton + empty state + error) inside a data-fetching section. Alert.QueryError is the error sub-component DataState uses internally; prefer DataState when you also need the loading/empty states.",
7848
+ "ErrorSurface — a whole-page semantic exception state (403/404/5xx) that REPLACES the page. An `Alert` (any variant) annotates a page that still works.",
7849
+ "Prose — the typographic column a `variant=\"callout\"` normally sits inside; the callout's block margin is tuned to that rhythm.",
8174
7850
  "EmptyState — use for the zero-data case inside a list or table section, not for errors or warnings. Alert is for status messages; EmptyState is for the absence of data."
8175
7851
  ],
8176
7852
  "rules": [],
8177
- "storyPath": "feedback/Alert.stories.tsx",
7853
+ "storyPath": "feedback/alert.tsx",
8178
7854
  "subParts": [
8179
7855
  "AlertActions",
8180
7856
  "AlertBase",
@@ -8184,7 +7860,7 @@
8184
7860
  "AlertQueryError",
8185
7861
  "AlertTitle"
8186
7862
  ],
8187
- "tagline": "Inline alert banner with variant-aware icon + optional dismiss. Parts: Alert/AlertTitle/AlertDescription/AlertActions/AlertQueryError.",
7863
+ "tagline": "Status message with a tone icon and optional dismiss, in three presentations: inline card, full-bleed page/shell strip (`variant=\"banner\"`), or a non-live aside inside a document (`variant=\"callout\"`, GitHub `> [!NOTE]`). Parts: Alert.Title/Description/Content/Actions/QueryError.",
8188
7864
  "usage": [
8189
7865
  "ANATOMY (positions are fixed — never re-lay-them-out): Alert is ONE horizontal row — a SINGLE leading tone icon at the inline-start (top-aligned to the first text line, auto-selected by `tone`, never two icons), then the text body (Title/Description), then `<Alert.Actions>` in a trailing-RIGHT column (≥sm), and the dismiss × pinned to the TOP-RIGHT corner (rendered by `onDismiss`). DON'T stack these vertically, DON'T make the action a full-width bar under the text, DON'T center the × at the bottom, DON'T add a second icon — that hand-rolled vertical banner is the #1 Alert mistake.",
8190
7866
  "DO compose text as `<Alert.Title>` + `<Alert.Description>` — they stack vertically inside the body (the Example below is canonical). When you add `<Alert.Actions>`, the body becomes a two-column grid (text | actions) at ≥sm; group multi-part text in `<Alert.Content>` so it occupies the text column as one block: `<Alert tone=\"destructive\"><Alert.Content><Alert.Title>Error</Alert.Title><Alert.Description>{msg}</Alert.Description></Alert.Content><Alert.Actions><Button …/></Alert.Actions></Alert>`.",
@@ -8193,6 +7869,8 @@
8193
7869
  "DON'T pass raw action elements directly as top-level children of `<Alert>` without wrapping them in `<Alert.Actions>` — the layout slot only activates correctly via the `data-slot=\"alert-actions\"` wrapper.",
8194
7870
  "DON'T hand-roll a dismiss ✕ button — pass `onDismiss` to `<Alert>` and the component renders its own accessible dismiss button with `aria-label=\"Dismiss\"`. The `onDismiss` handler may return a Promise.",
8195
7871
  "DON'T suppress the icon with `icon={false}` unless there is a deliberate design reason; the icon is the primary a11y cue for sighted users since the root already carries `role=\"alert\"` for screen readers.",
7872
+ "BANNER (`variant=\"banner\"`): place it FULL-BLEED at the top of the surface it warns about — directly under the Topbar inside AppShell's children for shell-wide attention (past-due subscription, active support session), or as the first child of a page. Its inline inset defaults to the page gutter (--space-page-active-x) and its geometry is owned by the `--banner-*` tokens; never fake a strip with `className` overrides. At most ONE per surface.",
7873
+ "CALLOUT (`variant=\"callout\"`): for content that is PART of the document being read — a docs admonition, a CMS note, a policy caveat. It renders `role=\"note\"` and never announces. Map Markdown `[!NOTE]`/`[!TIP]`/`[!IMPORTANT]`/`[!WARNING]`/`[!CAUTION]` straight onto `kind`. DON'T pass `onDismiss` (prose is not dismissed) and DON'T use a callout for something that just HAPPENED — that is the default or banner variant, which announce.",
8196
7874
  "DO NOT use `Alert` for transient ephemeral feedback (e.g. 'saved successfully'). Use `toast()` from sonner + `<Toaster>` for that. `Alert` is for persistent, page-scoped banners that stay visible until the user acts or dismisses."
8197
7875
  ],
8198
7876
  "useCases": [
@@ -8201,197 +7879,11 @@
8201
7879
  "Success confirmation banner rendered after a bulk-import job completes and the user returns to the list page — `tone=\"success\"` with `Alert.Description` showing the record count imported.",
8202
7880
  "TanStack Query data-fetch failure inside a Card body — use `<Alert.QueryError error={error} onRetry={refetch} />` instead of writing a custom error state.",
8203
7881
  "Informational notice at the top of a settings page when a feature is in beta or requires a plan upgrade — `tone=\"info\"` with a short description and an `Alert.Actions` 'Learn more' link.",
7882
+ "Past-due subscription strip across the console shell — `variant=\"banner\" tone=\"warning\"` with an `Alert.Actions` \"お支払い方法を更新\" Button, rendered above the page slot until billing recovers.",
7883
+ "Docs/handbook admonition inside a prose column — `variant=\"callout\" kind=\"tip\"`; a destructive-consequence note beside a runbook step is `kind=\"caution\"`, still silent on load.",
8204
7884
  "Dismissible billing-overdue notice at the top of the dashboard — `tone=\"destructive\"` with `onDismiss` that sets a session flag so it does not reappear until the next login."
8205
7885
  ]
8206
7886
  },
8207
- {
8208
- "example": "import { Banner } from \"@godxjp/ui/feedback\";\nimport { Button } from \"@godxjp/ui/general\";\n\n<Banner tone=\"warning\">\n <Banner.Content>\n <Banner.Title>お支払いが確認できていません</Banner.Title>\n <Banner.Description>サービスの停止を避けるため、お支払い方法を更新してください。</Banner.Description>\n </Banner.Content>\n <Banner.Actions>\n <Button size=\"sm\" variant=\"outline\">お支払い方法を更新</Button>\n </Banner.Actions>\n</Banner>",
8209
- "group": "feedback",
8210
- "importPath": "@godxjp/ui/feedback",
8211
- "name": "Banner",
8212
- "props": [
8213
- {
8214
- "defaultValue": "\"default\"",
8215
- "description": "Semantic tone driving the surface colour, the default leading icon AND live-region politeness (\"destructive\"/\"warning\" announce assertively via role=\"alert\"; every other tone politely via role=\"status\").",
8216
- "name": "tone",
8217
- "type": "\"default\" | \"success\" | \"warning\" | \"destructive\" | \"info\" | \"muted\" | \"neutral\""
8218
- },
8219
- {
8220
- "description": "Renders the built-in localized dismiss button (top/inline-end). It sits LAST in DOM and focus order: content → actions → dismiss.",
8221
- "name": "onDismiss",
8222
- "type": "() => void | Promise<void>"
8223
- },
8224
- {
8225
- "description": "Override or hide (false) the tone's default leading icon.",
8226
- "name": "icon",
8227
- "type": "LucideIcon | false"
8228
- }
8229
- ],
8230
- "related": [
8231
- "Alert — the SAME primitive in its inline-card presentation; use inside a page section or Card for persistent local feedback. Banner is the full-bleed strip presentation for shell/page-level attention.",
8232
- "Toaster — transient auto-dismissing feedback (toast()). Banner is persistent until acted on or dismissed.",
8233
- "ErrorSurface — a whole-page semantic exception state (403/404/5xx) that REPLACES the page. Banner annotates a page that still works.",
8234
- "PageContainer — Banner sits ABOVE or as the first child of PageContainer, never inside the header slots; the page header's own status/meta belongs in PageContainer's `status` prop."
8235
- ],
8236
- "rules": [],
8237
- "storyPath": "feedback/Banner.stories.tsx",
8238
- "tagline": "Full-bleed page/shell attention strip (past-due subscription, support session, maintenance). Parts: Banner.Title/Description/Content/Actions.",
8239
- "usage": [
8240
- "CANONICAL BANNER CONTRACT: `Banner` is `Alert` with the structural axis fixed to `variant=\"banner\"` — same tone system, same slots, same dismiss/a11y behaviour, but STRIP geometry owned by the `--banner-*` tokens (square corners, hairline block-end rule, page-gutter inline inset). Never fake a banner by putting `className` overrides on an `Alert`, and never hand-roll a coloured div strip.",
8241
- "DO: Place a Banner FULL-BLEED at the top of the surface it warns about — directly under the Topbar inside AppShell's children for shell-wide attention (past-due subscription, active support session), or as the first child of a page for page-scoped notices. Its inline inset defaults to the page gutter (--space-page-active-x) so the text column aligns with page content.",
8242
- "DO: Compose text as `Banner.Title` + `Banner.Description` (group multi-part copy in `Banner.Content` when you add `Banner.Actions`). At >=640px actions sit in a trailing column; below the step they drop onto their own full-width WRAPPING line, so a 390px viewport wraps instead of clipping.",
8243
- "DO: Pass `onDismiss` for dismissible notices — the component renders its own accessible, localized dismiss button. DON'T hand-roll an × Button in `Banner.Actions`.",
8244
- "DON'T: Use Banner for transient feedback ('saved successfully') — that is `toast()` + `<Toaster>`. Banner is persistent and page/shell-scoped, and there should be at most ONE per surface; stack further messages inside the page as inline `Alert`s.",
8245
- "DON'T: Encode DXS business rules here (when past_due shows, who sees a support-session strip) — the app decides WHEN to render; Banner owns only presentation and behaviour."
8246
- ],
8247
- "useCases": [
8248
- "Past-due subscription strip across the console shell — `tone=\"warning\"` with a `Banner.Actions` \"お支払い方法を更新\" Button, rendered above the page slot until billing recovers.",
8249
- "Active support-session indicator — `tone=\"info\"` with the operator name in `Banner.Description` and a \"セッションを終了\" action; not dismissible while the session runs.",
8250
- "Scheduled maintenance notice — `tone=\"neutral\"` with `onDismiss` so the user can clear it for the session.",
8251
- "Read-only / archived-organization mode — `tone=\"muted\"` explaining why every mutation control on the page is disabled.",
8252
- "Degraded-service warning after a partial outage — `tone=\"destructive\"` with a status-page link in `Banner.Actions`."
8253
- ]
8254
- },
8255
- {
8256
- "example": "import { Callout } from \"@godxjp/ui/feedback\";\n\n<Callout kind=\"warning\">\n <Callout.Title>この操作は取り消せません</Callout.Title>\n <Callout.Description>\n リポジトリを削除すると、Issue と Pull Request も一緒に削除されます。\n </Callout.Description>\n</Callout>",
8257
- "group": "feedback",
8258
- "importPath": "@godxjp/ui/feedback",
8259
- "name": "Callout",
8260
- "props": [
8261
- {
8262
- "defaultValue": "\"note\"",
8263
- "description": "GitHub/Obsidian admonition preset; resolves the tone AND the leading glyph. A preset, not a second colour axis — `tone` and `icon` still override per instance.",
8264
- "name": "kind",
8265
- "type": "\"note\" | \"tip\" | \"important\" | \"warning\" | \"caution\""
8266
- },
8267
- {
8268
- "description": "Overrides the tone the `kind` resolves to. Drives surface colour and the leading rail ONLY — unlike Alert, tone never changes live-region politeness here, because a Callout has none.",
8269
- "name": "tone",
8270
- "type": "\"default\" | \"success\" | \"warning\" | \"destructive\" | \"info\" | \"muted\" | \"neutral\""
8271
- },
8272
- {
8273
- "description": "Override or hide (false) the kind's default leading glyph.",
8274
- "name": "icon",
8275
- "type": "LucideIcon | false"
8276
- }
8277
- ],
8278
- "related": [
8279
- "Alert — the SAME primitive in its inline-card presentation, and a LIVE REGION. Use it for an update to the page; Callout is for part of the page.",
8280
- "Banner — the same primitive as the full-bleed page/shell strip, also a live region.",
8281
- "Prose — the typographic column a Callout normally sits inside; the callout's block margin is tuned to that rhythm."
8282
- ],
8283
- "rules": [],
8284
- "storyPath": "feedback/Callout.stories.tsx",
8285
- "tagline": "Static aside INSIDE a document body (docs admonition, CMS note, GitHub `> [!NOTE]`). role=\"note\", never a live region. Parts: Callout.Title/Description/Content/Actions.",
8286
- "usage": [
8287
- "CANONICAL CALLOUT CONTRACT: `Callout` is `Alert` with the structural axis fixed to `variant=\"callout\"` — same tone system, same slots, but ASIDE geometry owned by the `--callout-*` tokens (leading rail, prose insets, block margin) and, uniquely, NO live region. Never fake a callout with `className` on an `Alert`, and never hand-roll a coloured div with a left border.",
8288
- "DO: Use Callout for content that is part of the document the reader is reading — a docs admonition, a note in a CMS article, a caveat inside a policy page. It is rendered with the page, so it must not announce.",
8289
- "DON'T: Use `<Alert role=\"note\">` for this. That worked only because `{...props}` is spread after the computed `role`, which the package never promised; one refactor would have silently restored the live region. A consumer neutralising a component's own semantics is the tell that it is the wrong primitive (gh#765).",
8290
- "DON'T: Reach for Callout to report something that just HAPPENED (a save failed, a session expired). That is an update to the page, not part of it — use `Alert` (inline), `Banner` (page/shell strip) or `toast()`, all of which announce.",
8291
- "DON'T: Pass `onDismiss` — the type excludes it. Prose does not get dismissed; if the reader can remove it, it is an Alert.",
8292
- "MARKDOWN RENDERERS: map the five GitHub types straight onto `kind` — `[!NOTE]`→note, `[!TIP]`→tip, `[!IMPORTANT]`→important, `[!WARNING]`→warning, `[!CAUTION]`→caution. Obsidian's lower-case spelling is the same set. `important` takes the NEUTRAL tone (this system has no purple role); its glyph, not its colour, is what tells it from `note`."
8293
- ],
8294
- "useCases": [
8295
- "Docs/handbook admonition inside a prose column — `kind=\"note\"` for an aside, `kind=\"tip\"` for a shortcut worth knowing.",
8296
- "A caveat inside a rendered CMS article (react-markdown + rehype-sanitize), drawn from `> [!WARNING]` in the source.",
8297
- "A legal or policy page clause that needs emphasis without interrupting a screen-reader user reading the page top to bottom — `kind=\"important\"`.",
8298
- "A destructive-consequence note beside a runbook step — `kind=\"caution\"`, still silent on load."
8299
- ]
8300
- },
8301
- {
8302
- "example": "import { SkeletonTable } from \"@godxjp/ui/feedback\";\n\n{!coupons ? <SkeletonTable rows={10} columns={6} /> : <DataTable data={coupons} columns={columns} />}",
8303
- "group": "feedback",
8304
- "importPath": "@godxjp/ui/feedback",
8305
- "name": "SkeletonTable",
8306
- "props": [
8307
- {
8308
- "defaultValue": "8",
8309
- "description": "Body skeleton rows.",
8310
- "name": "rows",
8311
- "type": "number"
8312
- },
8313
- {
8314
- "defaultValue": "5",
8315
- "description": "Columns in header + body.",
8316
- "name": "columns",
8317
- "type": "number"
8318
- }
8319
- ],
8320
- "related": [
8321
- "DataTable — sibling component that SkeletonTable precedes. Once DataTable mounts, use its `loading` prop (renders an in-table loading row) for subsequent refetches rather than swapping back to SkeletonTable. Pick SkeletonTable only for the pre-mount gap.",
8322
- "DataState — query lifecycle widget from `@godxjp/ui/query`; accepts SkeletonTable as its `skeleton` prop and handles loading/empty/error transitions automatically. Prefer DataState + SkeletonTable over a hand-rolled ternary when the data comes from a useQuery hook.",
8323
- "SkeletonStat — sibling skeleton shaped like a StatCard tile; use inside a ResponsiveGrid to placeholder KPI dashboard cards, not tabular data.",
8324
- "DataTable — when data is already mounted but re-fetching (e.g. pagination, filter change), set `loading={true}` on DataTable directly instead of unmounting it and swapping in SkeletonTable; avoids layout shift and preserves scroll position."
8325
- ],
8326
- "rules": [],
8327
- "storyPath": "feedback/Skeleton.stories.tsx",
8328
- "tagline": "Loading placeholder matching the DataTable layout (header + N rows). Drop-in while data loads (deferred props).",
8329
- "usage": [
8330
- "DO use SkeletonTable as the pre-mount placeholder — either as a ternary fallback (`{!data ? <SkeletonTable rows={10} columns={6} /> : <DataTable … />}`) for Inertia deferred props, or as the `skeleton` prop of `DataState` (`<DataState query={q} skeleton={<SkeletonTable />} …>`). It is NOT for in-table loading; once DataTable has mounted use its own `loading` prop instead.",
8331
- "DO match rows/columns to the final DataTable layout: pass `rows` equal to your expected page size and `columns` equal to your column count so the skeleton doesn't visually jump on hydration. Defaults are rows=8, columns=5.",
8332
- "DO NOT use SkeletonTable when data is already present but refetching — use `DataTable loading={isFetching}` for in-table refetch states. SkeletonTable is only for the initial pre-mount gap before DataTable is rendered.",
8333
- "DO NOT wrap SkeletonTable in a Card — it renders its own header + body structure matching DataTable's DOM. Placing it inside CardContent adds unwanted padding around the skeleton rail.",
8334
- "The root element carries `aria-busy=\"true\"` automatically — do not add a second aria-busy on a wrapper. Screen readers announce the loading state correctly without extra markup.",
8335
- "Import from `@godxjp/ui/feedback` (not `@godxjp/ui/admin`). Both paths resolve but the canonical export is `feedback`."
8336
- ],
8337
- "useCases": [
8338
- "Inertia deferred props: the server streams the page shell immediately and defers the table data; render SkeletonTable until the prop arrives (`{!invoices ? <SkeletonTable rows={20} columns={7} /> : <DataTable data={invoices} columns={columns} />}`).",
8339
- "TanStack Query initial load via DataState: pass SkeletonTable as the `skeleton` prop so DataState shows the correct table shape during the query's loading state before switching to the populated DataTable.",
8340
- "Filter / search reset that unmounts and remounts DataTable: briefly show SkeletonTable while the new dataset fetches, preventing a flash of the empty state before results arrive.",
8341
- "Admin list pages (invoices, journal entries, partners) where the table has a known column count — tune `columns` to match so column widths feel stable and don't reflow on hydration.",
8342
- "Page-level Suspense boundaries: use SkeletonTable as the `fallback` of a React Suspense wrapping a lazy-loaded data table component.",
8343
- "Route prefetch / navigation transitions: render SkeletonTable in the destination slot while Inertia visits are in-flight, keeping perceived layout stable."
8344
- ]
8345
- },
8346
- {
8347
- "example": "import { SkeletonRows } from \"@godxjp/ui/feedback\";\n\n<SkeletonRows rows={6} columns={3} />",
8348
- "group": "feedback",
8349
- "importPath": "@godxjp/ui/feedback",
8350
- "name": "SkeletonRows",
8351
- "props": [
8352
- {
8353
- "defaultValue": "6",
8354
- "description": "How many skeleton lines to draw.",
8355
- "name": "rows",
8356
- "type": "number"
8357
- },
8358
- {
8359
- "defaultValue": "4",
8360
- "description": "Columns per row on the internal ResponsiveGrid — match the loaded list's column count so nothing reflows on hydration.",
8361
- "name": "columns",
8362
- "type": "number"
8363
- },
8364
- {
8365
- "description": "Root class override.",
8366
- "name": "className",
8367
- "type": "string"
8368
- }
8369
- ],
8370
- "related": [
8371
- "SkeletonTable — tabular placeholder with a header band; not a flat line list.",
8372
- "SkeletonForm — label + control pairs on a form grid.",
8373
- "SkeletonArticle — avatar beside prose lines.",
8374
- "Skeleton — the bare block this preset is built from."
8375
- ],
8376
- "rules": [
8377
- 3,
8378
- 31
8379
- ],
8380
- "storyPath": "feedback/Skeleton.stories.tsx",
8381
- "tagline": "Repeated flat loading lines on a responsive grid — the list-shaped placeholder when the final layout is rows of short bars, not a table header, not avatar + prose.",
8382
- "usage": [
8383
- "DO use SkeletonRows for a flat list of short bars — activity feeds without avatars, settings rows, filter result lists.",
8384
- "DO match `rows` and `columns` to the loaded layout so the skeleton does not jump when data arrives.",
8385
- "DON'T use it for tabular data with a header row — SkeletonTable matches DataTable's chrome.",
8386
- "DON'T use it for a form — SkeletonForm draws label + control pairs on the form's column grid.",
8387
- "Import from `@godxjp/ui/feedback` (canonical). `@godxjp/ui/admin` re-exports it for admin pages."
8388
- ],
8389
- "useCases": [
8390
- "Card body while a simple list deferred prop resolves",
8391
- "Stacked settings rows before the record loads",
8392
- "Generic repeated rows when SkeletonArticle's avatar + prose rhythm is wrong"
8393
- ]
8394
- },
8395
7887
  {
8396
7888
  "example": "// app root — mount once\nimport { Toaster } from \"@godxjp/ui/feedback\";\n<>{children}<Toaster richColors /></>\n\n// anywhere — import toast from \"sonner\"\nimport { toast } from \"sonner\";\ntoast.success(\"クーポンを公開しました\");\ntoast.error(\"保存に失敗しました\");",
8397
7889
  "group": "feedback",
@@ -8416,7 +7908,6 @@
8416
7908
  "Dialog — use when the user must make a conscious decision (confirm delete, resolve conflict) before proceeding. Toaster toasts are fire-and-forget; Dialog blocks until the user responds."
8417
7909
  ],
8418
7910
  "rules": [],
8419
- "storyPath": "feedback/Toaster.stories.tsx",
8420
7911
  "tagline": "Mount once at app root to enable toasts. IMPORTANT: trigger toasts via `import { toast } from \"sonner\"` — NOT from @godxjp/ui.",
8421
7912
  "usage": [
8422
7913
  "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.",
@@ -8572,16 +8063,16 @@
8572
8063
  "related": [
8573
8064
  "Steps (@godxjp/ui/navigation) — sequential wizard/progress indicator. Use Steps when order and completion state matter (multi-step forms, onboarding flows); use Tabs when panels are non-sequential and any tab can be visited freely.",
8574
8065
  "Toolbar / ToolbarGroup (@godxjp/ui/navigation) — horizontal filter chip row. Visually resembles `line`-variant tabs but is semantically different: Toolbar filters a dataset, it does not switch content panels. Never use Tabs as a filter control.",
8575
- "DropdownMenu (@godxjp/ui/navigation) — use for space-constrained contexts where showing all tab triggers at once is impractical (e.g. mobile overflow menu). If only 2-3 options exist and screen space is tight, a DropdownSidebar is a lighter alternative to a full tab strip."
8066
+ "DropdownMenu (@godxjp/ui/navigation) — use for space-constrained contexts where showing all tab triggers at once is impractical (e.g. mobile overflow menu). If only 2-3 options exist and screen space is tight, a Segmented control is a lighter alternative to a full tab strip."
8576
8067
  ],
8577
8068
  "rules": [],
8578
- "storyPath": "navigation/Tabs.stories.tsx",
8069
+ "storyPath": "navigation/tabs.tsx",
8579
8070
  "subParts": [
8580
8071
  "TabsContent",
8581
8072
  "TabsList",
8582
8073
  "TabsTrigger"
8583
8074
  ],
8584
- "tagline": "Radix tab container with optional Ant-style `items` API. Pass items for the common full TabsList/TabsContent set, or compose TabsList/TabsTrigger/TabsContent manually when you need per-panel control.",
8075
+ "tagline": "React Aria tab container with optional Ant-style `items` API. Pass items for the common full TabsList/TabsContent set, or compose TabsList/TabsTrigger/TabsContent manually when you need per-panel control.",
8585
8076
  "usage": [
8586
8077
  "DO pass `items` when all tab content is known up front — each item needs a unique `value`, trigger `label`, and panel `content`.",
8587
8078
  "When not using `items`, compose the full four-part tree — `<Tabs>` root, `<TabsList>` trigger bar, one `<TabsTrigger value=\"…\">` per tab, one `<TabsContent value=\"…\">` per matching trigger.",
@@ -8709,7 +8200,7 @@
8709
8200
  "rules": [
8710
8201
  40
8711
8202
  ],
8712
- "storyPath": "navigation/Pagination.stories.tsx",
8203
+ "storyPath": "navigation/pagination.tsx",
8713
8204
  "subParts": [
8714
8205
  "PaginationContent",
8715
8206
  "PaginationEllipsis",
@@ -8801,7 +8292,7 @@
8801
8292
  "Sidebar — use Sidebar for persistent left-rail navigation. DropdownMenu is transient (opens on click, dismisses on select); Sidebar is always-visible structural navigation."
8802
8293
  ],
8803
8294
  "rules": [],
8804
- "storyPath": "navigation/DropdownMenu.stories.tsx",
8295
+ "storyPath": "navigation/dropdown-menu.tsx",
8805
8296
  "subParts": [
8806
8297
  "DropdownMenuCheckboxItem",
8807
8298
  "DropdownMenuContent",
@@ -8914,7 +8405,7 @@
8914
8405
  "Breadcrumb — use Breadcrumb for hierarchical location within a page tree; use Steps for sequential workflow progress where order and completion matter."
8915
8406
  ],
8916
8407
  "rules": [],
8917
- "storyPath": "navigation/Steps.stories.tsx",
8408
+ "storyPath": "navigation/steps.tsx",
8918
8409
  "tagline": "Multi-step progress indicator — horizontal or vertical, default or dot style.",
8919
8410
  "usage": [
8920
8411
  "DO: Pass all steps via the `items` array (each `{ title, subtitle?, description?, icon?, status?, disabled? }`) — Steps is a single-component API with no child sub-components to compose manually.",
@@ -9037,9 +8528,11 @@
9037
8528
  ],
9038
8529
  "storyPath": "navigation/toolbar.tsx",
9039
8530
  "subParts": [
9040
- "ToolbarGroup"
8531
+ "ToolbarGroup",
8532
+ "FilterBar",
8533
+ "FilterBarGroup"
9041
8534
  ],
9042
- "tagline": "List-page filter strip (the framework FilterBar) — SearchInput + labelled ToolbarGroup filter slots + a clear-all affordance, optionally sticky.",
8535
+ "tagline": "List-page filter strip — SearchInput + labelled ToolbarGroup filter slots (or the typed `search`/`filters`/`chips` model) + a clear-all affordance, optionally sticky. `FilterBar`/`FilterBarGroup` are the same components under their alias names.",
9043
8536
  "usage": [
9044
8537
  "DO prefer the typed model on a canonical list page — `search`/`filters`/`chips`/`onChipRemove`/`actions`/`resultCount` (+ `loading`/`disabled`/`error`) make the bar own layout, widths, chip lifecycle and keyboard order through --filter-bar-* tokens while ALL state stays consumer data. Children composition remains fully supported and unchanged when no model prop is passed.",
9045
8538
  "DO place Toolbar ABOVE the table Card, never inside a `CardContent flush`. Put SearchInput as a direct child (it self-labels) and wrap every other control in a ToolbarGroup with a `label`.",
@@ -9048,7 +8541,12 @@
9048
8541
  "DO pass `controlId` on a ToolbarGroup that wraps ONE control and give that control the same `id` — the visible caption then becomes the control's real `<label htmlFor>` (WCAG 2.5.3 label-in-name). Without it the caption only names the group wrapper and the control itself is NAMELESS (axe: select-name / button-name), so you must fall back to an `aria-label` that repeats the caption.",
9049
8542
  "DO switch to `overflow='scroll'` when a list page carries more filters than fit one row — one bounded row that scrolls inline keeps the table above the fold, where the default `wrap` would grow a 3-row strip with long JA/EN/VI labels. Never re-implement the geometry in the page: no page-local flex/grid/width rules on a filter strip.",
9050
8543
  "DON'T build active-filter chips by nesting a Button inside a Badge (invalid markup + broken focus). Render each chip as a Badge label with a SIBLING icon Button (`aria-label` = 'clear <filter>'); a ghost `size='sm'` Button clears all.",
9051
- "DON'T hand-roll a debounced search box or a raw `<select>` — compose SearchInput and Select. Toolbar is layout + clear-all only; the controls own their own state and a11y."
8544
+ "DON'T hand-roll a debounced search box or a raw `<select>` — compose SearchInput and Select. Toolbar is layout + clear-all only; the controls own their own state and a11y.",
8545
+ "Prefer the typed model for a canonical list page: pass `search` + `filters` + `chips` + `resultCount` and the bar owns the layout, widths, chip lifecycle, keyboard order (search → filters → children → reset → actions → chip removes) and responsive stacking through --filter-bar-* tokens. Everything stays consumer data — the bar renders state, never owns it.",
8546
+ "Compose real controls as children when the model doesn't fit; filter state and URL synchronization remain consumer-owned. Children also render INSIDE the model layout (after the typed filters) for one-off custom controls like a date-range picker.",
8547
+ "Give each FilterBarGroup a `controlId` matching its single control's `id` so the visible caption is that control's real <label>; otherwise the control is nameless to a screen reader. Typed `filters` wire this automatically.",
8548
+ "Reach for `overflow='scroll'` on filter-heavy list pages so a long JA/EN/VI label set never grows the strip into multiple rows and pushes the table below the fold.",
8549
+ "A mobile sheet presentation is a COMPOSITION, not a prop: put the same typed FilterBar inside a Sheet triggered from a compact toolbar when a screen wants drawer-style filters. The bar's own responsive behavior is stack (below 640px) + wrap/scroll (above), token-owned."
9052
8550
  ],
9053
8551
  "useCases": [
9054
8552
  "Master list screens (members, organizations, subscriptions, invoices) that need free-text search plus a few dropdown filters above a DataTable.",
@@ -9063,10 +8561,14 @@
9063
8561
  "name": "AppProvider",
9064
8562
  "props": [
9065
8563
  {
9066
- "defaultValue": "\"vi\"",
9067
- "description": "Initial locale.",
8564
+ "description": "Initial locale when nothing is stored. Resolution, first match wins: this prop (when registered — `de-AT` is served by a registered `de`) → `<html lang>` (client only) → the preset's `defaultLocale` → `en`. Never `navigator.language` (the server cannot see it, so SSR would hydrate differently). An SSR host passes the request's locale here. v32: the default was `vi`.",
9068
8565
  "name": "defaultLocale",
9069
- "type": "\"ja\" | \"en\" | \"vi\""
8566
+ "type": "string (BCP-47)"
8567
+ },
8568
+ {
8569
+ "description": "Product defaults the host opts into — `import { godxPreset } from \"@godxjp/ui/themes/godx\"` (pair it with `@godxjp/ui/themes/godx.css` after the base stylesheet). Its `defaultLocale` and `timeZone` fill only what the props leave unset, its `brandMark` is what an `AuthIdentity` with no `brand` draws, and its `name` is written to `<html data-preset>`. Omit for the neutral defaults (ink primary, `en`, no product mark).",
8570
+ "name": "preset",
8571
+ "type": "AppPreset"
9070
8572
  },
9071
8573
  {
9072
8574
  "defaultValue": "\"browser\"",
@@ -9130,31 +8632,35 @@
9130
8632
  }
9131
8633
  ],
9132
8634
  "related": [
9133
- "LocalePicker — the language-selector control that reads/writes AppProvider locale context automatically when used as a zero-prop child. Prefer LocalePicker over calling setLocale from useAppContext() directly in UI.",
9134
- "TimezonePicker — the timezone-selector control; inherits `timezoneOptions` from AppProvider context when its own `options` prop is omitted. Both pickers require AppProvider to be in the tree unless controlled props are passed.",
8635
+ "AppSettingPicker — `kind=\"locale\"` is the language selector that reads/writes AppProvider locale context automatically when used with no value props. Prefer it over calling setLocale from useAppContext() directly in UI.",
8636
+ "AppSettingPicker `kind=\"timezone\"` — the timezone selector; inherits `timezoneOptions` from AppProvider context. Every kind needs AppProvider in the tree unless value + onValueChange are passed.",
9135
8637
  "formatDate — the MANDATORY date/time formatter that reads locale, timezone, timeFormat, and dateFormat from AppProvider context. Do NOT call date-fns or Intl.DateTimeFormat directly; formatDate is the single source of truth for display.",
9136
- "AppShell — the top-level application shell that composes AppProvider, AppShell, Sidebar, and Topbar into a single ready-to-use layout. If your project uses AppShell, AppProvider is already mounted inside it — do not add a second one.",
8638
+ "AppShell — the authenticated layout (sidebar + topbar + main). It does NOT mount AppProvider: mount AppProvider once at the application root, ABOVE AppShell (and above any CenteredShell/MobileShell), so every shell and page reads the same context.",
9137
8639
  "OverlayPortalProvider — the other root-level provider, and the one to reach for when this library is mounted inside a SHADOW ROOT. Every overlay here (Popover, Dialog, Sheet, Tooltip, DropdownMenu, HoverCard) portals to `document.body` by default, which is correct on an ordinary page and wrong in a shadow root: the panel lands outside the tree carrying the stylesheet and renders with none of it — measured on an embedded bar as border 0, radius 0, a transparent background, and the anchor maths 40px off. `<OverlayPortalProvider container={shadowRoot}>` moves all of them at once; a per-component prop cannot, because a component that owns its own overlay (AppLauncher) is unreachable from the outside."
9138
8640
  ],
9139
8641
  "rules": [
9140
8642
  5
9141
8643
  ],
9142
- "storyPath": "app/AppProvider.stories.tsx",
8644
+ "storyPath": "providers/app-provider.tsx",
8645
+ "subParts": [
8646
+ "AppPresetContext"
8647
+ ],
9143
8648
  "tagline": "Root locale/timezone/date-time context — wrap the app ONCE. All pickers + formatDate read from it. Import from @godxjp/ui/app.",
9144
8649
  "usage": [
8650
+ "`AppPresetContext` is the React context AppProvider's `preset` prop fills; read it with `useAppPreset()` (the active `AppPreset`, or undefined for the neutral defaults). Don't provide it yourself — pass `preset` to AppProvider.",
9145
8651
  "DO drive the four theme axes (theme / brand / density / fontSize) from AppProvider props ONLY — they are written to <html data-*> and read by every component via tokens. Never hand-set --font-size-base or .ui-density-* in app CSS; that bypasses persistence + the runtime switchers. For runtime switching mount `<AppSettingPicker kind=\"density\" | \"fontSize\" | \"theme\" | \"brand\" >` or call setDensity/setFontSize/setTheme/setBrand from useAppContext().",
9146
- "DO mount AppProvider ONCE at the application root (e.g. in app.tsx or the Inertia layout), wrapping ALL children — every godx-ui picker (LocalePicker, TimezonePicker, DateFormatPicker, TimeFormatPicker), every formatDate call, and the Toaster all rely on the single context it provides. Nesting two AppProviders creates split contexts; inner pickers silently read the wrong one.",
9147
- "DO NOT omit AppProvider and then try to use LocalePicker, TimezonePicker, or formatDate standalone — useAppContext() throws 'useAppContext must be used within <AppProvider>' at runtime. The only exception is using those pickers in fully controlled mode (value + onChange) which reads useOptionalAppContext() and returns null safely.",
8652
+ "DO mount AppProvider ONCE at the application root (e.g. in app.tsx or the Inertia layout), wrapping ALL children — every `AppSettingPicker` (kind=\"locale\" / \"timezone\" / \"dateFormat\" / \"timeFormat\"), every formatDate call, and the Toaster all rely on the single context it provides. Nesting two AppProviders creates split contexts; inner pickers silently read the wrong one.",
8653
+ "DO NOT omit AppProvider and then try to use AppSettingPicker or formatDate standalone — useAppContext() throws 'useAppContext must be used within <AppProvider>' at runtime. The only exception is using those pickers in fully controlled mode (value + onChange) which reads useOptionalAppContext() and returns null safely.",
9148
8654
  "DO use `persist={false}` on AppProvider for isolated tests and standalone settings forms where localStorage must not be read or written. With the default `persist={true}` the provider reads localStorage key `godxjp.app` on mount (after first render), so initial state may differ between SSR and client.",
9149
8655
  "DO NOT reach for `persist={false}` because the SERVER owns the locale — pass the axes the BROWSER owns instead: `persist={[\"theme\", \"brand\", \"density\", \"fontSize\", \"scaling\"]}`. Storage is read after the props, so a stored `locale` overrides the one the server just resolved; turning persistence off wholesale fixes that and silently takes the viewer's theme with it, which is how a hosted app shipped a theme toggle that reset on every reload.",
9150
8656
  "DO set `defaultTimezone='system'` together with `systemTimezone={serverTimezone}` when your backend knows the legal entity's canonical timezone (e.g. 'Asia/Ho_Chi_Minh'). Use `defaultTimezone='browser'` (the default) only when you want the user's browser clock. Do NOT pass a raw IANA string to `defaultTimezone` if the user may be in a different zone — use the named aliases.",
9151
8657
  "DO wire `onLocaleChange`, `onTimezoneChange`, `onTimeFormatChange`, `onDateFormatChange` to persist changes server-side (e.g. patch user profile via Inertia router) in addition to the automatic localStorage write. These callbacks fire after state is set, so the new value is already reflected in context.",
9152
8658
  "DO set `emitFieldNames` on AppProvider when the app is driven by screen automation (RPA) or posts native forms — every control under a FormField then carries a real `name` taken from the field key, and legacy automation that addressed controls by `name` keeps working after a rewrite. Leave it off (the default) otherwise: it changes what a native submit sends. The `data-field` attribute is emitted either way, so e2e selectors do not depend on this flag.",
9153
- "DO restrict the timezone dropdown by passing `timezoneOptions={APP_TIMEZONE_PRESET}` (an exported constant) to AppProvider — all TimezonePicker instances that omit their own `options` prop will inherit this restricted list automatically from context. Without it, TimezonePicker renders the full IANA list (~600 entries)."
8659
+ "DO restrict the timezone dropdown by passing `timezoneOptions={APP_TIMEZONE_PRESET}` (an exported constant) to AppProvider — every `AppSettingPicker kind=\"timezone\"` inherits this restricted list from context. Without it the picker offers the full IANA list (~600 entries)."
9154
8660
  ],
9155
8661
  "useCases": [
9156
8662
  "App bootstrap in a multi-locale SaaS admin (ja/en/vi) — mount AppProvider at the root with the tenant's preferred locale and IANA timezone so every DataTable date column, every formatDate call, and every picker renders consistently in the user's locale without any per-component configuration.",
9157
- "User settings page — render LocalePicker, TimezonePicker, DateFormatPicker, and TimeFormatPicker as zero-prop children inside the existing AppProvider; each picker reads and writes context automatically. Wire `onLocaleChange` to an Inertia form submit to persist the change to the server profile.",
8663
+ "User settings page — render `<AppSettingPicker kind=\"locale\" />`, `kind=\"timezone\"`, `kind=\"dateFormat\"` and `kind=\"timeFormat\"` inside the existing AppProvider; each reads and writes context automatically. Wire `onLocaleChange` to an Inertia form submit to persist the change to the server profile.",
9158
8664
  "Server-rendered Inertia app with SSR hydration — pass `defaultTimezone='system'` and `systemTimezone={sharedProps.timezone}` (injected via HandleInertiaRequests) so the initial render is timezone-deterministic and avoids hydration mismatches caused by browser-timezone detection.",
9159
8665
  "Multi-entity accounting dashboard — use `timezoneOptions` to restrict the picker to the legal entity's permissible zones (e.g. Southeast Asian IANA ids only), preventing users from accidentally switching to an out-of-scope timezone that would misrepresent transaction timestamps.",
9160
8666
  "Isolated preview / Storybook story — wrap a single component in `<AppProvider persist={false} defaultLocale='en'>` to give it a stable context without polluting localStorage between stories.",
@@ -9188,7 +8694,6 @@
9188
8694
  "rules": [
9189
8695
  5
9190
8696
  ],
9191
- "storyPath": "app/OverlayPortalProvider.stories.tsx",
9192
8697
  "tagline": "Moves EVERY overlay in this library (Popover, Dialog, Sheet, Tooltip, DropdownMenu, HoverCard, Select, Cascader) into a container you name — the one thing an app mounted inside a SHADOW ROOT cannot do with props.",
9193
8698
  "usage": [
9194
8699
  "DO mount it when this library is rendered inside a shadow root (an embedded bar, a widget injected into a host page, a Web Component wrapper). Overlays default to document.body, which is outside the tree carrying this library's stylesheet — measured on an embedded bar as border 0, radius 0, a transparent background and the anchor maths 40px off.",
@@ -9236,7 +8741,7 @@
9236
8741
  "rules": [
9237
8742
  5
9238
8743
  ],
9239
- "storyPath": "app/ThemeScope.stories.tsx",
8744
+ "storyPath": "providers/theme-scope.tsx",
9240
8745
  "tagline": "Makes a themed REGION reach the overlays it opens. Every overlay portals to document.body, so custom-property inheritance stops at the portal boundary and a tenant-themed region's Dialog, Select listbox, Popover and Toast paint the package defaults. Wrap the region in ThemeScope and they carry the region's tokens.",
9241
8746
  "usage": [
9242
8747
  "DO wrap the region, then theme it the way you already do — `style={tenantTheme(hex).vars}`, `data-tenant=\"acme\"`, `className=\"dark\"`, or a stylesheet rule that never mentions React. ThemeScope reads the COMPUTED tokens at its own element, so all of those paths behave identically; it has no theme prop and needs none.",
@@ -9274,13 +8779,13 @@
9274
8779
  "related": [
9275
8780
  "AppProvider — required peer that seeds locale, timezone, dateFormat, and timeFormat into the module-level context that `formatDate` reads. Must be mounted once at app root; omitting it means `formatDate` silently falls back to Vietnamese/Ho Chi Minh City defaults.",
9276
8781
  "DatePicker — the corresponding input control for calendar dates. Use `DatePicker` to capture a date from the user; use `formatDate(value, { calendar: true })` to display the picked `Date` object back as a string.",
9277
- "DateFormatPicker / TimeFormatPicker / TimezonePicker — preference pickers that update AppProvider context; their selections are automatically picked up by subsequent `formatDate` calls with no extra wiring needed.",
8782
+ "AppSettingPicker — `kind=\"dateFormat\" | \"timeFormat\" | \"timezone\"` updates AppProvider context, and subsequent `formatDate` calls pick the selection up with no extra wiring.",
9278
8783
  "TimePicker — the corresponding input control for HH:mm time values. Use `TimePicker` to capture time; use `formatDate(hhmm)` (auto-detects `'time'` kind) to display the stored `HH:mm` string respecting the user's 12h/24h preference."
9279
8784
  ],
9280
8785
  "rules": [
9281
8786
  5
9282
8787
  ],
9283
- "storyPath": "app/formatDate.stories.tsx",
8788
+ "storyPath": "providers/format-date.tsx",
9284
8789
  "tagline": "MANDATORY for all date/time display. Auto-detects ISO date / HH:mm / instant; reads AppProvider context. Import from @godxjp/ui/datetime.",
9285
8790
  "usage": [
9286
8791
  "DO import from `@godxjp/ui/datetime` — NOT from `date-fns` or any other datetime utility. `formatDate` is the single mandatory display entry point; calling `date-fns/format` directly bypasses AppProvider locale/timezone/dateFormat/timeFormat context and produces inconsistent output across the app.",
@@ -9478,7 +8983,7 @@
9478
8983
  13,
9479
8984
  23
9480
8985
  ],
9481
- "storyPath": "data-entry/TimePicker.stories.tsx",
8986
+ "storyPath": "data-entry/time-picker.tsx",
9482
8987
  "tagline": "Time combobox with hours, minutes, optional seconds, confirmation and 12-hour formatting. Native submission stays canonical 24-hour time.",
9483
8988
  "usage": [
9484
8989
  "One trailing icon: empty shows calendar/clock; filled and clearable shows only ×; allowClear=false retains the picker icon. Click the input or ArrowDown to open a filled picker.",
@@ -9693,7 +9198,7 @@
9693
9198
  23,
9694
9199
  31
9695
9200
  ],
9696
- "storyPath": "data-entry/Cascader.stories.tsx",
9201
+ "storyPath": "data-entry/cascader.tsx",
9697
9202
  "tagline": "Multi-level hierarchical path picker (Popover + cascading columns); value is always a string[] path, never a flat ID — passing a bare string breaks it.",
9698
9203
  "usage": [
9699
9204
  "DO pass a string[] path as value in single mode (e.g. ['country','region','city']). DON'T pass a flat string ID — the component treats value as an ordered path array and will render nothing if you pass a bare string.",
@@ -9925,7 +9430,7 @@
9925
9430
  13,
9926
9431
  23
9927
9432
  ],
9928
- "storyPath": "data-entry/TreeSelect.stories.tsx",
9433
+ "storyPath": "data-entry/tree-select.tsx",
9929
9434
  "tagline": "Hierarchical tree picker in a Popover (single or multi-select with checkboxes) — `onValueChange` receives `string` in single mode and `string[]` in multi/checkable mode; never use a raw `<select>` for tree-structured data.",
9930
9435
  "usage": [
9931
9436
  "DO pair with a `<label htmlFor={id}>` and pass the matching `id` prop so screen readers announce the control correctly. The underlying trigger is a `<Button role='combobox'>` — not a native `<select>` — so an explicit label is required.",
@@ -10067,7 +9572,7 @@
10067
9572
  }
10068
9573
  ],
10069
9574
  "related": [
10070
- "MultiSelect — picks multiple values from a dropdown; prefer when the option set is large and a panel layout is not needed.",
9575
+ "Select mode=\"multiple\" — picks multiple values from a dropdown; prefer it when the option set is large and a two-panel layout is not needed.",
10071
9576
  "Checkbox (list) — use for a simple flat multi-select without a shuttle/move metaphor.",
10072
9577
  "Select (compound) — single or multi-value dropdown; not a dual-panel component.",
10073
9578
  "Tree — hierarchical item display; combine with Transfer's dataSource if items have a tree structure but the shuttle UX is still needed."
@@ -10076,7 +9581,7 @@
10076
9581
  23,
10077
9582
  31
10078
9583
  ],
10079
- "storyPath": "data-entry/Transfer.stories.tsx",
9584
+ "storyPath": "data-entry/transfer.tsx",
10080
9585
  "tagline": "Dual-list shuttle that moves items between source and target via Checkbox selection — you own targetKeys state; never hand-roll a two-panel picker.",
10081
9586
  "usage": [
10082
9587
  "DO own `targetKeys` in state and update it inside `onChange`: `const [targetKeys, setTargetKeys] = useState<string[]>([]); onValueChange={(next) => setTargetKeys(next)}`.",
@@ -10301,7 +9806,7 @@
10301
9806
  3,
10302
9807
  23
10303
9808
  ],
10304
- "storyPath": "data-entry/Upload.stories.tsx",
9809
+ "storyPath": "data-entry/upload.tsx",
10305
9810
  "tagline": "Drag-and-drop / button / avatar / picture file uploader in six variants — wire onUpload to your media-service and call collectUploadCommitActions on form submit; supports multipart forms and custom media storage.",
10306
9811
  "usage": [
10307
9812
  "DO provide onUpload to auto-upload on pick. The callback must return { mediaId, previewUrl? } — the component transitions item.status through uploading → done/error automatically. Without onUpload the File object sits in item.file until you manually process it.",
@@ -10363,7 +9868,7 @@
10363
9868
  13,
10364
9869
  23
10365
9870
  ],
10366
- "storyPath": "data-entry/UploadCropDialog.stories.tsx",
9871
+ "storyPath": "data-entry/upload-crop-dialog.tsx",
10367
9872
  "tagline": "Modal crop dialog for a single image file — always controlled (open + file + onConfirm required); do NOT use standalone when Upload variant=\"avatar-crop\" already embeds it.",
10368
9873
  "usage": [
10369
9874
  "DO pass a File object selected by the user (e.g. from an <input type='file'> or drag-drop handler) to `file`; the dialog creates and revokes its own object URL — never create one yourself before passing.",
@@ -10456,7 +9961,7 @@
10456
9961
  6,
10457
9962
  13
10458
9963
  ],
10459
- "storyPath": "data-entry/ColorPicker.stories.tsx",
9964
+ "storyPath": "data-entry/color-picker.tsx",
10460
9965
  "tagline": "Native color-swatch picker with an optional editable hex input — always pass a valid 3- or 6-digit hex `value`; invalid hex is silently ignored and the previous value is restored.",
10461
9966
  "usage": [
10462
9967
  "DO wrap in FormField when a label or validation message is needed — pass the same id to both FormField and ColorPicker so htmlFor wires up correctly: `<FormField id='brand' label='Brand color'><ColorPicker id='brand' value={v} onValueChange={setV} /></FormField>`.",
@@ -10616,8 +10121,8 @@
10616
10121
  6,
10617
10122
  31
10618
10123
  ],
10619
- "storyPath": "data-entry/Slider.stories.tsx",
10620
- "tagline": "Numeric slider on react-aria-components with the antd 6 API — one thumb over a point, or `range` for a span. `value` takes a plain number (antd) or a number[] (the Radix-era spelling); both still work.",
10124
+ "storyPath": "data-entry/slider.tsx",
10125
+ "tagline": "Numeric slider on react-aria-components with the antd 6 API — one thumb over a point, or `range` for a span. `value` takes a plain number (antd) or a number[] (the v22-era array spelling); both still work.",
10621
10126
  "usage": [
10622
10127
  "DO: Use `onChange` for live UI and `onChangeComplete` for the expensive work — a refetch, a save. Commit fires once per gesture, and only when the value moved.",
10623
10128
  "DO: Declare `range` when you want two thumbs. Leaving it to an array's length makes a range that is still loading render as a single point, and `onChange` then reports a number rather than a pair.",
@@ -10872,7 +10377,7 @@
10872
10377
  6,
10873
10378
  23
10874
10379
  ],
10875
- "storyPath": "data-entry/Calendar.stories.tsx",
10380
+ "storyPath": "data-entry/calendar.tsx",
10876
10381
  "subParts": [
10877
10382
  "DayButton"
10878
10383
  ],
@@ -11026,7 +10531,7 @@
11026
10531
  }
11027
10532
  ],
11028
10533
  "related": [
11029
- "SearchSelect — higher-level compound component that composes Command + Popover + server search; use SearchSelect for a fully managed async combobox instead of building your own with Command.",
10534
+ "Select — `showSearch` / `loadOptions` is the fully managed (async) combobox built on Command + Popover; use it instead of building your own with Command.",
11030
10535
  "Select — simple dropdown for static option lists without type-to-filter; use Select when there are fewer than ~10 options and no search is needed.",
11031
10536
  "SearchInput — standalone text input with a search icon; use SearchInput for filtering visible page content (tables, lists) not for selecting from a command menu.",
11032
10537
  "CommandInput (sub-part) — the styled search input that only works inside a Command root; never use it alone as a general search field."
@@ -11037,7 +10542,7 @@
11037
10542
  6,
11038
10543
  23
11039
10544
  ],
11040
- "storyPath": "data-entry/Command.stories.tsx",
10545
+ "storyPath": "data-entry/command.tsx",
11041
10546
  "subParts": [
11042
10547
  "CommandEmpty",
11043
10548
  "CommandGroup",
@@ -11048,7 +10553,7 @@
11048
10553
  "tagline": "Accessible, keyboard-navigable command palette / combobox list built on cmdk — always pair CommandInput inside its own wrapper div, never render items outside CommandList.",
11049
10554
  "usage": [
11050
10555
  "DO compose the full tree: Command > CommandInput + CommandList > (CommandEmpty | CommandGroup > CommandItem | CommandItem). Every interactive element must live inside CommandList; items outside it are invisible to the keyboard engine.",
11051
- "DO set shouldFilter={false} and manage filtering yourself when the options list comes from a server/async source (e.g. SearchSelect pattern). With shouldFilter=true the default client-side scoring runs over all rendered items automatically.",
10556
+ "DO set shouldFilter={false} and manage filtering yourself when the options list comes from a server/async source (e.g. Select `loadOptions`). With shouldFilter=true the default client-side scoring runs over all rendered items automatically.",
11052
10557
  "DO always provide a stable explicit value prop on CommandItem when the item's text content can change between renders — relying on inferred textContent with dynamic labels causes selection bugs.",
11053
10558
  "DO include CommandEmpty inside CommandList to show a no-results message. It renders automatically only when the filtered count is zero; do not conditionally render it yourself.",
11054
10559
  "DO use split for checkbox/option lists (a filter facet, a picker: Popover + PopoverContent flush + Command split) — rows get a divider between them and 0 list padding, so they read as one box. Keep the default (unsplit) for a command palette. Never re-create the look with page CSS (border on items, padding 0 on the group): retune --command-item-divider-color / --command-item-divider-width instead.",
@@ -11057,8 +10562,8 @@
11057
10562
  ],
11058
10563
  "useCases": [
11059
10564
  "Command palette / global action launcher (Cmd+K menu): wraps Command + CommandInput + grouped CommandItems for quick navigation across pages or actions.",
11060
- "Popover-based combobox with server-side search: Command with shouldFilter={false} inside a Popover, managing the query state externally and filtering options before rendering — this is exactly how SearchSelect is built internally.",
11061
- "Tree/cascader search panel: inject Command + CommandInput as a search header above a custom scroll area (no CommandList needed for the tree body) to get a styled, accessible search input — as used by CascaderSelect and TreeSelect.",
10565
+ "Popover-based combobox with server-side search: Command with shouldFilter={false} inside a Popover, managing the query state externally and filtering options before rendering — this is exactly how Select `showSearch` is built internally.",
10566
+ "Tree/cascader search panel: inject Command + CommandInput as a search header above a custom scroll area (no CommandList needed for the tree body) to get a styled, accessible search input — as Cascader and TreeSelect do.",
11062
10567
  "Multi-group option picker: use CommandGroup with heading to visually separate option categories (e.g. 'Accounts', 'Contacts', 'Documents') inside one dropdown with a single search box.",
11063
10568
  "Keyboard-first admin shortcut bar: embed Command with loop={true} and vimBindings={true} in a persistent sidebar for power-user keyboard navigation without page reloads."
11064
10569
  ]
@@ -11140,7 +10645,6 @@
11140
10645
  23,
11141
10646
  31
11142
10647
  ],
11143
- "storyPath": "data-entry/CheckboxGroup.stories.tsx",
11144
10648
  "tagline": "Multi-select checkbox list from an options array or manual children — use `options` prop for the data-driven path, never hand-roll individual Checkbox items in a group.",
11145
10649
  "usage": [
11146
10650
  "DO use the `options` prop for any data-driven list — it auto-generates IDs, handles checked state, and wires up Field (label + description) for each item. NEVER hand-roll individual `<Checkbox>` elements inside a loop when you have an options array.",
@@ -11235,7 +10739,6 @@
11235
10739
  13,
11236
10740
  23
11237
10741
  ],
11238
- "storyPath": "data-entry/Radio.stories.tsx",
11239
10742
  "subParts": [
11240
10743
  "RadioItem"
11241
10744
  ],
@@ -11258,6 +10761,11 @@
11258
10761
  ]
11259
10762
  },
11260
10763
  {
10764
+ "absorbed": [
10765
+ "HoverCard",
10766
+ "HoverCardTrigger",
10767
+ "HoverCardContent"
10768
+ ],
11261
10769
  "example": "import {\n Popover,\n PopoverTrigger,\n PopoverContent,\n PopoverHeader,\n PopoverTitle,\n PopoverDescription,\n} from \"@godxjp/ui/data-display\";\nimport { Button } from \"@godxjp/ui/general\";\n\n// Uncontrolled — basic usage\nexport function InvoiceFilterPopover() {\n return (\n <Popover>\n <PopoverTrigger asChild>\n <Button variant=\"outline\">Advanced filters</Button>\n </PopoverTrigger>\n <PopoverContent align=\"start\" width=\"trigger\">\n <PopoverHeader>\n <PopoverTitle>Filter invoices</PopoverTitle>\n <PopoverDescription>Narrow results by date range and status.</PopoverDescription>\n </PopoverHeader>\n {/* place form controls here */}\n </PopoverContent>\n </Popover>\n );\n}\n\n// Controlled — programmatic open/close\nimport * as React from \"react\";\n\nexport function ControlledPopover() {\n const [open, setOpen] = React.useState(false);\n return (\n <Popover open={open} onOpenChange={setOpen}>\n <PopoverTrigger asChild>\n <Button variant=\"ghost\" size=\"icon\" aria-label=\"Show details\">\n ?\n </Button>\n </PopoverTrigger>\n <PopoverContent side=\"right\" sideOffset={8}>\n <PopoverHeader>\n <PopoverTitle>About this field</PopoverTitle>\n <PopoverDescription>\n The MF ID is the unique identifier assigned by Money Forward.\n </PopoverDescription>\n </PopoverHeader>\n </PopoverContent>\n </Popover>\n );\n}",
11262
10770
  "group": "data-display",
11263
10771
  "importPath": "@godxjp/ui/data-display",
@@ -11281,10 +10789,28 @@
11281
10789
  },
11282
10790
  {
11283
10791
  "defaultValue": "false",
11284
- "description": "When true, interaction outside the popover is blocked and focus is trapped inside (Radix Root prop).",
10792
+ "description": "When true, interaction outside the popover is blocked and focus is trapped inside (React Aria modal popover).",
11285
10793
  "name": "modal",
11286
10794
  "type": "boolean"
11287
10795
  },
10796
+ {
10797
+ "defaultValue": "\"click\"",
10798
+ "description": "What opens the panel. \"click\": the trigger toggles a dialog panel. \"hover\": a HOVER CARD — rich, non-modal preview content that opens when the pointer rests on the trigger for `openDelay` and closes `closeDelay` after it leaves (moving onto the card cancels the close); keyboard focus opens it and blur closes it at once; touch never opens it. In hover mode the panel is not a dialog and takes no focus, and a trigger without `asChild` renders an `<a>`. This is what `HoverCard` was (v32).",
10799
+ "name": "openOn",
10800
+ "type": "\"click\" | \"hover\""
10801
+ },
10802
+ {
10803
+ "defaultValue": "200",
10804
+ "description": "`openOn=\"hover\"` only: ms the pointer rests on the trigger before opening.",
10805
+ "name": "openDelay",
10806
+ "type": "number"
10807
+ },
10808
+ {
10809
+ "defaultValue": "100",
10810
+ "description": "`openOn=\"hover\"` only: ms after the pointer leaves before closing.",
10811
+ "name": "closeDelay",
10812
+ "type": "number"
10813
+ },
11288
10814
  {
11289
10815
  "defaultValue": "\"center\"",
11290
10816
  "description": "PopoverContent prop. Horizontal alignment of the popover relative to the trigger.",
@@ -11311,13 +10837,13 @@
11311
10837
  },
11312
10838
  {
11313
10839
  "defaultValue": "false",
11314
- "description": "PopoverContent prop. The panel's CONTENT owns its inset: the popover zeroes its own --popover-space-inset so a Command list, a menu or a table runs edge to edge and draws its separators across the full width. Reach for it whenever the child already paints its own rows; leave it off for prose panels, which want the panel padding.",
10840
+ "description": "PopoverContent prop. The panel's CONTENT owns its inset: the popover zeroes its own --popover-space-inset so a Command list, a menu or a table runs edge to edge and draws its separators across the full width. Reach for it whenever the child already paints its own rows; leave it off for prose panels, which want the panel padding. Ignored under openOn=\"hover\" (a hover card keeps its own measure).",
11315
10841
  "name": "flush",
11316
10842
  "type": "boolean"
11317
10843
  },
11318
10844
  {
11319
10845
  "defaultValue": "\"panel\"",
11320
- "description": "PopoverContent prop. How the panel is MEASURED across the inline axis. \"panel\" (default) is the popover's own --popover-width (18rem) — right for prose and for a list the panel sizes itself. \"auto\" lets the CONTENT decide, the only correct answer for something with a width of its own (a two-month Calendar is far wider than 18rem and was being clipped by it). \"trigger\" matches the anchor. It is the counterpart of `flush`: both set a token the panel already reads, so a service theme keeps owning them, where a `w-*` utility on className is a per-call-site constant nothing can retune.",
10846
+ "description": "PopoverContent prop. How the panel is MEASURED across the inline axis. \"panel\" (default) is the popover's own --popover-width (18rem) — right for prose and for a list the panel sizes itself. \"auto\" lets the CONTENT decide, the only correct answer for something with a width of its own (a two-month Calendar is far wider than 18rem and was being clipped by it). \"trigger\" matches the anchor. It is the counterpart of `flush`: both set a token the panel already reads, so a service theme keeps owning them, where a `w-*` utility on className is a per-call-site constant nothing can retune. Ignored under openOn=\"hover\" (a hover card keeps its own measure).",
11321
10847
  "name": "width",
11322
10848
  "type": "\"panel\" | \"auto\" | \"trigger\""
11323
10849
  },
@@ -11338,7 +10864,7 @@
11338
10864
  31,
11339
10865
  35
11340
10866
  ],
11341
- "storyPath": "data-display/Popover.stories.tsx",
10867
+ "storyPath": "data-display/popover.tsx",
11342
10868
  "subParts": [
11343
10869
  "PopoverAnchor",
11344
10870
  "PopoverContent",
@@ -11347,8 +10873,9 @@
11347
10873
  "PopoverTitle",
11348
10874
  "PopoverTrigger"
11349
10875
  ],
11350
- "tagline": "Radix-backed floating panel anchored to a trigger — always compose with PopoverTrigger + PopoverContent; never use a raw div overlay.",
10876
+ "tagline": "React Aria floating panel anchored to a trigger — always compose with PopoverTrigger + PopoverContent; never use a raw div overlay.",
11351
10877
  "usage": [
10878
+ "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.",
11352
10879
  "DO compose: <Popover> → <PopoverTrigger asChild> → <Button/> and <PopoverContent>. All four parts are required for any popover to function; omitting PopoverTrigger or PopoverContent produces nothing.",
11353
10880
  "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.",
11354
10881
  "DO set `<PopoverContent flush>` when the panel holds a Command list, a menu or a table — the child owns its own inset, so its rows and separators reach the panel edges. Never zero the padding with a utility on className: that is a per-call-site constant no service theme can retune, while `flush` keeps the inset on --popover-space-inset.",
@@ -11356,9 +10883,10 @@
11356
10883
  "DO use controlled mode (open + onOpenChange) when external code must open/close the popover programmatically (e.g., form validation reveal, keyboard shortcut). For toggle-only interactions, uncontrolled (defaultOpen) is simpler.",
11357
10884
  "DO structure panel content with PopoverHeader > PopoverTitle + PopoverDescription for labelled panels. This is purely presentational but establishes the correct font-weight and muted-foreground on the description.",
11358
10885
  "DON'T hand-roll a floating div or use a CSS show/hide toggle — Popover provides portal rendering, focus trap, Escape-to-close, and ARIA automatically.",
11359
- "DON'T place a Popover inside a Dialog without setting modal={false} on the Popover — nested modals conflict with Radix's focus management and produce stuck focus."
10886
+ "DON'T place a Popover inside a Dialog without setting modal={false} on the Popover — nested modals conflict in focus management and produce stuck focus."
11360
10887
  ],
11361
10888
  "useCases": [
10889
+ "User/profile preview on an @mention, or an entity (customer/account) preview on a table cell — `openOn=\"hover\"`.",
11362
10890
  "Advanced filter panel: a Filters Button triggers a Popover containing filter inputs (date range, status selects); panel measured with `<PopoverContent width='auto'>` so the filters set the width, or `width='trigger'` to match the button.",
11363
10891
  "Row action menu overflow: when a DataTable row has too many actions for inline display, a Popover holds the secondary actions (Edit, Archive, Delete) without navigating away.",
11364
10892
  "Contextual help / tooltip-rich: a small '?' icon button opens a Popover with PopoverTitle + PopoverDescription explaining a form field — richer than a Tooltip but less intrusive than a Dialog.",
@@ -11441,11 +10969,11 @@
11441
10969
  24,
11442
10970
  31
11443
10971
  ],
11444
- "storyPath": "data-display/ScrollArea.stories.tsx",
10972
+ "storyPath": "data-display/scroll-area.tsx",
11445
10973
  "subParts": [
11446
10974
  "ScrollBar"
11447
10975
  ],
11448
- "tagline": "A native scrolling box (no Radix since v23): one `overflow: auto` element whose scrollbar is styled from --scroll-area-* tokens. Always set an explicit height/max-height, or nothing overflows and no scrollbar appears. Owns the scrolling element, so it also owns reaching it (viewportRef) and bottom anchoring for a live stream (anchor).",
10976
+ "tagline": "A native scrolling box (no scroll library since v23): one `overflow: auto` element whose scrollbar is styled from --scroll-area-* tokens. Always set an explicit height/max-height, or nothing overflows and no scrollbar appears. Owns the scrolling element, so it also owns reaching it (viewportRef) and bottom anchoring for a live stream (anchor).",
11449
10977
  "usage": [
11450
10978
  "DO set an explicit height or max-height on ScrollArea via className (e.g. `className=\"h-64\"` or `className=\"max-h-[min(300px,50vh)]\"`). Without a height constraint the viewport grows to fit content and the scrollbar is never rendered.",
11451
10979
  "DO wrap content in a single child element inside ScrollArea — the Viewport observes its single child's size to decide whether overflow exists.",
@@ -11506,13 +11034,13 @@
11506
11034
  },
11507
11035
  {
11508
11036
  "defaultValue": "false",
11509
- "description": "On CollapsibleTrigger only — merges Radix trigger behaviour onto the single child element (e.g. a godx-ui Button) instead of rendering a default button. The child must accept onClick and aria-* props.",
11037
+ "description": "On CollapsibleTrigger only — merges the trigger behaviour onto the single child element (e.g. a godx-ui Button) instead of rendering a default button. The child must accept onClick and aria-* props.",
11510
11038
  "name": "asChild (CollapsibleTrigger)",
11511
11039
  "type": "boolean"
11512
11040
  }
11513
11041
  ],
11514
11042
  "related": [
11515
- "Accordion (from @godxjp/ui/data-entry or Radix) — use Accordion when only ONE section can be open at a time across a group; use Collapsible when each section is independent and can be open simultaneously.",
11043
+ "Accordion (@godxjp/ui/data-display) — use Accordion when only ONE section can be open at a time across a group; use Collapsible when each section is independent and can be open simultaneously.",
11516
11044
  "Popover — use Popover when the revealed content should float above the layout in a portal overlay; use Collapsible when the content should push surrounding content down inline.",
11517
11045
  "Dialog/Sheet — use Dialog or Sheet for modal or slide-over panels that demand full user attention; Collapsible stays in-flow and non-modal.",
11518
11046
  "Tree (@godxjp/ui/data-display) — use Tree for hierarchical data that expands, collapses and is navigated by keyboard; use Collapsible for ad-hoc single-level toggle regions."
@@ -11522,7 +11050,7 @@
11522
11050
  6,
11523
11051
  23
11524
11052
  ],
11525
- "storyPath": "data-display/Collapsible.stories.tsx",
11053
+ "storyPath": "data-display/collapsible.tsx",
11526
11054
  "subParts": [
11527
11055
  "CollapsibleContent",
11528
11056
  "CollapsibleTrigger"
@@ -11533,7 +11061,7 @@
11533
11061
  "DO use asChild on CollapsibleTrigger when you want a godx-ui Button (or any styled element) to act as the trigger: <CollapsibleTrigger asChild><Button>Toggle</Button></CollapsibleTrigger>. Without asChild the trigger renders its own plain button.",
11534
11062
  "DO pass defaultOpen={true} (uncontrolled) when the section should auto-expand on mount — for example, a sidebar nav group whose active child matches the current route.",
11535
11063
  "DO use controlled mode (open + onOpenChange) when external UI — a separate button, route change, or search filter — needs to drive the open state independently of the trigger.",
11536
- "DON'T add hidden native <details>/<summary> as a fallback — the Radix primitive is already accessible (aria-expanded, aria-controls) out of the box.",
11064
+ "DON'T add hidden native <details>/<summary> as a fallback — the primitive is already accessible (aria-expanded, aria-controls) out of the box.",
11537
11065
  "DON'T put interactive controls (Buttons, links, inputs) inside CollapsibleTrigger itself unless using asChild — nested focusable elements break keyboard navigation. Put them inside CollapsibleContent instead."
11538
11066
  ],
11539
11067
  "useCases": [
@@ -11752,7 +11280,7 @@
11752
11280
  44,
11753
11281
  45
11754
11282
  ],
11755
- "storyPath": "data-display/Tree.stories.tsx",
11283
+ "storyPath": "data-display/tree.tsx",
11756
11284
  "tagline": "The standalone WAI-ARIA tree view on a page (antd `Tree` / `DirectoryTree`) — nested `treeData`, a disclosure triangle per branch, roving-tabindex arrow navigation, optional tri-state checkboxes, async `loadData` and `draggable` reorder/reparent (antd `onDrop`). Reach for it whenever nodes expand, collapse or are navigated by keyboard; TreeSelect is the same hierarchy inside a Popover.",
11757
11285
  "usage": [
11758
11286
  "DO name the tree — pass `aria-label` (through your own `t()`) or `aria-labelledby`. A tree with no name is an unlabelled landmark for every screen reader user.",
@@ -11805,13 +11333,13 @@
11805
11333
  },
11806
11334
  {
11807
11335
  "defaultValue": "false",
11808
- "description": "When true, the tooltip closes as soon as the pointer leaves the trigger (content is not hoverable). Passed through to Radix Root.",
11336
+ "description": "No effect on the React Aria base (kept for API compatibility): React Aria tooltips are never hoverable content, so they already close when the pointer leaves the trigger.",
11809
11337
  "name": "disableHoverableContent",
11810
11338
  "type": "boolean"
11811
11339
  },
11812
11340
  {
11813
11341
  "defaultValue": "\"top\"",
11814
- "description": "On TooltipContent — preferred side for the tooltip panel. Radix flips automatically when there is not enough space.",
11342
+ "description": "On TooltipContent — preferred side for the tooltip panel. It flips automatically when there is not enough space.",
11815
11343
  "name": "side",
11816
11344
  "type": "'top' | 'right' | 'bottom' | 'left'"
11817
11345
  },
@@ -11853,8 +11381,8 @@
11853
11381
  ],
11854
11382
  "related": [
11855
11383
  "Popover — use Popover (also @godxjp/ui/feedback) when the floating panel needs interactive content (forms, links, action menus) rather than read-only text. Tooltip is read-only; Popover is interactive.",
11856
- "HoverCard — for rich preview cards (user profiles, link previews) that appear on hover with more complex layout. Tooltip is for short text hints only.",
11857
- "Badge / StatusChip — for persistent, always-visible short labels inline with text; not hover-triggered. Use Tooltip when the hint should be hidden until hovered."
11384
+ "Popover — `openOn=\"hover\"` for rich preview cards (user profiles, link previews) that appear on hover with more complex layout. Tooltip is for short text hints only.",
11385
+ "Badge — for persistent, always-visible short labels inline with text; not hover-triggered. Use Tooltip when the hint should be hidden until hovered."
11858
11386
  ],
11859
11387
  "rules": [
11860
11388
  3,
@@ -11862,20 +11390,20 @@
11862
11390
  23,
11863
11391
  39
11864
11392
  ],
11865
- "storyPath": "feedback/Tooltip.stories.tsx",
11393
+ "storyPath": "feedback/tooltip.tsx",
11866
11394
  "subParts": [
11867
11395
  "TooltipContent",
11868
11396
  "TooltipProvider",
11869
11397
  "TooltipTrigger"
11870
11398
  ],
11871
- "tagline": "Radix-based hover/focus tooltip — self-providing, no app-level TooltipProvider required; compose Tooltip > TooltipTrigger > TooltipContent every time.",
11399
+ "tagline": "React Aria hover/focus tooltip — self-providing, no app-level TooltipProvider required; compose Tooltip > TooltipTrigger > TooltipContent every time.",
11872
11400
  "usage": [
11873
11401
  "DO compose the full three-part structure every time: <Tooltip> wraps <TooltipTrigger> (the element that triggers the tip) and <TooltipContent> (the floating panel). Omitting any part silently produces nothing.",
11874
- "DO NOT add an app-level <TooltipProvider> — every <Tooltip> self-provides its own Radix Provider. Only add <TooltipProvider> at a subtree root when you need a shared delayDuration different from the default 200ms across many tooltips.",
11402
+ "DO NOT add an app-level <TooltipProvider> — every <Tooltip> self-provides its own provider. Only add <TooltipProvider> at a subtree root when you need a shared delayDuration different from the default 200ms across many tooltips.",
11875
11403
  "DO use asChild on TooltipTrigger when the trigger is already a Button, IconButton, or other interactive element — this avoids nesting a <button> inside a <button>, which is invalid HTML and breaks keyboard focus.",
11876
- "DON'T put non-interactive elements (plain <div>, <span>) as the direct TooltipTrigger child without asChild — Radix needs a focusable element for keyboard accessibility. Wrap the target in a <span tabIndex={0}> or use a Button.",
11404
+ "DON'T put non-interactive elements (plain <div>, <span>) as the direct TooltipTrigger child without asChild — the trigger needs a focusable element for keyboard accessibility. Wrap the target in a <span tabIndex={0}> or use a Button.",
11877
11405
  "For controlled usage (e.g. programmatic show/hide or testing), pass open + onOpenChange to <Tooltip>. For typical hover/focus behaviour, leave both unset (uncontrolled).",
11878
- "TooltipContent renders inside a Radix Portal appended to document.body — z-index and overflow:hidden on ancestors do NOT clip it. Use className to extend max width beyond the built-in max-w-xs if long text is expected."
11406
+ "TooltipContent renders in a portal appended to document.body — z-index and overflow:hidden on ancestors do NOT clip it. Use className to extend max width beyond the built-in max-w-xs if long text is expected."
11879
11407
  ],
11880
11408
  "useCases": [
11881
11409
  "Explaining an icon-only Button action (e.g. a trash icon, a copy-to-clipboard icon) in a DataTable action column — show the label on hover without cluttering the row.",
@@ -11932,7 +11460,7 @@
11932
11460
  3,
11933
11461
  31
11934
11462
  ],
11935
- "storyPath": "data-display/PrefetchLink.stories.tsx",
11463
+ "storyPath": "react-router/prefetch-link.tsx",
11936
11464
  "tagline": "React Router Link that fires prefetchQuery on hover/focus so detail pages feel instant — requires a TanStack Query QueryClient in context. Imported from `@godxjp/ui/react-router` (not `/query`) because it imports the optional `react-router-dom` peer; in `/query` it broke every router-less app's build (gh#996).",
11937
11465
  "usage": [
11938
11466
  "DO: provide a queryKey that exactly matches the destination page's useQuery key — a mismatch means the prefetch populates a different cache slot and the page still loads cold.",
@@ -11974,16 +11502,6 @@
11974
11502
  "name": "appearance",
11975
11503
  "type": "\"default\" | \"tinted\""
11976
11504
  },
11977
- {
11978
- "description": "Which side of the trigger the panel opens on. Defaults from `appearance` — a bar drops the grid below, anything else opens to the inline-end. State it when the chrome can be RE-DOCKED: `appearance` says the trigger is not in a bar but cannot say which way is out, and a rail pinned to the top edge still opens downward. Ignored by responsive=\"fullscreen\" and by the Sheet surface, which are not anchored to the trigger.",
11979
- "name": "side",
11980
- "type": "\"top\" | \"right\" | \"bottom\" | \"left\""
11981
- },
11982
- {
11983
- "description": "How the panel aligns to the trigger. Defaults from `appearance` — end in a bar, start otherwise. Same rule as `side`: state it only for chrome that knows its own orientation.",
11984
- "name": "align",
11985
- "type": "\"start\" | \"center\" | \"end\""
11986
- },
11987
11505
  {
11988
11506
  "description": "Realtime reachability, drawn as an indicator at the block-end/inline-end corner of the mark. Ships the dot, its geometry tokens and a localized sr-only label TOGETHER, so presence is never colour-only (WCAG 1.4.1): each value also has its own silhouette — online = filled disc (--success), away = half-filled (--warning), busy = filled + a horizontal do-not-disturb bar (--destructive), offline = hollow ring (--muted-foreground). OMIT the prop for an entity with no presence concept (an organization mark, a capability medallion): no node and no attribute are emitted. `presence=\"offline\"` is the DIFFERENT, positive statement \"known to be unreachable\" — the same distinction ListRow draws between an omitted and a `false` `unread`.",
11989
11507
  "name": "presence",
@@ -12013,12 +11531,12 @@
12013
11531
  3,
12014
11532
  35
12015
11533
  ],
12016
- "storyPath": "data-display/Avatar.stories.tsx",
11534
+ "storyPath": "data-display/avatar.tsx",
12017
11535
  "subParts": [
12018
11536
  "AvatarFallback",
12019
11537
  "AvatarImage"
12020
11538
  ],
12021
- "tagline": "Radix Avatar wrapper with image and fallback slots for users, teams, and entities.",
11539
+ "tagline": "Avatar mark with image and fallback slots for users, teams, and entities.",
12022
11540
  "usage": [
12023
11541
  "DO compose Avatar > AvatarImage + AvatarFallback so broken or missing images still show a readable fallback.",
12024
11542
  "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.",
@@ -12106,7 +11624,7 @@
12106
11624
  44,
12107
11625
  45
12108
11626
  ],
12109
- "storyPath": "layout/Separator.stories.tsx",
11627
+ "storyPath": "layout/separator.tsx",
12110
11628
  "tagline": "Tokenized horizontal or vertical rule, optionally INTERRUPTED by a localized label (day divider, unread watermark, auth conjunction).",
12111
11629
  "usage": [
12112
11630
  "DO use Separator for section dividers instead of raw border divs.",
@@ -12127,7 +11645,13 @@
12127
11645
  ]
12128
11646
  },
12129
11647
  {
12130
- "example": "import { Skeleton } from \"@godxjp/ui/feedback\";\n\n<Skeleton className=\"h-6 w-48\" />\n\n// The same component is the antd namespace:\n<Skeleton.Button size=\"sm\" />",
11648
+ "absorbed": [
11649
+ "SkeletonAvatar",
11650
+ "SkeletonButton",
11651
+ "SkeletonNode",
11652
+ "SkeletonImage"
11653
+ ],
11654
+ "example": "import { Skeleton } from \"@godxjp/ui/feedback\";\n\n<Skeleton className=\"h-6 w-48\" />\n\n// The same component is the antd namespace:\n<Skeleton.Input size=\"sm\" />",
12131
11655
  "group": "feedback",
12132
11656
  "importPath": "@godxjp/ui/feedback",
12133
11657
  "name": "Skeleton",
@@ -12150,24 +11674,32 @@
12150
11674
  }
12151
11675
  ],
12152
11676
  "related": [
12153
- "SkeletonArticle",
12154
- "SkeletonRows",
12155
- "SkeletonTable",
12156
- "SkeletonStat",
12157
- "SkeletonButton",
12158
- "SkeletonInput",
12159
- "SkeletonAvatar",
12160
- "SkeletonNode",
12161
- "SkeletonImage",
12162
11677
  "Activity — the ambient 'working…' mark. Skeleton is a SHAPE standing in for content that has not arrived; Activity is motion beside content that is already there."
12163
11678
  ],
12164
11679
  "rules": [
12165
11680
  3,
12166
11681
  31
12167
11682
  ],
12168
- "storyPath": "feedback/Skeleton.stories.tsx",
12169
- "tagline": "Base pulsing skeleton block, and the namespace the shaped presets hang off (Skeleton.Avatar / .Button / .Input / .Node / .Image / .Article).",
12170
- "usage": [
11683
+ "storyPath": "feedback/skeleton.tsx",
11684
+ "subParts": [
11685
+ "SkeletonTable",
11686
+ "SkeletonRows",
11687
+ "SkeletonDetail",
11688
+ "SkeletonStat",
11689
+ "SkeletonArticle",
11690
+ "SkeletonInput",
11691
+ "SkeletonForm"
11692
+ ],
11693
+ "tagline": "Base pulsing skeleton block, the namespace the presets hang off (Skeleton.Input / .Article), and the ONE entry for every loading shape: SkeletonTable / SkeletonRows / SkeletonDetail / SkeletonStat / SkeletonArticle / SkeletonInput / SkeletonForm.",
11694
+ "usage": [
11695
+ "THE SHAPES (one family, one entry): SkeletonTable — Loading placeholder matching the DataTable layout (header + N rows). Drop-in while data loads (deferred props).",
11696
+ "SkeletonRows — Repeated flat loading lines on a responsive grid — the list-shaped placeholder when the final layout is rows of short bars, not a table header, not avatar + prose.",
11697
+ "SkeletonDetail — Fixed-shape placeholder for a RECORD page — title, subtitle, then a bordered box of six label/value pairs. Takes no props: the shape IS the contract, so every detail screen loads identically.",
11698
+ "SkeletonStat — Fixed-shape placeholder for ONE KPI tile — label, big value, caption. Takes no props, so a row of tiles loads at a uniform height and nothing reflows when the numbers arrive.",
11699
+ "SkeletonArticle — Avatar + heading line + paragraph placeholder — Ant Design's own <Skeleton> shape, for a comment, a profile block or a feed item.",
11700
+ "SkeletonInput — Field-shaped placeholder (also Skeleton.Input) — five control-heights wide.",
11701
+ "SkeletonForm — The skeleton of a `Form columns={N}` — label + control PAIRS on the form's own ResponsiveGrid, not the flat line list SkeletonRows draws.",
11702
+ "antd's Skeleton.Avatar / .Button / .Node / .Image presets are not components here (retired in v32, 0 users): the avatar box lives on as `SkeletonArticle avatar`, the control box as `SkeletonInput`; a media slot is a sized `Skeleton` block.",
12171
11703
  "DO use Skeleton for a custom block when SkeletonRows/Table/Stat/Article do not match the final layout.",
12172
11704
  "DO reach for a shaped preset before sizing a bare block by hand: SkeletonButton, SkeletonInput, SkeletonAvatar, SkeletonNode and SkeletonImage already carry the box of the control they stand in for, from the --control-height tier.",
12173
11705
  "DON'T use a spinner overlay for skeletonable page content.",
@@ -12180,428 +11712,7 @@
12180
11712
  ]
12181
11713
  },
12182
11714
  {
12183
- "example": "import { SkeletonDetail } from \"@godxjp/ui/feedback\";\n\n<SkeletonDetail />",
12184
- "group": "feedback",
12185
- "importPath": "@godxjp/ui/feedback",
12186
- "name": "SkeletonDetail",
12187
- "props": [],
12188
- "related": [
12189
- "SkeletonStat — the same propless treatment for a KPI tile.",
12190
- "SkeletonRows — flat list lines, not label/value pairs.",
12191
- "SkeletonTable — tabular placeholder with a header band.",
12192
- "Skeleton — the bare block this preset is built from; reach for it when you need a shape this one does not draw.",
12193
- "Descriptions — the component this placeholder is standing in for."
12194
- ],
12195
- "rules": [
12196
- 3,
12197
- 31
12198
- ],
12199
- "storyPath": "feedback/Skeleton.stories.tsx",
12200
- "tagline": "Fixed-shape placeholder for a RECORD page — title, subtitle, then a bordered box of six label/value pairs. Takes no props: the shape IS the contract, so every detail screen loads identically.",
12201
- "usage": [
12202
- "DO use it while a single record's data resolves — a detail page, a drawer showing one entity, a Descriptions block.",
12203
- "DO leave it propless. It has no `rows` knob on purpose: a record page that loads with a different number of lines each time reads as a layout bug, and `Skeleton` is there for a bespoke shape.",
12204
- "DON'T use it for a list or a table — SkeletonRows draws flat lines, SkeletonTable draws a header band.",
12205
- "Import from `@godxjp/ui/feedback` (canonical). `@godxjp/ui/admin` re-exports it for admin pages."
12206
- ],
12207
- "useCases": [
12208
- "Detail page body before the record arrives",
12209
- "Drawer or sheet opened on a row while its full entity loads",
12210
- "Descriptions block placeholder"
12211
- ]
12212
- },
12213
- {
12214
- "example": "import { SkeletonStat } from \"@godxjp/ui/feedback\";\nimport { ResponsiveGrid } from \"@godxjp/ui/layout\";\n\n<ResponsiveGrid columns={{ base: 1, sm: 2, lg: 4 }} gap=\"md\">\n {Array.from({ length: 4 }).map((_, i) => (\n <SkeletonStat key={i} />\n ))}\n</ResponsiveGrid>",
12215
- "group": "feedback",
12216
- "importPath": "@godxjp/ui/feedback",
12217
- "name": "SkeletonStat",
12218
- "props": [],
12219
- "related": [
12220
- "StatCard — the loaded component this stands in for; match the grid, not just the size.",
12221
- "SkeletonDetail — the same propless treatment for a record page.",
12222
- "SkeletonRows — flat list lines.",
12223
- "Skeleton — the bare block this preset is built from."
12224
- ],
12225
- "rules": [
12226
- 3,
12227
- 31
12228
- ],
12229
- "storyPath": "feedback/Skeleton.stories.tsx",
12230
- "tagline": "Fixed-shape placeholder for ONE KPI tile — label, big value, caption. Takes no props, so a row of tiles loads at a uniform height and nothing reflows when the numbers arrive.",
12231
- "usage": [
12232
- "DO render one per StatCard while the figures load, inside the same grid the loaded tiles use.",
12233
- "DO leave it propless — it mirrors StatCard's three-line rhythm exactly, which is what stops the row jumping on hydration.",
12234
- "DON'T wrap it in a Card yourself if the loaded state uses StatCard; put it where the StatCard goes so the surface is identical.",
12235
- "Import from `@godxjp/ui/feedback` (canonical). `@godxjp/ui/admin` re-exports it for admin pages."
12236
- ],
12237
- "useCases": [
12238
- "Dashboard KPI row before the metrics query resolves",
12239
- "A single figure on a detail page loading separately from the record",
12240
- "Placeholder inside a ResponsiveGrid of StatCards"
12241
- ]
12242
- },
12243
- {
12244
- "example": "import { SkeletonArticle } from \"@godxjp/ui/feedback\";\n\n<SkeletonArticle avatar active paragraph={{ rows: 3 }} />",
12245
- "group": "feedback",
12246
- "importPath": "@godxjp/ui/feedback",
12247
- "name": "SkeletonArticle",
12248
- "props": [
12249
- {
12250
- "defaultValue": "false",
12251
- "description": "Leading avatar placeholder. `true` takes the antd default matrix: a large circle, or a large SQUARE when there is a title but no paragraph (an entity header rather than a person).",
12252
- "name": "avatar",
12253
- "type": "boolean | { size?: 'xs' | 'sm' | 'md' | 'lg'; shape?: 'circle' | 'square' }"
12254
- },
12255
- {
12256
- "defaultValue": "true",
12257
- "description": "The heading LINE — not a string. `false` drops it; `{ width }` re-measures it. The default measure follows antd: 38% with a paragraph and no avatar, 50% with both, full width otherwise.",
12258
- "name": "title",
12259
- "type": "boolean | { width?: number | string }"
12260
- },
12261
- {
12262
- "defaultValue": "true",
12263
- "description": "The body lines. Default rows follow antd: 3 with a title and no avatar, else 2. A single `width` measures the LAST row; an array measures row by row. A number is read as pixels.",
12264
- "name": "paragraph",
12265
- "type": "boolean | { rows?: number; width?: number | string | (number | string)[] }"
12266
- },
12267
- {
12268
- "defaultValue": "false",
12269
- "description": "Pill corners on every line.",
12270
- "name": "round",
12271
- "type": "boolean"
12272
- },
12273
- {
12274
- "defaultValue": "false",
12275
- "description": "Travelling sheen instead of the resting pulse; descends to every line.",
12276
- "name": "active",
12277
- "type": "boolean"
12278
- },
12279
- {
12280
- "description": "Pass `false` to render `children` in place of the placeholder.",
12281
- "name": "loading",
12282
- "type": "boolean"
12283
- },
12284
- {
12285
- "description": "Extra classes on the article root.",
12286
- "name": "className",
12287
- "type": "string"
12288
- }
12289
- ],
12290
- "related": [
12291
- "SkeletonDetail — the house record-page shape (title + metadata pairs); SkeletonArticle is the avatar + prose shape.",
12292
- "SkeletonRows — repeated flat rows with no avatar or prose rhythm.",
12293
- "Skeleton — the bare block SkeletonArticle is built from."
12294
- ],
12295
- "rules": [
12296
- 3,
12297
- 31
12298
- ],
12299
- "storyPath": "feedback/Skeleton.stories.tsx",
12300
- "tagline": "Avatar + heading line + paragraph placeholder — Ant Design's own <Skeleton> shape, for a comment, a profile block or a feed item.",
12301
- "usage": [
12302
- "DO use SkeletonArticle where the loaded content is 'a person/entity beside a block of prose' — a comment, an activity item, a profile header, a notification row.",
12303
- "DO match `paragraph.rows` to the copy you expect so the layout does not jump on hydration.",
12304
- "DON'T use it for a table (SkeletonTable), a KPI tile (SkeletonStat) or a record page (SkeletonDetail) — those house shapes already match their targets.",
12305
- "DON'T nest it in a Card just to get a border; it draws no surface of its own."
12306
- ],
12307
- "useCases": [
12308
- "Comment thread loading state",
12309
- "Notification / activity feed placeholder",
12310
- "Profile header while the account record resolves",
12311
- "Chat or review list pre-mount placeholder"
12312
- ]
12313
- },
12314
- {
12315
- "example": "import { SkeletonAvatar } from \"@godxjp/ui/feedback\";\n\n<SkeletonAvatar size=\"lg\" shape=\"square\" />",
12316
- "group": "feedback",
12317
- "importPath": "@godxjp/ui/feedback",
12318
- "name": "SkeletonAvatar",
12319
- "props": [
12320
- {
12321
- "defaultValue": "\"md\"",
12322
- "description": "Box, from the same height tier the real Avatar sizes from.",
12323
- "name": "size",
12324
- "type": "\"xs\" | \"sm\" | \"md\" | \"lg\""
12325
- },
12326
- {
12327
- "defaultValue": "\"circle\"",
12328
- "description": "Circle for a person, square for an entity/service mark — Avatar's vocabulary.",
12329
- "name": "shape",
12330
- "type": "\"circle\" | \"square\""
12331
- },
12332
- {
12333
- "description": "Travelling sheen instead of the pulse.",
12334
- "name": "active",
12335
- "type": "boolean"
12336
- },
12337
- {
12338
- "description": "Extra classes.",
12339
- "name": "className",
12340
- "type": "string"
12341
- }
12342
- ],
12343
- "related": [
12344
- "Avatar",
12345
- "SkeletonArticle",
12346
- "Skeleton"
12347
- ],
12348
- "rules": [
12349
- 3,
12350
- 31
12351
- ],
12352
- "storyPath": "feedback/Skeleton.stories.tsx",
12353
- "tagline": "Avatar-shaped placeholder (also Skeleton.Avatar) sized from the --control-height tier.",
12354
- "usage": [
12355
- "DO pair the `shape` with the Avatar it replaces so the swap does not change the silhouette.",
12356
- "DON'T hand-size a bare Skeleton into a circle — this preset already carries the tier box."
12357
- ],
12358
- "useCases": [
12359
- "Member list loading rows",
12360
- "Comment author placeholder",
12361
- "Org switcher loading"
12362
- ]
12363
- },
12364
- {
12365
- "example": "import { SkeletonButton } from \"@godxjp/ui/feedback\";\n\n<SkeletonButton size=\"sm\" shape=\"pill\" />",
12366
- "group": "feedback",
12367
- "importPath": "@godxjp/ui/feedback",
12368
- "name": "SkeletonButton",
12369
- "props": [
12370
- {
12371
- "defaultValue": "\"md\"",
12372
- "description": "Height, from the --control-height tier Button sizes from.",
12373
- "name": "size",
12374
- "type": "\"xs\" | \"sm\" | \"md\" | \"lg\""
12375
- },
12376
- {
12377
- "defaultValue": "\"default\"",
12378
- "description": "Corner, in Button's own vocabulary. `pill` is Ant Design's shape=\"round\".",
12379
- "name": "shape",
12380
- "type": "\"default\" | \"pill\" | \"sharp\""
12381
- },
12382
- {
12383
- "defaultValue": "false",
12384
- "description": "Fill the inline axis instead of holding the control's own measure.",
12385
- "name": "block",
12386
- "type": "boolean"
12387
- },
12388
- {
12389
- "description": "Travelling sheen instead of the pulse.",
12390
- "name": "active",
12391
- "type": "boolean"
12392
- },
12393
- {
12394
- "description": "Extra classes.",
12395
- "name": "className",
12396
- "type": "string"
12397
- }
12398
- ],
12399
- "related": [
12400
- "Button",
12401
- "SkeletonInput",
12402
- "Skeleton"
12403
- ],
12404
- "rules": [
12405
- 3,
12406
- 31
12407
- ],
12408
- "storyPath": "feedback/Skeleton.stories.tsx",
12409
- "tagline": "Button-shaped placeholder (also Skeleton.Button) — two control-heights wide.",
12410
- "usage": [
12411
- "DO use it in a toolbar or card footer whose actions depend on data that has not arrived.",
12412
- "DO pass `block` when the real Button spans its column, so the row does not reflow on swap.",
12413
- "DON'T render a disabled Button as a loading state — a disabled control announces a different thing."
12414
- ],
12415
- "useCases": [
12416
- "Toolbar actions pre-mount",
12417
- "Card footer CTA placeholder",
12418
- "Form submit row"
12419
- ]
12420
- },
12421
- {
12422
- "example": "import { SkeletonInput } from \"@godxjp/ui/feedback\";\n\n<SkeletonInput block />",
12423
- "group": "feedback",
12424
- "importPath": "@godxjp/ui/feedback",
12425
- "name": "SkeletonInput",
12426
- "props": [
12427
- {
12428
- "defaultValue": "\"md\"",
12429
- "description": "Height, from the --control-height tier Input sizes from.",
12430
- "name": "size",
12431
- "type": "\"xs\" | \"sm\" | \"md\" | \"lg\""
12432
- },
12433
- {
12434
- "defaultValue": "false",
12435
- "description": "Fill the inline axis — the usual choice inside a FormField column.",
12436
- "name": "block",
12437
- "type": "boolean"
12438
- },
12439
- {
12440
- "description": "Travelling sheen instead of the pulse.",
12441
- "name": "active",
12442
- "type": "boolean"
12443
- },
12444
- {
12445
- "description": "Extra classes.",
12446
- "name": "className",
12447
- "type": "string"
12448
- }
12449
- ],
12450
- "related": [
12451
- "Input",
12452
- "FormField",
12453
- "SkeletonButton",
12454
- "Skeleton"
12455
- ],
12456
- "rules": [
12457
- 3,
12458
- 31
12459
- ],
12460
- "storyPath": "feedback/Skeleton.stories.tsx",
12461
- "tagline": "Field-shaped placeholder (also Skeleton.Input) — five control-heights wide.",
12462
- "usage": [
12463
- "DO use it for a form whose initial values are still loading, one per field.",
12464
- "DON'T render a real Input with a spinner inside it; an empty field invites typing that will be overwritten."
12465
- ],
12466
- "useCases": [
12467
- "Edit form before the record resolves",
12468
- "Filter bar pre-mount",
12469
- "Settings panel"
12470
- ]
12471
- },
12472
- {
12473
- "example": "import { SkeletonForm } from \"@godxjp/ui/feedback\";\n\n{loading ? <SkeletonForm columns={4} fields={8} /> : <Form columns={4}>…</Form>}",
12474
- "group": "feedback",
12475
- "importPath": "@godxjp/ui/feedback",
12476
- "name": "SkeletonForm",
12477
- "props": [
12478
- {
12479
- "defaultValue": "1",
12480
- "description": "Columns of the form this stands in for. Passed straight to ResponsiveGrid, so it is the SAME value and the SAME breakpoint ladder the Form uses — that shared source is the point of the component.",
12481
- "name": "columns",
12482
- "type": "number"
12483
- },
12484
- {
12485
- "defaultValue": "6",
12486
- "description": "How many label+control pairs to draw.",
12487
- "name": "fields",
12488
- "type": "number"
12489
- },
12490
- {
12491
- "description": "Travelling sheen instead of the pulse.",
12492
- "name": "active",
12493
- "type": "boolean"
12494
- },
12495
- {
12496
- "description": "Extra classes.",
12497
- "name": "className",
12498
- "type": "string"
12499
- }
12500
- ],
12501
- "related": [
12502
- "Form — the thing this stands in for; give both the same `columns`.",
12503
- "FormField — the label+control pair each cell here draws.",
12504
- "SkeletonRows — a FLAT list of lines, for a list. Not a form.",
12505
- "SkeletonInput — one field's control on its own, when you are composing something else."
12506
- ],
12507
- "rules": [
12508
- 3,
12509
- 31
12510
- ],
12511
- "storyPath": "feedback/Skeleton.stories.tsx",
12512
- "tagline": "The skeleton of a `Form columns={N}` — label + control PAIRS on the form's own ResponsiveGrid, not the flat line list SkeletonRows draws.",
12513
- "usage": [
12514
- "DO pass the same `columns` the Form takes. Anything else and the layout jumps when the data lands.",
12515
- "DO use it instead of SkeletonRows for a form: a field is a short label stacked on a full-width control, and SkeletonRows has no idea a cell has an inside.",
12516
- "DON'T hand-assemble SkeletonInput inside a ResponsiveGrid to approximate one — that is what this replaces, and 'close enough' drifts the first time someone changes the Form's columns."
12517
- ],
12518
- "useCases": [
12519
- "An edit form before the record resolves",
12520
- "A multi-column settings panel on first paint",
12521
- "A wizard step whose defaults arrive from the server"
12522
- ]
12523
- },
12524
- {
12525
- "example": "import { SkeletonNode } from \"@godxjp/ui/feedback\";\n\n<SkeletonNode />",
12526
- "group": "feedback",
12527
- "importPath": "@godxjp/ui/feedback",
12528
- "name": "SkeletonNode",
12529
- "props": [
12530
- {
12531
- "description": "Centred content, e.g. a glyph.",
12532
- "name": "children",
12533
- "type": "ReactNode"
12534
- },
12535
- {
12536
- "description": "Travelling sheen instead of the pulse.",
12537
- "name": "active",
12538
- "type": "boolean"
12539
- },
12540
- {
12541
- "description": "Extra classes.",
12542
- "name": "className",
12543
- "type": "string"
12544
- }
12545
- ],
12546
- "related": [
12547
- "SkeletonImage",
12548
- "Skeleton"
12549
- ],
12550
- "rules": [
12551
- 3,
12552
- 31
12553
- ],
12554
- "storyPath": "feedback/Skeleton.stories.tsx",
12555
- "tagline": "Square media/custom placeholder (also Skeleton.Node); children centre inside it.",
12556
- "usage": [
12557
- "DO use it as the generic square when no other preset matches — a chart slot, a map tile, a QR code.",
12558
- "DON'T put text in it; it is a shape, and its container is already aria-busy."
12559
- ],
12560
- "useCases": [
12561
- "Chart panel placeholder",
12562
- "Map / canvas slot",
12563
- "QR or barcode slot"
12564
- ]
12565
- },
12566
- {
12567
- "example": "import { SkeletonImage } from \"@godxjp/ui/feedback\";\n\n<SkeletonImage active />",
12568
- "group": "feedback",
12569
- "importPath": "@godxjp/ui/feedback",
12570
- "name": "SkeletonImage",
12571
- "props": [
12572
- {
12573
- "description": "Travelling sheen instead of the pulse.",
12574
- "name": "active",
12575
- "type": "boolean"
12576
- },
12577
- {
12578
- "description": "Extra classes.",
12579
- "name": "className",
12580
- "type": "string"
12581
- }
12582
- ],
12583
- "related": [
12584
- "SkeletonNode",
12585
- "Skeleton"
12586
- ],
12587
- "rules": [
12588
- 3,
12589
- 31
12590
- ],
12591
- "storyPath": "feedback/Skeleton.stories.tsx",
12592
- "tagline": "SkeletonNode carrying the image glyph (also Skeleton.Image).",
12593
- "usage": [
12594
- "DO use it where the loaded content is a picture, so the placeholder says WHICH kind of content is coming.",
12595
- "DON'T use it for a generic square — that is SkeletonNode."
12596
- ],
12597
- "useCases": [
12598
- "Gallery grid loading",
12599
- "Product thumbnail",
12600
- "Attachment preview"
12601
- ]
12602
- },
12603
- {
12604
- "example": "import { Toggle } from \"@godxjp/ui/data-entry\";\n\n<Toggle aria-label=\"Bold\">B</Toggle>\n\n// A counted filter chip — one control, one accessible name (\"Unread, 12 items\").\n// Drive the state with the usual Radix pair: pressed / defaultPressed / onPressedChange.\n// variant=\"soft\" + shape=\"pill\" is the antd Tag.CheckableTag shape: it has a RESTING fill, so\n// the chip is legible before it is pressed.\n<Toggle\n variant=\"soft\"\n shape=\"pill\"\n onPressedChange={setUnreadOnly}\n count={12}\n countLabel={t(\"common.items\")}\n>\n {t(\"inbox.unread\")}\n</Toggle>",
11715
+ "example": "import { Toggle } from \"@godxjp/ui/data-entry\";\n\n<Toggle aria-label=\"Bold\">B</Toggle>\n\n// A counted filter chip — one control, one accessible name (\"Unread, 12 items\").\n// Drive the state with the usual pair: pressed / defaultPressed / onPressedChange.\n// variant=\"soft\" + shape=\"pill\" is the antd Tag.CheckableTag shape: it has a RESTING fill, so\n// the chip is legible before it is pressed.\n<Toggle\n variant=\"soft\"\n shape=\"pill\"\n onPressedChange={setUnreadOnly}\n count={12}\n countLabel={t(\"common.items\")}\n>\n {t(\"inbox.unread\")}\n</Toggle>",
12605
11716
  "group": "data-entry",
12606
11717
  "importPath": "@godxjp/ui/data-entry",
12607
11718
  "name": "Toggle",
@@ -12667,8 +11778,8 @@
12667
11778
  13,
12668
11779
  45
12669
11780
  ],
12670
- "storyPath": "data-entry/Toggle.stories.tsx",
12671
- "tagline": "Radix Toggle wrapper with default/outline variants and tokenized sizes.",
11781
+ "storyPath": "data-entry/toggle.tsx",
11782
+ "tagline": "React Aria toggle button with default/outline/soft variants and tokenized sizes.",
12672
11783
  "usage": [
12673
11784
  "DO provide an accessible label when the toggle only contains an icon.",
12674
11785
  "DON'T use Toggle for multi-option selection; use ToggleGroup.",
@@ -12767,7 +11878,7 @@
12767
11878
  3,
12768
11879
  13
12769
11880
  ],
12770
- "storyPath": "data-entry/ToggleGroup.stories.tsx",
11881
+ "storyPath": "data-entry/toggle-group.tsx",
12771
11882
  "subParts": [
12772
11883
  "ToggleGroupItem"
12773
11884
  ],
@@ -12818,8 +11929,8 @@
12818
11929
  2,
12819
11930
  3
12820
11931
  ],
12821
- "storyPath": "layout/AspectRatio.stories.tsx",
12822
- "tagline": "Radix AspectRatio wrapper for stable media and preview frames.",
11932
+ "storyPath": "layout/aspect-ratio.tsx",
11933
+ "tagline": "Fixed-ratio box (CSS aspect-ratio) for stable media and preview frames.",
12823
11934
  "usage": [
12824
11935
  "DO use AspectRatio for media, maps, charts, or previews that must not jump during load.",
12825
11936
  "DON'T use it for unconstrained text content."
@@ -12848,101 +11959,46 @@
12848
11959
  "type": "boolean"
12849
11960
  },
12850
11961
  {
12851
- "description": "Controlled open item(s).",
12852
- "name": "value",
12853
- "type": "string | string[]"
12854
- },
12855
- {
12856
- "description": "Uncontrolled initial open item(s).",
12857
- "name": "defaultValue",
12858
- "type": "string | string[]"
12859
- },
12860
- {
12861
- "description": "Open-state callback.",
12862
- "name": "onValueChange",
12863
- "type": "(value: string | string[]) => void"
12864
- }
12865
- ],
12866
- "related": [
12867
- "Collapsible (single open/close region, no item set)",
12868
- "Tabs (mutually-exclusive views, always one visible)"
12869
- ],
12870
- "rules": [
12871
- 3,
12872
- 6
12873
- ],
12874
- "storyPath": "data-display/Accordion.stories.tsx",
12875
- "subParts": [
12876
- "AccordionContent",
12877
- "AccordionItem",
12878
- "AccordionTrigger"
12879
- ],
12880
- "tagline": "Radix accordion — vertically stacked, collapsible sections. Compose Accordion > AccordionItem > AccordionTrigger + AccordionContent.",
12881
- "usage": [
12882
- "DO compose the full set: <Accordion type=\"single\" collapsible><AccordionItem value=\"a\"><AccordionTrigger/><AccordionContent/></AccordionItem></Accordion>.",
12883
- "DO give each AccordionItem a unique `value`.",
12884
- "DON'T use it for primary navigation — that's Sidebar/Tabs. Accordion is for collapsible content/FAQ."
12885
- ],
12886
- "useCases": [
12887
- "FAQ lists",
12888
- "Grouped settings sections",
12889
- "Collapsible detail panels on a record page",
12890
- "Filter facet groups in a sidebar"
12891
- ]
12892
- },
12893
- {
12894
- "example": "import { HoverCard, HoverCardTrigger, HoverCardContent } from \"@godxjp/ui/data-display\";\n\n<HoverCard>\n <HoverCardTrigger>@yamada</HoverCardTrigger>\n <HoverCardContent>山田太郎 — 経理部</HoverCardContent>\n</HoverCard>",
12895
- "group": "data-display",
12896
- "importPath": "@godxjp/ui/data-display",
12897
- "name": "HoverCard",
12898
- "props": [
12899
- {
12900
- "defaultValue": "700",
12901
- "description": "ms before opening on hover.",
12902
- "name": "openDelay",
12903
- "type": "number"
12904
- },
12905
- {
12906
- "defaultValue": "300",
12907
- "description": "ms before closing.",
12908
- "name": "closeDelay",
12909
- "type": "number"
11962
+ "description": "Controlled open item(s).",
11963
+ "name": "value",
11964
+ "type": "string | string[]"
12910
11965
  },
12911
11966
  {
12912
- "description": "Controlled open state.",
12913
- "name": "open",
12914
- "type": "boolean"
11967
+ "description": "Uncontrolled initial open item(s).",
11968
+ "name": "defaultValue",
11969
+ "type": "string | string[]"
12915
11970
  },
12916
11971
  {
12917
11972
  "description": "Open-state callback.",
12918
- "name": "onOpenChange",
12919
- "type": "(open: boolean) => void"
11973
+ "name": "onValueChange",
11974
+ "type": "(value: string | string[]) => void"
12920
11975
  }
12921
11976
  ],
12922
11977
  "related": [
12923
- "Tooltip (short text label, not rich content)",
12924
- "Popover (click-triggered, interactive content)"
11978
+ "Collapsible (single open/close region, no item set)",
11979
+ "Tabs (mutually-exclusive views, always one visible)"
12925
11980
  ],
12926
11981
  "rules": [
12927
11982
  3,
12928
11983
  6
12929
11984
  ],
12930
- "storyPath": "data-display/HoverCard.stories.tsx",
11985
+ "storyPath": "data-display/accordion.tsx",
12931
11986
  "subParts": [
12932
- "HoverCardContent",
12933
- "HoverCardTrigger"
11987
+ "AccordionContent",
11988
+ "AccordionItem",
11989
+ "AccordionTrigger"
12934
11990
  ],
12935
- "tagline": "Radix hover card — a rich popover shown on hover/focus of a trigger (for sighted-pointer affordances; not a replacement for Tooltip's short text).",
11991
+ "tagline": "React Aria disclosure group — vertically stacked, collapsible sections. Compose Accordion > AccordionItem > AccordionTrigger + AccordionContent.",
12936
11992
  "usage": [
12937
- "DO compose HoverCard > HoverCardTrigger > HoverCardContent.",
12938
- "DO use for RICH preview content (a card, avatar + bio); for short plain-text hints use Tooltip.",
12939
- "DON'T rely on it for essential info — hover isn't available on touch; provide the same content on click/tap elsewhere."
11993
+ "DO compose the full set: <Accordion type=\"single\" collapsible><AccordionItem value=\"a\"><AccordionTrigger/><AccordionContent/></AccordionItem></Accordion>.",
11994
+ "DO give each AccordionItem a unique `value`.",
11995
+ "DON'T use it for primary navigation — that's Sidebar/Tabs. Accordion is for collapsible content/FAQ."
12940
11996
  ],
12941
11997
  "useCases": [
12942
- "User/profile preview on @mention hover",
12943
- "Entity preview (customer/account) on a table cell",
12944
- "Glossary term definitions",
12945
- "Commit/PR preview links"
11998
+ "FAQ lists",
11999
+ "Grouped settings sections",
12000
+ "Collapsible detail panels on a record page",
12001
+ "Filter facet groups in a sidebar"
12946
12002
  ]
12947
12003
  },
12948
12004
  {
@@ -13011,7 +12067,7 @@
13011
12067
  3,
13012
12068
  6
13013
12069
  ],
13014
- "storyPath": "data-entry/PasswordInput.stories.tsx",
12070
+ "storyPath": "data-entry/password-input.tsx",
13015
12071
  "tagline": "Input for passwords with a built-in show/hide eye toggle. Accepts all Input props except `type`.",
13016
12072
  "usage": [
13017
12073
  "DO use for any password / secret field instead of `<Input type=\"password\">` so users get the show/hide affordance.",
@@ -13068,7 +12124,7 @@
13068
12124
  3,
13069
12125
  6
13070
12126
  ],
13071
- "storyPath": "data-entry/PasswordStrength.stories.tsx",
12127
+ "storyPath": "data-entry/password-strength.tsx",
13072
12128
  "tagline": "Evaluates password quality with a 0-4 score and optional rule checklist. Use with PasswordInput in secure forms.",
13073
12129
  "usage": [
13074
12130
  "DO show PasswordStrength immediately below PasswordInput so users understand password quality before submitting.",
@@ -13198,7 +12254,7 @@
13198
12254
  3,
13199
12255
  6
13200
12256
  ],
13201
- "storyPath": "data-entry/InputOTP.stories.tsx",
12257
+ "storyPath": "data-entry/input-otp.tsx",
13202
12258
  "subParts": [
13203
12259
  "InputOTPGroup",
13204
12260
  "InputOTPSeparator",
@@ -13212,7 +12268,7 @@
13212
12268
  "For device codes, set `appearance='grouped'` on each InputOTPGroup to render one outline per group while preserving the single hidden input, paste, caret, keyboard and screen-reader behavior.",
13213
12269
  "DON'T build N separate Inputs — this is ONE field with paste, arrow-key, and caret handling built in.",
13214
12270
  "DO widen the slots with `--otp-slot-size` when a challenge row must fill a wide auth panel — it defaults to the live `--control-height` tier, so re-scoping `--control-height` on the card instead would also resize the submit button and every other input in it. Set a NAMED tier (`var(--control-height-lg)`), never an ad-hoc calc offset.",
13215
- "DO use `--otp-slot-inline-size` / `--otp-slot-block-size` when the code field is NOT square — a device-grant slot is taller than it is wide. They win over the `--otp-slot-size` shorthand and fall back to it, so setting neither keeps the square control tier. You rarely set them by hand inside `AuthShell preset=\"device-authorization\"`: that preset already owns its code-field measure.",
12271
+ "DO use `--otp-slot-inline-size` / `--otp-slot-block-size` when the code field is NOT square — a device-grant slot is taller than it is wide. They win over the `--otp-slot-size` shorthand and fall back to it, so setting neither keeps the square control tier. You rarely set them by hand inside `the auth CenteredShell preset=\"device-authorization\"`: that preset already owns its code-field measure.",
13216
12272
  "DO drive the sign-in MFA challenge from FormField: `error` wires aria-invalid + aria-errormessage + a role=alert message onto the single field, and the slot borders turn destructive. State is never colour-only."
13217
12273
  ],
13218
12274
  "useCases": [
@@ -13298,7 +12354,7 @@
13298
12354
  6,
13299
12355
  23
13300
12356
  ],
13301
- "storyPath": "data-entry/Rating.stories.tsx",
12357
+ "storyPath": "data-entry/rating.tsx",
13302
12358
  "tagline": "Star-rating input (radiogroup) — controlled via value/onValueChange, form-submittable via name, supports readOnly display.",
13303
12359
  "usage": [
13304
12360
  "DO theme the stars with `--rating-star-filled-color` (default `var(--warning)`) and `--rating-star-empty-color` (default `var(--muted-foreground)`, at `--rating-star-empty-alpha` 0.45) — HSL components, set on :root or a scoped [data-tenant] (gh#694). NEVER override `.ui-rating-star-filled`: it is an internal class. Unset, the stars paint exactly as before.",
@@ -13390,7 +12446,7 @@
13390
12446
  6,
13391
12447
  23
13392
12448
  ],
13393
- "storyPath": "data-entry/Segmented.stories.tsx",
12449
+ "storyPath": "data-entry/segmented.tsx",
13394
12450
  "tagline": "One-of-N from a small, closed, always-visible set — the enterprise Segmented / filter bar drawn on react-aria-components' RadioGroup. A track with the chosen item as a lifted slab. Reach for it INSTEAD OF a Select when there are 2-4 options and all of them fit on screen, and instead of ToggleGroup when exactly one must always be chosen.",
13395
12451
  "usage": [
13396
12452
  "DO use it for a closed set of 2-4 peer choices that are cheap to show — theme, view mode, a date range preset.",
@@ -13410,111 +12466,6 @@
13410
12466
  "Status filter above a list, each option carrying its count (All 128 / Active 96 / Pending 12 / Archived 0)"
13411
12467
  ]
13412
12468
  },
13413
- {
13414
- "example": "import { TagInput } from \"@godxjp/ui/data-entry\";\n\n<TagInput name=\"labels\" placeholder=\"ラベルを追加…\" onValueChange={(tags) => setTags(tags)} />",
13415
- "group": "data-entry",
13416
- "importPath": "@godxjp/ui/data-entry",
13417
- "name": "TagInput",
13418
- "props": [
13419
- {
13420
- "description": "Prevent adding and removing tags.",
13421
- "name": "readOnly",
13422
- "type": "boolean"
13423
- },
13424
- {
13425
- "description": "Controlled tag list.",
13426
- "name": "value",
13427
- "type": "string[]"
13428
- },
13429
- {
13430
- "description": "Uncontrolled initial tags.",
13431
- "name": "defaultValue",
13432
- "type": "string[]"
13433
- },
13434
- {
13435
- "description": "Tag-list callback.",
13436
- "name": "onValueChange",
13437
- "type": "(tags: string[]) => void"
13438
- },
13439
- {
13440
- "description": "Shown when empty.",
13441
- "name": "placeholder",
13442
- "type": "string"
13443
- },
13444
- {
13445
- "description": "Hidden input (comma-joined) for native form submission.",
13446
- "name": "name",
13447
- "type": "string"
13448
- },
13449
- {
13450
- "description": "antd `status`. `error` recolours the control AND sets aria-invalid (a colour-only error fails WCAG 2.2 SC 1.4.1); `warning` recolours only. An aria-invalid injected by FormField always wins.",
13451
- "name": "status",
13452
- "type": "\"error\" | \"warning\""
13453
- },
13454
- {
13455
- "description": "antd `variant` — the control surface. Default `outlined`. Drawn from --control-{surface,filled,borderless}-* tokens, so a theme retunes all three at once.",
13456
- "name": "variant",
13457
- "type": "\"outlined\" | \"filled\" | \"borderless\""
13458
- },
13459
- {
13460
- "description": "antd `size` — height tier on the shared --control-height ladder.",
13461
- "name": "size",
13462
- "type": "\"xs\" | \"sm\" | \"md\" | \"lg\""
13463
- },
13464
- {
13465
- "description": "antd `maxTagCount` — how many selected values stay visible before the rest collapse into the overflow node. antd's `\"responsive\"` is not supported (see the parity PR).",
13466
- "name": "maxTagCount",
13467
- "type": "number"
13468
- },
13469
- {
13470
- "description": "antd `maxTagPlaceholder` — the node standing in for what maxTagCount hid. Defaults to a localized `+N`.",
13471
- "name": "maxTagPlaceholder",
13472
- "type": "React.ReactNode | ((omitted: { value: string; label: React.ReactNode }[]) => React.ReactNode)"
13473
- },
13474
- {
13475
- "description": "antd `maxCount` — a hard ceiling on how many tags may be held. A tag past the limit is refused, so the value handed to onValueChange is never over it.",
13476
- "name": "maxCount",
13477
- "type": "number"
13478
- },
13479
- {
13480
- "description": "antd `maxTagTextLength` — cut each chip's TEXT to this many characters (an ellipsis marks the cut). The value is untouched and stays in the chip's title and in the remover's accessible name, so a long identifier is never silently swallowed. Without it one unbroken token sets the chip's width and the chip sets the row's (gh#840).",
13481
- "name": "maxTagTextLength",
13482
- "type": "number"
13483
- },
13484
- {
13485
- "description": "antd `tagRender` — owns the chip body. The onClose it receives is the same remover the built-in ✕ calls, so a custom chip can never be unremovable.",
13486
- "name": "tagRender",
13487
- "type": "(props: { value: string; label: React.ReactNode; onClose: () => void; index: number; disabled: boolean }) => React.ReactNode"
13488
- },
13489
- {
13490
- "description": "antd `tokenSeparators` (default [\",\"]) — characters that commit the draft into a tag. A pasted run containing one is split into several tags. Enter always commits and is not a separator.",
13491
- "name": "tokenSeparators",
13492
- "type": "string[]"
13493
- }
13494
- ],
13495
- "related": [
13496
- "Select (multiple) — when the values come from a fixed option set",
13497
- "Combobox (multi) — searchable known set"
13498
- ],
13499
- "rules": [
13500
- 3,
13501
- 6,
13502
- 23
13503
- ],
13504
- "storyPath": "data-entry/TagInput.stories.tsx",
13505
- "tagline": "Chips/tags input — type + Enter (or comma) to add a tag, Backspace to remove the last; controlled via value/onValueChange (string[]).",
13506
- "usage": [
13507
- "DO use for free-form multi-value entry (labels, emails, keywords) where options aren't a fixed list.",
13508
- "DO note dedupe is built in; Enter/comma commits, Backspace on empty removes the last chip.",
13509
- "DON'T use for choosing from a KNOWN set — use Select (multiple) or a multi-Combobox instead."
13510
- ],
13511
- "useCases": [
13512
- "Labels / tags on a record",
13513
- "Recipient email entry",
13514
- "Keyword / skill lists",
13515
- "Ad-hoc filter terms"
13516
- ]
13517
- },
13518
12469
  {
13519
12470
  "example": "import { ResizablePanelGroup, ResizablePanel, ResizableHandle } from \"@godxjp/ui/layout\";\n\n<ResizablePanelGroup orientation=\"horizontal\">\n <ResizablePanel id=\"list\" defaultSize=\"35%\" minSize=\"20%\">Panel A</ResizablePanel>\n <ResizableHandle />\n <ResizablePanel id=\"detail\" defaultSize=\"65%\" minSize=\"40%\">Panel B</ResizablePanel>\n</ResizablePanelGroup>",
13520
12471
  "group": "layout",
@@ -13563,7 +12514,7 @@
13563
12514
  3,
13564
12515
  6
13565
12516
  ],
13566
- "storyPath": "layout/ResizablePanel.stories.tsx",
12517
+ "storyPath": "layout/resizable-panel.tsx",
13567
12518
  "subParts": [
13568
12519
  "ResizableHandle",
13569
12520
  "ResizablePanelGroup"
@@ -13585,9 +12536,9 @@
13585
12536
  ]
13586
12537
  },
13587
12538
  {
13588
- "example": "import { Carousel, CarouselContent, CarouselItem, CarouselPrevious, CarouselNext, CarouselDots } from \"@godxjp/ui/data-display\";\n\n// CarouselDots reads the Embla api from context — no setApi wiring needed.\n<Carousel opts={{ loop: true }}>\n <CarouselContent>\n <CarouselItem>1</CarouselItem>\n <CarouselItem>2</CarouselItem>\n </CarouselContent>\n <CarouselPrevious />\n <CarouselNext />\n <CarouselDots />\n</Carousel>",
12539
+ "example": "import { Carousel, CarouselContent, CarouselItem, CarouselPrevious, CarouselNext, CarouselDots } from \"@godxjp/ui/lab\";\n\n// CarouselDots reads the Embla api from context — no setApi wiring needed.\n<Carousel opts={{ loop: true }}>\n <CarouselContent>\n <CarouselItem>1</CarouselItem>\n <CarouselItem>2</CarouselItem>\n </CarouselContent>\n <CarouselPrevious />\n <CarouselNext />\n <CarouselDots />\n</Carousel>",
13589
12540
  "group": "data-display",
13590
- "importPath": "@godxjp/ui/data-display",
12541
+ "importPath": "@godxjp/ui/lab",
13591
12542
  "name": "Carousel",
13592
12543
  "props": [
13593
12544
  {
@@ -13610,7 +12561,7 @@
13610
12561
  3,
13611
12562
  6
13612
12563
  ],
13613
- "storyPath": "data-display/Carousel.stories.tsx",
12564
+ "storyPath": "data-display/carousel.tsx",
13614
12565
  "subParts": [
13615
12566
  "CarouselContent",
13616
12567
  "CarouselDots",
@@ -13619,6 +12570,7 @@
13619
12570
  "CarouselPrevious"
13620
12571
  ],
13621
12572
  "tagline": "Embla-backed carousel primitives: previous/next controls, CarouselDots indicators, and a context API.",
12573
+ "tier": "lab",
13622
12574
  "usage": [
13623
12575
  "DO compose the full set: `<Carousel>` › `<CarouselContent>` › many `<CarouselItem>`, with `<CarouselPrevious>`/`<CarouselNext>` for arrows and `<CarouselDots>` for the indicator row. Don't render items outside `<CarouselContent>` — the track is the scroll container.",
13624
12576
  "DO use `<CarouselDots>` for the active-slide indicator instead of wiring `setApi` by hand — it reads `selectedIndex`/`scrollSnaps` from the Carousel context, renders one `aria-current` dot per snap, and auto-hides when there is ≤1 slide.",
@@ -13645,13 +12597,20 @@
13645
12597
  "DateFormatPicker",
13646
12598
  "TimeFormatPicker",
13647
12599
  "ThemePicker",
13648
- "DensityPicker"
12600
+ "DensityPicker",
12601
+ "AppSettingToggle"
13649
12602
  ],
13650
12603
  "example": "{`// Uncontrolled — AppProvider manages and persists every setting\nimport { AppProvider } from \"@godxjp/ui/app\";\nimport { AppSettingPicker } from \"@godxjp/ui/navigation\";\n\nexport function SettingsPanel() {\n return (\n <AppProvider defaultLocale=\"ja\" defaultTimezone=\"Asia/Tokyo\">\n <AppSettingPicker kind=\"locale\" />\n <AppSettingPicker kind=\"timezone\" />\n <AppSettingPicker kind=\"dateFormat\" />\n <AppSettingPicker kind=\"timeFormat\" />\n </AppProvider>\n );\n}\n\n// Controlled — no AppProvider required\nimport { useState } from \"react\";\nimport { AppSettingPicker } from \"@godxjp/ui/navigation\";\n\nexport function LocaleField() {\n const [locale, setLocale] = useState(\"en\");\n return <AppSettingPicker kind=\"locale\" value={locale} onValueChange={setLocale} />;\n}\n\n// Topbar locale switcher (globe) — a cell OF the bar, no CSS overrides\nimport { Topbar } from \"@godxjp/ui/layout\";\nimport { AppSettingPicker } from \"@godxjp/ui/navigation\";\n\nexport function TopbarLocale() {\n // \"bar\" inside a Topbar slot; \"icon\" everywhere else.\n return <Topbar end={<AppSettingPicker kind=\"locale\" appearance=\"bar\" />} />;\n}`}",
13651
12604
  "group": "navigation",
13652
12605
  "importPath": "@godxjp/ui/navigation",
13653
12606
  "name": "AppSettingPicker",
13654
12607
  "props": [
12608
+ {
12609
+ "defaultValue": "true",
12610
+ "description": "true (default): open-then-choose from a menu. false: ONE button that steps the setting to its NEXT value on each tap and shows that value as its glyph (theme Sun/Moon/Monitor, density rows, fontSize A-marks, timeFormat text) — only for the closed, short kinds \"theme\" | \"density\" | \"fontSize\" | \"timeFormat\" (a 400-entry IANA list is a menu, not a cycle). Same binding contract and the same option order as the menu, read from the same constants. Its `appearance` is then \"bar\" (default — a full-height TopbarItem cell) | \"icon\" (a square ghost Button off-bar). The accessible name carries the setting AND the current value (\"Theme: Dark\"). v32 folded `AppSettingToggle` in here.",
12611
+ "name": "menu",
12612
+ "type": "boolean"
12613
+ },
13655
12614
  {
13656
12615
  "description": "Which AppProvider setting this picker reads and writes. Determines the option list, icon, trigger width, and the context value/setter used. The theme-axis kinds (theme/brand/density/fontSize) write <html data-*>; brand's first option opts out (null → app token).",
13657
12616
  "name": "kind",
@@ -13705,9 +12664,14 @@
13705
12664
  6,
13706
12665
  23
13707
12666
  ],
13708
- "storyPath": "navigation/AppSettingPicker.stories.tsx",
12667
+ "storyPath": "navigation/app-setting-picker.tsx",
13709
12668
  "tagline": "One provider-bound Select for a single AppProvider setting, chosen by `kind` (locale | timezone | dateFormat | timeFormat | theme | brand | density | fontSize) — covers locale/format AND the four theme axes. Throws if used without AppProvider AND without controlled value+onValueChange.",
13710
12669
  "usage": [
12670
+ "CYCLE (menu={false}): DO: Reach for it in a top bar when the value set is closed and short — <AppSettingPicker menu={false} kind=\"theme\" /> is the light/dark/system switcher, and a dropdown for three values is a menu nobody wanted to open.",
12671
+ "CYCLE (menu={false}): DO: Trust the accessible name — it always names BOTH the setting and the current value (\"Theme: Dark\"), because the glyph is the only visible state. Never override it with a kind-only aria-label.",
12672
+ "CYCLE (menu={false}): DON'T: Set a height, a radius or a background on it in a bar. The bar cell shape is TopbarItem's, and any utility you add outranks @layer components and re-creates the floating-pill defect.",
12673
+ "CYCLE (menu={false}): DON'T: Reach for it for locale, timezone, dateFormat or brand — those are not in `kind` on purpose. Use AppSettingPicker.",
12674
+ "CYCLE (menu={false}): DON'T: Hand-roll a theme button with useAppContext + a Sun/Moon ternary — that loses the localized value-bearing name, the shared option order, and the bar-cell shape.",
13711
12675
  "DO: Mount inside <AppProvider> for zero-config use — the picker reads and writes the context value named by kind, no value/onValueChange needed.",
13712
12676
  "DO: Use controlled mode (value + onValueChange) when managing state outside AppProvider, e.g. a standalone settings form or a Storybook story. Both are required together in this mode.",
13713
12677
  "DO NOT: Render without AppProvider and without both controlled props — it throws 'AppSettingPicker requires <AppProvider> or controlled value + onValueChange'.",
@@ -13717,6 +12681,10 @@
13717
12681
  "DON'T hand-roll a locale/timezone/format Select — AppSettingPicker already composes Select + the right icon + translated, context-wired options. There is no separate LocalePicker/TimezonePicker/DateFormatPicker/TimeFormatPicker anymore; use kind."
13718
12682
  ],
13719
12683
  "useCases": [
12684
+ "CYCLE (menu={false}): Topbar light/dark/system switcher: <AppSettingPicker menu={false} kind=\"theme\" /> in a Topbar `end` slot, beside the other TopbarItem cells.",
12685
+ "CYCLE (menu={false}): Density or font-size step-through in an admin bar, for users who resize the grid all day: <AppSettingPicker menu={false} kind=\"density\" />.",
12686
+ "CYCLE (menu={false}): Clock-format flip (24h/12h) next to a schedule view: <AppSettingPicker menu={false} kind=\"timeFormat\" /> — the only kind that shows its value as text, since no glyph can say \"24-hour\".",
12687
+ "CYCLE (menu={false}): Settings row outside a bar: <AppSettingPicker menu={false} kind=\"theme\" appearance=\"icon\" /> beside its label.",
13720
12688
  "App-shell top-nav language switcher: <AppSettingPicker kind=\"locale\" /> under AppProvider, persisting to localStorage with no extra state.",
13721
12689
  "Topbar locale switcher (globe): <AppSettingPicker kind=\"locale\" appearance=\"bar\" /> in a Topbar `end` slot — a CELL of the bar: full bar height, squared to --topbar-item-radius, so its hover surface matches the TopbarItem beside it. appearance=\"icon\" is the same structural drops shaped as a CONTROL, for a toolbar or card header; in a taller bar it leaves a --control-height pill floating mid-strip and reads as a foreign control family. This line said \"icon\" while the prop doc said \"bar\", and a consumer duly shipped the pill.",
13722
12690
  "Auth-footer locale switch: <AppSettingPicker kind=\"locale\" appearance=\"labeled\" compact /> inside an <AuthFooter locale={…}> slot — the readable language name at the small control tier, hugging its value.",
@@ -13725,76 +12693,6 @@
13725
12693
  "Storybook/test harness without AppProvider — fully controlled: <AppSettingPicker kind=\"timeFormat\" value=\"24h\" onValueChange={fn} />."
13726
12694
  ]
13727
12695
  },
13728
- {
13729
- "example": "{`import { AppProvider } from \"@godxjp/ui/app\";\nimport { Topbar } from \"@godxjp/ui/layout\";\nimport { AppSettingToggle } from \"@godxjp/ui/navigation\";\n\n// Context-bound: one tap steps light -> dark -> system -> light.\nexport function AppBar() {\n return (\n <AppProvider>\n <Topbar end={<AppSettingToggle kind=\"theme\" />} />\n </AppProvider>\n );\n}\n\n// Controlled - no AppProvider required.\nimport { useState } from \"react\";\n\nexport function ThemeField() {\n const [theme, setTheme] = useState(\"light\");\n return <AppSettingToggle kind=\"theme\" appearance=\"icon\" value={theme} onValueChange={setTheme} />;\n}`}",
13730
- "group": "navigation",
13731
- "importPath": "@godxjp/ui/navigation",
13732
- "name": "AppSettingToggle",
13733
- "props": [
13734
- {
13735
- "description": "Which AppProvider setting this button cycles. Deliberately the CLOSED-value-set subset of AppSettingKind — locale/timezone/dateFormat/brand are absent because a long or service-extensible list is a menu, not a cycle; reach for AppSettingPicker there. The cycle order is the SAME list AppSettingPicker offers for that kind (APP_THEMES / APP_DENSITIES / APP_FONT_SIZES / APP_TIME_FORMAT_OPTIONS), read from those constants rather than copied, so the two controls can never drift.",
13736
- "name": "kind",
13737
- "type": "\"theme\" | \"density\" | \"fontSize\" | \"timeFormat\""
13738
- },
13739
- {
13740
- "defaultValue": "\"bar\"",
13741
- "description": "The BOX the button takes; there is no labeled/inline form because there is no menu to label. \"bar\" (default) renders a TopbarItem — a CELL of the bar: it stretches to the full bar height (AppShell grid row, --topbar-height, or the coarse-pointer bar), squares its corners to --topbar-item-radius, and paints the bar's own hover surface across the whole strip. It emits NO height of its own, deliberately: a length here would freeze a --control-height pill inside a taller bar, which is exactly the mismatch it exists to remove. \"icon\" is a square --control-height ghost Button for everywhere that is NOT a bar (a settings row, a card header); the kinds that show value TEXT (timeFormat) take the small labelled tier instead of a square that would clip them.",
13742
- "name": "appearance",
13743
- "type": "\"bar\" | \"icon\""
13744
- },
13745
- {
13746
- "description": "Controlled value for the chosen kind. Omit to read the current value from AppProvider context.",
13747
- "name": "value",
13748
- "type": "string"
13749
- },
13750
- {
13751
- "description": "Controlled change handler, called with the NEXT value in the cycle. Omit to call the matching AppProvider setter (setTheme/setDensity/setFontSize/setTimeFormat). Required together with value when no AppProvider is present.",
13752
- "name": "onValueChange",
13753
- "type": "(value: string) => void"
13754
- },
13755
- {
13756
- "description": "Extra CSS classes merged onto the button.",
13757
- "name": "className",
13758
- "type": "string"
13759
- },
13760
- {
13761
- "description": "Disables the button.",
13762
- "name": "disabled",
13763
- "type": "boolean"
13764
- },
13765
- {
13766
- "description": "HTML id forwarded to the button.",
13767
- "name": "id",
13768
- "type": "string"
13769
- }
13770
- ],
13771
- "related": [
13772
- "AppSettingPicker — the same settings as a Select. Use it when the value list is long (locale, timezone) or when the user must SEE the options before choosing.",
13773
- "TopbarItem — the bar-cell shape appearance=\"bar\" renders; use it directly for your own bar triggers.",
13774
- "AppProvider — required peer unless fully controlled; supplies the value and the setter for each kind."
13775
- ],
13776
- "rules": [
13777
- 3,
13778
- 5,
13779
- 6,
13780
- 23
13781
- ],
13782
- "storyPath": "navigation/AppSettingToggle.stories.tsx",
13783
- "tagline": "ONE button that steps a single AppProvider setting to its NEXT value and shows that value as its glyph — theme (Sun/Moon/Monitor), density, fontSize, timeFormat. The no-menu counterpart to AppSettingPicker: same binding contract, same option order, one tap instead of open-then-choose. Renders disabled (never throws) outside AppProvider when uncontrolled.",
13784
- "usage": [
13785
- "DO: Reach for it in a top bar when the value set is closed and short — <AppSettingToggle kind=\"theme\" /> is the light/dark/system switcher, and a dropdown for three values is a menu nobody wanted to open.",
13786
- "DO: Trust the accessible name — it always names BOTH the setting and the current value (\"Theme: Dark\"), because the glyph is the only visible state. Never override it with a kind-only aria-label.",
13787
- "DON'T: Set a height, a radius or a background on it in a bar. The bar cell shape is TopbarItem's, and any utility you add outranks @layer components and re-creates the floating-pill defect.",
13788
- "DON'T: Reach for it for locale, timezone, dateFormat or brand — those are not in `kind` on purpose. Use AppSettingPicker.",
13789
- "DON'T: Hand-roll a theme button with useAppContext + a Sun/Moon ternary — that loses the localized value-bearing name, the shared option order, and the bar-cell shape."
13790
- ],
13791
- "useCases": [
13792
- "Topbar light/dark/system switcher: <AppSettingToggle kind=\"theme\" /> in a Topbar `end` slot, beside the other TopbarItem cells.",
13793
- "Density or font-size step-through in an admin bar, for users who resize the grid all day: <AppSettingToggle kind=\"density\" />.",
13794
- "Clock-format flip (24h/12h) next to a schedule view: <AppSettingToggle kind=\"timeFormat\" /> — the only kind that shows its value as text, since no glyph can say \"24-hour\".",
13795
- "Settings row outside a bar: <AppSettingToggle kind=\"theme\" appearance=\"icon\" /> beside its label."
13796
- ]
13797
- },
13798
12696
  {
13799
12697
  "example": "{`import { Field, Switch } from \"@godxjp/ui/data-entry\";\n\nexport function NotifyRow() {\n return (\n <Field id=\"notify\" label=\"メール通知\" description=\"重要な更新をメールで受け取る\">\n <Switch id=\"notify\" defaultChecked />\n </Field>\n );\n}`}",
13800
12698
  "group": "data-entry",
@@ -13844,7 +12742,6 @@
13844
12742
  "rules": [
13845
12743
  23
13846
12744
  ],
13847
- "storyPath": "data-entry/Field.stories.tsx",
13848
12745
  "tagline": "Label + optional description laid out beside a single checkbox/radio/switch control — the inline alternative to FormField's full block layout.",
13849
12746
  "usage": [
13850
12747
  "DO: Use Field to label a single boolean/choice control (Switch, Checkbox, Radio) in a compact two-column row — control beside label + description.",
@@ -13962,7 +12859,6 @@
13962
12859
  "DataTable — use when exact per-row figures matter more than the trend shape."
13963
12860
  ],
13964
12861
  "rules": [],
13965
- "storyPath": "charts/LineChart.stories.tsx",
13966
12862
  "tagline": "Trends over an ordered category axis — one or more series, locale-formatted ticks/tooltips, screen-reader text alternative built in. Data-visualization graph / plot.",
13967
12863
  "usage": [
13968
12864
  "DO import from the tree-shaken charts entry: `import { LineChart } from \"@godxjp/ui/charts\";`. Importing any other subpath never pulls in recharts.",
@@ -14083,7 +12979,6 @@
14083
12979
  "DataTable — exact tabular figures."
14084
12980
  ],
14085
12981
  "rules": [],
14086
- "storyPath": "charts/BarChart.stories.tsx",
14087
12982
  "tagline": "Compare a value across categories — grouped or `stacked`, vertical or `horizontal`, with localized ticks/tooltips and a built-in text alternative. Data-visualization graph / plot / diagram.",
14088
12983
  "usage": [
14089
12984
  "DO import from the charts entry: `import { BarChart } from \"@godxjp/ui/charts\";` (recharts optional peer required).",
@@ -14183,7 +13078,7 @@
14183
13078
  "Progress — one ratio against a target, not a series over time."
14184
13079
  ],
14185
13080
  "rules": [],
14186
- "storyPath": "charts/CompactBarTrend.stories.tsx",
13081
+ "storyPath": "charts/compact-bar-trend.tsx",
14187
13082
  "tagline": "DEPENDENCY-FREE compact vertical bar trend for dashboard summary cards — N category/value pairs, muted marks plus ONE emphasized 'current' bar, all geometry from --chart-trend-* tokens. Needs NO recharts. Sparkline / micro-chart / activity pulse / KPI trend strip.",
14188
13083
  "usage": [
14189
13084
  "DO import from the isolated entry: `import { CompactBarTrend } from \"@godxjp/ui/charts/compact-bar-trend\";` — unlike the charts barrel, this path never links modules that require the optional `recharts` peer.",
@@ -14307,7 +13202,6 @@
14307
13202
  "BarChart — discrete category comparison."
14308
13203
  ],
14309
13204
  "rules": [],
14310
- "storyPath": "charts/AreaChart.stories.tsx",
14311
13205
  "tagline": "Magnitude over an ordered category axis — overlay or `stacked` areas, optional `curved` smoothing, localized formatting + text alternative. Data-visualization graph / plot.",
14312
13206
  "usage": [
14313
13207
  "DO import from the charts entry: `import { AreaChart } from \"@godxjp/ui/charts\";` (recharts optional peer required).",
@@ -14407,7 +13301,6 @@
14407
13301
  "Progress — single ratio against a target rather than a multi-slice split."
14408
13302
  ],
14409
13303
  "rules": [],
14410
- "storyPath": "charts/PieChart.stories.tsx",
14411
13304
  "tagline": "Part-to-whole composition across a small set of slices — `donut` option, localized tooltips, and a screen-reader breakdown of every slice. Data-visualization graph / diagram.",
14412
13305
  "usage": [
14413
13306
  "DO import from the charts entry: `import { PieChart } from \"@godxjp/ui/charts\";` (recharts optional peer required).",
@@ -14507,7 +13400,7 @@
14507
13400
  }
14508
13401
  ],
14509
13402
  "rules": [],
14510
- "storyPath": "data-entry/CommandPalette.stories.tsx",
13403
+ "storyPath": "data-entry/command-palette.tsx",
14511
13404
  "tagline": "Searchable command dialog with controlled or uncontrolled open state and consumer-owned selection.",
14512
13405
  "usage": [
14513
13406
  "Provide localized labels and real command groups; the component does not fetch commands.",
@@ -14518,9 +13411,9 @@
14518
13411
  },
14519
13412
  {
14520
13413
  "docPath": "docs/data-entry/emoji-picker.tsx",
14521
- "example": "import { EmojiPicker } from \"@godxjp/ui/data-entry\";\n\n<EmojiPicker value={icon} onValueChange={setIcon} />",
13414
+ "example": "import { EmojiPicker } from \"@godxjp/ui/lab\";\n\n<EmojiPicker value={icon} onValueChange={setIcon} />",
14522
13415
  "group": "data-entry",
14523
- "importPath": "@godxjp/ui/data-entry",
13416
+ "importPath": "@godxjp/ui/lab",
14524
13417
  "name": "EmojiPicker",
14525
13418
  "props": [
14526
13419
  {
@@ -14589,6 +13482,7 @@
14589
13482
  "rules": [],
14590
13483
  "storyPath": "data-entry/emoji-picker.tsx",
14591
13484
  "tagline": "Page icon / reaction picker in a Popover (gh#1164): search by the active language's CLDR keywords (ja / en / vi — emojibase-data, loaded lazily for that locale only), categories, per-user recents, a WAI-ARIA grid (one tab stop, arrows, Home / End, Enter), and \"remove icon\". No skin tones.",
13485
+ "tier": "lab",
14592
13486
  "usage": [
14593
13487
  "DO use it for a page icon next to PageContainer `icon` — pass the icon itself as `trigger`.",
14594
13488
  "The data loads on first open (one locale, ~100 KB gzip); nothing ships in your main bundle."
@@ -14596,9 +13490,9 @@
14596
13490
  },
14597
13491
  {
14598
13492
  "docPath": "docs/data-entry/sortable-list.tsx",
14599
- "example": "import { SortableList } from \"@godxjp/ui/data-entry\";\n\n<SortableList\n aria-label=\"Widgets\"\n items={[{ value: \"cal\", label: \"Calendar\" }, { value: \"mail\", label: \"Mail\" }]}\n value={order}\n onValueChange={setOrder}\n/>",
13493
+ "example": "import { SortableList } from \"@godxjp/ui/lab\";\n\n<SortableList\n aria-label=\"Widgets\"\n items={[{ value: \"cal\", label: \"Calendar\" }, { value: \"mail\", label: \"Mail\" }]}\n value={order}\n onValueChange={setOrder}\n/>",
14600
13494
  "group": "data-entry",
14601
- "importPath": "@godxjp/ui/data-entry",
13495
+ "importPath": "@godxjp/ui/lab",
14602
13496
  "name": "SortableList",
14603
13497
  "props": [
14604
13498
  {
@@ -14661,6 +13555,7 @@
14661
13555
  "rules": [],
14662
13556
  "storyPath": "data-entry/sortable-list.tsx",
14663
13557
  "tagline": "Reorder a flat list or a grid of tiles (gh#1173). Each item has a grip: drag it with a pointer (touch included — the grip sets touch-action: none), or focus it and press Space/Enter to lift, the arrows (Home / End) to move, Space/Enter to drop, Esc to cancel. Every step is spoken in a polite live region (\"2 of 3\"). The order is the value.",
13558
+ "tier": "lab",
14664
13559
  "usage": [
14665
13560
  "DO persist the order in onValueChange — it fires once per drop, so it is safe to save there.",
14666
13561
  "DO give every item a human `label`: a screen reader hears it on lift, every move and drop.",
@@ -14744,7 +13639,7 @@
14744
13639
  }
14745
13640
  ],
14746
13641
  "rules": [],
14747
- "storyPath": "feedback/TwoFactorSetup.stories.tsx",
13642
+ "storyPath": "feedback/two-factor-setup.tsx",
14748
13643
  "tagline": "Canonical two-factor enrollment dialog for QR/manual-key verification and recovery-code acknowledgement.",
14749
13644
  "usage": [
14750
13645
  "Keep enrollment, verification, persistence, and secret lifecycle in the consumer; this component is presentational.",
@@ -14776,7 +13671,7 @@
14776
13671
  44,
14777
13672
  45
14778
13673
  ],
14779
- "storyPath": "layout/AuthDivider.stories.tsx",
13674
+ "storyPath": "layout/auth-divider.tsx",
14780
13675
  "tagline": "The auth-scoped PRESET over `Separator label` — a localized conjunction between two equal rules.",
14781
13676
  "usage": [
14782
13677
  "DO use AuthDivider only INSIDE an auth form. It is a thin preset over `<Separator label>`: it re-points the --separator-* knobs at the --auth-shell-divider-* layer (the 11px auth micro-scale, the auth rule/label colours), so using it elsewhere drags auth geometry into an unrelated screen.",
@@ -14824,11 +13719,11 @@
14824
13719
  "rules": [
14825
13720
  45
14826
13721
  ],
14827
- "storyPath": "layout/AuthFooter.stories.tsx",
13722
+ "storyPath": "layout/auth-footer.tsx",
14828
13723
  "tagline": "Compact hosted-auth footer for consumer-owned product, legal, privacy, and locale content.",
14829
13724
  "usage": [
14830
13725
  "Pass real links and locale controls from the consumer; AuthFooter never invents navigation.",
14831
- "DO drop it into AuthShell's `footer` slot — that slot supplies the contentinfo landmark, so AuthFooter itself renders a plain div and can also sit inside an existing footer without nesting landmarks.",
13726
+ "DO drop it into the auth CenteredShell's `footer` slot — that slot supplies the contentinfo landmark, so AuthFooter itself renders a plain div and can also sit inside an existing footer without nesting landmarks.",
14832
13727
  "AuthFooter owns ONLY the geometry: mono ramp, wrap, and a `·` separator between the slots that are actually PRESENT (omit `locale` and its separator disappears). Don't hand-write separators into the slot content.",
14833
13728
  "Public type: `AuthFooterProp` (alias `AuthFooterProps`) from `@godxjp/ui/layout` — registered in the prop registry, not a local interface.",
14834
13729
  "Retune the line through `--auth-footer-content-gap` / `--auth-footer-text-font-size`, never page CSS (rule #45)."
@@ -14847,7 +13742,7 @@
14847
13742
  "type": "ReactNode"
14848
13743
  },
14849
13744
  {
14850
- "description": "Brand artwork in the MARK's place, for a service whose design ships a real lockup (mark + wordmark, sometimes a product suffix). Omit it and the canonical GoDX mark renders. data-slot, .ui-auth-identity spacing and the h1 are untouched, so swapping artwork never forks the block.",
13745
+ "description": "Brand artwork in the MARK's place, for a service whose design ships a real lockup (mark + wordmark, sometimes a product suffix). Omitted → the active preset's `brandMark` (`<AppProvider preset={godxPreset}>` supplies the GoDX mark), otherwise NO mark: a neutral package draws no product's logo. `null` → no mark, preset or not. data-slot, .ui-auth-identity spacing and the h1 are untouched, so swapping artwork never forks the block.",
14851
13746
  "name": "brand",
14852
13747
  "type": "ReactNode"
14853
13748
  },
@@ -14865,11 +13760,11 @@
14865
13760
  "rules": [
14866
13761
  45
14867
13762
  ],
14868
- "storyPath": "layout/AuthIdentity.stories.tsx",
13763
+ "storyPath": "layout/auth-identity.tsx",
14869
13764
  "tagline": "Hosted-auth identity heading with the shared mark (or the product's own lockup) and optional requesting-client context.",
14870
13765
  "usage": [
14871
13766
  "Only show `requester` when the consumer has authoritative client context.",
14872
- "It ALREADY renders the canonical brand-green GoDX mark (`Logo mark=\"godx\" tone=\"success\"`, independent of --primary) plus the page h1 — don't add a second Logo or heading above it.",
13767
+ "It renders the page h1 plus a mark: the `brand` you pass, else the active preset's `brandMark`, else none (v32: the neutral default draws no product logo). Under `<AppProvider preset={godxPreset}>` (from `@godxjp/ui/themes/godx`, with `@godxjp/ui/themes/godx.css`) that is the brand-green GoDX mark — don't add a second Logo or heading above it.",
14873
13768
  "To show YOUR OWN lockup instead, pass it as `brand` — never wrap or rebuild the block. `brand` replaces the mark only, so the h1 stays; if your lockup already spells the product name, make `title` the SCREEN'S PURPOSE (\"Sign in\") rather than repeating the brand.",
14874
13769
  "DO keep `brand` non-interactive. The slot is DECORATIVE at every fill: the mark it replaces was always aria-hidden, and a real lockup is not a mark — `Logo mark=\"godx-lockup\" productSuffix=\"ID\"` exposes \"GoDX ID\" as real text, so left in the tree beside the h1 the block announces the product twice (measured: \"GoDXIDGoDX ID\"). aria-hidden is merged onto your element, and aria-hidden over a focusable child is a WCAG failure.",
14875
13770
  "Centring and rhythm are token-owned (`--auth-identity-gap` / `--auth-requester-*`); no page CSS (rule #45).",
@@ -14927,7 +13822,7 @@
14927
13822
  "rules": [
14928
13823
  40
14929
13824
  ],
14930
- "storyPath": "layout/AccountChip.stories.tsx",
13825
+ "storyPath": "layout/account-chip.tsx",
14931
13826
  "tagline": "Signed-in user for a PageContainer extra slot, Topbar or footer row: avatar, name and one ghost action at the control tier height — never a hand-rolled rounded-full border div.",
14932
13827
  "usage": [
14933
13828
  "Put it in PageContainer `extra` beside Buttons and a Select width=\"auto\": every control in that row shares --control-height.",
@@ -14987,7 +13882,7 @@
14987
13882
  "rules": [
14988
13883
  45
14989
13884
  ],
14990
- "storyPath": "layout/AuthAccountSummary.stories.tsx",
13885
+ "storyPath": "layout/auth-account-summary.tsx",
14991
13886
  "tagline": "Compact signed-in account row for hosted auth: avatar fallback, authoritative email and switch action.",
14992
13887
  "usage": [
14993
13888
  "Pass only the authoritative signed-in email; AuthAccountSummary never fetches or invents identity data.",
@@ -15017,7 +13912,7 @@
15017
13912
  }
15018
13913
  ],
15019
13914
  "rules": [],
15020
- "storyPath": "layout/AuthStack.stories.tsx",
13915
+ "storyPath": "layout/auth-stack.tsx",
15021
13916
  "tagline": "Token-spaced vertical stack for direct sections inside a canonical auth card."
15022
13917
  },
15023
13918
  {
@@ -15099,7 +13994,7 @@
15099
13994
  "Sidebar — APP scope, and therefore NOT where this goes. Its `brand` slot is the app's own lockup; putting the organization switcher there mixes two scopes in one header."
15100
13995
  ],
15101
13996
  "rules": [],
15102
- "storyPath": "layout/OrgSwitcher.stories.tsx",
13997
+ "storyPath": "layout/org-switcher.tsx",
15103
13998
  "tagline": "Searchable organization switcher with canonical trigger geometry, desktop popover, and mobile sheet.",
15104
13999
  "usage": [
15105
14000
  "Provide only organizations the current user may select; the component does not authorize or fetch tenants.",
@@ -15167,7 +14062,7 @@
15167
14062
  },
15168
14063
  {
15169
14064
  "defaultValue": "\"bar\"",
15170
- "description": "The BOX the trigger takes — the same split AppSettingToggle draws, for the same reason. `bar` (default) is a TopbarItem: a cell as tall as the bar, whose hover IS the bar's surface. `icon` is a square ghost Button, for chrome that is NOT a bar — a nav rail (GoDX Dock puts it there), a card header, a toolbar. A TopbarItem outside a bar has nothing to bleed to: it stretches to a container that never set a band height, and its squared corners and full-bleed hover read as a broken cell rather than a control. The panel, the grid, the labels and the responsive contract are identical in both.",
14065
+ "description": "The BOX the trigger takes — the same split `AppSettingPicker menu={false}` draws, for the same reason. `bar` (default) is a TopbarItem: a cell as tall as the bar, whose hover IS the bar's surface. `icon` is a square ghost Button, for chrome that is NOT a bar — a nav rail (GoDX Dock puts it there), a card header, a toolbar. A TopbarItem outside a bar has nothing to bleed to: it stretches to a container that never set a band height, and its squared corners and full-bleed hover read as a broken cell rather than a control. The panel, the grid, the labels and the responsive contract are identical in both.",
15171
14066
  "name": "appearance",
15172
14067
  "type": "\"bar\" | \"icon\""
15173
14068
  },
@@ -15204,7 +14099,7 @@
15204
14099
  "TopbarItem — the bar cell the trigger is built from; use it directly for a one-off bar control."
15205
14100
  ],
15206
14101
  "rules": [],
15207
- "storyPath": "layout/AppLauncher.stories.tsx",
14102
+ "storyPath": "layout/app-launcher.tsx",
15208
14103
  "tagline": "Nine-dot topbar app grid — the platform-standard way to switch app (the Google Workspace shape).",
15209
14104
  "usage": [
15210
14105
  "DO drop it straight into a Topbar slot. It renders NO wrapper element, so the trigger is the slot's own flex child and `.ui-topbar-item { align-self: stretch }` reaches the bar's height.",
@@ -15219,105 +14114,6 @@
15219
14114
  "Replacing a hand-rolled dropdown of product links in the topbar end slot."
15220
14115
  ]
15221
14116
  },
15222
- {
15223
- "example": "import { FilterBar } from \"@godxjp/ui/navigation\";\n\n<FilterBar\n search={{ value: query, onValueChange: setQuery, placeholder: \"Search records\" }}\n filters={[\n {\n value: \"status\",\n label: \"Status\",\n options: STATUS_OPTIONS,\n selected: status,\n onSelectedChange: setStatus,\n },\n ]}\n chips={appliedChips}\n onChipRemove={removeFilter}\n onClear={clearFilters}\n hasActiveFilters={hasFilters}\n resultCount={rows.length}\n actions={<Button onClick={openCreate}>Add member</Button>}\n/>",
15224
- "group": "navigation",
15225
- "importPath": "@godxjp/ui/navigation",
15226
- "name": "FilterBar",
15227
- "props": [
15228
- {
15229
- "description": "Filter controls and groups (composition form). In the typed-model form children remain valid as CUSTOM filters, rendered after the typed `filters`.",
15230
- "name": "children",
15231
- "type": "ReactNode"
15232
- },
15233
- {
15234
- "description": "Consumer-owned clear/reset action — also the chips' clear-all.",
15235
- "name": "onClear",
15236
- "type": "() => void"
15237
- },
15238
- {
15239
- "defaultValue": "true",
15240
- "description": "Shows clear only when filters are active.",
15241
- "name": "hasActiveFilters",
15242
- "type": "boolean"
15243
- },
15244
- {
15245
- "defaultValue": "false",
15246
- "description": "Uses the token-owned sticky presentation.",
15247
- "name": "sticky",
15248
- "type": "boolean"
15249
- },
15250
- {
15251
- "defaultValue": "'wrap'",
15252
- "description": "Responsive overflow strategy. 'wrap': stacked column below 640px, wrapping rows above. 'scroll': one bounded inline-scrolling row above 640px (still stacked below) with the clear-all action pinned at the inline end. The geometry is entirely token/CSS owned — never re-implement it in the page.",
15253
- "name": "overflow",
15254
- "type": "'wrap' | 'scroll'"
15255
- },
15256
- {
15257
- "description": "Typed model: search slot, first in the strip, token-owned width (--filter-bar-search-width). Presence of ANY model prop (search/filters/chips/onChipRemove/actions/resultCount/loading/disabled/error) activates the model layout; without them the composition form renders unchanged.",
15258
- "name": "search",
15259
- "type": "FilterBarSearchProp"
15260
- },
15261
- {
15262
- "description": "Typed model: labelled Select filters ({ value, label, options, selected/defaultSelected/onSelectedChange, placeholder, disabled }) — label becomes the control's real <label>. Width knob: --filter-bar-filter-width.",
15263
- "name": "filters",
15264
- "type": "FilterBarFilterProp[]"
15265
- },
15266
- {
15267
- "description": "Typed model: applied-filter chips ({ value, label, disabled }) in a labelled row. Add = include in the array; remove = onChipRemove(value); clear-all = onClear.",
15268
- "name": "chips",
15269
- "type": "FilterBarChipProp[]"
15270
- },
15271
- {
15272
- "description": "Per-chip remove handler; required for the × remove buttons to render.",
15273
- "name": "onChipRemove",
15274
- "type": "(value: string) => void"
15275
- },
15276
- {
15277
- "description": "Typed model: trailing action slot at the inline end, after reset in DOM/keyboard order.",
15278
- "name": "actions",
15279
- "type": "ReactNode"
15280
- },
15281
- {
15282
- "description": "Typed model: localized CLDR-pluralized count in a polite role='status' line; 0 is the rendered empty state.",
15283
- "name": "resultCount",
15284
- "type": "number"
15285
- },
15286
- {
15287
- "description": "Typed model: aria-busy strip + data-loading root while results (re)load.",
15288
- "name": "loading",
15289
- "type": "boolean"
15290
- },
15291
- {
15292
- "description": "Typed model: disables all model-rendered controls.",
15293
- "name": "disabled",
15294
- "type": "boolean"
15295
- },
15296
- {
15297
- "description": "Typed model: role='alert' error line replacing the result count.",
15298
- "name": "error",
15299
- "type": "ReactNode"
15300
- },
15301
- {
15302
- "description": "Optional structural class override.",
15303
- "name": "className",
15304
- "type": "string"
15305
- }
15306
- ],
15307
- "rules": [],
15308
- "storyPath": "navigation/FilterBar.stories.tsx",
15309
- "subParts": [
15310
- "FilterBarGroup"
15311
- ],
15312
- "tagline": "Domain-neutral list-page filter toolbar with optional clear action and labelled groups.",
15313
- "usage": [
15314
- "Prefer the typed model for a canonical list page: pass `search` + `filters` + `chips` + `resultCount` and the bar owns the layout, widths, chip lifecycle, keyboard order (search → filters → children → reset → actions → chip removes) and responsive stacking through --filter-bar-* tokens. Everything stays consumer data — the bar renders state, never owns it.",
15315
- "Compose real controls as children when the model doesn't fit; filter state and URL synchronization remain consumer-owned. Children also render INSIDE the model layout (after the typed filters) for one-off custom controls like a date-range picker.",
15316
- "Give each FilterBarGroup a `controlId` matching its single control's `id` so the visible caption is that control's real <label>; otherwise the control is nameless to a screen reader. Typed `filters` wire this automatically.",
15317
- "Reach for `overflow='scroll'` on filter-heavy list pages so a long JA/EN/VI label set never grows the strip into multiple rows and pushes the table below the fold.",
15318
- "A mobile sheet presentation is a COMPOSITION, not a prop: put the same typed FilterBar inside a Sheet triggered from a compact toolbar when a screen wants drawer-style filters. The bar's own responsive behavior is stack (below 640px) + wrap/scroll (above), token-owned."
15319
- ]
15320
- },
15321
14117
  {
15322
14118
  "docPath": "data-display/permission-matrix.tsx",
15323
14119
  "example": "import { Card, CardContent, PermissionMatrix } from \"@godxjp/ui/data-display\";\nimport { grantKey } from \"@godxjp/ui/lib/permission-grid\";\n\nconst grants = new Set<string>(rolePermissions.map((rp) => grantKey(rp.roleId, rp.permissionId)));\n\n<Card>\n <CardContent flush>\n <PermissionMatrix\n roles={roles}\n permissions={permissions}\n grants={grants}\n onGrantChange={(roleId, permissionId, granted) => mutate({ roleId, permissionId, granted })}\n />\n </CardContent>\n</Card>",
@@ -15389,7 +14185,7 @@
15389
14185
  "rules": [
15390
14186
  24
15391
14187
  ],
15392
- "storyPath": "data-display/PermissionMatrix.stories.tsx",
14188
+ "storyPath": "data-display/permission-matrix.tsx",
15393
14189
  "tagline": "Domain data is 100% consumer-supplied.",
15394
14190
  "usage": [
15395
14191
  "DO import it — it is a real export from @godxjp/ui/data-display (the lesson: a docs page is not importable). Never hand-compose the sticky-column grid per app.",
@@ -15494,7 +14290,7 @@
15494
14290
  "rules": [
15495
14291
  24
15496
14292
  ],
15497
- "storyPath": "data-entry/BranchScopePicker.stories.tsx",
14293
+ "storyPath": "data-entry/branch-scope-picker.tsx",
15498
14294
  "tagline": "Canonical scope control: all branches vs an explicit subset.",
15499
14295
  "usage": [
15500
14296
  "DO treat scope as ONE form field: the single { mode, branchIds } value goes through FormField like any other control.",
@@ -15582,7 +14378,7 @@
15582
14378
  24,
15583
14379
  40
15584
14380
  ],
15585
- "storyPath": "layout/ServiceRolePanel.stories.tsx",
14381
+ "storyPath": "layout/service-role-panel.tsx",
15586
14382
  "tagline": "Geometry (1440/1024 two-track, 390 stacked) is MasterDetail's tokens.",
15587
14383
  "usage": [
15588
14384
  "DO compose the detail from real primitives — the panel deliberately owns NO detail layout; PermissionMatrix + Descriptions is the typical body.",
@@ -15598,9 +14394,9 @@
15598
14394
  },
15599
14395
  {
15600
14396
  "docPath": "data-display/chat-bubble.tsx",
15601
- "example": "import { Avatar, AvatarFallback, ChatBubble } from \"@godxjp/ui/data-display\";\nimport { formatAppTime } from \"@godxjp/ui/datetime\";\n\n<ChatBubble\n placement=\"start\"\n variant=\"filled\"\n avatar={<Avatar aria-hidden=\"true\"><AvatarFallback>AI</AvatarFallback></Avatar>}\n header={t(\"chat.bubble.assistant\")}\n footer={formatAppTime(message.sentAt)}\n typing={{ step: 2, interval: 24 }}\n>\n {message.text}\n</ChatBubble>\n\n// The reply has been requested and has not arrived yet.\n<ChatBubble placement=\"start\" header={t(\"chat.bubble.assistant\")} loading />\n\n// A failed send: colour AND a localized sr-only word, never colour alone.\n<ChatBubble placement=\"end\" tone=\"destructive\" header={t(\"chat.bubble.you\")}>\n {draft}\n</ChatBubble>",
14397
+ "example": "import { Avatar, AvatarFallback } from \"@godxjp/ui/data-display\";\nimport { ChatBubble } from \"@godxjp/chat\";\nimport { formatAppTime } from \"@godxjp/ui/datetime\";\n\n<ChatBubble\n placement=\"start\"\n variant=\"filled\"\n avatar={<Avatar aria-hidden=\"true\"><AvatarFallback>AI</AvatarFallback></Avatar>}\n header={t(\"chat.bubble.assistant\")}\n footer={formatAppTime(message.sentAt)}\n typing={{ step: 2, interval: 24 }}\n>\n {message.text}\n</ChatBubble>\n\n// The reply has been requested and has not arrived yet.\n<ChatBubble placement=\"start\" header={t(\"chat.bubble.assistant\")} loading />\n\n// A failed send: colour AND a localized sr-only word, never colour alone.\n<ChatBubble placement=\"end\" tone=\"destructive\" header={t(\"chat.bubble.you\")}>\n {draft}\n</ChatBubble>",
15602
14398
  "group": "data-display",
15603
- "importPath": "@godxjp/ui/data-display",
14399
+ "importPath": "@godxjp/chat",
15604
14400
  "name": "ChatBubble",
15605
14401
  "props": [
15606
14402
  {
@@ -15678,7 +14474,7 @@
15678
14474
  44,
15679
14475
  45
15680
14476
  ],
15681
- "storyPath": "data-display/ChatBubble.stories.tsx",
14477
+ "storyPath": "data-display/chat-bubble.tsx",
15682
14478
  "tagline": "One message in a conversation: an author mark, a header naming the turn, the body, and a footer. Renders as an <article> inside ChatBubbleList's log; `loading` shows Skeleton and `typing` streams a string in, dropping the animation entirely under prefers-reduced-motion.",
15683
14479
  "usage": [
15684
14480
  "DO let ChatBubbleList own the feed. A lone ChatBubble is for a single quoted message; a conversation is `items` + `roles` on the list, which is where stick-to-bottom and the one aria-live region live.",
@@ -15698,9 +14494,9 @@
15698
14494
  },
15699
14495
  {
15700
14496
  "docPath": "data-display/chat-bubble.tsx",
15701
- "example": "import { ChatBubbleList, type ChatMessageProp } from \"@godxjp/ui/data-display\";\n\nconst messages: ChatMessageProp[] = [\n { id: \"m1\", role: \"assistant\", content: \"How can I help?\" },\n { id: \"m2\", role: \"user\", content: \"Summarise yesterday's invoices.\" },\n { id: \"m3\", role: \"assistant\", content: reply, typing: true },\n];\n\n<ChatBubbleList\n label={t(\"chat.list.label\")}\n items={messages}\n roles={{\n assistant: { placement: \"start\", variant: \"filled\", avatar: assistantAvatar },\n user: { placement: \"end\", variant: \"outlined\" },\n system: { placement: \"start\", variant: \"borderless\", tone: \"info\", size: \"sm\" },\n }}\n/>",
14497
+ "example": "import { ChatBubbleList, type ChatMessageProp } from \"@godxjp/chat\";\n\nconst messages: ChatMessageProp[] = [\n { id: \"m1\", role: \"assistant\", content: \"How can I help?\" },\n { id: \"m2\", role: \"user\", content: \"Summarise yesterday's invoices.\" },\n { id: \"m3\", role: \"assistant\", content: reply, typing: true },\n];\n\n<ChatBubbleList\n label={t(\"chat.list.label\")}\n items={messages}\n roles={{\n assistant: { placement: \"start\", variant: \"filled\", avatar: assistantAvatar },\n user: { placement: \"end\", variant: \"outlined\" },\n system: { placement: \"start\", variant: \"borderless\", tone: \"info\", size: \"sm\" },\n }}\n/>",
15702
14498
  "group": "data-display",
15703
- "importPath": "@godxjp/ui/data-display",
14499
+ "importPath": "@godxjp/chat",
15704
14500
  "name": "ChatBubbleList",
15705
14501
  "props": [
15706
14502
  {
@@ -15743,7 +14539,7 @@
15743
14539
  44,
15744
14540
  45
15745
14541
  ],
15746
- "storyPath": "data-display/ChatBubbleList.stories.tsx",
14542
+ "storyPath": "data-display/chat-bubble.tsx",
15747
14543
  "tagline": "The message feed. It owns STICK-TO-BOTTOM (auto-scroll only while the reader is already at the bottom; the moment they scroll up the pin is revoked and a focusable jump-to-latest button appears), one aria-live=\"polite\" role=\"log\" region for the whole conversation, and per-role bubble defaults.",
15748
14544
  "usage": [
15749
14545
  "DO drive the feed from `items` and shape it with `roles`. Mapping ChatBubbles yourself inside a ScrollArea loses stick-to-bottom, the jump affordance and the single live region — the three reasons this component exists.",
@@ -15762,9 +14558,9 @@
15762
14558
  },
15763
14559
  {
15764
14560
  "docPath": "data-entry/chat-composer.tsx",
15765
- "example": "import { ChatComposer } from \"@godxjp/ui/data-entry\";\n\nconst [draft, setDraft] = useState(\"\");\nconst [streaming, setStreaming] = useState(false);\n\n<ChatComposer\n value={draft}\n onValueChange={setDraft}\n onSubmit={(text) => { send(text); setDraft(\"\"); }}\n loading={streaming}\n onCancel={() => setStreaming(false)}\n placeholder={t(\"chat.placeholder\")}\n footer={t(\"dataEntry.chatComposer.hintEnter\")}\n/>",
14561
+ "example": "import { ChatComposer } from \"@godxjp/chat\";\n\nconst [draft, setDraft] = useState(\"\");\nconst [streaming, setStreaming] = useState(false);\n\n<ChatComposer\n value={draft}\n onValueChange={setDraft}\n onSubmit={(text) => { send(text); setDraft(\"\"); }}\n loading={streaming}\n onCancel={() => setStreaming(false)}\n placeholder={t(\"chat.placeholder\")}\n footer={t(\"dataEntry.chatComposer.hintEnter\")}\n/>",
15766
14562
  "group": "data-entry",
15767
- "importPath": "@godxjp/ui/data-entry",
14563
+ "importPath": "@godxjp/chat",
15768
14564
  "name": "ChatComposer",
15769
14565
  "props": [
15770
14566
  {
@@ -15901,7 +14697,7 @@
15901
14697
  43,
15902
14698
  45
15903
14699
  ],
15904
- "storyPath": "data-entry/ChatComposer.stories.tsx",
14700
+ "storyPath": "data-entry/chat-composer.tsx",
15905
14701
  "tagline": "The message input of a conversation (Ant Design X Sender): an auto-growing Textarea plus exactly ONE trailing action — send, or cancel while a response streams. Enter / Shift+Enter / ⌘-or-Ctrl+Enter is configurable and never fires during an IME conversion.",
15906
14702
  "usage": [
15907
14703
  "DO pair a controlled `value` with `onValueChange` — a controlled value with no synchronised handler is the classic frozen-input bug, and it freezes the whole conversation.",
@@ -15924,9 +14720,9 @@
15924
14720
  },
15925
14721
  {
15926
14722
  "docPath": "data-entry/chat-composer.tsx",
15927
- "example": "import { ChatComposer, ChatSuggestion } from \"@godxjp/ui/data-entry\";\n\nconst [draft, setDraft] = useState(\"\");\n\n<ChatSuggestion\n items={[\n { value: \"summarize\", label: \"要約する\", description: \"Summarize the thread\" },\n { value: \"translate\", label: \"翻訳する\" },\n ]}\n onValueChange={(value) => setDraft(\"/\" + value + \" \")}\n>\n {({ onTrigger, onKeyDown }) => (\n <ChatComposer\n value={draft}\n onValueChange={(next) => { setDraft(next); onTrigger(next); }}\n onKeyDown={onKeyDown}\n onSubmit={(text) => send(text)}\n />\n )}\n</ChatSuggestion>",
14723
+ "example": "import { ChatComposer, ChatSuggestion } from \"@godxjp/chat\";\n\nconst [draft, setDraft] = useState(\"\");\n\n<ChatSuggestion\n items={[\n { value: \"summarize\", label: \"要約する\", description: \"Summarize the thread\" },\n { value: \"translate\", label: \"翻訳する\" },\n ]}\n onValueChange={(value) => setDraft(\"/\" + value + \" \")}\n>\n {({ onTrigger, onKeyDown }) => (\n <ChatComposer\n value={draft}\n onValueChange={(next) => { setDraft(next); onTrigger(next); }}\n onKeyDown={onKeyDown}\n onSubmit={(text) => send(text)}\n />\n )}\n</ChatSuggestion>",
15928
14724
  "group": "data-entry",
15929
- "importPath": "@godxjp/ui/data-entry",
14725
+ "importPath": "@godxjp/chat",
15930
14726
  "name": "ChatSuggestion",
15931
14727
  "props": [
15932
14728
  {
@@ -16015,7 +14811,7 @@
16015
14811
  3,
16016
14812
  6
16017
14813
  ],
16018
- "storyPath": "data-entry/ChatSuggestion.stories.tsx",
14814
+ "storyPath": "data-entry/chat-composer.tsx",
16019
14815
  "tagline": "Trigger-character autocomplete over a ChatComposer (Ant Design X Suggestion): type / at a word boundary and a Command list opens against the composer, driven from the textarea without ever taking focus off it.",
16020
14816
  "usage": [
16021
14817
  "DO wire BOTH halves of the render prop: `onTrigger` from the composer's onValueChange and `onKeyDown` from its onKeyDown. With only one wired the list either never opens or cannot be driven.",
@@ -16032,9 +14828,9 @@
16032
14828
  },
16033
14829
  {
16034
14830
  "docPath": "navigation/conversations.tsx",
16035
- "example": "import { Conversations } from \"@godxjp/ui/navigation\";\n\nconst [active, setActive] = useState(\"c1\");\n\n<Conversations\n activeKey={active}\n onActiveChange={setActive}\n groupable={{ collapsible: true }}\n creation={{ onClick: () => startNewChat() }}\n items={[\n { key: \"c1\", label: \"請求書の下書き\", group: \"today\" },\n { key: \"c2\", label: \"経費精算の規則\", group: \"today\" },\n { key: \"c3\", label: \"出張手当の確認\", group: \"earlier\" },\n ]}\n menu={{\n items: [\n { key: \"rename\", label: \"名前を変更\" },\n { key: \"delete\", label: \"削除\", danger: true },\n ],\n onClick: ({ key, conversation }) => run(key, conversation.key),\n }}\n/>",
14831
+ "example": "import { Conversations } from \"@godxjp/chat\";\n\nconst [active, setActive] = useState(\"c1\");\n\n<Conversations\n activeKey={active}\n onActiveChange={setActive}\n groupable={{ collapsible: true }}\n creation={{ onClick: () => startNewChat() }}\n items={[\n { key: \"c1\", label: \"請求書の下書き\", group: \"today\" },\n { key: \"c2\", label: \"経費精算の規則\", group: \"today\" },\n { key: \"c3\", label: \"出張手当の確認\", group: \"earlier\" },\n ]}\n menu={{\n items: [\n { key: \"rename\", label: \"名前を変更\" },\n { key: \"delete\", label: \"削除\", danger: true },\n ],\n onClick: ({ key, conversation }) => run(key, conversation.key),\n }}\n/>",
16036
14832
  "group": "navigation",
16037
- "importPath": "@godxjp/ui/navigation",
14833
+ "importPath": "@godxjp/chat",
16038
14834
  "name": "Conversations",
16039
14835
  "props": [
16040
14836
  {
@@ -16096,7 +14892,7 @@
16096
14892
  44,
16097
14893
  45
16098
14894
  ],
16099
- "storyPath": "navigation/Conversations.stories.tsx",
14895
+ "storyPath": "navigation/conversations.tsx",
16100
14896
  "tagline": "The session rail of a chat surface (Ant Design X Conversations): past conversations, the current one marked with aria-current, a per-row overflow menu, and recency buckets — the whole rail one roving-tabindex tab stop, not one tab stop per conversation.",
16101
14897
  "usage": [
16102
14898
  "DO give every conversation a stable `key` — it is what activeKey, onActiveChange and the menu callback all address. A key that changes on re-render moves the selection.",
@@ -16114,9 +14910,9 @@
16114
14910
  },
16115
14911
  {
16116
14912
  "docPath": "navigation/mega-menu.tsx",
16117
- "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/>",
14913
+ "example": "import { MegaMenu } from \"@godxjp/ui/lab\";\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/>",
16118
14914
  "group": "navigation",
16119
- "importPath": "@godxjp/ui/navigation",
14915
+ "importPath": "@godxjp/ui/lab",
16120
14916
  "name": "MegaMenu",
16121
14917
  "props": [
16122
14918
  {
@@ -16214,8 +15010,9 @@
16214
15010
  44,
16215
15011
  45
16216
15012
  ],
16217
- "storyPath": "navigation/MegaMenu.stories.tsx",
15013
+ "storyPath": "navigation/mega-menu.tsx",
16218
15014
  "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.",
15015
+ "tier": "lab",
16219
15016
  "usage": [
16220
15017
  "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.",
16221
15018
  "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.",
@@ -16234,9 +15031,9 @@
16234
15031
  },
16235
15032
  {
16236
15033
  "docPath": "data-display/welcome.tsx",
16237
- "example": "import { Welcome } from \"@godxjp/ui/data-display\";\nimport { Button } from \"@godxjp/ui/general\";\nimport { Bot } from \"lucide-react\";\n\n<Welcome\n icon={<Bot />}\n title=\"こんにちは\"\n description=\"請求、経費、勤怠のことならお手伝いできます。\"\n extra={<Button variant=\"ghost\" size=\"sm\">閉じる</Button>}\n/>",
15034
+ "example": "import { Welcome } from \"@godxjp/chat\";\nimport { Button } from \"@godxjp/ui/general\";\nimport { Bot } from \"lucide-react\";\n\n<Welcome\n icon={<Bot />}\n title=\"こんにちは\"\n description=\"請求、経費、勤怠のことならお手伝いできます。\"\n extra={<Button variant=\"ghost\" size=\"sm\">閉じる</Button>}\n/>",
16238
15035
  "group": "data-display",
16239
- "importPath": "@godxjp/ui/data-display",
15036
+ "importPath": "@godxjp/chat",
16240
15037
  "name": "Welcome",
16241
15038
  "props": [
16242
15039
  {
@@ -16282,7 +15079,7 @@
16282
15079
  44,
16283
15080
  45
16284
15081
  ],
16285
- "storyPath": "data-display/Welcome.stories.tsx",
15082
+ "storyPath": "data-display/welcome.tsx",
16286
15083
  "tagline": "The greeting block at the head of an empty conversation (Ant Design X Welcome): glyph, greeting, one line under it, and a trailing slot ON THE TITLE ROW — which is the placement a hand-roll gets wrong.",
16287
15084
  "usage": [
16288
15085
  "DO put it above the composer on an empty chat, with ChatSuggestion or a Prompts row beneath it — that is the surface it belongs to.",
@@ -16355,7 +15152,7 @@
16355
15152
  44,
16356
15153
  45
16357
15154
  ],
16358
- "storyPath": "general/Actions.stories.tsx",
15155
+ "storyPath": "general/actions.tsx",
16359
15156
  "subParts": [
16360
15157
  "ActionsItem",
16361
15158
  "ActionsCopy",
@@ -16377,9 +15174,9 @@
16377
15174
  },
16378
15175
  {
16379
15176
  "docPath": "data-display/thought-chain.tsx",
16380
- "example": "import { ThoughtChain } from \"@godxjp/ui/data-display\";\n\n<ThoughtChain\n label=\"思考の手順\"\n defaultExpandedKeys={[\"search\"]}\n items={[\n { key: \"read\", title: \"資料を読む\", description: \"3件\", status: \"success\" },\n {\n key: \"search\",\n title: \"社内規程を検索\",\n status: \"loading\",\n collapsible: true,\n blink: true,\n content: <pre>{hits}</pre>,\n },\n { key: \"write\", title: \"下書きを書く\", status: \"abort\" },\n ]}\n/>",
15177
+ "example": "import { ThoughtChain } from \"@godxjp/chat\";\n\n<ThoughtChain\n label=\"思考の手順\"\n defaultExpandedKeys={[\"search\"]}\n items={[\n { key: \"read\", title: \"資料を読む\", description: \"3件\", status: \"success\" },\n {\n key: \"search\",\n title: \"社内規程を検索\",\n status: \"loading\",\n collapsible: true,\n blink: true,\n content: <pre>{hits}</pre>,\n },\n { key: \"write\", title: \"下書きを書く\", status: \"abort\" },\n ]}\n/>",
16381
15178
  "group": "data-display",
16382
- "importPath": "@godxjp/ui/data-display",
15179
+ "importPath": "@godxjp/chat",
16383
15180
  "name": "ThoughtChain",
16384
15181
  "props": [
16385
15182
  {
@@ -16432,7 +15229,7 @@
16432
15229
  44,
16433
15230
  45
16434
15231
  ],
16435
- "storyPath": "data-display/ThoughtChain.stories.tsx",
15232
+ "storyPath": "data-display/thought-chain.tsx",
16436
15233
  "subParts": [
16437
15234
  "ThoughtChainItem"
16438
15235
  ],
@@ -16453,9 +15250,9 @@
16453
15250
  },
16454
15251
  {
16455
15252
  "docPath": "data-display/org-chart.tsx",
16456
- "example": "import { Avatar, AvatarFallback, Badge, OrgChart } from \"@godxjp/ui/data-display\";\n\n<OrgChart\n label=\"Company org chart\"\n data={[\n {\n key: \"ceo\", name: \"Haruka Tanaka\", title: \"CEO\",\n avatar: <Avatar size=\"sm\"><AvatarFallback>HT</AvatarFallback></Avatar>,\n children: [\n { key: \"cto\", name: \"Kenji Watanabe\", title: \"CTO\" },\n { key: \"bot\", name: \"Review Agent\", title: \"Code review\", variant: \"agent\",\n extra: <Badge tone=\"success\">Running</Badge> },\n ],\n },\n ]}\n/>",
15253
+ "example": "import { Avatar, AvatarFallback, Badge } from \"@godxjp/ui/data-display\";\nimport { OrgChart } from \"@godxjp/ui/lab\";\n\n<OrgChart\n label=\"Company org chart\"\n data={[\n {\n key: \"ceo\", name: \"Haruka Tanaka\", title: \"CEO\",\n avatar: <Avatar size=\"sm\"><AvatarFallback>HT</AvatarFallback></Avatar>,\n children: [\n { key: \"cto\", name: \"Kenji Watanabe\", title: \"CTO\" },\n { key: \"bot\", name: \"Review Agent\", title: \"Code review\", variant: \"agent\",\n extra: <Badge tone=\"success\">Running</Badge> },\n ],\n },\n ]}\n/>",
16457
15254
  "group": "data-display",
16458
- "importPath": "@godxjp/ui/data-display",
15255
+ "importPath": "@godxjp/ui/lab",
16459
15256
  "name": "OrgChart",
16460
15257
  "props": [
16461
15258
  {
@@ -16496,8 +15293,9 @@
16496
15293
  44,
16497
15294
  45
16498
15295
  ],
16499
- "storyPath": "data-display/OrgChart.stories.tsx",
15296
+ "storyPath": "data-display/org-chart.tsx",
16500
15297
  "tagline": "An organization chart: boxes (avatar, name, title, extra) joined by CSS connector lines, top-down; `agent` nodes are dashed. Scrolls horizontally in its own named region when wider than its container, and turns into an indented Tree when the CONTAINER is under 40rem. APG tree view (tree/treeitem/group, roving tabindex, arrow keys).",
15298
+ "tier": "lab",
16501
15299
  "usage": [
16502
15300
  "DO mark AI agents with `variant: \"agent\"` — the box is dashed AND its accessible name ends in a localized \"AI agent\", so the kind is never carried by the stroke alone.",
16503
15301
  "DO give it the width it has; the breakpoint is a container query, so a chart in a narrow side panel switches to the Tree form on a wide screen too.",
@@ -16511,6 +15309,9 @@
16511
15309
  ]
16512
15310
  },
16513
15311
  {
15312
+ "absorbed": [
15313
+ "Thumbnail"
15314
+ ],
16514
15315
  "docPath": "data-display/image.tsx",
16515
15316
  "example": "import { Image, ImagePreviewGroup, Prose } from \"@godxjp/ui/data-display\";\n\n<ImagePreviewGroup>\n <Prose>\n <p><Image src=\"/shots/before.png\" alt=\"Before the fix\" width={320} /></p>\n <p><Image src=\"/shots/after.png\" alt=\"After the fix\" width={320} /></p>\n <p><Image src=\"/logo.svg\" alt=\"\" width={24} preview={false} /></p>\n </Prose>\n</ImagePreviewGroup>",
16516
15317
  "group": "data-display",
@@ -16553,9 +15354,9 @@
16553
15354
  "type": "\"sm\" | \"md\" | \"lg\""
16554
15355
  },
16555
15356
  {
16556
- "description": "object-fit inside a fixed frame. Default `cover` once the height is fixed (size or height).",
15357
+ "description": "object-fit inside a fixed frame (`cover` crops, `contain` letterboxes). Default `cover` once the height is fixed (size or height). `intrinsic` keeps the picture's OWN ratio: the frame is the `size` height (default md) at the picture's own width — never cropped — with the hairline thumbnail frame on a muted backdrop, clamped to its column; `width`/`height` then stay `<img>` attributes (the file's real pixels) so the width is reserved before the bytes land. Use it for a wrapping row of mixed portrait/landscape screenshots at one height — what the retired `Thumbnail` was (v32). Pair with `preview={false}` for a plain picture.",
16557
15358
  "name": "fit",
16558
- "type": "\"cover\" | \"contain\""
15359
+ "type": "\"cover\" | \"contain\" | \"intrinsic\""
16559
15360
  },
16560
15361
  {
16561
15362
  "description": "Text under the picture in a figure held to the picture's width — a long file name truncates instead of widening the tile. godx extension.",
@@ -16584,7 +15385,7 @@
16584
15385
  }
16585
15386
  ],
16586
15387
  "related": [
16587
- "Thumbnail — a framed picture at a fixed height, with no preview.",
15388
+ "Image `fit=\"intrinsic\" preview={false}` — a framed picture at a fixed height and its own width, with no preview (the former Thumbnail).",
16588
15389
  "Prose — the Markdown body the group usually wraps.",
16589
15390
  "Carousel — slides on the page itself, not a full-viewport preview."
16590
15391
  ],
@@ -16595,7 +15396,7 @@
16595
15396
  44,
16596
15397
  45
16597
15398
  ],
16598
- "storyPath": "data-display/Image.stories.tsx",
15399
+ "storyPath": "data-display/image.tsx",
16599
15400
  "subParts": [
16600
15401
  "ImagePreviewGroup"
16601
15402
  ],
@@ -16615,9 +15416,9 @@
16615
15416
  },
16616
15417
  {
16617
15418
  "docPath": "data-entry/attachments.tsx",
16618
- "example": "import { Attachments } from \"@godxjp/ui/data-entry\";\n\n<Attachments\n items={files}\n onChange={({ fileList }) => setFiles(fileList)}\n overflow=\"scrollX\"\n maxCount={5}\n/>",
15419
+ "example": "import { Attachments } from \"@godxjp/chat\";\n\n<Attachments\n items={files}\n onChange={({ fileList }) => setFiles(fileList)}\n overflow=\"scrollX\"\n maxCount={5}\n/>",
16619
15420
  "group": "data-entry",
16620
- "importPath": "@godxjp/ui/data-entry",
15421
+ "importPath": "@godxjp/chat",
16621
15422
  "name": "Attachments",
16622
15423
  "props": [
16623
15424
  {
@@ -16702,7 +15503,7 @@
16702
15503
  44,
16703
15504
  45
16704
15505
  ],
16705
- "storyPath": "data-entry/Attachments.stories.tsx",
15506
+ "storyPath": "data-entry/attachments.tsx",
16706
15507
  "tagline": "The chat-surface attachment collection (Ant Design X Attachments): file cards, inline placeholder, optional full-screen drop target, and ref.select/ref.upload — inherits antd Upload props but names the list `items`.",
16707
15508
  "usage": [
16708
15509
  "DO keep antd field names on each item (`thumbUrl`, `originFileObj`, `uid`) — an Ant X call site should compile unchanged.",
@@ -16717,9 +15518,9 @@
16717
15518
  },
16718
15519
  {
16719
15520
  "docPath": "data-display/marquee.tsx",
16720
- "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>",
15521
+ "example": "import { Marquee } from \"@godxjp/ui/lab\";\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>",
16721
15522
  "group": "data-display",
16722
- "importPath": "@godxjp/ui/data-display",
15523
+ "importPath": "@godxjp/ui/lab",
16723
15524
  "name": "Marquee",
16724
15525
  "props": [
16725
15526
  {
@@ -16789,8 +15590,9 @@
16789
15590
  44,
16790
15591
  45
16791
15592
  ],
16792
- "storyPath": "data-display/Marquee.stories.tsx",
15593
+ "storyPath": "data-display/marquee.tsx",
16793
15594
  "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.",
15595
+ "tier": "lab",
16794
15596
  "usage": [
16795
15597
  "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.",
16796
15598
  "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.",
@@ -16808,9 +15610,9 @@
16808
15610
  },
16809
15611
  {
16810
15612
  "docPath": "layout/masonry.tsx",
16811
- "example": "import { Masonry } from \"@godxjp/ui/layout\";\nimport { Card, CardContent } from \"@godxjp/ui/data-display\";\nimport { Text } from \"@godxjp/ui/general\";\n\n// T flows from `items`, so `data` in itemRender is whatever you put there — give the\n// collection a shape and the render callback needs no annotation.\ntype Note = { id: string; body: string };\nconst notes: Note[] = useNotes();\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/>",
15613
+ "example": "import { Masonry } from \"@godxjp/ui/lab\";\nimport { Card, CardContent } from \"@godxjp/ui/data-display\";\nimport { Text } from \"@godxjp/ui/general\";\n\n// T flows from `items`, so `data` in itemRender is whatever you put there — give the\n// collection a shape and the render callback needs no annotation.\ntype Note = { id: string; body: string };\nconst notes: Note[] = useNotes();\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/>",
16812
15614
  "group": "layout",
16813
- "importPath": "@godxjp/ui/layout",
15615
+ "importPath": "@godxjp/ui/lab",
16814
15616
  "name": "Masonry",
16815
15617
  "props": [
16816
15618
  {
@@ -16869,8 +15671,9 @@
16869
15671
  44,
16870
15672
  45
16871
15673
  ],
16872
- "storyPath": "layout/Masonry.stories.tsx",
15674
+ "storyPath": "layout/masonry.tsx",
16873
15675
  "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.",
15676
+ "tier": "lab",
16874
15677
  "usage": [
16875
15678
  "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.",
16876
15679
  "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.",
@@ -16948,7 +15751,7 @@
16948
15751
  44,
16949
15752
  45
16950
15753
  ],
16951
- "storyPath": "layout/Affix.stories.tsx",
15754
+ "storyPath": "layout/affix.tsx",
16952
15755
  "tagline": "Ant Design `Affix` (6.6.5): pin an element to its scrollport once the page scrolls past it, and REPORT that it is pinned. `position: sticky` pins and then says nothing, so a sticky header cannot shrink, swap its logo or raise its shadow; this one exposes the boolean as `data-affixed` for CSS and `onChange` for JavaScript, and holds the flow open with a MEASURED placeholder so the page does not jump by the bar's height at the moment it pins.",
16953
15756
  "usage": [
16954
15757
  "DO use it whenever the pinned state has to be VISIBLE — a header that condenses, a toolbar that gains a shadow over the rows sliding under it, a filter rail that swaps to a compact form. That state is the entire reason this exists; if you need none of it, plain `position: sticky` is lighter and correct.",
@@ -16968,9 +15771,9 @@
16968
15771
  },
16969
15772
  {
16970
15773
  "docPath": "navigation/anchor.tsx",
16971
- "example": "import { Anchor } from \"@godxjp/ui/navigation\";\n\n<Anchor\n offsetBlockStart={64}\n targetOffsetBlockStart={88}\n label=\"On this page\"\n items={[\n { key: \"overview\", href: \"#overview\", title: \"Overview\" },\n {\n key: \"pricing\",\n href: \"#pricing\",\n title: \"Pricing\",\n children: [{ key: \"tiers\", href: \"#tiers\", title: \"Tiers\" }],\n },\n { key: \"faq\", href: \"#faq\", title: \"FAQ\" },\n ]}\n onValueChange={(href) => console.log(\"reading\", href)}\n/>",
15774
+ "example": "import { Anchor } from \"@godxjp/ui/lab\";\n\n<Anchor\n offsetBlockStart={64}\n targetOffsetBlockStart={88}\n label=\"On this page\"\n items={[\n { key: \"overview\", href: \"#overview\", title: \"Overview\" },\n {\n key: \"pricing\",\n href: \"#pricing\",\n title: \"Pricing\",\n children: [{ key: \"tiers\", href: \"#tiers\", title: \"Tiers\" }],\n },\n { key: \"faq\", href: \"#faq\", title: \"FAQ\" },\n ]}\n onValueChange={(href) => console.log(\"reading\", href)}\n/>",
16972
15775
  "group": "navigation",
16973
- "importPath": "@godxjp/ui/navigation",
15776
+ "importPath": "@godxjp/ui/lab",
16974
15777
  "name": "Anchor",
16975
15778
  "props": [
16976
15779
  {
@@ -17072,8 +15875,9 @@
17072
15875
  44,
17073
15876
  45
17074
15877
  ],
17075
- "storyPath": "navigation/Anchor.stories.tsx",
15878
+ "storyPath": "navigation/anchor.tsx",
17076
15879
  "tagline": "Ant Design `Anchor` (6.6.5): the in-page section navigation, and the only thing in this library that COMPUTES which section is current. `NavList activeId` takes that answer as a prop. Resolves by antd's rule — the last section whose edge has crossed a single decision line — which is a pure function of scroll position and therefore cannot flicker, suppresses itself during the programmatic scroll a click starts, reads the landing hash before any scroll event, and jumps rather than tweens under `prefers-reduced-motion`.",
15880
+ "tier": "lab",
17077
15881
  "usage": [
17078
15882
  "DO give every target a real `id` and point `href` at it as `#id`. The links are real anchors: middle-clickable, deep-linkable, and correct before JavaScript boots.",
17079
15883
  "DO set `targetOffsetBlockStart` to the height of whatever is pinned above the content, or a clicked section lands under the header. Leave `offsetBlockStart` alone unless the DECISION LINE also needs moving; it follows `targetOffsetBlockStart` by default.",