@justin_evo/evo-ui 1.1.0 → 1.2.1

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 (80) hide show
  1. package/README.md +3 -3
  2. package/dist/TopNav/TopNav.d.ts +19 -0
  3. package/dist/declarations.d.ts +6 -6
  4. package/dist/evo-ui.css +1 -1
  5. package/dist/index.cjs.js +1 -1
  6. package/dist/index.es.js +3301 -3197
  7. package/package.json +52 -52
  8. package/src/Alert/Alert.tsx +49 -49
  9. package/src/AutoComplete/AutoComplete.tsx +810 -810
  10. package/src/Badge/Badge.tsx +53 -53
  11. package/src/Breadcrumb/Breadcrumb.tsx +53 -53
  12. package/src/Button/Button.tsx +125 -125
  13. package/src/Card/Card.tsx +257 -257
  14. package/src/Checkbox/Checkbox.tsx +59 -59
  15. package/src/CommandPalette/CommandPalette.tsx +185 -185
  16. package/src/Container/Container.tsx +31 -31
  17. package/src/Divider/Divider.tsx +31 -31
  18. package/src/Form/Form.tsx +185 -185
  19. package/src/Grid/Grid.tsx +66 -66
  20. package/src/ImageCropper/ImageCropper.tsx +911 -911
  21. package/src/Input/Input.tsx +74 -74
  22. package/src/Modal/Modal.tsx +77 -77
  23. package/src/Nav/Nav.tsx +708 -708
  24. package/src/Notification/Notification.tsx +1503 -1503
  25. package/src/Pagination/Pagination.tsx +76 -76
  26. package/src/Radio/Radio.tsx +69 -69
  27. package/src/RichTextArea/RichTextArea.tsx +886 -869
  28. package/src/Select/Select.tsx +515 -515
  29. package/src/Skeleton/Skeleton.tsx +70 -70
  30. package/src/Stack/Stack.tsx +52 -52
  31. package/src/Table/Table.tsx +335 -335
  32. package/src/Tabs/Tabs.tsx +90 -90
  33. package/src/Theme/ThemeProvider.tsx +253 -253
  34. package/src/Theme/ThemeToggle.tsx +79 -79
  35. package/src/Toggle/Toggle.tsx +48 -48
  36. package/src/Tooltip/Tooltip.tsx +38 -38
  37. package/src/TopNav/TopNav.tsx +1163 -994
  38. package/src/TreeSelect/TreeSelect.tsx +825 -825
  39. package/src/css/alert.module.scss +93 -93
  40. package/src/css/autocomplete.module.scss +416 -416
  41. package/src/css/badge.module.scss +82 -82
  42. package/src/css/base/_color.scss +159 -159
  43. package/src/css/base/_theme.scss +237 -237
  44. package/src/css/base/_variables.scss +161 -161
  45. package/src/css/breadcrumb.module.scss +50 -50
  46. package/src/css/button.module.scss +385 -385
  47. package/src/css/card.module.scss +217 -217
  48. package/src/css/checkbox.module.scss +123 -120
  49. package/src/css/commandpalette.module.scss +211 -211
  50. package/src/css/container.module.scss +18 -18
  51. package/src/css/divider.module.scss +41 -41
  52. package/src/css/form.module.scss +245 -245
  53. package/src/css/imagecropper.module.scss +397 -397
  54. package/src/css/input.module.scss +89 -89
  55. package/src/css/modal.module.scss +105 -105
  56. package/src/css/nav.module.scss +494 -494
  57. package/src/css/notification.module.scss +691 -691
  58. package/src/css/pagination.module.scss +63 -63
  59. package/src/css/radio.module.scss +89 -89
  60. package/src/css/richtextarea.module.scss +307 -307
  61. package/src/css/select.module.scss +525 -525
  62. package/src/css/skeleton.module.scss +30 -30
  63. package/src/css/table.module.scss +386 -386
  64. package/src/css/tabs.module.scss +63 -63
  65. package/src/css/theme-toggle.module.scss +83 -83
  66. package/src/css/toggle.module.scss +54 -54
  67. package/src/css/tooltip.module.scss +97 -97
  68. package/src/css/topnav.module.scss +568 -396
  69. package/src/css/treeselect.module.scss +558 -558
  70. package/src/css/utilities/_borders.scss +111 -111
  71. package/src/css/utilities/_colors.scss +66 -66
  72. package/src/css/utilities/_effects.scss +216 -216
  73. package/src/css/utilities/_layout.scss +181 -181
  74. package/src/css/utilities/_position.scss +75 -75
  75. package/src/css/utilities/_sizing.scss +138 -138
  76. package/src/css/utilities/_spacing.scss +99 -99
  77. package/src/css/utilities/_typography.scss +121 -121
  78. package/src/css/utilities/index.scss +24 -24
  79. package/src/declarations.d.ts +6 -6
  80. package/src/index.ts +60 -60
