canny-ui 6.7.0 → 7.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 (49) hide show
  1. package/dist/canny-ui.min.css +1 -1
  2. package/dist/table-resize.js +16 -1
  3. package/package.json +1 -1
  4. package/src/_utilities.scss +4 -4
  5. package/src/base/_motion.scss +32 -0
  6. package/src/base/_reset.scss +7 -0
  7. package/src/base/_scrollbar-mixin.scss +84 -0
  8. package/src/base/_touch.scss +60 -0
  9. package/src/canny-ui.scss +5 -0
  10. package/src/components/_accordion.scss +10 -1
  11. package/src/components/_alerts.scss +18 -8
  12. package/src/components/_autocomplete.scss +16 -10
  13. package/src/components/_avatar.scss +22 -1
  14. package/src/components/_breadcrumb.scss +36 -2
  15. package/src/components/_buttons.scss +35 -24
  16. package/src/components/_charts-extra.scss +432 -0
  17. package/src/components/_charts.scss +90 -0
  18. package/src/components/_chip.scss +8 -7
  19. package/src/components/_command-palette.scss +27 -5
  20. package/src/components/_confirm.scss +14 -0
  21. package/src/components/_datatable.scss +383 -53
  22. package/src/components/_datepicker.scss +44 -21
  23. package/src/components/_dialog.scss +12 -0
  24. package/src/components/_drawer.scss +12 -0
  25. package/src/components/_fields.scss +115 -27
  26. package/src/components/_forms.scss +28 -10
  27. package/src/components/_gantt.scss +554 -0
  28. package/src/components/_inputs.scss +12 -7
  29. package/src/components/_list.scss +37 -3
  30. package/src/components/_menu.scss +23 -9
  31. package/src/components/_navbar.scss +98 -7
  32. package/src/components/_pagination.scss +105 -2
  33. package/src/components/_panels.scss +19 -5
  34. package/src/components/_popover.scss +3 -5
  35. package/src/components/_progress.scss +31 -0
  36. package/src/components/_scrollbar.scss +16 -159
  37. package/src/components/_sidebar.scss +24 -20
  38. package/src/components/_skeleton.scss +40 -1
  39. package/src/components/_slider.scss +8 -2
  40. package/src/components/_stepper.scss +22 -4
  41. package/src/components/_table.scss +23 -3
  42. package/src/components/_tabs.scss +120 -4
  43. package/src/components/_timeline.scss +26 -0
  44. package/src/components/_toast.scss +24 -12
  45. package/src/components/_tooltip-radix.scss +25 -4
  46. package/src/components/_tree.scss +51 -3
  47. package/src/components/_uploader.scss +97 -1
  48. package/src/scripts/table-resize.js +16 -1
  49. package/src/tokens/_semantic.scss +85 -21
@@ -31,7 +31,7 @@
31
31
  padding: 0 $space-3;
32
32
 
33
33
  &:focus,
34
- &.is-open { border-color: var(--border-focus); }
34
+ &.is-open { border-color: var(--border-input-focus); }
35
35
  &[disabled] { cursor: not-allowed; }
36
36
  }
37
37
 
@@ -44,8 +44,8 @@
44
44
  transition: color 140ms ease;
45
45
  }
46
46
  &-trigger:hover &-icon,
47
- &-trigger.is-open &-icon { color: var(--border-focus); }
48
- &-trigger.danger &-icon { color: var(--color-danger); }
47
+ &-trigger.is-open &-icon { color: var(--text-primary); }
48
+ &-trigger.danger &-icon { color: var(--color-danger-text); }
49
49
 
50
50
  &-value {
51
51
  flex: 1 1 auto;
@@ -80,7 +80,7 @@
80
80
  display: block;
81
81
  margin-top: 6px;
82
82
  font-size: 0.75rem;
83
- color: var(--color-danger);
83
+ color: var(--color-danger-text);
84
84
  line-height: 1.4;
85
85
  }
