@godxjp/ui 23.0.0 → 23.2.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/date-format-labels.d.ts +14 -2
  2. package/dist/app/date-format-labels.js +1 -1
  3. package/dist/app/date-formats.d.ts +14 -2
  4. package/dist/app/date-formats.js +9 -2
  5. package/dist/components/data-display/card.d.ts +45 -2
  6. package/dist/components/data-display/card.js +4 -2
  7. package/dist/components/data-display/data-table.d.ts +8 -1
  8. package/dist/components/data-display/data-table.js +9 -2
  9. package/dist/components/data-display/feature-list.d.ts +26 -0
  10. package/dist/components/data-display/feature-list.js +29 -0
  11. package/dist/components/data-display/index.d.ts +6 -0
  12. package/dist/components/data-display/index.js +6 -0
  13. package/dist/components/data-display/swatch.d.ts +24 -0
  14. package/dist/components/data-display/swatch.js +19 -0
  15. package/dist/components/data-display/thumbnail.d.ts +37 -0
  16. package/dist/components/data-display/thumbnail.js +20 -0
  17. package/dist/components/data-entry/checkbox.js +1 -1
  18. package/dist/components/data-entry/form-field.js +2 -2
  19. package/dist/components/data-entry/input.js +1 -1
  20. package/dist/components/data-entry/number-input.js +27 -6
  21. package/dist/components/data-entry/radio.js +1 -1
  22. package/dist/components/data-entry/switch.js +1 -1
  23. package/dist/components/feedback/dialog.js +2 -2
  24. package/dist/components/feedback/index.d.ts +2 -2
  25. package/dist/components/feedback/index.js +19 -1
  26. package/dist/components/feedback/sheet.js +2 -22
  27. package/dist/components/feedback/skeleton.d.ts +33 -3
  28. package/dist/components/feedback/skeleton.js +154 -3
  29. package/dist/components/layout/flex.d.ts +1 -1
  30. package/dist/components/layout/flex.js +17 -5
  31. package/dist/components/layout/sidebar.js +9 -2
  32. package/dist/components/navigation/filter-bar.js +2 -1
  33. package/dist/components/navigation/tabs-scroll.d.ts +27 -0
  34. package/dist/components/navigation/tabs-scroll.js +36 -1
  35. package/dist/components/navigation/tabs.d.ts +2 -2
  36. package/dist/components/navigation/tabs.js +60 -16
  37. package/dist/i18n/messages/en.json +10 -1
  38. package/dist/i18n/messages/ja.json +10 -1
  39. package/dist/i18n/messages/vi.json +10 -1
  40. package/dist/lib/breakpoint-token.d.ts +8 -0
  41. package/dist/lib/breakpoint-token.js +29 -0
  42. package/dist/props/components/data-display.prop.d.ts +101 -1
  43. package/dist/props/components/feedback.prop.d.ts +85 -1
  44. package/dist/props/components/layout.prop.d.ts +72 -1
  45. package/dist/props/components/navigation.prop.d.ts +80 -2
  46. package/dist/props/registry.d.ts +124 -1
  47. package/dist/props/registry.js +140 -0
  48. package/dist/props/vocabulary/data.prop.d.ts +5 -0
  49. package/dist/props/vocabulary/index.d.ts +1 -1
  50. package/dist/styles/alert-layout.css +139 -0
  51. package/dist/styles/card-layout.css +48 -0
  52. package/dist/styles/control.css +41 -5
  53. package/dist/styles/data-display-layout.css +88 -0
  54. package/dist/styles/focus-ring.css +28 -0
  55. package/dist/styles/form-layout.css +4 -0
  56. package/dist/styles/layout.css +2 -1
  57. package/dist/styles/navigation-layout.css +89 -5
  58. package/dist/styles/shell-layout.css +22 -0
  59. package/dist/styles/table-layout.css +5 -2
  60. package/dist/tokens/components/card.css +2 -0
  61. package/dist/tokens/components/control.css +8 -3
  62. package/dist/tokens/components/data-display.css +23 -0
  63. package/dist/tokens/components/feedback.css +18 -0
  64. package/dist/tokens/components/navigation.css +4 -0
  65. package/dist/tokens/foundation.css +18 -16
  66. package/docs/CONSUMER-RULES.md +8 -3
  67. package/docs/CUSTOMER-THEMING.md +18 -8
  68. package/docs/DATETIME.md +11 -2
  69. package/docs/DESIGN-AUTHORITY.md +2 -1
  70. package/docs/FRAME-COVERAGE-REPORT.md +29 -3
  71. package/docs/SPACING.md +35 -5
  72. package/docs/TOKENS.md +1 -1
  73. package/docs/assets/shot-landscape.svg +12 -0
  74. package/docs/assets/shot-portrait.svg +10 -0
  75. package/docs/data-display/card/index.tsx +40 -0
  76. package/docs/data-display/data-table/index.tsx +17 -2
  77. package/docs/data-display/feature-list.tsx +159 -0
  78. package/docs/data-display/swatch.tsx +107 -0
  79. package/docs/data-display/table.tsx +1 -1
  80. package/docs/data-display/thumbnail.tsx +128 -0
  81. package/docs/data-entry/segmented-in-filter-row.tsx +77 -0
  82. package/docs/feedback/skeleton.tsx +57 -0
  83. package/docs/layout/flex.tsx +29 -0
  84. package/docs/layout/sidebar.tsx +66 -10
  85. package/docs/navigation/dropdown-menu.tsx +4 -4
  86. package/docs/navigation/tabs.tsx +131 -0
  87. package/docs/roadmap/ai-chat-components.md +1 -2
  88. package/docs/roadmap/tree-components.md +0 -1
  89. package/package.json +12 -21
  90. package/scripts/_agent-setup.mjs +18 -0
  91. package/scripts/consumer-rule.md +16 -0
  92. package/scripts/guinea-pig-skill.md +12 -6
  93. package/scripts/ui-audit.mjs +184 -7
