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
@@ -0,0 +1,432 @@
1
+ @use '../tokens/primitives' as *;
2
+
3
+ // Extra charts — Donut, Sparkline, Gauge, ProgressRing, StatCard, Radar,
4
+ // Scatter, Funnel, Heatmap. Animations live only under `.cny-chart-anim`
5
+ // (React drops that class for `animate={false}` / reduced motion) and inside
6
+ // a no-preference media query, so they are off in both cases.
7
+
8
+ @property --cny-sweep {
9
+ syntax: '<angle>';
10
+ inherits: false;
11
+ initial-value: 360deg;
12
+ }
13
+
14
+ .cny-chart-loading {
15
+ border-radius: $radius-md;
16
+ background: var(--bg-surface-low);
17
+ opacity: 0.7;
18
+ }
19
+
20
+ .cny-chart-center-value {
21
+ font-size: 1.5rem;
22
+ font-weight: 700;
23
+ line-height: 1.1;
24
+ font-variant-numeric: tabular-nums;
25
+ }
26
+
27
+ .cny-chart-center-caption {
28
+ font-size: 0.6875rem;
29
+ font-weight: 400;
30
+ color: var(--text-muted);
31
+ }
32
+
33
+ // hover emphasis: dim every mark except the active one
34
+ .cny-chart-emph {
35
+ .cny-x-mark {
36
+ transition: opacity 150ms ease;
37
+ }
38
+
39
+ &:has(.cny-x-mark.is-active, .cny-chart-slice.is-active) {
40
+ .cny-x-mark:not(.is-active),
41
+ .cny-chart-slice:not(.is-active) {
42
+ opacity: 0.35;
43
+ }
44
+ }
45
+ }
46
+
47
+ .cny-chart-spark {
48
+ vertical-align: middle;
49
+ }
50
+
51
+ .cny-chart-spark-empty {
52
+ display: inline-flex;
53
+ align-items: center;
54
+ color: var(--text-muted);
55
+ }
56
+
57
+ // gauge / ring
58
+ .cny-chart-gauge-track {
59
+ stroke: var(--bg-surface-edge);
60
+ stroke-width: 14;
61
+ stroke-linecap: round;
62
+ }
63
+
64
+ .cny-chart-gauge-arc {
65
+ stroke-width: 14;
66
+ stroke-linecap: round;
67
+ }
68
+
69
+ .cny-chart-gauge-band {
70
+ stroke-width: 4;
71
+ opacity: 0.8;
72
+ }
73
+
74
+ .cny-chart-gauge-value {
75
+ fill: var(--text-primary);
76
+ font-size: 26px;
77
+ font-weight: 700;
78
+ font-variant-numeric: tabular-nums;
79
+ }
80
+
81
+ .cny-chart-ring-center {
82
+ position: absolute;
83
+ inset: 0;
84
+ display: flex;
85
+ align-items: center;
86
+ justify-content: center;
87
+ font-size: 1.25rem;
88
+ font-weight: 700;
89
+ font-variant-numeric: tabular-nums;
90
+ color: var(--text-primary);
91
+ }
92
+
93
+ // stat card
94
+ .cny-stat {
95
+ display: flex;
96
+ flex-direction: column;
97
+ gap: $space-2;
98
+ padding: $space-4;
99
+ border: 1px solid var(--border-color);
100
+ border-radius: $radius-lg;
101
+ background: var(--bg-surface-lowest);
102
+ font-family: var(--font-body);
103
+ }
104
+
105
+ .cny-stat-head {
106
+ display: flex;
107
+ align-items: center;
108
+ justify-content: space-between;
109
+ gap: $space-2;
110
+ min-width: 0;
111
+ color: var(--text-secondary);
112
+ font-size: 0.8125rem;
113
+ }
114
+
115
+ .cny-stat-label {
116
+ min-width: 0;
117
+ overflow: hidden;
118
+ text-overflow: ellipsis;
119
+ white-space: nowrap;
120
+ }
121
+
122
+ .cny-stat-icon {
123
+ flex: none;
124
+ display: inline-flex;
125
+ color: var(--text-muted);
126
+ }
127
+
128
+ .cny-stat-body {
129
+ display: flex;
130
+ flex-wrap: wrap;
131
+ align-items: flex-end;
132
+ justify-content: space-between;
133
+ gap: $space-2 $space-3;
134
+ min-width: 0;
135
+ }
136
+
137
+ .cny-stat-value {
138
+ min-width: 0;
139
+ max-width: 100%;
140
+ overflow: hidden;
141
+ text-overflow: ellipsis;
142
+ white-space: nowrap;
143
+ font-size: 1.75rem;
144
+ font-weight: 700;
145
+ line-height: 1.1;
146
+ color: var(--text-primary);
147
+ font-variant-numeric: tabular-nums;
148
+ }
149
+
150
+ .cny-stat-delta {
151
+ display: inline-flex;
152
+ align-items: center;
153
+ gap: 6px;
154
+ font-size: 0.75rem;
155
+ font-weight: 600;
156
+ color: var(--text-muted);
157
+
158
+ &.is-good {
159
+ color: var(--color-success-text);
160
+ }
161
+
162
+ &.is-bad {
163
+ color: var(--color-danger-text);
164
+ }
165
+ }
166
+
167
+ .cny-stat-delta-label {
168
+ font-weight: 400;
169
+ color: var(--text-muted);
170
+ }
171
+
172
+ // radar
173
+ .cny-chart-radar-ring {
174
+ fill: none;
175
+ stroke: var(--hairline-color);
176
+ }
177
+
178
+ .cny-chart-radar-fill {
179
+ fill-opacity: 0.18;
180
+ stroke-width: 2;
181
+ stroke-linejoin: round;
182
+ }
183
+
184
+ // funnel / heatmap / scatter
185
+ .cny-chart-funnel-seg {
186
+ stroke: var(--bg-surface-lowest);
187
+ stroke-width: 2;
188
+ outline: none;
189
+
190
+ &:focus-visible {
191
+ stroke: var(--color-primary);
192
+ }
193
+ }
194
+
195
+ .cny-chart-funnel-text {
196
+ fill: var(--color-primary-on);
197
+ font-size: 0.8125rem;
198
+ font-weight: 600;
199
+ pointer-events: none;
200
+ }
201
+
202
+ // Label placed beside a stage too narrow to hold it.
203
+ .cny-chart-funnel-text.is-outside {
204
+ fill: var(--text-primary);
205
+ }
206
+
207
+ .cny-chart-funnel-sub {
208
+ font-weight: 400;
209
+ opacity: 0.85;
210
+ }
211
+
212
+ .cny-chart-cell {
213
+ outline: none;
214
+
215
+ // The active cell is outlined instead of dimming its hundreds of neighbours,
216
+ // which strobed as the pointer crossed the grid.
217
+ &.is-active,
218
+ &:focus-visible {
219
+ stroke: var(--text-primary);
220
+ stroke-width: 1.5;
221
+ }
222
+ }
223
+
224
+ .cny-chart-cell-text {
225
+ fill: var(--text-primary);
226
+ font-size: 0.6875rem;
227
+ pointer-events: none;
228
+ }
229
+
230
+ .cny-chart-scatter-dot {
231
+ stroke: var(--bg-surface-lowest);
232
+ stroke-width: 1.5;
233
+ fill-opacity: 0.8;
234
+ outline: none;
235
+
236
+ &:focus-visible {
237
+ stroke: var(--text-primary);
238
+ }
239
+ }
240
+
241
+ // ---- motion -----------------------------------------------------------------
242
+ @media (prefers-reduced-motion: no-preference) {
243
+ .cny-chart-anim {
244
+ // arcs / lines
245
+ .cny-x-sweep {
246
+ stroke-dasharray: 100;
247
+ animation: cny-sweep 900ms cubic-bezier(0.22, 1, 0.36, 1) backwards;
248
+ }
249
+
250
+ .cny-x-draw {
251
+ stroke-dasharray: 1;
252
+ animation: cny-draw 900ms ease-out backwards;
253
+ animation-delay: calc(var(--i, 0) * 90ms);
254
+ }
255
+
256
+ .cny-x-needle {
257
+ animation: cny-needle 900ms cubic-bezier(0.22, 1, 0.36, 1) backwards;
258
+ }
259
+
260
+ // marks
261
+ .cny-x-grow-y,
262
+ .cny-x-grow-x,
263
+ .cny-x-pop,
264
+ .cny-x-scale {
265
+ transform-box: fill-box;
266
+ animation-duration: 520ms;
267
+ animation-timing-function: cubic-bezier(0.34, 1.4, 0.64, 1);
268
+ animation-fill-mode: backwards;
269
+ animation-delay: calc(var(--i, 0) * var(--d, 45ms));
270
+ }
271
+
272
+ .cny-x-grow-y {
273
+ transform-origin: 50% 100%;
274
+ animation-name: cny-grow-y;
275
+ }
276
+
277
+ .cny-x-grow-x {
278
+ transform-origin: 50% 50%;
279
+ animation-name: cny-grow-x;
280
+ }
281
+
282
+ .cny-x-pop {
283
+ transform-origin: 50% 50%;
284
+ animation-name: cny-pop;
285
+ }
286
+
287
+ .cny-x-scale {
288
+ transform-origin: 50% 50%;
289
+ animation-name: cny-scale;
290
+ }
291
+
292
+ .cny-chart-radar-fill {
293
+ transform-box: view-box;
294
+ transform-origin: 50% 50%;
295
+ }
296
+
297
+ .cny-x-late {
298
+ animation-delay: 800ms;
299
+ }
300
+
301
+ // smooth transitions when chart data changes (bars / lines / slices tween)
302
+ .cny-chart-bar {
303
+ transition: opacity 120ms ease, y 400ms ease-out, height 400ms ease-out;
304
+ }
305
+
306
+ .cny-chart-line,
307
+ .cny-chart-area,
308
+ .cny-chart-slice {
309
+ transition: d 400ms ease-out, opacity 120ms ease;
310
+ }
311
+
312
+ .cny-x-fade {
313
+ animation: cny-fade 700ms ease-out backwards;
314
+ animation-delay: calc(var(--i, 0) * var(--d, 45ms) + 200ms);
315
+ }
316
+
317
+ // donut: conic mask sweeps clockwise
318
+ &.cny-chart-donut svg {
319
+ mask: conic-gradient(#000 var(--cny-sweep), transparent 0);
320
+ -webkit-mask: conic-gradient(#000 var(--cny-sweep), transparent 0);
321
+ animation: cny-donut-sweep 1000ms cubic-bezier(0.22, 1, 0.36, 1) backwards;
322
+ }
323
+ }
324
+ }
325
+
326
+ @keyframes cny-sweep {
327
+ from {
328
+ stroke-dashoffset: 100;
329
+ }
330
+ to {
331
+ stroke-dashoffset: 0;
332
+ }
333
+ }
334
+
335
+ @keyframes cny-draw {
336
+ from {
337
+ stroke-dashoffset: 1;
338
+ }
339
+ to {
340
+ stroke-dashoffset: 0;
341
+ }
342
+ }
343
+
344
+ @keyframes cny-needle {
345
+ from {
346
+ transform: rotate(var(--from));
347
+ }
348
+ }
349
+
350
+ @keyframes cny-grow-y {
351
+ from {
352
+ transform: scaleY(0);
353
+ }
354
+ }
355
+
356
+ @keyframes cny-grow-x {
357
+ from {
358
+ transform: scaleX(0);
359
+ opacity: 0;
360
+ }
361
+ }
362
+
363
+ @keyframes cny-pop {
364
+ from {
365
+ transform: scale(0);
366
+ opacity: 0;
367
+ }
368
+ }
369
+
370
+ @keyframes cny-scale {
371
+ from {
372
+ transform: scale(0.4);
373
+ opacity: 0;
374
+ }
375
+ }
376
+
377
+ @keyframes cny-fade {
378
+ from {
379
+ opacity: 0;
380
+ }
381
+ }
382
+
383
+ @keyframes cny-donut-sweep {
384
+ from {
385
+ --cny-sweep: 0deg;
386
+ }
387
+ to {
388
+ --cny-sweep: 360deg;
389
+ }
390
+ }
391
+
392
+ // pie / donut / radar extras: grouped-slice toggle, ignored-values note, tooltip member list
393
+ .cny-chart-more {
394
+ display: block;
395
+ margin: $space-2 auto 0;
396
+ padding: 0;
397
+ border: 0;
398
+ background: none;
399
+ font: inherit;
400
+ font-size: 0.75rem;
401
+ color: var(--color-primary);
402
+ cursor: pointer;
403
+
404
+ &:hover {
405
+ text-decoration: underline;
406
+ }
407
+ }
408
+
409
+ .cny-chart-note {
410
+ margin-top: $space-2;
411
+ text-align: center;
412
+ font-size: 0.6875rem;
413
+ color: var(--text-muted);
414
+ }
415
+
416
+ .cny-chart-tooltip-more {
417
+ margin-top: 4px;
418
+ padding-top: 4px;
419
+ border-top: 1px solid var(--border-color);
420
+ }
421
+
422
+ .cny-chart-tooltip-row > .cny-chart-tooltip-label {
423
+ min-width: 0;
424
+ max-width: 28ch;
425
+ overflow: hidden;
426
+ text-overflow: ellipsis;
427
+ white-space: nowrap;
428
+ }
429
+
430
+ .cny-chart-funnel-scroll {
431
+ overflow-x: hidden;
432
+ }
@@ -5,6 +5,8 @@
5
5
  .cny-chart {
6
6
  width: 100%;
7
7
  font-family: var(--font-body);
8
+ // the visually-hidden data table can be very wide (many series); never let it widen the page
9
+ overflow-x: clip;
8
10
  }
9
11
 
10
12
  .cny-chart-responsive {
@@ -69,6 +71,17 @@
69
71
  }
70
72
  }
71
73
 
74
+ // whole-svg keyboard focus (arrow keys move through marks when there are too many for per-mark tab stops)
75
+ .cny-chart-focusable {
76
+ outline: none;
77
+
78
+ &:focus-visible {
79
+ outline: 2px solid var(--color-primary);
80
+ outline-offset: 2px;
81
+ border-radius: 4px;
82
+ }
83
+ }
84
+
72
85
  // visually hidden but available to assistive tech (chart data tables)
73
86
  .cny-sr-only {
74
87
  position: absolute;
@@ -108,6 +121,8 @@
108
121
  color: var(--text-primary);
109
122
  pointer-events: none;
110
123
  z-index: 2;
124
+ max-width: min(280px, 90%);
125
+ overflow-wrap: anywhere;
111
126
  }
112
127
 
113
128
  .cny-chart-tooltip-title {
@@ -143,10 +158,58 @@
143
158
  color: var(--text-secondary);
144
159
  }
145
160
 
161
+ // many entries: one scrollable row (JS adds `.cny-scroll` for the expand scrollbar)
162
+ .cny-chart-legend--scroll {
163
+ flex-wrap: nowrap;
164
+ justify-content: flex-start;
165
+ overflow-x: auto;
166
+ overflow-y: hidden;
167
+ padding-bottom: 2px;
168
+
169
+ .cny-chart-legend-item {
170
+ flex: 0 0 auto;
171
+ }
172
+ }
173
+
174
+ .cny-chart-legend-label {
175
+ max-width: 24ch;
176
+ overflow: hidden;
177
+ text-overflow: ellipsis;
178
+ white-space: nowrap;
179
+ }
180
+
146
181
  .cny-chart-legend-item {
147
182
  display: inline-flex;
148
183
  align-items: center;
149
184
  gap: 6px;
185
+ max-width: 100%;
186
+
187
+ // legend entries are toggle buttons — strip the native button chrome
188
+ &.is-toggle {
189
+ appearance: none;
190
+ margin: 0;
191
+ padding: 2px 4px;
192
+ border: 0;
193
+ border-radius: var(--radius-xs);
194
+ background: none;
195
+ font: inherit;
196
+ color: inherit;
197
+ cursor: pointer;
198
+ transition: opacity 120ms ease;
199
+
200
+ &:hover {
201
+ color: var(--text-primary);
202
+ }
203
+ &:focus-visible {
204
+ outline: 2px solid var(--border-focus);
205
+ outline-offset: 1px;
206
+ }
207
+ // hidden series: dimmed with a struck-through label
208
+ &.is-off {
209
+ opacity: 0.45;
210
+ text-decoration: line-through;
211
+ }
212
+ }
150
213
  }
151
214
 
152
215
  .cny-chart-swatch {
@@ -156,3 +219,30 @@
156
219
  border-radius: 3px;
157
220
  flex: none;
158
221
  }
222
+
223
+ // hover crosshair (line / area) and hovered-category band (bar)
224
+ .cny-chart-crosshair {
225
+ stroke: var(--text-muted);
226
+ stroke-width: 1;
227
+ stroke-dasharray: 3 3;
228
+ pointer-events: none;
229
+ }
230
+
231
+ .cny-chart-band {
232
+ fill: var(--bg-surface-low);
233
+ opacity: 0.6;
234
+ pointer-events: none;
235
+ }
236
+
237
+ // no-data message, centered over the empty plot (the chart container is position: relative)
238
+ .cny-chart-empty {
239
+ position: absolute;
240
+ inset: 0;
241
+ display: flex;
242
+ align-items: center;
243
+ justify-content: center;
244
+ padding-bottom: 28px;
245
+ font-size: 0.875rem;
246
+ color: var(--text-muted);
247
+ pointer-events: none;
248
+ }
@@ -48,6 +48,7 @@
48
48
  height: 16px;
49
49
  margin-right: -4px;
50
50
  background: transparent;
51
+ padding: 0;
51
52
  border: 0;
52
53
  border-radius: 50%;
53
54
  color: currentColor;
@@ -60,7 +61,7 @@
60
61
  background: color-mix(in srgb, var(--chip-color) 15%, transparent);
61
62
  }
