@webority/theme 0.13.5 → 0.15.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +50 -6
- package/dist/purge-safelist.json +824 -0
- package/dist/webority-flags.css +1 -0
- package/dist/webority-theme.css +1 -1
- package/index.scss +3 -3
- package/package.json +4 -2
- package/scss/_components.scss +6 -6
- package/scss/_tokens.scss +6 -0
- package/scss/_wui-aliases.scss +36 -0
- package/scss/_wui-components.scss +6 -14
- package/scss/wui/_choice.scss +106 -27
- package/scss/wui/_command-palette.scss +195 -0
- package/scss/wui/_controls.scss +26 -6
- package/scss/wui/_display.scss +73 -19
- package/scss/wui/_flow.scss +10 -3
- package/scss/wui/_media.scss +6 -4
- package/scss/wui/_overlays.scss +13 -8
- package/scss/wui/_shell.scss +8 -2
- package/scss/wui/_utilities.scss +213 -206
- package/scss/wui/_workspace.scss +1 -1
package/scss/wui/_controls.scss
CHANGED
|
@@ -348,7 +348,7 @@ textarea.form-control.autosize {
|
|
|
348
348
|
background-color: var(--color-surface);
|
|
349
349
|
color: var(--color-text);
|
|
350
350
|
// Soft brand border is the focus cue (per the reference) — no ring/shadow.
|
|
351
|
-
border-color: var(--
|
|
351
|
+
border-color: var(--color-focus-border);
|
|
352
352
|
box-shadow: none;
|
|
353
353
|
}
|
|
354
354
|
.form-control::placeholder,
|
|
@@ -625,6 +625,14 @@ $wui-field-error-glyph: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/
|
|
|
625
625
|
color: var(--color-text);
|
|
626
626
|
-webkit-text-fill-color: var(--color-text);
|
|
627
627
|
}
|
|
628
|
+
// ...but -webkit-text-fill-color is inherited by ::placeholder and beats its `color`, so the rule
|
|
629
|
+
// above painted the placeholder as bright as a typed value. Hand the placeholder its own subtle
|
|
630
|
+
// ink back (the same --color-text-subtle every other placeholder uses).
|
|
631
|
+
:root[data-theme="dark"] .form-control.error::placeholder,
|
|
632
|
+
:root[data-theme="dark"] .input.error::placeholder {
|
|
633
|
+
color: var(--color-text-subtle);
|
|
634
|
+
-webkit-text-fill-color: var(--color-text-subtle);
|
|
635
|
+
}
|
|
628
636
|
|
|
629
637
|
// Field-relative wrap for trailing affixes (error icon, password toggle).
|
|
630
638
|
.input-wrap {
|
|
@@ -793,9 +801,10 @@ $wui-field-error-glyph: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/
|
|
|
793
801
|
}
|
|
794
802
|
.input-affixed:focus-within {
|
|
795
803
|
background: var(--color-surface);
|
|
796
|
-
border-color: var(--
|
|
804
|
+
border-color: var(--color-focus-border);
|
|
797
805
|
}
|
|
798
|
-
.input-affixed:has(.input.error)
|
|
806
|
+
.input-affixed:has(.input.error),
|
|
807
|
+
.input-affixed:has(.input[aria-invalid="true"]) {
|
|
799
808
|
background: var(--color-surface);
|
|
800
809
|
border-color: color-mix(in srgb, var(--color-danger) 30%, transparent);
|
|
801
810
|
}
|
|
@@ -806,9 +815,18 @@ $wui-field-error-glyph: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/
|
|
|
806
815
|
border-radius: 0;
|
|
807
816
|
background: transparent;
|
|
808
817
|
}
|
|
818
|
+
// A select in the affixed shell (AppSelect prefix/suffix): the custom element is the flex
|
|
819
|
+
// item, and its inner .input control already loses its own border through the rule above.
|
|
820
|
+
.input-affixed > wui-select {
|
|
821
|
+
flex: 1;
|
|
822
|
+
min-width: 0;
|
|
823
|
+
}
|
|
809
824
|
.input-affixed .affix {
|
|
810
825
|
display: flex;
|
|
811
826
|
align-items: center;
|
|
827
|
+
// A one-character affix (₹, $, #) still reads as a box, centred in it, not a sliver.
|
|
828
|
+
justify-content: center;
|
|
829
|
+
min-width: 44px;
|
|
812
830
|
padding: 0 12px;
|
|
813
831
|
// Transparent so the affix inherits the wrapper fill — the addon and the input read as
|
|
814
832
|
// one continuous surface (only the separator border divides them).
|
|
@@ -993,6 +1011,7 @@ $wui-field-error-glyph: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/
|
|
|
993
1011
|
// into a plain div, a grid cell, a table cell — it would still collapse. Being a flex
|
|
994
1012
|
// item blockifies it; nothing else does.
|
|
995
1013
|
wui-select,
|
|
1014
|
+
wui-country-select,
|
|
996
1015
|
wui-autocomplete,
|
|
997
1016
|
wui-multiselect,
|
|
998
1017
|
wui-taginput,
|
|
@@ -1010,6 +1029,7 @@ wui-select[compact] {
|
|
|
1010
1029
|
width: auto;
|
|
1011
1030
|
}
|
|
1012
1031
|
.field > wui-select,
|
|
1032
|
+
.field > wui-country-select,
|
|
1013
1033
|
.field > wui-autocomplete,
|
|
1014
1034
|
.field > wui-multiselect,
|
|
1015
1035
|
.field > wui-taginput,
|
|
@@ -1069,7 +1089,7 @@ wui-select[compact] {
|
|
|
1069
1089
|
}
|
|
1070
1090
|
.input-with-unit:focus-within {
|
|
1071
1091
|
background: var(--color-surface);
|
|
1072
|
-
border-color: var(--
|
|
1092
|
+
border-color: var(--color-focus-border);
|
|
1073
1093
|
}
|
|
1074
1094
|
.input-with-unit:has(.num-input.error) {
|
|
1075
1095
|
background: var(--color-surface);
|
|
@@ -1142,7 +1162,7 @@ wui-select[compact] {
|
|
|
1142
1162
|
}
|
|
1143
1163
|
.num-wrap:focus-within {
|
|
1144
1164
|
background: var(--color-surface);
|
|
1145
|
-
border-color: var(--
|
|
1165
|
+
border-color: var(--color-focus-border);
|
|
1146
1166
|
}
|
|
1147
1167
|
.num-wrap:has(.num-input.error),
|
|
1148
1168
|
.num-wrap:has(.num-input.is-invalid),
|
|
@@ -1512,7 +1532,7 @@ wui-otp {
|
|
|
1512
1532
|
// surface fill + soft brand border, no ring.
|
|
1513
1533
|
.otp-slot.active {
|
|
1514
1534
|
background: var(--color-surface);
|
|
1515
|
-
border-color: var(--
|
|
1535
|
+
border-color: var(--color-focus-border);
|
|
1516
1536
|
box-shadow: none;
|
|
1517
1537
|
}
|
|
1518
1538
|
.otp-field:disabled ~ .otp-slots .otp-slot {
|
package/scss/wui/_display.scss
CHANGED
|
@@ -407,15 +407,26 @@ a.info-row:hover .info-value {
|
|
|
407
407
|
// ============================================================================
|
|
408
408
|
// Segmented control — 2–4 mutually-exclusive options shown side by side.
|
|
409
409
|
// The theme toggle (light/dark/system) is the canonical use.
|
|
410
|
+
//
|
|
411
|
+
// The selected segment is an OUTLINED pill: a primary ring over a faint primary tint, with the
|
|
412
|
+
// label in primary. The tint is 8% so the label clears AA on it in both themes (light 5.6:1,
|
|
413
|
+
// dark 4.5:1); a stronger tint drops the dark label under 4.5:1.
|
|
410
414
|
// ============================================================================
|
|
415
|
+
// Each size sets one option height, icon size and note size, so a text, icon or note option in
|
|
416
|
+
// the same control lines up, and the four sizes step evenly: 28 / 32 / 38 / 44px tall overall
|
|
417
|
+
// (the track adds 8px), xl matching the 44px field height. A note option grows past the height.
|
|
411
418
|
.segmented {
|
|
419
|
+
--segmented-selected-bg: color-mix(in srgb, var(--color-primary) 8%, var(--color-surface));
|
|
420
|
+
--segmented-option-h: 24px;
|
|
421
|
+
--segmented-icon: 13px;
|
|
422
|
+
--segmented-note: 10px;
|
|
412
423
|
position: relative;
|
|
413
424
|
display: inline-flex;
|
|
414
425
|
gap: 2px;
|
|
415
426
|
padding: 3px;
|
|
416
427
|
border: 1px solid var(--color-border);
|
|
417
428
|
border-radius: 999px;
|
|
418
|
-
background: var(--color-
|
|
429
|
+
background: var(--color-surface);
|
|
419
430
|
}
|
|
420
431
|
.segmented-option {
|
|
421
432
|
position: relative;
|
|
@@ -423,7 +434,10 @@ a.info-row:hover .info-value {
|
|
|
423
434
|
display: inline-flex;
|
|
424
435
|
align-items: center;
|
|
425
436
|
gap: 5px;
|
|
426
|
-
|
|
437
|
+
min-height: var(--segmented-option-h);
|
|
438
|
+
// A small fixed vertical padding: the min-height sets the height, so an icon cannot push
|
|
439
|
+
// one option taller than its text-only siblings. Only a two-line note option grows.
|
|
440
|
+
padding: 4px 12px;
|
|
427
441
|
border: 0;
|
|
428
442
|
border-radius: 999px;
|
|
429
443
|
background: transparent;
|
|
@@ -432,7 +446,7 @@ a.info-row:hover .info-value {
|
|
|
432
446
|
font-weight: 600;
|
|
433
447
|
line-height: 1;
|
|
434
448
|
white-space: nowrap;
|
|
435
|
-
color: var(--color-text
|
|
449
|
+
color: var(--color-text);
|
|
436
450
|
cursor: pointer;
|
|
437
451
|
transition:
|
|
438
452
|
background 0.14s ease,
|
|
@@ -457,7 +471,7 @@ a.info-row:hover .info-value {
|
|
|
457
471
|
white-space: nowrap;
|
|
458
472
|
}
|
|
459
473
|
.segmented-option:hover {
|
|
460
|
-
color: var(--color-
|
|
474
|
+
color: var(--color-primary);
|
|
461
475
|
}
|
|
462
476
|
// A per-option sub-label under the main text — optional, so a segment without one keeps the
|
|
463
477
|
// plain single-line layout above untouched.
|
|
@@ -473,20 +487,20 @@ a.info-row:hover .info-value {
|
|
|
473
487
|
color: inherit;
|
|
474
488
|
}
|
|
475
489
|
.segmented-option-note {
|
|
476
|
-
font-size:
|
|
490
|
+
font-size: var(--segmented-note);
|
|
477
491
|
font-weight: 400;
|
|
478
|
-
// Inherits the option's current text colour (
|
|
492
|
+
// Inherits the option's current text colour (plain, hover, or primary-on-checked) rather
|
|
479
493
|
// than a fixed token, so it always matches without a separate state per variant. No opacity
|
|
480
494
|
// muting — that dropped the sub-label below AA on an unchecked option; the 10px size already
|
|
481
495
|
// distinguishes it from the label.
|
|
482
496
|
color: currentColor;
|
|
483
497
|
}
|
|
498
|
+
// Without JS this per-option pill stands in for the sliding thumb below, so it carries the
|
|
499
|
+
// same ring and tint.
|
|
484
500
|
.segmented-option:has(.segmented-input:checked) {
|
|
485
|
-
background: var(--
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
// --wui-primary-text to a dark value and this follows automatically.
|
|
489
|
-
color: var(--color-primary-text);
|
|
501
|
+
background: var(--segmented-selected-bg);
|
|
502
|
+
box-shadow: inset 0 0 0 2px var(--color-primary);
|
|
503
|
+
color: var(--color-primary);
|
|
490
504
|
}
|
|
491
505
|
// The ring is drawn on the label because the input it belongs to has no box.
|
|
492
506
|
.segmented-option:has(.segmented-input:focus-visible) {
|
|
@@ -504,21 +518,41 @@ a.info-row:hover .info-value {
|
|
|
504
518
|
width: 0;
|
|
505
519
|
z-index: 0;
|
|
506
520
|
border-radius: 999px;
|
|
507
|
-
background: var(--
|
|
508
|
-
|
|
521
|
+
background: var(--segmented-selected-bg);
|
|
522
|
+
// An inset ring, not a border: a border would add to the width JS measures and sets.
|
|
523
|
+
box-shadow: inset 0 0 0 2px var(--color-primary);
|
|
509
524
|
opacity: 0;
|
|
510
525
|
pointer-events: none;
|
|
511
526
|
}
|
|
512
527
|
.segmented.is-animated .segmented-thumb {
|
|
513
528
|
opacity: 1;
|
|
514
|
-
// A
|
|
529
|
+
// A fast ease-out that never overshoots. A spring easing slid the thumb past its target, so
|
|
530
|
+
// at the first or last segment the ring crossed the track's edge mid-slide. Clipping the
|
|
531
|
+
// track instead is not an option: it would also clip the options' focus outline.
|
|
515
532
|
transition:
|
|
516
|
-
transform 0.
|
|
517
|
-
width 0.
|
|
533
|
+
transform 0.32s cubic-bezier(0.22, 1, 0.36, 1),
|
|
534
|
+
width 0.32s cubic-bezier(0.22, 1, 0.36, 1);
|
|
518
535
|
}
|
|
519
|
-
// While the thumb draws the
|
|
536
|
+
// While the thumb draws the pill, the checked option drops its own (keeps the primary label).
|
|
520
537
|
.segmented.is-animated .segmented-option:has(.segmented-input:checked) {
|
|
521
538
|
background: transparent;
|
|
539
|
+
box-shadow: none;
|
|
540
|
+
}
|
|
541
|
+
// A two-line note option keeps a little more air inside the ring than a one-line option needs.
|
|
542
|
+
.segmented-option:has(.segmented-option-note) {
|
|
543
|
+
padding-top: 6px;
|
|
544
|
+
padding-bottom: 6px;
|
|
545
|
+
}
|
|
546
|
+
.segmented-option > .wui-icon {
|
|
547
|
+
flex: none;
|
|
548
|
+
width: var(--segmented-icon);
|
|
549
|
+
height: var(--segmented-icon);
|
|
550
|
+
}
|
|
551
|
+
// Borderless (bordered={false} / bordered="false") — the selected pill keeps its tint and primary
|
|
552
|
+
// label but drops the ring, on both the sliding thumb and the no-JS per-option pill.
|
|
553
|
+
.segmented-borderless .segmented-thumb,
|
|
554
|
+
.segmented-borderless .segmented-option:has(.segmented-input:checked) {
|
|
555
|
+
box-shadow: none;
|
|
522
556
|
}
|
|
523
557
|
// Icon pop — the selected segment's glyph springs up a touch as it becomes active.
|
|
524
558
|
.segmented-option:has(.segmented-input:checked) .wui-icon {
|
|
@@ -526,19 +560,39 @@ a.info-row:hover .info-value {
|
|
|
526
560
|
transition: transform 0.3s cubic-bezier(0.34, 1.56, 0.64, 1);
|
|
527
561
|
}
|
|
528
562
|
.segmented-sm .segmented-thumb,
|
|
529
|
-
.segmented-lg .segmented-thumb
|
|
563
|
+
.segmented-lg .segmented-thumb,
|
|
564
|
+
.segmented-xl .segmented-thumb {
|
|
530
565
|
top: 3px;
|
|
531
566
|
bottom: 3px;
|
|
532
567
|
}
|
|
568
|
+
.segmented-sm {
|
|
569
|
+
--segmented-option-h: 20px;
|
|
570
|
+
--segmented-icon: 12px;
|
|
571
|
+
}
|
|
572
|
+
.segmented-lg {
|
|
573
|
+
--segmented-option-h: 30px;
|
|
574
|
+
--segmented-icon: 15px;
|
|
575
|
+
--segmented-note: 11px;
|
|
576
|
+
}
|
|
577
|
+
.segmented-xl {
|
|
578
|
+
--segmented-option-h: 36px;
|
|
579
|
+
--segmented-icon: 17px;
|
|
580
|
+
--segmented-note: 12px;
|
|
581
|
+
}
|
|
533
582
|
.segmented-sm .segmented-option {
|
|
534
583
|
padding: 4px 9px;
|
|
535
584
|
font-size: 11px;
|
|
536
585
|
}
|
|
537
586
|
.segmented-lg .segmented-option {
|
|
538
587
|
gap: 6px;
|
|
539
|
-
padding:
|
|
588
|
+
padding: 4px 16px;
|
|
540
589
|
font-size: 13px;
|
|
541
590
|
}
|
|
591
|
+
.segmented-xl .segmented-option {
|
|
592
|
+
gap: 7px;
|
|
593
|
+
padding: 4px 20px;
|
|
594
|
+
font-size: 15px;
|
|
595
|
+
}
|
|
542
596
|
// Full width — segments share the row equally (a toggle spanning a card or toolbar).
|
|
543
597
|
.segmented-full {
|
|
544
598
|
display: flex;
|
package/scss/wui/_flow.scss
CHANGED
|
@@ -260,11 +260,18 @@ button.step:focus-visible .step-circle {
|
|
|
260
260
|
overflow: hidden;
|
|
261
261
|
text-overflow: ellipsis;
|
|
262
262
|
}
|
|
263
|
+
// Where connectors are drawn, a step takes only its content's width (and still shrinks to
|
|
264
|
+
// ellipsize its text) and the connector takes the rest, so the rail joins each step to the
|
|
265
|
+
// next. A journey bar without connectors keeps its evenly spread steps.
|
|
266
|
+
.stepper-journey:has(> .step-line) > .step {
|
|
267
|
+
flex: 0 1 auto;
|
|
268
|
+
}
|
|
263
269
|
.stepper-journey .step-line {
|
|
264
|
-
flex:
|
|
265
|
-
width:
|
|
270
|
+
flex: 1 1 32px;
|
|
271
|
+
min-width: 24px;
|
|
266
272
|
height: 2px;
|
|
267
|
-
margin: 0
|
|
273
|
+
margin: 0 16px;
|
|
274
|
+
border-radius: 999px;
|
|
268
275
|
background: var(--color-border-strong);
|
|
269
276
|
}
|
|
270
277
|
.stepper-journey .step-line.done {
|
package/scss/wui/_media.scss
CHANGED
|
@@ -552,7 +552,7 @@
|
|
|
552
552
|
}
|
|
553
553
|
.file-input:focus-within {
|
|
554
554
|
background: var(--color-surface);
|
|
555
|
-
border-color: var(--
|
|
555
|
+
border-color: var(--color-focus-border);
|
|
556
556
|
box-shadow: none;
|
|
557
557
|
}
|
|
558
558
|
.file-input-btn {
|
|
@@ -614,7 +614,7 @@
|
|
|
614
614
|
.file-input.file-input-tall:focus-within,
|
|
615
615
|
.file-input.file-input-tall:active,
|
|
616
616
|
.file-input.file-input-tall.dragging {
|
|
617
|
-
border-color: var(--
|
|
617
|
+
border-color: var(--color-focus-border);
|
|
618
618
|
background: var(--color-surface);
|
|
619
619
|
box-shadow: none;
|
|
620
620
|
}
|
|
@@ -664,7 +664,7 @@
|
|
|
664
664
|
background: var(--color-bg-muted);
|
|
665
665
|
}
|
|
666
666
|
.file-input.file-input-tall.has-files.dragging {
|
|
667
|
-
border-color: var(--
|
|
667
|
+
border-color: var(--color-focus-border);
|
|
668
668
|
background: var(--color-surface);
|
|
669
669
|
}
|
|
670
670
|
// Width presets — put a .file-w-* class on the field (React className / Razor class) to cap the
|
|
@@ -820,7 +820,9 @@
|
|
|
820
820
|
gap: 8px;
|
|
821
821
|
padding: 9px 18px;
|
|
822
822
|
background: rgba(255, 255, 255, 0.96);
|
|
823
|
-
|
|
823
|
+
// The pill stays white in every theme, so its ink is fixed too: --color-text flips light in
|
|
824
|
+
// dark mode and would vanish on the white pill.
|
|
825
|
+
color: var(--wui-text-heading);
|
|
824
826
|
font-size: 14px;
|
|
825
827
|
font-weight: 600;
|
|
826
828
|
border-radius: 999px;
|
package/scss/wui/_overlays.scss
CHANGED
|
@@ -316,18 +316,23 @@
|
|
|
316
316
|
// keeps a non-square source whole rather than cropping it.
|
|
317
317
|
.wui-confirm-icon img {
|
|
318
318
|
display: block;
|
|
319
|
-
width:
|
|
320
|
-
height:
|
|
319
|
+
width: var(--wui-confirm-icon-size, 120px);
|
|
320
|
+
height: var(--wui-confirm-icon-size, 120px);
|
|
321
321
|
object-fit: contain;
|
|
322
322
|
}
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
323
|
+
.wui-confirm-icon svg {
|
|
324
|
+
width: var(--wui-confirm-icon-size, 120px);
|
|
325
|
+
height: var(--wui-confirm-icon-size, 120px);
|
|
326
|
+
}
|
|
327
|
+
// Icon size tracks the copy: a title+message dialog keeps the full 120px badge, but a lone
|
|
328
|
+
// title (no message) is one line and gets a smaller badge. `.wui-confirm-msg` is a real
|
|
329
|
+
// element rendered only when a message exists, so `:has()` is reliable here; CSS width/height
|
|
330
|
+
// override the SVG's own width/height attributes. An explicit iconSize / icon-size sets
|
|
331
|
+
// --wui-confirm-icon-size on the badge and wins in both cases.
|
|
327
332
|
.wui-confirm-content:not(:has(.wui-confirm-msg)) .wui-confirm-icon svg,
|
|
328
333
|
.wui-confirm-content:not(:has(.wui-confirm-msg)) .wui-confirm-icon img {
|
|
329
|
-
width:
|
|
330
|
-
height:
|
|
334
|
+
width: var(--wui-confirm-icon-size, 84px);
|
|
335
|
+
height: var(--wui-confirm-icon-size, 84px);
|
|
331
336
|
}
|
|
332
337
|
.wui-confirm-title {
|
|
333
338
|
margin: 0 0 10px;
|
package/scss/wui/_shell.scss
CHANGED
|
@@ -194,8 +194,12 @@
|
|
|
194
194
|
.app-shell {
|
|
195
195
|
--app-sidebar-w: 260px;
|
|
196
196
|
--app-sidebar-rail-w: 88px;
|
|
197
|
+
// The sidebar's bottom band, from the rule above the collapse toggle to the sidebar's
|
|
198
|
+
// bottom edge, is the height of a one-line footer. The rule then continues the footer's
|
|
199
|
+
// top edge across the shell. The toggle fills what is left after the rule and two gaps.
|
|
200
|
+
--app-shell-foot-h: 39px;
|
|
197
201
|
// The equal space above (below the rule) and below the collapse toggle.
|
|
198
|
-
--app-sidebar-toggle-gap:
|
|
202
|
+
--app-sidebar-toggle-gap: 4px;
|
|
199
203
|
|
|
200
204
|
position: relative;
|
|
201
205
|
display: grid;
|
|
@@ -552,7 +556,9 @@
|
|
|
552
556
|
justify-content: center;
|
|
553
557
|
width: 100%;
|
|
554
558
|
position: relative;
|
|
555
|
-
|
|
559
|
+
// The band height less the rule and the gaps above and below the toggle, so rule + toggle
|
|
560
|
+
// + bottom inset add up to exactly --app-shell-foot-h.
|
|
561
|
+
height: calc(var(--app-shell-foot-h) - 1px - 2 * var(--app-sidebar-toggle-gap));
|
|
556
562
|
// 8px above the rule, the 1px rule, then the same gap below it as under the row.
|
|
557
563
|
margin-top: calc(9px + var(--app-sidebar-toggle-gap));
|
|
558
564
|
padding: 0;
|