@godxjp/ui 18.15.4 → 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 +25 -8
  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
@@ -50,3 +50,181 @@
50
50
  }
51
51
  }
52
52
  }
53
+
54
+ /*
55
+ * AMBIENT MOTION — the `Activity` primitive (gh#313): a continuous, unbounded "something is
56
+ * happening right now, elsewhere" mark. The LOOP counterpart to `.ui-reveal`'s one-shot entrance,
57
+ * living in the same file so the library keeps exactly one place where motion is written.
58
+ *
59
+ * NOT `.ui-skeleton-block` (content is loading — `aria-busy`), NOT the Button spinner (this action
60
+ * is in flight). Every interval, offset, size and colour reads a token, so a service retunes the
61
+ * whole ambient feel from its theme instead of forking a `@keyframes` (cardinal rule #45).
62
+ *
63
+ * REDUCED MOTION IS A DESIGNED FRAME, NOT AN ABSENCE. Under `prefers-reduced-motion: reduce` the
64
+ * loops are dropped and every mark falls back to its BASE rule, which is authored to be a legible
65
+ * resting state: three solid dots (a static ellipsis — the same "…" convention the animation
66
+ * spells out), a solid pulse mark, and a bar segment parked at the reading-start of its track so
67
+ * the bar can never read as "complete". Nothing is hidden, nothing collapses, no box changes size
68
+ * — so there is no layout shift either way (WCAG 2.2 SC 2.3.3 / 2.2.2).
69
+ */
70
+ @layer components {
71
+ .ui-activity {
72
+ display: inline-flex;
73
+ align-items: center;
74
+ gap: var(--activity-gap);
75
+ font-size: var(--activity-font-size-sm);
76
+ }
77
+ .ui-activity[data-size="xs"] {
78
+ font-size: var(--activity-font-size-xs);
79
+ }
80
+ .ui-activity[data-size="md"] {
81
+ font-size: var(--activity-font-size-md);
82
+ }
83
+ .ui-activity[data-size="lg"] {
84
+ font-size: var(--activity-font-size-lg);
85
+ }
86
+
87
+ /* The mark row. `block-size` reserves the loop's full travel so a dot at the top of its arc is
88
+ * never clipped and the row never grows mid-cycle. */
89
+ .ui-activity-mark {
90
+ display: inline-flex;
91
+ align-items: flex-end;
92
+ gap: var(--activity-gap);
93
+ block-size: calc(var(--activity-mark-size) + var(--activity-mark-offset));
94
+ color: hsl(var(--activity-color, var(--muted-foreground)));
95
+ }
96
+
97
+ /* Tone → mark ink. Two-attribute selectors (0,2,0) outrank the muted base (0,1,0). `muted` has
98
+ * no rule of its own: it IS the base, and therefore the one tone `--activity-color` retints. */
99
+ .ui-activity[data-tone="default"] .ui-activity-mark {
100
+ color: hsl(var(--foreground));
101
+ }
102
+ .ui-activity[data-tone="primary"] .ui-activity-mark {
103
+ color: hsl(var(--primary));
104
+ }
105
+ .ui-activity[data-tone="success"] .ui-activity-mark {
106
+ color: hsl(var(--success));
107
+ }
108
+ .ui-activity[data-tone="warning"] .ui-activity-mark {
109
+ color: hsl(var(--warning));
110
+ }
111
+ .ui-activity[data-tone="destructive"] .ui-activity-mark {
112
+ color: hsl(var(--destructive));
113
+ }
114
+ .ui-activity[data-tone="info"] .ui-activity-mark {
115
+ color: hsl(var(--info));
116
+ }
117
+
118
+ /* dots — the ellipsis convention. Resting state (= the reduced-motion frame): three solid dots. */
119
+ .ui-activity-dot {
120
+ inline-size: var(--activity-mark-size);
121
+ block-size: var(--activity-mark-size);
122
+ border-radius: var(--radius-pill);
123
+ background: currentColor;
124
+ opacity: 1;
125
+ animation: ui-activity-bounce var(--activity-interval) var(--ease-standard) infinite;
126
+ }
127
+ /* nth-child, not a physical offset: in RTL the flex row lays the dots end→start, so the cascade
128
+ * follows the reading direction with no `[dir]` rule. */
129
+ .ui-activity-dot:nth-child(2) {
130
+ animation-delay: calc(var(--activity-stagger-step) * 1);
131
+ }
132
+ .ui-activity-dot:nth-child(3) {
133
+ animation-delay: calc(var(--activity-stagger-step) * 2);
134
+ }
135
+
136
+ /* pulse — a single breathing mark (live / recording). Opacity only: the reference design keeps
137
+ * motion LOW, and an opacity loop degrades to a solid mark instead of a resized box. */
138
+ .ui-activity-pulse {
139
+ inline-size: var(--activity-pulse-mark-size);
140
+ block-size: var(--activity-pulse-mark-size);
141
+ border-radius: var(--radius-pill);
142
+ background: currentColor;
143
+ opacity: 1;
144
+ animation: ui-activity-breathe var(--activity-interval) var(--ease-standard) infinite;
145
+ }
146
+
147
+ /* bar — an indeterminate sweep (syncing). */
148
+ .ui-activity-bar {
149
+ position: relative;
150
+ overflow: hidden;
151
+ inline-size: var(--activity-bar-width);
152
+ block-size: var(--activity-bar-height);
153
+ border-radius: var(--activity-bar-radius);
154
+ background: hsl(
155
+ var(--activity-color, var(--muted-foreground)) / var(--activity-bar-track-alpha)
156
+ );
157
+ }
158
+ .ui-activity-bar-segment {
159
+ position: absolute;
160
+ inset-block: 0;
161
+ /* Parked at the reading-start when the animation is dropped: a partial bar, never a full one
162
+ * (a full track would read as "complete", which is the opposite of indeterminate). */
163
+ inset-inline-start: 0;
164
+ inline-size: var(--activity-bar-segment-width);
165
+ border-radius: inherit;
166
+ background: currentColor;
167
+ animation: ui-activity-sweep var(--activity-interval) var(--ease-standard) infinite;
168
+ }
169
+
170
+ /* The label is a real `Text`; it only needs to stop the mark's travel box stretching the row. */
171
+ .ui-activity-label {
172
+ min-inline-size: 0;
173
+ }
174
+
175
+ @keyframes ui-activity-bounce {
176
+ 0%,
177
+ 70%,
178
+ 100% {
179
+ transform: translateY(0);
180
+ opacity: var(--activity-mark-rest-alpha);
181
+ }
182
+ 35% {
183
+ transform: translateY(calc(-1 * var(--activity-mark-offset)));
184
+ opacity: 1;
185
+ }
186
+ }
187
+
188
+ @keyframes ui-activity-breathe {
189
+ 0%,
190
+ 100% {
191
+ opacity: var(--activity-mark-rest-alpha);
192
+ }
193
+ 50% {
194
+ opacity: 1;
195
+ }
196
+ }
197
+
198
+ @keyframes ui-activity-sweep {
199
+ from {
200
+ inset-inline-start: calc(-1 * var(--activity-bar-segment-width));
201
+ }
202
+ to {
203
+ inset-inline-start: 100%;
204
+ }
205
+ }
206
+
207
+ /* Accessibility: no motion → no loop. Every mark falls back to the legible resting state
208
+ * authored above; the label is visible text either way, so the affordance still says what it
209
+ * means (WCAG 2.2 SC 2.3.3 Animation from Interactions, SC 2.2.2 Pause/Stop/Hide — an
210
+ * indefinite loop runs longer than five seconds and the user cannot pause it). */
211
+ @media (prefers-reduced-motion: reduce) {
212
+ .ui-activity-dot,
213
+ .ui-activity-pulse,
214
+ .ui-activity-bar-segment {
215
+ animation: none;
216
+ }
217
+ }
218
+
219
+ /* Forced colours: the OS palette replaces the token ink, so the mark must not depend on it. */
220
+ @media (forced-colors: active) {
221
+ .ui-activity-dot,
222
+ .ui-activity-pulse,
223
+ .ui-activity-bar-segment {
224
+ background: CanvasText;
225
+ }
226
+ .ui-activity-bar {
227
+ border: 1px solid CanvasText;
228
+ }
229
+ }
230
+ }
@@ -42,6 +42,17 @@
42
42
  --control-padding-x: var(--app-setting-picker-compact-padding-x);