62
63
  &:focus-visible {
63
- outline: 2px solid var(--chip-color);
64
+ outline: 2px solid var(--border-focus);
64
65
  outline-offset: 1px;
65
66
  }
66
67
  }
@@ -72,7 +73,7 @@
72
73
  &:hover { transform: translateY(-1px); }
73
74
  &:active { transform: translateY(0); }
74
75
  &:focus-visible {
75
- outline: 2px solid color-mix(in srgb, var(--chip-color) 60%, transparent);
76
+ outline: 2px solid var(--border-focus);
76
77
  outline-offset: 2px;
77
78
  }
78
79
  }
@@ -84,27 +85,27 @@
84
85
 
85
86
  // ── Color tones ──
86
87
  &.primary {
87
- --chip-color: var(--color-primary);
88
+ --chip-color: var(--text-accent);
88
89
  --chip-bg: color-mix(in srgb, var(--color-primary) 10%, transparent);
89
90
  --chip-border: color-mix(in srgb, var(--color-primary) 22%, transparent);
90
91
  }
91
92
  &.success {
92
- --chip-color: var(--color-success);
93
+ --chip-color: var(--color-success-text);
93
94
  --chip-bg: color-mix(in srgb, var(--color-success) 10%, transparent);
94
95
  --chip-border: color-mix(in srgb, var(--color-success) 24%, transparent);
95
96
  }
