@webority/theme 0.7.5 → 0.7.7

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.
@@ -10,6 +10,10 @@
10
10
  // .stepper-journey — wide: 40px dot beside a label + title, for a page masthead.
11
11
  // ============================================================================
12
12
  .stepper {
13
+ // Full width so the flex:1 steps spread evenly across the container. Without it,
14
+ // a stepper dropped into a flex/grid parent shrinks to its content and the steps
15
+ // bunch at one edge, colliding their labels.
16
+ width: 100%;
13
17
  display: flex;
14
18
  align-items: flex-start;
15
19
  gap: 0;
@@ -79,27 +83,61 @@
79
83
  .step.done .step-label {
80
84
  color: var(--color-text);
81
85
  }
82
- // A step that is an <a> is a deep link back to an earlier stage of the wizard.
86
+ // An interactive step — an <a> deep-linking back to a stage, or a <button> in a
87
+ // JS-driven wizard. A button needs its chrome stripped so it reads as a step.
88
+ button.step {
89
+ border: none;
90
+ background: none;
91
+ padding: 0;
92
+ font: inherit;
93
+ color: inherit;
94
+ }
83
95
  a.step {
84
96
  text-decoration: none;
97
+ }
98
+ a.step,
99
+ button.step {
85
100
  cursor: pointer;
86
101
  }
87
- a.step:hover .step-circle {
102
+ a.step:hover .step-circle,
103
+ button.step:hover .step-circle {
88
104
  box-shadow: 0 0 0 4px var(--color-primary-soft);
89
105
  transform: scale(1.04);
90
106
  }
91
- a.step:hover .step-label {
107
+ a.step:hover .step-label,
108
+ button.step:hover .step-label {
92
109
  color: var(--color-primary);
93
110
  }
94
- a.step:focus-visible {
111
+ a.step:focus-visible,
112
+ button.step:focus-visible {
95
113
  outline: none;
96
114
  }
97
- // The ring moves onto the circle: an outline on the <a> would box the whole column.
98
- a.step:focus-visible .step-circle {
115
+ // The ring moves onto the circle: an outline on the <a>/<button> would box the whole column.
116
+ a.step:focus-visible .step-circle,
117
+ button.step:focus-visible .step-circle {
99
118
  box-shadow:
100
119
  0 0 0 4px var(--color-primary-soft),
101
120
  0 0 0 6px var(--color-primary);
102
121
  }
122
+ // A step that failed its stage — a red circle with an alert, so an invalid step
123
+ // reads as wrong at a glance rather than merely "not done".
124
+ .step.error .step-circle {
125
+ border-color: var(--color-danger);
126
+ background: var(--color-danger);
127
+ color: var(--color-on-scrim);
128
+ }
129
+ .step.error .step-label {
130
+ color: var(--color-danger-text);
131
+ }
132
+ // A secondary line under the label — a short "what this step is for". Muted and
133
+ // narrow so it never competes with the label it explains.
134
+ .step-desc {
135
+ max-width: 130px;
136
+ font-size: 12px;
137
+ line-height: 1.3;
138
+ color: var(--color-text-muted);
139
+ overflow-wrap: break-word;
140
+ }
103
141
 
104
142
  .stepper-journey {
105
143
  display: flex;
@@ -166,6 +204,15 @@ a.step:focus-visible .step-circle {
166
204
  .stepper-journey .step.active .step-index {
167
205
  color: var(--color-primary);
168
206
  }
207
+ .stepper-journey .step.error .step-circle {
208
+ background: var(--color-danger-soft);
209
+ color: var(--color-danger-text);
210
+ box-shadow: none;
211
+ transform: none;
212
+ }
213
+ .stepper-journey .step.error .step-index {
214
+ color: var(--color-danger-text);
215
+ }
169
216
  .stepper-journey .step-label {
170
217
  max-width: none;
171
218
  margin-top: 8px;
@@ -176,6 +223,15 @@ a.step:focus-visible .step-circle {
176
223
  text-overflow: ellipsis;
177
224
  color: var(--color-text);
178
225
  }
226
+ // In the journey bar the description sits inside the text column, under the label —
227
+ // wider and left-aligned to match, not the centred compact treatment.
228
+ .stepper-journey .step-desc {
229
+ max-width: none;
230
+ margin-top: 2px;
231
+ white-space: nowrap;
232
+ overflow: hidden;
233
+ text-overflow: ellipsis;
234
+ }
179
235
  .stepper-journey .step-line {
180
236
  flex: none;
181
237
  width: 32px;
@@ -199,8 +255,86 @@ a.step:focus-visible .step-circle {
199
255
  display: none;
200
256
  }
201
257
  }
258
+ // Vertical orientation. The steps run down a side rail instead of across — for a
259
+ // narrow column, a drawer, or a settings wizard.
260
+ // ----------------------------------------------------------------------------
261
+ .stepper-vertical {
262
+ flex-direction: column;
263
+ align-items: stretch;
264
+ // The gap between steps; kept in sync with the ::after `bottom` so the connector
265
+ // spans it exactly and still meets the next circle.
266
+ gap: 18px;
267
+ padding: 0;
268
+ }
269
+ // Each compact step becomes a two-column row: the circle in a fixed rail on the
270
+ // left, the label and its description stacked on the right.
271
+ .stepper-vertical .step {
272
+ display: grid;
273
+ grid-template-columns: 28px 1fr;
274
+ column-gap: 12px;
275
+ // No gap between the label and its description — they read as one block; the
276
+ // base .step gap of 10px is for the horizontal circle→label spacing.
277
+ row-gap: 0;
278
+ align-items: start;
279
+ text-align: left;
280
+ }
281
+ // The circle spans the whole text block rather than sitting in the first row: a
282
+ // row sized to the 28px circle would leave dead space below a single-line label
283
+ // before the description. Spanning lets each row size to its text instead.
284
+ .stepper-vertical .step-circle {
285
+ grid-column: 1;
286
+ grid-row: 1 / -1;
287
+ align-self: start;
288
+ }
289
+ .stepper-vertical .step-label {
290
+ grid-column: 2;
291
+ grid-row: 1;
292
+ max-width: none;
293
+ // Centred against the circle when the step has no description (single 28px row);
294
+ // a no-op when a description makes the row size to the label.
295
+ align-self: center;
296
+ }
297
+ .stepper-vertical .step-desc {
298
+ grid-column: 2;
299
+ grid-row: 2;
300
+ max-width: none;
301
+ }
302
+ // The compact connector is horizontal (::before); redraw it as a vertical segment
303
+ // down the circle's centre, from below one circle to the top of the next.
304
+ .stepper-vertical .step + .step::before {
305
+ content: none;
306
+ }
307
+ .stepper-vertical .step:not(:last-child)::after {
308
+ content: "";
309
+ position: absolute;
310
+ // 13px = half the 28px circle minus 1 for the 2px line's own width.
311
+ left: 13px;
312
+ top: 28px;
313
+ // Reaches through the 18px inter-step gap to meet the next circle's top; keep
314
+ // this in sync with the .stepper-vertical `gap`.
315
+ bottom: -18px;
316
+ width: 2px;
317
+ background: var(--color-border);
318
+ z-index: 0;
319
+ }
320
+ .stepper-vertical .step.done:not(:last-child)::after {
321
+ background: var(--color-primary);
322
+ }
323
+ // The journey bar stacked vertically reuses its own narrow-width treatment: a plain
324
+ // column with the horizontal connectors dropped (a real vertical rail here would
325
+ // fight the two-line text blocks for alignment).
326
+ .stepper-journey.stepper-vertical {
327
+ flex-direction: column;
328
+ align-items: stretch;
329
+ gap: 10px;
330
+ }
331
+ .stepper-journey.stepper-vertical .step-line {
332
+ display: none;
333
+ }
334
+
202
335
  @media (prefers-reduced-motion: reduce) {
203
336
  a.step:hover .step-circle,
337
+ button.step:hover .step-circle,
204
338
  .stepper-journey .step-circle {
205
339
  transition: none;
206
340
  transform: none;
@@ -407,4 +541,79 @@ a.step:focus-visible .step-circle {
407
541
  text-align: center;
408
542
  }
409
543
 
410
- // ============================================================================
544
+ // ============================================================================
545
+ // Form layout helpers — section, row (grid), actions footer.
546
+ //
547
+ // AppFormSection wraps related fields with a title + description intro.
548
+ // AppFormRow arranges fields in a responsive grid (1/2/3 columns).
549
+ // AppFormActions is an action button footer, optionally sticky-bottom.
550
+ // ============================================================================
551
+ .form-section {
552
+ display: flex;
553
+ flex-direction: column;
554
+ gap: 20px;
555
+ }
556
+ .form-section-head {
557
+ display: flex;
558
+ flex-direction: column;
559
+ gap: 6px;
560
+ }
561
+ .form-section-title {
562
+ margin: 0;
563
+ font-size: 17px;
564
+ font-weight: 600;
565
+ line-height: 1.3;
566
+ color: var(--color-text);
567
+ }
568
+ .form-section-desc {
569
+ margin: 0;
570
+ font-size: 14px;
571
+ line-height: 1.4;
572
+ color: var(--color-text-muted);
573
+ }
574
+ .form-section-body {
575
+ display: flex;
576
+ flex-direction: column;
577
+ gap: 16px;
578
+ }
579
+
580
+ // ============================================================================
581
+ .form-row-grid {
582
+ display: grid;
583
+ grid-template-columns: repeat(var(--form-cols, 2), minmax(0, 1fr));
584
+ gap: 16px;
585
+ align-items: start;
586
+ }
587
+ // Below md, collapse to 1 column.
588
+ @include media-breakpoint-down(md) {
589
+ .form-row-grid.form-row-grid {
590
+ grid-template-columns: 1fr;
591
+ }
592
+ }
593
+
594
+ // ============================================================================
595
+ .form-actions {
596
+ display: flex;
597
+ align-items: center;
598
+ gap: 12px;
599
+ justify-content: flex-end;
600
+ }
601
+ .form-actions-start {
602
+ justify-content: flex-start;
603
+ }
604
+ .form-actions-between {
605
+ justify-content: space-between;
606
+ }
607
+ .form-actions-sticky {
608
+ position: sticky;
609
+ bottom: 0;
610
+ padding: 16px 0;
611
+ margin-top: 24px;
612
+ border-top: 1px solid var(--color-border);
613
+ background: var(--color-surface);
614
+ }
615
+ @media (prefers-reduced-motion: reduce) {
616
+ .form-actions-sticky {
617
+ transition: none;
618
+ }
619
+ }
@@ -6,26 +6,55 @@
6
6
  // here may be overridden by a later partial. Never reorder the imports.
7
7
 
8
8
  // ── Avatar ──
9
- // A photo fills the avatar disc edge to edge; the circle is the frame, so
10
- // the image is cropped to it rather than letterboxed.
9
+ // The initials/icon face is the base layer; a photo overlays it edge to edge, cropped
10
+ // to the disc. While the photo loads — or if it breaks — the face shows through.
11
+ .avatar-face {
12
+ display: inline-flex;
13
+ align-items: center;
14
+ justify-content: center;
15
+ line-height: 1;
16
+ }
11
17
  .avatar > .avatar-img {
18
+ position: absolute;
19
+ inset: 0;
12
20
  width: 100%;
13
21
  height: 100%;
14
22
  border-radius: inherit;
15
23
  object-fit: cover;
16
24
  display: block;
25
+ // Photo fades + scales in so a late-arriving blob doesn't pop.
26
+ animation: wui-avatar-in 0.28s var(--wui-ease-out-quart, ease) both;
17
27
  }
18
28
  .avatar {
19
- width: 32px;
20
- height: 32px;
29
+ // `--avatar-size` drives the default (md, its 32px fallback) and any custom size. It is
30
+ // read via `var(… , 32px)` rather than declared here, so an avatar with no size INHERITS
31
+ // one from an AppAvatarGroup that sets it — while a preset class (explicit width below) or
32
+ // an avatar's own inline `--avatar-size` still wins. Never re-declare it on `.avatar`.
33
+ position: relative;
34
+ width: var(--avatar-size, 32px);
35
+ height: var(--avatar-size, 32px);
21
36
  border-radius: 999px;
22
37
  background: color-mix(in srgb, var(--color-primary) 15%, var(--color-surface));
23
38
  color: var(--color-primary);
24
- font-size: 12px;
39
+ font-size: calc(var(--avatar-size, 32px) * 0.375);
25
40
  font-weight: 600;
26
41
  display: inline-flex;
27
42
  align-items: center;
28
43
  justify-content: center;
44
+ flex: none;
45
+ user-select: none;
46
+ }
47
+
48
+ // ── Size scale ──
49
+ .avatar-xs {
50
+ width: 20px;
51
+ height: 20px;
52
+ font-size: 8px;
53
+ }
54
+ .avatar-sm {
55
+ width: 24px;
56
+ height: 24px;
57
+ font-size: 10px;
29
58
  }
30
59
  .avatar-lg {
31
60
  width: 48px;
@@ -37,10 +66,230 @@
37
66
  height: 64px;
38
67
  font-size: 22px;
39
68
  }
40
- .avatar-sm {
41
- width: 24px;
42
- height: 24px;
69
+ .avatar-2xl {
70
+ width: 96px;
71
+ height: 96px;
72
+ font-size: 34px;
73
+ }
74
+
75
+ // ── Shape ── (`shape`): the disc is the default; rounded/square are card-like.
76
+ .avatar-rounded {
77
+ border-radius: 25%;
78
+ }
79
+ .avatar-square {
80
+ border-radius: 12%;
81
+ }
82
+
83
+ // ── Tone ── (`tone`): the initials fill. `auto` derives a stable hue from the name.
84
+ .avatar-neutral {
85
+ background: color-mix(in srgb, var(--color-text-muted) 16%, var(--color-surface));
86
+ color: var(--color-text-muted);
87
+ }
88
+ .avatar-success {
89
+ background: var(--color-success-soft);
90
+ color: var(--color-success-text);
91
+ }
92
+ .avatar-warning {
93
+ background: var(--color-warning-soft);
94
+ color: var(--color-warning-text);
95
+ }
96
+ .avatar-danger {
97
+ background: var(--color-danger-soft);
98
+ color: var(--color-danger-text);
99
+ }
100
+ .avatar-info {
101
+ background: var(--color-info-soft);
102
+ color: var(--color-info-text);
103
+ }
104
+ .avatar-auto {
105
+ background: hsl(var(--avatar-hue, 0) 60% 90%);
106
+ color: hsl(var(--avatar-hue, 0) 42% 32%);
107
+ }
108
+
109
+ // ── Ring ── (`ring`): a tone-coloured ring with a surface gap, separating stacked
110
+ // avatars or marking an active one.
111
+ .avatar-ring {
112
+ box-shadow:
113
+ 0 0 0 2px var(--color-surface),
114
+ 0 0 0 4px currentColor;
115
+ }
116
+
117
+ // ── Corner badge ── (`badge`): a small count/marker at the top-right.
118
+ .avatar-badge {
119
+ position: absolute;
120
+ top: -2px;
121
+ right: -2px;
122
+ display: inline-flex;
123
+ align-items: center;
124
+ justify-content: center;
125
+ min-width: 16px;
126
+ height: 16px;
127
+ padding: 0 4px;
128
+ border-radius: 999px;
129
+ background: var(--color-danger);
130
+ color: #fff;
43
131
  font-size: 10px;
132
+ font-weight: 700;
133
+ line-height: 1;
134
+ box-shadow: 0 0 0 2px var(--color-surface);
135
+ }
136
+
137
+ // ── Clickable ── (`href` / `onClick`): the whole disc is one control.
138
+ .avatar-clickable {
139
+ padding: 0;
140
+ border: 0;
141
+ font: inherit;
142
+ text-decoration: none;
143
+ cursor: pointer;
144
+ -webkit-appearance: none;
145
+ appearance: none;
146
+ transition:
147
+ transform 0.15s var(--wui-ease-out-quart, ease),
148
+ box-shadow 0.15s ease;
149
+ }
150
+ .avatar-clickable:hover {
151
+ transform: scale(1.06);
152
+ }
153
+ .avatar-clickable:focus-visible {
154
+ outline: 2px solid var(--color-primary);
155
+ outline-offset: 2px;
156
+ }
157
+
158
+ // ── Presence status ── (`status`): a dot at the lower-right, ringed in the surface
159
+ // colour so it reads on a photo. Online quietly pulses.
160
+ .avatar-status {
161
+ position: absolute;
162
+ right: 2%;
163
+ bottom: 2%;
164
+ width: 28%;
165
+ height: 28%;
166
+ min-width: 8px;
167
+ min-height: 8px;
168
+ max-width: 16px;
169
+ max-height: 16px;
170
+ border-radius: 999px;
171
+ box-shadow: 0 0 0 2px var(--color-surface);
172
+ }
173
+ .avatar-status-online {
174
+ background: var(--color-success);
175
+ }
176
+ .avatar-status-away {
177
+ background: var(--color-warning);
178
+ }
179
+ .avatar-status-busy {
180
+ background: var(--color-danger);
181
+ }
182
+ .avatar-status-offline {
183
+ background: color-mix(in srgb, var(--color-text-muted) 55%, var(--color-surface));
184
+ }
185
+ .avatar-status-online::after {
186
+ content: "";
187
+ position: absolute;
188
+ inset: 0;
189
+ border-radius: inherit;
190
+ background: var(--color-success);
191
+ animation: wui-avatar-ping 1.8s cubic-bezier(0, 0, 0.2, 1) infinite;
192
+ }
193
+
194
+ // ── Hover label ── (`label`): a tooltip above the avatar, revealed on hover/focus.
195
+ .avatar-label {
196
+ position: absolute;
197
+ bottom: calc(100% + 8px);
198
+ left: 50%;
199
+ transform: translateX(-50%) translateY(4px);
200
+ padding: 4px 8px;
201
+ border-radius: var(--radius-sm);
202
+ background: var(--color-text);
203
+ color: var(--color-surface);
204
+ font-size: 12px;
205
+ font-weight: 500;
206
+ line-height: 1.2;
207
+ white-space: nowrap;
208
+ opacity: 0;
209
+ pointer-events: none;
210
+ z-index: 30;
211
+ transition:
212
+ opacity 0.16s ease,
213
+ transform 0.16s var(--wui-ease-out-quart, ease);
214
+ }
215
+ .avatar-label::after {
216
+ content: "";
217
+ position: absolute;
218
+ top: 100%;
219
+ left: 50%;
220
+ transform: translateX(-50%);
221
+ border: 4px solid transparent;
222
+ border-top-color: var(--color-text);
223
+ }
224
+ .avatar:hover > .avatar-label,
225
+ .avatar:focus-visible > .avatar-label,
226
+ .avatar:focus-within > .avatar-label {
227
+ opacity: 1;
228
+ transform: translateX(-50%) translateY(0);
229
+ }
230
+
231
+ // ── Avatar group ── (AppAvatarGroup): a row of overlapping avatars, each ringed in the
232
+ // surface colour so it reads against its neighbour. Hover raises one to the front.
233
+ .avatar-group {
234
+ display: inline-flex;
235
+ align-items: center;
236
+ }
237
+ .avatar-group > .avatar {
238
+ margin-left: -10px;
239
+ box-shadow: 0 0 0 2px var(--color-surface);
240
+ transition: transform 0.15s var(--wui-ease-out-quart, ease);
241
+ }
242
+ .avatar-group > .avatar:first-child {
243
+ margin-left: 0;
244
+ }
245
+ .avatar-group > .avatar:hover {
246
+ z-index: 1;
247
+ transform: translateY(-2px);
248
+ }
249
+ .avatar-group-tight > .avatar {
250
+ margin-left: -14px;
251
+ }
252
+ .avatar-group-loose > .avatar {
253
+ margin-left: -4px;
254
+ }
255
+ // The "+N" overflow chip — a muted avatar-shaped tail.
256
+ .avatar-more {
257
+ background: color-mix(in srgb, var(--color-text-muted) 16%, var(--color-surface));
258
+ color: var(--color-text-muted);
259
+ font-weight: 600;
260
+ }
261
+
262
+ @keyframes wui-avatar-in {
263
+ from {
264
+ opacity: 0;
265
+ transform: scale(0.92);
266
+ }
267
+ to {
268
+ opacity: 1;
269
+ transform: scale(1);
270
+ }
271
+ }
272
+ @keyframes wui-avatar-ping {
273
+ 0% {
274
+ opacity: 0.5;
275
+ transform: scale(1);
276
+ }
277
+ 70%,
278
+ 100% {
279
+ opacity: 0;
280
+ transform: scale(2.2);
281
+ }
282
+ }
283
+ @media (prefers-reduced-motion: reduce) {
284
+ .avatar > .avatar-img {
285
+ animation: none;
286
+ }
287
+ .avatar-status-online::after {
288
+ animation: none;
289
+ }
290
+ .avatar-label {
291
+ transition: opacity 0.16s ease;
292
+ }
44
293
  }
45
294
 
46
295
  // ── File attach ──
@@ -63,6 +312,14 @@
63
312
  border-color: var(--color-primary);
64
313
  background: var(--color-primary-soft);
65
314
  }
315
+ // The hidden input is a sibling of the label (not a descendant), so :focus-within on
316
+ // .attach-drop never fires — reach the label through the general sibling combinator instead.
317
+ .attach-drop:has(+ input:focus-visible) {
318
+ border-color: var(--color-primary);
319
+ background: var(--color-primary-soft);
320
+ outline: 2px solid var(--color-primary);
321
+ outline-offset: 2px;
322
+ }
66
323
  .attach-drop-icon {
67
324
  flex: 0 0 auto;
68
325
  width: 36px;
@@ -161,7 +418,7 @@
161
418
  width: 28px;
162
419
  height: 28px;
163
420
  border: 0;
164
- background: rgba(0, 0, 0, 0.62);
421
+ background: var(--color-scrim-strong);
165
422
  backdrop-filter: blur(10px);
166
423
  -webkit-backdrop-filter: blur(10px);
167
424
  color: #fff;
@@ -524,8 +781,9 @@
524
781
  display: flex;
525
782
  align-items: center;
526
783
  justify-content: center;
527
- // Subtle dim so the glassy CTA reads on any image, without hiding it.
528
- background: rgba(16, 24, 40, 0.22);
784
+ // Subtle dim so the glassy CTA reads on any image, without hiding it —
785
+ // half the standard scrim strength.
786
+ background: color-mix(in srgb, var(--color-scrim) 50%, transparent);
529
787
  opacity: 0;
530
788
  transition: opacity 0.15s ease;
531
789
  }
@@ -560,7 +818,7 @@
560
818
  padding: 0;
561
819
  border: 2px solid transparent;
562
820
  border-radius: 50%;
563
- background: rgba(16, 24, 40, 0.55);
821
+ background: var(--color-scrim-strong);
564
822
  color: #fff;
565
823
  cursor: pointer;
566
824
  opacity: 0;
@@ -646,7 +904,7 @@
646
904
  align-items: center;
647
905
  justify-content: center;
648
906
  gap: 4px;
649
- background: rgba(20, 26, 33, 0.62);
907
+ background: var(--color-scrim-strong);
650
908
  color: #fff;
651
909
  font-size: 11px;
652
910
  font-weight: 600;
@@ -794,7 +1052,7 @@
794
1052
  align-items: center;
795
1053
  justify-content: center;
796
1054
  gap: 4px;
797
- background: var(--color-scrim);
1055
+ background: var(--color-scrim-strong);
798
1056
  color: var(--color-on-scrim);
799
1057
  font-size: 12px;
800
1058
  font-weight: 600;
@@ -42,7 +42,9 @@
42
42
  height: 100vh;
43
43
  background: var(--color-surface);
44
44
  border-left: 1px solid var(--color-border);
45
- z-index: 200;
45
+ // Same rung as the mobile sidebar drawer — the highest shell-chrome rung below
46
+ // the modal ladder, so this panel is never trapped under sticky/fixed shell chrome.
47
+ z-index: var(--wui-z-shell-drawer);
46
48
  transform: translateX(100%);
47
49
  transition: transform 0.25s cubic-bezier(0.4, 0, 0.2, 1);
48
50
  display: flex;
@@ -54,11 +56,11 @@
54
56
  .drawer-backdrop {
55
57
  position: fixed;
56
58
  inset: 0;
57
- background: rgba(15, 23, 42, 0.4);
59
+ background: var(--color-scrim);
58
60
  opacity: 0;
59
61
  pointer-events: none;
60
62
  transition: opacity 0.2s;
61
- z-index: 150;
63
+ z-index: var(--wui-z-shell-scrim);
62
64
  }
63
65
  .drawer-backdrop.open {
64
66
  opacity: 1;
@@ -15,7 +15,7 @@
15
15
  // Do NOT put layout on backdropClassName — that targets a sibling, not
16
16
  // .modal.
17
17
  .modal-backdrop.wui-modal-dim {
18
- background: rgba(15, 23, 42, 0.45);
18
+ background: var(--color-scrim);
19
19
  z-index: var(--wui-z-modal-backdrop);
20
20
  }
21
21
  .modal.wui-modal-shell {
@@ -328,3 +328,47 @@
328
328
  font-size: 14px;
329
329
  font-weight: 500;
330
330
  }
331
+
332
+ // ── Toast (notifications) ──
333
+ // Bootstrap toast styling + wui extensions for title/action buttons.
334
+ // Used by both React AppToastContainer and Razor WUI.toast.
335
+
336
+ // Toast header with title
337
+ .wui-toast-header {
338
+ display: flex;
339
+ align-items: center;
340
+ justify-content: space-between;
341
+ gap: 12px;
342
+ padding: 10px 12px;
343
+ border: none;
344
+ }
345
+
346
+ .wui-toast-header strong {
347
+ font-size: 14px;
348
+ font-weight: 600;
349
+ color: inherit;
350
+ }
351
+
352
+ // Toast action button — inherits text color from the variant tone. Scoped to the
353
+ // component class, not every .btn-link a consumer might place in a toast body.
354
+ .toast .wui-toast-action {
355
+ color: inherit;
356
+ text-decoration: none;
357
+ font-size: 13px;
358
+ font-weight: 500;
359
+ padding: 2px 8px;
360
+ white-space: nowrap;
361
+ flex: none;
362
+ opacity: 0.9;
363
+ transition: opacity 150ms;
364
+ }
365
+
366
+ .toast .wui-toast-action:hover {
367
+ opacity: 1;
368
+ text-decoration: underline;
369
+ }
370
+
371
+ .toast .wui-toast-action:focus-visible {
372
+ outline: 2px solid currentColor;
373
+ outline-offset: 2px;
374
+ }