@godxjp/ui 28.7.0 → 28.8.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 (85) hide show
  1. package/dist/components/data-display/index.d.ts +2 -0
  2. package/dist/components/data-display/index.js +2 -0
  3. package/dist/components/data-display/marquee.d.ts +16 -0
  4. package/dist/components/data-display/marquee.js +155 -0
  5. package/dist/components/general/reveal.d.ts +23 -2
  6. package/dist/components/general/reveal.js +37 -7
  7. package/dist/components/general/typography.d.ts +4 -1
  8. package/dist/components/general/typography.js +14 -1
  9. package/dist/components/layout/affix.d.ts +86 -0
  10. package/dist/components/layout/affix.js +187 -0
  11. package/dist/components/layout/index.d.ts +4 -0
  12. package/dist/components/layout/index.js +4 -0
  13. package/dist/components/layout/legal-document-shell.js +4 -3
  14. package/dist/components/layout/masonry.d.ts +74 -0
  15. package/dist/components/layout/masonry.js +214 -0
  16. package/dist/components/layout/page-container.js +5 -20
  17. package/dist/components/navigation/anchor.d.ts +64 -0
  18. package/dist/components/navigation/anchor.js +284 -0
  19. package/dist/components/navigation/index.d.ts +4 -0
  20. package/dist/components/navigation/index.js +4 -0
  21. package/dist/components/navigation/mega-menu.d.ts +21 -0
  22. package/dist/components/navigation/mega-menu.js +526 -0
  23. package/dist/contracts/measurement.json +1 -1
  24. package/dist/i18n/messages/en.json +517 -0
  25. package/dist/i18n/messages/ja.json +513 -0
  26. package/dist/i18n/messages/vi.json +513 -0
  27. package/dist/lib/hooks.d.ts +68 -0
  28. package/dist/lib/hooks.js +52 -0
  29. package/dist/lib/platform.d.ts +14 -0
  30. package/dist/lib/platform.js +10 -1
  31. package/dist/lib/utils.d.ts +1 -1
  32. package/dist/lib/utils.js +3 -2
  33. package/dist/props/components/data-display.prop.d.ts +95 -1
  34. package/dist/props/components/general.prop.d.ts +47 -3
  35. package/dist/props/components/layout.prop.d.ts +194 -0
  36. package/dist/props/components/navigation.prop.d.ts +263 -0
  37. package/dist/props/registry.d.ts +359 -4
  38. package/dist/props/registry.js +472 -3
  39. package/dist/props/vocabulary/index.d.ts +1 -1
  40. package/dist/props/vocabulary/interaction.prop.d.ts +39 -2
  41. package/dist/styles/control.css +5 -6
  42. package/dist/styles/data-display-layout.css +2 -1
  43. package/dist/styles/density.css +4 -0
  44. package/dist/styles/layout.css +79 -0
  45. package/dist/styles/motion.css +121 -1
  46. package/dist/styles/navigation-layout.css +397 -1
  47. package/dist/styles/shell-layout.css +3 -0
  48. package/dist/styles/text-layout.css +52 -4
  49. package/dist/tokens/base.css +5 -0
  50. package/dist/tokens/components/affix.css +7 -0
  51. package/dist/tokens/components/anchor.css +17 -0
  52. package/dist/tokens/components/control.css +3 -3
  53. package/dist/tokens/components/form.css +1 -1
  54. package/dist/tokens/components/marquee.css +7 -0
  55. package/dist/tokens/components/masonry.css +6 -0
  56. package/dist/tokens/components/mega-menu.css +62 -0
  57. package/dist/tokens/components/shell.css +3 -0
  58. package/dist/tokens/foundation.css +11 -0
  59. package/dist/tokens/semantic/layout.css +7 -0
  60. package/docs/COMPOSITION-VS-COMPONENT.md +19 -1
  61. package/docs/DESIGN-AUTHORITY.md +99 -18
  62. package/docs/FRAME-COVERAGE-REPORT.md +7 -2
  63. package/docs/data-display/marquee.tsx +254 -0
  64. package/docs/foundation/_theme-editor-scope.ts +222 -0
  65. package/docs/foundation/density.tsx +12 -2
  66. package/docs/foundation/spacing.tsx +5 -0
  67. package/docs/foundation/theme-editor.tsx +645 -0
  68. package/docs/general/activity.tsx +65 -0
  69. package/docs/general/reveal.tsx +290 -22
  70. package/docs/general/typography.tsx +91 -1
  71. package/docs/layout/affix.tsx +209 -0
  72. package/docs/layout/masonry.tsx +291 -0
  73. package/docs/navigation/anchor.tsx +285 -0
  74. package/docs/navigation/mega-menu-panel.tsx +86 -0
  75. package/docs/navigation/mega-menu.tsx +254 -0
  76. package/docs/roadmap/website-components.md +779 -0
  77. package/docs/showcase/acme-website.tsx +75 -39
  78. package/docs/showcase/futurelastic-web.tsx +91 -49
  79. package/docs/showcase/marketing-page.tsx +885 -0
  80. package/docs/showcase/table-footer-totals.tsx +12 -2
  81. package/docs/showcase/theme-customization.tsx +1259 -0
  82. package/package.json +5 -3
  83. package/scripts/brand-accent.generated.mjs +27 -0
  84. package/scripts/ui-audit.mjs +66 -0
  85. package/scripts/visual-audit-rules.mjs +46 -2
