@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
@@ -54,11 +54,6 @@
54
54
  color: hsl(var(--primary-foreground));
55
55
  }
56
56
 
57
- .ui-toggle:focus-visible {
58
- outline: none;
59
- box-shadow: 0 0 0 3px hsl(var(--focus-ring-color, var(--ring)) / 0.35);
60
- }
61
-
62
57
  .ui-toggle:disabled,
63
58
  .ui-toggle[data-disabled] {
64
59
  cursor: not-allowed;
@@ -83,14 +78,19 @@
83
78
  transition-duration: 150ms;
84
79
  }
85
80
 
86
- .ui-button:focus-visible {
81
+ .ui-button {
82
+ flex-shrink: 0;
83
+ font-size: var(--font-size-sm);
87
84
  outline: none;
88
- box-shadow: 0 0 0 var(--focus-ring-width) hsl(var(--focus-ring-color, var(--ring)));
89
85
  }
90
86
 
91
- .ui-button:disabled {
87
+ .ui-button:disabled,
88
+ .ui-button[data-loading] {
92
89
  pointer-events: none;
93
- opacity: 0.5;
90
+ }
91
+
92
+ .ui-button:disabled {
93
+ opacity: var(--disabled-opacity);
94
94
  }
95
95
 
96
96
  .ui-button svg {
@@ -249,13 +249,95 @@
249
249
 
250
250
  .ui-control-multiline {
251
251
  min-height: calc(var(--control-height) * 2.75);
252
- padding: var(--space-2) var(--control-padding-x);
252
+ padding-block: var(--control-multiline-padding-block);
253
+ padding-inline: var(--control-padding-x);
253
254
  font-size: var(--control-font-size);
254
255
  border-width: var(--control-border-width);
255
256
  border-style: solid;
256
257
  box-shadow: var(--control-shadow);
257
258
  }
258
259
 
260
+ /* TEXTAREA AUTO-GROW (gh#310) — the box grows with its content, bounded in TEXT ROWS.
261
+ *
262
+ * TECHNIQUE: the replicated-content grid. The wrapper is a one-cell grid; a hidden `::after`
263
+ * carrying the same text in the same typography occupies that cell and SIZES the row, and the
264
+ * textarea (a grid item, `align-self: stretch` by default) fills whatever the row became. The
265
+ * JS side therefore only ever WRITES one attribute — it never reads `scrollHeight`, never sets
266
+ * `style.height`, and so never forces a synchronous reflow per keystroke, and never touches
267
+ * `scrollTop` (which is what makes typing in the middle of a long value jump).
268
+ *
269
+ * Both bounds live on the PSEUDO-ELEMENT rather than on the textarea, deliberately: a
270
+ * pseudo-element is out of reach of Tailwind utilities, so `variant="ghost"`'s `min-h-0`
271
+ * (@layer utilities, which beats anything here in @layer components) cannot silently defeat
272
+ * the floor the way it would if the bounds sat on the control itself. */
273
+ .ui-textarea-autogrow {
274
+ position: relative;
275
+ display: grid;
276
+ /* `minmax(0, 1fr)`, never the default `auto`: an auto column is sized by the replica's
277
+ * MAX-CONTENT, so a single unbroken 2,000-character token would widen the column — and with
278
+ * it the control — to 18,000px instead of wrapping. Measured, not assumed. */
279
+ grid-template-columns: minmax(0, 1fr);
280
+ inline-size: 100%;
281
+ }
282
+
283
+ .ui-textarea-autogrow > .ui-control-multiline,
284
+ .ui-textarea-autogrow::after {
285
+ grid-area: 1 / 1 / 2 / 2;
286
+ }
287
+
288
+ .ui-textarea-autogrow::after {
289
+ content: attr(data-autogrow-value) " ";
290
+ visibility: hidden;
291
+ /* Explicit, not inherited from preflight: the replica's bounds are BORDER-box measures, and a
292
+ * content-box replica overshoots every height by the box inset. */
293
+ box-sizing: border-box;
294
+ margin: 0;
295
+ /* `pre-wrap` + `break-word` is the UA treatment of a textarea's own text: newlines are kept,
296
+ * an unbroken 2,000-character token wraps instead of widening the box. The trailing " " is
297
+ * what keeps a value ending in a newline one row taller, exactly as the control renders it. */
298
+ white-space: pre-wrap;
299
+ overflow-wrap: break-word;
300
+ font-size: var(--control-font-size);
301
+ line-height: var(--textarea-autogrow-line-height, var(--line-height-normal));
302
+ padding-block: var(--control-multiline-padding-block);
303
+ padding-inline: var(--control-padding-x);
304
+ border: var(--control-border-width) solid transparent;
305
+ /* The floor never undercuts the control tier, so a resting one-row composer still lines up
306
+ * with the Input / Button on its row; above it the box counts in rows. */
307
+ min-block-size: max(
308
+ var(--control-height),
309
+ calc(
310
+ var(--textarea-autogrow-min-height-rows) *
311
+ var(--textarea-autogrow-line-height, var(--line-height-normal)) *
312
+ var(--control-font-size) + var(--textarea-autogrow-box-inset)
313
+ )
314
+ );
315
+ max-block-size: calc(
316
+ var(--textarea-autogrow-max-height-rows) *
317
+ var(--textarea-autogrow-line-height, var(--line-height-normal)) * var(--control-font-size) +
318
+ var(--textarea-autogrow-box-inset)
319
+ );
320
+ }
321
+
322
+ /* The ghost variant draws no border, so the row maths must not add one back. */
323
+ .ui-textarea-autogrow--ghost {
324
+ --textarea-autogrow-box-inset: calc(var(--control-multiline-padding-block) * 2);
325
+ }
326
+
327
+ .ui-textarea-autogrow--ghost::after {
328
+ border-width: 0;
329
+ }
330
+
331
+ .ui-textarea-autogrow > .ui-control-multiline {
332
+ /* The mirror owns the floor; the control must be free to stretch down to it. */
333
+ min-block-size: 0;
334
+ /* Growth is the box's job, not a drag handle's, and past the ceiling the control scrolls
335
+ * itself rather than pushing the page. */
336
+ resize: none;
337
+ overflow-y: auto;
338
+ line-height: var(--textarea-autogrow-line-height, var(--line-height-normal));
339
+ }
340
+
259
341
  .ui-table-row {
260
342
  height: var(--table-row-height);
261
343
  }
@@ -309,6 +391,36 @@
309
391
  font-size: var(--choice-description-font-size);
310
392
  }
311
393
 
394
+ /* Label primitive (#319). Its box and type were Tailwind literals on label.tsx
395
+ * (`flex items-center gap-2 text-sm leading-none select-none`), so a service theme could not
396
+ * retune the label type ramp or the label→addon gap without forking the component.
397
+ *
398
+ * SOURCE ORDER IS LOAD-BEARING — this rule MUST stay AFTER `.ui-choice-label` above.
399
+ * `Field` renders `<Label className="ui-choice-label">`, so that element carries both classes at
400
+ * equal specificity (0,1,0) in the same layer and the LATER rule wins. Today the `text-sm`
401
+ * utility out-ranks `.ui-choice-label`'s `font-size: var(--font-size-base)` outright (utilities
402
+ * are layered after components); putting `.ui-label` EARLIER would hand every checkbox/radio
403
+ * label back to `.ui-choice-label` and silently change its type. `font-weight` is deliberately
404
+ * NOT declared here for the same reason: `.ui-choice-label` sets `--font-weight-normal` and
405
+ * Label's `font-medium` utility beats it today, so `font-medium` stays a utility.
406
+ *
407
+ * The disabled states stay utilities too (`peer-disabled:` / `group-data-[disabled=true]:`):
408
+ * a state must out-rank the resting rule, which in Tailwind v4 means the utilities layer. So
409
+ * does `line-height` — the `leading-none` it replaces existed precisely to beat the companion
410
+ * line-height that every `text-*` utility carries, and only a utility can still do that when a
411
+ * consumer passes their own `text-lg`. Its value is --control-label-line-height all the same.
412
+ *
413
+ * Not a regression for the auth shell: the canonical field-label override in shell-layout.css is
414
+ * INTENTIONALLY UNLAYERED (gh#263) and unlayered author CSS still beats every layer, so it keeps
415
+ * winning over these declarations exactly as it won over the utilities. */
416
+ .ui-label {
417
+ display: flex;
418
+ align-items: center;
419
+ gap: var(--control-label-space-gap);
420
+ font-size: var(--control-label-font-size);
421
+ user-select: none;
422
+ }
423
+
312
424
  .ui-checkbox,
313
425
  .ui-radio {
314
426
  width: var(--checkbox-size);
@@ -327,20 +439,19 @@
327
439
  border-radius: var(--radius-pill);
328
440
  }
329
441
 
330
- .ui-checkbox:focus-visible,
331
- .ui-radio:focus-visible,
332
- .ui-slider-thumb:focus-visible,
333
- .ui-switch:focus-visible {
334
- outline: none;
335
- box-shadow: 0 0 0 var(--focus-ring-width) hsl(var(--focus-ring-color, var(--ring)));
336
- }
337
-
442
+ /* `:disabled` only matches form elements, so a purely visual checkbox rendered as a <span>
443
+ * (Cascader's CheckboxVisual, Transfer's row marks) never picked this up and had to repeat
444
+ * `cursor-not-allowed opacity-50` on the component instead (#319). Matching [data-disabled]
445
+ * too lets those share the one rule. The alpha is the system token, not a literal 0.5. */
338
446
  .ui-checkbox:disabled,
339
447
  .ui-radio:disabled,
340
448
  .ui-slider-thumb:disabled,
341
- .ui-switch:disabled {
449
+ .ui-switch:disabled,
450
+ .ui-checkbox[data-disabled],
451
+ .ui-radio[data-disabled],
452
+ .ui-switch[data-disabled] {
342
453
  cursor: not-allowed;
343
- opacity: 0.5;
454
+ opacity: var(--disabled-opacity);
344
455
  }
345
456
 
346
457
  .ui-checkbox[data-state="checked"] {
@@ -385,7 +496,7 @@
385
496
  }
386
497
 
387
498
  .ui-switch[data-state="unchecked"] {
388
- background: hsl(var(--input));
499
+ background: var(--switch-unchecked-background, hsl(var(--input)));
389
500
  }
390
501
 
391
502
  .ui-switch-thumb {
@@ -534,8 +645,6 @@
534
645
  }
535
646
 
536
647
  .ui-command-input:focus-visible {
537
- box-shadow: none;
538
- outline: none;
539
648
  }
540
649
 
541
650
  .ui-command-list {
@@ -806,8 +915,8 @@
806
915
  justify-content: center;
807
916
  }
808
917
  .ui-otp-caret {
809
- width: 1px;
810
- height: 1rem;
918
+ width: var(--otp-caret-inline-size);
919
+ height: var(--otp-caret-block-size);
811
920
  background: hsl(var(--foreground));
812
921
  animation: ui-otp-blink 1s step-end infinite;
813
922
  }
@@ -817,8 +926,8 @@
817
926
  }
818
927
  }
819
928
  .ui-otp-separator-icon {
820
- width: 1rem;
821
- height: 1rem;
929
+ width: var(--otp-separator-icon-size);
930
+ height: var(--otp-separator-icon-size);
822
931
  color: hsl(var(--muted-foreground));
823
932
  }
824
933
 
@@ -861,8 +970,6 @@
861
970
  fill: currentColor;
862
971
  }