@@ -238,6 +238,24 @@
238
238
  --phi-0: var(--phi-unit);
239
239
  --phi-p1: calc(var(--phi-unit) * var(--ratio-phi));
240
240
  --phi-p2: calc(var(--phi-p1) * var(--ratio-phi));
241
+
242
+ --duration-fast: 150ms;
243
+ --duration-base: 250ms;
244
+ --duration-slow: 500ms;
245
+ --ease-standard: cubic-bezier(0.2, 0, 0, 1);
246
+ --ease-emphasized: cubic-bezier(0.32, 0.72, 0, 1);
247
+ --ease-decelerate: cubic-bezier(0, 0, 0, 1);
248
+ --ease-accelerate: cubic-bezier(0.3, 0, 1, 1);
249
+ --reveal-distance: 10px;
250
+
251
+ --reveal-stagger-step: 60ms;
252
+
253
+ --duration-loop: 1400ms;
254
+
255
+ --activity-interval: var(--duration-loop);
256
+
257
+ --activity-stagger-step: 160ms;
258
+
241
259
  }
242
260
 
243
261
  .ui-scale-fixed {
@@ -289,22 +307,6 @@
289
307
  --space-chrome-gap: var(--space-2);
290
308
  --field-label-gap: var(--space-2);
291
309
 
292
- --duration-fast: 150ms;
293
- --duration-base: 250ms;
294
- --duration-slow: 500ms;
295
- --ease-standard: cubic-bezier(0.2, 0, 0, 1);
296
- --ease-emphasized: cubic-bezier(0.32, 0.72, 0, 1);
297
- --ease-decelerate: cubic-bezier(0, 0, 0, 1);
298
- --ease-accelerate: cubic-bezier(0.3, 0, 1, 1);
299
- --reveal-distance: 10px;
300
-
301
- --reveal-stagger-step: 60ms;
302
-
303
- --duration-loop: 1400ms;
304
-
305
- --activity-interval: var(--duration-loop);
306
-
307
- --activity-stagger-step: 160ms;
308
310
  }
309
311
 
310
312
  .dark,
@@ -5,13 +5,18 @@ Read this once; the audit enforces it. Everything else in `docs/` is for contrib
5
5
  1. Load styles with `@import "@godxjp/ui/styles"` (fonts bundled) or `@import "@godxjp/ui/styles/core"` (no fonts). Never cherry-pick `*-layout.css`.
6
6
  2. Every page is `<PageContainer title subtitle extra footer>`; its sections are spaced by the page. Group items inside a section with `<Flex direction="col" gap>` or `<ResponsiveGrid>`.
7
7
  3. No Tailwind layout on your own elements: no `flex`, `grid`, `gap-*`, `p-*`, `m-*`, `space-*`. Rows are `<Flex>` (default row), stacks are `<Flex direction="col">`, grids are `<ResponsiveGrid>`.
8
- 4. No hand-rolled surfaces: no `rounded-* border bg-*` divs. A box is `Card`, a pill is `Badge`, a person is `Avatar`, a row is `ListRow`, a label/value pair is `Descriptions`, an empty area is `EmptyState`.
8
+ 4. No hand-rolled surfaces: no `rounded-* border bg-*` divs. A box is `Card`, a pill is `Badge`, a person is `Avatar`, a row is `ListRow`, a label/value pair is `Descriptions`, an empty area is `EmptyState`, a read-only sample of a colour the USER chose is `Swatch`.
9
9
  5. Real controls only: `Button`, `Input`, `Select`, `Textarea`, `Checkbox`… never raw `<button>`/`<input>`; a labelled control lives in `<FormField label>`. A Select outside a form takes `width="auto"`. **A disabled control's reason is visible text, never a tooltip** — see below.
10
10
  6. Text is `<Text>` / `<Heading>` with `tone`, `size`, `weight`, `truncate`, `mono` — not `className="text-muted-foreground font-semibold"`.
11
11
  7. Colours are semantic tokens (`tone="destructive"`, `bg-primary`), never palette names, hex, or `bg-black` / `text-white`.
12
12
  8. Sizes come from props (`size`, `width`, `columns`), never `w-[240px]` / `max-h-[420px]`.
13
13
  9. Logical directions (`ms-`, `me-`, `start-`, `end-`) when a utility is unavoidable; never `ml-` / `left-`.
