@godxjp/ui 28.8.0 → 28.10.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 (271) hide show
  1. package/agent/START-HERE.md +193 -0
  2. package/agent/anti-ai-tells.json +158 -0
  3. package/agent/components/Accordion.json +60 -0
  4. package/agent/components/AccountChip.json +59 -0
  5. package/agent/components/Actions.json +78 -0
  6. package/agent/components/Activity.json +80 -0
  7. package/agent/components/Affix.json +78 -0
  8. package/agent/components/Alert.json +65 -0
  9. package/agent/components/AlertDialog.json +109 -0
  10. package/agent/components/AlertDialogRoot.json +52 -0
  11. package/agent/components/Anchor.json +119 -0
  12. package/agent/components/AppLauncher.json +95 -0
  13. package/agent/components/AppProvider.json +105 -0
  14. package/agent/components/AppSettingPicker.json +88 -0
  15. package/agent/components/AppSettingToggle.json +70 -0
  16. package/agent/components/AppShell.json +159 -0
  17. package/agent/components/AreaChart.json +105 -0
  18. package/agent/components/AspectRatio.json +38 -0
  19. package/agent/components/Attachments.json +76 -0
  20. package/agent/components/AuthAccountSummary.json +64 -0
  21. package/agent/components/AuthDivider.json +35 -0
  22. package/agent/components/AuthFooter.json +48 -0
  23. package/agent/components/AuthIdentity.json +42 -0
  24. package/agent/components/AuthShell.json +108 -0
  25. package/agent/components/AuthStack.json +21 -0
  26. package/agent/components/Avatar.json +89 -0
  27. package/agent/components/Badge.json +96 -0
  28. package/agent/components/Banner.json +48 -0
  29. package/agent/components/BarChart.json +108 -0
  30. package/agent/components/BranchScopePicker.json +89 -0
  31. package/agent/components/Breadcrumb.json +54 -0
  32. package/agent/components/Button.json +133 -0
  33. package/agent/components/Calendar.json +259 -0
  34. package/agent/components/Callout.json +46 -0
  35. package/agent/components/Card.json +112 -0
  36. package/agent/components/CardBar.json +49 -0
  37. package/agent/components/CardContent.json +51 -0
  38. package/agent/components/Carousel.json +50 -0
  39. package/agent/components/Cascader.json +209 -0
  40. package/agent/components/CenteredShell.json +66 -0
  41. package/agent/components/ChatBubble.json +100 -0
  42. package/agent/components/ChatBubbleList.json +64 -0
  43. package/agent/components/ChatComposer.json +160 -0
  44. package/agent/components/ChatSuggestion.json +86 -0
  45. package/agent/components/Checkbox.json +68 -0
  46. package/agent/components/CheckboxGroup.json +96 -0
  47. package/agent/components/CodeBlock.json +64 -0
  48. package/agent/components/Collapsible.json +74 -0
  49. package/agent/components/ColorPicker.json +87 -0
  50. package/agent/components/Command.json +168 -0
  51. package/agent/components/CommandPalette.json +84 -0
  52. package/agent/components/CompactBarTrend.json +101 -0
  53. package/agent/components/Conversations.json +82 -0
  54. package/agent/components/CredentialReveal.json +93 -0
  55. package/agent/components/DataState.json +79 -0
  56. package/agent/components/DataTable.json +268 -0
  57. package/agent/components/DatePicker.json +275 -0
  58. package/agent/components/Descriptions.json +67 -0
  59. package/agent/components/Dialog.json +78 -0
  60. package/agent/components/DraggablePanel.json +106 -0
  61. package/agent/components/DropdownMenu.json +102 -0
  62. package/agent/components/EmptyState.json +83 -0
  63. package/agent/components/ErrorSurface.json +128 -0
  64. package/agent/components/FeatureList.json +43 -0
  65. package/agent/components/Field.json +64 -0
  66. package/agent/components/FilterBar.json +99 -0
  67. package/agent/components/Flex.json +153 -0
  68. package/agent/components/FloatButton.json +91 -0
  69. package/agent/components/Form.json +87 -0
  70. package/agent/components/FormErrors.json +51 -0
  71. package/agent/components/FormField.json +137 -0
  72. package/agent/components/FormFieldArray.json +39 -0
  73. package/agent/components/FormFieldControl.json +129 -0
  74. package/agent/components/FormRoot.json +122 -0
  75. package/agent/components/Heading.json +61 -0
  76. package/agent/components/HoverCard.json +55 -0
  77. package/agent/components/Icon.json +60 -0
  78. package/agent/components/InfiniteQueryState.json +58 -0
  79. package/agent/components/Input.json +122 -0
  80. package/agent/components/InputOTP.json +106 -0
  81. package/agent/components/Label.json +43 -0
  82. package/agent/components/LegalDocumentShell.json +102 -0
  83. package/agent/components/Legend.json +42 -0
  84. package/agent/components/LineChart.json +103 -0
  85. package/agent/components/Link.json +41 -0
  86. package/agent/components/ListRow.json +92 -0
  87. package/agent/components/Logo.json +85 -0
  88. package/agent/components/Marquee.json +91 -0
  89. package/agent/components/Masonry.json +82 -0
  90. package/agent/components/MasterDetail.json +95 -0
  91. package/agent/components/MegaMenu.json +120 -0
  92. package/agent/components/MobileShell.json +73 -0
  93. package/agent/components/NavList.json +63 -0
  94. package/agent/components/NumberInput.json +158 -0
  95. package/agent/components/OrgSwitcher.json +89 -0
  96. package/agent/components/OverlayPortalProvider.json +42 -0
  97. package/agent/components/PageContainer.json +181 -0
  98. package/agent/components/Pagination.json +132 -0
  99. package/agent/components/Paragraph.json +40 -0
  100. package/agent/components/PasswordInput.json +79 -0
  101. package/agent/components/PasswordStrength.json +51 -0
  102. package/agent/components/PermissionMatrix.json +81 -0
  103. package/agent/components/PieChart.json +99 -0
  104. package/agent/components/Popover.json +110 -0
  105. package/agent/components/PrefetchLink.json +65 -0
  106. package/agent/components/Progress.json +79 -0
  107. package/agent/components/Prose.json +57 -0
  108. package/agent/components/QrCode.json +62 -0
  109. package/agent/components/Radio.json +98 -0
  110. package/agent/components/RadioGroup.json +91 -0
  111. package/agent/components/RangeTimeline.json +80 -0
  112. package/agent/components/Rating.json +92 -0
  113. package/agent/components/ResizablePanel.json +69 -0
  114. package/agent/components/ResponsiveGrid.json +77 -0
  115. package/agent/components/Reveal.json +70 -0
  116. package/agent/components/ScrollArea.json +104 -0
  117. package/agent/components/SearchInput.json +98 -0
  118. package/agent/components/Segmented.json +96 -0
  119. package/agent/components/Select.json +397 -0
  120. package/agent/components/Separator.json +86 -0
  121. package/agent/components/ServiceCatalogCta.json +46 -0
  122. package/agent/components/ServiceLauncherCard.json +86 -0
  123. package/agent/components/ServiceRolePanel.json +83 -0
  124. package/agent/components/Sheet.json +85 -0
  125. package/agent/components/Sidebar.json +118 -0
  126. package/agent/components/Skeleton.json +57 -0
  127. package/agent/components/SkeletonArticle.json +71 -0
  128. package/agent/components/SkeletonAvatar.json +50 -0
  129. package/agent/components/SkeletonButton.json +57 -0
  130. package/agent/components/SkeletonForm.json +52 -0
  131. package/agent/components/SkeletonImage.json +37 -0
  132. package/agent/components/SkeletonInput.json +51 -0
  133. package/agent/components/SkeletonNode.json +42 -0
  134. package/agent/components/SkeletonRows.json +49 -0
  135. package/agent/components/SkeletonTable.json +45 -0
  136. package/agent/components/Slider.json +160 -0
  137. package/agent/components/SplitPane.json +66 -0
  138. package/agent/components/StatCard.json +83 -0
  139. package/agent/components/Steps.json +95 -0
  140. package/agent/components/Swatch.json +41 -0
  141. package/agent/components/Switch.json +81 -0
  142. package/agent/components/Table.json +112 -0
  143. package/agent/components/Tabs.json +158 -0
  144. package/agent/components/TagInput.json +105 -0
  145. package/agent/components/Text.json +201 -0
  146. package/agent/components/Textarea.json +126 -0
  147. package/agent/components/ThoughtChain.json +76 -0
  148. package/agent/components/Thumbnail.json +70 -0
  149. package/agent/components/TimePicker.json +200 -0
  150. package/agent/components/TimeRangePicker.json +90 -0
  151. package/agent/components/Timeline.json +47 -0
  152. package/agent/components/TimelineGrid.json +92 -0
  153. package/agent/components/Title.json +67 -0
  154. package/agent/components/Toaster.json +42 -0
  155. package/agent/components/Toggle.json +90 -0
  156. package/agent/components/ToggleGroup.json +102 -0
  157. package/agent/components/Toolbar.json +120 -0
  158. package/agent/components/Tooltip.json +110 -0
  159. package/agent/components/Topbar.json +83 -0
  160. package/agent/components/TopbarItem.json +79 -0
  161. package/agent/components/Transfer.json +141 -0
  162. package/agent/components/Tree.json +185 -0
  163. package/agent/components/TreeSelect.json +232 -0
  164. package/agent/components/TwoFactorSetup.json +79 -0
  165. package/agent/components/Typography.json +42 -0
  166. package/agent/components/Upload.json +221 -0
  167. package/agent/components/UploadCropDialog.json +60 -0
  168. package/agent/components/VisuallyHidden.json +20 -0
  169. package/agent/components/Welcome.json +65 -0
  170. package/agent/components/formatDate.json +46 -0
  171. package/agent/components/inertiaUpload.json +32 -0
  172. package/agent/components/useZodForm.json +39 -0
  173. package/agent/components-index.json +884 -0
  174. package/agent/components.json +15507 -0
  175. package/agent/index.json +56 -0
  176. package/agent/llms.txt +32 -0
  177. package/agent/patterns/account-recovery-settings.json +19 -0
  178. package/agent/patterns/async-data-state.json +20 -0
  179. package/agent/patterns/auth-recovery-panels.json +29 -0
  180. package/agent/patterns/badge-coloring.json +14 -0
  181. package/agent/patterns/common-fixes.json +16 -0
  182. package/agent/patterns/confirm-destructive.json +11 -0
  183. package/agent/patterns/data-table-page.json +18 -0
  184. package/agent/patterns/deferred-loading.json +12 -0
  185. package/agent/patterns/error-pages.json +28 -0
  186. package/agent/patterns/inertia-detail-page.json +13 -0
  187. package/agent/patterns/inertia-list-page.json +15 -0
  188. package/agent/patterns/inertia-persistent-layout.json +14 -0
  189. package/agent/patterns/organization-memberships.json +19 -0
  190. package/agent/patterns/page-sections.json +18 -0
  191. package/agent/patterns/settings-page-responsive.json +18 -0
  192. package/agent/patterns/settings-section-rows.json +23 -0
  193. package/agent/patterns/signup-form.json +13 -0
  194. package/agent/patterns/topbar-account-chip.json +18 -0
  195. package/agent/patterns/transactional-email.json +22 -0
  196. package/agent/patterns-index.json +323 -0
  197. package/agent/patterns.json +342 -0
  198. package/agent/rules.json +237 -0
  199. package/agent/tokens.json +8422 -0
  200. package/agent/vocabulary.json +198 -0
  201. package/dist/components/data-entry/input.js +8 -1
  202. package/dist/components/layout/flex.d.ts +2 -2
  203. package/dist/components/layout/flex.js +2 -0
  204. package/dist/components/ui/tag-input.d.ts +10 -0
  205. package/dist/components/ui/tag-input.js +35 -2
  206. package/dist/contracts/measurement.json +1 -1
  207. package/dist/i18n/messages/en.json +23 -1
  208. package/dist/i18n/messages/ja.json +21 -1
  209. package/dist/i18n/messages/vi.json +21 -1
  210. package/dist/lib/variants.js +4 -1
  211. package/dist/props/components/data-entry.prop.d.ts +21 -2
  212. package/dist/props/components/layout.prop.d.ts +42 -0
  213. package/dist/props/registry.d.ts +9 -0
  214. package/dist/props/registry.js +6 -0
  215. package/dist/props/vocabulary/layout.prop.d.ts +1 -1
  216. package/dist/styles/base.css +47 -14
  217. package/dist/styles/card-layout.css +6 -6
  218. package/dist/styles/chart-layout.css +6 -6
  219. package/dist/styles/control.css +41 -6
  220. package/dist/styles/data-display-layout.css +21 -6
  221. package/dist/styles/density.css +2 -0
  222. package/dist/styles/dialog-layout.css +4 -1
  223. package/dist/styles/focus-ring.css +4 -1
  224. package/dist/styles/layout.css +30 -3
  225. package/dist/styles/navigation-layout.css +3 -1
  226. package/dist/styles/shell-layout.css +27 -21
  227. package/dist/styles/table-layout.css +50 -9
  228. package/dist/styles/text-layout.css +94 -23
  229. package/dist/tokens/components/activity.css +13 -4
  230. package/dist/tokens/components/attachments.css +1 -1
  231. package/dist/tokens/components/badge.css +1 -1
  232. package/dist/tokens/components/card.css +28 -7
  233. package/dist/tokens/components/chart.css +4 -1
  234. package/dist/tokens/components/chat-composer.css +4 -1
  235. package/dist/tokens/components/control.css +69 -30
  236. package/dist/tokens/components/conversations.css +4 -1
  237. package/dist/tokens/components/data-display.css +42 -15
  238. package/dist/tokens/components/data-entry.css +8 -2
  239. package/dist/tokens/components/descriptions.css +1 -1
  240. package/dist/tokens/components/feedback.css +8 -5
  241. package/dist/tokens/components/float-button.css +8 -2
  242. package/dist/tokens/components/legal-document.css +12 -3
  243. package/dist/tokens/components/logo.css +15 -6
  244. package/dist/tokens/components/mega-menu.css +14 -5
  245. package/dist/tokens/components/navigation.css +37 -13
  246. package/dist/tokens/components/segmented.css +9 -2
  247. package/dist/tokens/components/separator.css +4 -1
  248. package/dist/tokens/components/shell.css +96 -31
  249. package/dist/tokens/components/table.css +13 -6
  250. package/dist/tokens/components/thought-chain.css +4 -1
  251. package/dist/tokens/components/toggle.css +4 -1
  252. package/dist/tokens/components/tree.css +1 -1
  253. package/dist/tokens/components/upload.css +21 -9
  254. package/dist/tokens/foundation.css +24 -30
  255. package/dist/tokens/semantic/layout.css +19 -5
  256. package/docs/COMPOSITION-VS-COMPONENT.md +41 -0
  257. package/docs/DESIGN-AUTHORITY.md +14 -0
  258. package/docs/DEVELOPMENT.md +81 -6
  259. package/docs/TOKENS.md +16 -1
  260. package/docs/data-entry/tag-input.tsx +37 -0
  261. package/docs/layout/flex.tsx +40 -0
  262. package/docs/roadmap/website-components.md +34 -0
  263. package/docs/showcase/case4-login.tsx +10 -2
  264. package/docs/showcase/case5-shift-calendar.tsx +1 -1
  265. package/docs/showcase/case6-agency-handy.tsx +6 -6
  266. package/docs/showcase/futurelastic-web.tsx +7 -9
  267. package/docs/showcase/marketing-page.tsx +61 -52
  268. package/docs/showcase/table-expandable-rows.tsx +4 -1
  269. package/docs/showcase/table-pagination.tsx +88 -18
  270. package/docs/showcase/theme-customization.tsx +25 -2
  271. package/package.json +8 -5