43
43
  --control-font-size: var(--app-setting-picker-compact-font-size);
44
44
  }
45
+ /* Tabs root — the flex box that stacks the strip above its panel (#319). The strip↔panel gap is
46
+ * a token so a service can retune the tab rhythm to its own grid; `min-inline-size: 0` is NOT a
47
+ * knob but the flexbox shrink idiom that used to ride along as `min-w-0`: without it the strip's
48
+ * intrinsic width forces every ancestor wider instead of letting the strip scroll its own
49
+ * overflow (gh#175). `display:flex` / the vertical column stay on the component — they are
50
+ * structure, not a service-tunable constant. */
51
+ [data-slot="tabs"] {
52
+ min-inline-size: 0;
53
+ gap: var(--tabs-root-gap);
54
+ }
55
+
45
56
  [data-slot="tabs-list"] {
46
57
  max-inline-size: var(--tabs-list-max-inline-size);
47
58
  overflow-x: var(--tabs-list-overflow);
@@ -88,9 +99,19 @@
88
99
  }
89
100
  }
90
101
 
102
+ /* Pagination chevrons (#319). The `simple` form draws Prev/Next as size="sm" Buttons whose only
103
+ * content is the glyph, so it must NOT inherit the small-button icon tier — the chevron is the
104
+ * whole control. Pagination lives in styles/layout.css, but this override has to out-specify
105
+ * `.ui-button--sm svg` (0,1,1) from styles/control.css, so it is authored here at (0,2,1); a
106
+ * plain `.ui-pagination svg` rule would lose the cascade and silently shrink both chevrons. */
107
+ .ui-pagination .ui-button svg {
108
+ inline-size: var(--pagination-icon-size);
109
+ block-size: var(--pagination-icon-size);
110
+ }
111
+
91
112
  .ui-context-menu-content {
92
- z-index: 50;
93
- min-width: 10rem;
113
+ z-index: var(--overlay-z-index);
114
+ min-width: var(--menu-content-min-width);
94
115
  overflow: hidden;
95
116
  border-radius: var(--radius-md);
96
117
  border: 1px solid hsl(var(--border));
@@ -111,14 +132,29 @@
111
132
  .ui-menubar-trigger,
112
133
  .ui-menubar-sub-trigger,
113
134
  .ui-menubar-checkbox-item,
114
- .ui-menubar-radio-item {
115
- border-radius: var(--radius-sm);
116
- padding-inline: var(--space-2);
117
- height: 2rem;
135
+ .ui-menubar-radio-item,
136
+ .ui-dropdown-menu-item,
137
+ .ui-dropdown-menu-label,
138
+ .ui-dropdown-menu-sub-trigger,
139
+ .ui-dropdown-menu-checkbox-item,
140
+ .ui-dropdown-menu-radio-item,
141
+ .ui-dropdown-menu-shortcut,
142
+ .ui-select-item,
143
+ .ui-select-label {
144
+ border-radius: var(--menu-item-radius);
145
+ padding-inline: var(--menu-item-space-inline);
146
+ height: var(--menu-item-height);
118
147
  display: flex;
119
148
  align-items: center;
120
- gap: var(--space-2);
121
- font-size: var(--font-size-base);
149
+ gap: var(--menu-item-space-gap);
150
+ font-size: var(--menu-item-font-size);
151
+ }
152
+
153
+ /* A row marked inset reserves the indicator column so its label lines up with checked siblings. */
154
+ .ui-dropdown-menu-item[data-inset="true"],
155
+ .ui-dropdown-menu-label[data-inset="true"],
156
+ .ui-dropdown-menu-sub-trigger[data-inset="true"] {
157
+ padding-inline-start: var(--menu-item-inset-space-inline-start);
122
158
  }
123
159
 
124
160
  .ui-context-menu-item,
@@ -129,6 +165,10 @@
129
165
  .ui-menubar-checkbox-item,
130
166
  .ui-menubar-radio-item,
131
167
  .ui-menubar-sub-trigger,
168
+ .ui-dropdown-menu-item,
169
+ .ui-dropdown-menu-checkbox-item,
170
+ .ui-dropdown-menu-radio-item,
171
+ .ui-dropdown-menu-sub-trigger,
132
172
  .ui-navigation-menu-link {
133
173
  cursor: pointer;
134
174
  outline: none;
@@ -142,6 +182,8 @@
142
182
  .ui-context-menu-sub-trigger:hover,
143
183
  .ui-menubar-item:hover,
144
184
  .ui-menubar-sub-trigger:hover,
185
+ .ui-dropdown-menu-item:hover,
186
+ .ui-dropdown-menu-sub-trigger:hover,
145
187
  .ui-navigation-menu-link:hover,
146
188
  .ui-navigation-menu-item:hover {
147
189
  background: var(--menubar-item-hover-background, hsl(var(--accent)));
@@ -155,6 +197,11 @@
155
197
  .ui-menubar-item[data-state="active"],
156
198
  .ui-menubar-item[data-state="checked"],
157
199
  .ui-menubar-sub-trigger[data-state="open"],
200
+ .ui-dropdown-menu-item[data-state="open"],
201
+ .ui-dropdown-menu-item[data-highlighted],
202
+ .ui-dropdown-menu-checkbox-item[data-highlighted],
203
+ .ui-dropdown-menu-radio-item[data-highlighted],
204
+ .ui-dropdown-menu-sub-trigger[data-state="open"],
158
205
  .ui-navigation-menu-link[data-highlighted],
159
206
  .ui-navigation-menu-item[data-focus-visible] {
160
207
  background: var(--menubar-item-hover-background, hsl(var(--accent)));
@@ -162,7 +209,8 @@
162
209
  }
163
210
 
164
211
  .ui-context-menu-item[data-variant="destructive"],
165
- .ui-menubar-item[data-variant="destructive"] {
212
+ .ui-menubar-item[data-variant="destructive"],
213
+ .ui-dropdown-menu-item[data-variant="destructive"] {
166
214
  color: hsl(var(--destructive));
167
215
  }
168
216
 
@@ -173,21 +221,26 @@
173
221
  }
174
222
 
175
223
  .ui-context-menu-separator,
176
- .ui-menubar-separator {
177
- margin: var(--space-1) 0;
224
+ .ui-menubar-separator,
225
+ .ui-dropdown-menu-separator,
226
+ .ui-select-separator {
227
+ margin-block: var(--menu-separator-space-block);
228
+ margin-inline: var(--menu-separator-space-inline);
178
229
  height: 1px;
179
230
  background: hsl(var(--border));
180
231
  }
181
232
 
182
233
  .ui-context-menu-label,
183
- .ui-menubar-label {
184
- min-height: 2rem;
234
+ .ui-menubar-label,
235
+ .ui-dropdown-menu-label {
236
+ min-height: var(--menu-item-height);
185
237
  color: hsl(var(--muted-foreground));
186
238
  font-weight: var(--font-weight-medium);
187
239
  }
188
240
 
189
241
  .ui-context-menu-shortcut,
190
- .ui-menubar-shortcut {
242
+ .ui-menubar-shortcut,
243
+ .ui-dropdown-menu-shortcut {
191
244
  margin-inline-start: auto;
192
245
  text-align: end;
193
246
  color: hsl(var(--muted-foreground));
@@ -196,6 +249,7 @@
196
249
 
197
250
  .ui-context-menu-item-indicator,
198
251
  .ui-menubar-item-indicator,
252
+ .ui-dropdown-menu-item-indicator,
199
253
  .ui-navigation-menu-item-indicator {
200
254
  display: inline-flex;
201
255
  width: 1rem;
@@ -238,10 +292,14 @@
238
292
  box-shadow: var(--shadow-md), var(--shadow-glow);
239
293
  }
240
294
 
295
+ /* Reads --menu-icon-size, the same knob DropdownMenu's sub-trigger chevron already reads
296
+ * (`.ui-dropdown-menu-sub-trigger-icon`, below). The three menu surfaces share one row rhythm,
297
+ * so the literal `1rem` here meant setting --menu-icon-size resized the dropdown chevron and
298
+ * silently left these two behind. Same resolved value, one owner (gh#326). */
241
299
  .ui-context-menu-sub-trigger-icon,
242
300
  .ui-menubar-sub-trigger-icon {
243
- width: 1rem;
244
- height: 1rem;
301
+ width: var(--menu-icon-size);
302
+ height: var(--menu-icon-size);
245
303
  margin-inline-start: auto;
246
304
  opacity: 0.75;
247
305
  }
@@ -304,8 +362,8 @@
304
362
  }
305
363
 
306
364
  .ui-navigation-menu-trigger-icon {
307
- width: 0.9rem;
308
- height: 0.9rem;
365
+ width: var(--navigation-menu-trigger-icon-size);
366
+ height: var(--navigation-menu-trigger-icon-size);
309
367
  margin-inline-start: var(--space-2);
310
368
  opacity: 0.7;
311
369
  }
@@ -390,10 +448,6 @@
390
448
  outline: none;
391
449
  }
392
450
 
393
- button.ui-steps-inline-control:focus-visible {
394
- box-shadow: 0 0 0 var(--focus-ring-width) hsl(var(--focus-ring-color, var(--ring)));
395
- }
396
-
397
451
  /* The step NUMBER carries the emphasis. Both knobs exist because the canonical hosted-identity
398
452
  * row marks the step with an accent TINT at normal weight, while the original marked it with
399
453
  * bold at the inherited colour — a service picks its own emphasis instead of forking this rule
@@ -433,4 +487,326 @@
433
487
  flex: none;
434
488
  color: var(--steps-inline-separator-color, hsl(var(--muted-foreground)));
435
489
  }
490
+
491
+ /* DROPDOWN MENU — the third Radix menu surface. It was the only one still carrying its whole
492
+ * box as Tailwind literals on the component (#319); it now joins ContextMenu and Menubar on the
493
+ * shared row rhythm above, and scopes the one value that legitimately differs. */
494
+ .ui-dropdown-menu-content,
495
+ .ui-dropdown-menu-sub-content {
496
+ --menu-content-min-width: var(--dropdown-content-min-width);
497
+
498
+ z-index: var(--overlay-z-index);
499
+ min-width: var(--menu-content-min-width);
500
+ border-radius: var(--radius-md);
501
+ border: 1px solid hsl(var(--border));
502
+ background: hsl(var(--popover));
503
+ color: hsl(var(--popover-foreground));
504
+ padding: var(--menu-content-space-inset);
505
+ box-shadow: var(--shadow-md), var(--shadow-glow);
506
+ }
507
+
508
+ .ui-dropdown-menu-content {
509
+ max-height: var(--radix-dropdown-menu-content-available-height);
510
+ overflow-x: hidden;
511
+ overflow-y: auto;
512
+ }
513
+
514
+ .ui-dropdown-menu-sub-content {
515
+ overflow: hidden;
516
+ }
517
+
518
+ /* The checkbox indicator is absolutely placed in the reserved inset column. */
519
+ .ui-dropdown-menu-indicator-slot {
520
+ pointer-events: none;
521
+ position: absolute;
522
+ inset-inline-start: var(--menu-indicator-inset-inline-start);
523
+ display: flex;
524
+ inline-size: var(--menu-indicator-size);
525
+ block-size: var(--menu-indicator-size);
526
+ align-items: center;
527
+ justify-content: center;
528
+ }
529
+
530
+ .ui-dropdown-menu-check,
531
+ .ui-dropdown-menu-sub-trigger-icon {
532
+ inline-size: var(--menu-icon-size);
533
+ block-size: var(--menu-icon-size);
534
+ }
535
+
536
+ .ui-dropdown-menu-sub-trigger-icon {
537
+ margin-inline-start: auto;
538
+ }
539
+
540
+ /* Checkbox and radio rows are inset by construction — they always reserve the marker column. */
541
+ .ui-dropdown-menu-checkbox-item {
542
+ position: relative;
543
+ padding-inline-start: var(--menu-item-inset-space-inline-start);
544
+ padding-inline-end: var(--menu-item-space-inline);
545
+ }
546
+
547
+ .ui-dropdown-menu-item,
548
+ .ui-dropdown-menu-radio-item {
549
+ position: relative;
550
+ }
551
+
552
+ .ui-dropdown-menu-radio-item[data-state="checked"] {
553
+ background: hsl(var(--primary) / 0.1);
554
+ color: hsl(var(--primary));
555
+ font-weight: var(--font-weight-medium);
556
+ }
557
+
558
+ .ui-dropdown-menu-item[data-variant="destructive"][data-highlighted] {
559
+ background: hsl(var(--destructive) / 0.1);
560
+ color: hsl(var(--destructive));
561
+ }
562
+
563
+ .ui-dropdown-menu-item[data-disabled],
564
+ .ui-dropdown-menu-checkbox-item[data-disabled],
565
+ .ui-dropdown-menu-radio-item[data-disabled] {
566
+ pointer-events: none;
567
+ opacity: var(--disabled-opacity);
568
+ }
569
+
570
+ .ui-dropdown-menu-item svg,
571
+ .ui-dropdown-menu-sub-trigger svg {
572
+ pointer-events: none;
573
+ flex-shrink: 0;
574
+ }
575
+ }
576
+
577
+ @layer components {
578
+ /* STEPS — full (non-inline) variant. The inline form keeps its own --steps-inline-* set. */
579
+ .ui-steps-dot {
580
+ display: block;
581
+ inline-size: var(--steps-dot-size);
582
+ block-size: var(--steps-dot-size);
583
+ border-radius: var(--radius-pill);
584
+ }
585
+
586
+ .ui-steps-dot[data-status="finish"],
587
+ .ui-steps-dot[data-status="process"] {
588
+ background: hsl(var(--primary));
589
+ }
590
+
591
+ .ui-steps-dot[data-status="process"] {
592
+ box-shadow: 0 0 0 var(--steps-dot-process-ring-width) hsl(var(--primary) / 0.2);
593
+ }
594
+
595
+ .ui-steps-dot[data-status="error"] {
596
+ background: hsl(var(--destructive));
597
+ }
598
+
599
+ .ui-steps-dot[data-status="wait"] {
600
+ background: hsl(var(--muted-foreground) / 0.3);
601
+ }
602
+
603
+ .ui-steps-marker {
604
+ border-radius: var(--radius-pill);
605
+ border: var(--steps-marker-border-width) solid transparent;
606
+ font-size: var(--steps-marker-font-size);
607
+ font-weight: var(--font-weight-medium);
608
+ }
609
+
610
+ .ui-steps-marker[data-status="finish"] {
611
+ border-color: hsl(var(--primary));
612
+ background: hsl(var(--primary));
613
+ color: hsl(var(--primary-foreground));
614
+ }
615
+
616
+ .ui-steps-marker[data-status="process"] {
617
+ border-color: hsl(var(--primary));
618
+ color: hsl(var(--primary));
619
+ }
620
+
621
+ .ui-steps-marker[data-status="error"] {
622
+ border-color: hsl(var(--destructive));
623
+ background: hsl(var(--destructive));
624
+ color: hsl(var(--destructive-foreground));
625
+ }
626
+
627
+ .ui-steps-marker[data-status="wait"] {
628
+ border-color: hsl(var(--muted-foreground) / 0.3);
629
+ color: hsl(var(--muted-foreground));
630
+ }
631
+
632
+ .ui-steps-marker-icon {
633
+ inline-size: var(--steps-marker-icon-size);
634
+ block-size: var(--steps-marker-icon-size);
635
+ }
636
+
637
+ .ui-steps-wait-icon {
638
+ inline-size: var(--steps-wait-icon-size);
639
+ block-size: var(--steps-wait-icon-size);
640
+ fill: currentColor;
641
+ }
642
+
643
+ .ui-steps-text {
644
+ min-inline-size: 0;
645
+ }
646
+
647
+ .ui-steps-item[data-direction="vertical"] .ui-steps-text {
648
+ padding-block-start: var(--steps-vertical-text-space-block-start);
649
+ text-align: start;
650
+ }
651
+
652
+ .ui-steps-item[data-direction="horizontal"] .ui-steps-text {
653
+ margin-block-start: var(--steps-horizontal-text-space-block-start);
654
+ padding-inline: var(--steps-horizontal-text-space-inline);
655
+ }
656
+
657
+ .ui-steps-item[data-direction="horizontal"][data-title-placement="vertical"] .ui-steps-text {
658
+ margin-block-start: var(--steps-vertical-text-space-block-start);
659
+ }
660
+
661
+ .ui-steps-title {
662
+ font-weight: var(--font-weight-medium);
663
+ font-size: var(--steps-title-font-size);
664
+ }
665
+
666
+ .ui-steps-title[data-compact] {
667
+ font-size: var(--steps-title-font-size-compact);
668
+ }
669
+
670
+ .ui-steps-title[data-status="process"] {
671
+ color: hsl(var(--foreground));
672
+ }
673
+
674
+ .ui-steps-title[data-status="wait"] {
675
+ color: hsl(var(--muted-foreground));
676
+ }
677
+
678
+ .ui-steps-title[data-status="error"] {
679
+ color: hsl(var(--destructive));
680
+ }
681
+
682
+ .ui-steps-subtitle {
683
+ font-size: var(--steps-subtitle-font-size);
684
+ color: hsl(var(--muted-foreground));
685
+ }
686
+
687
+ .ui-steps-description {
688
+ font-size: var(--steps-title-font-size);
689
+ color: hsl(var(--muted-foreground));
690
+ }
691
+
692
+ .ui-steps-description[data-compact] {
693
+ font-size: var(--steps-title-font-size-compact);
694
+ }
695
+
696
+ .ui-steps-list[data-direction="vertical"] {
697
+ flex-direction: column;
698
+ gap: 0;
699
+ }
700
+
701
+ .ui-steps-list[data-direction="horizontal"] {
702
+ flex-direction: row;
703
+ align-items: flex-start;
704
+ }
705
+
706
+ .ui-steps-control {
707
+ display: flex;
708
+ min-inline-size: 0;
709
+ }
710
+
711
+ .ui-steps-control[data-direction="vertical"] {
712
+ flex-direction: row;
713
+ align-items: flex-start;
714
+ gap: var(--steps-vertical-space-gap);
715
+ }
716
+
717
+ .ui-steps-control[data-direction="horizontal"] {
718
+ flex-direction: column;
719
+ align-items: center;
720
+ }
721
+
722
+ .ui-steps-item {
723
+ position: relative;
724
+ display: flex;
725
+ min-inline-size: 0;
726
+ }
727
+
728
+ .ui-steps-item[data-direction="vertical"] {
729
+ flex-direction: row;
730
+ gap: var(--steps-vertical-space-gap);
731
+ padding-block-end: var(--steps-vertical-space-block-end);
732
+ }
733
+
734
+ .ui-steps-item[data-direction="vertical"]:last-child {
735
+ padding-block-end: 0;
736
+ }
737
+
738
+ .ui-steps-item[data-direction="horizontal"] {
739
+ flex-direction: column;
740
+ align-items: center;
741
+ text-align: center;
742
+ }
743
+
744
+ /* The connector spans between two markers, inset by the marker radius at each end so it never
745
+ * runs underneath one. Both ends move together when --steps-connector-inset is retuned. */
746
+ .ui-steps-item[data-direction="horizontal"][data-connector]::after {
747
+ content: "";
748
+ position: absolute;
749
+ inset-block-start: var(--steps-connector-space-block-start);
750
+ inset-inline-start: calc(50% + var(--steps-connector-inset));
751
+ inline-size: calc(100% - var(--steps-connector-inset) * 2);
752
+ block-size: 1px;
753
+ background: hsl(var(--border));
754
+ }
755
+
756
+ /* APP SETTING PICKER — icon-only box + per-kind labelled widths (#319). */
757
+
758
+ /* Structurally icon-only: square to the density-aware --control-height tap target and drop the
759
+ * form-input chrome so it sits among the topbar's ghost icon buttons. The open-state and
760
+ * focus-visible rings from controlTriggerClass are untouched. */
761
+ .ui-app-setting-picker-icon {
762
+ inline-size: var(--control-height);
763
+ justify-content: center;
764
+ border-color: transparent;
765
+ background: transparent;
766
+ padding-inline: 0;
767
+ box-shadow: none;
768
+ }
769
+
770
+ .ui-app-setting-picker-glyph {
771
+ inline-size: var(--app-setting-picker-icon-size);
772
+ block-size: var(--app-setting-picker-icon-size);
773
+ flex-shrink: 0;
774
+ opacity: var(--app-setting-picker-icon-rest-alpha);
775
+ }
776
+
777
+ /* Each kind is sized to the longest value it can show — a timezone name is far wider than a
778
+ * theme name. Below the breakpoint every kind hugs its content instead. */
779
+ @media (min-width: 40rem) {
780
+ .ui-app-setting-picker-trigger[data-kind="locale"] {
781
+ inline-size: var(--app-setting-picker-locale-width);
782
+ }
783
+
784
+ .ui-app-setting-picker-trigger[data-kind="timezone"] {
785
+ inline-size: var(--app-setting-picker-timezone-width);
786
+ }
787
+
788
+ .ui-app-setting-picker-trigger[data-kind="dateFormat"] {
789
+ inline-size: var(--app-setting-picker-date-format-width);
790
+ }
791
+
792
+ .ui-app-setting-picker-trigger[data-kind="timeFormat"] {
793
+ inline-size: var(--app-setting-picker-time-format-width);
794
+ }
795
+
796
+ .ui-app-setting-picker-trigger[data-kind="theme"] {
797
+ inline-size: var(--app-setting-picker-theme-width);
798
+ }
799
+
800
+ .ui-app-setting-picker-trigger[data-kind="brand"] {
801
+ inline-size: var(--app-setting-picker-brand-width);
802
+ }
803
+
804
+ .ui-app-setting-picker-trigger[data-kind="density"] {
805
+ inline-size: var(--app-setting-picker-density-width);
806
+ }
807
+
808
+ .ui-app-setting-picker-trigger[data-kind="fontSize"] {
809
+ inline-size: var(--app-setting-picker-font-size-width);
810
+ }
811
+ }
436
812
  }