96
97
  &.info {
97
- --chip-color: var(--color-info);
98
+ --chip-color: var(--color-info-text);
98
99
  --chip-bg: color-mix(in srgb, var(--color-info) 10%, transparent);
99
100
  --chip-border: color-mix(in srgb, var(--color-info) 24%, transparent);
100
101
  }
101
102
  &.warning {
102
- --chip-color: var(--color-warning);
103
+ --chip-color: var(--color-warning-text);
103
104
  --chip-bg: color-mix(in srgb, var(--color-warning) 12%, transparent);
104
105
  --chip-border: color-mix(in srgb, var(--color-warning) 26%, transparent);
105
106
  }
106
107
  &.danger {
107
- --chip-color: var(--color-danger);
108
+ --chip-color: var(--color-danger-text);
108
109
  --chip-bg: color-mix(in srgb, var(--color-danger) 10%, transparent);
109
110
  --chip-border: color-mix(in srgb, var(--color-danger) 26%, transparent);
110
111
  }
@@ -1,3 +1,4 @@
1
+ @use '../base/scrollbar-mixin' as sb;
1
2
  @use '../tokens/primitives' as *;
2
3
 
3
4
  // CommandPalette — keyboard-driven search overlay.
@@ -43,7 +44,7 @@
43
44
  }
