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
@@ -1,3 +1,4 @@
1
+ @use '../base/scrollbar-mixin' as sb;
1
2
  @use '../tokens/primitives' as *;
2
3
 
3
4
  // ── Sidebar ───────────────────────────────────────────────────
@@ -78,7 +79,7 @@
78
79
  &:active { transform: scale(0.96); }
79
80
 
80
81
  &:focus-visible {
81
- outline: 2px solid var(--color-primary);
82
+ outline: 2px solid var(--border-focus);
82
83
  outline-offset: 2px;
83
84
  }
84
85
 
@@ -104,14 +105,19 @@
104
105
  .cny-sidebar-nav {
105
106
  display: flex;
106
107
  flex-direction: column;
107
- gap: 2px;
108
+ // No gap between rows: each row carries a 1px transparent border instead, so
109
+ // the hover target is contiguous (no dead strip between items).
110
+ gap: 0;
108
111
  }
109
112
 
110
113
  .cny-sidebar-item {
111
114
  display: flex;
112
115
  align-items: center;
113
116
  gap: $space-3;
114
- padding: 8px $space-3;
117
+ padding: 7px $space-3;
118
+ border-top: 1px solid transparent;
119
+ border-bottom: 1px solid transparent;
120
+ background-clip: padding-box;
115
121
  // Break out of the rail's left padding so the surface meets the sidebar edge
116
122
  // with no gap, then hand the content back its indent so nothing shifts. The
117
123
  // right side stays inset, and only the right corners are curved.
@@ -151,8 +157,8 @@
151
157
  }
152
158
 
153
159
  &:focus-visible {
154
- outline: 2px solid var(--color-primary);
155
- outline-offset: -1px;
160
+ outline: 2px solid var(--border-focus);
161
+ outline-offset: -2px;
156
162
  }
157
163
 
158
164
  &.active {
@@ -164,7 +170,8 @@
164
170
  // The resting badge is a light surface; on a solid fill it needs to read
165
171
  // as part of the row rather than a chip sitting on top of it.
166
172
  .cny-sidebar-item-badge {
167
- background: rgb(255 255 255 / 0.22);
173
+ // text-primary is the opposite pole of primary-on in both themes, so the overlay always lifts contrast
174
+ background: color-mix(in srgb, var(--text-primary) 22%, transparent);
168
175
  color: var(--color-primary-on);
169
176
  }
170
177
  }
@@ -178,8 +185,10 @@
178
185
 
179
186
  &.disabled {
180
187
  color: var(--text-muted);
181
- pointer-events: none;
182
188
  opacity: 0.55;
189
+ cursor: not-allowed;
190
+
191
+ &:hover { background: transparent; color: var(--text-muted); }
183
192
  }
184
193
 
185
194
  .cny-sidebar-item-icon {
@@ -218,18 +227,8 @@
218
227
 
219
228
  // Themed scrollbar for sidebar content
220
229
  .cny-sidebar {
221
- scrollbar-width: thin;
222
- scrollbar-color: var(--border-color) transparent;
223
-
224
- &::-webkit-scrollbar { width: 6px; }
225
- &::-webkit-scrollbar-track { background: transparent; }
226
- &::-webkit-scrollbar-thumb {
227
- background: var(--border-color);
228
- border-radius: 3px;
229
- }
230
- &::-webkit-scrollbar-thumb:hover {
231
- background: var(--border-color-strong);
232
- }
230
+ scrollbar-gutter: stable;
231
+ @include sb.expand-scrollbar;
233
232
  }
234
233
 
235
234
  // Collapsed variant: icons only
@@ -254,7 +253,7 @@
254
253
 
255
254
  .cny-sidebar-item {
256
255
  justify-content: center;
257
- padding: 10px;
256
+ padding: 9px 10px;
258
257
  // The collapsed rail centres its items, so undo the flush-left break-out.
259
258
  margin-left: 0;
260
259
  margin-right: 0;
@@ -286,6 +285,8 @@
286
285
  z-index: 1001;
287
286
  width: min(300px, 86vw);
288
287
  max-height: none;
288
+ // an overlay: lift it off the page (and the dimmed backdrop) in dark
289
+ background: var(--bg-surface-lowest);
289
290
  transform: translateX(-100%);
290
291
  visibility: hidden;
291
292
  transition: transform 220ms var(--ease-standard), visibility 0s linear 220ms;
@@ -312,6 +313,9 @@
312
313
  }
313
314
 
314
315
  .cny-dark .cny-sidebar-backdrop.open { background: rgba(0, 0, 0, 0.7); }
316
+ @media (prefers-color-scheme: dark) {
317
+ :root:not(.cny-light) .cny-sidebar-backdrop.open { background: rgba(0, 0, 0, 0.7); }
318
+ }
315
319
  }
