@godxjp/ui 24.0.0 → 25.0.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.
- package/dist/components/data-display/badge.js +3 -0
- package/dist/components/data-entry/select.js +7 -0
- package/dist/components/general/logo.d.ts +16 -0
- package/dist/components/general/logo.js +10 -2
- package/dist/components/layout/auth-identity.d.ts +1 -1
- package/dist/components/layout/auth-identity.js +5 -2
- package/dist/components/navigation/dropdown-menu.js +19 -2
- package/dist/components/navigation/tabs.js +24 -6
- package/dist/contracts/measurement.json +1 -1
- package/dist/email/tokens.generated.d.ts +1 -1
- package/dist/email/tokens.generated.js +1 -1
- package/dist/props/components/layout.prop.d.ts +32 -1
- package/dist/props/registry.d.ts +4 -0
- package/dist/props/registry.js +5 -0
- package/dist/props/vocabulary/interaction.prop.d.ts +10 -1
- package/dist/styles/control.css +5 -1
- package/dist/styles/data-display-layout.css +16 -5
- package/dist/styles/data-entry-layout.css +5 -0
- package/dist/styles/layout.css +5 -0
- package/dist/styles/logo-layout.css +30 -3
- package/dist/styles/motion.css +4 -0
- package/dist/styles/shell-layout.css +22 -6
- package/dist/styles/text-layout.css +5 -0
- package/dist/tokens/components/logo.css +8 -0
- package/dist/tokens/components/shell.css +7 -3
- package/dist/tokens/derived.css +8 -8
- package/dist/tokens/foundation.css +2 -2
- package/docs/DESIGN-AUTHORITY.md +36 -22
- package/docs/DEVELOPMENT.md +20 -0
- package/docs/FRAME-A11Y-CI.md +164 -0
- package/docs/foundation/colors.tsx +4 -1
- package/docs/general/logo.tsx +63 -0
- package/docs/general/typography.tsx +31 -0
- package/docs/layout/auth-identity.tsx +34 -0
- package/docs/layout/topbar.tsx +19 -8
- package/docs/showcase/acme-website.tsx +3 -1
- package/docs/showcase/case4-login.tsx +2 -2
- package/docs/showcase/case5-shift-calendar.tsx +5 -3
- package/docs/showcase/case6-agency-handy.tsx +13 -9
- package/docs/showcase/futurelastic-web.tsx +11 -3
- package/docs/showcase/table-bulk-actions.tsx +13 -2
- package/docs/showcase/table-density.tsx +3 -1
- package/docs/showcase/table-expandable-rows.tsx +25 -15
- package/docs/showcase/table-grouped-subtotals.tsx +5 -1
- package/package.json +4 -2
|
@@ -126,8 +126,10 @@ function ShiftPill({
|
|
|
126
126
|
<Text as="span" size="2xs" weight="medium">
|
|
127
127
|
{meta.label}
|
|
128
128
|
</Text>
|
|
129
|
+
{/* `inherit`, not `muted` (gh#643): the chip is a TINTED surface, and `muted` is an absolute
|
|
130
|
+
token that measured 4.46:1 on it. Inheriting lets the chip's own ink win. */}
|
|
129
131
|
{staff ? (
|
|
130
|
-
<Text as="span" size="2xs" tone="
|
|
132
|
+
<Text as="span" size="2xs" tone="inherit" truncate>
|
|
131
133
|
{staff}
|
|
132
134
|
</Text>
|
|
133
135
|
) : null}
|
|
@@ -433,7 +435,7 @@ export default function ShiftCalendarShowcase() {
|
|
|
433
435
|
<Text size="xs" weight="medium" className="whitespace-nowrap">
|
|
434
436
|
{SHIFT_META[k].label}
|
|
435
437
|
</Text>
|
|
436
|
-
<Text size="xs" tone="
|
|
438
|
+
<Text size="xs" tone="inherit" tabular className="whitespace-nowrap">
|
|
437
439
|
{SHIFT_META[k].time}
|
|
438
440
|
</Text>
|
|
439
441
|
</Badge>
|
|
@@ -582,7 +584,7 @@ function MonthGrid({ onPick }: { onPick: (d: DayCell) => void }) {
|
|
|
582
584
|
tone="destructive"
|
|
583
585
|
variant="outline"
|
|
584
586
|
shape="sharp"
|
|
585
|
-
className="text-[var(--font-size-2xs)]"
|
|
587
|
+
className="text-[length:var(--font-size-2xs)]"
|
|
586
588
|
>
|
|
587
589
|
祝
|
|
588
590
|
</Badge>
|
|
@@ -368,7 +368,7 @@ function TabBar({ active, onChange }: { active: string; onChange: (id: string) =
|
|
|
368
368
|
onClick={() => onChange(t.id)}
|
|
369
369
|
aria-current={isActive ? "page" : undefined}
|
|
370
370
|
className={
|
|
371
|
-
"h-full flex-col rounded-none
|
|
371
|
+
"h-full flex-col rounded-none text-[length:var(--font-size-2xs)] font-medium " +
|
|
372
372
|
(isActive ? "text-primary" : "text-muted-foreground")
|
|
373
373
|
}
|
|
374
374
|
>
|
|
@@ -637,12 +637,16 @@ function InboundTab({
|
|
|
637
637
|
<ToggleGroupItem
|
|
638
638
|
key={f.id}
|
|
639
639
|
value={f.id}
|
|
640
|
-
className="h-9 shrink-0 rounded-full
|
|
640
|
+
className="h-9 shrink-0 rounded-full text-[length:var(--font-size-xs)] whitespace-nowrap"
|
|
641
|
+
// The library's OWN counter pill, not a `Text` at 70% opacity (gh#643). The
|
|
642
|
+
// hand-rolled one measured 2.15:1 on the selected chip's --primary fill, because
|
|
643
|
+
// `opacity-70` blends whatever ink it inherits back towards the fill underneath it —
|
|
644
|
+
// `tone="inherit"` cannot help with that. `.ui-toggle[data-state="on"]
|
|
645
|
+
// .ui-toggle-count` inverts instead (--primary on --primary-foreground, 6.32:1) and
|
|
646
|
+
// folds the number into the accessible name.
|
|
647
|
+
count={f.count}
|
|
641
648
|
>
|
|
642
649
|
{f.label}
|
|
643
|
-
<Text size="xs" mono tabular className="opacity-70">
|
|
644
|
-
{f.count}
|
|
645
|
-
</Text>
|
|
646
650
|
</ToggleGroupItem>
|
|
647
651
|
))}
|
|
648
652
|
</Flex>
|
|
@@ -848,19 +852,19 @@ function OutboundTab({ seg, setSeg }: { seg: string; setSeg: (v: string) => void
|
|
|
848
852
|
<ResponsiveGrid columns={3} gap="xs">
|
|
849
853
|
<ToggleGroupItem
|
|
850
854
|
value="seal"
|
|
851
|
-
className="h-9 rounded-xl
|
|
855
|
+
className="h-9 rounded-xl text-[length:var(--font-size-xs)] whitespace-nowrap"
|
|
852
856
|
>
|
|
853
857
|
Chờ niêm phong
|
|
854
858
|
</ToggleGroupItem>
|
|
855
859
|
<ToggleGroupItem
|
|
856
860
|
value="handoff"
|
|
857
|
-
className="h-9 rounded-xl
|
|
861
|
+
className="h-9 rounded-xl text-[length:var(--font-size-xs)] whitespace-nowrap"
|
|
858
862
|
>
|
|
859
863
|
Chờ bàn giao
|
|
860
864
|
</ToggleGroupItem>
|
|
861
865
|
<ToggleGroupItem
|
|
862
866
|
value="done"
|
|
863
|
-
className="h-9 rounded-xl
|
|
867
|
+
className="h-9 rounded-xl text-[length:var(--font-size-xs)] whitespace-nowrap"
|
|
864
868
|
>
|
|
865
869
|
Đã bàn giao
|
|
866
870
|
</ToggleGroupItem>
|
|
@@ -1014,7 +1018,7 @@ export default function AgencyHandyShowcase() {
|
|
|
1014
1018
|
<ToggleGroupItem
|
|
1015
1019
|
key={s}
|
|
1016
1020
|
value={s}
|
|
1017
|
-
className="size-7 rounded-md text-[var(--font-size-2xs)] uppercase"
|
|
1021
|
+
className="size-7 rounded-md text-[length:var(--font-size-2xs)] uppercase"
|
|
1018
1022
|
title={s}
|
|
1019
1023
|
>
|
|
1020
1024
|
{s[0]}
|
|
@@ -63,7 +63,15 @@ const THEME = `
|
|
|
63
63
|
[data-tenant="futurelastic"] .fl-section { padding-block: 5.5rem; }
|
|
64
64
|
[data-tenant="futurelastic"] .fl-navbar { position: sticky; top: 0; z-index: 30; background: hsl(var(--background) / 0.8);
|
|
65
65
|
backdrop-filter: blur(12px); border-bottom: 1px solid hsl(var(--border)); }
|
|
66
|
-
|
|
66
|
+
/* REFLOW, NOT DECORATION (gh#643). This row was a fixed 68px flex line with no wrap, so at 320 its
|
|
67
|
+
content ran to x=569.6 inside a 320px nav: every link painted OUTSIDE the navbar's own
|
|
68
|
+
translucent box, over the page behind it, and axe read the white labels against the light
|
|
69
|
+
isolate background at 1.01:1 — 2 nodes at 320, 1 at 375. It was never a contrast bug; it was
|
|
70
|
+
WCAG 2.2 SC 1.4.10 reflow, reported by the contrast rule because that is what an overflowing
|
|
71
|
+
element looks like from the outside. min-height rather than height, so a wrapped row may be
|
|
72
|
+
taller than one line and the language cluster stops overflowing the bar vertically (measured at
|
|
73
|
+
320: it was 148.3px tall, starting at y=-40.2). */
|
|
74
|
+
[data-tenant="futurelastic"] .fl-navbar-inner { min-height: 68px; display: flex; align-items: center; flex-wrap: wrap; column-gap: 2rem; row-gap: 0.75rem; padding-block: 0.75rem; }
|
|
67
75
|
[data-tenant="futurelastic"] .fl-brand { font-family: var(--font-family-display); font-weight: 700; font-size: 1.25rem; color: hsl(var(--foreground)); }
|
|
68
76
|
[data-tenant="futurelastic"] .fl-eyebrow { font-family: var(--font-family-body); font-weight: 600; font-size: 0.75rem;
|
|
69
77
|
letter-spacing: 0.18em; text-transform: uppercase; color: hsl(var(--primary)); }
|
|
@@ -122,14 +130,14 @@ function Navbar() {
|
|
|
122
130
|
<span className="fl-brand">
|
|
123
131
|
futur<span className="fl-gold">elastic</span>
|
|
124
132
|
</span>
|
|
125
|
-
<Flex direction="row" gap="xs" align="center">
|
|
133
|
+
<Flex direction="row" gap="xs" align="center" wrap>
|
|
126
134
|
{links.map((l, i) => (
|
|
127
135
|
<Button key={l} variant="ghost" size="sm" aria-current={i === 0 ? "page" : undefined}>
|
|
128
136
|
{l}
|
|
129
137
|
</Button>
|
|
130
138
|
))}
|
|
131
139
|
</Flex>
|
|
132
|
-
<Flex direction="row" gap="sm" align="center" className="ms-auto">
|
|
140
|
+
<Flex direction="row" gap="sm" align="center" wrap className="ms-auto">
|
|
133
141
|
<Text as="span" size="xs" tone="muted">
|
|
134
142
|
EN · 日本語 · VI
|
|
135
143
|
</Text>
|
|
@@ -205,8 +205,19 @@ function BulkActionBar({
|
|
|
205
205
|
<Card className="bg-primary/5 border-primary/30">
|
|
206
206
|
<CardContent solo>
|
|
207
207
|
<Flex role="region" aria-label="一括操作" wrap align="center" gap="md">
|
|
208
|
-
{/* left: count + cross-page select-all escalation + safe batch actions
|
|
209
|
-
|
|
208
|
+
{/* left: count + cross-page select-all escalation + safe batch actions.
|
|
209
|
+
THE `wrap`, AND THE ABSENT `min-w-0`, ARE BOTH LOAD-BEARING (gh#643). This cluster
|
|
210
|
+
used to carry `min-w-0 flex-1` — "squeeze me to nothing" — while every child inside
|
|
211
|
+
refuses to be squeezed: two `Button`s and a `whitespace-nowrap` count. At 375 the box
|
|
212
|
+
duly shrank to 113.6px, the content still ran to x=462, and it painted straight over
|
|
213
|
+
the destructive cluster beside it — 一括承認 at 264.6→360.2 under 解除 at
|
|
214
|
+
272.3→341.0, which axe reported as `target-size` with 19.2×30.2 of the button left
|
|
215
|
+
unobscured against SC 2.5.8's 24×24. Without `min-w-0` the cluster keeps a real
|
|
216
|
+
min-content width, and `wrap` is what makes that width small enough to be honest:
|
|
217
|
+
measured after, 375 puts the two clusters on separate rows (left 307px over two
|
|
218
|
+
lines, destructive below) and 1440 is unchanged — one 30.2px row, destructive still
|
|
219
|
+
pushed to x=1217.5 by `flex-1`. */}
|
|
220
|
+
<Flex direction="row" align="center" gap="sm" wrap className="flex-1">
|
|
210
221
|
<Text tone="primary" weight="medium" tabular className="whitespace-nowrap">
|
|
211
222
|
{effective}件を選択中
|
|
212
223
|
</Text>
|
|
@@ -267,7 +267,9 @@ export default function Demo() {
|
|
|
267
267
|
>
|
|
268
268
|
<Flex direction="row" align="center" gap="xs">
|
|
269
269
|
<span>{DENSITY_META[key].label}</span>
|
|
270
|
-
|
|
270
|
+
{/* `inherit` (gh#643): a SELECTED item fills with --primary, and `muted`
|
|
271
|
+
is an absolute token — it measured 1.11:1 on that fill. */}
|
|
272
|
+
<Text size="2xs" tone="inherit" tabular>
|
|
271
273
|
{DENSITY_META[key].px}
|
|
272
274
|
</Text>
|
|
273
275
|
</Flex>
|
|
@@ -299,7 +299,7 @@ function DetailPanel({ employee }: { employee: Employee }) {
|
|
|
299
299
|
{/* Per-day breakdown — quiet nested list, real Card chrome */}
|
|
300
300
|
<Card>
|
|
301
301
|
<CardHeader>
|
|
302
|
-
<CardTitle level={2} className="text-[var(--font-size-xs)]">
|
|
302
|
+
<CardTitle level={2} className="text-[length:var(--font-size-xs)]">
|
|
303
303
|
今週の打刻
|
|
304
304
|
</CardTitle>
|
|
305
305
|
<CardAction>
|
|
@@ -396,27 +396,37 @@ function ExpandableList() {
|
|
|
396
396
|
const panelId = `row-detail-${emp.id}`;
|
|
397
397
|
return (
|
|
398
398
|
<React.Fragment key={emp.id}>
|
|
399
|
+
{/* THE DISCLOSURE IS THE BUTTON, NOT THE ROW (gh#643, found by check:frame-axe).
|
|
400
|
+
This row used to carry `tabIndex={0}` + `aria-expanded` + `aria-controls` and be
|
|
401
|
+
the control itself. `aria-expanded` is valid on a TREEGRID row and not on a table
|
|
402
|
+
row, so axe scored it `aria-conditional-attr` — and the row was a focus stop with
|
|
403
|
+
no role and no name, which is the worse half of the same mistake. The chevron is
|
|
404
|
+
now a real button: it owns the state, the name and the tab stop. Clicking the row
|
|
405
|
+
still works, as a pointer convenience that claims no semantics of its own. */}
|
|
399
406
|
<TableRow
|
|
400
407
|
data-state={isOpen ? "selected" : undefined}
|
|
401
|
-
tabIndex={0}
|
|
402
|
-
aria-expanded={isOpen}
|
|
403
|
-
aria-controls={panelId}
|
|
404
408
|
onClick={() => {
|
|
405
409
|
toggle(emp.id);
|
|
406
410
|
}}
|
|
407
|
-
|
|
408
|
-
if (e.key !== "Enter" && e.key !== " ") return;
|
|
409
|
-
if (e.target !== e.currentTarget) return;
|
|
410
|
-
e.preventDefault();
|
|
411
|
-
toggle(emp.id);
|
|
412
|
-
}}
|
|
413
|
-
className="hover:bg-muted/50 focus-visible:ring-ring cursor-pointer focus-visible:ring-2 focus-visible:outline-none focus-visible:ring-inset"
|
|
411
|
+
className="hover:bg-muted/50 cursor-pointer"
|
|
414
412
|
>
|
|
415
413
|
<TableCell className={CELL}>
|
|
416
|
-
<
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
414
|
+
<Button
|
|
415
|
+
variant="ghost"
|
|
416
|
+
size="icon-xs"
|
|
417
|
+
aria-expanded={isOpen}
|
|
418
|
+
aria-controls={isOpen ? panelId : undefined}
|
|
419
|
+
aria-label={`${emp.name} の詳細を${isOpen ? "折りたたむ" : "展開する"}`}
|
|
420
|
+
onClick={(e) => {
|
|
421
|
+
e.stopPropagation();
|
|
422
|
+
toggle(emp.id);
|
|
423
|
+
}}
|
|
424
|
+
>
|
|
425
|
+
<ChevronRight
|
|
426
|
+
className={`text-muted-foreground size-4 transition-transform ${isOpen ? "rotate-90" : ""}`}
|
|
427
|
+
aria-hidden="true"
|
|
428
|
+
/>
|
|
429
|
+
</Button>
|
|
420
430
|
</TableCell>
|
|
421
431
|
<TableCell className={CELL}>
|
|
422
432
|
<Text as="div" weight="medium">
|
|
@@ -232,7 +232,11 @@ function GroupHeaderRow({
|
|
|
232
232
|
size="sm"
|
|
233
233
|
onClick={onToggle}
|
|
234
234
|
aria-expanded={open}
|
|
235
|
-
aria-controls
|
|
235
|
+
/* NO `aria-controls` (gh#643, found by check:frame-axe). It pointed at `grp-<id>`, and
|
|
236
|
+
no element in this table ever carried that id — axe: `aria-valid-attr-value`, 5 nodes.
|
|
237
|
+
It cannot simply be added, either: what this button expands is a RUN OF SIBLING `<tr>`s
|
|
238
|
+
with no single container to name. `aria-expanded` alone is complete and valid here —
|
|
239
|
+
`aria-controls` is optional, and a broken one is worse than none. */
|
|
236
240
|
className="h-7 font-medium"
|
|
237
241
|
>
|
|
238
242
|
<Chevron className="text-muted-foreground size-4" aria-hidden="true" />
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@godxjp/ui",
|
|
3
|
-
"version": "
|
|
4
|
-
"godxUiMcp": "
|
|
3
|
+
"version": "25.0.0",
|
|
4
|
+
"godxUiMcp": "25.0.0",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"repository": {
|
|
7
7
|
"type": "git",
|
|
@@ -322,6 +322,7 @@
|
|
|
322
322
|
"check:example-imports": "node scripts/check-example-imports.mjs",
|
|
323
323
|
"check:frame-coverage": "node scripts/frame-coverage.mjs",
|
|
324
324
|
"check:frame-coverage-report": "node scripts/frame-coverage.mjs --check",
|
|
325
|
+
"check:frame-axe": "node scripts/check-frame-axe.mjs",
|
|
325
326
|
"gen:frame-coverage-ledger": "node scripts/gen-frame-coverage-ledger.mjs",
|
|
326
327
|
"check:frame-coverage-ledger": "node scripts/gen-frame-coverage-ledger.mjs --check && node scripts/check-frame-coverage-ledger.mjs",
|
|
327
328
|
"check:screen-reader-evidence": "node scripts/check-screen-reader-evidence.mjs",
|
|
@@ -469,6 +470,7 @@
|
|
|
469
470
|
"vaul": "^1.1.2"
|
|
470
471
|
},
|
|
471
472
|
"devDependencies": {
|
|
473
|
+
"@axe-core/playwright": "^4.13.0",
|
|
472
474
|
"@eslint/js": "^10.0.1",
|
|
473
475
|
"@hookform/resolvers": "^5.9.1",
|
|
474
476
|
"@radix-ui/react-accordion": "^1.2.20",
|