@godxjp/ui 19.4.1 → 19.5.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 (93) hide show
  1. package/dist/app/app-provider.js +10 -1
  2. package/dist/app/theme-axes.d.ts +18 -3
  3. package/dist/app/theme-axes.js +15 -3
  4. package/dist/components/data-display/badge.js +4 -1
  5. package/dist/components/data-display/data-table.js +9 -2
  6. package/dist/components/data-display/list-row.d.ts +2 -0
  7. package/dist/components/data-display/list-row.js +16 -12
  8. package/dist/components/data-display/timeline-grid.js +5 -3
  9. package/dist/components/data-entry/checkbox.d.ts +12 -0
  10. package/dist/components/data-entry/checkbox.js +35 -4
  11. package/dist/components/data-entry/form-field.js +5 -5
  12. package/dist/components/data-entry/index.d.ts +2 -0
  13. package/dist/components/data-entry/index.js +2 -0
  14. package/dist/components/data-entry/input.js +1 -1
  15. package/dist/components/data-entry/radio.js +1 -1
  16. package/dist/components/data-entry/search-input.d.ts +2 -18
  17. package/dist/components/data-entry/search-input.js +3 -0
  18. package/dist/components/data-entry/segmented.d.ts +2 -0
  19. package/dist/components/data-entry/segmented.js +5 -0
  20. package/dist/components/data-entry/select.d.ts +2 -0
  21. package/dist/components/data-entry/select.js +9 -4
  22. package/dist/components/feedback/sheet.js +1 -1
  23. package/dist/components/feedback/sonner.d.ts +7 -1
  24. package/dist/components/feedback/sonner.js +27 -7
  25. package/dist/components/general/button.d.ts +2 -0
  26. package/dist/components/general/button.js +11 -2
  27. package/dist/components/general/inert-background.d.ts +8 -3
  28. package/dist/components/general/inert-background.js +28 -3
  29. package/dist/components/general/logo.d.ts +53 -0
  30. package/dist/components/general/logo.js +57 -3
  31. package/dist/components/layout/app-shell.js +1 -1
  32. package/dist/components/layout/index.d.ts +4 -0
  33. package/dist/components/layout/index.js +4 -0
  34. package/dist/components/layout/master-detail.d.ts +1 -1
  35. package/dist/components/layout/master-detail.js +47 -3
  36. package/dist/components/layout/nav-list.d.ts +26 -0
  37. package/dist/components/layout/nav-list.js +21 -0
  38. package/dist/components/layout/responsive-grid.js +7 -4
  39. package/dist/components/layout/topbar-item.d.ts +17 -0
  40. package/dist/components/layout/topbar-item.js +21 -0
  41. package/dist/components/navigation/app-setting-picker.js +31 -5
  42. package/dist/components/navigation/context-menu.js +2 -2
  43. package/dist/components/navigation/dropdown-menu.js +2 -2
  44. package/dist/components/navigation/tabs.js +42 -10
  45. package/dist/components/ui/segmented.d.ts +48 -0
  46. package/dist/components/ui/segmented.js +48 -0
  47. package/dist/email/tokens.generated.d.ts +4 -4
  48. package/dist/email/tokens.generated.js +4 -4
  49. package/dist/i18n/messages/en.json +2 -1
  50. package/dist/i18n/messages/ja.json +2 -1
  51. package/dist/i18n/messages/vi.json +2 -1
  52. package/dist/props/components/app.prop.d.ts +9 -2
  53. package/dist/props/components/data-entry.prop.d.ts +10 -5
  54. package/dist/props/components/general.prop.d.ts +4 -0
  55. package/dist/props/components/layout.prop.d.ts +54 -2
  56. package/dist/props/registry.d.ts +21 -2
  57. package/dist/props/registry.js +29 -1
  58. package/dist/props/vocabulary/shared.prop.d.ts +11 -2
  59. package/dist/styles/alert-layout.css +4 -0
  60. package/dist/styles/base.css +2 -0
  61. package/dist/styles/control.css +123 -26
  62. package/dist/styles/data-display-layout.css +24 -2
  63. package/dist/styles/dialog-layout.css +2 -1
  64. package/dist/styles/focus-ring.css +84 -22
  65. package/dist/styles/form-layout.css +6 -0
  66. package/dist/styles/layout.css +38 -2
  67. package/dist/styles/logo-layout.css +56 -0
  68. package/dist/styles/navigation-layout.css +17 -1
  69. package/dist/styles/shell-layout.css +64 -10
  70. package/dist/tokens/antd.generated.css +40 -0
  71. package/dist/tokens/axes.css +6 -0
  72. package/dist/tokens/base.css +3 -0
  73. package/dist/tokens/components/control.css +5 -1
  74. package/dist/tokens/components/data-display.css +2 -1
  75. package/dist/tokens/components/feedback.css +2 -0
  76. package/dist/tokens/components/legal-document.css +0 -2
  77. package/dist/tokens/components/logo.css +13 -0
  78. package/dist/tokens/components/navigation.css +2 -0
  79. package/dist/tokens/components/segmented.css +27 -0
  80. package/dist/tokens/components/shell.css +15 -2
  81. package/dist/tokens/foundation.css +21 -13
  82. package/docs/DESIGN-AUTHORITY.md +346 -0
  83. package/docs/FRAME-COVERAGE-REPORT.md +5 -2
  84. package/docs/README.md +14 -13
  85. package/docs/data-display/list-row.tsx +21 -0
  86. package/docs/data-entry/search-input.tsx +1 -0
  87. package/docs/data-entry/segmented.tsx +124 -0
  88. package/docs/general/button/index.md +4 -0
  89. package/docs/layout/master-detail.tsx +3 -0
  90. package/docs/layout/nav-list.tsx +63 -0
  91. package/docs/layout/responsive-grid.tsx +19 -2
  92. package/docs/layout/topbar-item.tsx +157 -0
  93. package/package.json +8 -2
