@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.
- package/dist/webority-theme.css +1 -1
- package/package.json +1 -1
- package/scss/_components.scss +13 -8
- package/scss/_tokens.scss +10 -2
- package/scss/_variables.scss +17 -0
- package/scss/_wui-aliases.scss +11 -3
- package/scss/wui/_card.scss +138 -0
- package/scss/wui/_choice.scss +124 -2
- package/scss/wui/_controls.scss +228 -3
- package/scss/wui/_data.scss +26 -13
- package/scss/wui/_display.scss +160 -1
- package/scss/wui/_flow.scss +216 -7
- package/scss/wui/_media.scss +272 -14
- package/scss/wui/_navigation.scss +5 -3
- package/scss/wui/_overlays.scss +45 -1
- package/scss/wui/_shell.scss +14 -4
- package/scss/wui/_typography.scss +16 -0
- package/scss/wui/_utilities.scss +144 -7
package/scss/wui/_flow.scss
CHANGED
|
@@ -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
|
-
//
|
|
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
|
+
}
|
package/scss/wui/_media.scss
CHANGED
|
@@ -6,26 +6,55 @@
|
|
|
6
6
|
// here may be overridden by a later partial. Never reorder the imports.
|
|
7
7
|
|
|
8
8
|
// ── Avatar ──
|
|
9
|
-
//
|
|
10
|
-
// the
|
|
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
|
-
|
|
20
|
-
|
|
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:
|
|
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-
|
|
41
|
-
width:
|
|
42
|
-
height:
|
|
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:
|
|
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
|
-
|
|
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:
|
|
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:
|
|
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
|
-
|
|
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:
|
|
59
|
+
background: var(--color-scrim);
|
|
58
60
|
opacity: 0;
|
|
59
61
|
pointer-events: none;
|
|
60
62
|
transition: opacity 0.2s;
|
|
61
|
-
z-index:
|
|
63
|
+
z-index: var(--wui-z-shell-scrim);
|
|
62
64
|
}
|
|
63
65
|
.drawer-backdrop.open {
|
|
64
66
|
opacity: 1;
|
package/scss/wui/_overlays.scss
CHANGED
|
@@ -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:
|
|
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
|
+
}
|