@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
@@ -61,6 +61,125 @@
61
61
  background: var(--avatar-background, hsl(var(--muted)));
62
62
  }
63
63
 
64
+ /* PRESENCE (gh#309) — the realtime reachability dot on the mark itself.
65
+ *
66
+ * Opt-in ONLY: with no `presence` prop the avatar emits neither the attribute nor the node, so
67
+ * every existing call site keeps today's DOM and today's clip. WITH it the ROOT stops clipping —
68
+ * the dot straddles the mark's edge, which is exactly why a consumer could never place it from
69
+ * outside — and the clip moves down onto the image/fallback, which is where it was doing the real
70
+ * work. `border-radius: inherit` keeps that clip correct for both shapes: --radius-pill for a
71
+ * person, --avatar-square-radius for an entity mark. */
72
+ .ui-avatar[data-presence] {
73
+ overflow: visible;
74
+ }
75
+
76
+ .ui-avatar[data-presence] > .ui-avatar-image,
77
+ .ui-avatar[data-presence] > .ui-avatar-fallback {
78
+ overflow: hidden;
79
+ border-radius: inherit;
80
+ }
81
+
82
+ /* NEVER COLOUR-ALONE (WCAG 1.4.1). Three independent encodings ride together: the localized
83
+ * sr-only string inside this span, a semantic ROLE colour, and a SHAPE — filled · half-filled ·
84
+ * barred · hollow — so the four states stay apart in greyscale, for a deuteranope, and under
85
+ * forced colors. `box-sizing: border-box` makes --avatar-presence-size the OUTER diameter, so the
86
+ * state stroke never changes the dot's footprint between states.
87
+ *
88
+ * The dot is a state, not an event: nothing here animates, so there is nothing for
89
+ * prefers-reduced-motion to switch off. Presence is deliberately NOT a live region either — a
90
+ * roster of 40 avatars resyncing over a socket would flood a screen reader; announcing a CHANGE is
91
+ * the consumer's call, in the consumer's own live region. */
92
+ [data-slot="avatar-presence"] {
93
+ position: absolute;
94
+ inset-block-end: var(--avatar-presence-inset);
95
+ inset-inline-end: var(--avatar-presence-inset);
96
+ display: block;
97
+ box-sizing: border-box;
98
+ inline-size: var(--avatar-presence-size);
99
+ block-size: var(--avatar-presence-size);
100
+ min-inline-size: var(--avatar-presence-min-size);
101
+ min-block-size: var(--avatar-presence-min-size);
102
+ border: var(--avatar-presence-stroke-width) solid transparent;
103
+ border-radius: var(--radius-pill);
104
+ background-origin: border-box;
105
+ /* Separator ring — chrome, therefore a knob (#44). It is what keeps the dot legible over a busy
106
+ * photo; a service that puts the avatar on a coloured surface retunes the colour once. */
107
+ box-shadow: 0 0 0 var(--avatar-presence-ring-width)
108
+ hsl(var(--avatar-presence-ring-color, var(--background)));
109
+ }
110
+
111
+ /* ONLINE — a SOLID disc. */
112
+ [data-slot="avatar-presence"][data-presence="online"] {
113
+ border-color: hsl(var(--avatar-presence-online-color, var(--success)));
114
+ background-color: hsl(var(--avatar-presence-online-color, var(--success)));
115
+ }
116
+
117
+ /* AWAY — a HALF-FILLED disc (block-end half), the one silhouette that survives greyscale next to
118
+ * both the solid and the hollow states. */
119
+ [data-slot="avatar-presence"][data-presence="away"] {
120
+ border-color: hsl(var(--avatar-presence-away-color, var(--warning)));
121
+ background-color: hsl(var(--avatar-presence-ring-color, var(--background)));
122
+ background-image: linear-gradient(
123
+ to bottom,
124
+ transparent 0 50%,
125
+ hsl(var(--avatar-presence-away-color, var(--warning))) 50%
126
+ );
127
+ }
128
+
129
+ /* BUSY (do-not-disturb) — a solid disc CUT by a horizontal bar. */
130
+ [data-slot="avatar-presence"][data-presence="busy"] {
131
+ border-color: hsl(var(--avatar-presence-busy-color, var(--destructive)));
132
+ background-color: hsl(var(--avatar-presence-busy-color, var(--destructive)));
133
+ }
134
+
135
+ /* `inset: 0; margin: auto` centres the bar in BOTH axes with no translate, so it stays centred
136
+ * under dir="rtl" (a start-anchored `translate(-50%)` would slide it off-centre when the inline
137
+ * axis flips). */
138
+ [data-slot="avatar-presence"][data-presence="busy"]::before {
139
+ content: "";
140
+ position: absolute;
141
+ inset: 0;
142
+ margin: auto;
143
+ inline-size: var(--avatar-presence-bar-inline-size);
144
+ block-size: var(--avatar-presence-bar-block-size);
145
+ border-radius: var(--radius-pill);
146
+ background: hsl(var(--avatar-presence-ring-color, var(--background)));
147
+ }
148
+
149
+ /* OFFLINE — a HOLLOW ring. Distinct from an ABSENT `presence`, which draws nothing at all: absent
150
+ * means "this entity has no presence concept" (an organization mark), offline means "known to be
151
+ * unreachable" — the same distinction ListRow draws between an omitted and a `false` `unread`. */
152
+ [data-slot="avatar-presence"][data-presence="offline"] {
153
+ border-color: hsl(var(--avatar-presence-offline-color, var(--muted-foreground)));
154
+ background-color: hsl(var(--avatar-presence-ring-color, var(--background)));
155
+ }
156
+
157
+ /* FORCED COLORS — the UA replaces author colours, and box-shadow is dropped outright, so the
158
+ * separator ring would vanish into the mark and all four states would collapse to one swatch.
159
+ * System-colour keywords ARE honoured inside this block, so re-draw the ring as an outline and
160
+ * re-state each silhouette in Canvas / CanvasText: the shape encoding survives, which is the half
161
+ * of WCAG 1.4.1 that a colour-only dot loses here first. */
162
+ @media (forced-colors: active) {
163
+ [data-slot="avatar-presence"] {
164
+ border-color: CanvasText;
165
+ background-color: CanvasText;
166
+ background-image: none;
167
+ outline: var(--avatar-presence-ring-width) solid Canvas;
168
+ }
169
+
170
+ [data-slot="avatar-presence"][data-presence="away"] {
171
+ background-image: linear-gradient(to bottom, Canvas 0 50%, CanvasText 50%);
172
+ }
173
+
174
+ [data-slot="avatar-presence"][data-presence="offline"] {
175
+ background-color: Canvas;
176
+ }
177
+
178
+ [data-slot="avatar-presence"][data-presence="busy"]::before {
179
+ background: Canvas;
180
+ }
181
+ }
182
+
64
183
  .ui-progress {
65
184
  display: flex;
66
185
  flex-direction: column;
@@ -305,8 +424,6 @@
305
424
  text-decoration: underline;
306
425
  }