316
320
 
317
321
  @media (prefers-reduced-motion: reduce) {
@@ -36,7 +36,8 @@
36
36
  width: 60%;
37
37
  border-radius: var(--radius-xs);
38
38
  }
39
- &.circle { border-radius: 50%; }
39
+ &.circle { border-radius: 50%; width: 32px; height: 32px; }
40
+ &.rect { width: 100%; height: 96px; border-radius: var(--radius-md); }
40
41
  &.button { height: 36px; width: 96px; border-radius: var(--radius-sm); }
41
42
  &.avatar { width: 32px; height: 32px; border-radius: 50%; }
42
43
  }
@@ -70,3 +71,41 @@
70
71
  );
71
72
  }
72
73
  }
74
+
75
+ // .cny-light wins over a dark OS preference
76
+ .cny-light .skeleton::after {
77
+ background: linear-gradient(
78
+ 90deg,
79
+ transparent 0%,
80
+ rgba(255, 255, 255, 0.45) 50%,
81
+ transparent 100%
82
+ );
83
+ }
84
+
85
+ // Composite layouts
86
+ .skeleton-lines {
87
+ display: block;
88
+ width: 100%;
89
+ }
90
+
91
+ .skeleton-card {
92
+ display: flex;
93
+ flex-direction: column;
94
+ gap: 10px;
95
+ padding: 12px;
96
+ border: 1px solid var(--border-color);
97
+ border-radius: var(--radius-md);
98
+ background: var(--bg-surface-lowest);
99
+ }
100
+
101
+ .skeleton-list-item {
102
+ display: flex;
103
+ align-items: center;
104
+ gap: 12px;
105
+ padding: 12px 14px;
106
+ }
107
+
108
+ .skeleton-list-item-body {
109
+ flex: 1 1 auto;
110
+ min-width: 0;
111
+ }
@@ -16,7 +16,7 @@
16
16
  &-track {
17
17
  position: relative;
18
18
  flex: 1;
19
- background: var(--bg-surface-low);
19
+ background: var(--border-input); // rail >=3:1 against the page (WCAG 1.4.11)
20
20
  border-radius: 999px;
21
21
  height: 4px;
22
22
  overflow: hidden;
@@ -42,7 +42,7 @@
42
42
  &:hover { transform: scale(1.08); }
43
43
  &:active { cursor: grabbing; }
44
44
  &:focus-visible {
45
- outline: 2px solid var(--slider-accent);
45
+ outline: 2px solid var(--border-focus);
46
46
  outline-offset: 2px;
47
47
  }
48
48
  }
@@ -68,6 +68,12 @@
68
68
  &-thumb:focus-visible &-bubble,
69
69
  &-thumb[data-state='active'] &-bubble { opacity: 1; }
70
70
 