86
86
  }
@@ -96,15 +96,12 @@
96
96
 
97
97
  .cny-datepicker-panel {
98
98
  position: relative;
99
- z-index: 1010;
99
+ z-index: var(--z-popover, 1010);
100
100
  padding: 10px;
101
101
  background: var(--bg-surface-lowest);
102
- border: 1px solid var(--border-color);
102
+ border: 1px solid var(--border-color-strong);
103
103
  border-radius: var(--radius-sm);
104
- box-shadow:
105
- 0 1px 0 rgba(15, 23, 42, 0.02),
106
- 0 6px 18px -6px rgba(15, 23, 42, 0.14),
107
- 0 14px 32px -16px rgba(15, 23, 42, 0.16);
104
+ box-shadow: var(--shadow-popover);
108
105
  font-family: var(--font-body);
109
106
  color: var(--text-primary);
110
107
  animation: cny-dp-pop 140ms cubic-bezier(0.2, 0.8, 0.2, 1) both;
@@ -175,7 +172,7 @@
175
172
 
176
173
  &:hover {
177
174
  background: var(--bg-surface-low);
178
- color: var(--color-info);
175
+ color: var(--color-info-text);
179
176
 
180
177
  &::after { opacity: 1; transform: rotate(45deg) translateY(0); }
181
178
  }
@@ -248,10 +245,10 @@
248
245
  &:hover:not(:disabled):not(.is-selected) { background: var(--bg-surface-low); }
249
246
  &:focus-visible { outline: 2px solid var(--border-focus); outline-offset: -1px; z-index: 1; }
250
247
 
251
- &.is-outside { color: var(--text-muted); opacity: 0.5; }
248
+ &.is-outside { color: var(--text-muted); }
252
249
 
253
250
  &.is-today:not(.is-selected) {
254
- color: var(--color-info);
251
+ color: var(--color-info-text);
255
252
  font-weight: 600;
256
253
  box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--color-info) 35%, transparent);
257
254
 
@@ -303,12 +300,12 @@
303
300
  &:hover:not(:disabled):not(.is-selected) { background: var(--bg-surface-low); }
304
301
  &:focus-visible { outline: 2px solid var(--border-focus); outline-offset: -1px; }
305
302
 
306
- &.is-outside { color: var(--text-muted); opacity: 0.55; }
303
+ &.is-outside { color: var(--text-muted); }
307
304
 
308
305
  // Marks the currently-viewed month/year when drilling up for navigation
309
306
  &.is-current {
310
307
  box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--color-info) 40%, transparent);
311
- color: var(--color-info);
308
+ color: var(--color-info-text);
312
309
  font-weight: 600;
313
310
  }
314
311
 
@@ -362,16 +359,16 @@
362
359
  align-items: stretch;
363
360
  height: 30px;
364
361
  background: var(--bg-surface-lowest);
365
- border: 1px solid var(--border-color);
362
+ border: 1px solid var(--border-input);
366
363
  border-radius: var(--radius-xs);
367
364
  overflow: hidden;
368
365
  transition: border-color 120ms ease, box-shadow 120ms ease;
369
366
 
370
- &:hover { border-color: var(--border-color-strong, var(--border-color)); }
367
+ &:hover { border-color: var(--border-input-hover); }
371
368
 
372
369
  &:focus-within {
373
- border-color: var(--border-focus);
374
- box-shadow: 0 0 0 2px color-mix(in srgb, var(--border-focus) 18%, transparent);
370
+ border-color: var(--border-input-focus);
371
+ box-shadow: none;
375
372
  }
376
373
 