@@ -1,691 +1,691 @@
1
- @use 'base/variables' as *;
2
- @use 'base/color' as *;
3
-
4
- // ============================================================
5
- // EvoNotification — toast + inbox stylesheet
6
- // ------------------------------------------------------------
7
- // Two visual subsystems share this file:
8
- // 1. Toast (transient, portaled to body)
9
- // 2. Bell + Panel + Item (inbox center)
10
- // All colour comes from --evo-color-* tokens so dark mode is
11
- // automatic. Six anchors and a strict mobile breakpoint below.
12
- // ============================================================
13
-
14
- // ---------- Animations ----------
15
-
16
- @keyframes evoToastEnterRight {
17
- from { transform: translateX(110%); opacity: 0; }
18
- to { transform: translateX(0); opacity: 1; }
19
- }
20
-
21
- @keyframes evoToastEnterLeft {
22
- from { transform: translateX(-110%); opacity: 0; }
23
- to { transform: translateX(0); opacity: 1; }
24
- }
25
-
26
- @keyframes evoToastEnterTop {
27
- from { transform: translateY(-110%); opacity: 0; }
28
- to { transform: translateY(0); opacity: 1; }
29
- }
30
-
31
- @keyframes evoToastEnterBottom {
32
- from { transform: translateY(110%); opacity: 0; }
33
- to { transform: translateY(0); opacity: 1; }
34
- }
35
-
36
- @keyframes evoToastExit {
37
- from { opacity: 1; transform: scale(1); }
38
- to { opacity: 0; transform: scale(0.92); }
39
- }
40
-
41
- @keyframes evoPanelEnter {
42
- from { opacity: 0; transform: translateY(-6px) scale(0.98); }
43
- to { opacity: 1; transform: translateY(0) scale(1); }
44
- }
45
-
46
- // ---------- Toaster (portal root) ----------
47
-
48
- .toasterRoot {
49
- position: fixed;
50
- inset: 0;
51
- pointer-events: none;
52
- z-index: 10000;
53
- }
54
-
55
- // Visually-hidden screen-reader live region. Always present in the DOM so
56
- // announcements fire reliably when its text content changes.
57
- .srOnly {
58
- position: absolute;
59
- width: 1px;
60
- height: 1px;
61
- margin: -1px;
62
- padding: 0;
63
- border: 0;
64
- overflow: hidden;
65
- clip: rect(0, 0, 0, 0);
66
- white-space: nowrap;
67
- }
68
-
69
- .anchor {
70
- position: absolute;
71
- display: flex;
72
- flex-direction: column;
73
- gap: 0.5rem;
74
- padding: 1rem;
75
- max-width: min(420px, calc(100vw - 2rem));
76
- pointer-events: none;
77
- }
78
-
79
- .anchor > * {
80
- pointer-events: auto;
81
- }
82
-
83
- // Stacking direction. The newest toast is always rendered last in the DOM and
84
- // must sit flush against the anchored screen edge — top anchors grow downward
85
- // from the top, bottom anchors grow upward from the bottom. So top anchors
86
- // reverse the column (DOM-last renders visually first/topmost) while bottom
87
- // anchors keep the natural column (DOM-last renders visually last/bottom-most).
88
- // The depth offset in ToastRow then pushes older cards *away* from that edge.
89
- .anchor-top-left { top: 0; left: 0; align-items: flex-start; flex-direction: column-reverse; }
90
- .anchor-top-center { top: 0; left: 50%; transform: translateX(-50%); align-items: center; flex-direction: column-reverse; }
91
- .anchor-top-right { top: 0; right: 0; align-items: flex-end; flex-direction: column-reverse; }
92
- .anchor-bottom-left { bottom: 0; left: 0; align-items: flex-start; }
93
- .anchor-bottom-center {
94
- bottom: 0; left: 50%; transform: translateX(-50%);
95
- align-items: center;
96
- }
97
- .anchor-bottom-right {
98
- bottom: 0; right: 0; align-items: flex-end;
99
- }
100
-
101
- // Mobile: pin to top or bottom edge, full-width.
102
- @media (max-width: 23.4375rem) { // 375px
103
- .anchor {
104
- left: 0 !important;
105
- right: 0 !important;
106
- transform: none !important;
107
- max-width: 100%;
108
- padding: 0.5rem;
109
- align-items: stretch;
110
- }
111
- }
112
-
113
- .overflowPill {
114
- font-size: $text-xs;
115
- font-weight: 600;
116
- color: $color-text-secondary;
117
- background-color: $color-surface-elevated;
118
- border: 1px solid $color-border;
119
- border-radius: $radius-full;
120
- padding: 0.25rem 0.625rem;
121
- box-shadow: $shadow-sm;
122
- }
123
-
124
- // ---------- Single toast card ----------
125
-
126
- .toast {
127
- position: relative;
128
- display: flex;
129
- align-items: flex-start;
130
- gap: 0.625rem;
131
- padding: 0.75rem 0.875rem;
132
- min-width: 280px;
133
- max-width: 100%;
134
- background-color: $color-surface-elevated;
135
- color: $color-text-primary;
136
- border: 1px solid $color-border;
137
- border-radius: $radius-md;
138
- box-shadow: $shadow-xl;
139
- font-family: $font-sans;
140
- font-size: $text-sm;
141
- line-height: 1.45;
142
- // Left colour bar (severity)
143
- border-left-width: 3px;
144
- transition: transform 220ms cubic-bezier(0.16, 1, 0.3, 1),
145
- opacity 220ms ease;
146
- will-change: transform, opacity;
147
-
148
- // Width parity on mobile.
149
- @media (max-width: 23.4375rem) {
150
- min-width: 0;
151
- width: 100%;
152
- }
153
- }
154
-
155
- // Anchor-aware enter animations applied via parent.
156
- .anchor-top-right > .toast { animation: evoToastEnterRight 240ms cubic-bezier(0.16, 1, 0.3, 1); }
157
- .anchor-bottom-right > .toast { animation: evoToastEnterRight 240ms cubic-bezier(0.16, 1, 0.3, 1); }
158
- .anchor-top-left > .toast { animation: evoToastEnterLeft 240ms cubic-bezier(0.16, 1, 0.3, 1); }
159
- .anchor-bottom-left > .toast { animation: evoToastEnterLeft 240ms cubic-bezier(0.16, 1, 0.3, 1); }
160
- .anchor-top-center > .toast { animation: evoToastEnterTop 240ms cubic-bezier(0.16, 1, 0.3, 1); }
161
- .anchor-bottom-center > .toast { animation: evoToastEnterBottom 240ms cubic-bezier(0.16, 1, 0.3, 1); }
162
-
163
- .toast.exiting {
164
- animation: evoToastExit 180ms ease forwards;
165
- }
166
-
167
- .toast.noMotion,
168
- .toast.noMotion.exiting {
169
- animation: none !important;
170
- transition: opacity 1ms linear !important;
171
- }
172
-
173
- @media (prefers-reduced-motion: reduce) {
174
- .toast,
175
- .toast.exiting {
176
- animation: none !important;
177
- transition: opacity 1ms linear !important;
178
- }
179
- }
180
-
181
- .sev-success {
182
- border-left-color: $evo-success-color;
183
- .toastIcon { background-color: $evo-success-color; color: $evo-success-fg; }
184
- }
185
- .sev-error {
186
- border-left-color: $evo-danger-color;
187
- .toastIcon { background-color: $evo-danger-color; color: $evo-danger-fg; }
188
- }
189
- .sev-warning {
190
- border-left-color: $evo-warning-color;
191
- .toastIcon { background-color: $evo-warning-color; color: $evo-warning-text; }
192
- }
193
- .sev-info {
194
- border-left-color: $evo-info-color;
195
- .toastIcon { background-color: $evo-info-color; color: $evo-info-fg; }
196
- }
197
-
198
- .toastIcon {
199
- display: inline-flex;
200
- align-items: center;
201
- justify-content: center;
202
- width: 1.25rem;
203
- height: 1.25rem;
204
- border-radius: 50%;
205
- font-size: $text-xs;
206
- font-weight: 700;
207
- flex-shrink: 0;
208
- margin-top: 0.0625rem;
209
- }
210
-
211
- .toastBody {
212
- flex: 1;
213
- min-width: 0;
214
- }
215
-
216
- .toastTitle {
217
- font-weight: 600;
218
- color: $color-text-primary;
219
- }
220
-
221
- .toastDescription {
222
- margin-top: 0.1875rem;
223
- color: $color-text-secondary;
224
- font-size: $text-sm;
225
- }
226
-
227
- // Count badge for coalesced toasts (groupKey) — "×3" beside the title.
228
- .toastCount {
229
- display: inline-flex;
230
- align-items: center;
231
- justify-content: center;
232
- min-width: 1.25rem;
233
- height: 1.125rem;
234
- margin-left: 0.375rem;
235
- padding: 0 0.3125rem;
236
- font-size: $text-xs;
237
- font-weight: 700;
238
- font-variant-numeric: tabular-nums;
239
- color: $color-text-secondary;
240
- background-color: $color-surface-sunken;
241
- border: 1px solid $color-border;
242
- border-radius: $radius-full;
243
- vertical-align: middle;
244
- }
245
-
246
- // Determinate progress bar (toast.progress / EvoToastOptions.progress).
247
- // Pinned along the bottom edge of the card.
248
- .toastProgressTrack {
249
- position: absolute;
250
- left: 0;
251
- right: 0;
252
- bottom: 0;
253
- height: 3px;
254
- background-color: $color-border-subtle;
255
- border-bottom-left-radius: $radius-md;
256
- border-bottom-right-radius: $radius-md;
257
- overflow: hidden;
258
- }
259
-
260
- .toastProgressFill {
261
- height: 100%;
262
- width: 100%;
263
- transform-origin: left center;
264
- background-color: $evo-primary-color;
265
- transition: transform 240ms cubic-bezier(0.16, 1, 0.3, 1);
266
- }
267
-
268
- // Fill tracks the toast severity so a resolved progress toast reads green.
269
- .sev-success .toastProgressFill { background-color: $evo-success-color; }
270
- .sev-error .toastProgressFill { background-color: $evo-danger-color; }
271
- .sev-warning .toastProgressFill { background-color: $evo-warning-color; }
272
- .sev-info .toastProgressFill { background-color: $evo-info-color; }
273
-
274
- .toast.noMotion .toastProgressFill { transition: none; }
275
-
276
- @media (prefers-reduced-motion: reduce) {
277
- .toastProgressFill { transition: none; }
278
- }
279
-
280
- .toastAction {
281
- align-self: center;
282
- font: inherit;
283
- font-weight: 600;
284
- font-size: $text-xs;
285
- color: $evo-primary-color;
286
- background: transparent;
287
- border: 1px solid $color-border;
288
- border-radius: $radius-sm;
289
- padding: 0.25rem 0.5rem;
290
- cursor: pointer;
291
- flex-shrink: 0;
292
- min-height: 1.75rem;
293
- transition: background-color $transition-fast, border-color $transition-fast;
294
-
295
- &:hover { background-color: $color-surface-hover; }
296
- &:focus-visible {
297
- outline: 2px solid $evo-primary-focus;
298
- outline-offset: 1px;
299
- }
300
- }
301
-
302
- .toastClose {
303
- align-self: flex-start;
304
- background: transparent;
305
- border: none;
306
- color: $color-text-muted;
307
- font-size: $text-xs;
308
- cursor: pointer;
309
- padding: 0.25rem;
310
- border-radius: $radius-sm;
311
- flex-shrink: 0;
312
- transition: color $transition-fast, background-color $transition-fast;
313
-
314
- &:hover { color: $color-text-primary; background-color: $color-surface-hover; }
315
- &:focus-visible {
316
- outline: 2px solid $evo-primary-focus;
317
- outline-offset: 1px;
318
- }
319
- }
320
-
321
- // ---------- Bell ----------
322
-
323
- .bellWrapper {
324
- position: relative;
325
- display: inline-block;
326
- }
327
-
328
- .bell {
329
- position: relative;
330
- display: inline-flex;
331
- align-items: center;
332
- justify-content: center;
333
- background: transparent;
334
- border: 1px solid transparent;
335
- border-radius: $radius-full;
336
- color: $color-text-primary;
337
- cursor: pointer;
338
- font: inherit;
339
- transition: background-color $transition-fast, color $transition-fast, border-color $transition-fast;
340
-
341
- &:hover { background-color: $color-surface-hover; }
342
- &:focus-visible {
343
- outline: 2px solid $evo-primary-focus;
344
- outline-offset: 2px;
345
- }
346
- }
347
-
348
- .bell-ghost { background-color: transparent; }
349
- .bell-solid {
350
- background-color: $color-surface-elevated;
351
- border-color: $color-border;
352
- }
353
-
354
- .bell-sm { width: 2rem; height: 2rem; }
355
- .bell-md { width: 2.75rem; height: 2.75rem; } // 44px — WCAG touch target
356
- .bell-lg { width: 3rem; height: 3rem; }
357
-
358
- .bellOpen {
359
- background-color: $color-surface-hover;
360
- }
361
-
362
- .bellBadge {
363
- position: absolute;
364
- top: 0.125rem;
365
- right: 0.125rem;
366
- min-width: 1rem;
367
- height: 1rem;
368
- padding: 0 0.25rem;
369
- background-color: $evo-danger-color;
370
- color: $evo-danger-fg;
371
- font-size: 0.625rem;
372
- font-weight: 700;
373
- border-radius: $radius-full;
374
- display: inline-flex;
375
- align-items: center;
376
- justify-content: center;
377
- border: 2px solid $color-surface;
378
- line-height: 1;
379
- }
380
-
381
- .bellBadgeZero {
382
- background-color: $color-border-strong;
383
- color: $color-text-inverted;
384
- }
385
-
386
- // ---------- Panel placement (anchored to Bell) ----------
387
-
388
- .bellPanelHost {
389
- position: absolute;
390
- z-index: 9001;
391
- min-width: 22rem;
392
- max-width: 24rem;
393
-
394
- @media (max-width: 30rem) {
395
- position: fixed;
396
- top: auto !important;
397
- bottom: auto !important;
398
- left: 0.5rem !important;
399
- right: 0.5rem !important;
400
- min-width: 0;
401
- max-width: none;
402
- }
403
- }
404
-
405
- .place-bottom-end { top: calc(100% + 0.5rem); right: 0; }
406
- .place-bottom-start { top: calc(100% + 0.5rem); left: 0; }
407
- .place-bottom { top: calc(100% + 0.5rem); left: 50%; transform: translateX(-50%); }
408
- .place-top-end { bottom: calc(100% + 0.5rem); right: 0; }
409
- .place-top-start { bottom: calc(100% + 0.5rem); left: 0; }
410
-
411
- // ---------- Panel ----------
412
-
413
- .panel {
414
- background-color: $color-surface-elevated;
415
- border: 1px solid $color-border;
416
- border-radius: $radius-lg;
417
- box-shadow: $shadow-xl;
418
- display: flex;
419
- flex-direction: column;
420
- overflow: hidden;
421
- font-family: $font-sans;
422
- color: $color-text-primary;
423
- animation: evoPanelEnter 160ms ease;
424
-
425
- @media (prefers-reduced-motion: reduce) {
426
- animation: none;
427
- }
428
- }
429
-
430
- .panelHeader {
431
- display: flex;
432
- align-items: center;
433
- justify-content: space-between;
434
- padding: 0.75rem 1rem;
435
- border-bottom: 1px solid $color-border;
436
- gap: 0.5rem;
437
- }
438
-
439
- .panelTitle {
440
- font-weight: 600;
441
- font-size: $text-sm;
442
- }
443
-
444
- .panelHeaderActions {
445
- display: inline-flex;
446
- align-items: center;
447
- gap: 0.25rem;
448
- }
449
-
450
- .panelMarkAll {
451
- background: transparent;
452
- border: none;
453
- cursor: pointer;
454
- font: inherit;
455
- font-size: $text-xs;
456
- font-weight: 600;
457
- color: $evo-primary-color;
458
- padding: 0.25rem 0.5rem;
459
- border-radius: $radius-sm;
460
- min-height: 1.75rem;
461
-
462
- &:hover { background-color: $color-surface-hover; }
463
- &:focus-visible {
464
- outline: 2px solid $evo-primary-focus;
465
- outline-offset: 1px;
466
- }
467
- }
468
-
469
- .panelClose {
470
- background: transparent;
471
- border: none;
472
- cursor: pointer;
473
- color: $color-text-muted;
474
- padding: 0.25rem;
475
- font-size: $text-xs;
476
- border-radius: $radius-sm;
477
- min-height: 1.75rem;
478
- min-width: 1.75rem;
479
- display: inline-flex;
480
- align-items: center;
481
- justify-content: center;
482
-
483
- &:hover { color: $color-text-primary; background-color: $color-surface-hover; }
484
- }
485
-
486
- .panelBody {
487
- overflow-y: auto;
488
- }
489
-
490
- .panelState {
491
- display: flex;
492
- align-items: center;
493
- justify-content: center;
494
- padding: 2rem 1.5rem;
495
- color: $color-text-secondary;
496
- font-size: $text-sm;
497
- text-align: center;
498
- }
499
-
500
- .panelStateError {
501
- color: $evo-danger-color;
502
- }
503
-
504
- .itemList {
505
- list-style: none;
506
- margin: 0;
507
- padding: 0;
508
-
509
- li {
510
- border-bottom: 1px solid $color-border-subtle;
511
-
512
- &:last-child { border-bottom: none; }
513
- }
514
- }
515
-
516
- // ---------- Empty state ----------
517
-
518
- .emptyState {
519
- display: flex;
520
- flex-direction: column;
521
- align-items: center;
522
- gap: 0.5rem;
523
- padding: 2rem 1rem;
524
- text-align: center;
525
- }
526
-
527
- .emptyIcon {
528
- display: inline-flex;
529
- align-items: center;
530
- justify-content: center;
531
- width: 2.5rem;
532
- height: 2.5rem;
533
- border-radius: 50%;
534
- background-color: $color-surface-sunken;
535
- color: $color-text-muted;
536
- }
537
-
538
- .emptyTitle {
539
- font-weight: 600;
540
- font-size: $text-sm;
541
- color: $color-text-primary;
542
- }
543
-
544
- .emptyHint {
545
- font-size: $text-xs;
546
- color: $color-text-secondary;
547
- }
548
-
549
- // ---------- Item ----------
550
-
551
- .item {
552
- position: relative;
553
- display: flex;
554
- align-items: flex-start;
555
- gap: 0.625rem;
556
- padding: 0.75rem 1rem 0.75rem 1.25rem;
557
- background-color: transparent;
558
- transition: background-color $transition-fast;
559
-
560
- &.itemUnread { background-color: color-mix(in srgb, $evo-primary-color 6%, transparent); }
561
- }
562
-
563
- .itemInteractive {
564
- cursor: pointer;
565
-
566
- &:hover { background-color: $color-surface-hover; }
567
- &:focus-visible {
568
- outline: 2px solid $evo-primary-focus;
569
- outline-offset: -2px;
570
- }
571
- }
572
-
573
- .itemUnreadDot {
574
- position: absolute;
575
- left: 0.5rem;
576
- top: 1rem;
577
- width: 0.4375rem;
578
- height: 0.4375rem;
579
- border-radius: 50%;
580
- background-color: $evo-primary-color;
581
- opacity: 1;
582
-
583
- // Hidden when read (parent doesn't get .itemUnread).
584
- .item:not(.itemUnread) & { opacity: 0; }
585
- }
586
-
587
- .itemMedia {
588
- width: 2rem;
589
- height: 2rem;
590
- border-radius: 50%;
591
- display: inline-flex;
592
- align-items: center;
593
- justify-content: center;
594
- color: $color-text-inverted;
595
- font-size: $text-xs;
596
- font-weight: 700;
597
- flex-shrink: 0;
598
- overflow: hidden;
599
-
600
- &.sev-success { background-color: $evo-success-color; }
601
- &.sev-error { background-color: $evo-danger-color; }
602
- &.sev-warning { background-color: $evo-warning-color; }
603
- &.sev-info { background-color: $evo-info-color; }
604
- }
605
-
606
- .itemAvatar {
607
- width: 100%;
608
- height: 100%;
609
- object-fit: cover;
610
- }
611
-
612
- .itemMediaGlyph {
613
- line-height: 1;
614
- }
615
-
616
- .itemBody {
617
- flex: 1;
618
- min-width: 0;
619
- }
620
-
621
- .itemTitle {
622
- font-size: $text-sm;
623
- font-weight: 600;
624
- color: $color-text-primary;
625
- line-height: 1.35;
626
- }
627
-
628
- .itemDescription {
629
- margin-top: 0.125rem;
630
- font-size: $text-sm;
631
- color: $color-text-secondary;
632
- line-height: 1.4;
633
- display: -webkit-box;
634
- -webkit-line-clamp: 2;
635
- -webkit-box-orient: vertical;
636
- overflow: hidden;
637
- }
638
-
639
- .itemMeta {
640
- margin-top: 0.375rem;
641
- display: flex;
642
- align-items: center;
643
- gap: 0.5rem;
644
- font-size: $text-xs;
645
- color: $color-text-muted;
646
- }
647
-
648
- .itemTimestamp {
649
- font-variant-numeric: tabular-nums;
650
- }
651
-
652
- .itemAction {
653
- background: transparent;
654
- border: 1px solid $color-border;
655
- color: $evo-primary-color;
656
- border-radius: $radius-sm;
657
- cursor: pointer;
658
- font: inherit;
659
- font-size: $text-xs;
660
- font-weight: 600;
661
- padding: 0.1875rem 0.5rem;
662
- min-height: 1.5rem;
663
-
664
- &:hover { background-color: $color-surface-hover; }
665
- &:focus-visible {
666
- outline: 2px solid $evo-primary-focus;
667
- outline-offset: 1px;
668
- }
669
- }
670
-
671
- .itemDismiss {
672
- align-self: flex-start;
673
- background: transparent;
674
- border: none;
675
- color: $color-text-muted;
676
- font-size: $text-xs;
677
- cursor: pointer;
678
- padding: 0.25rem;
679
- border-radius: $radius-sm;
680
- opacity: 0;
681
- transition: opacity $transition-fast, color $transition-fast;
682
- flex-shrink: 0;
683
-
684
- .item:hover &,
685
- &:focus-visible { opacity: 1; }
686
- &:hover { color: $color-text-primary; }
687
- &:focus-visible {
688
- outline: 2px solid $evo-primary-focus;
689
- outline-offset: 1px;
690
- }
691
- }
1
+ @use 'base/variables' as *;
2
+ @use 'base/color' as *;
3
+
4
+ // ============================================================
5
+ // EvoNotification — toast + inbox stylesheet
6
+ // ------------------------------------------------------------
7
+ // Two visual subsystems share this file:
8
+ // 1. Toast (transient, portaled to body)
9
+ // 2. Bell + Panel + Item (inbox center)
10
+ // All colour comes from --evo-color-* tokens so dark mode is
11
+ // automatic. Six anchors and a strict mobile breakpoint below.
12
+ // ============================================================
13
+
14
+ // ---------- Animations ----------
15
+
16
+ @keyframes evoToastEnterRight {
17
+ from { transform: translateX(110%); opacity: 0; }
18
+ to { transform: translateX(0); opacity: 1; }
19
+ }
20
+
21
+ @keyframes evoToastEnterLeft {
22
+ from { transform: translateX(-110%); opacity: 0; }
23
+ to { transform: translateX(0); opacity: 1; }
24
+ }
25
+
26
+ @keyframes evoToastEnterTop {
27
+ from { transform: translateY(-110%); opacity: 0; }
28
+ to { transform: translateY(0); opacity: 1; }
29
+ }
30
+
31
+ @keyframes evoToastEnterBottom {
32
+ from { transform: translateY(110%); opacity: 0; }
33
+ to { transform: translateY(0); opacity: 1; }
34
+ }
35
+
36
+ @keyframes evoToastExit {
37
+ from { opacity: 1; transform: scale(1); }
38
+ to { opacity: 0; transform: scale(0.92); }
39
+ }
40
+
41
+ @keyframes evoPanelEnter {
42
+ from { opacity: 0; transform: translateY(-6px) scale(0.98); }
43
+ to { opacity: 1; transform: translateY(0) scale(1); }
44
+ }
45
+
46
+ // ---------- Toaster (portal root) ----------
47
+
48
+ .toasterRoot {
49
+ position: fixed;
50
+ inset: 0;
51
+ pointer-events: none;
52
+ z-index: 10000;
53
+ }
54
+
55
+ // Visually-hidden screen-reader live region. Always present in the DOM so
56
+ // announcements fire reliably when its text content changes.
57
+ .srOnly {
58
+ position: absolute;
59
+ width: 1px;
60
+ height: 1px;
61
+ margin: -1px;
62
+ padding: 0;
63
+ border: 0;
64
+ overflow: hidden;
65
+ clip: rect(0, 0, 0, 0);
66
+ white-space: nowrap;
67
+ }
68
+
69
+ .anchor {
70
+ position: absolute;
71
+ display: flex;
72
+ flex-direction: column;
73
+ gap: 0.5rem;
74
+ padding: 1rem;
75
+ max-width: min(420px, calc(100vw - 2rem));
76
+ pointer-events: none;
77
+ }
78
+
79
+ .anchor > * {
80
+ pointer-events: auto;
81
+ }
82
+
83
+ // Stacking direction. The newest toast is always rendered last in the DOM and
84
+ // must sit flush against the anchored screen edge — top anchors grow downward
85
+ // from the top, bottom anchors grow upward from the bottom. So top anchors
86
+ // reverse the column (DOM-last renders visually first/topmost) while bottom
87
+ // anchors keep the natural column (DOM-last renders visually last/bottom-most).
88
+ // The depth offset in ToastRow then pushes older cards *away* from that edge.
89
+ .anchor-top-left { top: 0; left: 0; align-items: flex-start; flex-direction: column-reverse; }
90
+ .anchor-top-center { top: 0; left: 50%; transform: translateX(-50%); align-items: center; flex-direction: column-reverse; }
91
+ .anchor-top-right { top: 0; right: 0; align-items: flex-end; flex-direction: column-reverse; }
92
+ .anchor-bottom-left { bottom: 0; left: 0; align-items: flex-start; }
93
+ .anchor-bottom-center {
94
+ bottom: 0; left: 50%; transform: translateX(-50%);
95
+ align-items: center;
96
+ }
97
+ .anchor-bottom-right {
98
+ bottom: 0; right: 0; align-items: flex-end;
99
+ }
100
+
101
+ // Mobile: pin to top or bottom edge, full-width.
102
+ @media (max-width: 23.4375rem) { // 375px
103
+ .anchor {
104
+ left: 0 !important;
105
+ right: 0 !important;
106
+ transform: none !important;
107
+ max-width: 100%;
108
+ padding: 0.5rem;
109
+ align-items: stretch;
110
+ }
111
+ }
112
+
113
+ .overflowPill {
114
+ font-size: $text-xs;
115
+ font-weight: 600;
116
+ color: $color-text-secondary;
117
+ background-color: $color-surface-elevated;
118
+ border: 1px solid $color-border;
119
+ border-radius: $radius-full;
120
+ padding: 0.25rem 0.625rem;
121
+ box-shadow: $shadow-sm;
122
+ }
123
+
124
+ // ---------- Single toast card ----------
125
+
126
+ .toast {
127
+ position: relative;
128
+ display: flex;
129
+ align-items: flex-start;
130
+ gap: 0.625rem;
131
+ padding: 0.75rem 0.875rem;
132
+ min-width: 280px;
133
+ max-width: 100%;
134
+ background-color: $color-surface-elevated;
135
+ color: $color-text-primary;
136
+ border: 1px solid $color-border;
137
+ border-radius: $radius-md;
138
+ box-shadow: $shadow-xl;
139
+ font-family: $font-sans;
140
+ font-size: $text-sm;
141
+ line-height: 1.45;
142
+ // Left colour bar (severity)
143
+ border-left-width: 3px;
144
+ transition: transform 220ms cubic-bezier(0.16, 1, 0.3, 1),
145
+ opacity 220ms ease;
146
+ will-change: transform, opacity;
147
+
148
+ // Width parity on mobile.
149
+ @media (max-width: 23.4375rem) {
150
+ min-width: 0;
151
+ width: 100%;
152
+ }
153
+ }
154
+
155
+ // Anchor-aware enter animations applied via parent.
156
+ .anchor-top-right > .toast { animation: evoToastEnterRight 240ms cubic-bezier(0.16, 1, 0.3, 1); }
157
+ .anchor-bottom-right > .toast { animation: evoToastEnterRight 240ms cubic-bezier(0.16, 1, 0.3, 1); }
158
+ .anchor-top-left > .toast { animation: evoToastEnterLeft 240ms cubic-bezier(0.16, 1, 0.3, 1); }
159
+ .anchor-bottom-left > .toast { animation: evoToastEnterLeft 240ms cubic-bezier(0.16, 1, 0.3, 1); }
160
+ .anchor-top-center > .toast { animation: evoToastEnterTop 240ms cubic-bezier(0.16, 1, 0.3, 1); }
161
+ .anchor-bottom-center > .toast { animation: evoToastEnterBottom 240ms cubic-bezier(0.16, 1, 0.3, 1); }
162
+
163
+ .toast.exiting {
164
+ animation: evoToastExit 180ms ease forwards;
165
+ }
166
+
167
+ .toast.noMotion,
168
+ .toast.noMotion.exiting {
169
+ animation: none !important;
170
+ transition: opacity 1ms linear !important;
171
+ }
172
+
173
+ @media (prefers-reduced-motion: reduce) {
174
+ .toast,
175
+ .toast.exiting {
176
+ animation: none !important;
177
+ transition: opacity 1ms linear !important;
178
+ }
179
+ }
180
+
181
+ .sev-success {
182
+ border-left-color: $evo-success-color;
183
+ .toastIcon { background-color: $evo-success-color; color: $evo-success-fg; }
184
+ }
185
+ .sev-error {
186
+ border-left-color: $evo-danger-color;
187
+ .toastIcon { background-color: $evo-danger-color; color: $evo-danger-fg; }
188
+ }
189
+ .sev-warning {
190
+ border-left-color: $evo-warning-color;
191
+ .toastIcon { background-color: $evo-warning-color; color: $evo-warning-text; }
192
+ }
193
+ .sev-info {
194
+ border-left-color: $evo-info-color;
195
+ .toastIcon { background-color: $evo-info-color; color: $evo-info-fg; }
196
+ }
197
+
198
+ .toastIcon {
199
+ display: inline-flex;
200
+ align-items: center;
201
+ justify-content: center;
202
+ width: 1.25rem;
203
+ height: 1.25rem;
204
+ border-radius: 50%;
205
+ font-size: $text-xs;
206
+ font-weight: 700;
207
+ flex-shrink: 0;
208
+ margin-top: 0.0625rem;
209
+ }
210
+
211
+ .toastBody {
212
+ flex: 1;
213
+ min-width: 0;
214
+ }
215
+
216
+ .toastTitle {
217
+ font-weight: 600;
218
+ color: $color-text-primary;
219
+ }
220
+
221
+ .toastDescription {
222
+ margin-top: 0.1875rem;
223
+ color: $color-text-secondary;
224
+ font-size: $text-sm;
225
+ }
226
+
227
+ // Count badge for coalesced toasts (groupKey) — "×3" beside the title.
228
+ .toastCount {
229
+ display: inline-flex;
230
+ align-items: center;
231
+ justify-content: center;
232
+ min-width: 1.25rem;
233
+ height: 1.125rem;
234
+ margin-left: 0.375rem;
235
+ padding: 0 0.3125rem;
236
+ font-size: $text-xs;
237
+ font-weight: 700;
238
+ font-variant-numeric: tabular-nums;
239
+ color: $color-text-secondary;
240
+ background-color: $color-surface-sunken;
241
+ border: 1px solid $color-border;
242
+ border-radius: $radius-full;
243
+ vertical-align: middle;
244
+ }
245
+
246
+ // Determinate progress bar (toast.progress / EvoToastOptions.progress).
247
+ // Pinned along the bottom edge of the card.
248
+ .toastProgressTrack {
249
+ position: absolute;
250
+ left: 0;
251
+ right: 0;
252
+ bottom: 0;
253
+ height: 3px;
254
+ background-color: $color-border-subtle;
255
+ border-bottom-left-radius: $radius-md;
256
+ border-bottom-right-radius: $radius-md;
257
+ overflow: hidden;
258
+ }
259
+
260
+ .toastProgressFill {
261
+ height: 100%;
262
+ width: 100%;
263
+ transform-origin: left center;
264
+ background-color: $evo-primary-color;
265
+ transition: transform 240ms cubic-bezier(0.16, 1, 0.3, 1);
266
+ }
267
+
268
+ // Fill tracks the toast severity so a resolved progress toast reads green.
269
+ .sev-success .toastProgressFill { background-color: $evo-success-color; }
270
+ .sev-error .toastProgressFill { background-color: $evo-danger-color; }
271
+ .sev-warning .toastProgressFill { background-color: $evo-warning-color; }
272
+ .sev-info .toastProgressFill { background-color: $evo-info-color; }
273
+
274
+ .toast.noMotion .toastProgressFill { transition: none; }
275
+
276
+ @media (prefers-reduced-motion: reduce) {
277
+ .toastProgressFill { transition: none; }
278
+ }
279
+
280
+ .toastAction {
281
+ align-self: center;
282
+ font: inherit;
283
+ font-weight: 600;
284
+ font-size: $text-xs;
285
+ color: $evo-primary-color;
286
+ background: transparent;
287
+ border: 1px solid $color-border;
288
+ border-radius: $radius-sm;
289
+ padding: 0.25rem 0.5rem;
290
+ cursor: pointer;
291
+ flex-shrink: 0;
292
+ min-height: 1.75rem;
293
+ transition: background-color $transition-fast, border-color $transition-fast;
294
+
295
+ &:hover { background-color: $color-surface-hover; }
296
+ &:focus-visible {
297
+ outline: 2px solid $evo-primary-focus;
298
+ outline-offset: 1px;
299
+ }
300
+ }
301
+
302
+ .toastClose {
303
+ align-self: flex-start;
304
+ background: transparent;
305
+ border: none;
306
+ color: $color-text-muted;
307
+ font-size: $text-xs;
308
+ cursor: pointer;
309
+ padding: 0.25rem;
310
+ border-radius: $radius-sm;
311
+ flex-shrink: 0;
312
+ transition: color $transition-fast, background-color $transition-fast;
313
+
314
+ &:hover { color: $color-text-primary; background-color: $color-surface-hover; }
315
+ &:focus-visible {
316
+ outline: 2px solid $evo-primary-focus;
317
+ outline-offset: 1px;
318
+ }
319
+ }
320
+
321
+ // ---------- Bell ----------
322
+
323
+ .bellWrapper {
324
+ position: relative;
325
+ display: inline-block;
326
+ }
327
+
328
+ .bell {
329
+ position: relative;
330
+ display: inline-flex;
331
+ align-items: center;
332
+ justify-content: center;
333
+ background: transparent;
334
+ border: 1px solid transparent;
335
+ border-radius: $radius-full;
336
+ color: $color-text-primary;
337
+ cursor: pointer;
338
+ font: inherit;
339
+ transition: background-color $transition-fast, color $transition-fast, border-color $transition-fast;
340
+
341
+ &:hover { background-color: $color-surface-hover; }
342
+ &:focus-visible {
343
+ outline: 2px solid $evo-primary-focus;
344
+ outline-offset: 2px;
345
+ }
346
+ }
347
+
348
+ .bell-ghost { background-color: transparent; }
349
+ .bell-solid {
350
+ background-color: $color-surface-elevated;
351
+ border-color: $color-border;
352
+ }
353
+
354
+ .bell-sm { width: 2rem; height: 2rem; }
355
+ .bell-md { width: 2.75rem; height: 2.75rem; } // 44px — WCAG touch target
356
+ .bell-lg { width: 3rem; height: 3rem; }
357
+
358
+ .bellOpen {
359
+ background-color: $color-surface-hover;
360
+ }
361
+
362
+ .bellBadge {
363
+ position: absolute;
364
+ top: 0.125rem;
365
+ right: 0.125rem;
366
+ min-width: 1rem;
367
+ height: 1rem;
368
+ padding: 0 0.25rem;
369
+ background-color: $evo-danger-color;
370
+ color: $evo-danger-fg;
371
+ font-size: 0.625rem;
372
+ font-weight: 700;
373
+ border-radius: $radius-full;
374
+ display: inline-flex;
375
+ align-items: center;
376
+ justify-content: center;
377
+ border: 2px solid $color-surface;
378
+ line-height: 1;
379
+ }
380
+
381
+ .bellBadgeZero {
382
+ background-color: $color-border-strong;
383
+ color: $color-text-inverted;
384
+ }
385
+
386
+ // ---------- Panel placement (anchored to Bell) ----------
387
+
388
+ .bellPanelHost {
389
+ position: absolute;
390
+ z-index: 9001;
391
+ min-width: 22rem;
392
+ max-width: 24rem;
393
+
394
+ @media (max-width: 30rem) {
395
+ position: fixed;
396
+ top: auto !important;
397
+ bottom: auto !important;
398
+ left: 0.5rem !important;
399
+ right: 0.5rem !important;
400
+ min-width: 0;
401
+ max-width: none;
402
+ }
403
+ }
404
+
405
+ .place-bottom-end { top: calc(100% + 0.5rem); right: 0; }
406
+ .place-bottom-start { top: calc(100% + 0.5rem); left: 0; }
407
+ .place-bottom { top: calc(100% + 0.5rem); left: 50%; transform: translateX(-50%); }
408
+ .place-top-end { bottom: calc(100% + 0.5rem); right: 0; }
409
+ .place-top-start { bottom: calc(100% + 0.5rem); left: 0; }
410
+
411
+ // ---------- Panel ----------
412
+
413
+ .panel {
414
+ background-color: $color-surface-elevated;
415
+ border: 1px solid $color-border;
416
+ border-radius: $radius-lg;
417
+ box-shadow: $shadow-xl;
418
+ display: flex;
419
+ flex-direction: column;
420
+ overflow: hidden;
421
+ font-family: $font-sans;
422
+ color: $color-text-primary;
423
+ animation: evoPanelEnter 160ms ease;
424
+
425
+ @media (prefers-reduced-motion: reduce) {
426
+ animation: none;
427
+ }
428
+ }
429
+
430
+ .panelHeader {
431
+ display: flex;
432
+ align-items: center;
433
+ justify-content: space-between;
434
+ padding: 0.75rem 1rem;
435
+ border-bottom: 1px solid $color-border;
436
+ gap: 0.5rem;
437
+ }
438
+
439
+ .panelTitle {
440
+ font-weight: 600;
441
+ font-size: $text-sm;
442
+ }
443
+
444
+ .panelHeaderActions {
445
+ display: inline-flex;
446
+ align-items: center;
447
+ gap: 0.25rem;
448
+ }
449
+
450
+ .panelMarkAll {
451
+ background: transparent;
452
+ border: none;
453
+ cursor: pointer;
454
+ font: inherit;
455
+ font-size: $text-xs;
456
+ font-weight: 600;
457
+ color: $evo-primary-color;
458
+ padding: 0.25rem 0.5rem;
459
+ border-radius: $radius-sm;
460
+ min-height: 1.75rem;
461
+
462
+ &:hover { background-color: $color-surface-hover; }
463
+ &:focus-visible {
464
+ outline: 2px solid $evo-primary-focus;
465
+ outline-offset: 1px;
466
+ }
467
+ }
468
+
469
+ .panelClose {
470
+ background: transparent;
471
+ border: none;
472
+ cursor: pointer;
473
+ color: $color-text-muted;
474
+ padding: 0.25rem;
475
+ font-size: $text-xs;
476
+ border-radius: $radius-sm;
477
+ min-height: 1.75rem;
478
+ min-width: 1.75rem;
479
+ display: inline-flex;
480
+ align-items: center;
481
+ justify-content: center;
482
+
483
+ &:hover { color: $color-text-primary; background-color: $color-surface-hover; }
484
+ }
485
+
486
+ .panelBody {
487
+ overflow-y: auto;
488
+ }
489
+
490
+ .panelState {
491
+ display: flex;
492
+ align-items: center;
493
+ justify-content: center;
494
+ padding: 2rem 1.5rem;
495
+ color: $color-text-secondary;
496
+ font-size: $text-sm;
497
+ text-align: center;
498
+ }
499
+
500
+ .panelStateError {
501
+ color: $evo-danger-color;
502
+ }
503
+
504
+ .itemList {
505
+ list-style: none;
506
+ margin: 0;
507
+ padding: 0;
508
+
509
+ li {
510
+ border-bottom: 1px solid $color-border-subtle;
511
+
512
+ &:last-child { border-bottom: none; }
513
+ }
514
+ }
515
+
516
+ // ---------- Empty state ----------
517
+
518
+ .emptyState {
519
+ display: flex;
520
+ flex-direction: column;
521
+ align-items: center;
522
+ gap: 0.5rem;
523
+ padding: 2rem 1rem;
524
+ text-align: center;
525
+ }
526
+
527
+ .emptyIcon {
528
+ display: inline-flex;
529
+ align-items: center;
530
+ justify-content: center;
531
+ width: 2.5rem;
532
+ height: 2.5rem;
533
+ border-radius: 50%;
534
+ background-color: $color-surface-sunken;
535
+ color: $color-text-muted;
536
+ }
537
+
538
+ .emptyTitle {
539
+ font-weight: 600;
540
+ font-size: $text-sm;
541
+ color: $color-text-primary;
542
+ }
543
+
544
+ .emptyHint {
545
+ font-size: $text-xs;
546
+ color: $color-text-secondary;
547
+ }
548
+
549
+ // ---------- Item ----------
550
+
551
+ .item {
552
+ position: relative;
553
+ display: flex;
554
+ align-items: flex-start;
555
+ gap: 0.625rem;
556
+ padding: 0.75rem 1rem 0.75rem 1.25rem;
557
+ background-color: transparent;
558
+ transition: background-color $transition-fast;
559
+
560
+ &.itemUnread { background-color: color-mix(in srgb, $evo-primary-color 6%, transparent); }
561
+ }
562
+
563
+ .itemInteractive {
564
+ cursor: pointer;
565
+
566
+ &:hover { background-color: $color-surface-hover; }
567
+ &:focus-visible {
568
+ outline: 2px solid $evo-primary-focus;
569
+ outline-offset: -2px;
570
+ }
571
+ }
572
+
573
+ .itemUnreadDot {
574
+ position: absolute;
575
+ left: 0.5rem;
576
+ top: 1rem;
577
+ width: 0.4375rem;
578
+ height: 0.4375rem;
579
+ border-radius: 50%;
580
+ background-color: $evo-primary-color;
581
+ opacity: 1;
582
+
583
+ // Hidden when read (parent doesn't get .itemUnread).
584
+ .item:not(.itemUnread) & { opacity: 0; }
585
+ }
586
+
587
+ .itemMedia {
588
+ width: 2rem;
589
+ height: 2rem;
590
+ border-radius: 50%;
591
+ display: inline-flex;
592
+ align-items: center;
593
+ justify-content: center;
594
+ color: $color-text-inverted;
595
+ font-size: $text-xs;
596
+ font-weight: 700;
597
+ flex-shrink: 0;
598
+ overflow: hidden;
599
+
600
+ &.sev-success { background-color: $evo-success-color; }
601
+ &.sev-error { background-color: $evo-danger-color; }
602
+ &.sev-warning { background-color: $evo-warning-color; }
603
+ &.sev-info { background-color: $evo-info-color; }
604
+ }
605
+
606
+ .itemAvatar {
607
+ width: 100%;
608
+ height: 100%;
609
+ object-fit: cover;
610
+ }
611
+
612
+ .itemMediaGlyph {
613
+ line-height: 1;
614
+ }
615
+
616
+ .itemBody {
617
+ flex: 1;
618
+ min-width: 0;
619
+ }
620
+
621
+ .itemTitle {
622
+ font-size: $text-sm;
623
+ font-weight: 600;
624
+ color: $color-text-primary;
625
+ line-height: 1.35;
626
+ }
627
+
628
+ .itemDescription {
629
+ margin-top: 0.125rem;
630
+ font-size: $text-sm;
631
+ color: $color-text-secondary;
632
+ line-height: 1.4;
633
+ display: -webkit-box;
634
+ -webkit-line-clamp: 2;
635
+ -webkit-box-orient: vertical;
636
+ overflow: hidden;
637
+ }
638
+
639
+ .itemMeta {
640
+ margin-top: 0.375rem;
641
+ display: flex;
642
+ align-items: center;
643
+ gap: 0.5rem;
644
+ font-size: $text-xs;
645
+ color: $color-text-muted;
646
+ }
647
+
648
+ .itemTimestamp {
649
+ font-variant-numeric: tabular-nums;
650
+ }
651
+
652
+ .itemAction {
653
+ background: transparent;
654
+ border: 1px solid $color-border;
655
+ color: $evo-primary-color;
656
+ border-radius: $radius-sm;
657
+ cursor: pointer;
658
+ font: inherit;
659
+ font-size: $text-xs;
660
+ font-weight: 600;
661
+ padding: 0.1875rem 0.5rem;
662
+ min-height: 1.5rem;
663
+
664
+ &:hover { background-color: $color-surface-hover; }
665
+ &:focus-visible {
666
+ outline: 2px solid $evo-primary-focus;
667
+ outline-offset: 1px;
668
+ }
669
+ }
670
+
671
+ .itemDismiss {
672
+ align-self: flex-start;
673
+ background: transparent;
674
+ border: none;
675
+ color: $color-text-muted;
676
+ font-size: $text-xs;
677
+ cursor: pointer;
678
+ padding: 0.25rem;
679
+ border-radius: $radius-sm;
680
+ opacity: 0;
681
+ transition: opacity $transition-fast, color $transition-fast;
682
+ flex-shrink: 0;
683
+
684
+ .item:hover &,
685
+ &:focus-visible { opacity: 1; }
686
+ &:hover { color: $color-text-primary; }
687
+ &:focus-visible {
688
+ outline: 2px solid $evo-primary-focus;
689
+ outline-offset: 1px;
690
+ }
691
+ }