71
+ // Value tooltip (`showValueTooltip`): only while dragging / keyboard-focused,
72
+ // never on plain hover.
73
+ &-tooltip { opacity: 0; }
74
+ &.is-dragging &-tooltip,
75
+ &-thumb:focus-visible &-tooltip { opacity: 1; }
76
+
71
77
  &[data-disabled] {
72
78
  opacity: 0.5;
73
79
  .cny-slider-thumb { cursor: not-allowed; }
@@ -1,3 +1,4 @@
1
+ @use '../base/scrollbar-mixin' as sb;
1
2
  @use '../tokens/primitives' as *;
2
3
 
3
4
  // Stepper — wizard / multi-step progress trail.
@@ -32,7 +33,7 @@
32
33
  &:hover .cny-stepper-label { color: var(--text-primary); }
33
34
  }
34
35
  &:focus-visible {
35
- outline: 2px solid var(--color-primary);
36
+ outline: 2px solid var(--border-focus);
36
37
  outline-offset: 2px;
37
38
  }
38
39
  }
@@ -95,14 +96,26 @@
95
96
  .cny-stepper-connector { background: var(--color-success); }
96
97
  }
97
98
 
98
- // Horizontal
99
+ &-step.is-error {
100
+ .cny-stepper-marker {
101
+ background: var(--color-danger);
102
+ border-color: var(--color-danger);
103
+ color: var(--color-danger-on);
104
+ }
105
+ .cny-stepper-label { color: var(--color-danger-text); }
106
+ .cny-stepper-connector { background: var(--border-color); }
107
+ }
108
+
109
+ // Horizontal — steps never shrink; a wide trail scrolls sideways instead of clipping.
99
110
  &-horizontal {
100
111
  display: flex;
101
112
  align-items: center;
102
113
  gap: $space-2;
114
+ overflow-x: auto;
115
+ @include sb.expand-scrollbar;
103
116
 
104
- .cny-stepper-step { flex: 0 1 auto; }
105
- .cny-stepper-step:not(:last-child) { flex: 1 1 0; }
117
+ .cny-stepper-step { flex: 0 0 auto; }
118
+ .cny-stepper-step:not(:last-child) { flex: 1 0 auto; }
106
119
  .cny-stepper-connector { height: 2px; margin: 0 $space-2; min-width: 12px; }
107
120
  }
108
121
 
@@ -123,3 +136,8 @@
123
136
  .cny-stepper-desc { font-size: 0.7rem; }
124
137
  }
125
138
  }
139
+
140
+ // Coarse pointers: each step is a >=44px target.
141
+ @media (pointer: coarse) {
142
+ .cny-stepper-step-row { min-height: 44px; }
143
+ }
@@ -1,3 +1,4 @@
1
+ @use '../base/scrollbar-mixin' as sb;
1
2
  @use '../tokens/primitives' as *;
2
3
 
3
4
  // Table — enterprise data tables. Opt-in .striped, .bordered, .compact.
@@ -34,9 +35,22 @@
34
35
  &:last-child td { border-bottom: 0; }
35
36
  }
36
37
 
38
+ &.striped tbody tr:nth-child(odd) { background: var(--bg-surface-low); }
39
+
40
+ // after .striped so hover wins on odd rows; on a striped table the hover tint
41
+ // is a step beyond the stripe, otherwise hovering an odd row shows nothing
37
42
  &.hoverable tbody tr:hover { background: var(--bg-surface-low); }
43
+ &.striped.hoverable tbody tr:hover {
44
+ background: color-mix(in srgb, var(--color-primary) 9%, var(--bg-surface-low));
45
+ }
38
46
 
39
- &.striped tbody tr:nth-child(odd) { background: var(--bg-surface-low); }
47
+ // Keep the header in view while the rows scroll. Needs a scrolling ancestor
48
+ // with a bounded height (e.g. `.table-wrap` with a `max-height`).
49
+ &.sticky-header thead th {
50
+ position: sticky;
51
+ top: 0;
52
+ z-index: 2;
53
+ }
40
54
 