377
374
  &-input {
@@ -460,7 +457,7 @@
460
457
 
461
458
  &.is-active {
462
459
  background: var(--bg-surface-lowest);
463
- color: var(--color-info);
460
+ color: var(--color-info-text);
464
461
  box-shadow: 0 1px 2px rgba(15, 23, 42, 0.08), inset 0 0 0 1px var(--hairline-color, var(--border-color));
465
462
  }
466
463
 
@@ -489,7 +486,7 @@
489
486
  .cny-datepicker-action {
490
487
  border: 0;
491
488
  background: transparent;
492
- color: var(--color-info);
489
+ color: var(--color-info-text);
493
490
  font-family: var(--font-body);
494
491
  font-size: 0.75rem;
495
492
  font-weight: 500;
@@ -514,3 +511,29 @@
514
511
  }
515
512
  }
516
513
  }
514
+
515
+ // Quick presets row + typed-date field (DatePicker `presets` / `typeable`)
516
+ .cny-datepicker-presets {
517
+ display: flex;
518
+ flex-wrap: wrap;
519
+ gap: 2px;
520
+ margin-bottom: 6px;
521
+ padding-bottom: 6px;
522
+ border-bottom: 1px solid var(--hairline-color, var(--border-color));
523
+ }
524
+
525
+ .cny-datepicker-typed {
526
+ display: block;
527
+ width: 100%;
528
+ margin-bottom: 8px;
529
+ padding: 5px 8px;
530
+ background: var(--bg-surface-lowest);
531
+ color: var(--text-primary);
532
+ border: 1px solid var(--border-input);
533
+ border-radius: var(--radius-sm);
534
+ font-family: var(--font-body);
535
+ font-size: 0.8125rem;
536
+
537
+ &:focus-visible { outline: none; border-color: var(--border-focus); }
538
+ &[aria-invalid='true'] { border-color: var(--color-danger); }
539
+ }
@@ -1,3 +1,4 @@
1
+ @use '../base/scrollbar-mixin' as sb;
1
2
  @use '../tokens/primitives' as *;
2
3
 
3
4
  // Dialog (Modal) — portaled, centered, focus-trapped.
@@ -18,6 +19,7 @@
18
19
  animation: cny-dialog-overlay-in 160ms ease-out;
19
20
  }
20
21
  .cny-dark .cny-dialog-overlay { background: rgba(0, 0, 0, 0.7); }
22
+ @media (prefers-color-scheme: dark) { :root:not(.cny-light) .cny-dialog-overlay { background: rgba(0, 0, 0, 0.7); } }
21
23
 