307
426
  .ui-accordion-trigger:focus-visible {
308
- outline: var(--focus-ring-width) solid hsl(var(--focus-ring-color, var(--ring)));
309
- outline-offset: 2px;
310
427
  border-radius: var(--radius-sm);
311
428
  }
312
429
  .ui-accordion-trigger:disabled,
@@ -316,8 +433,8 @@
316
433
  pointer-events: none;
317
434
  }
318
435
  .ui-accordion-chevron {
319
- width: 1rem;
320
- height: 1rem;
436
+ width: var(--accordion-chevron-size);
437
+ height: var(--accordion-chevron-size);
321
438
  color: hsl(var(--muted-foreground));
322
439
  transition: transform 0.2s ease;
323
440
  flex-shrink: 0;
@@ -397,8 +514,8 @@
397
514
  }
398
515
 
399
516
  .ui-carousel-arrow {
400
- width: 1rem;
401
- height: 1rem;
517
+ width: var(--carousel-arrow-icon-size);
518
+ height: var(--carousel-arrow-icon-size);
402
519
  }
403
520
 
404
521
  /* Dot indicators — horizontal, centered row of round dots; the active one fills + widens. */
@@ -431,11 +548,6 @@
431
548
  background: hsl(var(--primary));
432
549
  }
433
550
 