41
55
  &.compact {
42
56
  th, td { padding: $space-2 $space-3; }
@@ -80,7 +94,7 @@
80
94
  position: absolute;
81
95
  top: 0;
82
96
  right: 0;
83
- width: 6px;
97
+ width: 12px;
84
98
  height: 100%;
85
99
  cursor: col-resize;
86
100
  user-select: none;
@@ -89,11 +103,16 @@
89
103
  background: transparent;
90
104
  transition: background-color var(--transition-fast);
91
105
 
106
+ &:focus-visible {
107
+ outline: 2px solid var(--border-focus);
108
+ outline-offset: -2px;
109
+ }
110
+
92
111
  &::after {
93
112
  content: '';
94
113
  position: absolute;
95
114
  top: 20%;
96
- right: 2px;
115
+ right: 5px;
97
116
  width: 1px;
98
117
  height: 60%;
99
118
  background: var(--border-color);
@@ -123,6 +142,7 @@
123
142
  // Wrapper for horizontal scroll on narrow viewports
124
143
  .table-wrap {
125
144
  overflow-x: auto;
145
+ @include sb.expand-scrollbar;
126
146
  border: 1px solid var(--border-color);
127
147
  border-radius: var(--radius-md);
128
148
  background: var(--bg-surface-lowest);
@@ -48,7 +48,7 @@
48
48
 
49
49
  &:hover { color: var(--text-secondary); }
50
50
  &:focus-visible {
51
- outline: 2px solid color-mix(in srgb, var(--color-primary) 60%, transparent);
51
+ outline: 2px solid var(--border-focus);
52
52
  outline-offset: 2px;
53
53
  border-radius: var(--radius-xs);
54
54
  }
@@ -72,7 +72,7 @@
72
72
  border-bottom: 2px solid transparent;
73
73
 
74
74
  &[data-state='active'] {
75
- color: var(--color-primary);
75
+ color: var(--text-accent);
76
76
  border-bottom-color: var(--color-primary);
77
77
  }
78
78
  }
@@ -103,7 +103,7 @@
103
103
 
104
104
  &:hover { background: var(--bg-surface-low); }
105
105
  &[data-state='active'] {
106
- color: var(--color-primary);
106
+ color: var(--text-accent);
107
107
  background: color-mix(in srgb, var(--color-primary) 12%, transparent);
108
108
  }
109
109
  }
@@ -141,8 +141,124 @@
141
141
  flex: 1 1 auto;
142
142
 
143
143
  &:focus-visible {
144
- outline: 2px solid var(--color-primary);
144
+ outline: 2px solid var(--border-focus);
145
145
  outline-offset: 2px;
146
146
  border-radius: var(--radius-xs);
147
147
  }
148
148
  }