14
- 10. Run `node node_modules/@godxjp/ui/scripts/ui-audit.mjs <dir>` before every review; then `visual-audit.mjs <url>` on the running app. Zero errors is the bar.
14
+ 10. A `Card` whose whole body is a table gets `<CardContent flush>` the table must touch the
15
+ card's inner edge. A default `CardContent` pads 16px while the table draws its own border, so
16
+ the table reads as a second box inside the card, and a wide one runs past the card entirely.
17
+ (`flush` alone is enough: it drops the border, the radius and the inline padding. `tight` is a
18
+ different knob — it governs the header band, not the body's edges.)
19
+ 11. Run `node node_modules/@godxjp/ui/scripts/ui-audit.mjs --changed` before every review; a browser sweep with `visual-audit.mjs` is a separate, heavier run — do it when the work is a visual review, not on every change; `visual-audit.mjs <url>` on the running app. Zero errors is the bar.
15
20
 
16
21
  **Opting a deliberate exception out.** Name the rule; the block form also has to say why.
17
22
 
@@ -45,6 +50,6 @@ information belongs on the screen:
45
50
  - `ServiceLauncherCard` already has this shape as a prop — `disabledReason`, rendered above the
46
51
  action — and it is the pattern to copy anywhere else.
47
52
 
48
- If the control is disabled *pending something the user can fix*, prefer leaving it enabled and
53
+ If the control is disabled _pending something the user can fix_, prefer leaving it enabled and
49
54
  explaining on submit: an inert control with a paragraph beside it is often a validation message
50
55
  wearing a disguise.
@@ -89,7 +89,7 @@ Import the styles, then set anchor tokens in your app's `theme.css` (loaded afte
89
89
 
90
90
  /* ── Shadow tint — ONE knob ─────────────────────────────────────── */ --shadow-color: 12 26 49; /* RGB channels (default 0 0 0). Tints the WHOLE shadow ramp. */
91
91
 
92
- /* ── Brand depth — all opt-in, all quiet by default ─────────────── */ --shadow-glow: 0 8px 20px hsl(var(--primary) / 0.32); /* glow halo on the primary CTA */ --card-shadow: 0 1px 2px rgb(12 26 49 / 0.06), 0 10px 28px -14px rgb(12 26 49 / 0.2); /* lift every Card */ --focus-ring-color: var(--ring); /* hue of every focus ring */ --focus-ring-width: 2px; /* thickness of every focus ring — 0 turns it OFF */ --focus-ring-opacity: 1; /* alpha of every focus ring */ --focus-ring-offset: 0px; /* gap, outline-form rings only (star, dot, anchor) */ --gradient-hero: linear-gradient( 180deg, hsl(var(--accent)), transparent ); /* PageContainer header banner */ --gradient-glow: radial-gradient( 60% 70% at 90% -8%, hsl(var(--primary) / 0.1), transparent 70% ); /* AppShell ambient wash */ --overlay-background: rgb(12 26 49 / 0.55); /* modal scrim (Dialog / Sheet / Drawer) */ } ```
92
+ /* ── Brand depth — all opt-in, all quiet by default ─────────────── */ --shadow-glow: 0 8px 20px hsl(var(--primary) / 0.32); /* glow halo on the primary CTA */ --card-shadow: 0 1px 2px rgb(12 26 49 / 0.06), 0 10px 28px -14px rgb(12 26 49 / 0.2); /* lift every Card */ --focus-ring-color: var(--ring); /* hue of every focus ring */ --focus-ring-weight: var(--stroke-md); /* thickness of every focus ring — 2px; nothing paints until <html data-focus-outline="on"> */ --focus-ring-opacity: 1; /* alpha of every focus ring */ --focus-ring-offset: 0px; /* gap, outline-form rings only (star, dot, anchor) */ --gradient-hero: linear-gradient( 180deg, hsl(var(--accent)), transparent ); /* PageContainer header banner */ --gradient-glow: radial-gradient( 60% 70% at 90% -8%, hsl(var(--primary) / 0.1), transparent 70% ); /* AppShell ambient wash */ --overlay-background: rgb(12 26 49 / 0.55); /* modal scrim (Dialog / Sheet / Drawer) */ } ```
93
93
 
94
94
  ### The anchor tokens (single knobs that propagate)
95
95
 
@@ -101,23 +101,33 @@ Import the styles, then set anchor tokens in your app's `theme.css` (loaded afte
101
101
  | `--shadow-color` | `0 0 0` | every shadow step `--shadow-{xs…2xl}` (`rgb(color / α)`) |
102
102
  | `--shadow-glow` | invisible | brand glow halo on the primary CTA (set the full shadow value) |
103
103
  | `--card-shadow` | `none` | resting elevation of every Card |
104
- | `--focus-ring-color` / `--focus-ring-width` | `var(--ring)` / `2px` | hue & thickness of every keyboard-focus ring (`width: 0` turns every ring OFF) |
104
+ | `--focus-ring-color` / `--focus-ring-weight` | `var(--ring)` / `1px` | hue & thickness of every keyboard-focus ring but nothing paints until the switch below is on |
105
105
  | `--focus-ring-opacity` / `--focus-ring-offset` | `1` / `0px` | alpha of every ring · gap for outline-form rings (star, carousel dot, heading anchor) |
106
106
  | `--gradient-{hero,glow,brand}` | `none` | hero header banner / AppShell ambient wash / spare (set the full gradient) |
107
107
  | `--overlay-background` | `rgb(0 0 0 / .5)` | the scrim of every overlay (Dialog / AlertDialog / Sheet / Drawer) |
108
108
 
109
- ### Focus ring — one definition, three levels of override
109
+ ### Focus ring — THE SWITCH FIRST, then three levels of override
110
110
 
111
- Every keyboard-focus ring in the system is drawn by a single rule (`src/styles/focus-ring.css`) reading the four tokens above. Nothing else paints one; a test fails the build if a stylesheet tries.
111
+ **The indicator ships OFF.** `--focus-outline` is `0`, and every painted focus length multiplies by it, so by default nothing paints a focus mark. That is a product decision with a stated cost — it forfeits WCAG 2.2 SC 2.4.7 (AA) and a JIS X 8341-3 AA claim — recorded in `docs/DESIGN-AUTHORITY.md`. Turning it on is ONE attribute on the root element, no CSS:
112
112
 
113
- That means retuning is a one-liner and it reaches everything:
113
+ ```html
114
+ <html data-focus-outline="on"></html>
115
+ ```
116
+
117
+ (`AppProvider` has the equivalent.) Every `:focus-visible` selector is present either way; only the paint is switched.
118
+
119
+ Every ring is then drawn by a single rule (`src/styles/focus-ring.css`) reading the tokens above. Nothing else paints one; a test fails the build if a stylesheet tries.
120
+
121
+ **Thickness is `--focus-ring-weight`, never `--focus-ring-width`.** The width is DERIVED — `calc(var(--focus-ring-weight) * var(--focus-outline))` — so assigning it directly paints a ring even while the indicator is switched off, and that is exactly what the build-failing test forbids a stylesheet to do.
114
122
 