@@ -0,0 +1,181 @@
1
+ {
2
+ "example": "import { PageContainer, Flex } from \"@godxjp/ui/layout\";\nimport { Button } from \"@godxjp/ui/general\";\n\nexport default function OrdersPage() {\n return (\n <PageContainer\n title=\"注文一覧\"\n subtitle=\"直近30日間の受注データ\"\n breadcrumb={[{ label: \"ホーム\", to: \"/\" }, { label: \"注文一覧\" }]}\n extra={<Button>新規注文</Button>}\n >\n <Flex direction=\"col\" gap=\"lg\">{/* page content */}</Flex>\n </PageContainer>\n );\n}",
3
+ "group": "layout",
4
+ "importPath": "@godxjp/ui/layout",
5
+ "name": "PageContainer",
6
+ "props": [
7
+ {
8
+ "description": "Instance footer inset; omitted preserves the theme.",
9
+ "name": "footerPad",
10
+ "type": "PadProp"
11
+ },
12
+ {
13
+ "description": "Instance toolbar inset; omitted preserves the theme.",
14
+ "name": "toolbarPad",
15
+ "type": "PadProp"
16
+ },
17
+ {
18
+ "description": "Tên khả truy cập của landmark <nav> breadcrumb. Mặc định là chuỗi Breadcrumb đã dịch.",
19
+ "name": "breadcrumbLabel",
20
+ "type": "string"
21
+ },
22
+ {
23
+ "description": "Page heading rendered as <h1>.",
24
+ "name": "title",
25
+ "required": true,
26
+ "type": "string"
27
+ },
28
+ {
29
+ "description": "Secondary line beneath the title.",
30
+ "name": "subtitle",
31
+ "type": "string"
32
+ },
33
+ {
34
+ "description": "Status/meta band beside the title (StatusBadge, environment tag, \"updated …\" meta). Sits on the title line at the token-owned --page-header-status-gap and wraps UNDER the title on compact viewports. Part of the canonical page-header contract — never hand-lay a badge next to the <h1>.",
35
+ "name": "status",
36
+ "type": "ReactNode"
37
+ },
38
+ {
39
+ "description": "Action buttons / controls rendered at the END of the title row. A bare node is the whole slot and stays the shape every page already passes — it lands in `start`. `{ start, end }` splits it in two so a page can put something AFTER the actions: the record-screen convention is identity → actions → PAGER LAST, and with one slot the pager had to live in a second header band inside the body, so an app carried two header shapes (gh#734). Both sub-slots are direct children of the one header-extra box in source order, so DOM order IS reading order and the accessibility tree follows; unused, nothing renders at all. The union is `TabsExtraProp`'s on purpose — `Tabs.extra` already accepts exactly this for exactly this reason, and the names are logical (`start`/`end`, never left/right), so they swap sides under dir=\"rtl\".",
40
+ "name": "extra",
41
+ "type": "ReactNode | { start?: ReactNode; end?: ReactNode }"
42
+ },
43
+ {
44
+ "description": "FIXED chrome band between the page header and the body — a filter strip, a status bar, a \"channel workflow\" rail. It is a SIBLING of the body, not content inside it: with `fill` the body is the scroll viewport, so the band is `flex: none` OUTSIDE the scroller and never scrolls away or gets slid under. Shares the page gutters and the `measure` cap with the header and the body (the three bands line up on both edges), goes full-bleed under variant=\"flush\" (wrap padded strips in PageContainer.Inset), and renders NOTHING when omitted — no wrapper, no gap. It sits FLUSH against the header above and the body below: chrome is attached, not a third page section floating between two voids, so the band cancels the container gap from itself and its ONLY breathing room is its own inset.",
45
+ "name": "toolbar",
46
+ "type": "ReactNode"
47
+ },
48
+ {
49
+ "description": "Content area pinned below the page body. The BAND (its top rule and, with `stickyFooter`, its background) always spans the page; its CONTENT is laid out in the same column as the header and body (gh#682). With a cap — `measure=\"narrow\" | \"medium\"` or a service-wide `--app-shell-page-max-width` inside AppShell — an end-aligned Save/Cancel bar ends on the body's end edge and a full-row composer (`<Flex grow>`) spans exactly the body's content column; with no cap it is unchanged. The footer's children stay its direct children (no wrapper element), and an instance `footerPad` end inset composes with the cap. Never add a call-site `max-w-*` to line it up.",
50
+ "name": "footer",
51
+ "type": "ReactNode"
52
+ },
53
+ {
54
+ "description": "The page sections. Every direct child of the body is spaced from the previous one by --page-body-gap (the section step): drop your Cards straight in — do NOT wrap them in a Flex to space them, do NOT add gap-*/mt-*. Group items INSIDE a section with <Flex direction=\"col\" gap> or <ResponsiveGrid>.",
55
+ "name": "children",
56
+ "type": "ReactNode"
57
+ },
58
+ {
59
+ "description": "Ordered trail of { label, to? } segments above the title.",
60
+ "name": "breadcrumb",
61
+ "type": "BreadcrumbItemProp[]"
62
+ },
63
+ {
64
+ "description": "Override the breadcrumb nav landmark's accessible name (defaults to a localized \"Breadcrumb\"). Required when more than one PageContainer (each with its own breadcrumb) renders on the same page/view — two nav landmarks sharing one name/role fail landmark-unique.",
65
+ "name": "breadcrumbAriaLabel",
66
+ "type": "string"
67
+ },
68
+ {
69
+ "defaultValue": "\"default\"",
70
+ "description": "Page shell layout; flush removes padding for full-bleed content.",
71
+ "name": "variant",
72
+ "type": "\"default\" | \"narrow\" | \"flush\" | \"ghost\""
73
+ },
74
+ {
75
+ "defaultValue": "\"default\"",
76
+ "description": "Spacing density across the page subtree.",
77
+ "name": "density",
78
+ "type": "\"compact\" | \"default\" | \"comfortable\""
79
+ },
80
+ {
81
+ "defaultValue": "\"default\"",
82
+ "description": "Whole-page semantic composition. \"admin-collection\" owns header-to-toolbar rhythm, collection search measure, control height and table density for the subtree through themeable tokens.",
83
+ "name": "preset",
84
+ "type": "\"default\" | \"admin-collection\""
85
+ },
86
+ {
87
+ "defaultValue": "\"stack\"",
88
+ "description": "How the title band and `extra` share the header row BELOW the 640px step. \"stack\" (default) drops `extra` onto its own full-width line under the subtitle. \"responsive-inline\" keeps it beside the title at the token-owned --page-header-extra-measure (11rem) and lets the title/subtitle wrap — use it for ONE compact control (a search field, a single primary action) that must stay on the title row at 390px. At >=640px the two arrangements are identical.",
89
+ "name": "headerLayout",
90
+ "type": "\"stack\" | \"responsive-inline\""
91
+ },
92
+ {
93
+ "defaultValue": "\"document\"",
94
+ "description": "What the page's top row IS, which decides the `<h1>`'s type step. \"document\" (default) = the row is the page's TITLE (a record, a form, a collection, a report): --page-title-font-size (h1, 20px) with the existing responsive step down at 720px; no attribute is emitted, so an existing page is byte-identical. \"chrome\" = the row is the surface's own furniture — a chat channel name, a mail subject line, an IDE tab — naming the thing the user is already INSIDE instead of announcing a document; the h1 takes --page-title-font-size-chrome (--heading-h3 = the 14px body step) at EVERY width, including below 720px where the document-scale step would otherwise pull it back UP. The heading stays an `<h1>` either way — this moves the type step only, never the element, so the screen-reader outline is untouched.",
95
+ "name": "headerScale",
96
+ "type": "\"document\" | \"chrome\""
97
+ },
98
+ {
99
+ "defaultValue": "\"default\"",
100
+ "description": "Bounded page MEASURE shared by the header AND the body — a third axis, ORTHOGONAL to `variant` (chrome) and `headerLayout` (arrangement). \"narrow\" (--page-measure-narrow, 42rem outer → 624px visible surface) and \"medium\" (--page-measure-medium, 48rem outer → 720px visible surface) cap BOTH bands, so a header `extra` action ends flush with the body surface instead of stranded at the page edge — unlike variant=\"narrow\", which caps only the body. The package-owned page gutters sit INSIDE the cap, and it is a max, so a 390px viewport stays fluid (358px surface at the 16px compact gutter). The footer BAND is intentionally not capped (its border/background is page chrome when `stickyFooter` pins it), but its CONTENT follows the same measure (gh#682): an end-aligned footer action ends flush with the body too, instead of stranded at the band edge. Inside AppShell the page is fluid by default; a service-wide `--app-shell-page-max-width` caps the same header/toolbar/body bands and the footer CONTENT (never the footer band), and `measure` overrides it on the page that sets it.",
101
+ "name": "measure",
102
+ "type": "\"default\" | \"narrow\" | \"medium\""
103
+ },
104
+ {
105
+ "defaultValue": "false",
106
+ "description": "Pin footer to viewport bottom on scroll — pairs with variant=\"narrow\".",
107
+ "name": "stickyFooter",
108
+ "type": "boolean"
109
+ },
110
+ {
111
+ "defaultValue": "\"always\"",
112
+ "description": "When the footer is sticky, control WHEN it shows. \"always\" keeps it pinned the whole time; \"onScroll\" hides it until the header scrolls out of view then slides it up — the standard edit/create save bar. Stays mounted (no reflow → no jitter).",
113
+ "name": "footerReveal",
114
+ "type": "\"always\" | \"onScroll\""
115
+ },
116
+ {
117
+ "defaultValue": "false",
118
+ "description": "Grow the body to fill the remaining shell height. Default false = top-packed, content-height (short pages leave no stretched void). Enable for a full-height DataTable, SplitPane, or a chat surface.",
119
+ "name": "fill",
120
+ "type": "boolean"
121
+ },
122
+ {
123
+ "defaultValue": "false",
124
+ "description": "Skeletonise the TITLE BAND only while the page's record resolves (title/subtitle placeholders + aria-busy; the <h1> stays in the outline with an sr-only accessible name). Breadcrumbs and `extra` stay live — they come from the route, not the record. This is not a page-wide loading flag; use DataState for the body.",
125
+ "name": "headerLoading",
126
+ "type": "boolean"
127
+ },
128
+ {
129
+ "description": "Link component used for breadcrumb / header links (e.g. an Inertia or React Router `Link`). Defaults to a native `<a>`.",
130
+ "name": "linkComponent",
131
+ "type": "React.ElementType"
132
+ }
133
+ ],
134
+ "related": [
135
+ "PageContainer.Inset — use INSIDE a `variant='flush'` PageContainer to re-introduce horizontal padding for strips like Toolbar or intro text that should align with the page header, while the surrounding DataTable stays full-bleed. Not a standalone page shell.",
136
+ "PageContainer — always use PageContainer for new pages; it supports `children`, `toolbar`, `footer`, `variant`, `density`, `stickyFooter`, and `fill`. Legacy code using the old prop names (`description` → `subtitle`, `actions` → `extra`) should be migrated to PageContainer.",
137
+ "AppShell — the outer shell that owns the sidebar/topbar layout grid; PageContainer lives inside AppShell's `children` slot. Do not put AppShell inside PageContainer — the nesting order is AppShell → PageContainer.",
138
+ "SplitPane — use instead of PageContainer when the page body needs a fixed-width aside panel alongside main content (e.g. a detail drawer next to a list). PageContainer has no aside slot; SplitPane fills that gap and can itself be placed inside PageContainer's children."
139
+ ],
140
+ "rules": [
141
+ 23
142
+ ],
143
+ "storyPath": "layout/PageContainer.stories.tsx",
144
+ "tagline": "Mandatory page shell — EVERY page wraps its content in PageContainer (title/subtitle/extra/footer/breadcrumb).",
145
+ "usage": [
146
+ "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.",
147
+ "CANONICAL PAGE-HEADER CONTRACT: PageContainer's embedded header IS the DXS `PageHeader` — there is deliberately NO separate PageHeader export, so the page header cannot be re-created or nested. It owns breadcrumbs (`breadcrumb`), title (`title`), subtitle/description (`subtitle`), status/meta (`status`), actions (`extra`) and responsive overflow (`headerLayout` + `measure`). Loading/error/denied are compositions of siblings, never hand-rolls: skeleton `title`/`subtitle` content or `SkeletonDetail` body while a detail loads; `ErrorSurface` (from @godxjp/ui/layout) REPLACES the page for denied (403) / not-found (404) / failed (5xx) whole-page states; `Alert.QueryError` / `DataState` own an in-body query failure.",
148
+ "DO: Use the `extra` prop (not a sibling div, not a wrapper) for action buttons or controls that sit right of the title row — e.g. `extra={<Button>新規作成</Button>}`. Use the `footer` prop for a pinned action bar below the body (e.g. Save/Cancel on a form page); combine with `stickyFooter` to pin it to the viewport bottom on scroll.",
149
+ "DO: Use `toolbar` for any FIXED strip that belongs between the page header and the page content — a filter/segment bar, a status or connection band, a list's bulk-action rail. DON'T put it in `children` (with `fill` the body is the scroller, so it scrolls out of sight) and NEVER hand-lay it with `position: sticky` / a `top-0 z-10` wrapper at the call site: page chrome is the shell's job, and a sticky strip still lets content flow underneath it — the half-sliced row every hand-rolled version produces. It stays out of the scroll viewport, inherits the page gutters and the `measure` cap so it lines up with the title and the body, and is entirely absent from the DOM when the prop is omitted.",
150
+ "DO: Know the `toolbar` band draws NO bottom rule by default — `--page-toolbar-divider` is unset and falls back to `--page-header-divider` (itself `none`), so a service that opts into the page-header divider gets a consistent band rule in ONE declaration, and `--page-toolbar-divider: none` silences just the band. `variant='ghost'` keeps both quiet unless a theme opts one in explicitly, which it then lets through.",
151
+ "DO: Give the `toolbar` band its own SURFACE from the theme when the design separates it from the page ground — `--page-toolbar-background: hsl(var(--card));` declared once (`:root` or a scoped `[data-tenant]`) paints the whole band, page gutters and `measure` cap included, and stays re-themeable per tenant. It is the `background` shorthand, so a gradient works too. The default is `transparent`, i.e. the band looks exactly as it did before the knob existed (rule #44).",
152
+ "DO: Set `--page-toolbar-pad-block` in the SAME theme declaration that paints or rules the band. It is the band's ONLY breathing room: the band sits FLUSH against the header and the body (chrome is attached — a ruled, painted band adrift in two 16px voids divides nothing), so there is no outside space to tune. The default is `0` and stays `0`: a transparent band is not a surface and has no inside for an inset to breathe, and under `fill` every pixel of band height comes straight off the scroll viewport the slot exists to protect. Once the band is painted or ruled it DOES have an inside, and `--page-toolbar-pad-block: var(--space-2)` is where that inset belongs. The CALL SITE never sets it — a strip padded at the call site pads only the strip, not the band.",
153
+ "DO: Silence the `footer` band's top rule with `--page-footer-divider: none` when the footer content already carries its own frame — a chat composer is a bordered Card, and the shell's full-width rule then lands directly above it as a SECOND line (a pixel diff against a consumer chat design caught a 100%-wide rule at y=701 the design does not have). This is the ONE page-chrome divider whose default is a RULE rather than silence, deliberately: `footer` is the shared slot a form's Save/Cancel bar lands in, where that line separates the actions from the content. Unset is byte-identical to the literal the rule used to hard-code. All three page bands are now one contract: `--page-header-divider` / `--page-toolbar-divider` / `--page-footer-divider`, each read at the CALL SITE with a fallback, none of them a `border-*` utility at the call site.",
154
+ "DON'T: Style the `toolbar` band from the call site. `toolbar={<div className='bg-card py-1.5'>…</div>}` is hand-laid page chrome: the utility paints the STRIP, not the band, so it stops at the content box instead of running the full page width (and full-bleed under `variant='flush'`); it is invisible to per-tenant theming; and it puts geometry the shell owns back into the app. The band's ground, inset and rule are `--page-toolbar-background` / `--page-toolbar-pad-block` / `--page-toolbar-divider` — three theme declarations, zero call-site classes.",
155
+ "DO: Set `headerScale='chrome'` when the page's top row is CHROME rather than a document title — a chat channel header, a mail thread's subject line, an IDE tab, a conversation view. The `<h1>` drops to the body type step (--page-title-font-size-chrome) at every width, so the header band stops eating the height the content needs: a consumer chat page measured a 61px band with a 24px channel name where the design asked for ~40px at the `sm` step. Pair it with `variant='ghost'` for the full quiet chrome header — ghost drops the header's bottom pad and lets no divider inherit in — and with `fill` + `toolbar` + `footer`/`stickyFooter` for the canonical chat surface. The heading stays an `<h1>`: this is a type step, never a heading-level downgrade. The same attribute also drops the page's top padding to `--page-pad-block-start-chrome` (0), so the band sits ON the frame instead of floating in a document's top margin — four consequences of ONE fact (this row is furniture), not four props a call site has to keep in lockstep: the subtitle drops to `--page-subtitle-font-size-chrome` (~11px) so the caption under a channel name stops matching the name's own size, and the `extra` cluster centres on the bar instead of top-packing against a heading that is no longer tall. If a design wants its chrome inset or a different caption step, the theme retunes those tokens once; never pad, negative-margin or `self-center` the page at the call site.",
156
+ "DON'T: Reach for `headerScale='chrome'` just because a title \"looks too big\" on an ordinary document page (a record detail, a form, a collection, a report) — the page title is the document's headline and the h1 step is the system's answer for it; shrinking it there only breaks the type rhythm the rest of the page is measured against. And NEVER override `--page-title-font-size` (or put a `text-sm` / `text-base` utility on the title) at the call site to fake it: that re-themes every page in the subtree, is invisible to the 720px responsive step, and puts page-chrome geometry back in the app. If a service wants a different chrome step, it retunes `--page-title-font-size-chrome` (or `--page-subtitle-font-size-chrome`) once in its theme. Same for the header actions: never hang `self-center` / `items-center` on the node you pass to `extra` to fix an off-centre icon row — that aligns one call site's box while every other chrome page keeps the document's top-packed row.",
157
+ "DO: Use `variant='flush'` when the page body contains a full-bleed component like DataTable. Inside a flush container, wrap any padded strips (Toolbar, intro text) in `<PageContainer.Inset>` to align them with the header. Never add manual `px-*` or `p-*` padding to compensate — use PageContainer.Inset.",
158
+ "DO: Pass `breadcrumb` as an ordered array of `{ label, to? }` objects from root to current page. The last item is automatically rendered without a link and receives `aria-current='page'`; earlier items with `to` become router `<Link>` elements. Never hand-roll a breadcrumb nav inside a PageContainer.",
159
+ "DON'T: Use `density` to change individual control sizes — it cascades spacing across the entire page subtree. Set it once per page (e.g. `density='compact'` for data-dense list pages) and let all child components inherit it. Do not apply density classes manually.",
160
+ "DO: Use `preset='admin-collection'` for canonical Admin list pages. It owns the toolbar/search/control/table composition once at PageContainer level; do not repeat widths, heights, cell padding or media queries on child fields and rows.",
161
+ "DO: Use `subtitle` (not `description`) and `extra` (not `actions`) — those are the canonical page-header names. If you see `description` / `actions` in old code, migrate them.",
162
+ "DO: Pass `extra={{ start: <actions/>, end: <pager/> }}` when the header convention is identity → action cluster → PAGER LAST. The two sub-slots render in that order inside the one header-extra box, so the record-pager band that used to live inside the body moves up and the app stops carrying two header shapes (gh#734). The shape is `Tabs.extra`'s, logical (`start`/`end`), and a bare node still means exactly what it always did.",
163
+ "DON'T: Fake \"after the actions\" by putting the pager in a second header strip inside `children`, or by appending it to the same node you pass to `extra` and spacing it with a utility — the first gives the page two header bands to keep in sync, the second hand-lays the gap the header-extra box already owns.",
164
+ "DO: Leave `fill` off (the default) for ordinary pages — the body is content-height and top-packed, so a short page on a tall viewport leaves no stretched empty void below the content (the page background simply spans the shell). Only set `fill` when the body itself should occupy the full remaining height: a full-height DataTable, a SplitPane, or a chat surface whose message list scrolls and whose composer is pinned to the bottom via `footer` + `stickyFooter`; or a page whose ENTIRE body is a `variant='page'` EmptyState, which then takes that height and centres in it (a zero-state that is the whole page is the one short page that must NOT top-pack — see EmptyState). DON'T add a manual `min-h-screen` / `flex-1` wrapper or a spacer div to fight or fake this.",
165
+ "DO: Reach for `headerLayout=\"responsive-inline\"` when a SINGLE compact header control (a member search, one primary action) must stay beside the title at 390px instead of wrapping under the subtitle. Its measure is the token `--page-header-extra-measure` (11rem) — never a consumer `w-[176px]` or a media query in app CSS. Keep the default `stack` when `extra` holds a toolbar of several buttons; squeezing those into the compact measure only makes them wrap in a narrower box.",
166
+ "DO: Know the header draws NO bottom divider by default — it is governed by the semantic token `--page-header-divider` (default `none`). A service theme opts in once, globally, with `--page-header-divider: 1px solid hsl(var(--border));` in its theme CSS. Never re-create the divider with a `border-b` utility on the header or a `<Separator>` under the title. `variant='ghost'` does NOT overrule the token: it blocks a divider from INHERITING in (so an unset token stays silent) but an explicit `--page-header-divider` still draws on a ghost page — the same shape as `--page-toolbar-divider` on the band. Ghost's real quiet half is the header's bottom pad, which it drops.",
167
+ "DO: Bound a readable/feed page with `measure=\"medium\"` (720px visible surface) or `measure=\"narrow\"` (624px) — NEVER a page-local `max-w-[720px]`, a wrapper div, or a consumer CSS variable override. `measure` caps the HEADER and the BODY together, which is the whole point: with `variant=\"narrow\"` the header action stays out at the page edge while the body is 624px, so the action and the card do not share an end edge. Retune the presets once in a service theme via `--page-measure-narrow` / `--page-measure-medium`.",
168
+ "DO: Compose the axes — `variant=\"ghost\" measure=\"medium\" headerLayout=\"responsive-inline\"` is the canonical quiet notification/inbox feed: ghost owns the quiet header rhythm (no divider, no header bottom pad, tighter title→body gap), `measure` owns the shared 720px measure, `headerLayout` keeps one compact control on the title row at 390px. They are independent props precisely so chrome and measure are no longer one variant axis. DON'T stack `variant=\"narrow\"` on top of `measure` — the measure rule simply wins on the body (verified in Chromium: variant=\"narrow\" + measure=\"medium\" resolves the body to 768px, not the intersection), so the `variant=\"narrow\"` is dead weight that only misleads the next reader. `variant=\"narrow\"` is the legacy body-only cap; `measure=\"narrow\"` is the same 624px surface with the header included."
169
+ ],
170
+ "useCases": [
171
+ "A master list page (e.g. invoices, journal entries, customers) where the header holds the page title, a 'New Invoice' button in `extra`, a breadcrumb trail, and a full-bleed DataTable as the body — use `variant='flush'` + `<PageContainer.Inset>` for the Toolbar above the table.",
172
+ "A detail / edit form page where the footer holds Save and Cancel buttons — use `footer={<Flex direction='row' justify='between' fill><Button variant='outline'>削除</Button><Button>保存</Button></Flex>}` with `stickyFooter` + `footerReveal='onScroll'` so the save bar slides up only once the header (and its actions) scroll out of view — the canonical edit/create pattern.",
173
+ "A settings or narrow-form page (e.g. account profile, entity configuration) where `variant='narrow'` constrains content to a readable column width and `stickyFooter` pins the submit bar.",
174
+ "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.",
175
+ "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.",
176
+ "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.",
177
+ "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.",
178
+ "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.",
179
+ "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'`)."
180
+ ]
181
+ }
@@ -0,0 +1,132 @@
1
+ {
2
+ "example": "import { Pagination } from \"@godxjp/ui/navigation\";\n\n<Pagination value={page} total={filtered.length} pageSize={10} showTotal onValueChange={(p) => setPage(p)} />",
3
+ "group": "navigation",
4
+ "importPath": "@godxjp/ui/navigation",
5
+ "name": "Pagination",
6
+ "props": [
7
+ {
8
+ "description": "Tên khả truy cập của landmark phân trang. Bắt buộc khi một trang có nhiều bộ phân trang.",
9
+ "name": "ariaLabel",
10
+ "type": "string"
11
+ },
12
+ {
13
+ "defaultValue": "1",
14
+ "description": "Current page (1-indexed).",
15
+ "name": "value",
16
+ "type": "number"
17
+ },
18
+ {
19
+ "description": "Total number of items.",
20
+ "name": "total",
21
+ "type": "number"
22
+ },
23
+ {
24
+ "defaultValue": "10",
25
+ "description": "Items per page.",
26
+ "name": "pageSize",
27
+ "type": "number"
28
+ },
29
+ {
30
+ "description": "Show total count, or a custom label fn.",
31
+ "name": "showTotal",
32
+ "type": "boolean | (total, range) => ReactNode"
33
+ },
34
+ {
35
+ "description": "Page / page-size change handler.",
36
+ "name": "onValueChange",
37
+ "type": "(page: number, pageSize: number) => void"
38
+ },
39
+ {
40
+ "description": "Selectable page sizes shown in the size changer.",
41
+ "name": "pageSizeOptions",
42
+ "type": "number[]"
43
+ },
44
+ {
45
+ "description": "Show the page-size selector beside the pager.",
46
+ "name": "showSizeChanger",
47
+ "type": "boolean"
48
+ },
49
+ {
50
+ "defaultValue": "true",
51
+ "description": "Hide the control when there is nothing to page through — zero items OR exactly one page. Set false to opt in to the bar on a single page (e.g. to keep showTotal visible); total=0 is always hidden.",
52
+ "name": "hideOnSinglePage",
53
+ "type": "boolean"
54
+ },
55
+ {
56
+ "description": "Compact form for narrow contexts — Prev / n·N / Next, no page-number buttons. The intentional mobile transformation (desktop never wraps).",
57
+ "name": "simple",
58
+ "type": "boolean"
59
+ },
60
+ {
61
+ "description": "Ant Design `showQuickJumper` — a 'go to page' number field at the inline end of the bar. Enter (or the optional `goButton`) commits, clamped into [1, pageCount]; a non-numeric commit is ignored rather than jumping to NaN. The field is named by a real `<label htmlFor>`, not an aria-label.",
62
+ "name": "showQuickJumper",
63
+ "type": "boolean | { goButton?: React.ReactNode }"
64
+ },
65
+ {
66
+ "defaultValue": "\"md\"",
67
+ "description": "Ant Design `size` (`small` → `sm`, `middle` → `md`). Implemented as ONE local `--control-height` on the bar, so the page buttons, the size-changer trigger and the quick-jumper field shrink together and cannot drift apart. `sm` reads `--control-height-sm` of the surrounding density scope (knob `--pagination-control-height-sm`, default `initial`), so a small pager in a compact DataTable matches its size=\"sm\" toolbar controls.",
68
+ "name": "size",
69
+ "type": "\"sm\" | \"md\""
70
+ },
71
+ {
72
+ "defaultValue": "\"end\"",
73
+ "description": "Ant Design `align`, on the logical inline axis. `end` keeps the long-standing table-footer position. With `align=\"end\"` the `showTotal` label sits BESIDE the page buttons (antd); `start` / `center` keep the total pushed to the inline start.",
74
+ "name": "align",
75
+ "type": "\"start\" | \"center\" | \"end\""
76
+ },
77
+ {
78
+ "defaultValue": "true",
79
+ "description": "Ant Design `responsive`. Collapses the bar to its `simple` form below the library's single mobile breakpoint (`useIsMobile`, max-width 767px) instead of leaving a number strip wider than the phone to scroll. `simple` always wins; pass `responsive={false}` to pin the full pager at every width.",
80
+ "name": "responsive",
81
+ "type": "boolean"
82
+ },
83
+ {
84
+ "description": "Disable all navigation controls.",
85
+ "name": "disabled",
86
+ "type": "boolean"
87
+ },
88
+ {
89
+ "description": "Override the nav landmark's accessible name (defaults to a localized \"Pagination\"). Required when more than one Pagination renders on the same page/view — two nav landmarks sharing one name/role fail landmark-unique.",
90
+ "name": "aria-label",
91
+ "type": "string"
92
+ }
93
+ ],
94
+ "related": [
95
+ "DataTable.Pagination — use instead of standalone Pagination when the list is rendered inside a DataTable compound and uses cursor-based navigation (cursor + hasMore + onChange). DataTable.Pagination handles First/Next without page arithmetic; standalone Pagination requires a known total.",
96
+ "InfiniteQueryState — use for infinite-scroll / load-more lists driven by useInfiniteQuery. It auto-manages skeleton, empty, and error states; Pagination is inappropriate here because there is no discrete page number.",
97
+ "DataTable — when offset pagination is needed inside DataTable, prefer composing DataTable with a standalone Pagination below the card rather than DataTable.Pagination if the API is offset-based and returns a total count. DataTable itself does not paginate; you supply `data` for the current page.",
98
+ "SearchInput — often placed in the same toolbar as Pagination. Resetting `value` to page 1 inside the search `onSearchChange` handler is mandatory; forgetting this is the most common bug when combining search and Pagination."
99
+ ],
100
+ "rules": [
101
+ 40
102
+ ],
103
+ "storyPath": "navigation/Pagination.stories.tsx",
104
+ "subParts": [
105
+ "PaginationContent",
106
+ "PaginationEllipsis",
107
+ "PaginationItem",
108
+ "PaginationLink",
109
+ "PaginationNext",
110
+ "PaginationPrevious"
111
+ ],
112
+ "tagline": "Offset/page-based pagination bar. Sits below a table card.",
113
+ "usage": [
114
+ "DO always control Pagination externally: store `value` (page) and `pageSize` in React state (or URL params), and update both in the `onValueChange(page, pageSize)` callback. Pagination is fully controlled — it has no internal state and will not move unless `value` changes.",
115
+ "DO let Pagination hide itself for zero items and single pages (`hideOnSinglePage`, default true) — it is navigation between multiple result pages. Render it inside a table footer only in the DATA state: never during loading, empty, error, or an unmet prerequisite. Pass `hideOnSinglePage={false}` only when you still want the bar on one page to keep `showTotal` visible.",
116
+ "DO trust Pagination to stay ONE horizontal row on desktop (it never wraps). For genuinely narrow viewports use `simple` for the intentional compact transformation rather than letting controls wrap.",
117
+ "DO pass `total` as the raw item count (not page count). The component computes `Math.ceil(total / pageSize)` internally; passing a pre-computed page count as `total` will over-paginate.",
118
+ "DO use `showSizeChanger` together with `pageSizeOptions` when the user needs density control (default options are [10, 20, 50, 100]). When `showSizeChanger` is omitted the page-size Select is not rendered at all — do NOT hand-roll your own Select beside Pagination.",
119
+ "DO use `simple` mode for compact contexts (mobile, sidebars, sheet footers) — it renders Prev / `n / total` / Next with no page-number buttons. Use the full form for primary admin list pages.",
120
+ "DO use `showTotal` to surface item counts: pass `true` for the built-in i18n label, or a function `(total, [from, to]) => ReactNode` for a custom range label like '1–10 of 342 invoices'. Never hard-code a total string beside the component.",
121
+ "DON'T use Pagination for cursor- or infinite-scroll-based lists. Pagination is strictly offset/page-based (`value` is a page number). For cursor pagination inside a DataTable use `DataTable.Pagination`; for infinite scroll use `InfiniteQueryState`.",
122
+ "NOTE the page strip scrolls horizontally rather than wrapping, and its page buttons are normally the keyboard route to that overflow. Disable the whole bar (`disabled`) and there is no such route, so the strip takes `tabindex=0` itself to stay keyboard-scrollable (WCAG 2.1.1). Nothing to configure — just don't strip the attribute in consumer CSS/JS."
123
+ ],
124
+ "useCases": [
125
+ "Server-paged DataTable: do NOT place a Pagination below the card — pass `pagination={{ total, current, pageSize, onChange }}` to DataTable and it renders this component in its own footer (total + page numbers, density-sized).",
126
+ "Standalone offset-paginated admin list pages (e.g. invoice list, customer list, transaction history) rendered outside DataTable — place Pagination below the table card, outside the card border, with `showTotal` and optionally `showSizeChanger`.",
127
+ "Search results pages where the backend accepts `page` + `per_page` query parameters and returns a total count — wire `value` and `pageSize` to URL search params so the URL is shareable and browser-back works.",
128
+ "Reports and filtered data grids where the user needs to export 'all selected pages': `showTotal` with a custom function lets you show '1–50 of 1 200 rows' so the user understands the scope before exporting.",
129
+ "Compact modal or sheet footers with a long list (e.g. selecting from a product catalog inside a dialog) — use `simple` mode to save horizontal space while keeping navigation accessible.",
130
+ "DataTable instances where the server returns an offset-based total and `DataTable.Pagination` is not being used: attach a standalone Pagination below the card and pass the same `page` / `pageSize` state to both the DataTable `data` prop and the API fetch."
131
+ ]
132
+ }
@@ -0,0 +1,40 @@
1
+ {
2
+ "example": "import { Paragraph } from \"@godxjp/ui/general\";\n\n<Paragraph ellipsis={{ rows: 3, expandable: true }}>{description}</Paragraph>\n<Paragraph copyable>{ticketBody}</Paragraph>",
3
+ "group": "general",
4
+ "importPath": "@godxjp/ui/general",
5
+ "name": "Paragraph",
6
+ "props": [
7
+ {
8
+ "defaultValue": "\"div\"",
9
+ "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.",
10
+ "name": "as",
11
+ "type": "\"div\" | \"p\" | \"span\""
12
+ },
13
+ {
14
+ "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).",
15
+ "name": "ellipsis",
16
+ "type": "boolean | { rows, expandable, suffix, symbol, defaultExpanded, expanded, onExpand, onEllipsis, tooltip }"
17
+ }
18
+ ],
19
+ "related": [
20
+ "Text",
21
+ "Typography",
22
+ "Prose",
23
+ "Title"
24
+ ],
25
+ "rules": [
26
+ 2,
27
+ 23
28
+ ],
29
+ "storyPath": "general/typography.tsx",
30
+ "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.",
31
+ "usage": [
32
+ "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.",
33
+ "DO keep the default <div>. Change it to a <p> only when you know the content holds no interactive parts.",
34
+ "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."
35
+ ],
36
+ "useCases": [
37
+ "An issue description clamped to three lines with a 続きを読む control: `<Paragraph ellipsis={{ rows: 3, expandable: true }}>{body}</Paragraph>`.",
38
+ "A release note whose text can be edited in place by an admin."
39
+ ]
40
+ }
@@ -0,0 +1,79 @@
1
+ {
2
+ "example": "import { PasswordInput } from \"@godxjp/ui/data-entry\";\n\n<PasswordInput name=\"password\" autoComplete=\"current-password\" placeholder=\"パスワード\" />",
3
+ "group": "data-entry",
4
+ "importPath": "@godxjp/ui/data-entry",
5
+ "name": "PasswordInput",
6
+ "props": [
7
+ {
8
+ "description": "Control password visibility; false hides the reveal action.",
9
+ "name": "visibilityToggle",
10
+ "type": "boolean | { visible?: boolean; onVisibleChange?: (visible: boolean) => void }"
11
+ },
12
+ {
13
+ "description": "Customize the reveal glyph. The reveal action owns the single trailing slot.",
14
+ "name": "iconRender",
15
+ "type": "(visible: boolean) => ReactNode"
16
+ },
17
+ {
18
+ "description": "Validation state the field paints — Ant Design `status`. `error` also reports `aria-invalid`, so the red boundary and what a screen reader hears are one fact; `warning` paints only, because a warning is not a validity failure. antd's `success`/`validating` are not implemented: antd only draws them together with its `hasFeedback` icon slot, which FormField owns here.",
19
+ "name": "status",
20
+ "type": "\"error\" | \"warning\""
21
+ },
22
+ {
23
+ "defaultValue": "\"outlined\"",
24
+ "description": "Chrome level — Ant Design `variant`. `outlined` is the historical field; `filled` swaps the boundary for a tinted surface (dense forms); `borderless` drops both, for a field inside a box that already draws one. antd's fourth member `underlined` is deliberately absent — a single bottom rule is a Material convention and SmartHR, the JP authority here, draws every field as a full box.",
25
+ "name": "variant",
26
+ "type": "\"outlined\" | \"filled\" | \"borderless\""
27
+ },
28
+ {
29
+ "description": "Character counter — inherited from Input.",
30
+ "name": "count",
31
+ "type": "ControlCountProp"
32
+ },
33
+ {
34
+ "description": "Controlled value (or use defaultValue/uncontrolled).",
35
+ "name": "value",
36
+ "type": "string"
37
+ },
38
+ {
39
+ "description": "Form field name for native submission.",
40
+ "name": "name",
41
+ "type": "string"
42
+ },
43
+ {
44
+ "description": "Placeholder text.",
45
+ "name": "placeholder",
46
+ "type": "string"
47
+ },
48
+ {
49
+ "description": "Disables the field + toggle.",
50
+ "name": "disabled",
51
+ "type": "boolean"
52
+ },
53
+ {
54
+ "description": "Inherited from Input — a leading glyph (e.g. a Lock icon) pinned inside the start of the field. The built-in show/hide eye stays on the trailing edge, so leading + trailing coexist here.",
55
+ "name": "leadingIcon",
56
+ "type": "React.ReactNode"
57
+ }
58
+ ],
59
+ "related": [
60
+ "Input (the base text field this wraps)",
61
+ "FormField (label + error wrapper around it)"
62
+ ],
63
+ "rules": [
64
+ 3,
65
+ 6
66
+ ],
67
+ "storyPath": "data-entry/PasswordInput.stories.tsx",
68
+ "tagline": "Input for passwords with a built-in show/hide eye toggle. Accepts all Input props except `type`.",
69
+ "usage": [
70
+ "DO use for any password / secret field instead of `<Input type=\"password\">` so users get the show/hide affordance.",
71
+ "DO pass `name` + `autoComplete=\"current-password\"|\"new-password\"` for correct form/password-manager behavior.",
72
+ "DON'T add your own eye button — it's built in (and excluded from the tab order)."
73
+ ],
74
+ "useCases": [
75
+ "Login password field",
76
+ "Sign-up / change-password forms (new-password)",
77
+ "API key / secret entry in settings"
78
+ ]
79
+ }
@@ -0,0 +1,51 @@
1
+ {
2
+ "example": "import { PasswordInput, PasswordStrength } from \"@godxjp/ui/data-entry\";\n\nconst rules = [\"length\", \"upper\", \"lower\", \"number\", \"symbol\"] as const;\n\nexport default function PasswordBlock() {\n const [value, setValue] = useState(\"\");\n return (\n <div className=\"ui-stack\">\n <PasswordInput value={value} onChange={(event) => setValue(event.target.value)} />\n <PasswordStrength value={value} rules={rules} />\n </div>\n );\n}",
3
+ "group": "data-entry",
4
+ "importPath": "@godxjp/ui/data-entry",
5
+ "name": "PasswordStrength",
6
+ "props": [
7
+ {
8
+ "description": "The current password value to evaluate.",
9
+ "name": "value",
10
+ "required": true,
11
+ "type": "string"
12
+ },
13
+ {
14
+ "description": "`length` | `upper` | `lower` | `number` | `symbol`. Omit to use defaults.",
15
+ "name": "rules",
16
+ "type": "PasswordRule[]"
17
+ },
18
+ {
19
+ "defaultValue": "true",
20
+ "description": "Render an optional checklist of rule checks below the bar.",
21
+ "name": "showChecklist",
22
+ "type": "boolean"
23
+ },
24
+ {
25
+ "description": "Text labels for the three score buckets.",
26
+ "name": "labels",
27
+ "type": "{ weak: string; fair: string; strong: string }"
28
+ }
29
+ ],
30
+ "related": [
31
+ "PasswordInput",
32
+ "FormField",
33
+ "Input"
34
+ ],
35
+ "rules": [
36
+ 3,
37
+ 6
38
+ ],
39
+ "storyPath": "data-entry/PasswordStrength.stories.tsx",
40
+ "tagline": "Evaluates password quality with a 0-4 score and optional rule checklist. Use with PasswordInput in secure forms.",
41
+ "usage": [
42
+ "DO show PasswordStrength immediately below PasswordInput so users understand password quality before submitting.",
43
+ "DO keep `rules` stable (default list is recommended for broad UI compatibility).",
44
+ "DON'T treat the score as cryptographic strength; use it as a UI hint only."
45
+ ],
46
+ "useCases": [
47
+ "Account signup password field",
48
+ "Password reset workflow",
49
+ "Admin user invite form"
50
+ ]
51
+ }
@@ -0,0 +1,81 @@
1
+ {
2
+ "docPath": "data-display/permission-matrix.tsx",
3
+ "example": "import { Card, CardContent, PermissionMatrix } from \"@godxjp/ui/data-display\";\nimport { grantKey } from \"@godxjp/ui/lib/permission-grid\";\n\nconst grants = new Set(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>",
4
+ "group": "data-display",
5
+ "importPath": "@godxjp/ui/data-display",
6
+ "name": "PermissionMatrix",
7
+ "props": [
8
+ {
9
+ "description": "Role COLUMNS in render order. `locked` keeps that role's cells read-only (with a localized lock badge) even in an editable matrix.",
10
+ "name": "roles",
11
+ "required": true,
12
+ "type": "{ id: string; name: string; description?: string; locked?: boolean }[]"
13
+ },
14
+ {
15
+ "description": "Permission ROWS in render order, with an optional category caption.",
16
+ "name": "permissions",
17
+ "required": true,
18
+ "type": "{ id: string; name: string; description?: string; group?: string }[]"
19
+ },
20
+ {
21
+ "description": "The grant relation: the grantKey(roleId, permissionId) Set from @godxjp/ui/lib/permission-grid (canonical, O(1)), or a plain pair array normalized through the same encoding.",
22
+ "name": "grants",
23
+ "required": true,
24
+ "type": "ReadonlySet<string> | { roleId: string; permissionId: string }[]"
25
+ },
26
+ {
27
+ "description": "Its PRESENCE makes the matrix editable (real Checkbox cells, Space toggles). Omitted, the matrix is the canonical read-only ✓/— grid.",
28
+ "name": "onGrantChange",
29
+ "type": "(roleId: string, permissionId: string, granted: boolean) => void"
30
+ },
31
+ {
32
+ "defaultValue": "false",
33
+ "description": "Force the read-only grid even when onGrantChange is present (viewer permission).",
34
+ "name": "readOnly",
35
+ "type": "boolean"
36
+ },
37
+ {
38
+ "description": "Two role ids compared side by side: their columns tint, and rows where they disagree carry a localized difference badge.",
39
+ "name": "compare",
40
+ "type": "[string, string] | null"
41
+ },
42
+ {
43
+ "defaultValue": "false",
44
+ "description": "With `compare`, keep only the rows the two roles disagree on (差分のみ).",
45
+ "name": "diffOnly",
46
+ "type": "boolean"
47
+ },
48
+ {
49
+ "description": "`true` renders the built-in localized surface; a node replaces it; onRetry adds Retry to the built-in error only.",
50
+ "name": "loading / denied / error / empty / onRetry",
51
+ "type": "boolean | ReactNode / handler"
52
+ },
53
+ {
54
+ "description": "Accessible table name (localized default).",
55
+ "name": "label",
56
+ "type": "string"
57
+ }
58
+ ],
59
+ "related": [
60
+ "lib/permission-grid — the pure grant/diff data helpers the matrix (and any custom RBAC UI) shares.",
61
+ "DataTable — general tabular data with sorting/selection/pagination; PermissionMatrix is the fixed role-grid specialization with a sticky FIRST column (which DataTable cannot pin).",
62
+ "ServiceRolePanel — the master-detail roles surface a matrix typically renders inside."
63
+ ],
64
+ "rules": [
65
+ 24
66
+ ],
67
+ "storyPath": "data-display/PermissionMatrix.stories.tsx",
68
+ "tagline": "Domain data is 100% consumer-supplied.",
69
+ "usage": [
70
+ "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.",
71
+ "DO keep grants in the lib/permission-grid grantKey Set form when you already hold role:permission tuples — the pair-array form exists for convenience and is normalized through the same encoding.",
72
+ "DO put it in a Card with CardContent flush: <Card><CardContent flush><PermissionMatrix …/></CardContent></Card>. Below its natural measure the grid scrolls horizontally INSIDE its own container (390px keeps the sticky permission column).",
73
+ "DO NOT encode platform roles/permissions in the library — roles, permissions and grants are consumer data by contract.",
74
+ "DO NOT pass compare pickers/toggles into the matrix — compose Select + Switch beside it and drive `compare`/`diffOnly` (see the showcase)."
75
+ ],
76
+ "useCases": [
77
+ "RBAC role tab on a service detail screen: read-only matrix + role compare.",
78
+ "Org role editor: editable matrix (onGrantChange) with the system role locked.",
79
+ "Permission-denied / failed read states without hand-rolling: denied / error / onRetry."
80
+ ]
81
+ }