149
+
150
+ // ── Sliding indicator (Tabs `indicator`) ──────────────────────
151
+ // The active trigger's own underline/fill is replaced by one element that
152
+ // slides to its position via --cny-tabs-x/y/w/h set from the measured tab.
153
+ .cny-tabs-indicator {
154
+ position: absolute;
155
+ left: 0;
156
+ top: 0;
157
+ pointer-events: none;
158
+ opacity: 0;
159
+ width: var(--cny-tabs-w, 0);
160
+ height: var(--cny-tabs-h, 0);
161
+ transform: translate(var(--cny-tabs-x, 0), var(--cny-tabs-y, 0));
162
+ transition: transform 220ms cubic-bezier(0.4, 0, 0.2, 1), width 220ms cubic-bezier(0.4, 0, 0.2, 1),
163
+ height 220ms cubic-bezier(0.4, 0, 0.2, 1);
164
+ &[data-ready] { opacity: 1; }
165
+ }
166
+
167
+ .cny-tabs.has-indicator {
168
+ .cny-tabs-trigger { position: relative; z-index: 1; }
169
+
170
+ &.variant-line {
171
+ .cny-tabs-trigger[data-state='active'] { border-bottom-color: transparent; border-right-color: transparent; }
172
+ .cny-tabs-indicator {
173
+ top: auto;
174
+ bottom: -1px;
175
+ height: 2px;
176
+ background: var(--color-primary);
177
+ transform: translateX(var(--cny-tabs-x, 0));
178
+ }
179
+ &[data-orientation='vertical'] .cny-tabs-indicator {
180
+ left: auto;
181
+ right: -1px;
182
+ top: 0;
183
+ bottom: auto;
184
+ width: 2px;
185
+ height: var(--cny-tabs-h, 0);
186
+ transform: translateY(var(--cny-tabs-y, 0));
187
+ }
188
+ }
189
+ &.variant-pills {
190
+ .cny-tabs-trigger[data-state='active'] { background: transparent; }
191
+ .cny-tabs-indicator {
192
+ border-radius: 999px;
193
+ background: color-mix(in srgb, var(--color-primary) 12%, transparent);
194
+ }
195
+ }
196
+ &.variant-segmented {
197
+ .cny-tabs-trigger[data-state='active'] { background: transparent; box-shadow: none; }
198
+ .cny-tabs-indicator {
199
+ border-radius: 999px;
200
+ background: var(--bg-surface-lowest);
201
+ box-shadow: 0 1px 2px rgba(15, 23, 42, 0.06), inset 0 0 0 1px var(--hairline-color);
202
+ }
203
+ }
204
+ }
205
+
206
+ // ── Scrolling list (Tabs `overflow="scroll"`) ─────────────────
207
+ .cny-tabs-scroller {
208
+ position: relative;
209
+ display: flex;
210
+ align-items: stretch;
211
+ min-width: 0;
212
+
213
+ .cny-tabs-list.is-scroll {
214
+ flex: 1 1 auto;
215
+ min-width: 0;
216
+ flex-wrap: nowrap;
217
+ overflow-x: auto;
218
+ scrollbar-width: none;
219
+ &::-webkit-scrollbar { display: none; }
220
+ }
221
+
222
+ // Edge fades appear only on the side that has more content.
223
+ &[data-start] .cny-tabs-list.is-scroll { mask-image: linear-gradient(to right, transparent, #000 28px); }
224
+ &[data-end] .cny-tabs-list.is-scroll { mask-image: linear-gradient(to left, transparent, #000 28px); }
225
+ &[data-start][data-end] .cny-tabs-list.is-scroll {
226
+ mask-image: linear-gradient(to right, transparent, #000 28px, #000 calc(100% - 28px), transparent);
227
+ }
228
+ }
229
+
230
+ .cny-tabs-arrow {
231
+ position: absolute;
232
+ top: 50%;
233
+ transform: translateY(-50%);
234
+ z-index: 2;
235
+ display: none;
236
+ align-items: center;
237
+ justify-content: center;
238
+ width: 24px;
239
+ height: 24px;
240
+ padding: 0;
241
+ border: 1px solid var(--hairline-color);
242
+ border-radius: 50%;
243
+ background: var(--bg-surface-lowest);
244
+ color: var(--text-secondary);
245
+ cursor: pointer;
246
+ &:hover { color: var(--text-primary); }
247
+ &.is-start { left: 0; }
248
+ &.is-end { right: 0; }
249
+ }
250
+ .cny-tabs-scroller[data-start] .cny-tabs-arrow.is-start,
251
+ .cny-tabs-scroller[data-end] .cny-tabs-arrow.is-end { display: inline-flex; }
252
+
253
+ @media (prefers-reduced-motion: reduce) {
254
+ .cny-tabs-indicator { transition: none; }
255
+ }
256
+
257
+ // Coarse pointers: >=44px triggers and scroll arrows.
258
+ @media (pointer: coarse) {
259
+ .cny-tabs-trigger { min-height: 44px; }
260
+ .cny-tabs-arrow {
261
+ width: 44px;
262
+ height: 44px;
263
+ }
264
+ }
@@ -1,3 +1,4 @@
1
+ @use '../base/scrollbar-mixin' as sb;
1
2
  @use '../tokens/primitives' as *;
2
3
 
3
4
  // Timeline — activity / history rail.
@@ -87,6 +88,8 @@
87
88
 
88
89
  &-soft {
89
90
  background: var(--dot-soft);
91
+ // The soft tint alone vanishes against either page; a tinted rim keeps the dot findable.
92
+ border: 1.5px solid color-mix(in srgb, var(--dot-color) 55%, transparent);
90
93
  color: var(--dot-color);
91
94
  }
92
95
 
@@ -293,6 +296,15 @@
293
296
  flex-direction: row;
294
297
  align-items: flex-start;
295
298
  width: 100%;
299
+ max-width: 100%;
300
+ // A wide rail scrolls inside itself rather than pushing the page sideways.
301
+ overflow-x: auto;
302
+ @include sb.expand-scrollbar;
303
+
304
+ &:focus-visible {
305
+ outline: 2px solid var(--border-focus);
306
+ outline-offset: 2px;
307
+ }
296
308
  }
297
309
 
298
310
  .cny-timeline-horizontal .cny-timeline-item {
@@ -366,3 +378,17 @@
366
378
  padding: 0 $space-2 $space-2;
367
379
  text-align: center;
368
380
  }
381
+
382
+ // `navigable` focus ring, plus `empty` state
383
+ .cny-timeline-item:focus-visible {
384
+ outline: 2px solid var(--border-focus);
385
+ outline-offset: 2px;
386
+ border-radius: var(--radius-sm);
387
+ }
388
+
389
+ .cny-timeline-empty {
390
+ list-style: none;
391
+ padding: 16px 0;
392
+ font-size: 0.875rem;
393
+ color: var(--text-muted);
394
+ }
@@ -61,6 +61,7 @@
61
61
  // ── Toast surface — flat white card ─────────────────────────────
62
62
  .cny-toast {
63
63
  --toast-accent: var(--color-primary);
64
+ --toast-accent-text: var(--text-accent);
64
65
 
65
66
  position: relative;
66
67
  display: flex;
@@ -75,23 +76,20 @@
75
76
  color: var(--text-primary);
76
77
  border: 1px solid color-mix(in srgb, var(--border-color) 60%, transparent);
77
78
  border-radius: 10px;
78
- box-shadow:
79
- 0 1px 2px rgba(15, 23, 42, 0.04),
80
- 0 8px 24px rgba(149, 157, 165, 0.18);
79
+ box-shadow: var(--shadow-popover);
81
80
  font-family: var(--font-body);
82
81
  font-size: 0.875rem;
83
82
  line-height: 1.5;
84
83
  overflow: hidden;
85
84
  pointer-events: auto;
85
+ touch-action: pan-y; // vertical page scroll stays native; horizontal = swipe-to-dismiss
86
86
  transition: transform 200ms cubic-bezier(0.2, 0.8, 0.2, 1),
87
87
  box-shadow 200ms cubic-bezier(0.2, 0.8, 0.2, 1);
88
88
  animation: cny-toast-in-bottom 220ms cubic-bezier(0.2, 0.8, 0.2, 1) both;
89
89
 
90
90
  &:hover {
91
91
  transform: translateY(-2px);
92
- box-shadow:
93
- 0 1px 2px rgba(15, 23, 42, 0.04),
94
- 0 14px 32px rgba(149, 157, 165, 0.24);
92
+ box-shadow: var(--shadow-lg);
95
93
  }
96
94
 
97
95
  // Direction-aware entrance
@@ -113,11 +111,11 @@
113
111
  }
114
112
 
115
113
  // ── Variants — only --toast-accent shifts ──
116
- &.success { --toast-accent: var(--color-success); }
117
- &.info { --toast-accent: var(--color-info); }
118
- &.warning { --toast-accent: var(--color-warning); }
119
- &.danger { --toast-accent: var(--color-danger); }
120
- &.loading { --toast-accent: var(--color-primary); }
114
+ &.success { --toast-accent: var(--color-success); --toast-accent-text: var(--color-success-text); }
115
+ &.info { --toast-accent: var(--color-info); --toast-accent-text: var(--color-info-text); }
116
+ &.warning { --toast-accent: var(--color-warning); --toast-accent-text: var(--color-warning-text); }
117
+ &.danger { --toast-accent: var(--color-danger); --toast-accent-text: var(--color-danger-text); }
118
+ &.loading { --toast-accent: var(--color-primary); --toast-accent-text: var(--text-accent); }
121
119
  }
122
120
 
123
121
  // ── Wave decoration on the left edge ─────────────────────────────
@@ -183,7 +181,7 @@
183
181
  letter-spacing: -0.01em;
184
182
  line-height: 1.25;
185
183
  margin: 0;
186
- color: var(--toast-accent);
184
+ color: var(--toast-accent-text);
187
185
  font-size: 16px;
188
186
  text-align: center;
189
187
  cursor: default;
@@ -208,6 +206,20 @@
208
206
  z-index: 1;
209
207
  }
210
208
 
209
+ .cny-toast-action-btn {
210
+ padding: 4px 10px;
211
+ background: transparent;
212
+ border: 0;
213
+ border-radius: var(--radius-sm);
214
+ font: inherit;
215
+ font-weight: 650;
216
+ color: var(--toast-accent-text);
217
+ cursor: pointer;
218
+ transition: background-color 140ms ease;
219
+ &:hover { background: color-mix(in srgb, var(--toast-accent) 12%, transparent); }
220
+ &:focus-visible { outline: 2px solid var(--toast-accent); outline-offset: 2px; }
221
+ }
222
+
211
223
  // ── Close button ───────────────────────────────────────────────
212
224
  .cny-toast-close {
213
225
  flex: none;
@@ -8,10 +8,16 @@
8
8
  }
9
9
 
10
10
  .cny-tooltip-content {
11
- z-index: 1020;
11
+ z-index: var(--z-tooltip, 1020);
12
12
  padding: 5px 9px;
13
- background: var(--text-primary);
14
- color: var(--bg-surface-lowest);
13
+ // Light: inverted dark bubble. Dark: an elevated dark surface with a border —
14
+ // an inverted (white) bubble glares on a dark page.
15
+ --cny-tt-bg: var(--text-primary);
16
+ --cny-tt-fg: var(--bg-surface-lowest);
17
+ --cny-tt-border: transparent;
18
+ background: var(--cny-tt-bg);
19
+ color: var(--cny-tt-fg);
20
+ border: 1px solid var(--cny-tt-border);
15
21
  border-radius: var(--radius-xs);
16
22
  font-family: var(--font-body);
17
23
  font-size: 0.75rem;
@@ -30,7 +36,22 @@
30
36
  &[data-side='right'] { transform-origin: left center; }
31
37
  }
32
38
 
33
- .cny-tooltip-arrow { fill: var(--text-primary); }
39
+ // Same trigger as the dark tokens: OS dark unless .cny-light, or an explicit .cny-dark.
40
+ @media (prefers-color-scheme: dark) {
41
+ :root:not(.cny-light) .cny-tooltip-content {
42
+ --cny-tt-bg: var(--bg-surface-edge);
43
+ --cny-tt-fg: var(--text-primary);
44
+ --cny-tt-border: var(--border-color-strong);
45
+ }
46
+ }
47
+
48
+ .cny-dark .cny-tooltip-content {
49
+ --cny-tt-bg: var(--bg-surface-edge);
50
+ --cny-tt-fg: var(--text-primary);
51
+ --cny-tt-border: var(--border-color-strong);
52
+ }
53
+
54
+ .cny-tooltip-arrow { fill: var(--cny-tt-bg, var(--text-primary)); }
34
55
 
35
56
  @media (prefers-reduced-motion: reduce) {
36
57
  .cny-tooltip-content { animation: none; }