@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
@@ -283,11 +283,19 @@ export default function LoginShowcase() {
283
283
 
284
284
  <Text as="p" size="2xs" tone="muted" align="center" className="leading-relaxed">
285
285
  ログインすると{" "}
286
- <Button variant="link" size="sm" className="text-[length:var(--font-size-2xs)]">
286
+ <Button
287
+ variant="link"
288
+ size="sm"
289
+ className="text-[length:var(--font-size-2xs, calc(var(--font-size-base) / var(--font-size-ratio) / var(--font-size-ratio)))]"
290
+ >
287
291
  利用規約
288
292
  </Button>{" "}
289
293
  ·{" "}
290
- <Button variant="link" size="sm" className="text-[length:var(--font-size-2xs)]">
294
+ <Button
295
+ variant="link"
296
+ size="sm"
297
+ className="text-[length:var(--font-size-2xs, calc(var(--font-size-base) / var(--font-size-ratio) / var(--font-size-ratio)))]"
298
+ >
291
299
  プライバシーポリシー
292
300
  </Button>{" "}
293
301
  に同意したものとみなされます。
@@ -588,7 +588,7 @@ function MonthGrid({ onPick }: { onPick: (d: DayCell) => void }) {
588
588
  tone="destructive"
589
589
  variant="outline"
590
590
  shape="sharp"
591
- className="text-[length:var(--font-size-2xs)]"
591
+ className="text-[length:var(--font-size-2xs, calc(var(--font-size-base) / var(--font-size-ratio) / var(--font-size-ratio)))]"
592
592
  >
593
593
  祝
594
594
  </Badge>
@@ -368,7 +368,7 @@ function TabBar({ active, onChange }: { active: string; onChange: (id: string) =
368
368
  onClick={() => onChange(t.id)}
369
369
  aria-current={isActive ? "page" : undefined}
370
370
  className={
371
- "h-full flex-col rounded-none text-[length:var(--font-size-2xs)] font-medium " +
371
+ "text-[length:var(--font-size-2xs, calc(var(--font-size-base) / var(--font-size-ratio) / var(--font-size-ratio)))] h-full flex-col rounded-none font-medium " +
372
372
  (isActive ? "text-primary" : "text-muted-foreground")
373
373
  }
374
374
  >
@@ -637,7 +637,7 @@ function InboundTab({
637
637
  <ToggleGroupItem
638
638
  key={f.id}
639
639
  value={f.id}
640
- className="h-9 shrink-0 rounded-full text-[length:var(--font-size-xs)] whitespace-nowrap"
640
+ className="text-[length:var(--font-size-xs, calc(var(--font-size-base) / var(--font-size-ratio)))] h-9 shrink-0 rounded-full whitespace-nowrap"
641
641
  // The library's OWN counter pill, not a `Text` at 70% opacity (gh#643). The
642
642
  // hand-rolled one measured 2.15:1 on the selected chip's --primary fill, because
643
643
  // `opacity-70` blends whatever ink it inherits back towards the fill underneath it —
@@ -852,19 +852,19 @@ function OutboundTab({ seg, setSeg }: { seg: string; setSeg: (v: string) => void
852
852
  <ResponsiveGrid columns={3} gap="xs">
853
853
  <ToggleGroupItem
854
854
  value="seal"
855
- className="h-9 rounded-xl text-[length:var(--font-size-xs)] whitespace-nowrap"
855
+ className="text-[length:var(--font-size-xs, calc(var(--font-size-base) / var(--font-size-ratio)))] h-9 rounded-xl whitespace-nowrap"
856
856
  >
857
857
  Chờ niêm phong
858
858
  </ToggleGroupItem>
859
859
  <ToggleGroupItem
860
860
  value="handoff"
861
- className="h-9 rounded-xl text-[length:var(--font-size-xs)] whitespace-nowrap"
861
+ className="text-[length:var(--font-size-xs, calc(var(--font-size-base) / var(--font-size-ratio)))] h-9 rounded-xl whitespace-nowrap"
862
862
  >
863
863
  Chờ bàn giao
864
864
  </ToggleGroupItem>
865
865
  <ToggleGroupItem
866
866
  value="done"
867
- className="h-9 rounded-xl text-[length:var(--font-size-xs)] whitespace-nowrap"
867
+ className="text-[length:var(--font-size-xs, calc(var(--font-size-base) / var(--font-size-ratio)))] h-9 rounded-xl whitespace-nowrap"
868
868
  >
869
869
  Đã bàn giao
870
870
  </ToggleGroupItem>
@@ -1018,7 +1018,7 @@ export default function AgencyHandyShowcase() {
1018
1018
  <ToggleGroupItem
1019
1019
  key={s}
1020
1020
  value={s}
1021
- className="size-7 rounded-md text-[length:var(--font-size-2xs)] uppercase"
1021
+ className="text-[length:var(--font-size-2xs, calc(var(--font-size-base) / var(--font-size-ratio) / var(--font-size-ratio)))] size-7 rounded-md uppercase"
1022
1022
  title={s}
1023
1023
  >
1024
1024
  {s[0]}
@@ -55,16 +55,14 @@ const THEME = `
55
55
  --font-family-display: "Sora", system-ui, sans-serif;
56
56
  --font-family-body: "Be Vietnam Pro", system-ui, "Noto Sans JP", sans-serif;
57
57
  --font-family-sans: var(--font-family-body);
58
- /* 80px hero. --font-size-display is the display ramp's BASE KNOB, but overriding it HERE does
59
- not move the derived steps: --font-size-5xl is declared at :root as var(--font-size-display),
60
- so it computes on :root and merely INHERITS down — the freeze docs/TOKENS.md names, and the
61
- reason its role-mirror knobs are declared initial. The whole --font-size-* scale is built this way
62
- (--font-size-lg freezes on --font-size-base identically), so it is a systemic token-tier
63
- defect, not this ramp's, and not gh#826's to fix. Measured, not assumed: with only the line
64
- below, <Heading size="5xl"> painted 54px here, the :root value. Setting the STEP works,
65
- because this declaration is on an ancestor of the heading. */
58
+ /* 80px hero, from the display ramp's BASE KNOB alone (gh#834).
59
+ This used to need a second line — --font-size-5xl: 5rem — and the comment that stood here said
60
+ why: --font-size-5xl was declared at :root as var(--font-size-display), so it substituted on
61
+ <html> and this scope merely INHERITED the answer root had given. Heading size="5xl" painted
62
+ 54px with the display knob alone. The step is an initial knob now and the formula sits at the
63
+ call site, so one line moves the ramp: re-measured here at 80px, with 3xl/4xl following to
64
+ 48.83/62.5px. */
66
65
  --font-size-display: 5rem;
67
- --font-size-5xl: 5rem;
68
66
  /* The glass bar, opted into once (gh#831). acme-website.tsx hand-wrote the identical pair,
69
67
  which is why these are framework knobs now and not two literals on .fl-navbar. */
70
68
  --topbar-background-alpha: 80%;
@@ -30,32 +30,40 @@
30
30
  *
31
31
  * ── WHAT IS STILL HAND-WRITTEN, AND WHY (the finding this page exists to produce) ───────────────
32
32
  *
33
- * Zero classes and zero literals did NOT mean zero CSS. Six things below are inline `style`
34
- * objects because no PROP reaches them. Each one is a named gap, in the order a reader meets them:
33
+ * Zero classes and zero literals did NOT mean zero CSS. Six things below WERE inline `style`
34
+ * objects because no PROP reached them. Each one is a named gap, in the order a reader meets them;
35
+ * gh#839 closed the first two:
35
36
  *
36
- * 1. THE SECTION BAND — `paddingBlock: var(--space-section-band | --space-section-hero)`.
37
- * gh#831 minted the tokens and nothing reads them. `Flex pad` / `ResponsiveGrid pad` take a
38
- * `GapProp`, whose numeric ladder stops at `12` (`--space-12`, 48px); `--space-20` (80px) and
39
- * `--space-24` (96px) are off the end of it, so the marketing rhythm is the one spacing a
40
- * layout primitive cannot state. It also has no compact step: `--space-section-hero` measures
41
- * 96px at 1440 AND at 320, where the package's own landing preset drops its band through
42
- * `--centered-shell-landing-main-padding-block-compact`.
43
- * 2. THE CENTRED COLUMN — `marginInline: auto` + `inlineSize: 100%` +
37
+ * 1. THE SECTION BAND — was `paddingBlock: var(--space-section-band | --space-section-hero)`.
38
+ * CLOSED by gh#839: the numeric `GapProp` ladder reaches the two band steps, so
39
+ * `pad={{ block: 20 }}` (80px) and `pad={{ block: 24 }}` (96px) state the rhythm through the
40
+ * public API, and every band below now does. Neither step has a compact variant and gh#839
41
+ * decided it should not: `--space-section-hero` measures 96px at 1440 AND at 320, which is
42
+ * deliberate — Tailwind UI's own marketing sections are `py-24 sm:py-32`, i.e. 96px is the
43
+ * NARROW rung of the industry pair and the desktop one is the step above. A numeric `pad`
44
+ * step is a VALUE (`pad={3}` is 12px everywhere); making one of them responsive would break
45
+ * that contract for the whole ladder. `--centered-shell-landing-main-padding-block-compact`
46
+ * is not the counter-example it looks like: 40px → 24px is a SHELL's main padding, not a
47
+ * marketing band, and it switches inside `.ui-centered-shell`'s own scope, which a plain
48
+ * `<section>` has no equivalent of.
49
+ * 2. THE CENTRED COLUMN — was `marginInline: auto` + `inlineSize: 100%` +
44
50
  * `maxInlineSize: var(--page-measure-wide)` + `paddingInline: var(--space-6)`, repeated at
45
- * every band. gh#831 minted `--page-measure-wide` and deliberately did not add
46
- * `PageContainer measure="wide"`, reasoning that a marketing page is full-bleed sections with
47
- * a centred inner column and `PageContainer` owns neither. The reasoning holds; its
48
- * consequence is that NOTHING owns the centred inner column.
49
- * 3. THE STICKY BAR — `position: sticky` + `insetBlockStart` + `zIndex`. `Affix` does not exist
50
- * here (`docs/roadmap/website-components.md` §5.2 proposed `--affix-inset-block-start` and
51
- * `--affix-z-index`; neither shipped) and `Topbar` has no `sticky` prop.
51
+ * every band. CLOSED by gh#839 as `Flex measure="wide"` + `pad={{ inline: 6 }}`. gh#831 minted
52
+ * `--page-measure-wide` and deliberately did not add `PageContainer measure="wide"`, reasoning
53
+ * that a marketing page is full-bleed sections with a centred inner column and `PageContainer`
54
+ * owns neither. That reasoning still holds and was not reopened — the prop went to `Flex`, the
55
+ * primitive a full-bleed `<section>` can actually put inside itself, where it has seven call
56
+ * sites on this page. `measure` adds NO gutter: `pad` owns that, and one band with two padding
57
+ * owners is how a measure and its inset drift apart.
58
+ * 3. THE STICKY BAR — `position: sticky` + `insetBlockStart` + `zIndex`. `Affix` has since
59
+ * shipped (28.8.0, with the measured placeholder that removes the 62px page jump) and this
60
+ * page has not yet been moved onto it; `Topbar` still has no `sticky` prop.
52
61
  * 4. THE GLASS — `--topbar-background-alpha` / `--topbar-backdrop-blur-size` as inline custom
53
- * properties. They are `initial` by design, so a call site MUST set them, and that part is
54
- * correct. The blur is the gap: this library has no blur SCALE (no `--blur-*` tier), so the
55
- * only sources for a length are a raw literal or another component's private knob
56
- * (`--app-launcher-launchpad-backdrop-blur-size`). It reads `--space-2` here, which keeps the
57
- * literal count at zero by borrowing a token that means something else. A `--blur-sm|md|lg`
58
- * foundation tier would close it properly. Measured result: `blur(8px)`.
62
+ * properties. They are `initial` by design, so a call site MUST set them, and that is correct.
63
+ * This used to read `--space-2`, borrowing a token that means something else, because the
64
+ * library had no blur SCALE at all. gh#837 closed that: `--blur-sm|md|lg` now exists in
65
+ * `foundation.css`, deliberately NOT `--scaling`-multiplied, because a backdrop blur is an
66
+ * optical effect and compact density does not mean thinner glass. Measured: `blur(8px)`.
59
67
  * 5. THE HALO — `position: relative` / `overflow: hidden` on the band and `position: absolute` /
60
68
  * `inset: 0` on the `.ui-brand-glow` layer. This is the package's own idiom (both siblings
61
69
  * write `className="ui-brand-glow absolute inset-0"`), spelled as logical inline style so
@@ -155,20 +163,9 @@ import { useTranslation } from "@godxjp/ui/i18n";
155
163
  import { Flex, ResponsiveGrid, Separator, Topbar } from "@godxjp/ui/layout";
156
164
  import { AppSettingPicker } from "@godxjp/ui/navigation";
157
165
 
158
- /* ── The five inline-style constants named in the docblock. Every value is a token reference; no
159
- number with a unit appears anywhere in this file. ─────────────────────────────────────────── */
160
-
161
- /** (2) The centred inner column. Nothing in the package owns this shape yet. */
162
- const SHELL: CSSProperties = {
163
- marginInline: "auto",
164
- inlineSize: "100%",
165
- maxInlineSize: "var(--page-measure-wide)",
166
- paddingInline: "var(--space-6)",
167
- };
168
-
169
- /** (1) The section rhythm gh#831 named and no prop reads. */
170
- const BAND: CSSProperties = { paddingBlock: "var(--space-section-band)" };
171
- const HERO_BAND: CSSProperties = { paddingBlock: "var(--space-section-hero)" };
166
+ /* ── The inline-style constants named in the docblock. Every value is a token reference; no number
167
+ with a unit appears anywhere in this file. Gaps 1 and 2 no longer have one: `Flex measure` and
168
+ `Flex pad` state the band through props (gh#839). ──────────────────────────────────────────── */
172
169
 
173
170
  /** (5) The halo host and the halo layer. */
174
171
  const GLOW_HOST: CSSProperties = { position: "relative", overflow: "hidden" };
@@ -189,7 +186,7 @@ const GLASS_BAR: CSSProperties = {
189
186
  package writes for CenteredShell's landing preset, so no page-local media query. */
190
187
  "--topbar-inset": "max(var(--space-6), calc((100% - var(--page-measure-wide)) / 2))",
191
188
  "--topbar-background-alpha": "85%",
192
- "--topbar-backdrop-blur-size": "var(--space-2)",
189
+ "--topbar-backdrop-blur-size": "var(--blur-md)",
193
190
  } as CSSProperties;
194
191
 
195
192
  /** Per-instance halo shaping — the four `--brand-glow-*` knobs, at the call site. */
@@ -446,7 +443,13 @@ export default function MarketingPage() {
446
443
  className="ui-brand-glow"
447
444
  style={{ ...GLOW_LAYER, ...HERO_GLOW }}
448
445
  />
449
- <div style={{ ...SHELL, ...HERO_BAND, ...ABOVE_GLOW }}>
446
+ <Flex
447
+ direction="col"
448
+ gap="none"
449
+ measure="wide"
450
+ pad={{ inline: 6, block: 24 }}
451
+ style={ABOVE_GLOW}
452
+ >
450
453
  <ResponsiveGrid columns={{ base: 1, lg: 2 }} gap="xl" align="start">
451
454
  <Reveal>
452
455
  <Flex direction="col" gap="lg">
@@ -532,12 +535,12 @@ export default function MarketingPage() {
532
535
  </Card>
533
536
  </Reveal>
534
537
  </ResponsiveGrid>
535
- </div>
538
+ </Flex>
536
539
  </section>
537
540
 
538
541
  {/* ── LOGO WALL ────────────────────────────────────────────────────────────────────── */}
539
542
  <section aria-labelledby="logos-title">
540
- <div style={{ ...SHELL, ...BAND }}>
543
+ <Flex direction="col" gap="none" measure="wide" pad={{ inline: 6, block: 20 }}>
541
544
  <Flex direction="col" gap="lg">
542
545
  <Flex direction="col" gap="xs">
543
546
  <Heading level={2} size="lg" id="logos-title">
@@ -567,13 +570,13 @@ export default function MarketingPage() {
567
570
  ))}
568
571
  </Marquee>
569
572
  </Flex>
570
- </div>
573
+ </Flex>
571
574
  </section>
572
575
 
573
576
  {/* ── FEATURE GRID ─────────────────────────────────────────────────────────────────── */}
574
577
  <section id="solutions" aria-labelledby="features-title">
575
578
  <Flex as="div" direction="col" gap="none" surface="muted">
576
- <div style={{ ...SHELL, ...BAND }}>
579
+ <Flex direction="col" gap="none" measure="wide" pad={{ inline: 6, block: 20 }}>
577
580
  <Flex direction="col" gap="xl">
578
581
  <Flex direction="col" gap="xs">
579
582
  <Heading level={2} size="3xl" weight="bold" id="features-title">
@@ -613,13 +616,13 @@ export default function MarketingPage() {
613
616
  ))}
614
617
  </ResponsiveGrid>
615
618
  </Flex>
616
- </div>
619
+ </Flex>
617
620
  </Flex>
618
621
  </section>
619
622
 
620
623
  {/* ── TESTIMONIALS ─────────────────────────────────────────────────────────────────── */}
621
624
  <section aria-labelledby="quotes-title">
622
- <div style={{ ...SHELL, ...BAND }}>
625
+ <Flex direction="col" gap="none" measure="wide" pad={{ inline: 6, block: 20 }}>
623
626
  <Flex direction="col" gap="lg">
624
627
  <Heading level={2} size="3xl" weight="bold" id="quotes-title">
625
628
  {t("marketingShowcase.testimonials.title")}
@@ -673,13 +676,13 @@ export default function MarketingPage() {
673
676
  <CarouselDots />
674
677
  </Carousel>
675
678
  </Flex>
676
- </div>
679
+ </Flex>
677
680
  </section>
678
681
 
679
682
  {/* ── PRICING ──────────────────────────────────────────────────────────────────────── */}
680
683
  <section id="pricing" aria-labelledby="pricing-title">
681
684
  <Flex as="div" direction="col" gap="none" surface="muted">
682
- <div style={{ ...SHELL, ...BAND }}>
685
+ <Flex direction="col" gap="none" measure="wide" pad={{ inline: 6, block: 20 }}>
683
686
  <Flex direction="col" gap="xl">
684
687
  <Flex direction="col" gap="md">
685
688
  <Heading level={2} size="3xl" weight="bold" id="pricing-title">
@@ -774,7 +777,7 @@ export default function MarketingPage() {
774
777
  })}
775
778
  </ResponsiveGrid>
776
779
  </Flex>
777
- </div>
780
+ </Flex>
778
781
  </Flex>
779
782
  </section>
780
783
 
@@ -785,7 +788,13 @@ export default function MarketingPage() {
785
788
  className="ui-brand-glow"
786
789
  style={{ ...GLOW_LAYER, ...CTA_GLOW }}
787
790
  />
788
- <div style={{ ...SHELL, ...BAND, ...ABOVE_GLOW }}>
791
+ <Flex
792
+ direction="col"
793
+ gap="none"
794
+ measure="wide"
795
+ pad={{ inline: 6, block: 20 }}
796
+ style={ABOVE_GLOW}
797
+ >
789
798
  <Reveal on="view">
790
799
  <Flex direction="col" gap="md" align="center">
791
800
  <Heading level={2} size="4xl" weight="bold" align="center" id="cta-title">
@@ -810,14 +819,14 @@ export default function MarketingPage() {
810
819
  </Text>
811
820
  </Flex>
812
821
  </Reveal>
813
- </div>
822
+ </Flex>
814
823
  </section>
815
824
  </main>
816
825
 
817
826
  {/* ── FOOTER ───────────────────────────────────────────────────────────────────────── */}
818
827
  <footer>
819
828
  <Flex as="div" direction="col" gap="none" surface="muted">
820
- <div style={{ ...SHELL, ...BAND }}>
829
+ <Flex direction="col" gap="none" measure="wide" pad={{ inline: 6, block: 20 }}>
821
830
  <Flex direction="col" gap="lg">
822
831
  {/* ResponsiveGrid.Item span — the asymmetric footer both siblings hand-wrote a
823
832
  `grid-template-columns: 1.4fr 1fr 1fr 1fr` for. It has shipped the whole time. */}
@@ -877,7 +886,7 @@ export default function MarketingPage() {
877
886
  </Flex>
878
887
  </Flex>
879
888
  </Flex>
880
- </div>
889
+ </Flex>
881
890
  </Flex>
882
891
  </footer>
883
892
  </Flex>
@@ -299,7 +299,10 @@ function DetailPanel({ employee }: { employee: Employee }) {
299
299
  {/* Per-day breakdown — quiet nested list, real Card chrome */}
300
300
  <Card>
301
301
  <CardHeader>
302
- <CardTitle level={2} className="text-[length:var(--font-size-xs)]">
302
+ <CardTitle
303
+ level={2}
304
+ className="text-[length:var(--font-size-xs, calc(var(--font-size-base) / var(--font-size-ratio)))]"
305
+ >
303
306
  今週の打刻
304
307
  </CardTitle>
305
308
  <CardAction>
@@ -36,6 +36,7 @@ import {
36
36
  import { Button, Text } from "@godxjp/ui/general";
37
37
  import { Flex, PageContainer } from "@godxjp/ui/layout";
38
38
  import { Pagination } from "@godxjp/ui/navigation";
39
+ import { useTranslation } from "@godxjp/ui/i18n";
39
40
  import {
40
41
  Select,
41
42
  SelectContent,
@@ -91,8 +92,13 @@ function makeRows(count: number, prefix = "ATT"): Attendance[] {
91
92
  date: `2026-06-${String((i % 28) + 1).padStart(2, "0")}`,
92
93
  employee: NAMES[i % NAMES.length],
93
94
  dept: DEPTS[i % DEPTS.length],
94
- clockIn: `0${startHour}:${["02", "58", "31", "07"][i % 4]}`,
95
- clockOut: ot ? `${18 + Math.floor(ot / 60)}:${String(ot % 60).padStart(2, "0")}` : "18:00",
95
+ // `0${startHour}` hard-coded the leading zero and printed `010:07` at startHour 10 —
96
+ // the hand-built formatting this repo's own rules forbid. `padStart` on the hour, the
97
+ // same way the line above already does it for the date.
98
+ clockIn: `${String(startHour).padStart(2, "0")}:${["02", "58", "31", "07"][i % 4]}`,
99
+ clockOut: ot
100
+ ? `${String(18 + Math.floor(ot / 60)).padStart(2, "0")}:${String(ot % 60).padStart(2, "0")}`
101
+ : "18:00",
96
102
  overtime: ot,
97
103
  status: statuses[i % statuses.length],
98
104
  };
@@ -102,20 +108,38 @@ function makeRows(count: number, prefix = "ATT"): Attendance[] {
102
108
  const minutesFmt = (m: number) => (m === 0 ? "—" : `${m}分`);
103
109
 
104
110
  // Columns are shared between the cards (pure data — no per-card state inside).
111
+ /* EVERY column declares a width, not just the first (gh#849).
112
+ *
113
+ * Measured at 2000px before this, as empty pixels between one cell's ink and the next's:
114
+ *
115
+ * 部署 → 出勤 521 残業 → 状態 57
116
+ * 従業員 → 部署 411 勤務日 → 従業員 40
117
+ * 退勤 → 残業 280
118
+ *
119
+ * A 521px hole beside a 40px seam, in one row. Only `勤務日` carried a width, so
120
+ * `table-layout: auto` sized the rest from content and dumped ALL the slack of a 1952px card
121
+ * into the two free-text columns — and because those are start-aligned while the measures are
122
+ * end-aligned, the slack opened as craters BETWEEN columns rather than inside them.
123
+ *
124
+ * With a width on every column the engine distributes the excess proportionally instead, so the
125
+ * gaps scale together and no single one dominates. The numbers are a ratio, not a measurement:
126
+ * the two free-text columns get the larger share because they are the ones that actually wrap. */
105
127
  const columns: ColumnDef<Attendance>[] = [
106
128
  { key: "date", header: "勤務日", width: "w-28" },
107
- { key: "employee", header: "従業員" },
108
- { key: "dept", header: "部署", hiddenOnMobile: true },
129
+ { key: "employee", header: "従業員", width: "w-44" },
130
+ { key: "dept", header: "部署", hiddenOnMobile: true, width: "w-36" },
109
131
  {
110
132
  key: "clockIn",
111
133
  header: "出勤",
112
134
  align: "right",
135
+ width: "w-24",
113
136
  render: (row) => <Text tabular>{row.clockIn}</Text>,
114
137
  },
115
138
  {
116
139
  key: "clockOut",
117
140
  header: "退勤",
118
141
  align: "right",
142
+ width: "w-24",
119
143
  render: (row) => <Text tabular>{row.clockOut}</Text>,
120
144
  },
121
145
  {
@@ -123,12 +147,17 @@ const columns: ColumnDef<Attendance>[] = [
123
147
  header: "残業",
124
148
  align: "right",
125
149
  hiddenOnMobile: true,
150
+ width: "w-24",
126
151
  render: (row) => <Text tabular>{minutesFmt(row.overtime)}</Text>,
127
152
  },
128
153
  {
129
154
  key: "status",
130
155
  header: "状態",
131
- align: "center",
156
+ /* END, not CENTER (gh#849). The status column carries the table's right edge; centring a
157
+ * ~90px pill in it put the badges 102px inboard while the header count, the column headers
158
+ * and the pagination all landed on 17px. */
159
+ align: "right",
160
+ width: "w-32",
132
161
  render: (row) => <Badge status={row.status}>{STATUS_LABEL[row.status]}</Badge>,
133
162
  },
134
163
  ];
@@ -138,6 +167,8 @@ const columns: ColumnDef<Attendance>[] = [
138
167
  const NUMBERED_DATA = makeRows(83, "OFS");
139
168
 
140
169
  function NumberedPaginationCard() {
170
+ const { t } = useTranslation();
171
+ const fmt = useFormatters();
141
172
  const [page, setPage] = React.useState(2); // start mid-set so prev/next are both live
142
173
  const [pageSize, setPageSize] = React.useState(10);
143
174
 
@@ -150,7 +181,7 @@ function NumberedPaginationCard() {
150
181
  <CardTitle level={2}>番号付き + 件数選択</CardTitle>
151
182
  <CardAction>
152
183
  <Text size="xs" tone="muted" tabular>
153
- 全 {NUMBERED_DATA.length} 件
184
+ {t("showcase.pagination.recordCount", { count: NUMBERED_DATA.length })}
154
185
  </Text>
155
186
  </CardAction>
156
187
  </CardHeader>
@@ -164,7 +195,9 @@ function NumberedPaginationCard() {
164
195
  pageSize={pageSize}
165
196
  pageSizeOptions={[10, 20, 50]}
166
197
  showSizeChanger
167
- showTotal={(total, [from, to]) => `${from}–${to} / ${total} 件`}
198
+ showTotal={(total, [from, to]) =>
199
+ `${fmt.number(from)}–${fmt.number(to)} / ${t("showcase.pagination.recordCount", { count: total })}`
200
+ }
168
201
  onValueChange={(nextPage, nextSize) => {
169
202
  setPage(nextPage);
170
203
  setPageSize(nextSize);
@@ -181,6 +214,8 @@ const LOADMORE_DATA = makeRows(42, "LDM");
181
214
  const LOADMORE_STEP = 8;
182
215
 
183
216
  function LoadMoreCard() {
217
+ const { t } = useTranslation();
218
+ const fmt = useFormatters();
184
219
  // Mid-load state at rest: some rows already revealed, more remaining.
185
220
  const [visible, setVisible] = React.useState(LOADMORE_STEP * 2);
186
221
  const [loading, setLoading] = React.useState(false);
@@ -205,7 +240,8 @@ function LoadMoreCard() {
205
240
  <CardTitle level={2}>もっと読む</CardTitle>
206
241
  <CardAction>
207
242
  <Text size="xs" tone="muted" tabular>
208
- {rows.length} / {LOADMORE_DATA.length} 件表示
243
+ {fmt.number(rows.length)} /{" "}
244
+ {t("showcase.pagination.recordCount", { count: LOADMORE_DATA.length })}
209
245
  </Text>
210
246
  </CardAction>
211
247
  </CardHeader>
@@ -225,11 +261,13 @@ function LoadMoreCard() {
225
261
  {!done && <ChevronDown aria-hidden="true" />}
226
262
  {done
227
263
  ? "すべて表示しました"
228
- : `さらに ${Math.min(LOADMORE_STEP, remaining)} 件を読み込む`}
264
+ : t("showcase.pagination.loadMore", {
265
+ count: Math.min(LOADMORE_STEP, remaining),
266
+ })}
229
267
  </Button>
230
268
  {!done && (
231
269
  <Text size="xs" tone="muted" tabular>
232
- 残り {remaining} 件
270
+ {t("showcase.pagination.recordCount", { count: remaining })}
233
271
  </Text>
234
272
  )}
235
273
  </Flex>
@@ -242,11 +280,11 @@ function LoadMoreCard() {
242
280
 
243
281
  // Time-series 勤怠 logs grouped by month — offset paging is meaningless here,
244
282
  // so we use cursor first/next (DataTable.Pagination) + a period Select to jump.
245
- const PERIODS = [
246
- { value: "2026-06", label: "2026年 6月" },
247
- { value: "2026-05", label: "2026年 5月" },
248
- { value: "2026-04", label: "2026年 4月" },
249
- ] as const;
283
+ /* The VALUE is data; the LABEL is a formatting decision and belongs to the active locale.
284
+ * Hard-coded "2026年 6月" rendered beside the library's own "Đầu" / "Tiếp" — two languages in one
285
+ * row, which is what the owner photographed. `Intl.DateTimeFormat` gives 「2026年6月」, "June 2026"
286
+ * and "tháng 6 năm 2026" from the same value. */
287
+ const PERIODS = [{ value: "2026-06" }, { value: "2026-05" }, { value: "2026-04" }] as const;
250
288
 
251
289
  const PERIOD_DATA: Record<string, Attendance[]> = {
252
290
  "2026-06": makeRows(7, "CUR-06"),
@@ -255,6 +293,8 @@ const PERIOD_DATA: Record<string, Attendance[]> = {
255
293
  };
256
294
 
257
295
  function CursorPeriodCard() {
296
+ const { t } = useTranslation();
297
+ const fmt = useFormatters();
258
298
  const [period, setPeriod] = React.useState<string>("2026-06");
259
299
  // cursor === id of the last row of the previous page; undefined = first page.
260
300
  const [cursor, setCursor] = React.useState<string | undefined>(undefined);
@@ -284,7 +324,7 @@ function CursorPeriodCard() {
284
324
  <SelectContent>
285
325
  {PERIODS.map((p) => (
286
326
  <SelectItem key={p.value} value={p.value}>
287
- {p.label}
327
+ {fmt.month(p.value)}
288
328
  </SelectItem>
289
329
  ))}
290
330
  </SelectContent>
@@ -294,10 +334,19 @@ function CursorPeriodCard() {
294
334
  <CardContent flush>
295
335
  <DataTable data={rows} columns={columns} getRowId={(row) => row.id} density="compact" />
296
336
  </CardContent>
297
- <CardContent className="border-t">
337
+ {/* `DataTable.Pagination` owns its own inline inset, because it is normally the lone child
338
+ * of a FLUSH container. Here it shares a padded `CardContent` with the period label, so the
339
+ * two insets stack — measured 28px against the columns' 16px before this. Zeroing the slot's
340
+ * own knob hands the inset to the container that already supplies it; that is what
341
+ * `--table-pagination-padding-x` is for, and an instance override is the right tier for one
342
+ * element in one arrangement (docs/CUSTOMER-THEMING.md, tier 4). */}
343
+ <CardContent
344
+ className="border-t"
345
+ style={{ "--table-pagination-padding-x": "0" } as React.CSSProperties}
346
+ >
298
347
  <Flex direction="row" align="center" justify="between" wrap gap="sm">
299
348
  <Text size="xs" tone="muted" tabular>
300
- {PERIODS.find((p) => p.value === period)?.label} · {all.length} 件
349
+ {fmt.month(period)} · {t("showcase.pagination.recordCount", { count: all.length })}
301
350
  </Text>
302
351
  <DataTable.Pagination
303
352
  cursor={lastVisibleId}
@@ -310,6 +359,27 @@ function CursorPeriodCard() {
310
359
  );
311
360
  }
312
361
 
362
+ /* COUNTS AND MONTHS FOLLOW THE ACTIVE LOCALE, and this page used to hard-code them.
363
+ *
364
+ * The library's own chrome is localized — the page-size Select renders "10 / trang" under `vi` —
365
+ * so a page that hard-codes `件` puts two languages in one row and they can never agree. That is
366
+ * exactly what the owner photographed: "11–20 / 83 件" beside "10 / trang".
367
+ *
368
+ * `Intl.NumberFormat` groups the number for the locale; the unit word comes from the library's own
369
+ * message catalogue, which already carries a CLDR plural map per language. Nothing here is a
370
+ * hand-built string. */
371
+ const useFormatters = () => {
372
+ const { locale } = useTranslation();
373
+ return React.useMemo(() => {
374
+ const number = new Intl.NumberFormat(locale);
375
+ const month = new Intl.DateTimeFormat(locale, { year: "numeric", month: "long" });
376
+ return {
377
+ number: (n: number) => number.format(n),
378
+ month: (iso: string) => month.format(new Date(`${iso}-01T00:00:00`)),
379
+ };
380
+ }, [locale]);
381
+ };
382
+
313
383
  export default function Demo() {
314
384
  return (
315
385
  <PageContainer
@@ -302,7 +302,13 @@ export default function ThemeCustomizationShowcase() {
302
302
  const [corner, setCorner] = React.useState("default");
303
303
  const [custom, setCustom] = React.useState("#0F62FE");
304
304
  const [regions, setRegions] = React.useState<string[]>(["JP", "VN"]);
305
- const [labels, setLabels] = React.useState<string[]>(["本番", "ổn định", UNBREAKABLE]);
305
+ /* UNBREAKABLE FIRST, ON PURPOSE (gh#840). With `maxTagCount={2}` the tag in third place is the
306
+ * one that collapses into `+N` — so while the 71-character id sat last, the chip that painted
307
+ * outside the control was never actually drawn on this page, and `maxTagTextLength` had nothing
308
+ * to demonstrate. Held first, the row now draws all three of the issue's answers at once: the
309
+ * long id truncated to its ceiling (full value in `title` and in the remover's name), a normal
310
+ * chip beside it, and a `+1` that names the tag behind it. */
311
+ const [labels, setLabels] = React.useState<string[]>([UNBREAKABLE, "本番", "ổn định"]);
306
312
  const [plan, setPlan] = React.useState("standard");
307
313
  const [channels, setChannels] = React.useState<string[]>(["email"]);
308
314
  const [cadence, setCadence] = React.useState("weekly");
@@ -565,7 +571,19 @@ function ComponentsBoard(props: {
565
571
  } = props;
566
572
 
567
573
  return (
568
- <ResponsiveGrid columns={{ base: 1, sm: 2, lg: 3 }} gap="lg" align="start">
574
+ /* PADDED HERE, because nothing above this pads it (gh#841 follow-up). The board sits in
575
+ `<CardContent flush>` so the tab STRIP can meet the card edge — which is right, a line
576
+ variant's underline should run edge to edge — and `bodied` is a no-op on `variant="line"`
577
+ by design (`tabs.tsx`: `const bodiedCard = card && Boolean(bodied)`). So the body has no
578
+ inline inset from either side, and the form sat 1px from the card border: measured label
579
+ at x=25 against a card at x=24. `pad` on the board is the composition answer — the same
580
+ step `CardContent` would have used. */
581
+ <ResponsiveGrid
582
+ columns={{ base: 1, sm: 2, lg: 3 }}
583
+ gap="lg"
584
+ align="start"
585
+ pad={{ inline: "lg", block: "md" }}
586
+ >
569
587
  {/* 1 · text entry */}
570
588
  <Flex direction="col" gap="md">
571
589
  <FormField
@@ -618,6 +636,11 @@ function ComponentsBoard(props: {
618
636
  value={labels}
619
637
  onValueChange={setLabels}
620
638
  maxTagCount={2}
639
+ /* The ceiling the 71-character id is measured against (gh#840). 16 is the widest cut
640
+ that still leaves room for a second chip and the `+1` on one row at 375px; the value
641
+ is untouched — it stays in the chip's `title` and in the remover's accessible name,
642
+ which is the only reason truncating an identifier is allowed at all. */
643
+ maxTagTextLength={16}
621
644
  placeholder={t("themeShowcase.form.labelsPlaceholder")}
622
645
  />
623
646
  </FormField>