434
- .ui-carousel-dot:focus-visible {
435
- outline: var(--focus-ring-width) solid hsl(var(--focus-ring-color, var(--ring)));
436
- outline-offset: 2px;
437
- }
438
-
439
551
  /* Vertical orientation — stack the dots into a column beside the track. */
440
552
  .ui-carousel[data-orientation="vertical"] .ui-carousel-dots {
441
553
  flex-direction: column;
@@ -642,3 +754,65 @@
642
754
  inline-size: var(--qr-code-size-lg);
643
755
  }
644
756
  }
757
+
758
+ @layer components {
759
+ /* PERMISSION MATRIX — `.ui-permission-matrix` was a bare hook with no rule anywhere, so every
760
+ * constant sat on the component (#319). */
761
+ .ui-permission-matrix-table {
762
+ min-inline-size: var(--permission-matrix-min-width);
763
+ }
764
+
765
+ /* Sticky first column: the scrolling grid passes UNDER it, so it needs an opaque ground.
766
+ * Scoped through the matrix root because `[data-slot="table-head"]` sets its own background at
767
+ * the same specificity — an unscoped rule would win only by import order, which is not a
768
+ * property worth depending on. */
769
+ .ui-permission-matrix .ui-permission-matrix-pin {
770
+ position: sticky;
771
+ inset-inline-start: 0;
772
+ /* Above the scrolling cells, below any portaled overlay (--overlay-z-index). */
773
+ z-index: 20;
774
+ background-color: inherit;
775
+ border-inline-end: 1px solid hsl(var(--border));
776
+ inline-size: var(--permission-matrix-label-width);
777
+ min-inline-size: var(--permission-matrix-label-width);
778
+ }
779
+
780
+ .ui-permission-matrix-role {
781
+ display: inline-flex;
782
+ align-items: center;
783
+ gap: var(--permission-matrix-role-space-gap);
784
+ }
785
+
786
+ .ui-permission-matrix-name {
787
+ display: inline-flex;
788
+ align-items: center;
789
+ gap: var(--permission-matrix-name-space-gap);
790
+ }
791
+
792
+ .ui-permission-matrix-cell-icon {
793
+ inline-size: var(--permission-matrix-cell-icon-size);
794
+ block-size: var(--permission-matrix-cell-icon-size);
795
+ }
796
+ }
797
+
798
+ @layer components {
799
+ /* SCROLL AREA — the custom scrollbar rail (#319). There was no `.ui-scroll*` rule anywhere: the
800
+ * rail's whole geometry lived on the component as Tailwind literals, so a service could not
801
+ * retune it. Logical sizing (inline-/block-size) keeps the rail correct in RTL and in vertical
802
+ * writing modes; the thumb radius is a knob so a sharp-cornered theme squares it once. */
803
+ .ui-scroll-area-bar {
804
+ padding: var(--scroll-area-bar-padding);
805
+ }
806
+
807
+ .ui-scroll-area-bar--vertical {
808
+ inline-size: var(--scroll-area-bar-size);
809
+ }
810
+
811
+ .ui-scroll-area-bar--horizontal {
812
+ block-size: var(--scroll-area-bar-size);
813
+ }
814
+
815
+ .ui-scroll-area-thumb {
816
+ border-radius: var(--scroll-area-thumb-radius);
817
+ }
818
+ }
@@ -84,7 +84,6 @@
84
84
 
85
85
  .ui-time-input:focus-visible {
86
86
  border-color: hsl(var(--focus-ring-color, var(--ring)));
87
- box-shadow: 0 0 0 3px hsl(var(--focus-ring-color, var(--ring)) / 0.3);
88
87
  }