22
24
  .cny-dialog-content {
23
25
  position: fixed;
@@ -91,6 +93,16 @@
91
93
  .cny-dialog-body {
92
94
  flex: 1 1 auto;
93
95
  overflow-y: auto;
96
+ scrollbar-gutter: stable; // no layout jump when the scrollbar appears
97
+ overscroll-behavior: contain; // don't chain scroll to the page behind
98
+ // Scroll shadows: fade appears only where content overflows (CSS-only).
99
+ background:
100
+ linear-gradient(var(--bg-surface-lowest) 30%, transparent) top / 100% 24px no-repeat local,
101
+ linear-gradient(transparent, var(--bg-surface-lowest) 70%) bottom / 100% 24px no-repeat local,
102
+ radial-gradient(farthest-side at 50% 0, rgba(0, 0, 0, 0.14), transparent) top / 100% 10px no-repeat scroll,
103
+ radial-gradient(farthest-side at 50% 100%, rgba(0, 0, 0, 0.14), transparent) bottom / 100% 10px no-repeat scroll,
104
+ var(--bg-surface-lowest);
105
+ @include sb.expand-scrollbar;
94
106
  padding: 18px 20px;
95
107
  font-size: 0.9375rem;
96
108
  line-height: 1.6;
@@ -1,3 +1,4 @@
1
+ @use '../base/scrollbar-mixin' as sb;
1
2
  @use '../tokens/primitives' as *;
2
3
 
3
4
  // Drawer / Sheet — slide-in panel from any edge.
@@ -18,6 +19,7 @@
18
19
  animation: cny-drawer-overlay-in 160ms ease-out;
19
20
  }
20
21
  .cny-dark .cny-drawer-overlay { background: rgba(0, 0, 0, 0.7); }
22
+ @media (prefers-color-scheme: dark) { :root:not(.cny-light) .cny-drawer-overlay { background: rgba(0, 0, 0, 0.7); } }
21
23
 
22
24
  .cny-drawer-content {
23
25
  position: fixed;
@@ -114,6 +116,16 @@
114
116
  .cny-drawer-body {
115
117
  flex: 1 1 auto;
116
118
  overflow-y: auto;
119
+ scrollbar-gutter: stable;
120
+ overscroll-behavior: contain; // don't chain scroll to the page behind
121
+ // Scroll shadows: fade appears only where content overflows (CSS-only).
122
+ background:
123
+ linear-gradient(var(--bg-surface-lowest) 30%, transparent) top / 100% 24px no-repeat local,
124
+ linear-gradient(transparent, var(--bg-surface-lowest) 70%) bottom / 100% 24px no-repeat local,
125
+ radial-gradient(farthest-side at 50% 0, rgba(0, 0, 0, 0.14), transparent) top / 100% 10px no-repeat scroll,
126
+ radial-gradient(farthest-side at 50% 100%, rgba(0, 0, 0, 0.14), transparent) bottom / 100% 10px no-repeat scroll,
127
+ var(--bg-surface-lowest);
128
+ @include sb.expand-scrollbar;
117
129
  padding: 18px 20px;
118
130
  color: var(--text-secondary);
119
131
  }
@@ -1,3 +1,4 @@
1
+ @use '../base/scrollbar-mixin' as sb;
1
2
  @use '../tokens/primitives' as *;
2
3
 
3
4
  // ── Field wrapper ────────────────────────────────────────────────────────
@@ -36,7 +37,7 @@
36
37
 
37
38
  // Required asterisk
38
39
  .cny-field-required {
39
- color: var(--color-danger);
40
+ color: var(--color-danger-text);
40
41
  font-weight: 500;
41
42
  }
42
43
 
@@ -57,17 +58,18 @@
57
58
  color: var(--text-muted);
58
59
  font-variant-numeric: tabular-nums;
59
60
 
60
- &.danger { color: var(--color-danger); }
61
+ &.danger { color: var(--color-danger-text); }
61
62
  }
62
63
 
63
64
  .cny-field-hint {
65
+ font-family: inherit;
64
66
  font-size: 0.75rem;
65
67
  color: var(--text-muted);
66
68
  line-height: 1.5;
67
69
 
68
- &.danger { color: var(--color-danger); }
69
- &.warning { color: var(--color-warning); }
70
- &.success { color: var(--color-success); }
70
+ &.danger { color: var(--color-danger-text); }
71
+ &.warning { color: var(--color-warning-text); }
72
+ &.success { color: var(--color-success-text); }
71
73
  }
72
74
 
73
75
  // ── Input affix container ───────────────────────────────────────────────
@@ -115,8 +117,8 @@
115
117
 
116
118
  &:hover { background: var(--bg-surface-low); color: var(--text-primary); }
117
119
  &:focus-visible {
118
- outline: none;
119
- box-shadow: 0 0 0 2px var(--ring-color);
120
+ outline: var(--ring-width) solid var(--ring-color);
121
+ outline-offset: 1px;
120
122
  }
121
123
  }
122
124
 
@@ -132,6 +134,22 @@
132
134
  &.has-action > .input { padding-right: 32px; }
133
135
  }
134
136
 
137
+ // Character counter (Input / Textarea `showCount`)
138
+ .cny-input-counted {
139
+ display: flex;
140
+ flex-direction: column;
141
+ width: 100%;
142
+ gap: 4px;
143
+
144
+ .cny-input-count {
145
+ align-self: flex-end;
146
+ font-size: 0.75rem;
147
+ line-height: 1.2;
148
+ color: var(--text-muted);
149
+ font-variant-numeric: tabular-nums;
150
+ }
151
+ }
152
+
135
153
  // ── Select (Radix Select trigger + content) ─────────────────────────────
