@godxjp/ui 19.4.0 → 19.4.2
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.
- package/dist/components/data-display/badge.d.ts +1 -1
- package/dist/components/data-display/list-row.d.ts +2 -0
- package/dist/components/data-display/list-row.js +16 -12
- package/dist/components/data-display/popover.d.ts +1 -1
- package/dist/components/data-display/table.d.ts +1 -1
- package/dist/components/data-display/timeline-grid.js +5 -3
- package/dist/components/data-entry/checkbox.js +1 -1
- package/dist/components/data-entry/form-field.js +5 -5
- package/dist/components/data-entry/input.js +1 -1
- package/dist/components/data-entry/radio.js +1 -1
- package/dist/components/data-entry/search-input.d.ts +2 -18
- package/dist/components/data-entry/search-input.js +3 -0
- package/dist/components/data-entry/select.js +1 -1
- package/dist/components/general/button.d.ts +2 -0
- package/dist/components/general/button.js +5 -1
- package/dist/components/general/inert-background.js +2 -1
- package/dist/components/layout/app-shell.js +1 -1
- package/dist/components/layout/master-detail.d.ts +1 -1
- package/dist/components/layout/master-detail.js +47 -3
- package/dist/components/layout/mobile-shell.d.ts +1 -1
- package/dist/components/layout/responsive-grid.js +7 -4
- package/dist/components/navigation/app-setting-picker.js +10 -1
- package/dist/email/tokens.generated.d.ts +1 -1
- package/dist/email/tokens.generated.js +1 -1
- package/dist/props/components/data-display.prop.d.ts +1 -1
- package/dist/props/components/data-entry.prop.d.ts +10 -5
- package/dist/props/components/general.prop.d.ts +4 -0
- package/dist/props/components/layout.prop.d.ts +8 -2
- package/dist/props/registry.d.ts +2 -2
- package/dist/props/registry.js +2 -1
- package/dist/props/vocabulary/layout.prop.d.ts +1 -1
- package/dist/styles/control.css +29 -0
- package/dist/styles/data-display-layout.css +22 -1
- package/dist/styles/focus-ring.css +9 -0
- package/dist/styles/form-layout.css +6 -0
- package/dist/styles/layout.css +36 -1
- package/dist/styles/navigation-layout.css +2 -1
- package/dist/tokens/components/data-display.css +2 -1
- package/dist/tokens/foundation.css +2 -1
- package/docs/FRAME-A11Y-CI.md +99 -17
- package/docs/FRAME-COVERAGE-REPORT.md +4 -2
- package/docs/data-display/list-row.tsx +21 -0
- package/docs/data-display/popover.tsx +4 -1
- package/docs/data-entry/date-picker.tsx +2 -1
- package/docs/data-entry/form-field/index.tsx +2 -2
- package/docs/data-entry/search-input.tsx +1 -0
- package/docs/data-entry/select-matrix.tsx +2 -1
- package/docs/data-entry/select.tsx +3 -1
- package/docs/feedback/dialog.tsx +2 -1
- package/docs/feedback/sheet.tsx +2 -1
- package/docs/general/button/index.md +4 -0
- package/docs/layout/app-shell.tsx +4 -4
- package/docs/layout/master-detail.tsx +3 -0
- package/docs/layout/responsive-grid.tsx +21 -4
- package/docs/layout/sidebar.tsx +3 -3
- package/docs/layout/split-pane.tsx +4 -5
- package/docs/navigation/context-menu.tsx +6 -1
- package/docs/navigation/dropdown-menu.tsx +2 -1
- package/docs/navigation/tabs.tsx +2 -2
- package/docs/showcase/case6-agency-handy.tsx +2 -2
- package/docs/showcase/org-switcher.tsx +1 -1
- package/docs/showcase/settings-account-sections.tsx +2 -2
- package/docs/showcase/table-master-detail.tsx +1 -1
- package/docs/showcase/table-view-tabs.tsx +1 -1
- package/package.json +2 -2
package/dist/styles/control.css
CHANGED
|
@@ -2063,3 +2063,32 @@
|
|
|
2063
2063
|
color: var(--button-count-secondary-color, hsl(var(--secondary)));
|
|
2064
2064
|
}
|
|
2065
2065
|
}
|
|
2066
|
+
|
|
2067
|
+
@layer components {
|
|
2068
|
+
.ui-button[data-wrap] {
|
|
2069
|
+
--button-wrap-min-height: var(--control-height);
|
|
2070
|
+
height: auto;
|
|
2071
|
+
min-height: var(--button-wrap-min-height);
|
|
2072
|
+
max-inline-size: 100%;
|
|
2073
|
+
padding-block: var(--space-1);
|
|
2074
|
+
white-space: normal;
|
|
2075
|
+
overflow-wrap: anywhere;
|
|
2076
|
+
}
|
|
2077
|
+
.ui-button[data-wrap][data-size="xs"] {
|
|
2078
|
+
--button-wrap-min-height: var(--control-height-xs);
|
|
2079
|
+
}
|
|
2080
|
+
.ui-button[data-wrap][data-size="sm"] {
|
|
2081
|
+
--button-wrap-min-height: var(--control-height-sm);
|
|
2082
|
+
}
|
|
2083
|
+
.ui-button[data-wrap][data-size="lg"] {
|
|
2084
|
+
--button-wrap-min-height: var(--control-height-lg);
|
|
2085
|
+
}
|
|
2086
|
+
.ui-button[data-align="start"] {
|
|
2087
|
+
justify-content: flex-start;
|
|
2088
|
+
text-align: start;
|
|
2089
|
+
}
|
|
2090
|
+
.ui-button[data-align="end"] {
|
|
2091
|
+
justify-content: flex-end;
|
|
2092
|
+
text-align: end;
|
|
2093
|
+
}
|
|
2094
|
+
}
|
|
@@ -983,7 +983,9 @@
|
|
|
983
983
|
box-sizing: border-box;
|
|
984
984
|
inline-size: calc(100% / var(--timeline-grid-event-lanes, 1));
|
|
985
985
|
block-size: calc(var(--timeline-grid-hour-height) * var(--timeline-grid-event-span, 0));
|
|
986
|
-
min-block-size:
|
|
986
|
+
min-block-size: calc(
|
|
987
|
+
var(--timeline-grid-hour-height) * var(--timeline-grid-event-min-height-minutes) / 60
|
|
988
|
+
);
|
|
987
989
|
padding-inline-end: var(--timeline-grid-event-gap);
|
|
988
990
|
padding-block-end: var(--timeline-grid-event-gap);
|
|
989
991
|
}
|
|
@@ -1064,3 +1066,22 @@
|
|
|
1064
1066
|
pointer-events: none;
|
|
1065
1067
|
}
|
|
1066
1068
|
}
|
|
1069
|
+
|
|
1070
|
+
@layer components {
|
|
1071
|
+
a.ui-list-row {
|
|
1072
|
+
color: inherit;
|
|
1073
|
+
text-decoration: none;
|
|
1074
|
+
cursor: pointer;
|
|
1075
|
+
}
|
|
1076
|
+
a.ui-list-row:hover {
|
|
1077
|
+
background: hsl(var(--muted));
|
|
1078
|
+
}
|
|
1079
|
+
a.ui-list-row[aria-current="page"] {
|
|
1080
|
+
|
|
1081
|
+
background: hsl(var(--accent));
|
|
1082
|
+
box-shadow: inset var(--space-1) 0 0 hsl(var(--primary));
|
|
1083
|
+
}
|
|
1084
|
+
[dir="rtl"] a.ui-list-row[aria-current="page"] {
|
|
1085
|
+
box-shadow: inset calc(-1 * var(--space-1)) 0 0 hsl(var(--primary));
|
|
1086
|
+
}
|
|
1087
|
+
}
|
|
@@ -34,6 +34,7 @@
|
|
|
34
34
|
|
|
35
35
|
:is(
|
|
36
36
|
.ui-focus-ring-outline,
|
|
37
|
+
a.ui-list-row,
|
|
37
38
|
.ui-accordion-trigger,
|
|
38
39
|
.ui-carousel-dot,
|
|
39
40
|
.ui-rating-star,
|
|
@@ -57,6 +58,10 @@
|
|
|
57
58
|
--focus-ring-opacity: var(--topbar-icon-focus-ring-alpha);
|
|
58
59
|
}
|
|
59
60
|
|
|
61
|
+
a.ui-list-row {
|
|
62
|
+
--focus-ring-offset: calc(-1 * var(--focus-ring-width));
|
|
63
|
+
}
|
|
64
|
+
|
|
60
65
|
.ui-accordion-trigger {
|
|
61
66
|
--focus-ring-offset: var(--accordion-focus-ring-offset);
|
|
62
67
|
}
|
|
@@ -78,4 +83,8 @@
|
|
|
78
83
|
outline: none;
|
|
79
84
|
box-shadow: none;
|
|
80
85
|
}
|
|
86
|
+
|
|
87
|
+
[aria-invalid="true"] {
|
|
88
|
+
--focus-ring-color: var(--destructive);
|
|
89
|
+
}
|
|
81
90
|
}
|
package/dist/styles/layout.css
CHANGED
|
@@ -239,7 +239,7 @@
|
|
|
239
239
|
.ui-responsive-grid {
|
|
240
240
|
display: grid;
|
|
241
241
|
gap: var(--space-stack-md);
|
|
242
|
-
grid-template-columns: minmax(0, 1fr);
|
|
242
|
+
grid-template-columns: repeat(var(--responsive-grid-base, 1), minmax(0, 1fr));
|
|
243
243
|
}
|
|
244
244
|
|
|
245
245
|
.ui-responsive-grid[data-gap="none"] {
|
|
@@ -1348,3 +1348,38 @@
|
|
|
1348
1348
|
}
|
|
1349
1349
|
}
|
|
1350
1350
|
}
|
|
1351
|
+
|
|
1352
|
+
@layer components {
|
|
1353
|
+
.ui-master-detail-back {
|
|
1354
|
+
display: none;
|
|
1355
|
+
}
|
|
1356
|
+
.ui-master-detail[data-mobile-pane] {
|
|
1357
|
+
position: relative;
|
|
1358
|
+
}
|
|
1359
|
+
.ui-master-detail-measure {
|
|
1360
|
+
position: absolute;
|
|
1361
|
+
inset-inline: 0;
|
|
1362
|
+
inset-block-start: 0;
|
|
1363
|
+
block-size: 0;
|
|
1364
|
+
overflow: hidden;
|
|
1365
|
+
visibility: hidden;
|
|
1366
|
+
pointer-events: none;
|
|
1367
|
+
}
|
|
1368
|
+
.ui-master-detail-measure > span {
|
|
1369
|
+
display: block;
|
|
1370
|
+
inline-size: var(--master-detail-collapse-below);
|
|
1371
|
+
block-size: 0;
|
|
1372
|
+
}
|
|
1373
|
+
.ui-master-detail[data-mobile-compact][data-mobile-pane="master"] > .ui-master-detail-detail,
|
|
1374
|
+
.ui-master-detail[data-mobile-compact][data-mobile-pane="detail"] > .ui-master-detail-master {
|
|
1375
|
+
display: none;
|
|
1376
|
+
}
|
|
1377
|
+
.ui-master-detail[data-mobile-compact] > .ui-master-detail-master {
|
|
1378
|
+
max-block-size: none;
|
|
1379
|
+
overflow: visible;
|
|
1380
|
+
}
|
|
1381
|
+
.ui-master-detail[data-mobile-compact] .ui-master-detail-back {
|
|
1382
|
+
display: block;
|
|
1383
|
+
margin-block-end: var(--space-3);
|
|
1384
|
+
}
|
|
1385
|
+
}
|
|
@@ -86,7 +86,8 @@
|
|
|
86
86
|
--timeline-grid-hour-height: 2.5rem;
|
|
87
87
|
--timeline-grid-axis-width: 3.25rem;
|
|
88
88
|
--timeline-grid-column-min-width: 6rem;
|
|
89
|
-
|
|
89
|
+
|
|
90
|
+
--timeline-grid-event-min-height-minutes: 36;
|
|
90
91
|
--timeline-grid-axis-font-size: var(--font-size-2xs);
|
|
91
92
|
--timeline-grid-head-font-size: var(--font-size-xs);
|
|
92
93
|
--timeline-grid-event-font-size: var(--font-size-2xs);
|
package/docs/FRAME-A11Y-CI.md
CHANGED
|
@@ -20,11 +20,56 @@ A control the **user cannot bring into the frame** — not merely one whose box
|
|
|
20
20
|
|
|
21
21
|
- `check:contrast` (`scripts/check-contrast.mjs`) — a hand-picked set of ~6 pages checked for WCAG-AA text contrast. It never saw the other ~100 component frames. - `audit:examples` (`pnpm audit docs`) — a static source-regex linter. It can catch a hard-coded hex value but cannot see what actually renders: computed contrast, DOM landmark structure, focus order, or anything a Radix portal produces at runtime.
|
|
22
22
|
|
|
23
|
-
## Chrome vs component:
|
|
23
|
+
## Chrome vs component vs overlay: three independently-tracked scopes
|
|
24
24
|
|
|
25
|
-
`check:frame-axe` runs axe **twice** per frame/viewport:
|
|
25
|
+
`check:frame-axe` runs axe **twice** per frame/viewport, and a **third** time on the frames that declare an open step:
|
|
26
26
|
|
|
27
27
|
- **Preview chrome** = the zoom / dimension **toolbar** (`.demo-block-toolbar`, `preview/src/demo-block.tsx`). We own this. It **must be 0** — a chrome violation fails the build.
|
|
28
|
+
- **Component/demo** = everything else, in the frame's **default** state (the rendered frame _and_ its Radix portals). Allowlisted in `baseline.component`.
|
|
29
|
+
- **Overlay** = the same scope re-measured **after opening an overlay** — see below. Allowlisted separately in `baseline.overlay`.
|
|
30
|
+
|
|
31
|
+
### The overlay scope: opening the thing before measuring it (#355)
|
|
32
|
+
|
|
33
|
+
The gate used to load each frame in its default state and scan once, so every axe rule whose _condition only exists while an overlay is open_ — `aria-hidden-focus` first among them — was **outside its field of view**, not merely un-triggered. 164 frames × 2 viewports reported clean while `aria-hidden-focus` fired on the app background the moment a `Select` or `DropdownMenu` opened in a consumer app.
|
|
34
|
+
|
|
35
|
+
A frame opts in **declaratively**, from the demo itself:
|
|
36
|
+
|
|
37
|
+
```tsx
|
|
38
|
+
// the trigger itself, when it forwards DOM props
|
|
39
|
+
<DropdownMenuTrigger asChild>
|
|
40
|
+
<Button data-axe-open …>…</Button>
|
|
41
|
+
</DropdownMenuTrigger>
|
|
42
|
+
|
|
43
|
+
// the region that owns the trigger, when the component renders it internally
|
|
44
|
+
<CardContent data-axe-open>
|
|
45
|
+
<FormField id="status" label="状態"><Select … /></FormField>
|
|
46
|
+
</CardContent>
|
|
47
|
+
|
|
48
|
+
// right-click, for the one overlay that opens on no other gesture
|
|
49
|
+
<ContextMenuTrigger data-axe-open="contextmenu" …>
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
What the gate does with it, per frame/viewport, **after** the two default-state scans (opening an overlay is destructive to the state those measure):
|
|
53
|
+
|
|
54
|
+
1. Take the **first** `[data-axe-open]` on the page. No declaration ⇒ the frame skips this scope entirely and costs nothing.
|
|
55
|
+
2. Press **Escape** up to three times while any overlay is mounted. Several demos deliberately render one open at rest, and a modal one makes the rest of the page inert — so the trigger is unreachable until it is dismissed. This also makes the measurement a real **closed → open transition** rather than whatever the demo happened to leave mounted.
|
|
56
|
+
3. Resolve the click target: the declaring element itself when it matches `button, [role=button], [role=combobox], [role=menuitem], a[href], summary`, otherwise the first such control inside it. (`DatePicker`, the data-driven `Select` and `SearchSelect` own their trigger DOM and forward no `data-*` to it — hence the "declare on the region" form. `DatePicker`'s combobox `<input>` opens the calendar on click, which is what gets pressed there.)
|
|
57
|
+
4. Click (or right-click), then wait until **one more** overlay is mounted (`[data-radix-popper-content-wrapper], [role=dialog], [role=alertdialog], [role=menu], [role=listbox]`) plus a 400 ms animation settle — never a bare timer.
|
|
58
|
+
5. Re-run axe with the same scope as the component pass and record it under `baseline.overlay`.
|
|
59
|
+
|
|
60
|
+
A declaration whose overlay never opens is an **infrastructure error** (blocking), not a silent skip: a broken declaration must not read as a clean frame.
|
|
61
|
+
|
|
62
|
+
**Frames declaring an open step today** (the overlay families named in #355): `data-entry-select`, `data-entry-select-matrix` (SearchSelect), `data-entry-date-picker`, `data-display-popover`, `navigation-dropdown-menu`, `navigation-context-menu`, `feedback-dialog`, `feedback-sheet`.
|
|
63
|
+
|
|
64
|
+
**Cost**: the overlay pass only runs on declaring frames, so it is ~8 frames × 2 viewports, measured at **+~55 s on a ~9.5 min local sweep (~10 %)**. Both viewports are kept for now; if the declaring set grows, narrow `OVERLAY_VIEWPORTS` in the script to `["desktop"]` — an overlay is portalled to `<body>` and its aria-hidden background is the same tree at either width.
|
|
65
|
+
|
|
66
|
+
**What is still outside this scope** — deliberately, and worth knowing before trusting a green run:
|
|
67
|
+
|
|
68
|
+
- Only the **first** declaration per frame is exercised. A frame with several distinct overlays (the Sheet demo has five) is measured on one of them.
|
|
69
|
+
- Only **one step deep**: submenus (`DropdownMenuSub`), an overlay opened _from inside_ another overlay, and the state _after_ a selection are not reached.
|
|
70
|
+
- **Keyboard-only opening** is not exercised — the gate clicks. A trigger that opens on click but not on `Enter`/`Space` would pass.
|
|
71
|
+
- The overlay's own **focus trap and focus order** are still not asserted by axe here (`focus-order-semantics` needs the focus to actually move); `src/components/__tests__/overlay-hidden-background.a11y.test.tsx` remains the DOM-level guard for the aria-hidden/inert contract, and it is what fails first if the `inert` mirror in `src/components/general/inert-background.ts` regresses in jsdom.
|
|
72
|
+
- Frames with **no declaration** (156 of 164) are unchanged: default state only.
|
|
28
73
|
|
|
29
74
|
## The component allowlist — and how it shrinks to zero
|
|
30
75
|
|
|
@@ -43,6 +88,10 @@ git add scripts/frame-axe.baseline.json && git commit -m "chore(a11y): tighten f
|
|
|
43
88
|
|
|
44
89
|
There is **no blanket suppression**: a documented axe false positive would be an explicit per-rule entry with a comment, never a mute.
|
|
45
90
|
|
|
91
|
+
`baseline.overlay` is the same shape and obeys the same rule, for the overlay-open scope. It is only rewritten for frames whose open step **actually ran** — a frame that stopped declaring one has not been proven clean, it has been left unmeasured.
|
|
92
|
+
|
|
93
|
+
**Keys that are not violation data are preserved (#356).** The baseline also carries human notes — `scopeGap` records what the gate cannot see and which violations are deliberately unfixed. `--update-baseline` used to rebuild the file from scratch and delete them without a word. It now regenerates only the keys it owns (`generatedAt`, `note`, `component`, `overlay`), carries every other key over verbatim, and **names them in its log line** (`carried over: scopeGap`) so a disappearing key cannot go unnoticed.
|
|
94
|
+
|
|
46
95
|
`0` chrome violations, `0` regressions. See [Remaining baseline debt](#remaining-baseline-debt--the-12-frames) below for exactly what's left and the path to zero.
|
|
47
96
|
|
|
48
97
|
## Running it locally
|
|
@@ -68,28 +117,47 @@ pnpm exec playwright install chromium # local dev — downloads Playwrigh
|
|
|
68
117
|
| `pnpm check:frame-axe --format json` | Machine-readable result on stdout (same exit-code semantics) — no colored human report |
|
|
69
118
|
| `pnpm check:frame-axe --update-baseline` | Regenerate the allowlist (2-pass union by default) — see below |
|
|
70
119
|
| `AXE_RUNS=3 pnpm check:frame-axe --update-baseline` | More union passes, for a component with flaky portal timing |
|
|
120
|
+
| `AXE_SHARD=2/4 pnpm check:frame-axe` | Run only shard 2 of 4 (1-based) for CI fan-out — see below |
|
|
71
121
|
| `pnpm check:frame-axe http://localhost:6008` | Point at an already-running preview instead of building+serving one |
|
|
72
122
|
|
|
123
|
+
#### Sharding the sweep (`AXE_SHARD=i/n`)
|
|
124
|
+
|
|
125
|
+
`AXE_SHARD=2/4` runs only the frames of shard 2 of 4. The partition is taken over frame ids sorted **by code point**, not in manifest/directory order, so the same tree always yields the same partition, every frame lands in exactly one shard, and the union of all `n` shards is exactly the full sweep. Unset (the default) leaves the frame list untouched — sharding changes nothing until you ask for it.
|
|
126
|
+
|
|
127
|
+
Two things are deliberately _not_ the same under a shard, because a shard cannot know them:
|
|
128
|
+
|
|
129
|
+
- **"Baseline rule no longer fires" (shrink hints) is skipped.** The baseline is keyed per frame, so _new_ rule detection stays exact on a partial run — but a frame this shard never visited looks identical to a frame that got fixed. Only a full sweep can tell those apart.
|
|
130
|
+
- **`--update-baseline` refuses to run** with `AXE_SHARD` set (exit 2). Writing the baseline from a fraction of the frames would delete every frame the shard did not visit.
|
|
131
|
+
|
|
132
|
+
`AXE_FRAMES_LIMIT` truncates the list instead of partitioning it; it is a local smoke-test knob, not a fan-out mechanism.
|
|
133
|
+
|
|
73
134
|
If no preview server answers at the base URL (`http://localhost:6008` by default), the script builds one itself: `pnpm preview:build` then serves the static output with `vite preview` (`ensurePreviewServer` in `scripts/frame-harness.mjs`) — deterministic and stable under a long headless sweep, unlike the dev server's per-request recompilation. If a `pnpm preview` dev server (or a remote base) is already reachable, it's reused as-is and never rebuilt.
|
|
74
135
|
|
|
75
136
|
### Reading the output
|
|
76
137
|
|
|
77
|
-
Terminal report (human mode) has
|
|
138
|
+
Terminal report (human mode) has three blocks:
|
|
78
139
|
|
|
79
140
|
```
|
|
80
141
|
Preview-chrome axe (blocking — must be 0):
|
|
81
|
-
✓ 0 chrome violations across all
|
|
142
|
+
✓ 0 chrome violations across all 164 frame(s).
|
|
82
143
|
|
|
83
144
|
Component/demo axe (allowlisted — baseline may only shrink):
|
|
84
|
-
|
|
145
|
+
0 component violation node(s) remaining across 0 frame(s).
|
|
85
146
|
✓ no new component violation types (rule-set within baseline).
|
|
147
|
+
|
|
148
|
+
Overlay axe (frames declaring data-axe-open — baseline may only shrink):
|
|
149
|
+
8 frame(s) opened an overlay before scanning; 128 violation node(s) while open.
|
|
150
|
+
✓ no new overlay violation types (rule-set within baseline).
|
|
86
151
|
```
|
|
87
152
|
|
|
88
153
|
- A **red `✗ <rule>`** under chrome is always blocking.
|
|
89
154
|
- A **red `✗ N NEW violation-type(s) not in baseline`** under component means a frame gained a
|
|
90
155
|
rule the baseline didn't have — that's the actual CI failure mode for a component regression.
|
|
156
|
+
- A **red `✗ N NEW violation-type(s) not in the overlay baseline`** is the same failure for the
|
|
157
|
+
overlay-open state — that is what turns red when, say, the `inert` background patch regresses
|
|
158
|
+
and `aria-hidden-focus` comes back.
|
|
91
159
|
- A **yellow `↓ shrink hint`** means a baseline rule no longer fires anywhere it's listed — free
|
|
92
|
-
baseline tightening available via `--update-baseline`.
|
|
160
|
+
baseline tightening available via `--update-baseline`. Not emitted under `AXE_SHARD`.
|
|
93
161
|
|
|
94
162
|
### Evidence JSON
|
|
95
163
|
|
|
@@ -103,19 +171,27 @@ Shape:
|
|
|
103
171
|
"base": "http://localhost:6008",
|
|
104
172
|
"summary": {
|
|
105
173
|
"status": "ok", // "ok" | "fail"
|
|
106
|
-
"frames":
|
|
174
|
+
"frames": 164,
|
|
175
|
+
"shard": null, // "i/n" when AXE_SHARD is set
|
|
107
176
|
"viewports": ["desktop", "mobile"],
|
|
108
177
|
"chromeViolations": 0, // total chrome violation NODE count
|
|
109
|
-
"componentViolations":
|
|
178
|
+
"componentViolations": 0, // total component violation NODE count (reporting only)
|
|
110
179
|
"componentRegressions": 0, // rules present now but not in the baseline
|
|
180
|
+
"overlayFrames": 8, // frames whose declared open step ran
|
|
181
|
+
"overlayViolations": 128, // total NODE count measured with an overlay open
|
|
182
|
+
"overlayRegressions": 0, // overlay rules present now but not in baseline.overlay
|
|
111
183
|
"infrastructureErrors": 0, // frames that failed to load / axe crashed
|
|
112
184
|
},
|
|
113
185
|
"chrome": {/* rule → { impact, nodes, frames: ["<frameId>@<viewport>", …] } */},
|
|
114
186
|
"componentCurrent": {/* frameId → viewport → ruleId → node count, this run */},
|
|
115
187
|
"regressions": [/* { frame, rule } — new rules, the actual gate failure */],
|
|
116
188
|
"shrinkHints": [/* { frame, rule } — baseline rules that no longer fire */],
|
|
117
|
-
"
|
|
118
|
-
"
|
|
189
|
+
"overlayCurrent": {/* frameId → viewport → ruleId → node count, with the overlay OPEN */},
|
|
190
|
+
"overlayFrames": [/* frame ids whose declared open step ran */],
|
|
191
|
+
"overlayRegressions": [/* { frame, rule } — new rules in the overlay-open state */],
|
|
192
|
+
"overlayShrinkHints": [/* { frame, rule } */],
|
|
193
|
+
"infraErrors": [/* { frame, viewport, message } — includes "overlay open: …" failures */],
|
|
194
|
+
"results": [/* per-frame { id, viewports: { desktop: { chrome, component, overlay? }, … } } */],
|
|
119
195
|
}
|
|
120
196
|
```
|
|
121
197
|
|
|
@@ -140,6 +216,8 @@ Playwright Inspector / browser devtools with the `axe-core` extension against th
|
|
|
140
216
|
violation, not fixed an old one).
|
|
141
217
|
4. Commit the baseline alongside the fix in the same PR.
|
|
142
218
|
|
|
219
|
+
`--update-baseline` also preserves every non-violation key in the file verbatim (see #356 above) and refuses to run under `AXE_SHARD`.
|
|
220
|
+
|
|
143
221
|
`AXE_FRAMES_LIMIT` + `--update-baseline` together only touch the frames actually run — the merge
|
|
144
222
|
logic in `check-frame-axe.mjs` overwrites `runFrameIds` and leaves every other frame's baseline
|
|
145
223
|
entry untouched, so a scoped re-snapshot (e.g. after fixing just `Select`) never silently drops
|
|
@@ -258,10 +336,14 @@ Before opening a PR that adds or meaningfully changes a `/frame/**` example:
|
|
|
258
336
|
8. If you do land a frame with a tracked violation, add it to `scripts/frame-axe.baseline.json` via
|
|
259
337
|
`--update-baseline` (never hand-edit the JSON) and note _why_ + the follow-up issue in the PR
|
|
260
338
|
description — the baseline file's own `note` field explains it may only shrink from here.
|
|
261
|
-
9.
|
|
262
|
-
|
|
263
|
-
a
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
339
|
+
9. **If the frame demos an overlay, declare its open step**: `data-axe-open` on the trigger (or on
|
|
340
|
+
the region that owns it when the component renders the trigger itself), `data-axe-open="contextmenu"`
|
|
341
|
+
for a context menu. Without it the frame is only ever measured closed, and the whole
|
|
342
|
+
`aria-hidden-focus` family of rules cannot fire — see [the overlay scope](#the-overlay-scope-opening-the-thing-before-measuring-it-355).
|
|
343
|
+
10. Register the case you authored in `preview/frame-coverage.ledger.json` so
|
|
344
|
+
`check:frame-coverage-ledger` stops reporting that dimension `UNTESTED`. You **cannot** hand-write
|
|
345
|
+
a verdict — add an entry to the ledger's `cases` array (frame path + case heading + resolvable
|
|
346
|
+
evidence paths + reviewer + HTTPS review link + ISO timestamp) and run
|
|
347
|
+
`pnpm gen:frame-coverage-ledger`; the gate recomputes every cell from that evidence and rejects
|
|
348
|
+
any verdict it cannot reproduce. See [FRAME-COVERAGE-LEDGER.md](./FRAME-COVERAGE-LEDGER.md) and
|
|
349
|
+
[FRAME-COVERAGE-STANDARD.md](./FRAME-COVERAGE-STANDARD.md).
|
|
@@ -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: **
|
|
7
|
-
- With ≥1 frame: **
|
|
6
|
+
- Public components: **126**
|
|
7
|
+
- With ≥1 frame: **126** · zero-frame: **0**
|
|
8
8
|
- Fully covered (every contract axis declared): **0**
|
|
9
9
|
|
|
10
10
|
## Per-component contract axes
|
|
@@ -19,6 +19,7 @@ Legend: ✓ covered · N/A reasoned skip · · UNTESTED (blank cell = UNTESTED).
|
|
|
19
19
|
| AppShell | ✓ | N/A | N/A | N/A | · | · | · | · | · | · |
|
|
20
20
|
| AuthShell | ✓ | ✓ | N/A | N/A | · | · | · | · | · | · |
|
|
21
21
|
| CenteredShell | ✓ | N/A | N/A | N/A | · | · | · | · | · | · |
|
|
22
|
+
| MobileShell | ✓ | N/A | N/A | N/A | · | · | · | · | · | · |
|
|
22
23
|
| Sidebar | ✓ | N/A | N/A | N/A | · | · | · | · | · | · |
|
|
23
24
|
| Topbar | ✓ | N/A | N/A | N/A | · | · | · | · | · | · |
|
|
24
25
|
| MasterDetail | ✓ | N/A | N/A | N/A | · | · | · | · | · | · |
|
|
@@ -47,6 +48,7 @@ Legend: ✓ covered · N/A reasoned skip · · UNTESTED (blank cell = UNTESTED).
|
|
|
47
48
|
| CodeBlock | ✓ | · | N/A | N/A | · | · | · | · | · | · |
|
|
48
49
|
| Prose | ✓ | · | N/A | N/A | · | · | · | · | · | · |
|
|
49
50
|
| Timeline | ✓ | ✓ | N/A | N/A | · | · | · | · | · | · |
|
|
51
|
+
| TimelineGrid | ✓ | N/A | N/A | N/A | · | · | · | · | · | · |
|
|
50
52
|
| Table | ✓ | N/A | N/A | N/A | · | · | · | · | · | · |
|
|
51
53
|
| DataState | ✓ | N/A | · | N/A | · | · | · | · | · | · |
|
|
52
54
|
| InfiniteQueryState | ✓ | N/A | · | N/A | · | · | · | · | · | · |
|
|
@@ -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>
|
|
@@ -135,7 +135,10 @@ export default function Demo() {
|
|
|
135
135
|
<Flex direction="row" wrap gap="sm">
|
|
136
136
|
<Popover>
|
|
137
137
|
<PopoverTrigger asChild>
|
|
138
|
-
|
|
138
|
+
{/* data-axe-open: opened by check-frame-axe before its overlay scan. */}
|
|
139
|
+
<Button data-axe-open variant="outline">
|
|
140
|
+
side=top
|
|
141
|
+
</Button>
|
|
139
142
|
</PopoverTrigger>
|
|
140
143
|
<PopoverContent side="top" aria-label="上側の配置例">
|
|
141
144
|
<PopoverDescription>上に開きます。</PopoverDescription>
|
|
@@ -33,7 +33,8 @@ export default function Demo() {
|
|
|
33
33
|
形式に整形され、 解釈できない入力は元の値へ復帰する。
|
|
34
34
|
</CardDescription>
|
|
35
35
|
</CardHeader>
|
|
36
|
-
|
|
36
|
+
{/* data-axe-open: check-frame-axe opens the calendar before scanning. */}
|
|
37
|
+
<CardContent data-axe-open>
|
|
37
38
|
<FormField id="issue-date" label="発行日" required>
|
|
38
39
|
<DatePicker
|
|
39
40
|
id="issue-date"
|
|
@@ -130,7 +130,7 @@ export default function Demo() {
|
|
|
130
130
|
|
|
131
131
|
<Card>
|
|
132
132
|
<CardHeader>
|
|
133
|
-
<CardTitle level={2}>Field identity · data-field
|
|
133
|
+
<CardTitle level={2}>Field identity · data-field</CardTitle>
|
|
134
134
|
<CardDescription>
|
|
135
135
|
項目キーを control の data-field に出す。既定は id から導出。id が DOM
|
|
136
136
|
一意化のためのもので項目キーと違う場合だけ field を明示する。AppProvider
|
|
@@ -176,7 +176,7 @@ export default function Demo() {
|
|
|
176
176
|
|
|
177
177
|
<Card>
|
|
178
178
|
<CardHeader>
|
|
179
|
-
<CardTitle level={2}>Static value · mixed with editable fields
|
|
179
|
+
<CardTitle level={2}>Static value · mixed with editable fields</CardTitle>
|
|
180
180
|
<CardDescription>
|
|
181
181
|
staticText renders a read-only VALUE as a FormField itself: Descriptions.Item's exact
|
|
182
182
|
value typography, no control, no id/aria-* wiring. An immutable name/email row and an
|
|
@@ -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.
|
|
@@ -142,7 +142,8 @@ export default function Demo() {
|
|
|
142
142
|
references. The internal search textbox has its own visible-purpose accessible name.
|
|
143
143
|
</CardDescription>
|
|
144
144
|
</CardHeader>
|
|
145
|
-
|
|
145
|
+
{/* data-axe-open: check-frame-axe opens the searchable panel before scanning. */}
|
|
146
|
+
<CardContent data-axe-open>
|
|
146
147
|
<FormField
|
|
147
148
|
id="searchable-invalid"
|
|
148
149
|
label="担当拠点"
|
|
@@ -82,7 +82,9 @@ export default function Demo() {
|
|
|
82
82
|
Pass an options array; name= submits the value with the form.
|
|
83
83
|
</CardDescription>
|
|
84
84
|
</CardHeader>
|
|
85
|
-
|
|
85
|
+
{/* data-axe-open: check-frame-axe opens this Select before scanning, so the
|
|
86
|
+
overlay-open a11y state is measured. See docs/FRAME-A11Y-CI.md. */}
|
|
87
|
+
<CardContent data-axe-open>
|
|
86
88
|
<FormField id="status" label="状態">
|
|
87
89
|
<Select
|
|
88
90
|
id="status"
|
package/docs/feedback/dialog.tsx
CHANGED
|
@@ -109,7 +109,8 @@ export default function Demo() {
|
|
|
109
109
|
<CardContent>
|
|
110
110
|
<Dialog open={detailOpen} onOpenChange={setDetailOpen}>
|
|
111
111
|
<DialogTrigger asChild>
|
|
112
|
-
|
|
112
|
+
{/* data-axe-open: opened by check-frame-axe before its overlay scan. */}
|
|
113
|
+
<Button data-axe-open variant="outline" size="sm">
|
|
113
114
|
取引詳細を見る
|
|
114
115
|
</Button>
|
|
115
116
|
</DialogTrigger>
|
package/docs/feedback/sheet.tsx
CHANGED
|
@@ -213,7 +213,8 @@ export default function Demo() {
|
|
|
213
213
|
<CardContent>
|
|
214
214
|
<Sheet open={editOpen} onOpenChange={setEditOpen}>
|
|
215
215
|
<SheetTrigger asChild>
|
|
216
|
-
|
|
216
|
+
{/* data-axe-open: opened by check-frame-axe before its overlay scan. */}
|
|
217
|
+
<Button data-axe-open variant="ghost" size="sm">
|
|
217
218
|
取引先を編集
|
|
218
219
|
</Button>
|
|
219
220
|
</SheetTrigger>
|
|
@@ -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.
|
|
@@ -282,8 +282,8 @@ export default function Demo() {
|
|
|
282
282
|
を切り替え、sidebar・topbar・page actions・grid・split pane
|
|
283
283
|
の変換を同じ実用構成で確認する。900px 以下ではドックされたサイドバーが隠れ、
|
|
284
284
|
AppShell 所有のモバイルドロワー(トップバー左のハンバーガー → フォーカストラップ付き
|
|
285
|
-
Sheet · Esc で閉じてトリガーへフォーカス復帰)が navigation
|
|
286
|
-
|
|
285
|
+
Sheet · Esc で閉じてトリガーへフォーカス復帰)が navigation を提供する。 このデモは
|
|
286
|
+
mobileNav を渡していないため、同じ Sidebar がドロワーに再利用される。
|
|
287
287
|
</CardDescription>
|
|
288
288
|
<CardDescription>
|
|
289
289
|
「トップバーなし(チャット型)」を押すと topbar / topbarLeft / topbarRight / logo の
|
|
@@ -297,8 +297,8 @@ export default function Demo() {
|
|
|
297
297
|
で代用しないこと ― それは 4 スロットが埋まった状態と同じで、バーの行は残る。
|
|
298
298
|
</CardDescription>
|
|
299
299
|
<CardDescription>
|
|
300
|
-
横方向のページ余白軸の持ち主は <code>--space-page-x</code>
|
|
301
|
-
|
|
300
|
+
横方向のページ余白軸の持ち主は <code>--space-page-x</code> ひとつ。 トップバーは{" "}
|
|
301
|
+
<code>.app-main</code> と同じグリッド列に載っている ―
|
|
302
302
|
つまりバー先頭のコントロールとページタイトルは同じ縦線に並ぶべきもので、
|
|
303
303
|
<code>--app-shell-bar-inset</code> はその持ち主を読む (
|
|
304
304
|
<code>--app-shell-bar-inset-compact</code> も同様に
|
|
@@ -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" (先頭ナビゲーションレール)、
|
|
@@ -16,7 +16,7 @@ import { Flex, PageContainer, ResponsiveGrid, SplitPane } from "@godxjp/ui/layou
|
|
|
16
16
|
* with no external container-type declaration. Direct children are typically
|
|
17
17
|
* StatCard (self-contained bordered card · never wrap in Card/CardContent) or
|
|
18
18
|
* Card+CardContent for richer tile bodies. columns accepts a number OR breakpoint
|
|
19
|
-
* object { sm?, md?, lg? } — or use the named `preset` prop (e.g. "pricing-plans") for a
|
|
19
|
+
* object { base?, sm?, md?, lg? } — or use the named `preset` prop (e.g. "pricing-plans") for a
|
|
20
20
|
* recognised collection shape instead of hand-rolling the breakpoint map. Composed only from
|
|
21
21
|
* real @godxjp/ui components.
|
|
22
22
|
*/
|
|
@@ -27,6 +27,23 @@ export default function Demo() {
|
|
|
27
27
|
subtitle="等幅タイルグリッド · KPI・カード比較・ダッシュボード行"
|
|
28
28
|
>
|
|
29
29
|
<Flex direction="col" gap="lg">
|
|
30
|
+
<Card>
|
|
31
|
+
<CardHeader>
|
|
32
|
+
<CardTitle level={2}>Compact mobile metrics</CardTitle>
|
|
33
|
+
<CardDescription>
|
|
34
|
+
columns={{ base: 2, sm: 4 }} keeps two columns below the first
|
|
35
|
+
container breakpoint and four above it.
|
|
36
|
+
</CardDescription>
|
|
37
|
+
</CardHeader>
|
|
38
|
+
<CardContent>
|
|
39
|
+
<ResponsiveGrid columns={{ base: 2, sm: 4 }}>
|
|
40
|
+
<StatCard label="Services" value="3" />
|
|
41
|
+
<StatCard label="Members" value="14" />
|
|
42
|
+
<StatCard label="Invitations" value="0" />
|
|
43
|
+
<StatCard label="Invoices" value="2" />
|
|
44
|
+
</ResponsiveGrid>
|
|
45
|
+
</CardContent>
|
|
46
|
+
</Card>
|
|
30
47
|
<Card>
|
|
31
48
|
<CardHeader>
|
|
32
49
|
<CardTitle level={2}>コンテナクエリで折り返し(自己所有のクエリコンテナ)</CardTitle>
|
|
@@ -35,8 +52,8 @@ export default function Demo() {
|
|
|
35
52
|
列、右の狭いサイドバー(約 20rem)は 1 列に折り返す。ビューポート幅は同一なので、
|
|
36
53
|
折り返しの基準がコンテナ幅であることが分かる。ResponsiveGrid は自身のクエリコンテナ
|
|
37
54
|
(container-type: inline-size)を持つため、外側に @container を用意しなくても正しく
|
|
38
|
-
|
|
39
|
-
|
|
55
|
+
折り返す。閾値はコンテナ幅 40 / 48 / 64rem。 列間の gap は var(--space-stack-md)
|
|
56
|
+
が既定で、gap prop で変更できます。
|
|
40
57
|
</CardDescription>
|
|
41
58
|
</CardHeader>
|
|
42
59
|
<CardContent>
|
|
@@ -72,7 +89,7 @@ export default function Demo() {
|
|
|
72
89
|
<CardDescription>
|
|
73
90
|
固定幅の親の中でも、同じ columns={4} が親幅で列数を変える。親には
|
|
74
91
|
container-type を付けていない。ResponsiveGrid が自身のスコープでクエリするため、
|
|
75
|
-
外側にコンテナ宣言がなくても 1
|
|
92
|
+
外側にコンテナ宣言がなくても 1 列に潰れない。
|
|
76
93
|
</CardDescription>
|
|
77
94
|
</CardHeader>
|
|
78
95
|
<CardContent>
|