89
88
 
90
89
  .ui-time-input[aria-invalid="true"] {
@@ -162,3 +161,355 @@
162
161
  color: hsl(var(--destructive));
163
162
  }
164
163
  }
164
+
165
+ @layer components {
166
+ /* UPLOAD — five variants (dropzone · button · picture · picture-card · avatar) that used to
167
+ * carry their entire box as Tailwind literals on the component: 66 of them, the worst file in
168
+ * the library (#319). A service could not resize the avatar, dial back the dropzone's generous
169
+ * inset, or align the file row to its own grid without forking. Every constant is now a knob
170
+ * (rule #45); colour scrims are role-mirror knobs resolved at the CALL SITE so a scoped
171
+ * [data-tenant]/.dark override reaches them (docs/TOKENS.md). */
172
+
173
+ .ui-upload-dropzone {
174
+ cursor: pointer;
175
+ padding: var(--upload-dropzone-space-inset);
176
+ border-radius: var(--upload-dropzone-radius);
177
+ border: var(--upload-dropzone-border-width) dashed hsl(var(--border));
178
+ text-align: center;
179
+ transition: border-color 150ms ease;
180
+ }
181
+
182
+ .ui-upload-dropzone[data-drag-active] {
183
+ border-color: hsl(var(--primary));
184
+ background-color: hsl(var(--primary) / 0.05);
185
+ }
186
+
187
+ .ui-upload-dropzone:not([data-drag-active]):hover {
188
+ border-color: hsl(var(--primary) / 0.5);
189
+ }
190
+
191
+ .ui-upload-dropzone-icon {
192
+ display: block;
193
+ margin-inline: auto;
194
+ inline-size: var(--upload-dropzone-icon-size);
195
+ block-size: var(--upload-dropzone-icon-size);
196
+ color: hsl(var(--muted-foreground));
197
+ }
198
+
199
+ .ui-upload-dropzone-hint {
200
+ margin-block-start: var(--upload-dropzone-hint-space-block-start);
201
+ font-size: var(--upload-dropzone-hint-font-size);
202
+ }
203
+
204
+ .ui-upload-dropzone-meta {
205
+ margin-block-start: var(--upload-dropzone-meta-space-block-start);
206
+ font-size: var(--upload-dropzone-meta-font-size);
207
+ color: hsl(var(--muted-foreground));
208
+ }
209
+
210
+ .ui-upload-grid {
211
+ display: flex;
212
+ flex-wrap: wrap;
213
+ gap: var(--upload-tile-space-gap);
214
+ }
215
+
216
+ .ui-upload-tile {
217
+ position: relative;
218
+ inline-size: var(--upload-tile-size);
219
+ block-size: var(--upload-tile-size);
220
+ overflow: hidden;
221
+ border-radius: var(--upload-tile-radius);
222
+ border: 1px solid hsl(var(--border));
223
+ background-color: hsl(var(--muted));
224
+ }
225
+
226
+ .ui-upload-tile-add {
227
+ display: flex;
228
+ flex-direction: column;
229
+ align-items: center;
230
+ justify-content: center;
231
+ inline-size: var(--upload-tile-size);
232
+ block-size: var(--upload-tile-size);
233
+ border-radius: var(--upload-tile-radius);
234
+ border: 1px dashed hsl(var(--border));
235
+ color: hsl(var(--muted-foreground));
236
+ transition:
237
+ border-color 150ms ease,
238
+ color 150ms ease;
239
+ }
240
+
241
+ .ui-upload-tile-add:hover {
242
+ border-color: hsl(var(--primary));
243
+ color: hsl(var(--primary));
244
+ }
245
+
246
+ .ui-upload-tile-add-icon {
247
+ inline-size: var(--upload-tile-icon-size);
248
+ block-size: var(--upload-tile-icon-size);
249
+ }
250
+
251
+ .ui-upload-tile-add-label {
252
+ margin-block-start: var(--upload-tile-label-space-block-start);
253
+ font-size: var(--upload-tile-label-font-size);
254
+ }
255
+
256
+ /* Busy scrim — one share of the surface role, resolved here so a scoped theme reaches it. */
257
+ .ui-upload-overlay {
258
+ position: absolute;
259
+ inset: 0;
260
+ display: flex;
261
+ align-items: center;
262
+ justify-content: center;
263
+ background-color: var(
264
+ --upload-overlay-background,
265
+ color-mix(in srgb, hsl(var(--background)) var(--upload-overlay-alpha), transparent)
266
+ );
267
+ font-size: var(--upload-tile-overlay-font-size);
268
+ }
269
+
270
+ .ui-upload-tile-placeholder {
271
+ display: flex;
272
+ inline-size: 100%;
273
+ block-size: 100%;
274
+ align-items: center;
275
+ justify-content: center;
276
+ font-size: var(--upload-tile-overlay-font-size);
277
+ color: hsl(var(--muted-foreground));
278
+ }
279
+
280
+ .ui-upload-tile-remove {
281
+ position: absolute;
282
+ inset-block-start: var(--upload-remove-offset);
283
+ inset-inline-end: var(--upload-remove-offset);
284
+ padding: calc(var(--upload-remove-space-inset) / 2);
285
+ border-radius: var(--radius-pill);
286
+ background-color: hsl(var(--background) / 0.9);
287
+ box-shadow: var(--shadow-sm);
288
+ }
289
+
290
+ .ui-upload-tile-remove:hover {
291
+ background-color: hsl(var(--destructive));
292
+ color: hsl(var(--destructive-foreground));
293
+ }
294
+
295
+ .ui-upload-remove-icon {
296
+ inline-size: var(--upload-remove-icon-size);
297
+ block-size: var(--upload-remove-icon-size);
298
+ }
299
+
300
+ .ui-upload-picture {
301
+ max-inline-size: var(--upload-picture-max-width);
302
+ }
303
+
304
+ .ui-upload-picture-frame {
305
+ position: relative;
306
+ overflow: hidden;
307
+ border-radius: var(--upload-picture-radius);
308
+ border: 1px solid hsl(var(--border));
309
+ }
310
+
311
+ .ui-upload-picture-img {
312
+ inline-size: 100%;
313
+ max-block-size: var(--upload-picture-preview-max-height);
314
+ object-fit: cover;
315
+ }
316
+
317
+ .ui-upload-picture-overlay {
318
+ font-size: var(--upload-picture-overlay-font-size);
319
+ }
320
+
321
+ .ui-upload-picture-badge {
322
+ position: absolute;
323
+ inset-block-start: var(--upload-picture-badge-offset);
324
+ inset-inline-start: var(--upload-picture-badge-offset);
325
+ padding-inline: var(--upload-picture-badge-space-inline);
326
+ padding-block: var(--upload-picture-badge-space-block);
327
+ border-radius: var(--upload-picture-badge-radius);
328
+ background-color: hsl(var(--warning));
329
+ color: hsl(var(--warning-foreground));
330
+ font-size: var(--upload-picture-badge-font-size);
331
+ }
332
+
333
+ .ui-upload-picture-empty {
334
+ display: flex;
335
+ flex-direction: column;
336
+ align-items: center;
337
+ justify-content: center;
338
+ inline-size: 100%;
339
+ block-size: var(--upload-picture-empty-height);
340
+ border-radius: var(--upload-picture-radius);
341
+ border: 1px dashed hsl(var(--border));
342
+ color: hsl(var(--muted-foreground));
343
+ }
344
+
345
+ .ui-upload-picture-empty:hover {
346
+ border-color: hsl(var(--primary));
347
+ }
348
+
349
+ .ui-upload-picture-empty-label {
350
+ margin-block-start: var(--upload-picture-empty-label-space-block-start);
351
+ font-size: var(--upload-picture-empty-label-font-size);
352
+ }
353
+
354
+ .ui-upload-avatar {
355
+ position: relative;
356
+ inline-size: var(--upload-avatar-size);
357
+ block-size: var(--upload-avatar-size);
358
+ overflow: hidden;
359
+ border-radius: var(--radius-pill);
360
+ border: var(--upload-avatar-border-width) solid hsl(var(--border));
361
+ background-color: hsl(var(--muted));
362
+ }
363
+
364
+ .ui-upload-avatar[data-pending-delete] {
365
+ opacity: var(--upload-avatar-pending-delete-alpha);
366
+ }
367
+
368
+ .ui-upload-avatar-image {
369
+ inline-size: 100%;
370
+ block-size: 100%;
371
+ object-fit: cover;
372
+ }
373
+
374
+ .ui-upload-avatar-placeholder {
375
+ display: flex;
376
+ inline-size: 100%;
377
+ block-size: 100%;
378
+ align-items: center;
379
+ justify-content: center;
380
+ color: hsl(var(--muted-foreground));
381
+ }
382
+
383
+ /* Sits ON the photo, so the wash is a fixed dark scrim rather than a surface role — it must
384
+ * stay legible over an arbitrary user image in either theme. */
385
+ .ui-upload-avatar-change {
386
+ position: absolute;
387
+ inset-inline: 0;
388
+ inset-block-end: 0;
389
+ padding-block: var(--upload-avatar-change-space-block);
390
+ background-color: var(--upload-avatar-change-background);
391
+ color: var(--upload-avatar-change-foreground);
392
+ text-align: center;
393
+ }
394
+
395
+ .ui-upload-avatar-remove {
396
+ position: absolute;
397
+ inset-block-start: calc(var(--upload-remove-offset) * -1);
398
+ inset-inline-end: calc(var(--upload-remove-offset) * -1);
399
+ padding: var(--upload-remove-space-inset);
400
+ border-radius: var(--radius-pill);
401
+ border: 1px solid hsl(var(--border));
402
+ background-color: hsl(var(--background));
403
+ box-shadow: var(--shadow-sm);
404
+ }
405
+
406
+ .ui-upload-avatar-remove:hover {
407
+ background-color: hsl(var(--destructive));
408
+ color: hsl(var(--destructive-foreground));
409
+ }
410
+
411
+ .ui-upload-draft-undo {
412
+ display: flex;
413
+ flex-wrap: wrap;
414
+ align-items: center;
415
+ gap: var(--upload-draft-space-gap);
416
+ padding-inline: var(--upload-draft-space-inline);
417
+ padding-block: var(--upload-draft-space-block);
418
+ border-radius: var(--upload-draft-radius);
419
+ border: 1px dashed hsl(var(--destructive) / 0.4);
420
+ background-color: hsl(var(--destructive) / 0.05);
421
+ font-size: var(--upload-draft-font-size);
422
+ }
423
+
424
+ .ui-upload-draft-hint {
425
+ display: flex;
426
+ flex-wrap: wrap;
427
+ align-items: center;
428
+ gap: var(--upload-draft-space-gap);
429
+ font-size: var(--upload-draft-font-size);
430
+ color: hsl(var(--muted-foreground));
431
+ }
432
+
433
+ .ui-upload-draft-icon {
434
+ margin-inline-end: calc(var(--upload-draft-space-gap) / 2);
435
+ inline-size: var(--upload-draft-icon-size);
436
+ block-size: var(--upload-draft-icon-size);
437
+ }
438
+
439
+ .ui-upload-row {
440
+ display: flex;
441
+ align-items: center;
442
+ gap: var(--upload-row-space-gap);
443
+ padding-inline: var(--upload-row-space-inline);
444
+ padding-block: var(--upload-row-space-block);
445
+ border-radius: var(--upload-row-radius);
446
+ border: 1px solid hsl(var(--border));
447
+ font-size: var(--upload-row-font-size);
448
+ }
449
+
450
+ .ui-upload-row-main {
451
+ min-inline-size: 0;
452
+ flex: 1 1 0%;
453
+ }
454
+
455
+ .ui-upload-row-icon {
456
+ inline-size: var(--upload-row-icon-size);
457
+ block-size: var(--upload-row-icon-size);
458
+ }
459
+
460
+ .ui-upload-trigger-icon {
461
+ inline-size: var(--upload-row-icon-size);
462
+ block-size: var(--upload-row-icon-size);
463
+ }
464
+
465
+ .ui-upload-trigger-icon[data-with-label] {
466
+ margin-inline-end: var(--space-2);
467
+ }
468
+
469
+ /* Disabled affordances share the one system alpha rather than a per-variant literal. */
470
+ .ui-upload-dropzone[data-disabled],
471
+ .ui-upload-tile-add[data-disabled],
472
+ .ui-upload-avatar[data-disabled] {
473
+ pointer-events: none;
474
+ opacity: var(--disabled-opacity);
475
+ }
476
+
477
+ /* ── BranchScopePicker (gh#319) ────────────────────────────────────────────────
478
+ * `.ui-branch-scope-picker` shipped as a bare hook: the class existed but no rule did, so all
479
+ * of the control's geometry sat on the component as Tailwind literals. It composes RadioGroup +
480
+ * CheckboxGroup + SearchInput and has no field box of its own, so none of the control-affix
481
+ * machinery applies — these four rules are the whole surface. Every measure is a documented
482
+ * token (#45); see tokens/components/data-entry.css for why each default is a flat rem. */
483
+ .ui-branch-scope-picker {
484
+ display: flex;
485
+ flex-direction: column;
486
+ gap: var(--branch-scope-picker-gap);
487
+ }
488
+
489
+ .ui-branch-scope-picker-badges {
490
+ display: flex;
491
+ flex-wrap: wrap;
492
+ gap: var(--branch-scope-picker-badges-gap);
493
+ }
494
+
495
+ /* The `mode="selected"` subset, indented under its radio by a rule on the inline-start edge.
496
+ * Logical properties throughout, so the indent flips with the writing direction (the utilities
497
+ * this replaces, `border-s-2 ps-4`, already did). The colour is left to the global
498
+ * `* { border-color: hsl(var(--border)) }` so a scoped [data-tenant]/.dark override of
499
+ * `--border` still reaches it. */
500
+ .ui-branch-scope-picker-subset {
501
+ display: flex;
502
+ flex-direction: column;
503
+ gap: var(--branch-scope-picker-subset-gap);
504
+ padding-inline-start: var(--branch-scope-picker-subset-padding-inline);
505
+ border-inline-start-width: var(--branch-scope-picker-subset-border-width);
506
+ }
507
+
508
+ /* A bounded scroll region — the component gives it `tabIndex={0}` + a group name so it stays
509
+ * keyboard-reachable (WCAG 2.1.1 / axe scrollable-region-focusable) even when every checkbox
510
+ * in it is disabled. The cap is the token; the overflow is not negotiable. */
511
+ .ui-branch-scope-picker-list {
512
+ max-block-size: var(--branch-scope-picker-list-max-height);
513
+ overflow-y: auto;
514
+ }
515
+ }
@@ -61,8 +61,8 @@
61
61
  --control-height-sm: calc(var(--control-height) - calc(0.25rem * var(--scaling)));
62
62
  --control-height-lg: calc(var(--control-height) + calc(0.25rem * var(--scaling)));
63
63
  --control-height-xs: calc(var(--control-height) - calc(0.5rem * var(--scaling)));
64
- --control-icon-size: calc(1rem * var(--scaling));
65
- --control-icon-size-sm: calc(0.875rem * var(--scaling));
64
+ --control-icon-size: calc(var(--icon-size-md) * var(--scaling));
65
+ --control-icon-size-sm: calc(var(--icon-size-sm) * var(--scaling));
66
66
  --control-padding-x: var(--space-3);
67
67
  --control-gap: var(--space-2);
68
68
  --control-gap-sm: var(--space-1);