863
972
  .ui-rating-star:focus-visible {
864
- outline: var(--focus-ring-width) solid hsl(var(--focus-ring-color, var(--ring)));
865
- outline-offset: 2px;
866
973
  border-radius: var(--radius-sm);
867
974
  }
868
975
 
@@ -1003,3 +1110,1017 @@
1003
1110
  inline-size: 0.75rem;
1004
1111
  block-size: 0.75rem;
1005
1112
  }
1113
+
1114
+ @layer components {
1115
+ /* SELECT-FAMILY TRAILING AFFIX — the clear ✕ / chevron overlay parked at a trigger's inline
1116
+ * end. Select, SearchSelect and TagInput each repeated the same literal stack (#319); these
1117
+ * are the one shared set of knobs. */
1118
+ .ui-control-affix {
1119
+ position: absolute;
1120
+ inset-block: 0;
1121
+ inset-inline-end: var(--control-affix-inset-inline-end);
1122
+ display: flex;
1123
+ align-items: center;
1124
+ pointer-events: none;
1125
+ }
1126
+
1127
+ .ui-control-affix-action {
1128
+ pointer-events: auto;
1129
+ display: flex;
1130
+ inline-size: var(--control-affix-action-size);
1131
+ block-size: var(--control-affix-action-size);
1132
+ align-items: center;
1133
+ justify-content: center;
1134
+ border-radius: var(--control-affix-action-radius);
1135
+ opacity: var(--control-affix-rest-alpha);
1136
+ }
1137
+
1138
+ .ui-control-affix-action:hover,
1139
+ .ui-control-affix-action:focus-visible {
1140
+ opacity: 1;
1141
+ }
1142
+
1143
+ .ui-control-affix-icon {
1144
+ inline-size: var(--control-affix-icon-size);
1145
+ block-size: var(--control-affix-icon-size);
1146
+ flex-shrink: 0;
1147
+ }
1148
+
1149
+ /* The chevron is decorative and never interactive, so it keeps the resting weight. */
1150
+ .ui-control-affix-indicator {
1151
+ opacity: var(--control-affix-rest-alpha);
1152
+ }
1153
+
1154
+ .ui-control-trigger-affixed {
1155
+ padding-inline-end: var(--control-trigger-space-inline-end);
1156
+ }
1157
+
1158
+ /* SEARCH-SELECT — Popover + Command combobox. */
1159
+ .ui-search-select-panel {
1160
+ display: flex;
1161
+ flex-direction: column;
1162
+ max-block-size: var(--radix-popover-content-available-height);
1163
+ inline-size: max-content;
1164
+ max-inline-size: min(
1165
+ var(--search-select-panel-max-width),
1166
+ calc(100vw - var(--search-select-panel-viewport-inset))
1167
+ );
1168
+ min-inline-size: var(--radix-popover-trigger-width);
1169
+ padding: 0;
1170
+ }
1171
+
1172
+ .ui-search-select-command {
1173
+ display: flex;
1174
+ min-block-size: 0;
1175
+ flex-direction: column;
1176
+ }
1177
+
1178
+ .ui-search-select-search {
1179
+ flex-shrink: 0;
1180
+ border-block-end: 1px solid hsl(var(--border));
1181
+ }
1182
+
1183
+ /* The search field is the panel's own header, so it drops the standalone input chrome. */
1184
+ .ui-search-select-search-input {
1185
+ border: 0;
1186
+ border-radius: 0;
1187
+ box-shadow: none;
1188
+ }
1189
+
1190
+ /* The panel itself is the focused surface, so the inner search field suppresses its own RING
1191
+ * (this replaces a `focus-visible:ring-0` utility). It must NOT suppress the outline as well —
1192
+ * that would leave keyboard users with no focus affordance at all inside the panel. */
1193
+ .ui-search-select-search-input:focus-visible {
1194
+ box-shadow: none;
1195
+ }
1196
+
1197
+ .ui-search-select-list {
1198
+ max-block-size: none;
1199
+ min-block-size: 0;
1200
+ flex: 1 1 0%;
1201
+ overflow-y: auto;
1202
+ padding: var(--search-select-list-space-inset);
1203
+ }
1204
+
1205
+ /* The consumer's renderOption output — it must be able to truncate inside the row, which is
1206
+ * what min-inline-size:0 buys in a flex child. */
1207
+ .ui-search-select-option-slot {
1208
+ min-inline-size: 0;
1209
+ flex: 1 1 0%;
1210
+ }
1211
+
1212
+ .ui-search-select-option-body {
1213
+ display: flex;
1214
+ min-inline-size: 0;
1215
+ flex: 1 1 0%;
1216
+ align-items: center;
1217
+ gap: var(--search-select-option-space-gap);
1218
+ }
1219
+
1220
+ .ui-search-select-option-text {
1221
+ display: flex;
1222
+ min-inline-size: 0;
1223
+ flex-direction: column;
1224
+ }
1225
+
1226
+ .ui-search-select-option-label {
1227
+ overflow: hidden;
1228
+ text-overflow: ellipsis;
1229
+ white-space: nowrap;
1230
+ font-size: var(--search-select-option-font-size);
1231
+ }
1232
+
1233
+ .ui-search-select-option-sublabel {
1234
+ overflow: hidden;
1235
+ text-overflow: ellipsis;
1236
+ white-space: nowrap;
1237
+ font-size: var(--search-select-option-sublabel-font-size);
1238
+ color: hsl(var(--muted-foreground));
1239
+ }
1240
+
1241
+ .ui-search-select-status {
1242
+ display: flex;
1243
+ align-items: center;
1244
+ gap: var(--search-select-option-space-gap);
1245
+ padding-inline: var(--search-select-status-space-inline);
1246
+ padding-block: var(--search-select-status-space-block);
1247
+ font-size: var(--search-select-status-font-size);
1248
+ color: hsl(var(--muted-foreground));
1249
+ }
1250
+
1251
+ .ui-search-select-spinner {
1252
+ inline-size: var(--search-select-spinner-size);
1253
+ block-size: var(--search-select-spinner-size);
1254
+ }
1255
+
1256
+ /* Empty / error occupy the panel alone, so they get a taller centred block. */
1257
+ .ui-search-select-placeholder {
1258
+ padding-inline: var(--search-select-status-space-inline);
1259
+ padding-block: var(--search-select-placeholder-space-block);
1260
+ text-align: center;
1261
+ font-size: var(--search-select-status-font-size);
1262
+ color: hsl(var(--muted-foreground));
1263
+ }
1264
+
1265
+ .ui-search-select-placeholder[data-tone="destructive"] {
1266
+ color: hsl(var(--destructive));
1267
+ }
1268
+
1269
+ .ui-search-select-footer {
1270
+ flex-shrink: 0;
1271
+ padding: var(--search-select-footer-space-inset);
1272
+ border-block-start: 1px solid hsl(var(--border));
1273
+ }
1274
+ }
1275
+
1276
+ @layer components {
1277
+ /* CASCADER — column browser (desktop) + flat search list (query mode). */
1278
+ .ui-cascader-columns {
1279
+ display: flex;
1280
+ max-block-size: var(--cascader-columns-max-height);
1281
+ }
1282
+
1283
+ .ui-cascader-column {
1284
+ min-inline-size: var(--cascader-column-min-width);
1285
+ border-inline-end: 1px solid hsl(var(--border));
1286
+ }
1287
+
1288
+ .ui-cascader-column:last-child {
1289
+ border-inline-end: 0;
1290
+ }
1291
+
1292
+ .ui-cascader-option {
1293
+ display: flex;
1294
+ inline-size: 100%;
1295
+ align-items: center;
1296
+ gap: var(--cascader-option-space-gap);
1297
+ padding-inline: var(--cascader-option-space-inline);
1298
+ padding-block: var(--cascader-option-space-block);
1299
+ font-size: var(--cascader-option-font-size);
1300
+ outline: none;
1301
+ }
1302
+
1303
+ .ui-cascader-option-icon {
1304
+ margin-inline-end: var(--cascader-option-space-gap);
1305
+ inline-size: var(--cascader-option-icon-size);
1306
+ block-size: var(--cascader-option-icon-size);
1307
+ flex-shrink: 0;
1308
+ }
1309
+
1310
+ /* The chevron marks "has children"; it is decorative, so it keeps the resting weight. */
1311
+ .ui-cascader-option-chevron {
1312
+ inline-size: var(--cascader-option-icon-size);
1313
+ block-size: var(--cascader-option-icon-size);
1314
+ flex-shrink: 0;
1315
+ opacity: var(--control-affix-rest-alpha);
1316
+ }
1317
+
1318
+ .ui-cascader-list {
1319
+ max-block-size: var(--cascader-list-max-height);
1320
+ }
1321
+
1322
+ .ui-cascader-panel {
1323
+ padding: var(--cascader-panel-space-inset);
1324
+ }
1325
+
1326
+ .ui-cascader-popover {
1327
+ inline-size: auto;
1328
+ min-inline-size: var(--radix-popover-trigger-width);
1329
+ padding: 0;
1330
+ }
1331
+
1332
+ .ui-cascader-empty {
1333
+ padding-block: var(--cascader-empty-space-block);
1334
+ text-align: center;
1335
+ font-size: var(--cascader-option-font-size);
1336
+ color: hsl(var(--muted-foreground));
1337
+ }
1338
+
1339
+ /* Flat search result row — denser than a column row (no chevron to balance). */
1340
+ .ui-cascader-result {
1341
+ display: flex;
1342
+ inline-size: 100%;
1343
+ align-items: center;
1344
+ border-radius: var(--cascader-result-radius);
1345
+ padding-inline: var(--cascader-result-space-inline);
1346
+ padding-block: var(--cascader-result-space-block);
1347
+ font-size: var(--cascader-option-font-size);
1348
+ outline: none;
1349
+ }
1350
+
1351
+ .ui-cascader-result-icon {
1352
+ margin-inline-end: var(--cascader-result-icon-space-inline-end);
1353
+ inline-size: var(--cascader-option-icon-size);
1354
+ block-size: var(--cascader-option-icon-size);
1355
+ flex-shrink: 0;
1356
+ }
1357
+
1358
+ /* Reserve the check column even when unselected so labels do not jump on select. */
1359
+ .ui-cascader-result-icon[data-selected="false"] {
1360
+ opacity: 0;
1361
+ }
1362
+
1363
+ /* Cascader parks a SMALLER affix closer to the edge than SearchSelect does. Same shared knobs,
1364
+ * component-scoped values — the vocabulary is shared, the calibration is not. */
1365
+ .ui-cascader-affix {
1366
+ --control-affix-inset-inline-end: var(--space-3);
1367
+ --control-affix-action-size: var(--control-affix-icon-size);
1368
+ }
1369
+
1370
+ /* Disabled option rows keep pointer-events off and take the one system alpha. */
1371
+ .ui-cascader-option[data-disabled],
1372
+ .ui-cascader-trigger[data-disabled] {
1373
+ pointer-events: none;
1374
+ opacity: var(--disabled-opacity);
1375
+ }
1376
+ }
1377
+
1378
+ @layer components {
1379
+ /* SELECT — the Radix listbox popup. Its row rhythm, separator and label come from the shared
1380
+ * menu rules in navigation-layout.css (the same surface ContextMenu/Menubar/DropdownMenu use);
1381
+ * only what is genuinely Select's own lives here (#319). */
1382
+ .ui-select-content {
1383
+ position: relative;
1384
+ z-index: var(--overlay-z-index);
1385
+ max-height: var(--select-content-max-height);
1386
+ min-width: var(--menu-content-min-width);
1387
+ overflow: hidden;
1388
+ border-radius: var(--radius-md);
1389
+ border: 1px solid hsl(var(--border));
1390
+ background: hsl(var(--popover));
1391
+ color: hsl(var(--popover-foreground));
1392
+ box-shadow: var(--shadow-md);
1393
+ }
1394
+
1395
+ .ui-select-viewport {
1396
+ padding: var(--menu-content-space-inset);
1397
+ }
1398
+
1399
+ /* A listbox row has no leading icon column, so it takes a slightly wider inline inset to read
1400
+ * level with a menu row that does. */
1401
+ .ui-select-item {
1402
+ position: relative;
1403
+ inline-size: 100%;
1404
+ cursor: default;
1405
+ padding-inline: var(--select-item-space-inline);
1406
+ outline: none;
1407
+ user-select: none;
1408
+ }
1409
+
1410
+ .ui-select-item[data-state="checked"] {
1411
+ background: hsl(var(--accent));
1412
+ color: hsl(var(--foreground));
1413
+ font-weight: var(--font-weight-medium);
1414
+ }
1415
+
1416
+ .ui-select-item[data-highlighted] {
1417
+ background: hsl(var(--accent));
1418
+ color: hsl(var(--accent-foreground));
1419
+ }
1420
+
1421
+ .ui-select-item[data-disabled] {
1422
+ pointer-events: none;
1423
+ opacity: var(--disabled-opacity);
1424
+ }
1425
+
1426
+ .ui-select-item svg {
1427
+ pointer-events: none;
1428
+ flex-shrink: 0;
1429
+ }
1430
+
1431
+ .ui-select-label {
1432
+ font-weight: var(--font-weight-medium);
1433
+ }
1434
+
1435
+ .ui-select-scroll-button {
1436
+ display: flex;
1437
+ cursor: default;
1438
+ align-items: center;
1439
+ justify-content: center;
1440
+ padding-block: var(--select-scroll-button-space-block);
1441
+ }
1442
+
1443
+ .ui-select-scroll-icon,
1444
+ .ui-select-chevron {
1445
+ inline-size: var(--control-affix-icon-size);
1446
+ block-size: var(--control-affix-icon-size);
1447
+ }
1448
+
1449
+ .ui-select-chevron {
1450
+ flex-shrink: 0;
1451
+ opacity: var(--control-affix-rest-alpha);
1452
+ }
1453
+ }
1454
+
1455
+ @layer components {
1456
+ /* TIME PICKER — hour/minute/second scroll columns + an inline affix pair. */
1457
+ .ui-time-picker-panel {
1458
+ inline-size: var(--time-picker-panel-width);
1459
+ }
1460
+
1461
+ .ui-time-picker-panel[data-hour-cycle="h12"] {
1462
+ inline-size: var(--time-picker-panel-width-12h);
1463
+ }
1464
+
1465
+ .ui-time-picker-column {
1466
+ display: flex;
1467
+ min-inline-size: 0;
1468
+ flex: 1 1 0%;
1469
+ flex-direction: column;
1470
+ }
1471
+
1472
+ .ui-time-picker-column-heading {
1473
+ padding-inline: var(--time-picker-heading-space-inline);
1474
+ padding-block: var(--time-picker-heading-space-block);
1475
+ border-block-end: 1px solid hsl(var(--border));
1476
+ text-align: center;
1477
+ font-size: var(--time-picker-heading-font-size);
1478
+ font-weight: var(--font-weight-medium);
1479
+ color: hsl(var(--muted-foreground));
1480
+ }
1481
+
1482
+ .ui-time-picker-column-scroll {
1483
+ block-size: var(--time-picker-column-height);
1484
+ padding: var(--time-picker-column-space-inset);
1485
+ overflow-y: scroll;
1486
+ overscroll-behavior: contain;
1487
+ scrollbar-width: thin;
1488
+ scrollbar-gutter: stable;
1489
+ }
1490
+
1491
+ .ui-time-picker-option {
1492
+ display: flex;
1493
+ inline-size: 100%;
1494
+ align-items: center;
1495
+ justify-content: center;
1496
+ border-radius: var(--time-picker-option-radius);
1497
+ padding-block: var(--time-picker-option-space-block);
1498
+ font-size: var(--time-picker-option-font-size);
1499
+ font-variant-numeric: tabular-nums;
1500
+ transition: background-color 150ms ease;
1501
+ }
1502
+
1503
+ .ui-time-picker-option:hover {
1504
+ background: hsl(var(--accent));
1505
+ }
1506
+
1507
+ .ui-time-picker-option[data-selected="true"] {
1508
+ background: hsl(var(--primary));
1509
+ color: hsl(var(--primary-foreground));
1510
+ }
1511
+
1512
+ .ui-time-picker-option[data-selected="true"]:hover {
1513
+ background: hsl(var(--primary) / 0.9);
1514
+ }
1515
+
1516
+ .ui-time-picker-footer {
1517
+ padding: var(--time-picker-footer-space-inset);
1518
+ border-block-start: 1px solid hsl(var(--border));
1519
+ }
1520
+
1521
+ .ui-time-picker-popover {
1522
+ inline-size: auto;
1523
+ padding: 0;
1524
+ }
1525
+
1526
+ /* Two affix controls (clear + clock), so a wider reserved end than the single-affix triggers. */
1527
+ /* A popover whose content owns its own padding (a Calendar, a month grid) — the panel just
1528
+ * hugs it. Shared by the date/time pickers. */
1529
+ .ui-control-panel-flush {
1530
+ inline-size: auto;
1531
+ padding: 0;
1532
+ }
1533
+
1534
+ .ui-control-inline-affix-pair-affixed {
1535
+ padding-inline-end: var(--control-inline-affix-pair-space-inline-end);
1536
+ }
1537
+
1538
+ .ui-time-picker-affix {
1539
+ display: inline-flex;
1540
+ align-items: center;
1541
+ gap: var(--time-picker-affix-space-gap);
1542
+ }
1543
+
1544
+ /* Sits INSIDE the field rather than overlaying it, so it rests a touch heavier than the
1545
+ * overlay affix Select/SearchSelect use. Shared by Input and TimePicker. */
1546
+ .ui-control-inline-affix-action {
1547
+ display: inline-flex;
1548
+ inline-size: var(--control-inline-affix-size);
1549
+ block-size: var(--control-inline-affix-size);
1550
+ align-items: center;
1551
+ justify-content: center;
1552
+ border-radius: var(--control-affix-action-radius);
1553
+ color: hsl(var(--muted-foreground));
1554
+ opacity: var(--control-inline-affix-rest-alpha);
1555
+ transition: opacity 150ms ease;
1556
+ }
1557
+
1558
+ .ui-control-inline-affix-action:hover,
1559
+ .ui-control-inline-affix-action:focus-visible {
1560
+ color: hsl(var(--foreground));
1561
+ opacity: 1;
1562
+ }
1563
+
1564
+ .ui-control-inline-affix-icon {
1565
+ inline-size: var(--control-inline-affix-icon-size);
1566
+ block-size: var(--control-inline-affix-icon-size);
1567
+ }
1568
+
1569
+ /* The browser-owned file button, restyled to read as a control inside the control. */
1570
+ .ui-input::file-selector-button {
1571
+ display: inline-flex;
1572
+ block-size: var(--input-file-button-height);
1573
+ border: 0;
1574
+ background: transparent;
1575
+ color: hsl(var(--foreground));
1576
+ font-size: var(--input-file-button-font-size);
1577
+ font-weight: var(--font-weight-medium);
1578
+ }
1579
+
1580
+ /* A field in a flex row must be allowed to shrink below its content, or a long value pushes
1581
+ * the row wider instead of the text truncating. */
1582
+ .ui-input {
1583
+ min-inline-size: 0;
1584
+ }
1585
+
1586
+ .ui-input:disabled {
1587
+ pointer-events: none;
1588
+ cursor: not-allowed;
1589
+ opacity: var(--disabled-opacity);
1590
+ }
1591
+
1592
+ /* INPUT affix slots — leading/trailing controls parked inside the field box. */
1593
+ .ui-input-affix-wrapper {
1594
+ position: relative;
1595
+ display: inline-flex;
1596
+ inline-size: 100%;
1597
+ align-items: center;
1598
+ }
1599
+
1600
+ .ui-input-leading,
1601
+ .ui-input-trailing {
1602
+ position: absolute;
1603
+ inset-block: 0;
1604
+ display: inline-flex;
1605
+ align-items: center;
1606
+ }
1607
+
1608
+ .ui-input-leading {
1609
+ inset-inline-start: var(--control-inline-affix-inset-inline);
1610
+ pointer-events: none;
1611
+ color: hsl(var(--muted-foreground));
1612
+ }
1613
+
1614
+ .ui-input-trailing {
1615
+ inset-inline-end: var(--control-inline-affix-inset-inline);
1616
+ }
1617
+
1618
+ /* A textarea has no single line to centre an affix on, so its clear control parks at the
1619
+ * top-end corner. Everything else — box, radius, resting alpha, hover — is the shared
1620
+ * inside-field affix it borrows from Input. */
1621
+ .ui-textarea-clear {
1622
+ position: absolute;
1623
+ inset-block-start: var(--textarea-clear-inset-block-start);
1624
+ inset-inline-end: var(--control-inline-affix-inset-inline);
1625
+ }
1626
+
1627
+ /* The field reserves room on whichever side carries an affix, so its text never runs under. */
1628
+ .ui-input--leading-affix {
1629
+ padding-inline-start: var(--control-inline-affix-space-inline-end);
1630
+ }
1631
+
1632
+ .ui-input--trailing-affix {
1633
+ padding-inline-end: var(--control-inline-affix-space-inline-end);
1634
+ }
1635
+ }
1636
+
1637
+ @layer components {
1638
+ /* TREE SELECT — a Select whose panel holds a tree. */
1639
+ .ui-tree-select-popover {
1640
+ inline-size: var(--radix-popover-trigger-width);
1641
+ padding: 0;
1642
+ }
1643
+
1644
+ .ui-tree-select-list {
1645
+ max-block-size: var(--tree-select-list-max-height);
1646
+ }
1647
+
1648
+ .ui-tree-select-panel {
1649
+ padding: var(--menu-content-space-inset);
1650
+ }
1651
+
1652
+ .ui-tree-select-empty {
1653
+ padding-block: var(--tree-select-empty-space-block);
1654
+ text-align: center;
1655
+ font-size: var(--tree-select-row-font-size);
1656
+ color: hsl(var(--muted-foreground));
1657
+ }
1658
+
1659
+ /* Indent is base + depth × step, driven by `--tree-select-depth` on the row. It used to be a
1660
+ * magic expression inline in JSX, so no theme could reach it. */
1661
+ .ui-tree-select-row {
1662
+ display: flex;
1663
+ align-items: center;
1664
+ border-radius: var(--tree-select-row-radius);
1665
+ padding-block: var(--tree-select-row-space-block);
1666
+ padding-inline-end: var(--tree-select-row-space-inline-end);
1667
+ padding-inline-start: calc(
1668
+ var(--tree-select-depth-space-base) + var(--tree-select-depth, 0) *
1669
+ var(--tree-select-depth-space-step)
1670
+ );
1671
+ font-size: var(--tree-select-row-font-size);
1672
+ outline: none;
1673
+ }
1674
+
1675
+ .ui-tree-select-row:hover {
1676
+ background: hsl(var(--accent));
1677
+ color: hsl(var(--accent-foreground));
1678
+ }
1679
+
1680
+ .ui-tree-select-row[data-selected="true"] {
1681
+ background: hsl(var(--accent) / 0.6);
1682
+ }
1683
+
1684
+ .ui-tree-select-row[data-disabled] {
1685
+ pointer-events: none;
1686
+ opacity: var(--disabled-opacity);
1687
+ }
1688
+
1689
+ .ui-tree-select-toggle {
1690
+ display: flex;
1691
+ inline-size: var(--tree-select-toggle-size);
1692
+ block-size: var(--tree-select-toggle-size);
1693
+ flex-shrink: 0;
1694
+ align-items: center;
1695
+ justify-content: center;
1696
+ margin-inline-end: var(--tree-select-toggle-space-inline-end);
1697
+ border-radius: var(--tree-select-row-radius);
1698
+ }
1699
+
1700
+ .ui-tree-select-toggle[data-leaf] {
1701
+ visibility: hidden;
1702
+ }
1703
+
1704
+ .ui-tree-select-toggle-icon,
1705
+ .ui-tree-select-chevron {
1706
+ inline-size: var(--control-affix-icon-size);
1707
+ block-size: var(--control-affix-icon-size);
1708
+ }
1709
+
1710
+ .ui-tree-select-chevron {
1711
+ opacity: var(--control-affix-rest-alpha);
1712
+ }
1713
+
1714
+ .ui-tree-select-label {
1715
+ display: flex;
1716
+ flex: 1 1 0%;
1717
+ cursor: pointer;
1718
+ align-items: center;
1719
+ gap: var(--tree-select-label-space-gap);
1720
+ }
1721
+
1722
+ /* Its clear control is vertically centred against the whole field rather than laid out in a
1723
+ * flex overlay, so it keeps its own rule — but reads the shared affix knobs. */
1724
+ .ui-tree-select-clear {
1725
+ position: absolute;
1726
+ inset-inline-end: var(--space-3);
1727
+ inset-block-start: 50%;
1728
+ translate: 0 -50%;
1729
+ z-index: 10;
1730
+ display: flex;
1731
+ inline-size: var(--control-affix-action-size);
1732
+ block-size: var(--control-affix-action-size);
1733
+ align-items: center;
1734
+ justify-content: center;
1735
+ border-radius: var(--control-affix-action-radius);
1736
+ opacity: var(--control-affix-rest-alpha);
1737
+ }
1738
+
1739
+ .ui-tree-select-clear:hover,
1740
+ .ui-tree-select-clear:focus-visible {
1741
+ opacity: 1;
1742
+ }
1743
+
1744
+ /* Reserves room for a clear control that sits further in than the standard overlay affix. */
1745
+ .ui-tree-select-trigger-affixed {
1746
+ padding-inline-end: var(--space-10);
1747
+ }
1748
+ }
1749
+
1750
+ @layer components {
1751
+ /* CALENDAR — react-day-picker frame. The day/weekday cells keep sizing from --control-height
1752
+ * (a system tier decision); everything around them is now a knob. */
1753
+ .ui-calendar {
1754
+ position: relative;
1755
+ inline-size: fit-content;
1756
+ padding: var(--calendar-space-inset);
1757
+ }
1758
+
1759
+ .ui-calendar-months {
1760
+ display: flex;
1761
+ flex-direction: column;
1762
+ gap: var(--calendar-month-space-gap);
1763
+ }
1764
+
1765
+ @media (min-width: 40rem) {
1766
+ .ui-calendar-months {
1767
+ flex-direction: row;
1768
+ }
1769
+ }
1770
+
1771
+ .ui-calendar-month {
1772
+ position: relative;
1773
+ display: flex;
1774
+ flex-direction: column;
1775
+ gap: var(--calendar-month-space-gap);
1776
+ }
1777
+
1778
+ .ui-calendar-caption {
1779
+ display: flex;
1780
+ block-size: var(--control-height);
1781
+ inline-size: 100%;
1782
+ align-items: center;
1783
+ justify-content: center;
1784
+ padding-inline: var(--calendar-caption-space-inline);
1785
+ }
1786
+
1787
+ .ui-calendar-caption-label {
1788
+ font-size: var(--calendar-caption-font-size);
1789
+ font-weight: var(--font-weight-medium);
1790
+ }
1791
+
1792
+ .ui-calendar-nav {
1793
+ position: absolute;
1794
+ inset-inline: 0;
1795
+ inset-block-start: var(--calendar-nav-space-block-start);
1796
+ z-index: 10;
1797
+ display: flex;
1798
+ align-items: center;
1799
+ justify-content: space-between;
1800
+ padding-inline: var(--calendar-nav-space-inline);
1801
+ }
1802
+
1803
+ .ui-calendar-nav-button {
1804
+ background: transparent;
1805
+ padding: 0;
1806
+ opacity: var(--calendar-nav-rest-alpha);
1807
+ }
1808
+
1809
+ .ui-calendar-nav-button:hover {
1810
+ opacity: 1;
1811
+ }
1812
+
1813
+ .ui-calendar-chevron {
1814
+ inline-size: var(--calendar-chevron-size);
1815
+ block-size: var(--calendar-chevron-size);
1816
+ }
1817
+
1818
+ .ui-calendar-grid {
1819
+ margin-block-start: var(--calendar-grid-space-block-start);
1820
+ inline-size: 100%;
1821
+ border-collapse: collapse;
1822
+ }
1823
+
1824
+ .ui-calendar-weekday {
1825
+ inline-size: var(--control-height);
1826
+ border-radius: var(--calendar-weekday-radius);
1827
+ font-size: var(--calendar-weekday-font-size);
1828
+ font-weight: var(--font-weight-normal);
1829
+ color: hsl(var(--muted-foreground));
1830
+ }
1831
+
1832
+ .ui-calendar-week {
1833
+ margin-block-start: var(--calendar-week-space-block-start);
1834
+ display: flex;
1835
+ inline-size: 100%;
1836
+ }
1837
+
1838
+ .ui-calendar-day {
1839
+ position: relative;
1840
+ padding: 0;
1841
+ text-align: center;
1842
+ font-size: var(--calendar-day-font-size);
1843
+ }
1844
+
1845
+ .ui-calendar-day:focus-within {
1846
+ position: relative;
1847
+ z-index: 20;
1848
+ }
1849
+
1850
+ /* Scoped to the calendar root so it out-ranks `.ui-button--default-size`, which is declared
1851
+ * later in this file and would otherwise re-apply its block padding to a day cell. */
1852
+ .ui-calendar .ui-calendar-day-button {
1853
+ inline-size: var(--control-height);
1854
+ block-size: var(--control-height);
1855
+ padding: 0;
1856
+ font-weight: var(--font-weight-normal);
1857
+ border-radius: var(--calendar-day-radius);
1858
+ }
1859
+
1860
+ .ui-calendar .ui-calendar-day-button[aria-selected] {
1861
+ opacity: 1;
1862
+ }
1863
+
1864
+ .ui-calendar-day-disabled {
1865
+ color: hsl(var(--muted-foreground));
1866
+ opacity: var(--disabled-opacity);
1867
+ }
1868
+ }
1869
+
1870
+ @layer components {
1871
+ /* TRANSFER — two-pane list mover. */
1872
+ .ui-transfer {
1873
+ display: flex;
1874
+ flex-wrap: wrap;
1875
+ align-items: stretch;
1876
+ gap: var(--transfer-panes-space-gap);
1877
+ }
1878
+
1879
+ .ui-transfer-pane {
1880
+ display: flex;
1881
+ min-block-size: var(--transfer-pane-min-height);
1882
+ flex: 1 1 0%;
1883
+ flex-direction: column;
1884
+ border-radius: var(--transfer-pane-radius);
1885
+ border: 1px solid hsl(var(--border));
1886
+ background: hsl(var(--background));
1887
+ }
1888
+
1889
+ .ui-transfer-pane-header {
1890
+ display: flex;
1891
+ align-items: center;
1892
+ justify-content: space-between;
1893
+ padding-inline: var(--transfer-header-space-inline);
1894
+ padding-block: var(--transfer-header-space-block);
1895
+ border-block-end: 1px solid hsl(var(--border));
1896
+ font-size: var(--transfer-header-font-size);
1897
+ }
1898
+
1899
+ .ui-transfer-pane-check {
1900
+ display: flex;
1901
+ cursor: pointer;
1902
+ align-items: center;
1903
+ gap: var(--transfer-header-space-gap);
1904
+ font-weight: var(--font-weight-medium);
1905
+ }
1906
+
1907
+ .ui-transfer-search {
1908
+ padding: var(--transfer-search-space-inset);
1909
+ border-block-end: 1px solid hsl(var(--border));
1910
+ }
1911
+
1912
+ .ui-transfer-search[data-disabled] {
1913
+ pointer-events: none;
1914
+ opacity: var(--disabled-opacity);
1915
+ }
1916
+
1917
+ .ui-transfer-list {
1918
+ padding: var(--transfer-list-space-inset);
1919
+ }
1920
+
1921
+ .ui-transfer-empty {
1922
+ padding-block: var(--transfer-empty-space-block);
1923
+ text-align: center;
1924
+ font-size: var(--transfer-row-font-size);
1925
+ color: hsl(var(--muted-foreground));
1926
+ }
1927
+
1928
+ .ui-transfer-row {
1929
+ display: flex;
1930
+ cursor: pointer;
1931
+ align-items: flex-start;
1932
+ gap: var(--transfer-row-space-gap);
1933
+ border-radius: var(--transfer-row-radius);
1934
+ padding-inline: var(--transfer-row-space-inline);
1935
+ padding-block: var(--transfer-row-space-block);
1936
+ font-size: var(--transfer-row-font-size);
1937
+ }
1938
+
1939
+ /* Top-aligned against a two-line row, so it takes a hair of optical offset to sit level with
1940
+ * the first line's cap height rather than its box. */
1941
+ .ui-transfer-row-check {
1942
+ margin-block-start: var(--transfer-row-check-space-block-start);
1943
+ }
1944
+
1945
+ .ui-transfer-row-body {
1946
+ min-inline-size: 0;
1947
+ flex: 1 1 0%;
1948
+ }
1949
+
1950
+ .ui-transfer-row-description {
1951
+ font-size: var(--transfer-row-description-font-size);
1952
+ }
1953
+
1954
+ .ui-transfer-actions {
1955
+ display: flex;
1956
+ flex-direction: column;
1957
+ justify-content: center;
1958
+ gap: var(--transfer-actions-space-gap);
1959
+ }
1960
+
1961
+ .ui-transfer-action-icon {
1962
+ inline-size: var(--transfer-action-icon-size);
1963
+ block-size: var(--transfer-action-icon-size);
1964
+ }
1965
+ }
1966
+
1967
+ @layer components {
1968
+ /* MONTH PICKER — the year-nav + 3-column month grid shared by MonthPicker and
1969
+ * MonthRangePicker. One rule set for both; they used to hold identical copies. */
1970
+ .ui-control-composite-field {
1971
+ display: flex;
1972
+ inline-size: 100%;
1973
+ min-inline-size: 0;
1974
+ align-items: center;
1975
+ gap: var(--control-composite-field-space-gap);
1976
+ border-radius: var(--control-radius);
1977
+ border: 1px solid hsl(var(--input));
1978
+ background: hsl(var(--background));
1979
+ outline: none;
1980
+ transition:
1981
+ color 150ms ease,
1982
+ box-shadow 150ms ease;
1983
+ }
1984
+
1985
+ .ui-control-composite-field[data-disabled] {
1986
+ pointer-events: none;
1987
+ cursor: not-allowed;
1988
+ opacity: var(--disabled-opacity);
1989
+ }
1990
+
1991
+ .ui-month-picker-input {
1992
+ min-inline-size: 0;
1993
+ flex: 1 1 0%;
1994
+ background: transparent;
1995
+ font-variant-numeric: tabular-nums;
1996
+ outline: none;
1997
+ }
1998
+
1999
+ .ui-month-picker-input::placeholder {
2000
+ color: hsl(var(--muted-foreground));
2001
+ }
2002
+
2003
+ .ui-month-picker-input:disabled {
2004
+ cursor: not-allowed;
2005
+ }
2006
+
2007
+ .ui-month-picker-icon {
2008
+ inline-size: var(--month-picker-icon-size);
2009
+ block-size: var(--month-picker-icon-size);
2010
+ flex-shrink: 0;
2011
+ }
2012
+
2013
+ .ui-month-picker-separator-icon {
2014
+ inline-size: var(--month-picker-separator-icon-size);
2015
+ block-size: var(--month-picker-separator-icon-size);
2016
+ flex-shrink: 0;
2017
+ color: hsl(var(--muted-foreground));
2018
+ }
2019
+
2020
+ .ui-month-picker-panel {
2021
+ inline-size: auto;
2022
+ padding: var(--month-picker-panel-space-inset);
2023
+ }
2024
+
2025
+ .ui-month-picker-nav {
2026
+ display: flex;
2027
+ align-items: center;
2028
+ justify-content: space-between;
2029
+ }
2030
+
2031
+ .ui-month-picker-nav-button {
2032
+ background: transparent;
2033
+ opacity: var(--month-picker-nav-rest-alpha);
2034
+ }
2035
+
2036
+ .ui-month-picker-nav-button:hover {
2037
+ opacity: 1;
2038
+ }
2039
+
2040
+ .ui-month-picker-nav-label {
2041
+ font-size: var(--month-picker-nav-font-size);
2042
+ font-weight: var(--font-weight-medium);
2043
+ }
2044
+
2045
+ .ui-month-picker-grid {
2046
+ margin-block-start: var(--month-picker-grid-space-block-start);
2047
+ display: grid;
2048
+ /* 3 columns × 4 rows is the month grid's shape, not a service knob — 12 months only read
2049
+ * as a calendar year in this arrangement. */
2050
+ grid-template-columns: repeat(3, minmax(0, 1fr));
2051
+ gap: var(--month-picker-grid-space-gap);
2052
+ }
2053
+
2054
+ .ui-month-picker-cell {
2055
+ padding-inline: var(--month-picker-cell-space-inline);
2056
+ font-weight: var(--font-weight-normal);
2057
+ }
2058
+
2059
+ /* BUTTON size-xs + icon-adjacent padding + the count pill (#319). */
2060
+ .ui-button--xs {
2061
+ height: var(--button-xs-height);
2062
+ gap: var(--button-xs-space-gap);
2063
+ padding-inline: var(--button-xs-space-inline);
2064
+ font-size: var(--button-xs-font-size);
2065
+ }
2066
+
2067
+ .ui-button--default-size,
2068
+ .ui-button--md {
2069
+ padding-block: var(--button-space-block);
2070
+ }
2071
+
2072
+ /* An icon-only child needs less inline room than a label, or the glyph floats in a wide box. */
2073
+ .ui-button--default-size:has(> svg),
2074
+ .ui-button--md:has(> svg) {
2075
+ padding-inline: var(--button-icon-space-inline-md);
2076
+ }
2077
+
2078
+ .ui-button--sm:has(> svg) {
2079
+ padding-inline: var(--button-icon-space-inline-sm);
2080
+ }
2081
+
2082
+ .ui-button--lg:has(> svg) {
2083
+ padding-inline: var(--button-icon-space-inline-lg);
2084
+ }
2085
+
2086
+ .ui-button--xs:has(> svg) {
2087
+ padding-inline: var(--button-icon-space-inline-xs);
2088
+ }
2089
+
2090
+ /* COUNT PILL (gh#320). Opaque role fills, not a translucent tint over the button's surface: a
2091
+ * tint takes its contrast from whatever is behind it, and five variant×theme×hover combinations
2092
+ * measured below WCAG 1.4.3's 4.5:1 for small text. The measurements and the share-vs-split
2093
+ * reasoning live in tokens/components/control.css beside the knobs. Same treatment gh#312
2094
+ * validated on Toggle. */
2095
+ .ui-button-count {
2096
+ display: inline-flex;
2097
+ min-inline-size: var(--button-count-min-width);
2098
+ align-items: center;
2099
+ justify-content: center;
2100
+ border-radius: var(--radius-pill);
2101
+ padding-inline: var(--button-count-space-inline);
2102
+ background: var(--button-count-background, hsl(var(--muted)));
2103
+ color: var(--button-count-color, hsl(var(--foreground)));
2104
+ font-size: var(--button-count-font-size);
2105
+ line-height: 1;
2106
+ font-variant-numeric: tabular-nums;
2107
+ }
2108
+
2109
+ /* Each filled variant wears its OWN label pair, swapped. That makes the pill exactly as legible
2110
+ * as the label it sits beside — it cannot be less legible under any theme that keeps the button
2111
+ * itself readable, which a fixed colour could not promise. */
2112
+ .ui-button--default .ui-button-count {
2113
+ background: var(--button-count-default-background, hsl(var(--primary-foreground)));
2114
+ color: var(--button-count-default-color, hsl(var(--primary)));
2115
+ }
2116
+
2117
+ .ui-button--destructive .ui-button-count {
2118
+ background: var(--button-count-destructive-background, hsl(var(--destructive-foreground)));
2119
+ color: var(--button-count-destructive-color, hsl(var(--destructive)));
2120
+ }
2121
+
2122
+ .ui-button--secondary .ui-button-count {
2123
+ background: var(--button-count-secondary-background, hsl(var(--secondary-foreground)));
2124
+ color: var(--button-count-secondary-color, hsl(var(--secondary)));
2125
+ }
2126
+ }