136
154
  // Styled to match .input. The portalled content panel uses the surface
137
155
  // tokens and the same motion as our menus.
@@ -146,7 +164,8 @@
146
164
  padding: 0 $space-3;
147
165
  background: var(--bg-surface-lowest);
148
166
  color: var(--text-primary);
149
- border: 1px solid var(--border-color);
167
+ border: 1px solid var(--border-input);
168
+ box-shadow: var(--shadow-field);
150
169
  border-radius: var(--radius-sm);
151
170
  font-family: var(--font-body);
152
171
  font-size: 0.875rem;
@@ -158,12 +177,12 @@
158
177
 
159
178
  &[data-placeholder] { color: var(--text-muted); }
160
179
 
161
- &:hover:not([data-disabled]) { border-color: var(--border-color-strong); }
180
+ &:hover:not([data-disabled]) { border-color: var(--border-input-hover); }
162
181
 
163
182
  &:focus-visible,
164
183
  &[data-state='open'] {
165
184
  outline: none;
166
- border-color: var(--border-focus);
185
+ border-color: var(--border-input-focus);
167
186
  box-shadow: none;
168
187
  }
169
188
 
@@ -187,6 +206,22 @@
187
206
  }
188
207
  }
189
208
 
209
+ .cny-select-clear {
210
+ display: inline-flex;
211
+ align-items: center;
212
+ justify-content: center;
213
+ width: 20px;
214
+ height: 20px;
215
+ margin-left: auto;
216
+ flex-shrink: 0;
217
+ border-radius: var(--radius-xs);
218
+ color: var(--text-muted);
219
+ cursor: pointer;
220
+ transition: background-color 120ms, color 120ms;
221
+
222
+ &:hover { background: var(--bg-surface-low); color: var(--text-primary); }
223
+ }
224
+
190
225
  .cny-select-chevron {
191
226
  color: var(--text-muted);
192
227
  flex-shrink: 0;
@@ -195,13 +230,13 @@
195
230
  }
196
231
 
197
232
  .cny-select-content {
198
- z-index: 60;
233
+ z-index: var(--z-popover, 1010);
199
234
  min-width: var(--radix-select-trigger-width);
200
235
  max-height: var(--radix-select-content-available-height);
201
236
  padding: 4px;
202
237
  border-radius: var(--radius-md);
203
238
  background: var(--bg-surface-lowest);
204
- box-shadow: var(--shadow-lg), 0 0 0 1px var(--border-color);
239
+ box-shadow: var(--shadow-popover), 0 0 0 1px var(--border-color-strong);
205
240
  overflow: hidden;
206
241
  animation: cny-select-pop 160ms cubic-bezier(0.2, 0, 0, 1);
207
242
  }
@@ -211,7 +246,12 @@
211
246
  to { opacity: 1; transform: translateY(0) scale(1); }
212
247
  }
213
248
 
214
- .cny-select-viewport { padding: 2px; }
249
+ // Radix injects `scrollbar-width: none` + `::-webkit-scrollbar { display: none }` on the
250
+ // viewport; nesting under the content class outranks those rules.
251
+ .cny-select-content .cny-select-viewport {
252
+ padding: 2px;
253
+ @include sb.expand-scrollbar;
254
+ }
215
255
 
216
256
  .cny-select-item {
217
257
  position: relative;
@@ -230,7 +270,7 @@
230
270
  background: var(--bg-surface-low);
231
271
  }
232
272
  &[data-state='checked'] {
233
- color: var(--color-primary);
273
+ color: var(--text-accent);
234
274
  font-weight: 500;
235
275
  }
236
276
  &[data-disabled] {
@@ -243,7 +283,7 @@
243
283
  position: absolute;
244
284
  right: 8px;
245
285
  display: inline-flex;
246
- color: var(--color-primary);
286
+ color: var(--text-accent);
247
287
  }
