@webority/theme 0.7.6 → 0.7.8
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/README.md +50 -0
- package/dist/webority-theme.css +1 -1
- package/package.json +1 -1
- package/scss/_components.scss +184 -14
- package/scss/_tokens.scss +11 -3
- package/scss/_variables.scss +17 -0
- package/scss/_wui-aliases.scss +35 -5
- package/scss/_wui-components.scss +3 -0
- package/scss/wui/_card.scss +2 -2
- package/scss/wui/_choice.scss +11 -4
- package/scss/wui/_controls.scss +65 -24
- package/scss/wui/_data.scss +20 -0
- package/scss/wui/_media.scss +9 -1
- package/scss/wui/_navigation.scss +4 -2
- package/scss/wui/_overlays.scss +254 -25
- package/scss/wui/_shell.scss +49 -5
- package/scss/wui/_utilities.scss +3 -1
- package/scss/wui/_workspace.scss +239 -0
package/scss/wui/_controls.scss
CHANGED
|
@@ -191,6 +191,11 @@
|
|
|
191
191
|
// deltas on top of Bootstrap's $input-* vars: shared control height, hover
|
|
192
192
|
// fill, brand-border focus (no glow), soft invalid border.
|
|
193
193
|
//
|
|
194
|
+
// Soft-filled resting look (per the field design reference): the field rests on a
|
|
195
|
+
// muted fill with a barely-there border, then lifts to a white surface + brand
|
|
196
|
+
// border + a soft focus ring on focus. "Filled" is the same resting look with a
|
|
197
|
+
// value; the state ladder is default/filled → hover → focus → disabled → error.
|
|
198
|
+
//
|
|
194
199
|
// Keep shell utilities (.input-wrap, .input-icon, .input-sm, …) — those are
|
|
195
200
|
// structure, not a parallel visual system.
|
|
196
201
|
.form-control,
|
|
@@ -204,7 +209,7 @@
|
|
|
204
209
|
padding: 12px;
|
|
205
210
|
border-radius: var(--radius);
|
|
206
211
|
border: 1px solid var(--color-border);
|
|
207
|
-
background: var(--color-
|
|
212
|
+
background: var(--color-bg-muted);
|
|
208
213
|
font-size: 14px;
|
|
209
214
|
line-height: 1.5;
|
|
210
215
|
color: var(--color-text);
|
|
@@ -246,7 +251,7 @@ textarea.form-control.autosize {
|
|
|
246
251
|
// Hover/error guards: without them the hover fill repaints over an invalid border.
|
|
247
252
|
.form-control:hover:not(:focus):not(:disabled):not([readonly]):not([aria-invalid="true"]):not(.is-invalid):not(.error),
|
|
248
253
|
.form-select:hover:not(:focus):not(:disabled):not([aria-invalid="true"]):not(.is-invalid):not(.error),
|
|
249
|
-
.input:hover:not(:focus):not(:disabled):not([readonly]):not([aria-invalid="true"]):not(.is-invalid):not(.error),
|
|
254
|
+
.input:hover:not(:focus):not(:disabled):not([readonly]):not([aria-invalid="true"]):not(.is-invalid):not(.error):not(.open),
|
|
250
255
|
.select:hover:not(:focus):not(:disabled):not([aria-invalid="true"]):not(.is-invalid):not(.error),
|
|
251
256
|
.textarea:hover:not(:focus):not(:disabled):not([readonly]):not([aria-invalid="true"]):not(.is-invalid):not(.error) {
|
|
252
257
|
background-color: var(--color-bg-muted);
|
|
@@ -258,10 +263,11 @@ textarea.form-control.autosize {
|
|
|
258
263
|
.select:focus,
|
|
259
264
|
.textarea:focus {
|
|
260
265
|
outline: none;
|
|
261
|
-
|
|
266
|
+
// Lift off the muted resting fill onto a clean white surface while active.
|
|
267
|
+
background-color: var(--color-surface);
|
|
262
268
|
color: var(--color-text);
|
|
263
|
-
//
|
|
264
|
-
border-color: var(--
|
|
269
|
+
// Soft brand border is the focus cue (per the reference) — no ring/shadow.
|
|
270
|
+
border-color: var(--wui-primary-30);
|
|
265
271
|
box-shadow: none;
|
|
266
272
|
}
|
|
267
273
|
.form-control::placeholder,
|
|
@@ -297,8 +303,10 @@ textarea.form-control.autosize {
|
|
|
297
303
|
.input:disabled,
|
|
298
304
|
.select:disabled,
|
|
299
305
|
.textarea:disabled {
|
|
300
|
-
|
|
301
|
-
|
|
306
|
+
// A touch deeper than the resting fill so a disabled field stays distinct now that
|
|
307
|
+
// the resting state is itself filled (per the reference's disabled field).
|
|
308
|
+
background: color-mix(in srgb, var(--color-bg-muted) 72%, var(--color-border));
|
|
309
|
+
border-color: transparent;
|
|
302
310
|
color: var(--color-text-subtle);
|
|
303
311
|
cursor: not-allowed;
|
|
304
312
|
}
|
|
@@ -350,7 +358,9 @@ textarea.form-control.autosize {
|
|
|
350
358
|
.input.error:focus,
|
|
351
359
|
.textarea.error:focus {
|
|
352
360
|
background: var(--color-surface);
|
|
353
|
-
|
|
361
|
+
// Soft danger border (danger at 30%, mirroring the primary-30 focus border) — no ring,
|
|
362
|
+
// so the field reads as a quiet error, not a solid alarm box.
|
|
363
|
+
border-color: color-mix(in srgb, var(--color-danger) 30%, transparent);
|
|
354
364
|
box-shadow: none;
|
|
355
365
|
// Kill Bootstrap's default invalid SVG + extra padding when .is-invalid is used.
|
|
356
366
|
background-image: none;
|
|
@@ -618,17 +628,23 @@ textarea.form-control.autosize {
|
|
|
618
628
|
width: 100%;
|
|
619
629
|
border: 1px solid var(--color-border);
|
|
620
630
|
border-radius: var(--radius);
|
|
621
|
-
|
|
622
|
-
|
|
631
|
+
// Soft-filled at rest, white on focus — one shared fill for the whole control so the
|
|
632
|
+
// prefix/suffix and the input body are always the SAME colour combination as the field.
|
|
633
|
+
background: var(--color-bg-muted);
|
|
634
|
+
transition:
|
|
635
|
+
border-color 0.12s ease,
|
|
636
|
+
background-color 0.12s ease;
|
|
623
637
|
}
|
|
624
638
|
.input-affixed:hover:not(:focus-within) {
|
|
625
639
|
border-color: var(--color-border-strong);
|
|
626
640
|
}
|
|
627
641
|
.input-affixed:focus-within {
|
|
628
|
-
|
|
642
|
+
background: var(--color-surface);
|
|
643
|
+
border-color: var(--wui-primary-30);
|
|
629
644
|
}
|
|
630
645
|
.input-affixed:has(.input.error) {
|
|
631
|
-
|
|
646
|
+
background: var(--color-surface);
|
|
647
|
+
border-color: color-mix(in srgb, var(--color-danger) 30%, transparent);
|
|
632
648
|
}
|
|
633
649
|
.input-affixed .input {
|
|
634
650
|
flex: 1;
|
|
@@ -641,7 +657,9 @@ textarea.form-control.autosize {
|
|
|
641
657
|
display: flex;
|
|
642
658
|
align-items: center;
|
|
643
659
|
padding: 0 12px;
|
|
644
|
-
|
|
660
|
+
// Transparent so the affix inherits the wrapper fill — the addon and the input read as
|
|
661
|
+
// one continuous surface (only the separator border divides them).
|
|
662
|
+
background: transparent;
|
|
645
663
|
color: var(--color-text-subtle);
|
|
646
664
|
font-size: 14px;
|
|
647
665
|
white-space: nowrap;
|
|
@@ -669,19 +687,27 @@ textarea.form-control.autosize {
|
|
|
669
687
|
transform: translateY(-50%);
|
|
670
688
|
transform-origin: left center;
|
|
671
689
|
margin: 0;
|
|
672
|
-
padding: 0
|
|
690
|
+
padding: 0 6px;
|
|
691
|
+
// Rounded so the background mask reads as a soft pill sitting on the border, not a sharp
|
|
692
|
+
// rectangle. Invisible at rest (label fill == field fill); only shows once it floats up.
|
|
693
|
+
border-radius: var(--radius-sm);
|
|
673
694
|
max-width: calc(100% - 24px);
|
|
674
695
|
overflow: hidden;
|
|
675
696
|
text-overflow: ellipsis;
|
|
676
697
|
white-space: nowrap;
|
|
677
698
|
font-size: 14px;
|
|
678
699
|
color: var(--color-text-subtle);
|
|
679
|
-
background
|
|
700
|
+
// The floated label sits ON the top border, so its background masks the border under the
|
|
701
|
+
// text. It MUST match the field's own fill, or the mask reads as a separate patch. The
|
|
702
|
+
// resting/filled fill is --color-bg-muted (a white notch on the grey fill was the tell); it
|
|
703
|
+
// only lifts to --color-surface on focus/invalid, matched below.
|
|
704
|
+
background: var(--color-bg-muted);
|
|
680
705
|
pointer-events: none;
|
|
681
706
|
transition:
|
|
682
707
|
top 0.15s var(--wui-ease-ui),
|
|
683
708
|
transform 0.15s var(--wui-ease-ui),
|
|
684
|
-
color 0.15s var(--wui-ease-ui)
|
|
709
|
+
color 0.15s var(--wui-ease-ui),
|
|
710
|
+
background-color 0.12s;
|
|
685
711
|
}
|
|
686
712
|
.field-float .input:focus ~ .float-label,
|
|
687
713
|
.field-float .input:not(:placeholder-shown) ~ .float-label {
|
|
@@ -694,6 +720,14 @@ textarea.form-control.autosize {
|
|
|
694
720
|
.field-float .input.error ~ .float-label {
|
|
695
721
|
color: var(--color-danger-text);
|
|
696
722
|
}
|
|
723
|
+
// Track the field's fill: it lifts to --color-surface while focused or invalid, so the label
|
|
724
|
+
// mask follows to stay seamless in those states too.
|
|
725
|
+
.field-float .input:focus ~ .float-label,
|
|
726
|
+
.field-float .input.error ~ .float-label,
|
|
727
|
+
.field-float .input.is-invalid ~ .float-label,
|
|
728
|
+
.field-float .input[aria-invalid="true"] ~ .float-label {
|
|
729
|
+
background: var(--color-surface);
|
|
730
|
+
}
|
|
697
731
|
// Floating label for AppSelect — the trigger is a <button>, so there is no
|
|
698
732
|
// :placeholder-shown to key off. :has() raises the label when the control is
|
|
699
733
|
// focused/open, or once a value is chosen (the label span drops
|
|
@@ -707,6 +741,9 @@ textarea.form-control.autosize {
|
|
|
707
741
|
.field-float:has(.search-select-control:focus) .float-label,
|
|
708
742
|
.field-float:has(.search-select-control.open) .float-label {
|
|
709
743
|
color: var(--color-primary);
|
|
744
|
+
// The control turns white while focused/open, so the label mask follows (a filled-but-closed
|
|
745
|
+
// select keeps the default muted mask, matching its muted resting fill).
|
|
746
|
+
background: var(--color-surface);
|
|
710
747
|
}
|
|
711
748
|
|
|
712
749
|
.hint,
|
|
@@ -859,22 +896,24 @@ wui-select[compact] {
|
|
|
859
896
|
border: 1px solid var(--color-border);
|
|
860
897
|
border-radius: var(--radius-lg);
|
|
861
898
|
corner-shape: squircle;
|
|
862
|
-
|
|
899
|
+
// Soft-filled at rest, white on focus — the numeric input and its unit pill share the
|
|
900
|
+
// one wrapper fill, same colour combination as every other field.
|
|
901
|
+
background: var(--color-bg-muted);
|
|
863
902
|
transition:
|
|
864
903
|
border-color 0.12s,
|
|
865
904
|
background-color 0.12s;
|
|
866
905
|
overflow: hidden;
|
|
867
906
|
}
|
|
868
907
|
.input-with-unit:hover:not(:focus-within) {
|
|
869
|
-
background: var(--color-bg-muted);
|
|
870
908
|
border-color: var(--color-border-strong);
|
|
871
909
|
}
|
|
872
910
|
.input-with-unit:focus-within {
|
|
873
|
-
background: var(--color-
|
|
874
|
-
border-color:
|
|
911
|
+
background: var(--color-surface);
|
|
912
|
+
border-color: var(--wui-primary-30);
|
|
875
913
|
}
|
|
876
914
|
.input-with-unit:has(.num-input.error) {
|
|
877
|
-
|
|
915
|
+
background: var(--color-surface);
|
|
916
|
+
border-color: color-mix(in srgb, var(--color-danger) 30%, transparent);
|
|
878
917
|
}
|
|
879
918
|
|
|
880
919
|
// Numeric inputs are plain text fields — no native spinner, no stepper. The
|
|
@@ -889,7 +928,8 @@ wui-select[compact] {
|
|
|
889
928
|
box-shadow: none;
|
|
890
929
|
}
|
|
891
930
|
.input-with-unit .unit {
|
|
892
|
-
|
|
931
|
+
// Transparent so the pill inherits the wrapper fill — one continuous surface.
|
|
932
|
+
background: transparent;
|
|
893
933
|
border: none;
|
|
894
934
|
border-left: 1px solid var(--color-border);
|
|
895
935
|
padding: 9px 14px;
|
|
@@ -1311,7 +1351,7 @@ wui-otp {
|
|
|
1311
1351
|
background-color: var(--color-primary);
|
|
1312
1352
|
border: 2px solid var(--color-surface);
|
|
1313
1353
|
border-radius: 50%;
|
|
1314
|
-
box-shadow:
|
|
1354
|
+
box-shadow: var(--wui-shadow-thumb);
|
|
1315
1355
|
cursor: pointer;
|
|
1316
1356
|
transition:
|
|
1317
1357
|
background-color 0.15s ease,
|
|
@@ -1319,7 +1359,7 @@ wui-otp {
|
|
|
1319
1359
|
border-color 0.15s ease;
|
|
1320
1360
|
}
|
|
1321
1361
|
.slider-thumb:hover:not(:disabled) {
|
|
1322
|
-
box-shadow:
|
|
1362
|
+
box-shadow: var(--wui-shadow-thumb-hover);
|
|
1323
1363
|
}
|
|
1324
1364
|
.slider-thumb:focus-visible {
|
|
1325
1365
|
outline: 2px solid var(--color-primary);
|
|
@@ -1342,6 +1382,7 @@ wui-otp {
|
|
|
1342
1382
|
white-space: nowrap;
|
|
1343
1383
|
}
|
|
1344
1384
|
|
|
1385
|
+
// Dark mode: slider shadows need light ink for visibility.
|
|
1345
1386
|
// Size variants
|
|
1346
1387
|
.slider-sm {
|
|
1347
1388
|
height: 28px;
|
package/scss/wui/_data.scss
CHANGED
|
@@ -230,6 +230,10 @@
|
|
|
230
230
|
background: var(--color-bg-muted);
|
|
231
231
|
color: var(--color-text);
|
|
232
232
|
}
|
|
233
|
+
.wui-expand-toggle:focus-visible {
|
|
234
|
+
outline: 2px solid var(--color-primary);
|
|
235
|
+
outline-offset: 2px;
|
|
236
|
+
}
|
|
233
237
|
.wui-expand-toggle .bi {
|
|
234
238
|
transition: transform 0.18s var(--wui-ease-out-quart, ease);
|
|
235
239
|
}
|
|
@@ -429,6 +433,10 @@
|
|
|
429
433
|
background: color-mix(in srgb, var(--color-primary) 10%, transparent);
|
|
430
434
|
color: var(--color-primary);
|
|
431
435
|
}
|
|
436
|
+
.tpg-arrow:focus-visible {
|
|
437
|
+
outline: 2px solid var(--color-primary);
|
|
438
|
+
outline-offset: 2px;
|
|
439
|
+
}
|
|
432
440
|
// Text-5 already reads as "unavailable" — no opacity dimming on top of it.
|
|
433
441
|
.tpg-arrow:disabled {
|
|
434
442
|
color: var(--color-border-strong);
|
|
@@ -460,6 +468,10 @@
|
|
|
460
468
|
background: color-mix(in srgb, var(--color-primary) 10%, transparent);
|
|
461
469
|
color: var(--color-primary);
|
|
462
470
|
}
|
|
471
|
+
.tpg-page:focus-visible {
|
|
472
|
+
outline: 2px solid var(--color-primary);
|
|
473
|
+
outline-offset: 2px;
|
|
474
|
+
}
|
|
463
475
|
.tpg-page.is-active {
|
|
464
476
|
background: var(--color-primary);
|
|
465
477
|
color: var(--color-primary-text);
|
|
@@ -627,6 +639,10 @@
|
|
|
627
639
|
background: color-mix(in srgb, var(--color-primary) 10%, transparent);
|
|
628
640
|
color: var(--color-primary);
|
|
629
641
|
}
|
|
642
|
+
.u-action:focus-visible {
|
|
643
|
+
outline: 2px solid var(--color-primary);
|
|
644
|
+
outline-offset: 2px;
|
|
645
|
+
}
|
|
630
646
|
.u-action:disabled {
|
|
631
647
|
opacity: 0.4;
|
|
632
648
|
cursor: not-allowed;
|
|
@@ -729,6 +745,10 @@
|
|
|
729
745
|
cursor: pointer;
|
|
730
746
|
user-select: none;
|
|
731
747
|
}
|
|
748
|
+
.table th.sortable:focus-visible {
|
|
749
|
+
outline: 2px solid var(--color-primary);
|
|
750
|
+
outline-offset: -2px;
|
|
751
|
+
}
|
|
732
752
|
.table th.sortable .sort-icon {
|
|
733
753
|
// Reserved space so the header does not jump width when the arrow appears.
|
|
734
754
|
display: inline-block;
|
package/scss/wui/_media.scss
CHANGED
|
@@ -127,7 +127,7 @@
|
|
|
127
127
|
padding: 0 4px;
|
|
128
128
|
border-radius: 999px;
|
|
129
129
|
background: var(--color-danger);
|
|
130
|
-
color:
|
|
130
|
+
color: var(--wui-bg-light);
|
|
131
131
|
font-size: 10px;
|
|
132
132
|
font-weight: 700;
|
|
133
133
|
line-height: 1;
|
|
@@ -312,6 +312,14 @@
|
|
|
312
312
|
border-color: var(--color-primary);
|
|
313
313
|
background: var(--color-primary-soft);
|
|
314
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
|
+
}
|
|
315
323
|
.attach-drop-icon {
|
|
316
324
|
flex: 0 0 auto;
|
|
317
325
|
width: 36px;
|
|
@@ -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;
|
|
@@ -58,7 +60,7 @@
|
|
|
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
|
@@ -329,46 +329,275 @@
|
|
|
329
329
|
font-weight: 500;
|
|
330
330
|
}
|
|
331
331
|
|
|
332
|
+
// ── Tooltip ──
|
|
333
|
+
// Both surfaces render Bootstrap's .tooltip / .tooltip-inner (React via react-bootstrap
|
|
334
|
+
// <Tooltip>, Razor via Bootstrap's own tooltip JS). The wrappers tag the tooltip element with
|
|
335
|
+
// `wui-tooltip` (React className, Razor data-bs-custom-class), so this design lands on App
|
|
336
|
+
// tooltips only and never on a raw Bootstrap one. Every colour flows --wui-tooltip-* →
|
|
337
|
+
// Bootstrap's own --bs-tooltip-* vars, so the arrow (which Bootstrap paints from
|
|
338
|
+
// --bs-tooltip-bg) always tracks the body. Customisation seam: override a --wui-tooltip-* var
|
|
339
|
+
// from a caller className for any colour, or add a tone-* class for a preset.
|
|
340
|
+
//
|
|
341
|
+
// The --wui-tooltip-* defaults sit at ZERO specificity (:where) so a caller's own single class
|
|
342
|
+
// (or inline style) overrides them; the tone presets are ordinary (0,3,0) rules that win over
|
|
343
|
+
// the defaults but lose to nothing a caller would reasonably write.
|
|
344
|
+
:where(.tooltip.wui-tooltip) {
|
|
345
|
+
--wui-tooltip-bg: var(--color-surface);
|
|
346
|
+
--wui-tooltip-color: var(--color-text);
|
|
347
|
+
--wui-tooltip-border: var(--color-border-light);
|
|
348
|
+
--wui-tooltip-radius: var(--radius, 10px);
|
|
349
|
+
--wui-tooltip-shadow: var(--wui-shadow-overlay);
|
|
350
|
+
// Size is these four vars — the `md` default. A size-* class swaps them; a caller can
|
|
351
|
+
// override any one from its own className for a fully custom size.
|
|
352
|
+
--wui-tooltip-max-width: 240px;
|
|
353
|
+
--wui-tooltip-font-size: 0.8125rem;
|
|
354
|
+
--wui-tooltip-padding-y: 7px;
|
|
355
|
+
--wui-tooltip-padding-x: 11px;
|
|
356
|
+
}
|
|
357
|
+
.tooltip.wui-tooltip {
|
|
358
|
+
--bs-tooltip-bg: var(--wui-tooltip-bg);
|
|
359
|
+
--bs-tooltip-color: var(--wui-tooltip-color);
|
|
360
|
+
--bs-tooltip-max-width: var(--wui-tooltip-max-width);
|
|
361
|
+
--bs-tooltip-border-radius: var(--wui-tooltip-radius);
|
|
362
|
+
--bs-tooltip-font-size: var(--wui-tooltip-font-size);
|
|
363
|
+
--bs-tooltip-padding-y: var(--wui-tooltip-padding-y);
|
|
364
|
+
--bs-tooltip-padding-x: var(--wui-tooltip-padding-x);
|
|
365
|
+
--bs-tooltip-opacity: 1;
|
|
366
|
+
}
|
|
367
|
+
.tooltip.wui-tooltip .tooltip-inner {
|
|
368
|
+
// Padding + font-size come from the --bs-tooltip-* vars above (driven by --wui-tooltip-*),
|
|
369
|
+
// so a size-* class or a custom var resizes the tip without touching this rule.
|
|
370
|
+
font-weight: 500;
|
|
371
|
+
line-height: 1.4;
|
|
372
|
+
text-align: left;
|
|
373
|
+
border: 1px solid var(--wui-tooltip-border);
|
|
374
|
+
box-shadow: var(--wui-tooltip-shadow);
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
// Size presets — swap the four size vars. `md` is the :where default (no class).
|
|
378
|
+
.tooltip.wui-tooltip.size-sm {
|
|
379
|
+
--wui-tooltip-max-width: 200px;
|
|
380
|
+
--wui-tooltip-font-size: 0.75rem;
|
|
381
|
+
--wui-tooltip-padding-y: 4px;
|
|
382
|
+
--wui-tooltip-padding-x: 8px;
|
|
383
|
+
}
|
|
384
|
+
.tooltip.wui-tooltip.size-lg {
|
|
385
|
+
--wui-tooltip-max-width: 300px;
|
|
386
|
+
--wui-tooltip-font-size: 0.9375rem;
|
|
387
|
+
--wui-tooltip-padding-y: 10px;
|
|
388
|
+
--wui-tooltip-padding-x: 14px;
|
|
389
|
+
}
|
|
390
|
+
// A hairline shadow so the light default's white arrow still reads on a white page; on a tinted
|
|
391
|
+
// tone the arrow is coloured and this just adds subtle depth.
|
|
392
|
+
.tooltip.wui-tooltip .tooltip-arrow {
|
|
393
|
+
filter: drop-shadow(0 1px 1px color-mix(in srgb, var(--color-text) 14%, transparent));
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
// Tone presets — swap the three colour vars; the arrow follows --bs-tooltip-bg automatically.
|
|
397
|
+
// Fills that carry white text use the AA-cleared -strong tokens; warning stays dark-on-yellow.
|
|
398
|
+
.tooltip.wui-tooltip.tone-dark {
|
|
399
|
+
--wui-tooltip-bg: var(--color-text);
|
|
400
|
+
--wui-tooltip-color: #fff;
|
|
401
|
+
--wui-tooltip-border: transparent;
|
|
402
|
+
}
|
|
403
|
+
.tooltip.wui-tooltip.tone-primary {
|
|
404
|
+
--wui-tooltip-bg: var(--color-primary);
|
|
405
|
+
--wui-tooltip-color: var(--color-primary-text);
|
|
406
|
+
--wui-tooltip-border: transparent;
|
|
407
|
+
}
|
|
408
|
+
.tooltip.wui-tooltip.tone-success {
|
|
409
|
+
--wui-tooltip-bg: var(--wui-success-strong);
|
|
410
|
+
--wui-tooltip-color: #fff;
|
|
411
|
+
--wui-tooltip-border: transparent;
|
|
412
|
+
}
|
|
413
|
+
.tooltip.wui-tooltip.tone-danger {
|
|
414
|
+
--wui-tooltip-bg: var(--wui-danger-strong);
|
|
415
|
+
--wui-tooltip-color: #fff;
|
|
416
|
+
--wui-tooltip-border: transparent;
|
|
417
|
+
}
|
|
418
|
+
.tooltip.wui-tooltip.tone-warning {
|
|
419
|
+
--wui-tooltip-bg: var(--color-warning);
|
|
420
|
+
--wui-tooltip-color: var(--color-text);
|
|
421
|
+
--wui-tooltip-border: transparent;
|
|
422
|
+
}
|
|
423
|
+
|
|
332
424
|
// ── Toast (notifications) ──
|
|
333
|
-
//
|
|
334
|
-
//
|
|
425
|
+
// Surface-card toasts shared by React <AppToast/> and Razor <app-toast>/WUI.toast: an elevated
|
|
426
|
+
// card with a left accent bar + variant icon, an auto-dismiss progress bar (pauses on hover/focus),
|
|
427
|
+
// and swipe-to-dismiss. The variant tint drives one custom prop so every part stays in step.
|
|
335
428
|
|
|
336
|
-
|
|
337
|
-
|
|
429
|
+
.wui-toast-container {
|
|
430
|
+
z-index: var(--wui-z-toast);
|
|
338
431
|
display: flex;
|
|
339
|
-
|
|
340
|
-
|
|
432
|
+
flex-direction: column;
|
|
433
|
+
gap: 10px;
|
|
434
|
+
max-width: min(92vw, 400px);
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
.wui-toast {
|
|
438
|
+
--wui-toast-accent: var(--color-primary);
|
|
439
|
+
position: relative;
|
|
440
|
+
display: flex;
|
|
441
|
+
align-items: flex-start;
|
|
341
442
|
gap: 12px;
|
|
342
|
-
|
|
343
|
-
|
|
443
|
+
width: 100%;
|
|
444
|
+
padding: 14px 14px 14px 18px;
|
|
445
|
+
background: var(--color-surface);
|
|
446
|
+
color: var(--color-text);
|
|
447
|
+
border: 1px solid var(--color-border-light);
|
|
448
|
+
border-radius: var(--radius-lg, 12px);
|
|
449
|
+
box-shadow: var(--wui-shadow-overlay);
|
|
450
|
+
overflow: hidden;
|
|
451
|
+
touch-action: pan-y;
|
|
452
|
+
user-select: none;
|
|
453
|
+
transition:
|
|
454
|
+
transform 0.2s ease,
|
|
455
|
+
opacity 0.2s ease;
|
|
456
|
+
animation: wui-toast-in 0.24s ease;
|
|
344
457
|
}
|
|
345
458
|
|
|
346
|
-
|
|
347
|
-
|
|
459
|
+
// The left accent bar reads the variant tint.
|
|
460
|
+
.wui-toast::before {
|
|
461
|
+
content: "";
|
|
462
|
+
position: absolute;
|
|
463
|
+
inset: 0 auto 0 0;
|
|
464
|
+
width: 4px;
|
|
465
|
+
background: var(--wui-toast-accent);
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
// While a swipe is in progress the toast follows the pointer with no transition.
|
|
469
|
+
.wui-toast--dragging {
|
|
470
|
+
transition: none;
|
|
471
|
+
cursor: grabbing;
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
.wui-toast--success {
|
|
475
|
+
--wui-toast-accent: var(--color-success);
|
|
476
|
+
}
|
|
477
|
+
.wui-toast--info {
|
|
478
|
+
--wui-toast-accent: var(--color-info);
|
|
479
|
+
}
|
|
480
|
+
.wui-toast--warning {
|
|
481
|
+
--wui-toast-accent: var(--color-warning);
|
|
482
|
+
}
|
|
483
|
+
.wui-toast--error,
|
|
484
|
+
.wui-toast--danger {
|
|
485
|
+
--wui-toast-accent: var(--color-danger);
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
.wui-toast-icon {
|
|
489
|
+
flex: none;
|
|
490
|
+
display: inline-flex;
|
|
491
|
+
margin-top: 1px;
|
|
492
|
+
color: var(--wui-toast-accent);
|
|
493
|
+
line-height: 1;
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
.wui-toast-content {
|
|
497
|
+
flex: 1 1 auto;
|
|
498
|
+
min-width: 0;
|
|
499
|
+
display: flex;
|
|
500
|
+
flex-direction: column;
|
|
501
|
+
gap: 2px;
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
.wui-toast-title {
|
|
505
|
+
font-size: 0.9375rem;
|
|
348
506
|
font-weight: 600;
|
|
349
|
-
color:
|
|
507
|
+
color: var(--color-text);
|
|
350
508
|
}
|
|
351
509
|
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
510
|
+
.wui-toast-message {
|
|
511
|
+
font-size: 0.875rem;
|
|
512
|
+
color: var(--color-text);
|
|
513
|
+
overflow-wrap: anywhere;
|
|
514
|
+
}
|
|
515
|
+
|
|
516
|
+
// Action button — tinted with the variant accent, scoped to the toast so it never
|
|
517
|
+
// restyles an arbitrary .btn-link a rich body might contain.
|
|
518
|
+
.wui-toast-action {
|
|
519
|
+
flex: none;
|
|
520
|
+
align-self: center;
|
|
521
|
+
color: var(--wui-toast-accent);
|
|
356
522
|
text-decoration: none;
|
|
357
|
-
font-size:
|
|
358
|
-
font-weight:
|
|
523
|
+
font-size: 0.8125rem;
|
|
524
|
+
font-weight: 600;
|
|
359
525
|
padding: 2px 8px;
|
|
360
526
|
white-space: nowrap;
|
|
527
|
+
}
|
|
528
|
+
.wui-toast-action:hover {
|
|
529
|
+
color: var(--wui-toast-accent);
|
|
530
|
+
text-decoration: underline;
|
|
531
|
+
}
|
|
532
|
+
.wui-toast-action:focus-visible {
|
|
533
|
+
outline: 2px solid var(--wui-toast-accent);
|
|
534
|
+
outline-offset: 2px;
|
|
535
|
+
}
|
|
536
|
+
|
|
537
|
+
.wui-toast-close {
|
|
361
538
|
flex: none;
|
|
362
|
-
|
|
363
|
-
|
|
539
|
+
margin-top: 1px;
|
|
540
|
+
font-size: 0.65rem;
|
|
364
541
|
}
|
|
365
542
|
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
543
|
+
// A pure-CSS auto-dismiss countdown: the bar shrinks over the toast's duration; the JS side
|
|
544
|
+
// listens for animationend to remove it. Because :hover/:focus-within pause the animation,
|
|
545
|
+
// animationend cannot fire while the toast is being read — a self-synchronising timer.
|
|
546
|
+
.wui-toast-progress {
|
|
547
|
+
position: absolute;
|
|
548
|
+
left: 0;
|
|
549
|
+
bottom: 0;
|
|
550
|
+
height: 3px;
|
|
551
|
+
width: 100%;
|
|
552
|
+
transform-origin: left center;
|
|
553
|
+
background: var(--wui-toast-accent);
|
|
554
|
+
opacity: 0.5;
|
|
555
|
+
animation-name: wui-toast-countdown;
|
|
556
|
+
animation-timing-function: linear;
|
|
557
|
+
animation-fill-mode: forwards;
|
|
558
|
+
}
|
|
559
|
+
.wui-toast:hover .wui-toast-progress,
|
|
560
|
+
.wui-toast:focus-within .wui-toast-progress {
|
|
561
|
+
animation-play-state: paused;
|
|
369
562
|
}
|
|
370
563
|
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
564
|
+
// "+N more" chip when the stack exceeds maxVisible.
|
|
565
|
+
.wui-toast-more {
|
|
566
|
+
align-self: center;
|
|
567
|
+
font-size: 0.75rem;
|
|
568
|
+
font-weight: 600;
|
|
569
|
+
color: var(--color-text-subtle);
|
|
570
|
+
background: var(--color-surface);
|
|
571
|
+
border: 1px solid var(--color-border-light);
|
|
572
|
+
border-radius: 999px;
|
|
573
|
+
padding: 2px 12px;
|
|
574
|
+
box-shadow: var(--wui-shadow-overlay);
|
|
575
|
+
}
|
|
576
|
+
|
|
577
|
+
@keyframes wui-toast-countdown {
|
|
578
|
+
from {
|
|
579
|
+
transform: scaleX(1);
|
|
580
|
+
}
|
|
581
|
+
to {
|
|
582
|
+
transform: scaleX(0);
|
|
583
|
+
}
|
|
584
|
+
}
|
|
585
|
+
|
|
586
|
+
@keyframes wui-toast-in {
|
|
587
|
+
from {
|
|
588
|
+
opacity: 0;
|
|
589
|
+
transform: translateY(8px);
|
|
590
|
+
}
|
|
591
|
+
to {
|
|
592
|
+
opacity: 1;
|
|
593
|
+
transform: translateY(0);
|
|
594
|
+
}
|
|
595
|
+
}
|
|
596
|
+
|
|
597
|
+
@media (prefers-reduced-motion: reduce) {
|
|
598
|
+
// Drop the entrance slide; the countdown bar stays — it is the auto-dismiss timer, not decoration.
|
|
599
|
+
.wui-toast {
|
|
600
|
+
animation: none;
|
|
601
|
+
transition: none;
|
|
602
|
+
}
|
|
374
603
|
}
|