@@ -27,6 +27,22 @@
27
27
  font-size: var(--font-size-2xl);
28
28
  }
29
29
 
30
+ [data-slot="text"][data-size="3xl"],
31
+ [data-slot="text"][data-size="4xl"],
32
+ [data-slot="text"][data-size="5xl"] {
33
+ line-height: var(--line-height-tight);
34
+ letter-spacing: var(--letter-spacing-tight);
35
+ }
36
+ [data-slot="text"][data-size="3xl"] {
37
+ font-size: var(--font-size-3xl);
38
+ }
39
+ [data-slot="text"][data-size="4xl"] {
40
+ font-size: var(--font-size-4xl);
41
+ }
42
+ [data-slot="text"][data-size="5xl"] {
43
+ font-size: var(--font-size-5xl);
44
+ }
45
+
30
46
  [data-slot="text"][data-weight="regular"],
31
47
  [data-slot="heading"][data-weight="regular"] {
32
48
  font-weight: var(--font-weight-normal);
@@ -154,18 +170,50 @@
154
170
  letter-spacing: var(--letter-spacing-tight);
155
171
  line-height: var(--line-height-tight);
156
172
  }
157
- [data-slot="heading"][data-level="1"] {
173
+
174
+ [data-slot="heading"][data-level="1"]:not([data-size]) {
158
175
  font-size: var(--heading-h1);
159
176
  }
160
- [data-slot="heading"][data-level="2"] {
177
+ [data-slot="heading"][data-level="2"]:not([data-size]) {
161
178
  font-size: var(--heading-h2);
162
179
  }
163
- [data-slot="heading"][data-level="3"] {
180
+ [data-slot="heading"][data-level="3"]:not([data-size]) {
164
181
  font-size: var(--heading-h3);
165
182
  }
166
- [data-slot="heading"][data-level="4"] {
183
+ [data-slot="heading"][data-level="4"]:not([data-size]) {
167
184
  font-size: var(--heading-h4);
168
185
  }
186
+
187
+ [data-slot="heading"][data-size="2xs"] {
188
+ font-size: var(--font-size-2xs);
189
+ }
190
+ [data-slot="heading"][data-size="xs"] {
191
+ font-size: var(--font-size-xs);
192
+ }
193
+ [data-slot="heading"][data-size="sm"] {
194
+ font-size: var(--font-size-sm);
195
+ }
196
+ [data-slot="heading"][data-size="md"] {
197
+ font-size: var(--font-size-base);
198
+ }
199
+ [data-slot="heading"][data-size="lg"] {
200
+ font-size: var(--font-size-lg);
201
+ }
202
+ [data-slot="heading"][data-size="xl"] {
203
+ font-size: var(--font-size-xl);
204
+ }
205
+ [data-slot="heading"][data-size="2xl"] {
206
+ font-size: var(--font-size-2xl);
207
+ }
208
+ [data-slot="heading"][data-size="3xl"] {
209
+ font-size: var(--font-size-3xl);
210
+ }
211
+ [data-slot="heading"][data-size="4xl"] {
212
+ font-size: var(--font-size-4xl);
213
+ }
214
+ [data-slot="heading"][data-size="5xl"] {
215
+ font-size: var(--font-size-5xl);
216
+ }
169
217
  }
170
218
 
171
219
  @layer components {
@@ -44,3 +44,8 @@
44
44
  @import "./components/conversations.css";
45
45
  @import "./components/float-button.css";
46
46
  @import "./components/draggable-panel.css";
47
+ @import "./components/masonry.css";
48
+ @import "./components/mega-menu.css";
49
+ @import "./components/marquee.css";
50
+ @import "./components/affix.css";
51
+ @import "./components/anchor.css";
@@ -0,0 +1,7 @@
1
+
2
+ :root {
3
+
4
+ --affix-inset-block-start: 0px;
5
+
6
+ --affix-inset-block-end: 0px;
7
+ }
@@ -0,0 +1,17 @@
1
+
2
+ :root {
3
+
4
+ --anchor-ink-width: 2px;
5
+
6
+ --anchor-ink-color: initial;
7
+
8
+ --anchor-track-color: initial;
9
+
10
+ --anchor-item-padding-block: var(--space-1);
11
+
12
+ --anchor-item-padding-inline: var(--space-3);
13
+
14
+ --anchor-nested-padding-inline: var(--space-4);
15
+
16
+ --anchor-gap: var(--space-inline-lg);
17
+ }
@@ -377,11 +377,11 @@
377
377
 
378
378
  --control-label-disabled-alpha: 0.7;
379
379
 
380
- --control-surface-border-color: hsl(var(--input));
381
- --control-surface-background: hsl(var(--background));
380
+ --control-surface-border-color: initial;
381
+ --control-surface-background: initial;
382
382
 
383
383
  --control-filled-border-color: transparent;
384
- --control-filled-background: hsl(var(--muted));
384
+ --control-filled-background: initial;
385
385
  --control-filled-shadow: none;
386
386
 
387
387
  --control-borderless-border-color: transparent;
@@ -6,7 +6,7 @@
6
6
 
7
7
  --form-label-gap: var(--space-4);
8
8
 
9
- --form-label-font-size: var(--text-sm);
9
+ --form-label-font-size: var(--control-label-font-size, var(--text-sm));
10
10
 
11
11
  --form-block-gap: var(--space-4);
12
12
 
@@ -0,0 +1,7 @@
1
+
2
+ :root {
3
+
4
+ --marquee-gap-inline: var(--space-inline-lg);
5
+
6
+ --marquee-mask-width: var(--space-10);
7
+ }
@@ -0,0 +1,6 @@
1
+
2
+ :root {
3
+
4
+ --masonry-gap-inline: 0px;
5
+ --masonry-gap-block: 0px;
6
+ }
@@ -0,0 +1,62 @@
1
+
2
+
3
+ :root {
4
+
5
+ --mega-menu-bar-gap: var(--space-inline-xs);
6
+ --mega-menu-trigger-height: var(--control-height);
7
+ --mega-menu-trigger-padding-x: var(--space-3);
8
+ --mega-menu-trigger-gap: var(--space-inline-sm);
9
+ --mega-menu-trigger-radius: var(--radius-sm);
10
+ --mega-menu-trigger-font-size: var(--font-size-sm);
11
+ --mega-menu-trigger-foreground: hsl(var(--foreground));
12
+ --mega-menu-trigger-background: transparent;
13
+
14
+ --mega-menu-trigger-hover-background: initial;
15
+ --mega-menu-trigger-hover-foreground: initial;
16
+ --mega-menu-trigger-open-background: initial;
17
+ --mega-menu-trigger-open-foreground: initial;
18
+ --mega-menu-trigger-current-foreground: initial;
19
+ --mega-menu-trigger-disabled-alpha: 0.5;
20
+ --mega-menu-expand-icon-size: var(--control-icon-size-sm);
21
+
22
+ --mega-menu-panel-background: hsl(var(--popover));
23
+ --mega-menu-panel-foreground: hsl(var(--popover-foreground));
24
+ --mega-menu-panel-border-color: hsl(var(--border));
25
+ --mega-menu-panel-border-width: 1px;
26
+ --mega-menu-panel-radius: var(--radius-md);
27
+ --mega-menu-panel-padding: var(--space-6);
28
+ --mega-menu-panel-shadow: var(--shadow-md);
29
+ --mega-menu-panel-offset: 0px;
30
+
31
+ --mega-menu-panel-max-height: 70vh;
32
+
33
+ --mega-menu-panel-translate: var(--space-1);
34
+
35
+ --mega-menu-groups-gap: var(--space-6);
36
+ --mega-menu-group-gap: var(--space-inline-sm);
37
+
38
+ --mega-menu-group-min-width: 12rem;
39
+ --mega-menu-group-label-font-size: var(--font-size-xs);
40
+ --mega-menu-group-label-foreground: hsl(var(--muted-foreground));
41
+ --mega-menu-group-description-font-size: var(--font-size-xs);
42
+ --mega-menu-group-description-foreground: hsl(var(--muted-foreground));
43
+ --mega-menu-group-icon-size: var(--control-icon-size-sm);
44
+
45
+ --mega-menu-link-gap: var(--space-inline-sm);
46
+ --mega-menu-link-padding-x: var(--space-2);
47
+ --mega-menu-link-padding-y: var(--space-2);
48
+ --mega-menu-link-radius: var(--radius-sm);
49
+ --mega-menu-link-font-size: var(--font-size-sm);
50
+ --mega-menu-link-foreground: hsl(var(--foreground));
51
+ --mega-menu-link-hover-background: initial;
52
+ --mega-menu-link-hover-foreground: initial;
53
+ --mega-menu-link-current-foreground: initial;
54
+ --mega-menu-link-description-font-size: var(--font-size-xs);
55
+ --mega-menu-link-description-foreground: hsl(var(--muted-foreground));
56
+ --mega-menu-link-icon-size: var(--control-icon-size-sm);
57
+
58
+ --mega-menu-link-min-height: var(--touch-target-min);
59
+
60
+ --mega-menu-panel-footer-gap: var(--space-4);
61
+ --mega-menu-panel-footer-border-color: hsl(var(--border));
62
+ }
@@ -102,6 +102,9 @@
102
102
  --topbar-inset: 0px;
103
103
  --topbar-gap: var(--space-2);
104
104
 
105
+ --topbar-background-alpha: initial;
106
+ --topbar-backdrop-blur-size: initial;
107
+
105
108
  --topbar-center-compact-display: none;
106
109
 
107
110
  --topbar-item-padding-inline: var(--space-3);
@@ -205,6 +205,9 @@
205
205
  --space-10: calc(2.5rem * var(--scaling));
206
206
  --space-12: calc(3rem * var(--scaling));
207
207
 
208
+ --space-20: calc(5rem * var(--scaling));
209
+ --space-24: calc(6rem * var(--scaling));
210
+
208
211
  --icon-size-2xs: 0.625rem;
209
212
  --icon-size-xs: 0.75rem;
210
213
  --icon-size-sm: 0.875rem;
@@ -254,6 +257,10 @@
254
257
  --activity-interval: var(--duration-loop);
255
258
 
256
259
  --activity-stagger-step: 160ms;
260
+
261
+ --marquee-interval: 20s;
262
+ --marquee-interval-slow: calc(var(--marquee-interval) * 2);
263
+ --marquee-interval-fast: calc(var(--marquee-interval) / 2);
257
264
  }
258
265
 
259
266
  .ui-scale-fixed {
@@ -267,6 +274,8 @@
267
274
  --space-8: 2rem;
268
275
  --space-10: 2.5rem;
269
276
  --space-12: 3rem;
277
+ --space-20: 5rem;
278
+ --space-24: 6rem;
270
279
  --radius: 0.375rem;
271
280
  --control-height: var(--control-height-default);
272
281
 
@@ -289,6 +298,8 @@
289
298
  --space-page-x: var(--space-6);
290
299
  --space-page-y: var(--space-6);
291
300
  --space-section: var(--space-4);
301
+ --space-section-band: var(--space-20);
302
+ --space-section-hero: var(--space-24);
292
303
  --space-stack-xs: var(--space-1);
293
304
  --space-stack-sm: var(--space-2);
294
305
  --space-stack-md: var(--space-4);
@@ -5,6 +5,9 @@
5
5
  --space-page-x: var(--space-6);
6
6
  --space-page-y: var(--space-6);
7
7
  --space-section: var(--space-4);
8
+
9
+ --space-section-band: var(--space-20);
10
+ --space-section-hero: var(--space-24);
8
11
  --space-stack-xs: var(--space-1);
9
12
  --space-stack-sm: var(--space-2);
10
13
  --space-stack-md: var(--space-4);
@@ -17,6 +20,8 @@
17
20
 
18
21
  --overlay-z-index: 50;
19
22
 
23
+ --sticky-z-index: 20;
24
+
20
25
  --master-detail-rail-compact: 18.75rem;
21
26
  --master-detail-rail-narrow: 12rem;
22
27
  --master-detail-rail-wide: 24rem;
@@ -64,6 +69,8 @@
64
69
  --page-measure-narrow: 42rem;
65
70
  --page-measure-medium: 48rem;
66
71
 
72
+ --page-measure-wide: 72rem;
73
+
67
74
  --page-title-font-size: var(--heading-h1);
68
75
  --page-title-font-size-compact: var(--heading-h2);
69
76
  --page-subtitle-font-size: var(--font-size-base);
@@ -77,7 +77,8 @@ new UI need
77
77
  | `Select` (incl. search/async) | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | **Framework component** |
78
78
  | `DataTable`, `Dialog`, `Calendar`, `Switch`, `Combobox` | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | **Framework component** |
79
79
  | `StatCard` (+ `icon`) | ✅ | ➖ | ✅ | ✅ | ✅ | ✅ | ✅ | **Framework component** (a reusable KPI tile with a stable API + tokens) |
80
- | `ServiceLauncherCard` | ➖ | ❌ | ❌ | ✅ | ✅ | ➖ | ➖ | **Composition by the test — RETAINED as a recorded exception** (gh#814) |
80
+ | `ServiceLauncherCard` | ➖ | ❌ | ❌ | ✅ | ✅ | ➖ | ➖ | **Composition by the test — RETAINED as a recorded exception** (gh#814) |
81
+ | `MegaMenu` | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ➖ | **Framework component** — the instructive contrast with Hero, below |
81
82
  | Marketing **Hero** | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | **Composition** — section, static, composable, brand-specific |
82
83
  | **Navbar** / **Footer** | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | **Composition** — layout of `Text`/`Button`/`Flex` |
83
84
  | **PricingTable** / feature grid | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | **Composition** — `ResponsiveGrid` + `Card` |
@@ -86,6 +87,23 @@ new UI need
86
87
 
87
88
  `✅ pass · ❌ fail · ➖ borderline`. **StatCard** is the instructive borderline: C2 is weak (it owns little behavior), but it is a universal KPI tile with a controlled API, fully tokenized, broadly reused — so it earns its place. A **Hero** fails six of seven; it is unambiguously a composition.
88
89
 
90
+ **MegaMenu is the instructive PASS, and it is in this table because it sits next to Navbar.** A
91
+ Navbar fails every criterion; a megamenu is the same bar with behaviour bolted through it, and that
92
+ one difference flips the verdict. **C2** is the whole case: a roving tabindex across the top-level
93
+ items, `aria-expanded`/`aria-controls` per trigger, hover intent (a diagonal pointer path toward the
94
+ panel must not close it), `Escape` closing and returning focus to its trigger, Tab-out and
95
+ outside-click closing, and close-on-route-change. None of that is layout. **C3** is the one people
96
+ get wrong: `DropdownMenu` looks like the answer and is the WRONG primitive, not an awkward one — it
97
+ is react-aria-components `Menu`, i.e. `role="menu"` / `role="menuitem"`, which announces a set of
98
+ site links as a desktop application menu and takes Tab out of the widget. The APG says so itself in
99
+ its Disclosure Navigation example. A recipe over `DropdownMenu` would therefore ship the classic
100
+ megamenu accessibility defect, which is exactly the case C3 is asking about. **C7 is the weak
101
+ one** — marked ➖ deliberately: this is a JP business-software library, and a marketing megamenu is
102
+ not in every consumer's build. It is kept because the same disclosure bar is what an admin console
103
+ with several product areas needs, and because the cost is one file with no new dependency (no
104
+ `@radix-ui/react-navigation-menu`, no animation runtime). Compare `StatCard`, whose borderline is
105
+ C2 rather than C7.
106
+
89
107
  **ServiceLauncherCard is the ONE recorded exception, and it is recorded so that it stays one.** It shipped before the test was run against it, and when the test was run it came back with two hard FAILs: **C2** — no state, no keyboard handling, no focus management, its only ARIA three static attributes — and **C3** — its own imports are `Card` + `CardContent` + `Badge` + a Lucide glyph, so "could I build this right now from primitives?" is yes. By §2 that makes it a composition pattern. It is kept anyway because `src/components/layout/app-launcher.tsx` consumes it: it is an internal building block of a component that **does** pass, so the question was never "should it exist" but "should it be PUBLIC", and removing a public export is breaking. Keeping it public was the cheaper call and the ledger lives on gh#814 and at the top of `src/components/data-display/service-launcher-card.tsx`. **Consumers should compose `Card` + `Badge` for a service tile** — reach for `ServiceLauncherCard` only to match `AppLauncher`'s own tiles — and this row is not precedent for adding another static tile to `src/components/`.
90
108
 
91
109
  ---
@@ -12,17 +12,17 @@ It changes no code by itself. It is the tie-breaker a reviewer points at.
12
12
 
13
13
  ## The layers, and who owns each
14
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` — a naming precedent, nothing is installed |
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` — a naming precedent, nothing is installed |
21
21
  | Colour foundation | **SmartHR**, brand roles aside | the NEUTRAL spine is SmartHR's — `--foreground` = TEXT_BLACK, `--border` = BORDER. `--primary` was SmartHR MAIN `#0071bd` until identity v2.3 took the brand roles back: it is GoDX violet `#7A00FF` today |
22
- | **Derived colour — the interaction states hanging off each seed** | **Measured contrast (WCAG 2.2 / JIS X 8341-3)** | Authored in `src/tokens/derived.css`; no algorithm derives them. Four contrast suites read that file and hold every value to a threshold, and a fifth holds it to the seed's HUE (gh#648) — see below |
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** |
22
+ | **Derived colour — the interaction states hanging off each seed** | **Measured contrast (WCAG 2.2 / JIS X 8341-3)** | Authored in `src/tokens/derived.css`; no algorithm derives them. Four contrast suites read that file and hold every value to a threshold, and a fifth holds it to the seed's HUE (gh#648) — see below |
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
26
 
27
27
  The first five were already true and merely unwritten. The last three are the choices being made
28
28
  here. Carbon fills the one layer that had no outside answer at all: page rhythm, table density, form layout,
@@ -282,6 +282,87 @@ bottomEnd | none` (`TablePaginationPositionProp`, default `['bottomEnd']`), the
282
282
  rows, so an expanded detail row shifts every stripe after it, and `ui-audit` blocks the
283
283
  utilities it would need.
284
284
 
285
+ - **`Masonry.gap` is antd `gutter`, and its breakpoint steps are this library's (gh-masonry
286
+ port).** Three divergences on one component, each for a reason already written down elsewhere in
287
+ this file:
288
+
289
+ 1. **`gutter` → `gap`.** antd's `gutter` takes a raw pixel number (or a per-breakpoint map of
290
+ them). This library already owns that axis under the name `gap` on `Flex`, `ResponsiveGrid`
291
+ and `AuthStack`, `check:prop-vocabulary` maps a field called `gap` to `GapProp`, and a token
292
+ step is what makes spacing follow `--scaling` (density) and a tenant theme. "This library's
293
+ controlled vocabulary wins on values" is the rule; here it wins on the name too, because the
294
+ name and the value type move together. antd's tuple `[Gap, Gap]` survives as
295
+ `[GapProp, GapProp]` = `[inline, block]`. **`gutter` is not silently absent**: it is declared
296
+ on the prop type as `never`, so a consumer arriving from antd's docs gets a compile error and
297
+ a `@deprecated` hint naming `gap`, plus a development-time `console.warn`.
298
+ 2. **`columns` steps are `base sm md lg xl`, not antd's `xs sm md lg xl xxl`.** antd's `xs` IS
299
+ this library's `base` (the mobile-first floor), and a second spelling of one axis is what
300
+ `check:prop-vocabulary` exists to prevent; `xxl` has no step here at all. `Flex direction`
301
+ is the precedent shape and the media queries are literally the `--flex-direction-*` cascade's
302
+ (sm 40rem · md 48rem · lg 64rem · xl 80rem). Both antd spellings FAIL LOUDLY — TypeScript
303
+ rejects them as excess properties and the component names them in a development warning —
304
+ because a silently-ignored `xs` is the worst outcome of the three.
305
+ 3. **`MasonryItem.height` is HONOURED, where antd declares it and never reads it.** Verified in
306
+ antd `master`: `Masonry.tsx` builds every `ItemHeightData` from `getBoundingClientRect()`
307
+ alone, and all six demos carry their heights in `data`. Porting the field as inert would ship
308
+ a documented prop that does nothing. Here a finite `height` sizes the tile and replaces its
309
+ measurement, which is also what lets a first paint and an SSR render land in the right place.
310
+
311
+ Not ported, each under a rule that already exists: `classNames` / `styles` (a knob only a fork
312
+ could reach — `src/tokens/components/masonry.css` is the answer), `prefixCls` / `rootClassName`
313
+ (no `prefixCls` layer here), and rc-motion's tile-REMOVAL animation (the arrival fade and the
314
+ re-flow slide are ported in CSS and both snap under `prefers-reduced-motion`). `fresh` keeps
315
+ antd's name and antd's `false` default even though `docs/roadmap/list-masonry.md` (2026-09-10)
316
+ proposed renaming it `observeItems` and defaulting it on — that proposal predates the
317
+ 2026-09-12 restatement above, and "the antd name says nothing" is exactly the reasoning the
318
+ restatement rejects. The same roadmap's invented `sequential` prop is not ported for the same
319
+ reason: antd has no such knob, and improvements come after parity, not instead of it.
320
+
321
+ - **`Affix` and `Anchor` take the LOGICAL axis where antd takes the physical one (gh#827/gh#828).**
322
+ Ported from `ant-design@master`, read out of `components/affix/{index.tsx,utils.ts}` and
323
+ `components/anchor/{Anchor,AnchorLink}.tsx` on 2026-09-21, not out of the docs tables. Every
324
+ antd prop is present. Four renames, and nothing else:
325
+
326
+ 1. **`offsetTop` → `offsetBlockStart`, `offsetBottom` → `offsetBlockEnd`** (both components;
327
+ `Anchor.targetOffset` → `targetOffsetBlockStart` with it). `check:rtl` exists to keep
328
+ `inset-block-start` out of `top`'s hands in every stylesheet here, and a prop that names a
329
+ physical edge re-opens that door at the API. It is also load-bearing rather than cosmetic:
330
+ the offset is a CSS custom property (`--affix-inset-block-start`), read once by the sentinel
331
+ that detects the crossing and once by the pinned bar that paints it, so the name has to be
332
+ the one CSS uses. All three antd spellings are declared `never` — a compile error carrying an
333
+ `@deprecated` hint that names the replacement — plus a development `console.warn`, plus the
334
+ MCP catalog entry. The `Masonry gutter → gap` precedent, applied to a second component.
335
+ 2. **`Anchor.onChange` → `onValueChange`,** beside new `value` / `defaultValue`. The active
336
+ `href` is a controlled value and this library has one spelling for that. antd has no
337
+ controlled form of it at all (`getCurrentAnchor` is a transform of the computed answer, not a
338
+ value), so this is an ADDITION with a rename attached rather than a substitution, and
339
+ `getCurrentAnchor` is ported unchanged beside it — a controlled `value` simply outranks it.
340
+ antd's own note is ported too: `onValueChange` reports the link the SCROLL POSITION resolved,
341
+ not the one `getCurrentAnchor` substituted.
342
+
343
+ **`Affix.onChange` is NOT renamed**, and the distinction is the point: there is no pinned VALUE
344
+ to control, only an observation of a derived boolean, so no triad applies. `Attachments.onChange`
345
+ and `ActionsFeedback.onChange` already carry antd's name with a non-DOM payload for the same
346
+ reason.
347
+
348
+ **Not ported, each under a rule that already exists:** `classNames` / `styles` on `Anchor` (a
349
+ knob only a fork could reach — `src/tokens/components/anchor.css` is the answer),
350
+ `prefixCls` / `rootClassName`, and antd's deprecated `Anchor children` / `Anchor.Link` JSX form
351
+ (antd itself warns on it and says to use `items`).
352
+
353
+ **One deliberate behavioural difference, in `Anchor`.** antd resolves from scroll position on
354
+ mount unconditionally, which overwrites a landing hash on any page whose hashed section happens
355
+ to be above the fold. Here the hash is read in the state INITIALISER and the mount resolution is
356
+ skipped when it matched, so `/page#section` is correct on the first render with no scroll event
357
+ ever firing — which is gh#828's acceptance criterion, and the one place antd's behaviour is
358
+ worse rather than merely differently named.
359
+
360
+ **Two things antd does not have at all**, both required by the contract rather than invented:
361
+ `Anchor.label` (antd ships an unnamed `<div>`; a `<nav>` landmark on a page that already has a
362
+ breadcrumb and a rail must be named, so `label` defaults through `t()`), and `Affix`'s
363
+ `scroll-padding-block-start` on the scroll box while pinned (WCAG 2.4.11 — without it the pinned
364
+ bar covers whatever the browser scrolls a focused element to).
365
+
285
366
  - **`Command.split` is antd `List.split`, with this library's default.** Same name, same meaning:
286
367
  a divider between rows and none after the last (gh#699). antd's `List` defaults it to `true`;
287
368
  `Command` defaults it to `false`, because its resting home is a command palette, where inset
@@ -501,11 +582,11 @@ snapped to the seed's hue (1.008:1 / 1.010:1 from the kit value, i.e. the same c
501
582
  the kit's `action.primary.hover` and then reflects the pressed step, because the dark seed sits at
502
583
  86.9% L and the conventional step runs out of LIGHTNESS rather than out of contrast:
503
584
 
504
- | token | conventional step | shipped |
505
- | ------------------------ | ------------------------ | ---------------------------------- |
506
- | light `--primary-hover` | kit #6400D4 | #6500d4 · 8.23:1 |
507
- | light `--primary-active` | kit #5200B0 | #5400b0 · 10.31:1 |
508
- | dark `--primary-hover` | kit #E8DAFF | #ecdaff · 13.51:1 |
585
+ | token | conventional step | shipped |
586
+ | ------------------------ | ---------------------------- | ------------------------------- |
587
+ | light `--primary-hover` | kit #6400D4 | #6500d4 · 8.23:1 |
588
+ | light `--primary-active` | kit #5200B0 | #5400b0 · 10.31:1 |
589
+ | dark `--primary-hover` | kit #E8DAFF | #ecdaff · 13.51:1 |
509
590
  | dark `--primary-active` | #fbf7ff · 1.056:1 from white | #cd9fff · 8.41:1 (sign flipped) |
510
591
 
511
592
  **The ratio is blind to hue, and that cost a release (gh#648).** Identity v2.3 moved the seed 63
@@ -604,10 +685,10 @@ the **field** indicator on every control — one hairline (1px) in the focus hue
604
685
  `--control-outline` halo — rather than the heavy 3px outline form. Measured, in Chromium, on
605
686
  `ql.test` after the transition settles:
606
687
 
607
- | control | switch off | switch on |
608
- | -------------------------- | ---------------------------------------------------- | -------------------------------------------------------------- |
688
+ | control | switch off | switch on |
689
+ | -------------------------- | ---------------------------------------------------- | --------------------------------------------------------------- |
609
690
  | Input / Select trigger | border `1px rgb(144,135,127)`, resting shadow intact | border `1px` of `--ring` + `--control-outline` halo `0 0 0 2px` |
610
- | Button (primary) | outline `0px`, resting shadow intact | `outline: 1px solid` `--ring` @ `0px` + same halo |
691
+ | Button (primary) | outline `0px`, resting shadow intact | `outline: 1px solid` `--ring` @ `0px` + same halo |
611
692
  | Sidebar nav row / list row | outline `0px` | `outline: 1px solid` `--ring` @ **`-1px`** (inset into the row) |
612
693
 
613
694
  The GEOMETRY above is the Chromium measurement; the COLOUR is named by token rather than pinned as
@@ -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: **165**
7
- - With ≥1 frame: **144** · zero-frame: **21**
6
+ - Public components: **170**
7
+ - With ≥1 frame: **149** · zero-frame: **21**
8
8
  - Fully covered (every contract axis declared): **0**
9
9
 
10
10
  ## UNTESTED — no `/frame/**` route
@@ -198,8 +198,13 @@ Legend: ✓ covered · N/A reasoned skip · · UNTESTED (blank cell = UNTESTED).
198
198
  | ChatComposer | ✓ | · | · | · | · | · | · | · | · | · |
199
199
  | ChatSuggestion | ✓ | N/A | N/A | · | · | · | · | · | · | · |
200
200
  | Conversations | ✓ | N/A | N/A | N/A | · | · | · | · | · | · |
201
+ | MegaMenu | ✓ | · | N/A | · | · | · | · | · | · | · |
201
202
  | Welcome | ✓ | · | N/A | N/A | · | · | · | · | · | · |
202
203
  | Actions | ✓ | · | N/A | N/A | · | · | · | · | · | · |
203
204
  | ThoughtChain | ✓ | N/A | N/A | N/A | · | · | · | · | · | · |
204
205
  | Attachments | ✓ | N/A | · | N/A | · | · | · | · | · | · |
206
+ | Marquee | ✓ | N/A | N/A | N/A | · | · | · | · | · | · |
207
+ | Masonry | ✓ | N/A | N/A | N/A | · | · | · | · | · | · |
208
+ | Affix | ✓ | N/A | N/A | N/A | · | · | · | · | · | · |
209
+ | Anchor | ✓ | N/A | N/A | · | · | · | · | · | · | · |
205
210