@godxjp/ui 18.15.3 → 19.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/app/date-format-labels.d.ts +1 -1
- package/dist/app/time-format-labels.d.ts +1 -1
- package/dist/components/data-display/badge.d.ts +1 -1
- package/dist/components/data-display/card.d.ts +4 -13
- package/dist/components/data-display/card.js +2 -15
- package/dist/components/data-display/data-table.d.ts +3 -8
- package/dist/components/data-display/data-table.js +106 -68
- package/dist/components/data-display/descriptions.js +34 -5
- package/dist/components/data-display/empty-state.js +1 -1
- package/dist/components/data-display/permission-matrix.js +7 -7
- package/dist/components/data-display/popover.js +2 -9
- package/dist/components/data-display/qr-code.d.ts +1 -1
- package/dist/components/data-display/scroll-area.d.ts +3 -1
- package/dist/components/data-display/scroll-area.js +150 -16
- package/dist/components/data-display/table.js +12 -3
- package/dist/components/data-entry/branch-scope-picker.js +4 -4
- package/dist/components/data-entry/calendar.js +15 -24
- package/dist/components/data-entry/cascader.js +67 -71
- package/dist/components/data-entry/checkbox.js +9 -1
- package/dist/components/data-entry/command.d.ts +6 -6
- package/dist/components/data-entry/date-picker.js +28 -15
- package/dist/components/data-entry/date-range-picker.js +10 -10
- package/dist/components/data-entry/form-field.js +7 -3
- package/dist/components/data-entry/index.d.ts +2 -1
- package/dist/components/data-entry/input.js +8 -19
- package/dist/components/data-entry/label.js +4 -2
- package/dist/components/data-entry/month-picker.js +16 -15
- package/dist/components/data-entry/month-range-picker.js +17 -16
- package/dist/components/data-entry/radio.js +6 -1
- package/dist/components/data-entry/search-select.js +29 -29
- package/dist/components/data-entry/select.js +15 -18
- package/dist/components/data-entry/switch.js +7 -1
- package/dist/components/data-entry/textarea.d.ts +56 -0
- package/dist/components/data-entry/textarea.js +103 -40
- package/dist/components/data-entry/time-picker.js +52 -52
- package/dist/components/data-entry/toggle-group.d.ts +1 -0
- package/dist/components/data-entry/toggle.d.ts +1 -1
- package/dist/components/data-entry/transfer.js +14 -15
- package/dist/components/data-entry/tree-select.js +16 -22
- package/dist/components/data-entry/upload-crop-dialog.js +4 -4
- package/dist/components/data-entry/upload.d.ts +1 -1
- package/dist/components/data-entry/upload.js +46 -46
- package/dist/components/feedback/alert.js +7 -14
- package/dist/components/feedback/dialog.d.ts +23 -32
- package/dist/components/feedback/dialog.js +16 -13
- package/dist/components/feedback/sheet.d.ts +1 -1
- package/dist/components/feedback/sheet.js +14 -14
- package/dist/components/feedback/skeleton.js +13 -10
- package/dist/components/feedback/sonner.d.ts +1 -1
- package/dist/components/feedback/sonner.js +17 -8
- package/dist/components/feedback/tooltip.js +1 -1
- package/dist/components/general/activity.d.ts +44 -0
- package/dist/components/general/activity.js +65 -0
- package/dist/components/general/button.d.ts +2 -2
- package/dist/components/general/button.js +11 -24
- package/dist/components/general/index.d.ts +2 -0
- package/dist/components/general/index.js +2 -0
- package/dist/components/layout/app-shell.js +5 -3
- package/dist/components/layout/auth-divider.d.ts +9 -2
- package/dist/components/layout/auth-divider.js +7 -11
- package/dist/components/layout/index.d.ts +1 -0
- package/dist/components/layout/page-container.d.ts +1 -1
- package/dist/components/layout/page-container.js +32 -20
- package/dist/components/layout/resizable.d.ts +12 -12
- package/dist/components/layout/separator.d.ts +1 -0
- package/dist/components/layout/service-role-panel.js +47 -42
- package/dist/components/layout/sidebar.js +13 -1
- package/dist/components/layout/split-pane.d.ts +12 -1
- package/dist/components/layout/split-pane.js +13 -4
- package/dist/components/navigation/app-setting-picker.js +5 -14
- package/dist/components/navigation/dropdown-menu.js +10 -16
- package/dist/components/navigation/menubar.d.ts +3 -6
- package/dist/components/navigation/pagination-utils.d.ts +15 -2
- package/dist/components/navigation/pagination-utils.js +27 -21
- package/dist/components/navigation/pagination.d.ts +4 -4
- package/dist/components/navigation/pagination.js +2 -2
- package/dist/components/navigation/steps.js +31 -67
- package/dist/components/navigation/tabs.js +17 -5
- package/dist/components/query/infinite-query-state.js +2 -2
- package/dist/components/ui/accordion.d.ts +1 -1
- package/dist/components/ui/avatar.d.ts +1 -1
- package/dist/components/ui/avatar.js +29 -10
- package/dist/components/ui/input-otp.d.ts +8 -6
- package/dist/components/ui/rating.js +1 -1
- package/dist/components/ui/separator.d.ts +27 -2
- package/dist/components/ui/separator.js +42 -11
- package/dist/components/ui/toggle-group.d.ts +15 -5
- package/dist/components/ui/toggle-group.js +43 -18
- package/dist/components/ui/toggle.d.ts +66 -5
- package/dist/components/ui/toggle.js +64 -9
- package/dist/i18n/messages/en.json +8 -0
- package/dist/i18n/messages/ja.json +8 -0
- package/dist/i18n/messages/vi.json +8 -0
- package/dist/lib/control-styles.d.ts +14 -0
- package/dist/lib/control-styles.js +2 -0
- package/dist/props/components/data-display.prop.d.ts +85 -3
- package/dist/props/components/data-entry.prop.d.ts +17 -0
- package/dist/props/components/general.prop.d.ts +52 -1
- package/dist/props/components/layout.prop.d.ts +137 -2
- package/dist/props/registry.d.ts +116 -5
- package/dist/props/registry.js +165 -4
- package/dist/props/vocabulary/index.d.ts +2 -2
- package/dist/props/vocabulary/interaction.prop.d.ts +15 -0
- package/dist/props/vocabulary/layout.prop.d.ts +8 -0
- package/dist/styles/alert-layout.css +69 -3
- package/dist/styles/badge-layout.css +2 -2
- package/dist/styles/base.css +6 -0
- package/dist/styles/card-layout.css +27 -7
- package/dist/styles/control.css +1150 -29
- package/dist/styles/data-display-layout.css +185 -11
- package/dist/styles/data-entry-layout.css +352 -1
- package/dist/styles/density.css +2 -2
- package/dist/styles/dialog-layout.css +203 -4
- package/dist/styles/focus-ring.css +155 -0
- package/dist/styles/form-layout.css +50 -5
- package/dist/styles/index.css +1 -0
- package/dist/styles/layout.css +412 -23
- package/dist/styles/motion.css +178 -0
- package/dist/styles/navigation-layout.css +398 -22
- package/dist/styles/shell-layout.css +137 -39
- package/dist/styles/table-layout.css +153 -22
- package/dist/styles/toggle.css +59 -0
- package/dist/tokens/base.css +5 -0
- package/dist/tokens/components/activity.css +64 -0
- package/dist/tokens/components/badge.css +7 -0
- package/dist/tokens/components/card.css +14 -10
- package/dist/tokens/components/chart.css +1 -1
- package/dist/tokens/components/control.css +358 -8
- package/dist/tokens/components/data-display.css +75 -0
- package/dist/tokens/components/data-entry.css +25 -0
- package/dist/tokens/components/descriptions.css +22 -0
- package/dist/tokens/components/feedback.css +143 -4
- package/dist/tokens/components/form.css +16 -0
- package/dist/tokens/components/legal-document.css +5 -1
- package/dist/tokens/components/navigation.css +109 -0
- package/dist/tokens/components/scroll-area.css +21 -0
- package/dist/tokens/components/separator.css +52 -0
- package/dist/tokens/components/shell.css +132 -20
- package/dist/tokens/components/table.css +52 -3
- package/dist/tokens/components/toggle.css +82 -0
- package/dist/tokens/components/upload.css +107 -0
- package/dist/tokens/foundation.css +171 -10
- package/dist/tokens/semantic/layout.css +120 -1
- package/package.json +84 -68
- package/scripts/ui-audit.mjs +51 -2
|
@@ -39,17 +39,21 @@
|
|
|
39
39
|
);
|
|
40
40
|
}
|
|
41
41
|
|
|
42
|
+
/* Stacking joins the ONE system layer (--overlay-z-index) instead of a private z-50 — Sheet,
|
|
43
|
+
* Tooltip and Popover already do, so a service that lifts its overlays above an app-owned
|
|
44
|
+
* fixed element no longer has to fork the dialog to bring it along. Default is 50, the literal
|
|
45
|
+
* this replaces. */
|
|
42
46
|
.ui-dialog-overlay {
|
|
43
47
|
position: fixed;
|
|
44
48
|
inset: 0;
|
|
45
|
-
z-index:
|
|
49
|
+
z-index: var(--overlay-z-index);
|
|
46
50
|
}
|
|
47
51
|
|
|
48
52
|
[data-slot="dialog-content"] {
|
|
49
53
|
position: fixed;
|
|
50
54
|
inset-inline-start: 50%;
|
|
51
55
|
top: 50%;
|
|
52
|
-
z-index:
|
|
56
|
+
z-index: var(--overlay-z-index);
|
|
53
57
|
display: grid;
|
|
54
58
|
width: min(var(--dialog-width-default), calc(100% - var(--dialog-viewport-inset)));
|
|
55
59
|
max-width: none;
|
|
@@ -88,10 +92,52 @@
|
|
|
88
92
|
[data-slot="dialog-header"] {
|
|
89
93
|
display: flex;
|
|
90
94
|
flex-direction: column;
|
|
91
|
-
gap: var(--space-
|
|
95
|
+
gap: var(--dialog-header-space-gap);
|
|
92
96
|
text-align: start;
|
|
93
97
|
}
|
|
94
98
|
|
|
99
|
+
/* DIALOG chrome rhythm — the exact mirror of the `.ui-sheet-*` set at the foot of this file.
|
|
100
|
+
* Dialog's default header markup is byte-identical to Sheet's, but its gaps were literal on the
|
|
101
|
+
* component (`gap-[var(--space-3)] pe-[var(--space-8)]`, `min-w-0 flex-col gap-[var(--space-1)]`,
|
|
102
|
+
* `shrink-0 gap-[var(--space-2)]`), so the two siblings could not be retuned together (#319). */
|
|
103
|
+
.ui-dialog-title-row {
|
|
104
|
+
display: flex;
|
|
105
|
+
align-items: flex-start;
|
|
106
|
+
justify-content: space-between;
|
|
107
|
+
gap: var(--dialog-title-row-space-gap);
|
|
108
|
+
/* Reserves inline room for the absolutely-positioned close button so a long title or the extra
|
|
109
|
+
* slot never runs under it. Moves with --dialog-close-space-offset. */
|
|
110
|
+
padding-inline-end: var(--dialog-header-close-space-inline-end);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
.ui-dialog-title-block {
|
|
114
|
+
display: flex;
|
|
115
|
+
min-inline-size: 0;
|
|
116
|
+
flex-direction: column;
|
|
117
|
+
gap: var(--dialog-title-block-space-gap);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
.ui-dialog-extra {
|
|
121
|
+
display: flex;
|
|
122
|
+
flex-shrink: 0;
|
|
123
|
+
align-items: center;
|
|
124
|
+
gap: var(--dialog-extra-space-gap);
|
|
125
|
+
white-space: nowrap;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/* Close glyph — mirrors `.ui-sheet-close-icon`, so both overlay ✕ marks follow the one control
|
|
129
|
+
* affix size instead of a per-component `size-4`. */
|
|
130
|
+
.ui-dialog-close-icon {
|
|
131
|
+
inline-size: var(--control-affix-icon-size);
|
|
132
|
+
block-size: var(--control-affix-icon-size);
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/* Type-to-confirm challenge: the step-up failure line under the input. */
|
|
136
|
+
.ui-dialog-step-up-error {
|
|
137
|
+
font-size: var(--dialog-step-up-error-font-size);
|
|
138
|
+
color: hsl(var(--destructive));
|
|
139
|
+
}
|
|
140
|
+
|
|
95
141
|
[data-slot="dialog-footer"] {
|
|
96
142
|
display: flex;
|
|
97
143
|
flex-direction: column-reverse;
|
|
@@ -127,7 +173,7 @@
|
|
|
127
173
|
inset-inline-end: var(--dialog-close-space-offset);
|
|
128
174
|
top: var(--dialog-close-space-offset);
|
|
129
175
|
border-radius: var(--radius-sm);
|
|
130
|
-
opacity:
|
|
176
|
+
opacity: var(--dialog-close-rest-alpha);
|
|
131
177
|
}
|
|
132
178
|
|
|
133
179
|
[data-slot="dialog-close"]:hover {
|
|
@@ -238,3 +284,156 @@
|
|
|
238
284
|
transition: none;
|
|
239
285
|
}
|
|
240
286
|
}
|
|
287
|
+
|
|
288
|
+
@layer components {
|
|
289
|
+
/* TOOLTIP / POPOVER — the two portaled overlay surfaces.
|
|
290
|
+
*
|
|
291
|
+
* Both shipped their entire box as Tailwind literals on the component
|
|
292
|
+
* (`z-50 max-w-xs px-2 py-1 rounded-md text-xs shadow-md`, `w-72 p-4`), so a service
|
|
293
|
+
* theme could not retune tooltip density, popover width, or overlay stacking without
|
|
294
|
+
* forking the component — exactly the gap cardinal rule #45 exists to close.
|
|
295
|
+
*
|
|
296
|
+
* Colours read the popover ROLE at the call site (not bound at :root) so a scoped
|
|
297
|
+
* [data-tenant]/.dark override still reaches the portaled node — docs/TOKENS.md ·
|
|
298
|
+
* "Role-mirror knobs MUST be `initial`". */
|
|
299
|
+
.ui-tooltip-content {
|
|
300
|
+
z-index: var(--overlay-z-index);
|
|
301
|
+
width: fit-content;
|
|
302
|
+
max-width: var(--tooltip-max-width);
|
|
303
|
+
padding-inline: var(--tooltip-space-inline);
|
|
304
|
+
padding-block: var(--tooltip-space-block);
|
|
305
|
+
border-radius: var(--tooltip-radius);
|
|
306
|
+
border: 1px solid var(--tooltip-border-color, hsl(var(--border)));
|
|
307
|
+
background-color: var(--tooltip-background, hsl(var(--popover)));
|
|
308
|
+
color: var(--tooltip-foreground, hsl(var(--popover-foreground)));
|
|
309
|
+
font-size: var(--tooltip-font-size);
|
|
310
|
+
box-shadow: var(--tooltip-shadow);
|
|
311
|
+
outline: none;
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
.ui-popover-content {
|
|
315
|
+
z-index: var(--overlay-z-index);
|
|
316
|
+
width: var(--popover-width);
|
|
317
|
+
padding: var(--popover-space-inset);
|
|
318
|
+
border-radius: var(--popover-radius);
|
|
319
|
+
border: 1px solid var(--popover-surface-border-color, hsl(var(--border)));
|
|
320
|
+
background-color: var(--popover-surface-background, hsl(var(--popover)));
|
|
321
|
+
color: var(--popover-surface-foreground, hsl(var(--popover-foreground)));
|
|
322
|
+
box-shadow: var(--popover-shadow);
|
|
323
|
+
outline: none;
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
.ui-popover-header {
|
|
327
|
+
display: flex;
|
|
328
|
+
flex-direction: column;
|
|
329
|
+
gap: var(--popover-header-space-gap);
|
|
330
|
+
font-size: var(--popover-header-font-size);
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
@layer components {
|
|
335
|
+
/* SHEET chrome rhythm — --sheet-pad-x/-y already governed the panel inset; these are the gaps
|
|
336
|
+
* INSIDE that chrome, which were literal on the component (#319). */
|
|
337
|
+
.ui-sheet-header {
|
|
338
|
+
display: flex;
|
|
339
|
+
flex-direction: column;
|
|
340
|
+
gap: var(--sheet-header-space-gap);
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
/* Reserves inline room for the absolutely-positioned close button so a long title or the
|
|
344
|
+
* extra slot never runs under it. Moves with --sheet-close-offset. */
|
|
345
|
+
.ui-sheet-title-row {
|
|
346
|
+
display: flex;
|
|
347
|
+
align-items: flex-start;
|
|
348
|
+
justify-content: space-between;
|
|
349
|
+
gap: var(--sheet-title-row-space-gap);
|
|
350
|
+
padding-inline-end: var(--sheet-header-close-space-inline-end);
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
.ui-sheet-title-block {
|
|
354
|
+
display: flex;
|
|
355
|
+
min-inline-size: 0;
|
|
356
|
+
flex-direction: column;
|
|
357
|
+
gap: var(--sheet-title-block-space-gap);
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
.ui-sheet-extra {
|
|
361
|
+
display: flex;
|
|
362
|
+
flex-shrink: 0;
|
|
363
|
+
align-items: center;
|
|
364
|
+
gap: var(--sheet-extra-space-gap);
|
|
365
|
+
white-space: nowrap;
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
.ui-sheet-title {
|
|
369
|
+
font-size: var(--sheet-title-font-size);
|
|
370
|
+
font-weight: var(--font-weight-medium);
|
|
371
|
+
color: hsl(var(--foreground));
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
.ui-sheet-description {
|
|
375
|
+
font-size: var(--sheet-description-font-size);
|
|
376
|
+
color: hsl(var(--foreground) / 0.8);
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
.ui-sheet-body-space {
|
|
380
|
+
padding-block: var(--sheet-body-space-block);
|
|
381
|
+
scroll-padding-block: var(--sheet-body-space-block);
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
.ui-sheet-footer-row {
|
|
385
|
+
display: flex;
|
|
386
|
+
flex-wrap: wrap;
|
|
387
|
+
align-items: center;
|
|
388
|
+
justify-content: flex-end;
|
|
389
|
+
gap: var(--sheet-footer-space-gap);
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
.ui-sheet-close {
|
|
393
|
+
position: absolute;
|
|
394
|
+
inset-block-start: var(--sheet-close-offset);
|
|
395
|
+
inset-inline-end: var(--sheet-close-offset);
|
|
396
|
+
border-radius: var(--radius-sm);
|
|
397
|
+
opacity: var(--sheet-close-rest-alpha);
|
|
398
|
+
transition: opacity 150ms ease;
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
.ui-sheet-close:hover {
|
|
402
|
+
opacity: 1;
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
.ui-sheet-close-icon {
|
|
406
|
+
inline-size: var(--control-affix-icon-size);
|
|
407
|
+
block-size: var(--control-affix-icon-size);
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
/* Overlay + panel stacking joins the one system layer instead of a private z-50. */
|
|
411
|
+
.ui-sheet-overlay,
|
|
412
|
+
.ui-sheet-panel {
|
|
413
|
+
z-index: var(--overlay-z-index);
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
.ui-sheet-overlay {
|
|
417
|
+
inset: 0;
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
.ui-sheet-panel {
|
|
421
|
+
box-shadow: var(--sheet-shadow);
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
.ui-sheet-panel[data-side="top"] {
|
|
425
|
+
inset-block-start: 0;
|
|
426
|
+
border-block-end: 1px solid hsl(var(--border));
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
.ui-sheet-panel[data-side="bottom"] {
|
|
430
|
+
inset-block-end: 0;
|
|
431
|
+
border-block-start: 1px solid hsl(var(--border));
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
/* The scroll region must be allowed to shrink inside the flex column, or it grows past the
|
|
435
|
+
* panel and the footer is pushed out of view. */
|
|
436
|
+
.ui-sheet-body-space {
|
|
437
|
+
min-block-size: 0;
|
|
438
|
+
}
|
|
439
|
+
}
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
/* ─────────────────────────────────────────────────────────────────────────
|
|
2
|
+
* FOCUS RING — the ONE place the keyboard-focus affordance is drawn.
|
|
3
|
+
*
|
|
4
|
+
* WHY THIS FILE EXISTS: the ring used to be re-written by hand in every layout
|
|
5
|
+
* file, and it drifted into four incompatible shapes — the token pair
|
|
6
|
+
* (`0 0 0 var(--focus-ring-width) hsl(var(--focus-ring-color, var(--ring)))`),
|
|
7
|
+
* the same with an ad-hoc `/ 0.45`, and two that hardcoded `3px` with `/ 0.35`
|
|
8
|
+
* and `/ 0.3`, bypassing `--focus-ring-width` entirely. Same state, four
|
|
9
|
+
* thicknesses and opacities, and no way for a service to retune them together.
|
|
10
|
+
* A component that forgot the rule got the UA default instead (Chrome paints
|
|
11
|
+
* `rgb(0,95,204)` — a blue belonging to no theme; that is exactly how the
|
|
12
|
+
* pagination defect was found).
|
|
13
|
+
*
|
|
14
|
+
* THE API — three layers, each overriding the one above:
|
|
15
|
+
*
|
|
16
|
+
* 1. GLOBAL (`:root`, tokens/foundation.css)
|
|
17
|
+
* --focus-ring-width thickness (default 2px)
|
|
18
|
+
* --focus-ring-color hue, HSL components (default = live --ring)
|
|
19
|
+
* --focus-ring-opacity alpha (default 1)
|
|
20
|
+
* --focus-ring-offset gap, outline form (default 0px)
|
|
21
|
+
* Set any of these ONCE — even scoped under `[data-tenant]` — and EVERY
|
|
22
|
+
* ring in the system follows.
|
|
23
|
+
*
|
|
24
|
+
* 2. PER COMPONENT (tokens/components/*.css)
|
|
25
|
+
* A component that genuinely needs a different ring publishes its own
|
|
26
|
+
* knob and rebinds the local value, e.g.
|
|
27
|
+
* --toggle-focus-ring-alpha: 0.35; (public knob)
|
|
28
|
+
* .ui-toggle { --focus-ring-opacity: var(--toggle-focus-ring-alpha); }
|
|
29
|
+
* Custom properties inherit, so the rule below picks up the local value at
|
|
30
|
+
* the element that paints — one formula, per-component tuning, and the
|
|
31
|
+
* knob stays discoverable in the token file rather than buried in a
|
|
32
|
+
* hand-written box-shadow.
|
|
33
|
+
*
|
|
34
|
+
* 3. PER INSTANCE (consumer)
|
|
35
|
+
* `style={{ "--focus-ring-color": "0 84% 60%" }}` on any subtree.
|
|
36
|
+
*
|
|
37
|
+
* TURNING IT OFF: `--focus-ring-width: 0` — globally at `:root`, on one
|
|
38
|
+
* component, or on one instance. A zero-width ring paints nothing.
|
|
39
|
+
* A11y note: the default is ALWAYS ON (WCAG 2.4.7 / 2.4.11); switching it off
|
|
40
|
+
* is a deliberate consumer act, never a default of this package. The one
|
|
41
|
+
* exception that ships off is the REGION ring (`.app-main`,
|
|
42
|
+
* `--region-focus-ring-width: 0`) — a documented product-owner tradeoff (see
|
|
43
|
+
* tokens/foundation.css), because a control-strength frame around the whole
|
|
44
|
+
* content area reads as a glitch to mouse-first users.
|
|
45
|
+
*
|
|
46
|
+
* TWO FORMS, one API: `box-shadow` hugs the border-radius and never shifts
|
|
47
|
+
* layout, so it is the default. `outline` + `outline-offset` is for marks that
|
|
48
|
+
* need a visible GAP (a star, a dot, a heading anchor) where a hugging ring
|
|
49
|
+
* would touch the glyph. Both read the same four tokens.
|
|
50
|
+
*
|
|
51
|
+
* ADDING A COMPONENT: put its class in the matching `:is()` list below, or
|
|
52
|
+
* give the element `ui-focus-ring` / `ui-focus-ring-outline`. Do NOT write a
|
|
53
|
+
* `:focus-visible` box-shadow/outline anywhere else — a test enforces this
|
|
54
|
+
* (`src/styles/__tests__/focus-ring-single-source.test.ts`).
|
|
55
|
+
* ───────────────────────────────────────────────────────────────────────── */
|
|
56
|
+
@layer components {
|
|
57
|
+
/* Shadow form — the default. Hugs the radius, no layout shift. */
|
|
58
|
+
:is(
|
|
59
|
+
.ui-focus-ring,
|
|
60
|
+
.ui-button,
|
|
61
|
+
.ui-toggle,
|
|
62
|
+
.ui-checkbox,
|
|
63
|
+
.ui-radio,
|
|
64
|
+
.ui-slider-thumb,
|
|
65
|
+
.ui-switch,
|
|
66
|
+
.ui-time-input,
|
|
67
|
+
.ui-steps-inline-control,
|
|
68
|
+
.ui-pagination-link,
|
|
69
|
+
.ui-legal-document-toc-link,
|
|
70
|
+
.sb-user,
|
|
71
|
+
.tb-icon-btn
|
|
72
|
+
):focus-visible {
|
|
73
|
+
outline: none;
|
|
74
|
+
/* TWO declarations, one ring — because a control may also carry a Tailwind
|
|
75
|
+
* shadow/ring utility (`shadow-xs` on Checkbox, Radio, Switch, Input …).
|
|
76
|
+
* Those live in the UTILITIES layer, which outranks `components`, and they
|
|
77
|
+
* set `box-shadow: var(--tw-ring-offset-shadow), var(--tw-ring-shadow),
|
|
78
|
+
* var(--tw-shadow)` — so a plain `box-shadow` here loses and the ring never
|
|
79
|
+
* paints. Measured before this fix: Checkbox focus-visible resolved to
|
|
80
|
+
* `rgba(0,0,0,0) 0 0 0 0`, i.e. NO visible ring at all.
|
|
81
|
+
* Feeding `--tw-ring-shadow` makes the utility's own composite paint our
|
|
82
|
+
* ring (and keeps the element's resting shadow); the plain `box-shadow`
|
|
83
|
+
* covers elements that carry no such utility. */
|
|
84
|
+
--tw-ring-shadow: 0 0 0 var(--focus-ring-width)
|
|
85
|
+
hsl(var(--focus-ring-color, var(--ring)) / var(--focus-ring-opacity, 1));
|
|
86
|
+
box-shadow: 0 0 0 var(--focus-ring-width)
|
|
87
|
+
hsl(var(--focus-ring-color, var(--ring)) / var(--focus-ring-opacity, 1));
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/* Outline form — for marks that need a gap between glyph and ring. */
|
|
91
|
+
:is(
|
|
92
|
+
.ui-focus-ring-outline,
|
|
93
|
+
.ui-accordion-trigger,
|
|
94
|
+
.ui-carousel-dot,
|
|
95
|
+
.ui-rating-star,
|
|
96
|
+
.ui-legal-document-section
|
|
97
|
+
):focus-visible {
|
|
98
|
+
outline: var(--focus-ring-width) solid
|
|
99
|
+
hsl(var(--focus-ring-color, var(--ring)) / var(--focus-ring-opacity, 1));
|
|
100
|
+
outline-offset: var(--focus-ring-offset, 0px);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/* ── Per-component rebinds ────────────────────────────────────────────────
|
|
104
|
+
* Each line maps a PUBLIC knob (declared in tokens/components/*.css) onto the
|
|
105
|
+
* local value the rules above read. The historical look is preserved: these
|
|
106
|
+
* are the components that used to hand-write a different ring. */
|
|
107
|
+
|
|
108
|
+
/* Toggle: a heavier, softer ring — the control is a filled surface, so a
|
|
109
|
+
* hard 2px reads as a second border rather than a focus mark. */
|
|
110
|
+
.ui-toggle {
|
|
111
|
+
--focus-ring-width: var(--toggle-focus-ring-width);
|
|
112
|
+
--focus-ring-opacity: var(--toggle-focus-ring-alpha);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/* Time input: same reasoning as Toggle — sits inside a bordered field. */
|
|
116
|
+
.ui-time-input {
|
|
117
|
+
--focus-ring-width: var(--time-input-focus-ring-width);
|
|
118
|
+
--focus-ring-opacity: var(--time-input-focus-ring-alpha);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/* Shell chrome: softened so the ring reads as focus, not as a selected item,
|
|
122
|
+
* on the tinted sidebar/topbar grounds. */
|
|
123
|
+
.sb-user {
|
|
124
|
+
--focus-ring-opacity: var(--sidebar-user-focus-ring-alpha);
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
.tb-icon-btn {
|
|
128
|
+
--focus-ring-opacity: var(--topbar-icon-focus-ring-alpha);
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/* Outline-form offsets — the gap each mark needs. */
|
|
132
|
+
.ui-accordion-trigger {
|
|
133
|
+
--focus-ring-offset: var(--accordion-focus-ring-offset);
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
.ui-carousel-dot {
|
|
137
|
+
--focus-ring-offset: var(--carousel-dot-focus-ring-offset);
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
.ui-rating-star {
|
|
141
|
+
--focus-ring-offset: var(--rating-focus-ring-offset);
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
.ui-legal-document-section {
|
|
145
|
+
--focus-ring-offset: var(--legal-document-section-focus-ring-offset);
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
/* Opt OUT — the command palette input sits inside an already-focused dialog
|
|
149
|
+
* frame, so its own ring would double up (historical behaviour, kept). */
|
|
150
|
+
.ui-command-input:focus-visible {
|
|
151
|
+
--focus-ring-width: 0;
|
|
152
|
+
outline: none;
|
|
153
|
+
box-shadow: none;
|
|
154
|
+
}
|
|
155
|
+
}
|
|
@@ -34,15 +34,60 @@
|
|
|
34
34
|
}
|
|
35
35
|
|
|
36
36
|
/* …except when the fields are ResponsiveGrid ITEMS (Form columns={n}): there the grid's own
|
|
37
|
-
* `gap` owns the rhythm, and a per-item margin double-spaces every row
|
|
38
|
-
* alignment — the FIRST field has no preceding sibling so no margin, while its
|
|
39
|
-
* shifting row 1's columns
|
|
40
|
-
*
|
|
41
|
-
*
|
|
37
|
+
* `row-gap` owns the rhythm (see the rule below), and a per-item margin double-spaces every row
|
|
38
|
+
* AND breaks column alignment — the FIRST field has no preceding sibling so no margin, while its
|
|
39
|
+
* row-mate does, shifting row 1's columns apart by exactly --form-field-row-gap and inflating
|
|
40
|
+
* every grid track by the same amount (gh#304: measured 0 / +12px tops on row 1, 89px pitch
|
|
41
|
+
* against a 77px track). Fields merely NESTED somewhere inside a grid cell (a stacked pair in one
|
|
42
|
+
* cell) are not direct children, keep the margin, and are unaffected. */
|
|
42
43
|
.ui-responsive-grid > .ui-form-field + .ui-form-field {
|
|
43
44
|
margin-block-start: 0;
|
|
44
45
|
}
|
|
45
46
|
|
|
47
|
+
/* …and the rhythm the grid takes over is the FORM'S, not ResponsiveGrid's generic 16px stack gap
|
|
48
|
+
* (gh#304). Handing the rows to the grid fixed the alignment but silently re-tuned the spacing:
|
|
49
|
+
* a `columns={1}` form — and every `columns={n}` form once a narrow container collapses it to one
|
|
50
|
+
* column, i.e. every phone — laid its rows out 16px apart where the SAME fields without `columns`
|
|
51
|
+
* sat 12px apart. One canonical field rhythm on both paths: --form-grid-row-gap defaults to
|
|
52
|
+
* --form-field-row-gap, and the gutter becomes a knob at the same time (rule #45, 16px default).
|
|
53
|
+
*
|
|
54
|
+
* `:has(> .ui-form-field)` — a grid whose ITEMS are FormFields is a field grid, and that is the
|
|
55
|
+
* exact condition the margin reset above tests, so the two rules can never disagree about which
|
|
56
|
+
* mechanism owns a row. Deliberately NOT `.ui-form > …`: `columns={n}` is only one way to write
|
|
57
|
+
* this layout, and the Card-framed hand-written one (`Form > Card > CardContent > ResponsiveGrid
|
|
58
|
+
* > FormField*` — what a form with several titled sections must do, and what docs' invoice-form
|
|
59
|
+
* does) is the other. Keying on ancestry would give the two identical-looking layouts different
|
|
60
|
+
* row rhythms, which is the depth-dependence gh#295 removed from this file in the first place.
|
|
61
|
+
* A card/tile grid inside a form has no FormField items and keeps ResponsiveGrid's stack gap. */
|
|
62
|
+
.ui-responsive-grid:has(> .ui-form-field) {
|
|
63
|
+
row-gap: var(--form-grid-row-gap);
|
|
64
|
+
column-gap: var(--form-grid-column-gap);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/* colSpan is honoured only where the grid ACTUALLY HAS more than one column.
|
|
68
|
+
*
|
|
69
|
+
* `grid-column: span 2` on a one-column grid does not quietly degrade to one column: per spec it
|
|
70
|
+
* FABRICATES an implicit second track, that track auto-sizes to its content, and the real
|
|
71
|
+
* `minmax(0, 1fr)` track is left with the remainder — which at 320px is 0px. The field in it
|
|
72
|
+
* collapsed to zero width, and its Input's absolutely-positioned trailing affix (the clear
|
|
73
|
+
* button, `inset-inline-end: 8px` off a zero-width box) landed outside the frame with no
|
|
74
|
+
* scrollable ancestor between them: a control the keyboard could focus and the user could never
|
|
75
|
+
* see. That is a WCAG 2.1.1 failure, and a resting screenshot cannot show it because the field
|
|
76
|
+
* does not overflow — it disappears.
|
|
77
|
+
*
|
|
78
|
+
* The breakpoint mirrors ResponsiveGrid's own 2-column container query in layout.css, so the
|
|
79
|
+
* span turns on exactly when the second column does. Below it, `auto` puts the field in the
|
|
80
|
+
* single real track like every other field. */
|
|
81
|
+
.ui-responsive-grid > .ui-form-field {
|
|
82
|
+
grid-column: auto;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
@container responsive-grid (min-width: 40rem) {
|
|
86
|
+
.ui-responsive-grid > .ui-form-field {
|
|
87
|
+
grid-column: span var(--form-field-col-span, 1);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
46
91
|
/* Vertical (default + collapsed state): label stacked above the control column, LEFT-aligned.
|
|
47
92
|
* Gap = the shared --field-label-gap so FormField matches Descriptions and every label-above stack. */
|
|
48
93
|
.ui-form-field {
|