248
288
 
249
289
  .cny-select-group-label {
@@ -273,7 +313,8 @@
273
313
  padding: 3px 32px 3px 6px;
274
314
  background: var(--bg-surface-lowest);
275
315
  color: var(--text-primary);
276
- border: 1px solid var(--border-color);
316
+ border: 1px solid var(--border-input);
317
+ box-shadow: var(--shadow-field);
277
318
  border-radius: var(--radius-sm);
278
319
  font-family: var(--font-body);
279
320
  font-size: 0.875rem;
@@ -283,11 +324,11 @@
283
324
  border-color var(--transition-fast),
284
325
  box-shadow var(--transition-fast);
285
326
 
286
- &:hover:not([data-disabled]) { border-color: var(--border-color-strong); }
327
+ &:hover:not([data-disabled]) { border-color: var(--border-input-hover); }
287
328
 
288
329
  &[data-state='open'],
289
330
  &:focus-within {
290
- border-color: var(--border-focus);
331
+ border-color: var(--border-input-focus);
291
332
  box-shadow: none;
292
333
  }
293
334
 
@@ -336,6 +377,48 @@
336
377
  transition: transform 150ms var(--ease-standard);
337
378
  }
338
379
  &[data-state='open'] .cny-multiselect-chevron { transform: translateY(-50%) rotate(180deg); }
380
+
381
+ &.has-clear { padding-right: 56px; }
382
+ .cny-multiselect-clear {
383
+ position: absolute;
384
+ right: 28px;
385
+ top: 50%;
386
+ transform: translateY(-50%);
387
+ display: inline-flex;
388
+ align-items: center;
389
+ justify-content: center;
390
+ width: 20px;
391
+ height: 20px;
392
+ border: 0;
393
+ border-radius: var(--radius-xs);
394
+ background: transparent;
395
+ color: var(--text-muted);
396
+ cursor: pointer;
397
+
398
+ &:hover { background: var(--bg-surface-low); color: var(--text-primary); }
399
+ }
400
+ }
401
+
402
+ // "Select all / Clear" row inside the MultiSelect dropdown
403
+ .cny-multiselect-actions {
404
+ display: flex;
405
+ gap: 4px;
406
+ padding: 4px 6px;
407
+ border-bottom: 1px solid var(--border-color);
408
+
409
+ button {
410
+ border: 0;
411
+ background: transparent;
412
+ padding: 2px 6px;
413
+ border-radius: var(--radius-xs);
414
+ font: inherit;
415
+ font-size: 0.75rem;
416
+ color: var(--text-accent);
417
+ cursor: pointer;
418
+
419
+ &:hover:not(:disabled) { background: var(--bg-surface-low); }
420
+ &:disabled { color: var(--text-disabled); cursor: not-allowed; }
421
+ }
339
422
  }
340
423
 
341
424
  // Chip tokens
@@ -346,10 +429,13 @@
346
429
  padding: 2px 4px 2px 8px;
347
430
  border-radius: var(--radius-sm);
348
431
  background: var(--color-primary-soft);
349
- color: var(--color-primary);
432
+ color: var(--text-accent);
350
433
  font-size: 0.8125rem;
351
434
  line-height: 1.3;
352
435
  max-width: 100%;
436
+ min-width: 0;
437
+
438
+ &.cny-chip-more { padding-right: 8px; }
353
439
 
354
440
  .cny-chip-label {
355
441
  overflow: hidden;
@@ -363,6 +449,7 @@
363
449
  justify-content: center;
364
450
  width: 16px;
365
451
  height: 16px;
452
+ padding: 0;
366
453
  border: 0;
367
454
  border-radius: 3px;
368
455
  background: transparent;
@@ -370,22 +457,22 @@
370
457
  cursor: pointer;
371
458
  opacity: 0.7;
372
459
 
373
- &:hover { opacity: 1; background: rgba(255, 255, 255, 0.4); }
460
+ &:hover { opacity: 1; background: color-mix(in srgb, currentColor 16%, transparent); }
374
461
  &:focus-visible {
375
462
  outline: none;
376
463
  box-shadow: 0 0 0 2px currentColor;
377
464
  }
378
465
  }
