@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.
Files changed (145) hide show
  1. package/dist/app/date-format-labels.d.ts +1 -1
  2. package/dist/app/time-format-labels.d.ts +1 -1
  3. package/dist/components/data-display/badge.d.ts +1 -1
  4. package/dist/components/data-display/card.d.ts +4 -13
  5. package/dist/components/data-display/card.js +2 -15
  6. package/dist/components/data-display/data-table.d.ts +3 -8
  7. package/dist/components/data-display/data-table.js +106 -68
  8. package/dist/components/data-display/descriptions.js +34 -5
  9. package/dist/components/data-display/empty-state.js +1 -1
  10. package/dist/components/data-display/permission-matrix.js +7 -7
  11. package/dist/components/data-display/popover.js +2 -9
  12. package/dist/components/data-display/qr-code.d.ts +1 -1
  13. package/dist/components/data-display/scroll-area.d.ts +3 -1
  14. package/dist/components/data-display/scroll-area.js +150 -16
  15. package/dist/components/data-display/table.js +12 -3
  16. package/dist/components/data-entry/branch-scope-picker.js +4 -4
  17. package/dist/components/data-entry/calendar.js +15 -24
  18. package/dist/components/data-entry/cascader.js +67 -71
  19. package/dist/components/data-entry/checkbox.js +9 -1
  20. package/dist/components/data-entry/command.d.ts +6 -6
  21. package/dist/components/data-entry/date-picker.js +28 -15
  22. package/dist/components/data-entry/date-range-picker.js +10 -10
  23. package/dist/components/data-entry/form-field.js +7 -3
  24. package/dist/components/data-entry/index.d.ts +2 -1
  25. package/dist/components/data-entry/input.js +8 -19
  26. package/dist/components/data-entry/label.js +4 -2
  27. package/dist/components/data-entry/month-picker.js +16 -15
  28. package/dist/components/data-entry/month-range-picker.js +17 -16
  29. package/dist/components/data-entry/radio.js +6 -1
  30. package/dist/components/data-entry/search-select.js +29 -29
  31. package/dist/components/data-entry/select.js +15 -18
  32. package/dist/components/data-entry/switch.js +7 -1
  33. package/dist/components/data-entry/textarea.d.ts +56 -0
  34. package/dist/components/data-entry/textarea.js +103 -40
  35. package/dist/components/data-entry/time-picker.js +52 -52
  36. package/dist/components/data-entry/toggle-group.d.ts +1 -0
  37. package/dist/components/data-entry/toggle.d.ts +1 -1
  38. package/dist/components/data-entry/transfer.js +14 -15
  39. package/dist/components/data-entry/tree-select.js +16 -22
  40. package/dist/components/data-entry/upload-crop-dialog.js +4 -4
  41. package/dist/components/data-entry/upload.d.ts +1 -1
  42. package/dist/components/data-entry/upload.js +46 -46
  43. package/dist/components/feedback/alert.js +7 -14
  44. package/dist/components/feedback/dialog.d.ts +23 -32
  45. package/dist/components/feedback/dialog.js +16 -13
  46. package/dist/components/feedback/sheet.d.ts +1 -1
  47. package/dist/components/feedback/sheet.js +14 -14
  48. package/dist/components/feedback/skeleton.js +13 -10
  49. package/dist/components/feedback/sonner.d.ts +1 -1
  50. package/dist/components/feedback/sonner.js +17 -8
  51. package/dist/components/feedback/tooltip.js +1 -1
  52. package/dist/components/general/activity.d.ts +44 -0
  53. package/dist/components/general/activity.js +65 -0
  54. package/dist/components/general/button.d.ts +2 -2
  55. package/dist/components/general/button.js +11 -24
  56. package/dist/components/general/index.d.ts +2 -0
  57. package/dist/components/general/index.js +2 -0
  58. package/dist/components/layout/app-shell.js +5 -3
  59. package/dist/components/layout/auth-divider.d.ts +9 -2
  60. package/dist/components/layout/auth-divider.js +7 -11
  61. package/dist/components/layout/index.d.ts +1 -0
  62. package/dist/components/layout/page-container.d.ts +1 -1
  63. package/dist/components/layout/page-container.js +32 -20
  64. package/dist/components/layout/resizable.d.ts +12 -12
  65. package/dist/components/layout/separator.d.ts +1 -0
  66. package/dist/components/layout/service-role-panel.js +47 -42
  67. package/dist/components/layout/sidebar.js +13 -1
  68. package/dist/components/layout/split-pane.d.ts +12 -1
  69. package/dist/components/layout/split-pane.js +13 -4
  70. package/dist/components/navigation/app-setting-picker.js +5 -14
  71. package/dist/components/navigation/dropdown-menu.js +10 -16
  72. package/dist/components/navigation/menubar.d.ts +3 -6
  73. package/dist/components/navigation/pagination-utils.d.ts +15 -2
  74. package/dist/components/navigation/pagination-utils.js +27 -21
  75. package/dist/components/navigation/pagination.d.ts +4 -4
  76. package/dist/components/navigation/pagination.js +2 -2
  77. package/dist/components/navigation/steps.js +31 -67
  78. package/dist/components/navigation/tabs.js +17 -5
  79. package/dist/components/query/infinite-query-state.js +2 -2
  80. package/dist/components/ui/accordion.d.ts +1 -1
  81. package/dist/components/ui/avatar.d.ts +1 -1
  82. package/dist/components/ui/avatar.js +29 -10
  83. package/dist/components/ui/input-otp.d.ts +8 -6
  84. package/dist/components/ui/rating.js +1 -1
  85. package/dist/components/ui/separator.d.ts +27 -2
  86. package/dist/components/ui/separator.js +42 -11
  87. package/dist/components/ui/toggle-group.d.ts +15 -5
  88. package/dist/components/ui/toggle-group.js +43 -18
  89. package/dist/components/ui/toggle.d.ts +66 -5
  90. package/dist/components/ui/toggle.js +64 -9
  91. package/dist/i18n/messages/en.json +8 -0
  92. package/dist/i18n/messages/ja.json +8 -0
  93. package/dist/i18n/messages/vi.json +8 -0
  94. package/dist/lib/control-styles.d.ts +14 -0
  95. package/dist/lib/control-styles.js +2 -0
  96. package/dist/props/components/data-display.prop.d.ts +85 -3
  97. package/dist/props/components/data-entry.prop.d.ts +17 -0
  98. package/dist/props/components/general.prop.d.ts +52 -1
  99. package/dist/props/components/layout.prop.d.ts +137 -2
  100. package/dist/props/registry.d.ts +116 -5
  101. package/dist/props/registry.js +165 -4
  102. package/dist/props/vocabulary/index.d.ts +2 -2
  103. package/dist/props/vocabulary/interaction.prop.d.ts +15 -0
  104. package/dist/props/vocabulary/layout.prop.d.ts +8 -0
  105. package/dist/styles/alert-layout.css +69 -3
  106. package/dist/styles/badge-layout.css +2 -2
  107. package/dist/styles/base.css +6 -0
  108. package/dist/styles/card-layout.css +27 -7
  109. package/dist/styles/control.css +1150 -29
  110. package/dist/styles/data-display-layout.css +185 -11
  111. package/dist/styles/data-entry-layout.css +352 -1
  112. package/dist/styles/density.css +2 -2
  113. package/dist/styles/dialog-layout.css +203 -4
  114. package/dist/styles/focus-ring.css +155 -0
  115. package/dist/styles/form-layout.css +50 -5
  116. package/dist/styles/index.css +1 -0
  117. package/dist/styles/layout.css +412 -23
  118. package/dist/styles/motion.css +178 -0
  119. package/dist/styles/navigation-layout.css +398 -22
  120. package/dist/styles/shell-layout.css +137 -39
  121. package/dist/styles/table-layout.css +153 -22
  122. package/dist/styles/toggle.css +59 -0
  123. package/dist/tokens/base.css +5 -0
  124. package/dist/tokens/components/activity.css +64 -0
  125. package/dist/tokens/components/badge.css +7 -0
  126. package/dist/tokens/components/card.css +14 -10
  127. package/dist/tokens/components/chart.css +1 -1
  128. package/dist/tokens/components/control.css +358 -8
  129. package/dist/tokens/components/data-display.css +75 -0
  130. package/dist/tokens/components/data-entry.css +25 -0
  131. package/dist/tokens/components/descriptions.css +22 -0
  132. package/dist/tokens/components/feedback.css +143 -4
  133. package/dist/tokens/components/form.css +16 -0
  134. package/dist/tokens/components/legal-document.css +5 -1
  135. package/dist/tokens/components/navigation.css +109 -0
  136. package/dist/tokens/components/scroll-area.css +21 -0
  137. package/dist/tokens/components/separator.css +52 -0
  138. package/dist/tokens/components/shell.css +132 -20
  139. package/dist/tokens/components/table.css +52 -3
  140. package/dist/tokens/components/toggle.css +82 -0
  141. package/dist/tokens/components/upload.css +107 -0
  142. package/dist/tokens/foundation.css +171 -10
  143. package/dist/tokens/semantic/layout.css +120 -1
  144. package/package.json +84 -68
  145. 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: 50;
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: 50;
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-stack-xs);
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: 0.7;
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 AND breaks column
38
- * alignment — the FIRST field has no preceding sibling so no margin, while its row-mate does,
39
- * shifting row 1's columns 11px apart and inflating every grid track by the margin. Fields
40
- * merely NESTED somewhere inside a grid cell (a stacked pair in one cell) are not direct
41
- * children, keep the margin, and are unaffected. */
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 {
@@ -26,6 +26,7 @@
26
26
  @import "./motion.css";
27
27
  @import "./layout.css";
28
28
  @import "./control.css";
29
+ @import "./toggle.css";
29
30
  @import "./logo-layout.css";
30
31
  @import "./card-layout.css";
31
32
  @import "./text-layout.css";