115
123
  ```css
116
124
  :root {
117
125
  --focus-ring-color: 24 99% 46%; /* every ring, brand orange */
118
- --focus-ring-width: 3px; /* every ring, thicker */
126
+ --focus-ring-weight: var(--stroke-md); /* every ring, 2px */
119
127
  }
120
- ````
128
+ ```
129
+
130
+ **A slim ring that still satisfies the criteria.** Switched on, the shipped weight is one hairline (1px) in the focus hue, measured in Chromium at **5.05:1** light and **7.07:1** dark on every surface a control sits on — so SC 1.4.11 (3:1, non-text contrast) is met on colour, independent of thickness. SC 2.4.13 Focus Appearance (AAA) additionally wants a perimeter of at least 2 CSS px: set `--focus-ring-weight: var(--stroke-md)` (measured: a 2px ring, same hue, same ratio). `var(--stroke-lg)` is the heavy 3px mark.
121
131
 
122
132
  **Per component.** A component that genuinely needs a different ring publishes its own knob and the rule picks it up locally — Toggle and TimeInput ship a heavier, softer ring because they are filled surfaces where a hard 2px reads as a second border:
123
133
 
@@ -136,7 +146,7 @@ That means retuning is a one-liner and it reaches everything:
136
146
  <div style={{ "--focus-ring-color": "0 84% 60%" } as React.CSSProperties}>…</div>