379
466
 
380
- &.danger { background: var(--color-danger-soft); color: var(--color-danger); }
381
- &.warning { background: var(--color-warning-soft); color: var(--color-warning); }
382
- &.success { background: var(--color-success-soft); color: var(--color-success); }
467
+ &.danger { background: var(--color-danger-soft); color: var(--color-danger-text); }
468
+ &.warning { background: var(--color-warning-soft); color: var(--color-warning-text); }
469
+ &.success { background: var(--color-success-soft); color: var(--color-success-text); }
383
470
  &.neutral { background: var(--bg-surface-low); color: var(--text-secondary); }
384
471
  }
385
472
 
386
473
  // Popover panel for the combobox list (shared with date pickers etc.)
387
474
  .cny-popover {
388
- z-index: 60;
475
+ z-index: var(--z-popover, 1010);
389
476
  min-width: var(--radix-popover-trigger-width);
390
477
  // Height was capped here but width was not, so one long option label stretched the
391
478
  // popover far past its trigger and across the page. Read as: never wider than the
@@ -398,11 +485,11 @@
398
485
  padding: 4px;
399
486
  border-radius: var(--radius-md);
400
487
  background: var(--bg-surface-lowest);
401
- box-shadow: var(--shadow-lg), 0 0 0 1px var(--border-color);
488
+ box-shadow: var(--shadow-popover), 0 0 0 1px var(--border-color-strong);
402
489
  overflow: hidden;
403
490
  animation: cny-select-pop 160ms cubic-bezier(0.2, 0, 0, 1);
404
491
 
405
- [cmdk-list] { max-height: 280px; overflow-y: auto; scrollbar-width: thin; padding: 2px; }
492
+ [cmdk-list] { max-height: 280px; overflow-y: auto; padding: 2px; @include sb.expand-scrollbar; }
406
493
  [cmdk-input] {
407
494
  width: 100%;
408
495
  margin: 2px 0 4px;
@@ -443,7 +530,7 @@
443
530
 
444
531
  .cny-check {
445
532
  margin-left: auto;
446
- color: var(--color-primary);
533
+ color: var(--text-accent);
447
534
  }
448
535
  }
449
536
 
@@ -470,4 +557,5 @@
470
557
  width: 100%;
471
558
  resize: none;
472
559
  overflow: hidden;
560
+ transition: height 120ms ease;
473
561
  }
@@ -22,7 +22,7 @@
22
22
  margin: 0;
23
23
  padding: 0;
24
24
  background: var(--bg-surface-lowest);
25
- border: 1px solid var(--border-color-strong);
25
+ border: 1px solid var(--border-input);
26
26
  cursor: pointer;
27
27
  vertical-align: middle;
28
28
  transition:
@@ -32,8 +32,8 @@
32
32
 
33
33
  &:hover:not(:disabled):not([data-disabled]) { border-color: var(--color-primary); }
34
34
  &:focus-visible {
35
- outline: none;
36
- box-shadow: 0 0 0 var(--ring-width) var(--ring-color);
35
+ outline: var(--ring-width) solid var(--ring-color);
36
+ outline-offset: var(--ring-offset);
37
37
  }
38
38
  &:disabled,
39
39
  &[data-disabled] { opacity: 0.5; cursor: not-allowed; }
@@ -61,6 +61,12 @@
61
61
  }
62
62
  }
63
63
 