@@ -0,0 +1,346 @@
1
+ # Design authority — which external standard owns which decision
2
+
3
+ **Status:** accepted · 2026-09-07
4
+
5
+ ## Why this document exists
6
+
7
+ Every design question that has no external answer gets answered by us, in a meeting, again.
8
+ That is the expensive part — not the code. This file names, per layer, the outside system we
9
+ defer to, so the next question is a lookup instead of an invention.
10
+
11
+ It changes no code by itself. It is the tie-breaker a reviewer points at.
12
+
13
+ ## The layers, and who owns each
14
+
15
+ | Layer | Authority | Status in this repo |
16
+ | ------------------------------------------------------------------ | --------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------- |
17
+ | Interaction semantics, keyboard, ARIA | **WAI-ARIA APG** | already followed — 33 references in `src/` |
18
+ | Behaviour primitives | **Radix** | already the implementation — 193 references |
19
+ | Component composition shape | **shadcn** | already the structural convention — 23 references |
20
+ | Component taxonomy / grouping | **Ant Design** groups | already the catalog shape: `data-entry`, `data-display`, `layout`, `feedback`, `navigation`, `general` |
21
+ | Colour foundation | **SmartHR** | already the palette source — `--primary` = SmartHR MAIN `#0071bd`, `--foreground` = TEXT_BLACK, `--border` = BORDER |
22
+ | **Derived colour — every step computed from a seed** | **Ant Design (the algorithm itself)** | **NEW — this decision.** antd is no longer a shape reference here: `scripts/gen-antd-tokens.mjs` runs `theme.getDesignToken()` at build time and emits the derived map |
23
+ | **Japanese UI convention — density, JP typography, form patterns** | **SmartHR** | **NEW — this decision.** Extends SmartHR from "where the colours came from" to the authority for how a JP business screen behaves |
24
+ | **Japanese accessibility / public-sector convention** | **デジタル庁 Design System** (Digital Agency) | **NEW — this decision.** The reference when a JP customer asks which standard a screen meets (JIS X 8341-3) |
25
+ | **Spacing, density, type scale, information architecture** | **IBM Carbon** | **NEW — this decision** |
26
+
27
+ The first five were already true and merely unwritten. The last three are the choices being made
28
+ here. Carbon fills the one layer that had no outside answer at all: page rhythm, table density, form layout,
29
+ when to reach for which container. That gap is why `PageContainer` presets, `MasterDetail`,
30
+ `CenteredShell` and the `AuthShell` variants were each designed from scratch.
31
+
32
+ ## Why Carbon for that layer
33
+
34
+ **It is the closest fit to what this library is for.** Internal SaaS, dense, neutral, legible —
35
+ that is Carbon's design brief almost verbatim. Its visual language is deliberately plain, so it
36
+ does not fight the SmartHR colour foundation already in `foundation.css`.
37
+
38
+ **It is the cheapest to adopt, measurably.** The spacing scale here already matches Carbon's on
39
+ 8 of 9 steps:
40
+
41
+ | `@godxjp/ui` | px | Carbon |
42
+ | ------------ | ------ | ---------------------------------------- |
43
+ | `--space-1` | 4 | `spacing-02` |
44
+ | `--space-2` | 8 | `spacing-03` |
45
+ | `--space-3` | 12 | `spacing-04` |
46
+ | `--space-4` | 16 | `spacing-05` |
47
+ | `--space-5` | **20** | **no equivalent — Carbon steps 16 → 24** |
48
+ | `--space-6` | 24 | `spacing-06` |
49
+ | `--space-8` | 32 | `spacing-07` |
50
+ | `--space-10` | 40 | `spacing-08` |
51
+ | `--space-12` | 48 | `spacing-09` |
52
+
53
+ Body size agrees too: Carbon's `body-compact-01` is 14px, and `--font-size-base` is 14px.
54
+
55
+ **It is the most actively maintained of the candidates**, which matters when the point is to follow
56
+ someone rather than lead. Measured 2026-09-07 from the GitHub API:
57
+
58
+ | System | Last commit | Commits, 90 days |
59
+ | --------------------- | ----------- | ---------------- |
60
+ | Carbon (IBM) | 2026-09-04 | **418** |
61
+ | Fluent UI (Microsoft) | 2026-09-06 | 220 |
62
+ | Polaris (Shopify) | 2026-08-05 | — |
63
+ | SLDS (Salesforce) | 2026-06-02 | **0** |
64
+ | Primer (GitHub) | 2025-07-01 | 0 |
65
+
66
+ ## Why not the others
67
+
68
+ **Salesforce Lightning** was the first instinct and is a reasonable one — it is the archetypal
69
+ enterprise system. Two things rule it out. Its public repo has not moved since June 2026 (SLDS 2
70
+ lives in separate `salesforce-ux/design-system-2` repos on a much slower cadence), and the part of
71
+ SLDS with the most value is bound to Salesforce's own object model — record home, related lists,
72
+ object pages — which does not transfer to a product that is not Salesforce.
73
+
74
+ **Fluent UI** is complete (54 component packages) and extremely well maintained. It loses on fit,
75
+ not quality: its guidance is written for Microsoft product surfaces and its visual language carries
76
+ more Windows/Office flavour than a white-label framework wants.
77
+
78
+ **Apple HIG** is platform guidance for macOS/iOS. It has close to nothing to say about a dense web
79
+ admin table.
80
+
81
+ **Polaris** has the best content-design guidance of the group, worth borrowing from for microcopy,
82
+ but its patterns assume the Shopify admin.
83
+
84
+ **Atlassian** is a genuine alternative to Carbon and would also work. Carbon wins on being more
85
+ visually neutral and on the spacing scale already matching.
86
+
87
+ ## The Japanese market comes first, and it outranks Carbon
88
+
89
+ The customers are Japanese businesses. Where a Western system and a Japanese one disagree, **the
90
+ Japanese one wins for anything the user reads or touches** — typography, line rhythm, density,
91
+ form conventions, wording. Carbon keeps geometry (the spacing steps, the 4px grid) because that
92
+ layer is culture-neutral.
93
+
94
+ **SmartHR is the JP authority, not just the palette.** It is a Japanese HR SaaS — the same product
95
+ shape as ours, aimed at the same buyers — its library is open source, and it is the most actively
96
+ maintained system of any measured here: **561 commits in 90 days**, last commit 2026-09-07 (Carbon:
97
+ 418). It was already the colour source; this decision extends it to how a JP business screen
98
+ behaves.
99
+
100
+ **デジタル庁 (Digital Agency) is the standards reference.** Its design system is the closest thing
101
+ Japan has to a public, government-backed baseline, and it is what a JP enterprise customer or
102
+ procurement reviewer will cite. Use it to answer "which standard does this meet" (JIS X 8341-3),
103
+ not for visual style.
104
+
105
+ ### Japanese conventions this library already follows — now written down
106
+
107
+ These were correct decisions with no recorded reason. They are load-bearing; do not "simplify"
108
+ them toward a Latin default:
109
+
110
+ - **`--line-height-body: 1.7`.** Japanese body text needs 1.7–2.0, not the Latin 1.5. Full-width
111
+ kana and kanji fill the em box, so the same 1.5 that reads as airy in English reads as cramped in
112
+ Japanese. `--line-height-normal: 1.5` remains for Latin-only runs.
113
+ - **14px body.** The JP business-software norm, and it happens to agree with Carbon's
114
+ `body-compact-01`. Do not raise it to a Western 16px default.
115
+ - **A per-language font slot, not one merged stack.** `--font-sans-base` is deliberately a pure
116
+ system stack so the library renders with zero font setup; Japanese arrives through
117
+ `--font-sans-ja`, which `styles/base.css` wires to `[lang="ja"]`. Keep the indirection — merging
118
+ a JP face into the base stack looks like a simplification and quietly removes the hook a consumer
119
+ themes through.
120
+ - **Density never touches type size.** `density.css` sets `--scaling` only; `font-size` appears
121
+ **zero** times in it. This is the single most important rule for dense JP screens: Japanese loses
122
+ legibility far faster than Latin when shrunk, because the strokes-per-em of a kanji do not
123
+ survive it. Compress spacing to fit more on screen — never the characters.
124
+
125
+ ## Compact without becoming ugly — the rules that hold both
126
+
127
+ "Fit more on one screen" and "still looks composed" are not in tension if the compression is
128
+ applied to the right axis:
129
+
130
+ 1. **Compress spacing, never type.** See above. `--scaling` is the only density knob.
131
+ 2. **Keep the ratio, shrink the unit.** The φ macro rhythm (`docs/SPACING.md`) stays intact under
132
+ density because `--phi-unit` is itself a spacing token — the proportions between sections are
133
+ preserved while the absolute gaps shrink. That is why density does not make the page look
134
+ broken, only tighter.
135
+ 3. **Density is a token axis, not a per-screen decision.** `data-density` on the root, three values.
136
+ A screen that wants to be denser than `compact` is a screen with too much on it.
137
+ 4. **The information ceiling is the row, not the page.** Dense JP admin screens get their density
138
+ from row height and column count (`--table-cell-padding-y`, `--band-height-*`), not from
139
+ squeezing the page gutter. Take row heights from SmartHR's tables.
140
+ 5. **Touch targets do not participate in density.** See divergence 5 — this is currently violated.
141
+
142
+ ## What this does NOT mean
143
+
144
+ **We do not adopt Carbon's components.** There are 127 catalogued components here with 3,480 tests
145
+ and a token contract that took #316/#319 to land. Swapping libraries would throw that away to buy
146
+ guidance we can simply read. Carbon is the **specification authority**, not a dependency.
147
+
148
+ **We do not adopt Carbon's colours or brand.** Colour stays SmartHR-anchored. Carbon owns
149
+ geometry and rhythm; SmartHR owns hue.
150
+
151
+ ## The open divergences
152
+
153
+ Recorded rather than silently fixed, because each is a real decision:
154
+
155
+ 1. **`--space-5` (20px) has no Carbon equivalent.** Carbon steps 16 → 24 on purpose: a scale with
156
+ both 20 and 24 lets two authors space the same relationship differently. Either retire it or
157
+ write down what it is for. Pinned by `src/tokens/__tests__/carbon-scale-alignment.test.ts`, which
158
+ fails if a NEW off-scale step appears.
159
+ 2. **The type scale diverges by construction.** Carbon uses integers — 12, 14, 16, 18, 20, 24, 28,
160
+ 32, 36 — while `--font-size-ratio: 1.1227` (φ^¼) produces 11.1, 12.5, 17.6, 19.8, 22.2. Only the
161
+ 14px body agrees. Fractional sizes do cost something measurable — they snap a line box half a
162
+ pixel either way, which is ±0.5px of the residual in gh#370 — but they are not what that issue
163
+ is. gh#370 is that `place-items: center` centres a LINE BOX and a line box is not a letterform;
164
+ re-measured from the painted ink, the glyph classes' optical centres lie 0.21em apart (`g` 3.69px
165
+ low in the 28px `md` box where `神`, `G` and `GX` are within 0.94px), so no glyph-blind rule
166
+ centres them all — the best glyph-blind rule, `text-box: trim-both ex alphabetic`, still leaves
167
+ 2.25px. So the correction moved up a layer: `Logo` classifies the glyph string it is handed into
168
+ the ink band it occupies and CSS applies that band's offset, which measures 1.38px worst case and
169
+ needs no `text-box` at all (`src/styles/logo-layout.css` carries the full matrix). Aligning the
170
+ scale is a visual change to every screen, so it is a separate decision, not a side effect of this
171
+ one.
172
+ 3. **The large end is missing.** Carbon has 64/80/96/160px; this scale stops at 48px. Nothing needs
173
+ them yet — add from Carbon's steps when something does, rather than inventing a number.
174
+ 4. **2px (`spacing-01`) is missing.** Same rule: take Carbon's value if a hairline gap is ever
175
+ needed.
176
+ 5. **`--scaling` takes the whole scale off the 4px grid, and undercuts the 44px touch floor.**
177
+ `density="compact"` sets `--scaling: 0.92`, so every step becomes fractional — 16px → 14.72px,
178
+ 24px → 22.08px — and the grid Carbon's scale is built on only holds at default density. Worse,
179
+ the coarse-pointer override in `tokens/components/control.css` raises controls to
180
+ `--band-height-xl` (44px, "rule #24"), but `--control-height` multiplies it by `--scaling`
181
+ anyway: **44 × 0.92 = 40.48px on a touch device at compact density.** That still clears WCAG 2.2
182
+ AA (24px), but it breaks this library's own rule #24. Carbon's answer to density is discrete
183
+ size variants that stay on the grid, not a continuous multiplier — worth reconsidering. Verify
184
+ on a real touch device before changing anything.
185
+ 6. **Type scale vs Japanese rendering.** Divergence 2 is sharper for JP than for Latin: fractional
186
+ sizes (12.5px, 17.6px, 19.8px) put full-width glyphs on half-pixel boundaries, where kanji
187
+ strokes blur far more visibly than Latin letterforms. Another reason to prefer Carbon's integer
188
+ steps if the scale is ever revisited.
189
+
190
+ ## Derived colour is GENERATED, not authored
191
+
192
+ **The problem this fixes.** The library hand-authored roughly a thousand tokens and reasoned about
193
+ each one. A hover step was chosen by eye, an active step was chosen by eye, a focus-halo alpha was
194
+ chosen by eye and then justified in a paragraph. Every one of those is a decision with no outside
195
+ authority behind it, and every one has to be re-argued the next time somebody looks at it.
196
+
197
+ **Ant Design already solves this exactly once**: a SEED colour goes in, an ALGORITHM runs, and the
198
+ whole derived map falls out. This document already named antd as the authority for component
199
+ taxonomy and focus shape; that is now extended to make antd the **generator** of derived colour.
200
+
201
+ `scripts/gen-antd-tokens.mjs` reads the seeds out of `src/tokens/foundation.css`, runs antd 6.6.2's
202
+ own `theme.getDesignToken()` (`defaultAlgorithm` for light, `darkAlgorithm` for dark) and writes
203
+ `src/tokens/antd.generated.css`. `pnpm check:antd-tokens` fails CI if that file drifts from the
204
+ algorithm. **antd is a devDependency and a build-time tool only** — `pnpm check:no-antd-runtime`
205
+ asserts it never reaches `dependencies`, `src/`, or `dist/`.
206
+
207
+ **What is authored:** five brand colours per theme (SmartHR MAIN plus four 和色, and the lifted dark
208
+ ramp). **What is computed:** `--primary-hover`, `--primary-active`, `--primary-border`,
209
+ `--destructive-hover`, `--destructive-active`, `--control-outline*` and `--ring`.
210
+
211
+ **What is deliberately NOT taken from antd, and why — each a measurement, not a preference:**
212
+
213
+ - **The neutral spine.** Colour foundation stays SmartHR's (see the table above); the neutrals are
214
+ not derived from the brand seed. `colorBorder` is also the only role antd offers for a control
215
+ boundary and it measures **1.43:1** on the page, where `--input` is held to 3:1 by SC 1.4.11 and
216
+ by `input-boundary-contrast.test.ts`.
217
+ - **The text ramp.** antd's is alpha-based (`rgba(0,0,0,0.88)`), which cannot enter this library's
218
+ opaque `H S% L%` triple without choosing a surface to composite against — lossy by construction.
219
+ - **The dark `--primary` itself.** `darkAlgorithm` MOVES the seed (antd's own `#1677ff` becomes
220
+ `#1668dc`). Feeding this library's light seed in gives `#0363a4` at **2.81:1** on the dark spine,
221
+ which `primary-text-contrast.test.ts` rejects outright; antd's transform of the committed dark
222
+ seed gives `#3794d3` at 5.36:1 where the seed itself measures 7.07:1. The dark theme therefore
223
+ keeps its seed and everything derived from it is antd's. This is the one structural divergence,
224
+ recorded in `DIVERGENCES` in the generator.
225
+
226
+ **Geometry antd owns was already correct.** `lineWidth` 1, `controlOutlineWidth` 2,
227
+ `lineWidthFocus` 3, `borderRadius` 6, `controlHeight` 32 and `fontSize` 14 all match the named
228
+ scales this library already ships (`--stroke-*`, `--radius`, `--band-height-md`,
229
+ `--font-size-base`). The generator asserts that agreement rather than emitting a second copy, so
230
+ the two cannot drift apart silently.
231
+
232
+ ## Focus appearance — Ant Design owns the shape, and the indicator SHIPS OFF
233
+
234
+ ### The two forms, read from antd's source
235
+
236
+ **SmartHR** (`smarthr-ui@99.6.0`) draws focus as an opaque ring held off the control by a white
237
+ spacer. **Ant Design** (`antd@6.6.2`) has two forms instead:
238
+
239
+ - **Field** — `borderColor: colorPrimary` at the unchanged `lineWidth`, plus
240
+ `boxShadow: 0 0 0 ${controlOutlineWidth}px ${controlOutline}`
241
+ (`es/input/style/token.js:48-50`). `es/select/style/select-input.js:32` emits the same
242
+ declarations, which is why an antd Select focuses exactly like an antd Input.
243
+ - **Non-field** — `genFocusOutline` (`es/style/index.js:60-64`):
244
+ `outline: ${lineWidthFocus}px solid ${colorPrimaryBorder}; outline-offset: 1`.
245
+
246
+ **v5 → v6 changed none of this.** Both majors were unpacked and their own `formatToken` run side by
247
+ side; every value is identical to the digit in `antd@5.29.3`.
248
+
249
+ **Ant Design wins the shape.** The library shipped an opaque brand ring drawn immediately outside an
250
+ untouched grey border — two outlines of different colours claiming the same edge — and a Select that
251
+ could not be told to agree with an Input. antd's rule resolves both. SmartHR still owns the hue: the
252
+ focus colour is `--ring`, which the generated tier declares as `var(--primary)` because antd has no
253
+ separate focus-colour token at all.
254
+
255
+ ### The indicator ships OFF. That forfeits WCAG 2.4.7 and a JIS X 8341-3 AA claim.
256
+
257
+ `--focus-outline: 0` in `foundation.css`. Nothing paints a focus indicator by default.
258
+
259
+ **This is a product decision, made with the cost stated.** The indicator was reported as intrusive —
260
+ a thick blue outline appearing on shift-tab, stacked on an already-shaded selected nav row. The
261
+ owner chose to ship it off and let whoever needs it turn it on. What that costs, plainly:
262
+
263
+ - **WCAG 2.2 SC 2.4.7 Focus Visible is an AA criterion**, and with the switch off it is not met.
264
+ - **JIS X 8341-3:2016 tracks WCAG 2.0 AA**, so a Japanese enterprise procurement asking for that
265
+ conformance statement cannot be answered while the switch is off. Japanese public-sector and
266
+ large-enterprise buyers ask for it routinely, and デジタル庁 is this document's standards
267
+ reference for exactly that reason.
268
+
269
+ **Turning it back on is one attribute, on the root element, with no code change:**
270
+
271
+ ```html
272
+ <html data-focus-outline="on">
273
+ ```
274
+
275
+ **The switch is antd's own mechanism, not one invented here.** antd 6.6.2 carries a `focusOutline`
276
+ seed flag (`es/theme/themes/seed.js:67`) and consumes it in `es/theme/util/alias.js:71`:
277
+
278
+ ```js
279
+ lineWidthFocus: mergedToken.focusOutline === false ? 0 : mergedToken.lineWidth * 3
280
+ ```
281
+
282
+ The flag zeroes the focus line width. `--focus-outline` is that flag in CSS: every painted length
283
+ multiplies by it, so no component rebind can bring the mark back while it is `0`. The two paints
284
+ that are not lengths — the halo and a field's recoloured boundary — are scoped to the same attribute
285
+ in `styles/focus-ring.css`. **Every `:focus-visible` selector stays exactly where it is**; only the
286
+ painted result disappears.
287
+
288
+ ### The ON state is the LIGHT one
289
+
290
+ The complaint was weight, not existence, so the on-position is not the old mark restored. It paints
291
+ antd's **field** indicator on every control — one `lineWidth` (1px) in the focus hue, plus antd's
292
+ `controlOutline` halo — rather than antd's 3px `genFocusOutline`. Measured, in Chromium, on
293
+ `ql.test` after the transition settles:
294
+
295
+ | control | switch off | switch on |
296
+ | --- | --- | --- |
297
+ | Input / Select trigger | border `1px rgb(144,135,127)`, resting shadow intact | border `1px rgb(0,113,189)` + `rgba(0,182,228,0.11) 0 0 0 2px` |
298
+ | Button (primary) | outline `0px`, resting shadow intact | `outline: 1px solid rgb(0,113,189)` @ `0px` + same halo |
299
+ | Sidebar nav row / list row | outline `0px` | `outline: 1px solid rgb(0,113,189)` @ **`-1px`** (inset into the row) |
300
+
301
+ The field pair is **byte-identical to antd's own `activeBorderColor` + `activeShadow`** for this
302
+ seed. The nav row insets its mark into its own shape rather than wrapping an already-shaded surface,
303
+ which is the specific stacking that read as heavy.
304
+
305
+ **No control's box moves when it is focused**, measured with `getBoundingClientRect()` before and
306
+ after: the field form only recolours a border and an `outline` is painted outside the box model.
307
+ Input, Save and Delete all stay at 32.00px.
308
+
309
+ **Two criteria, and only one is met by thickness alone.** SC 1.4.11 (AA, non-text contrast) is about
310
+ COLOUR — the 1px mark measures 5.05:1 light and 7.07:1 dark on every surface a control sits on, so
311
+ the light weight costs nothing there. antd's own `colorPrimaryBorder` (#6dc0e3) measures **2.00:1
312
+ light / 1.66:1 dark** and could not have satisfied it at any thickness, which is why the on-state
313
+ takes the focus hue instead — the single place it departs from `genFocusOutline`. SC 2.4.13 Focus
314
+ Appearance (AAA) additionally wants a 2px perimeter; the on-state does not target it, and
315
+ `--focus-outline-weight: var(--stroke-lg)` restores antd's 3px if a customer needs the area clause.
316
+
317
+ Both positions of the switch are gated in `src/tokens/__tests__/focus-ring-contrast.test.ts`: with
318
+ it off nothing paints and no rebind can route around it, with it on the geometry and the ≥3:1
319
+ contrast hold on every surface. Per rule 4 below: a standard that is not enforced is a standard that
320
+ has already drifted.
321
+
322
+ ### The indicator is for CONTROLS, not containers
323
+
324
+ Buttons, fields, links, nav and menu rows carry it. A div, a content region, a scroll wrapper and
325
+ `body` do not. `.ui-legal-document-section` was removed from the list: it is a slab of prose that
326
+ carries `tabIndex={-1}` only so a table-of-contents link can move focus into it, so it is never in
327
+ the tab order and a keyboard user cannot arrive there unannounced. Its `tabIndex={-1}` stays,
328
+ because removing it would break the contents anchor.
329
+
330
+ **Every other focusable non-interactive element in this library is a genuinely scrollable region**
331
+ — `.app-main`, `.ui-mobile-shell-main`, `.ui-timeline-grid`, `.ui-code-block` when it overflows,
332
+ MasterDetail's bounded master, the DataTable and Table scroll containers, ScrollArea, and the
333
+ BranchScopePicker list. Each carries `tabIndex={0}` for axe `scrollable-region-focusable` and must
334
+ stay reachable, so each keeps BOTH halves rather than becoming focusable-but-unpainted. The region
335
+ ring (`--region-focus-ring-width`, off by default) now also multiplies by `--focus-outline`, so a
336
+ service that opts it in still cannot paint while the library-wide switch is off.
337
+
338
+ ## How a decision gets made from here
339
+
340
+ 1. Look it up in Carbon's guidance for the layer in question.
341
+ 2. If Carbon has an answer, take it, and cite it in the token or component comment.
342
+ 3. If Carbon has no answer, decide — and add the reason here, so it is looked up next time
343
+ instead of re-decided.
344
+ 4. Encode the decision as a gate wherever a gate can hold it. A standard that is not enforced is a
345
+ standard that has already drifted; `visual-audit-rules.test.ts` and the scale test above are the
346
+ pattern to follow.
@@ -3,8 +3,8 @@
3
3
  > Generated by `scripts/frame-coverage.mjs` (issue #163). Do not edit by hand — run `pnpm check:frame-coverage`.
4
4
  > Standard: [FRAME-COVERAGE-STANDARD.md](./FRAME-COVERAGE-STANDARD.md). A missing dimension is **UNTESTED**, never a pass.
5
5
 
6
- - Public components: **126**
7
- - With ≥1 frame: **126** · zero-frame: **0**
6
+ - Public components: **129**
7
+ - With ≥1 frame: **129** · zero-frame: **0**
8
8
  - Fully covered (every contract axis declared): **0**
9
9
 
10
10
  ## Per-component contract axes
@@ -22,6 +22,8 @@ Legend: ✓ covered · N/A reasoned skip · · UNTESTED (blank cell = UNTESTED).
22
22
  | MobileShell | ✓ | N/A | N/A | N/A | · | · | · | · | · | · |
23
23
  | Sidebar | ✓ | N/A | N/A | N/A | · | · | · | · | · | · |
24
24
  | Topbar | ✓ | N/A | N/A | N/A | · | · | · | · | · | · |
25
+ | TopbarItem | ✓ | N/A | N/A | N/A | · | · | · | · | · | · |
26
+ | NavList | ✓ | N/A | N/A | N/A | · | · | · | · | · | · |
25
27
  | MasterDetail | ✓ | N/A | N/A | N/A | · | · | · | · | · | · |
26
28
  | SplitPane | ✓ | N/A | N/A | N/A | · | · | · | · | · | · |
27
29
  | ErrorSurface | ✓ | ✓ | N/A | N/A | · | · | · | · | · | · |
@@ -113,6 +115,7 @@ Legend: ✓ covered · N/A reasoned skip · · UNTESTED (blank cell = UNTESTED).
113
115
  | PasswordStrength | ✓ | N/A | N/A | · | · | · | · | · | · | · |
114
116
  | InputOTP | ✓ | N/A | · | N/A | · | · | · | · | · | · |
115
117
  | Rating | ✓ | N/A | · | · | · | · | · | · | · | · |
118
+ | Segmented | ✓ | N/A | · | · | · | · | · | · | · | · |
116
119
  | TagInput | ✓ | N/A | · | · | · | · | · | · | · | · |
117
120
  | ContextMenu | ✓ | N/A | N/A | · | · | · | · | · | · | · |
118
121
  | Menubar | ✓ | N/A | N/A | · | · | · | · | · | · | · |
package/docs/README.md CHANGED
@@ -1,18 +1,19 @@
1
1
  # @godxjp/ui Documentation
2
2
 
3
- | Doc | Purpose |
4
- | -------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------- |
5
- | [CONSUMER-RULES.md](./CONSUMER-RULES.md) | **Start here if you build an app with @godxjp/ui** — the ten rules the audit enforces (styles entry, layout owners, real primitives, tokens) |
6
- | [DEVELOPMENT.md](./DEVELOPMENT.md) | **Contributor guideline** — role/boundary, architecture, how to add/extend, verify |
7
- | [COMPONENTS.md](./COMPONENTS.md) | Component catalog by Ant Design group |
8
- | [PROPS-VOCABULARY.md](./PROPS-VOCABULARY.md) | Atomic prop types (`*Prop` suffix) |
9
- | [PROPS-REGISTRY.md](./PROPS-REGISTRY.md) | Machine-readable registry + forbidden aliases |
10
- | [DATETIME.md](./DATETIME.md) | **Mandatory** `formatDate` — all date/time display |
11
- | [SPACING.md](./SPACING.md) | Golden ratio (φ) macro spacingPageContainer sections, Flex gap, Card rhythm |
12
- | [FORMS.md](./FORMS.md) | **Mandatory** react-hook-form + Zod 4 |
13
- | [TESTING.md](./TESTING.md) | **Mandatory** Vitest per component |
14
- | [FRAME-A11Y-CI.md](./FRAME-A11Y-CI.md) | Per-frame axe a11y + geometry + coverage CI gates — how to run/regenerate locally |
15
- | [../README.md](../README.md) | Setup, workspace wiring, theme |
3
+ | Doc | Purpose |
4
+ | -------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
5
+ | [CONSUMER-RULES.md](./CONSUMER-RULES.md) | **Start here if you build an app with @godxjp/ui** — the ten rules the audit enforces (styles entry, layout owners, real primitives, tokens) |
6
+ | [DEVELOPMENT.md](./DEVELOPMENT.md) | **Contributor guideline** — role/boundary, architecture, how to add/extend, verify |
7
+ | [COMPONENTS.md](./COMPONENTS.md) | Component catalog by Ant Design group |
8
+ | [PROPS-VOCABULARY.md](./PROPS-VOCABULARY.md) | Atomic prop types (`*Prop` suffix) |
9
+ | [PROPS-REGISTRY.md](./PROPS-REGISTRY.md) | Machine-readable registry + forbidden aliases |
10
+ | [DATETIME.md](./DATETIME.md) | **Mandatory** `formatDate` — all date/time display |
11
+ | [DESIGN-AUTHORITY.md](./DESIGN-AUTHORITY.md) | **Which external standard owns which decision** APG, Radix, shadcn, Ant Design groups, SmartHR colour, Carbon geometry. Read before inventing a spacing/density/layout answer |
12
+ | [SPACING.md](./SPACING.md) | Golden ratio (φ) macro spacing — PageContainer sections, Flex gap, Card rhythm |
13
+ | [FORMS.md](./FORMS.md) | **Mandatory** react-hook-form + Zod 4 |
14
+ | [TESTING.md](./TESTING.md) | **Mandatory** Vitest per component |
15
+ | [FRAME-A11Y-CI.md](./FRAME-A11Y-CI.md) | Per-frame axe a11y + geometry + coverage CI gates — how to run/regenerate locally |
16
+ | [../README.md](../README.md) | Setup, workspace wiring, theme |
16
17
 
17
18
  ## Preview
18
19
 
@@ -26,6 +26,27 @@ export default function Demo() {
26
26
  subtitle="leading · title/description · trailing action · short entity lists inside a Card"
27
27
  >
28
28
  <Flex direction="col" gap="lg">
29
+ <Card>
30
+ <CardHeader>
31
+ <CardTitle level={2}>Navigation rows</CardTitle>
32
+ <CardDescription>
33
+ The whole row is one link, including its metadata. Current location uses aria-current.
34
+ </CardDescription>
35
+ </CardHeader>
36
+ <CardContent flush>
37
+ <ListRow
38
+ asChild
39
+ title="Roles and permissions"
40
+ description="Inspect organization access"
41
+ overflow="wrap"
42
+ >
43
+ <a href="?section=roles" aria-current="page" />
44
+ </ListRow>
45
+ <ListRow asChild title="Members" description="Assign roles to organization members">
46
+ <a href="?section=members" />
47
+ </ListRow>
48
+ </CardContent>
49
+ </Card>
29
50
  <Card>
30
51
  <CardHeader>
31
52
  <CardTitle level={2}>アクティブなセッション Active sessions</CardTitle>
@@ -7,6 +7,7 @@ import { Flex, PageContainer } from "@godxjp/ui/layout";
7
7
 
8
8
  /**
9
9
  * SearchInput — debounced search box with a built-in clear button.
10
+ * onSearch runs after a changed query settles, never on mount.
10
11
  * Always listen to onSearch (NOT onChange). Uncontrolled for local filters,
11
12
  * controlled when search state lives in a URL param. Never a raw <input>.
12
13
  * Composed only from real @godxjp/ui components.
@@ -0,0 +1,124 @@
1
+ import { useState } from "react";
2
+
3
+ import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@godxjp/ui/data-display";
4
+ import { FormField, Segmented } from "@godxjp/ui/data-entry";
5
+ import { Text } from "@godxjp/ui/general";
6
+ import { Flex, PageContainer } from "@godxjp/ui/layout";
7
+ import { CalendarDays, Columns3, LayoutList, Monitor, Moon, Sun } from "lucide-react";
8
+
9
+ /**
10
+ * Segmented — one-of-N from a small, closed, always-visible set. antd's Segmented drawn on Radix
11
+ * RadioGroup: a recessed track with the chosen item as a lifted slab, radiogroup semantics, arrow
12
+ * keys between members. The track measures exactly --control-height, so it sits level with an
13
+ * Input or a Button on the same row.
14
+ */
15
+ export default function Demo() {
16
+ const [theme, setTheme] = useState("system");
17
+ const [view, setView] = useState("list");
18
+ const [range, setRange] = useState("week");
19
+
20
+ return (
21
+ <PageContainer title="Segmented" subtitle="閉じた選択肢からひとつだけ · テーマ・表示形式・期間">
22
+ <Flex direction="col" gap="lg">
23
+ <Card>
24
+ <CardHeader>
25
+ <CardTitle level={2}>ラベルのみ</CardTitle>
26
+ <CardDescription>
27
+ 選択肢が 2〜4 個で、すべて画面に出せるときは Select ではなくこちら。ひとつは必ず
28
+ 選ばれている状態なので、ToggleGroup ではなく radiogroup として読み上げられます。
29
+ </CardDescription>
30
+ </CardHeader>
31
+ <CardContent>
32
+ <Flex direction="col" gap="md">
33
+ <Segmented
34
+ aria-label="期間"
35
+ value={range}
36
+ onValueChange={setRange}
37
+ options={[
38
+ { value: "day", label: "日" },
39
+ { value: "week", label: "週" },
40
+ { value: "month", label: "月" },
41
+ ]}
42
+ />
43
+ <Text size="xs" tone="muted">
44
+ 選択中: {range}
45
+ </Text>
46
+ </Flex>
47
+ </CardContent>
48
+ </Card>
49
+
50
+ <Card>
51
+ <CardHeader>
52
+ <CardTitle level={2}>アイコン付き</CardTitle>
53
+ <CardDescription>
54
+ `icon` は aria-hidden で描かれ、読み上げ名は `label` のままです。
55
+ </CardDescription>
56
+ </CardHeader>
57
+ <CardContent>
58
+ <Flex direction="col" gap="md">
59
+ <Segmented
60
+ aria-label="テーマ"
61
+ value={theme}
62
+ onValueChange={setTheme}
63
+ options={[
64
+ { value: "light", label: "ライト", icon: <Sun aria-hidden="true" /> },
65
+ { value: "dark", label: "ダーク", icon: <Moon aria-hidden="true" /> },
66
+ { value: "system", label: "システム", icon: <Monitor aria-hidden="true" /> },
67
+ ]}
68
+ />
69
+ <Segmented
70
+ aria-label="表示形式"
71
+ value={view}
72
+ onValueChange={setView}
73
+ options={[
74
+ { value: "list", label: "一覧", icon: <LayoutList aria-hidden="true" /> },
75
+ { value: "board", label: "ボード", icon: <Columns3 aria-hidden="true" /> },
76
+ {
77
+ value: "calendar",
78
+ label: "カレンダー",
79
+ icon: <CalendarDays aria-hidden="true" />,
80
+ },
81
+ ]}
82
+ />
83
+ </Flex>
84
+ </CardContent>
85
+ </Card>
86
+
87
+ <Card>
88
+ <CardHeader>
89
+ <CardTitle level={2}>フォームの中で</CardTitle>
90
+ <CardDescription>
91
+ `name` を渡すとネイティブフォームで送信されます。無効化は選択肢ごとにも、グループ
92
+ 全体にもかけられます。
93
+ </CardDescription>
94
+ </CardHeader>
95
+ <CardContent>
96
+ <Flex direction="col" gap="md">
97
+ <FormField id="segmented-plan" label="プラン">
98
+ <Segmented
99
+ id="segmented-plan"
100
+ name="plan"
101
+ defaultValue="standard"
102
+ options={[
103
+ { value: "light", label: "ライト" },
104
+ { value: "standard", label: "スタンダード" },
105
+ { value: "enterprise", label: "エンタープライズ", disabled: true },
106
+ ]}
107
+ />
108
+ </FormField>
109
+ <Segmented
110
+ aria-label="無効化されたグループ"
111
+ defaultValue="a"
112
+ disabled
113
+ options={[
114
+ { value: "a", label: "A" },
115
+ { value: "b", label: "B" },
116
+ ]}
117
+ />
118
+ </Flex>
119
+ </CardContent>
120
+ </Card>
121
+ </Flex>
122
+ </PageContainer>
123
+ );
124
+ }
@@ -10,3 +10,7 @@ For filter tabs / segmented toggles that show a per-option total, pass the `coun
10
10
  (`<Button variant="outline" count={18}>Chờ bay</Button>`) instead of nesting a `Badge` —
11
11
  it renders a borderless counter pill, formatted in the active locale and toned to the
12
12
  button variant, so an outline button never ends up with a doubled border.
13
+
14
+ For long collection labels, use `wrap` with `align="start"` and `fullWidth`. The button
15
+ grows vertically while retaining the size preset's minimum target height. Keep badges
16
+ and other row metadata outside the button. `align="end"` follows the document direction.
@@ -15,6 +15,9 @@ import { Button, Text } from "@godxjp/ui/general";
15
15
  import { Flex, MasterDetail, PageContainer } from "@godxjp/ui/layout";
16
16
 
17
17
  /**
18
+ * Pass mobilePane="master" or "detail" for one-pane navigation below the token-owned collapse threshold.
19
+ * Supply detailBack with a native back link; own selection in URL/router history.
20
+ * Omit mobilePane to retain the original stacked composition.
18
21
  * MasterDetail · チーム画面 (SCR-110) の正準構成 — 流動的な一覧 + 固定幅の詳細レール。
19
22
  *
20
23
  * Shows: rail="detail" (既定 · 1fr / 320px) と rail="master" (先頭ナビゲーションレール)、