137
147
  ```
138
148
 
139
- **Turning it off** — `--focus-ring-width: 0`, at any of the three levels. It ships **on** (WCAG 2.4.7 / 2.4.11); switching it off is a deliberate act on your side, never a default of this package. Removing the visible focus indicator fails WCAG 2.4.7do it only where another indicator takes over.
149
+ **Turning it back off** — drop the `data-focus-outline` attribute (or set `--focus-outline: 0`), which is the shipped state. Do NOT reach for `--focus-ring-width`: it is derived from the weight and the switch, and pinning it to any value is the same mistake in the other direction. Note the cost either way with no visible focus indicator the package does not meet WCAG 2.2 SC 2.4.7.
140
150
 
141
151
  **Adding your own component to the system**: put `ui-focus-ring` (or `ui-focus-ring-outline` when the mark needs a gap) on the focusable element.
142
152
 
package/docs/DATETIME.md CHANGED
@@ -23,10 +23,18 @@ Omit `locale`, `timezone`, `timeFormat`, `dateFormat` → synced from AppProvide
23
23
 
24
24
  | value | Pattern | Default locale |
25
25
  | ----- | ------------ | -------------- |
26
- | `iso` | `yyyy-MM-dd` | ja |
26
+ | `iso` | `yyyy-MM-dd` | |
27
+ | `ymd` | `yyyy/MM/dd` | ja |
27
28
  | `dmy` | `dd/MM/yyyy` | vi |
28
29
  | `mdy` | `MM/dd/yyyy` | en |
29
30
 
31
+ `ymd` and `iso` are both year-first and are NOT interchangeable: a Japanese business document
32
+ (請求書, 申請書) is written `2026/05/01`, never `2026-05-01`. `ja` defaulted to `iso` until a
33
+ Japanese-first consumer shipped its own date formatter as an explicit stopgap purely to get the
34
+ slashes — rule 1 above broken by this package's own default. Only the DEFAULT moved: a stored
35
+ preference still wins, `defaultDateFormat` still pins any value, and `iso` is one option away in
36
+ `<DateFormatPicker />`.
37
+
30
38
  Sent to backend as `x-date-format`. Pick with `<DateFormatPicker />`.
31
39
 
32
40
  ## kind presets
@@ -43,9 +51,10 @@ Sent to backend as `x-date-format`. Pick with `<DateFormatPicker />`.
43
51
 
44
52
  ## ISO 8601 patterns
45
53
 
46
- Date order follows `dateFormat` from AppProvider (`iso` | `dmy` | `mdy`):
54
+ Date order follows `dateFormat` from AppProvider (`iso` | `ymd` | `dmy` | `mdy`):
47
55
 
48
56
  - `iso`: `yyyy-MM-dd` + `yyyy-MM-dd HH:mm`
57
+ - `ymd`: `yyyy/MM/dd` + `yyyy/MM/dd HH:mm`
49
58
  - `dmy`: `dd/MM/yyyy` + `dd/MM/yyyy HH:mm`
50
59
  - `mdy`: `MM/dd/yyyy` + `MM/dd/yyyy HH:mm`
51
60
 
@@ -253,7 +253,8 @@ a weaker one.** An algorithm guarantees a value is _consistent_; this repo had t
253
253
  times (below) precisely because consistent is not the same as _accessible_. A threshold guarantees
254
254
  the property actually being sold.
255
255
 
256
- **To change a derived value:** change it, run `pnpm test`, and if a threshold breaks the value is
256
+ **To change a derived value:** change it, run the test file that owns the threshold (never the full
257
+ suite — that is CI's job on the PR), and if a threshold breaks the value is
257
258
  wrong. Do not relax the threshold.
258
259
 
259
260
  **The four overrides that existed even while a generator did** — the clearest evidence the
@@ -3,16 +3,27 @@
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: **135**
7
- - With ≥1 frame: **130** · zero-frame: **5**
6
+ - Public components: **150**
7
+ - With ≥1 frame: **134** · zero-frame: **16**
8
8
  - Fully covered (every contract axis declared): **0**
9
9
 
10
10
  ## UNTESTED — no `/frame/**` route
11
11
 
12
12
  - [ ] `inertiaUpload`
13
+ - [ ] `FormFieldControl`
14
+ - [ ] `FormFieldArray`
15
+ - [ ] `useZodForm`
13
16
  - [ ] `CardBar`
14
17
  - [ ] `VisuallyHidden`
15
18
  - [ ] `RangeTimeline`
19
+ - [ ] `ServiceCatalogCta`
20
+ - [ ] `OverlayPortalProvider`
21
+ - [ ] `SkeletonArticle`
22
+ - [ ] `SkeletonAvatar`
23
+ - [ ] `SkeletonButton`
24
+ - [ ] `SkeletonInput`
25
+ - [ ] `SkeletonNode`
26
+ - [ ] `SkeletonImage`
16
27
  - [ ] `ChatBubbleList`
17
28
 
18
29
  ## Per-component contract axes
@@ -22,6 +33,10 @@ Legend: ✓ covered · N/A reasoned skip · · UNTESTED (blank cell = UNTESTED).
22
33
  | Component | Frame | visua | state | owner | conte | respo | inter | acces | prefe | async |
23
34
  | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- |
24
35
  | inertiaUpload | **✗** | · | · | · | · | · | · | · | · | · |
36
+ | FormRoot | ✓ | · | · | · | · | · | · | · | · | · |
37
+ | FormFieldControl | **✗** | · | · | · | · | · | · | · | · | · |
38
+ | FormFieldArray | **✗** | · | · | · | · | · | · | · | · | · |
39
+ | useZodForm | **✗** | · | · | · | · | · | · | · | · | · |
25
40
  | CardBar | **✗** | N/A | N/A | N/A | · | · | · | · | · | · |
26
41
  | TimeRangePicker | ✓ | · | · | · | · | · | · | · | · | · |
27
42
  | VisuallyHidden | **✗** | N/A | N/A | N/A | · | · | · | · | · | · |
@@ -53,8 +68,12 @@ Legend: ✓ covered · N/A reasoned skip · · UNTESTED (blank cell = UNTESTED).
53
68
  | CardContent | ✓ | N/A | N/A | N/A | · | · | · | · | · | · |
54
69
  | StatCard | ✓ | N/A | N/A | · | · | · | · | · | · | · |
55
70
  | ServiceLauncherCard | ✓ | N/A | N/A | N/A | · | · | · | · | · | · |
71
+ | ServiceCatalogCta | **✗** | N/A | N/A | N/A | · | · | · | · | · | · |
56
72
  | Badge | ✓ | ✓ | N/A | N/A | · | · | · | · | · | · |
57
73
  | Legend | ✓ | N/A | N/A | N/A | · | · | · | · | · | · |
74
+ | Swatch | ✓ | N/A | N/A | N/A | · | · | · | · | · | · |
75
+ | FeatureList | ✓ | N/A | N/A | N/A | · | · | · | · | · | · |
76
+ | Thumbnail | ✓ | · | N/A | N/A | · | · | · | · | · | · |
58
77
  | ListRow | ✓ | ✓ | N/A | N/A | · | · | · | · | · | · |
59
78
  | CredentialReveal | ✓ | ✓ | N/A | N/A | · | · | · | · | · | · |
60
79
  | QrCode | ✓ | ✓ | N/A | · | · | · | · | · | · | · |
@@ -95,6 +114,7 @@ Legend: ✓ covered · N/A reasoned skip · · UNTESTED (blank cell = UNTESTED).
95
114
  | Steps | ✓ | ✓ | N/A | · | · | · | · | · | · | · |
96
115
  | Toolbar | ✓ | N/A | · | N/A | · | · | · | · | · | · |
97
116
  | AppProvider | ✓ | · | · | · | · | · | · | · | · | · |
117
+ | OverlayPortalProvider | **✗** | · | · | · | · | · | · | · | · | · |
98
118
  | formatDate | ✓ | · | · | · | · | · | · | · | · | · |
99
119
  | TimePicker | ✓ | · | · | · | · | · | · | · | · | · |
100
120
  | Cascader | ✓ | ✓ | · | · | · | · | · | · | · | · |
@@ -116,7 +136,13 @@ Legend: ✓ covered · N/A reasoned skip · · UNTESTED (blank cell = UNTESTED).
116
136
  | PrefetchLink | ✓ | N/A | N/A | N/A | · | · | · | · | · | · |
117
137
  | Avatar | ✓ | ✓ | N/A | N/A | · | · | · | · | · | · |
118
138
  | Separator | ✓ | · | N/A | N/A | · | · | · | · | · | · |
119
- | Skeleton | ✓ | N/A | N/A | N/A | · | · | · | · | · | · |
139
+ | Skeleton | ✓ | N/A | · | N/A | · | · | · | · | · | · |
140
+ | SkeletonArticle | **✗** | N/A | · | N/A | · | · | · | · | · | · |
141
+ | SkeletonAvatar | **✗** | ✓ | N/A | N/A | · | · | · | · | · | · |
142
+ | SkeletonButton | **✗** | ✓ | N/A | N/A | · | · | · | · | · | · |
143
+ | SkeletonInput | **✗** | ✓ | N/A | N/A | · | · | · | · | · | · |
144
+ | SkeletonNode | **✗** | N/A | N/A | N/A | · | · | · | · | · | · |
145
+ | SkeletonImage | **✗** | N/A | N/A | N/A | · | · | · | · | · | · |
120
146
  | Toggle | ✓ | ✓ | · | N/A | · | · | · | · | · | · |
121
147
  | ToggleGroup | ✓ | ✓ | · | · | · | · | · | · | · | · |
122
148
  | AspectRatio | ✓ | N/A | N/A | N/A | · | · | · | · | · | · |
package/docs/SPACING.md CHANGED
@@ -42,11 +42,11 @@ Implementation: `src/tokens/base.css` (values) · layout owners: `src/styles/*-l
42
42
  | `--card-space-body-y` | `--space-section-active` | Header↔body gap |
43
43
  | `--card-space-header-y` / `--card-space-footer-y` | `--space-stack-sm` | Banded header + separated footer band |
44
44
 
45
- | Component | Use |
46
- | ------------------------- | ------------------------------------------- |
47
- | `StatCard` | KPI / stat tile (`solo` path) |
48
- | `CardContent solo` | Body-only card (same padding as `StatCard`) |
49
- | `CardContent flush tight` | Edge-to-edge table/tabs in card |
45
+ | Component | Use |
46
+ | ------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
47
+ | `StatCard` | KPI / stat tile (`solo` path) |
48
+ | `CardContent solo` | Body-only card (same padding as `StatCard`) |
49
+ | `CardContent flush` | Edge-to-edge table/tabs in card — REQUIRED when the table IS the body (rule 10). `flush` alone drops the border, radius and inline padding; add `tight` only to change the HEADER band. |
50
50
 
51
51
  See preview **Data Display → Card** for live examples.
52
52
 
@@ -64,6 +64,36 @@ Pick by AXIS, and note that the three do **not** share one value: each reads a d
64
64
 
65
65
  Because `ui-card-inset-x` reads `--card-space-inset`, it follows the card it sits in: a `Card density="tight"` or `"cozy"` moves those cells with the shell. The other two read fixed steps (band and body) and do not.
66
66
 
67
+ ## Control interiors are NOT on this scale
68
+
69
+ Everything above is the space **between** things — page sections, siblings in a stack, a card's
70
+ shell against its content. A control's **inside** is a different measurement and it is derived from
71
+ the control band (`--control-height`, `--control-padding-x`), never from `--space-*`. The two do not
72
+ meet, and reading a step off the table above as a minimum for a control's interior gives the wrong
73
+ answer.
74
+
75
+ The case that keeps coming up is `Segmented`'s track (gh#503). Its padding is **2px**, and the
76
+ label inside it therefore sits ~5px from the track's outer edge — under `--space-2` (8px), which is
77
+ NOT a floor this document sets for anything.
78
+
79
+ 2px is the whole geometry of the control:
80
+
81
+ ```
82
+ label height = --control-height − track padding × 2 → 32 − 4 = 28
83
+ ```
84
+
85
+ so the track measures **exactly** `--control-height` and a `Segmented` sits level with the `Input`
86
+ and the `Button` beside it on the same row. Raise the padding to 8px and one of two things has to
87
+ give: either the label band drops to 16px — a 14px type size in a 16px box, under every hit target
88
+ this library holds — or the track grows to 44px (28 + 8 × 2) and stops lining up with every other
89
+ control. The same derivation is why a `Button`'s own label sits ~5px inside its border. A control
90
+ is sized by its band; the band is what a dense enterprise UI is for.
91
+
92
+ **The knob, if a service wants a roomier control.** `--segmented-track-padding` is a published
93
+ component token (`src/tokens/components/segmented.css`); raising it re-derives the item height from
94
+ the same formula, so the track stays exactly one control tall. Do not reach for a Tailwind `p-*` on
95
+ the control — the audit rejects it, and it would break the identity above.
96
+
67
97
  ## MCP
68
98
 
69
99
  `get_pattern page-sections` (a page of Cards, spaced by the page) · `get_rule 40` (mobile-first spacing) · `list_audit_rules` (the spacing rules the CLI enforces).
package/docs/TOKENS.md CHANGED
@@ -220,7 +220,7 @@ The census confirmed the number and rejected the diagnosis: **`width` is not one
220
220
 
221
221
  **Px, never rem.** A stroke is a _device_ line: its job is to read as one crisp rule at any type size, so it must not grow when the root font-size does. Every token that moved onto this scale was already `px` for that reason; a `rem` thickness (`--slider-track-height: 0.375rem`) is a different decision and stayed where it was. **Not `--scaling`-multiplied** either — `--scaling` is a density knob, and a 1px divider at 0.92px would blur rather than tighten.
222
222
 
223
- `--focus-ring-width` is now a member (`var(--stroke-md)`) rather than a parallel authority, so a high-contrast theme that wants every emphasis stroke at 3px sets one token.
223
+ The focus mark reads this scale too, through `--focus-ring-weight` `var(--focus-outline-weight)`, which is `var(--stroke-hairline)` (the light 1px mark the ON state ships). `--focus-ring-width` is DERIVED from it (`weight × --focus-outline`, the on/off switch) and must never be set directly; a theme that wants a 2px focus perimeter (WCAG 2.2 SC 2.4.13) sets `--focus-ring-weight: var(--stroke-md)`. See docs/CUSTOMER-THEMING.md.
224
224
 
225
225
  #### `--band-height-*` — the vertical extent of a horizontal band
226
226
 
@@ -0,0 +1,12 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" width="960" height="540" viewBox="0 0 960 540" role="img">
2
+ <rect width="960" height="540" fill="hsl(210 20% 97%)"/>
3
+ <rect width="960" height="56" fill="hsl(152 34% 38%)"/>
4
+ <rect x="24" y="22" width="140" height="12" rx="6" fill="hsl(152 34% 80%)"/>
5
+ <rect y="56" width="216" height="484" fill="hsl(210 16% 92%)"/>
6
+ <rect x="24" y="92" width="160" height="10" rx="5" fill="hsl(210 12% 76%)"/>
7
+ <rect x="24" y="124" width="160" height="10" rx="5" fill="hsl(210 12% 82%)"/>
8
+ <rect x="24" y="156" width="160" height="10" rx="5" fill="hsl(210 12% 82%)"/>
9
+ <rect x="248" y="92" width="320" height="160" rx="12" fill="hsl(210 16% 88%)"/>
10
+ <rect x="592" y="92" width="320" height="160" rx="12" fill="hsl(210 16% 88%)"/>
11
+ <rect x="248" y="284" width="664" height="212" rx="12" fill="hsl(210 16% 90%)"/>
12
+ </svg>
@@ -0,0 +1,10 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" width="360" height="640" viewBox="0 0 360 640" role="img">
2
+ <rect width="360" height="640" fill="hsl(210 20% 97%)"/>
3
+ <rect width="360" height="72" fill="hsl(204 60% 46%)"/>
4
+ <rect x="20" y="30" width="120" height="12" rx="6" fill="hsl(204 60% 82%)"/>
5
+ <rect x="20" y="104" width="320" height="120" rx="10" fill="hsl(210 16% 88%)"/>
6
+ <rect x="20" y="248" width="320" height="64" rx="10" fill="hsl(210 16% 90%)"/>
7
+ <rect x="20" y="336" width="320" height="64" rx="10" fill="hsl(210 16% 90%)"/>
8
+ <rect x="20" y="424" width="320" height="64" rx="10" fill="hsl(210 16% 90%)"/>
9
+ <rect x="20" y="540" width="320" height="44" rx="10" fill="hsl(204 60% 46%)"/>
10
+ </svg>
@@ -111,6 +111,12 @@ export default function Demo() {
111
111
  </CardHeader>
112
112
  <CardContent>枠線のみ</CardContent>
113
113
  </Card>
114
+ <Card variant="borderless">
115
+ <CardHeader>
116
+ <CardTitle level={2}>borderless</CardTitle>
117
+ </CardHeader>
118
+ <CardContent>塗りのみ · 枠線なし</CardContent>
119
+ </Card>
114
120
  <Card variant="featured">
115
121
  <CardHeader>
116
122
  <CardTitle level={2}>featured</CardTitle>
@@ -120,6 +126,40 @@ export default function Demo() {
120
126
  </ResponsiveGrid>
121
127
  </Flex>
122
128
 
129
+ <Flex direction="col" gap="sm">
130
+ <Text as="div" weight="medium">
131
+ hoverable · actions — Ant Design の hoverable と actions
132
+ </Text>
133
+ <ResponsiveGrid columns={{ sm: 1, md: 2 }}>
134
+ {/*
135
+ * `hoverable` は見た目だけのフラグです。カード全体が押せるように見えるなら、
136
+ * キーボードでも届く本物のコントロールを必ず同居させます(ここでは footer の
137
+ * Button)。div に onClick を置くのは禁止です。
138
+ */}
139
+ <Card hoverable>
140
+ <CardHeader>
141
+ <CardTitle level={2}>ホバーで浮き上がる</CardTitle>
142
+ <CardDescription>--card-hover-shadow まで一段上がります</CardDescription>
143
+ </CardHeader>
144
+ <CardFooter actions>
145
+ <Button variant="ghost">複製</Button>
146
+ <Button variant="ghost">共有</Button>
147
+ <Button variant="ghost">削除</Button>
148
+ </CardFooter>
149
+ </Card>
150
+ <Card hoverable accent="info" accentPlacement="perimeter">
151
+ <CardHeader>
152
+ <CardTitle level={2}>accent と併用</CardTitle>
153
+ <CardDescription>ホバー中もアテンションのリングは消えません</CardDescription>
154
+ </CardHeader>
155
+ <CardFooter actions>
156
+ <Button variant="ghost">承認</Button>
157
+ <Button variant="ghost">却下</Button>
158
+ </CardFooter>
159
+ </Card>
160
+ </ResponsiveGrid>
161
+ </Flex>
162
+
123
163
  <Flex direction="col" gap="sm">