44
45
 
45
46
  &:focus-visible {
46
- outline: 2px solid color-mix(in srgb, var(--color-primary) 65%, transparent);
47
+ outline: 2px solid var(--border-focus);
47
48
  outline-offset: 2px;
48
49
  }
49
50
 
@@ -78,6 +79,7 @@
78
79
  }
79
80
  .cny-dark [cmdk-overlay],
80
81
  .cny-dark [data-cmdk-overlay] { background: rgba(0, 0, 0, 0.7); }
82
+ @media (prefers-color-scheme: dark) { :root:not(.cny-light) [cmdk-overlay], :root:not(.cny-light) [data-cmdk-overlay] { background: rgba(0, 0, 0, 0.7); } }
81
83
 
82
84
  [cmdk-dialog],
83
85
  .cny-cmdk-content {
@@ -136,7 +138,7 @@
136
138
  .cny-cmdk-list {
137
139
  overflow-y: auto;
138
140
  padding: 8px;
139
- scrollbar-width: thin;
141
+ @include sb.expand-scrollbar;
140
142
  }
141
143
 
142
144
  .cny-cmdk-empty {
@@ -150,6 +152,11 @@
150
152
  padding-bottom: 8px;
151
153
 
152
154
  [cmdk-group-heading] {
155
+ // Sticky so the current group stays labelled while scrolling a long list.
156
+ position: sticky;
157
+ top: -8px; // cancels .cny-cmdk-list padding so the heading sits flush
158
+ z-index: 1;
159
+ background: var(--bg-surface-lowest);
153
160
  padding: 8px 10px 6px;
154
161
  font-size: 0.6875rem;
155
162
  font-weight: 600;
@@ -181,7 +188,7 @@
181
188
  &[data-selected='true'] {
182
189
  background: color-mix(in srgb, var(--color-primary) 12%, var(--bg-surface-low));
183
190
 
184
- .cny-cmdk-item-icon { color: var(--color-primary); }
191
+ .cny-cmdk-item-icon { color: var(--text-accent); }
185
192
 
186
193
  &::after {
187
194
  content: '↵';
@@ -189,7 +196,7 @@
189
196
  right: 14px;
190
197
  font-family: var(--font-mono);
191
198
  font-size: 11px;
192
- color: var(--color-primary);
199
+ color: var(--text-accent);
193
200
  opacity: 0.7;
194
201
  }
195
202
  }
@@ -212,13 +219,28 @@
212
219
  }
213
220
  &[data-selected='true'] .cny-cmdk-item-hint { display: none; }
214
221
 
222
+ &-shortcut {
223
+ display: inline-flex;
224
+ align-items: center;
225
+ height: 18px;
226
+ padding: 0 5px;
227
+ border-radius: 3px;
228
+ background: var(--bg-surface-lowest);
229
+ color: var(--text-secondary);
230
+ font-family: var(--font-mono);
231
+ font-size: 0.625rem;
232
+ box-shadow: inset 0 0 0 1px var(--border-color);
233
+ }
234
+ // Shortcut stays visible on the active row, so drop the ↵ marker that would overlap it.
235
+ &.has-shortcut[data-selected='true']::after { content: none; }
236
+
215
237
  &-badge {
216
238
  display: inline-flex;
217
239
  align-items: center;
218
240
  padding: 1px 6px;
219
241
  border-radius: 999px;
220
242
  background: var(--color-primary-soft);
221
- color: var(--color-primary);
243
+ color: var(--text-accent);
222
244
  font-size: 0.625rem;
223
245
  font-weight: 600;
224
246
  text-transform: uppercase;
@@ -9,3 +9,17 @@
9
9
  line-height: 1.5;
10
10
  color: var(--text-secondary);
11
11
  }
12
+
13
+ // requireText — type-to-confirm field under the message.
14
+ .cny-confirm-require {
15
+ display: flex;
16
+ flex-direction: column;
17
+ gap: 6px;
18
+ margin-top: 14px;
19
+ }
20
+ .cny-confirm-require-label {
21
+ font-family: var(--font-body);
22
+ font-size: 0.8125rem;
23
+ color: var(--text-secondary);
24
+ strong { color: var(--text-primary); font-weight: 600; }
25
+ }