64
+ // Dark primary is light (white on it is 3.4:1) — flip the painted glyph to near-black.
65
+ @at-root {
66
+ :root:not(.cny-light) .checkbox:not([data-state]) { @media (prefers-color-scheme: dark) { &:checked { background-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='none'><path d='M3.5 8.5L6.5 11.5L12.5 5' stroke='%2309090b' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'/></svg>"); } &:indeterminate { background-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'><rect x='3' y='7' width='10' height='2' fill='%2309090b' rx='1'/></svg>"); } } }
67
+ .cny-dark .checkbox:not([data-state]) { &:checked { background-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='none'><path d='M3.5 8.5L6.5 11.5L12.5 5' stroke='%2309090b' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'/></svg>"); } &:indeterminate { background-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'><rect x='3' y='7' width='10' height='2' fill='%2309090b' rx='1'/></svg>"); } }
68
+ }
69
+
64
70
  // Radix pattern — Indicator child carries the Lucide check/minus icon.
65
71
  &[data-state='checked'],
66
72
  &[data-state='indeterminate'] {
@@ -73,9 +79,19 @@
73
79
  display: inline-flex;
74
80
  align-items: center;
75
81
  justify-content: center;
76
- color: #fff;
82
+ color: var(--color-primary-on, #fff);
77
83
  line-height: 0;
84
+ animation: cny-check-pop 140ms cubic-bezier(0.2, 0, 0, 1);
78
85
  }
86
+
87
+ // Radix Indicator renders both icons; show the one matching the state.
88
+ &[data-state='indeterminate'] .cny-checkbox-check,
89
+ &:not([data-state='indeterminate']) .cny-checkbox-minus { display: none; }
90
+ }
91
+
92
+ @keyframes cny-check-pop {
93
+ from { opacity: 0; transform: scale(0.5); }
94
+ to { opacity: 1; transform: scale(1); }
79
95
  }
80
96
 
81
97
  .radio {
@@ -100,6 +116,7 @@
100
116
  height: 8px;
101
117
  border-radius: 50%;
102
118
  background: var(--color-primary);
119
+ animation: cny-check-pop 140ms cubic-bezier(0.2, 0, 0, 1);
103
120
  }
104
121
  }
105
122
 
@@ -154,7 +171,7 @@
154
171
  height: var(--switch-h);
155
172
  margin: 0;
156
173
  padding: 0;
157
- background: var(--border-color-strong);
174
+ background: var(--border-input); // off-track >=3:1 against the page (WCAG 1.4.11)
158
175
  border: 0;
159
176
  border-radius: var(--radius-full);
160
177
  cursor: pointer;
@@ -183,8 +200,8 @@
183
200
  }
184
201
 
185
202
  &:focus-visible {
186
- outline: none;
187
- box-shadow: 0 0 0 var(--ring-width) var(--ring-color);
203
+ outline: var(--ring-width) solid var(--ring-color);
204
+ outline-offset: var(--ring-offset);
188
205
  }
189
206
 
190
207
  &:disabled { opacity: 0.5; cursor: not-allowed; }
@@ -224,7 +241,8 @@
224
241
  padding: 0 $space-8 0 $space-3;
225
242
  background: var(--bg-surface-lowest);
226
243
  color: var(--text-primary);
227
- border: 1px solid var(--border-color);
244
+ border: 1px solid var(--border-input);
245
+ box-shadow: var(--shadow-field);
228
246
  border-radius: var(--radius-sm);
229
247
  font-family: var(--font-body);
230
248
  font-size: 0.875rem;
@@ -238,11 +256,11 @@
238
256
  border-color var(--transition-fast),
239
257
  box-shadow var(--transition-fast);
240
258
 
241
- &:hover:not(:disabled):not(:focus) { border-color: var(--border-color-strong); }
259
+ &:hover:not(:disabled):not(:focus) { border-color: var(--border-input-hover); }
242
260
 
243
261
  &:focus {
244
262
  outline: none;
245
- border-color: var(--border-focus);
263
+ border-color: var(--border-input-focus);
246
264
  box-shadow: none;
247
265
  }
248
266