124
164
  <Text as="div" weight="medium">
125
165
  Accent · accentPlacement=&quot;edge&quot; (既定 · 先頭エッジのレール)
@@ -290,13 +290,28 @@ export default function Demo() {
290
290
  </AppProvider>
291
291
  </Flex>
292
292
 
293
- <Flex direction="col" gap="sm">
293
+ <Flex direction="col" gap="sm" id="empty-builtin">
294
294
  <Text as="div" weight="medium">
295
295
  空の状態(data が空のとき自動表示)
296
296
  </Text>
297
297
  <DataTable data={[]} columns={columns} getRowId={(row) => row.id} />
298
298
  </Flex>
299
299
 
300
+ {/* The SAME cell with consumer content in it. `empty` takes a ReactNode, so a plain string
301
+ is a legal call — and the lifecycle cell used to be `padding: 0`, which put that string
302
+ flush against the table's edge. Measured here; see check:data-table-empty-inset. */}
303
+ <Flex direction="col" gap="sm" id="empty-plain-string">
304
+ <Text as="div" weight="medium">
305
+ 空の状態(empty に文字列を渡す)
306
+ </Text>
307
+ <DataTable
308
+ data={[]}
309
+ columns={columns}
310
+ getRowId={(row) => row.id}
311
+ empty="まだ登録がありません"
312
+ />
313
+ </Flex>
314
+
300
315
  {/* — the two failure states, in the same table grid as empty/loading. */}
301
316
  <Flex direction="col" gap="sm">
302
317
  <Text as="div" weight="medium">
@@ -318,7 +333,7 @@ export default function Demo() {
318
333
  <DataTable data={[]} columns={columns} getRowId={(row) => row.id} error />
319
334
  </Flex>
320
335
 
321
- <Flex direction="col" gap="sm">
336
+ <Flex direction="col" gap="sm" id="error-custom-node">
322
337
  <Text as="div" weight="medium">
323
338
  エラー(error にノードを渡して文言を差し替え)
324
339
  </Text>
@@ -0,0 +1,159 @@
1
+ import {
2
+ Card,
3
+ CardContent,
4
+ CardDescription,
5
+ CardFooter,
6
+ CardHeader,
7
+ CardTitle,
8
+ FeatureList,
9
+ } from "@godxjp/ui/data-display";
10
+ import { Button, Text } from "@godxjp/ui/general";
11
+ import { Flex, PageContainer, ResponsiveGrid } from "@godxjp/ui/layout";
12
+
13
+ const STARTER = [
14
+ { state: "included", label: "ユーザー 10 名まで" },
15
+ { state: "included", label: "監査ログ", description: "直近 30 日分を保持します。" },
16
+ {
17
+ state: "limited",
18
+ label: "API 呼び出し",
19
+ description: "月 10,000 回を超えると 429 を返します。",
20
+ },
21
+ { state: "excluded", label: "SAML / OIDC シングルサインオン" },
22
+ { state: "excluded", label: "監査ログのエクスポート" },
23
+ ] as const;
24
+
25
+ const ENTERPRISE = [
26
+ { state: "included", label: "ユーザー数 無制限" },
27
+ { state: "included", label: "監査ログ", description: "保持期間は契約単位で設定します。" },
28
+ { state: "included", label: "API 呼び出し", description: "レート上限は個別に調整します。" },
29
+ {
30
+ state: "included",
31
+ label: "SAML / OIDC シングルサインオン",
32
+ description: "SAML 2.0 と OIDC の両方に対応。",
33
+ },
34
+ { state: "included", label: "監査ログのエクスポート" },
35
+ ] as const;
36
+
37
+ /**
38
+ * FeatureList — 先頭の状態グリフ(含む/含まない/制限あり)を持つ「文」のリスト。
39
+ *
40
+ * ListRow は 1 行のエンティティ行(区切り線・末尾アクション)、Timeline は順序のあるイベント
41
+ * レール、Descriptions は用語/値のグリッド。どれも「折り返す説明文つきの状態リスト」ではない。
42
+ * Composed only from real @godxjp/ui components.
43
+ */
44
+ export default function Demo() {
45
+ return (
46
+ <PageContainer
47
+ title="FeatureList"
48
+ subtitle="状態グリフつきの文のリスト · グリフは最初の行に揃う"
49
+ >
50
+ <Flex direction="col" gap="lg">
51
+ <Card>
52
+ <CardHeader>
53
+ <CardTitle level={2}>三つの状態</CardTitle>
54
+ <CardDescription>
55
+ included(✓)· limited(−)· excluded(✗)。色は意味を運ぶ唯一の手段ではありません ——
56
+ 形が違い、読み上げ用の語も添えられます(WCAG 1.4.1)。excluded を赤で塗らないのは、
57
+ 「含まれない」は失敗ではなく事実だからです。
58
+ </CardDescription>
59
+ </CardHeader>
60
+ <CardContent>
61
+ <FeatureList items={[...STARTER]} />
62
+ </CardContent>
63
+ </Card>
64
+
65
+ <Card>
66
+ <CardHeader>
67
+ <CardTitle level={2}>折り返しても、グリフは最初の行に揃う</CardTitle>
68
+ <CardDescription>
69
+ グリフの枠は 1 行分(1lh)の高さで、その中央にグリフが置かれます。だから 3 行に
70
+ 折り返すラベルでも、揃う先は箱の上端ではなく最初の行です。呼び出し側に数値は
71
+ ありません。
72
+ </CardDescription>
73
+ </CardHeader>
74
+ <CardContent>
75
+ <FeatureList
76
+ items={[
77
+ {
78
+ state: "included",
79
+ label:
80
+ "組織をまたぐ権限の委譲(部門管理者が自部門の範囲内でだけロールを付け替えられる設定を含みます)",
81
+ description:
82
+ "委譲の範囲は組織ツリーのノード単位で決まり、親を超える付与はできません。監査ログには委譲元と委譲先の両方が記録されます。",
83
+ },
84
+ { state: "limited", label: "短いラベル" },
85
+ ]}
86
+ />
87
+ </CardContent>
88
+ </Card>
89
+
90
+ <ResponsiveGrid columns={{ base: 1, md: 2 }}>
91
+ <Card>
92
+ <CardHeader>
93
+ <CardTitle level={2}>Starter</CardTitle>
94
+ <CardDescription>小さなチームのための最小構成。</CardDescription>
95
+ </CardHeader>
96
+ <CardContent>
97
+ <FeatureList items={[...STARTER]} />
98
+ </CardContent>
99
+ <CardFooter>
100
+ <Button variant="outline">Starter を選ぶ</Button>
101
+ </CardFooter>
102
+ </Card>
103
+
104
+ <Card>
105
+ <CardHeader>
106
+ <CardTitle level={2}>Enterprise</CardTitle>
107
+ <CardDescription>監査と SSO を必要とする組織向け。</CardDescription>
108
+ </CardHeader>
109
+ <CardContent>
110
+ <FeatureList items={[...ENTERPRISE]} />
111
+ </CardContent>
112
+ <CardFooter>
113
+ <Button>Enterprise を選ぶ</Button>
114
+ </CardFooter>
115
+ </Card>
116
+ </ResponsiveGrid>
117
+
118
+ <Card>
119
+ <CardHeader>
120
+ <CardTitle level={2}>数量はラベルに組み立てる</CardTitle>
121
+ <CardDescription>
122
+ 「· 10,000 req/mo」のような数量に専用の prop はありません。Text を label に入れる
123
+ 書き方がすでに合法で audit も通るため、prop を足すと docs/WHAT-BELONGS-HERE.md の問い
124
+ 1(consumer に手がないか)に落ちます。
125
+ </CardDescription>
126
+ </CardHeader>
127
+ <CardContent>
128
+ <FeatureList
129
+ items={[
130
+ {
131
+ state: "limited",
132
+ label: (
133
+ <>
134
+ API 呼び出し{" "}
135
+ <Text tone="muted" tabular>
136
+ 10,000 req/mo
137
+ </Text>
138
+ </>
139
+ ),
140
+ },
141
+ {
142
+ state: "included",
143
+ label: (
144
+ <>
145
+ ストレージ{" "}
146
+ <Text tone="muted" tabular>
147
+ 250 GB
148
+ </Text>
149
+ </>
150
+ ),
151
+ },
152
+ ]}
153
+ />
154
+ </CardContent>
155
+ </Card>
156
+ </Flex>
157
+ </PageContainer>
158
+ );
159
+ }