@webority/theme 0.7.9 → 0.8.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.
@@ -430,10 +430,16 @@
430
430
  z-index: var(--wui-z-toast);
431
431
  display: flex;
432
432
  flex-direction: column;
433
- gap: 10px;
433
+ gap: 8px;
434
434
  max-width: min(92vw, 400px);
435
435
  }
436
436
 
437
+ // Bootstrap's .toast-container stacks children with margin-bottom: $toast-spacing (1.5rem),
438
+ // which ADDED to the flex gap above — the stack spacing is the gap alone.
439
+ .wui-toast-container > :not(:last-child) {
440
+ margin-bottom: 0;
441
+ }
442
+
437
443
  .wui-toast {
438
444
  --wui-toast-accent: var(--color-primary);
439
445
  position: relative;
@@ -441,10 +447,12 @@
441
447
  align-items: flex-start;
442
448
  gap: 12px;
443
449
  width: 100%;
444
- padding: 14px 14px 14px 18px;
450
+ padding: 14px 16px;
445
451
  background: var(--color-surface);
446
452
  color: var(--color-text);
447
- border: 1px solid var(--color-border-light);
453
+ // The full-strength border, not border-light: on a white page the surface-on-white
454
+ // card otherwise merges into the background (the overlay shadow alone is too soft).
455
+ border: 1px solid var(--color-border);
448
456
  border-radius: var(--radius-lg, 12px);
449
457
  box-shadow: var(--wui-shadow-overlay);
450
458
  overflow: hidden;
@@ -456,15 +464,6 @@
456
464
  animation: wui-toast-in 0.24s ease;
457
465
  }
458
466
 
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
467
  // While a swipe is in progress the toast follows the pointer with no transition.
469
468
  .wui-toast--dragging {
470
469
  transition: none;
@@ -513,21 +512,81 @@
513
512
  overflow-wrap: anywhere;
514
513
  }
515
514
 
515
+ // Hierarchy: with a title present the message reads as the SUBTITLE and drops to muted —
516
+ // title (ink, 600) > message (muted) > action (compact ink, hover reveals the variant tint).
517
+ // A title-less toast keeps full ink, or a bare "Saved" would look washed out.
518
+ .wui-toast-title + .wui-toast-message {
519
+ color: var(--color-text-muted);
520
+ }
521
+
522
+ // Determinate task progress (toast `progress` 0–100) — work advancing, not time running out.
523
+ .wui-toast-task {
524
+ display: flex;
525
+ align-items: center;
526
+ gap: 8px;
527
+ margin-top: 7px;
528
+ }
529
+ .wui-toast-track {
530
+ flex: 1 1 auto;
531
+ height: 6px;
532
+ border-radius: 999px;
533
+ background: var(--color-bg-muted);
534
+ overflow: hidden;
535
+ }
536
+ .wui-toast-track-fill {
537
+ height: 100%;
538
+ border-radius: 999px;
539
+ background: var(--wui-toast-accent);
540
+ transition: width 200ms var(--wui-ease-ui);
541
+ }
542
+ .wui-toast-track-pct {
543
+ flex: none;
544
+ font-size: 0.75rem;
545
+ font-weight: 600;
546
+ color: var(--color-text-muted);
547
+ }
548
+
516
549
  // Action button — tinted with the variant accent, scoped to the toast so it never
517
550
  // restyles an arbitrary .btn-link a rich body might contain.
551
+ // Lives in the content column, below the message/progress (never beside them).
552
+ // Deliberately NOT a .btn — Bootstrap's button chrome (padding, hover lift, transitions)
553
+ // has no business on a text-line action, so the element carries only this class and the
554
+ // styling is self-contained. Neutral bold ink like the title; underline on hover, no motion.
555
+ // The 3px/8px padding exists for the hover pill; the negative margins give it back, so the
556
+ // resting text sits exactly where the padding-less version did and the approved rhythm holds.
518
557
  .wui-toast-action {
519
558
  flex: none;
520
- align-self: center;
521
- color: var(--wui-toast-accent);
522
- text-decoration: none;
559
+ align-self: flex-start;
560
+ background: none;
561
+ border: 0;
562
+ padding: 3px 8px;
563
+ margin: -1px 0 -3px -8px;
564
+ border-radius: 6px;
565
+ cursor: pointer;
566
+ color: var(--color-text);
567
+ font-family: inherit;
523
568
  font-size: 0.8125rem;
524
569
  font-weight: 600;
525
- padding: 2px 8px;
570
+ line-height: 1.4;
571
+ text-decoration: none;
526
572
  white-space: nowrap;
573
+ transition: background-color 120ms ease, color 120ms ease;
527
574
  }
575
+ // Hover: a soft pill in the toast's variant tint — highlight, not underline, no motion.
528
576
  .wui-toast-action:hover {
577
+ background: color-mix(in srgb, var(--wui-toast-accent) 14%, transparent);
529
578
  color: var(--wui-toast-accent);
530
- text-decoration: underline;
579
+ }
580
+ .wui-toast-action:focus-visible {
581
+ outline: 2px solid var(--wui-toast-accent);
582
+ outline-offset: 1px;
583
+ }
584
+
585
+ // Geometry: optical space above the action = message half-leading (3.5px) + column gap
586
+ // (2px) + 2px margin + action half-leading (2px) ≈ 9.5px; below = action half-leading
587
+ // (2px) + this padding (7px) ≈ 9px. Equal by construction — don't re-balance one side alone.
588
+ .wui-toast:has(.wui-toast-action) {
589
+ padding-bottom: 7px;
531
590
  }
532
591
  .wui-toast-action:focus-visible {
533
592
  outline: 2px solid var(--wui-toast-accent);
@@ -540,18 +599,18 @@
540
599
  font-size: 0.65rem;
541
600
  }
542
601
 
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.
602
+ // The auto-dismiss TIMER, deliberately invisible (height 0): the element still runs the
603
+ // countdown animation because its animationend is what dismisses the toast on both surfaces,
604
+ // and :hover/:focus-within pausing it is what keeps a toast open while being read. Do not
605
+ // remove the element or the animation — only its visibility is retired (a visible countdown
606
+ // reads as pressure).
546
607
  .wui-toast-progress {
547
608
  position: absolute;
548
609
  left: 0;
549
610
  bottom: 0;
550
- height: 3px;
611
+ height: 0;
551
612
  width: 100%;
552
613
  transform-origin: left center;
553
- background: var(--wui-toast-accent);
554
- opacity: 0.5;
555
614
  animation-name: wui-toast-countdown;
556
615
  animation-timing-function: linear;
557
616
  animation-fill-mode: forwards;
@@ -594,9 +653,146 @@
594
653
  }
595
654
  }
596
655
 
656
+ // ── Entrance variants (toast/WUI.toast `animation`; host default via AppToast/app-toast) ──
657
+ // The base .wui-toast entrance above is `rise`. The variants below animate the STANDALONE
658
+ // scale/translate properties so they never fight the swipe-to-dismiss inline transform.
659
+
660
+ // `organic` — studied frame-by-frame from Ivan Bjelajac's notification: the surface
661
+ // materializes softly while the status icon grows from a dot with a slight overshoot,
662
+ // and the message trails in last.
663
+ .wui-toast--anim-organic {
664
+ animation: wui-toast-materialize 360ms var(--wui-ease-out-quart);
665
+ }
666
+ .wui-toast--anim-organic .wui-toast-icon {
667
+ animation: wui-toast-icon-grow 420ms 60ms cubic-bezier(0.34, 1.45, 0.64, 1) backwards;
668
+ }
669
+ .wui-toast--anim-organic .wui-toast-content {
670
+ animation: wui-toast-content-in 300ms 190ms var(--wui-ease-out-quart) backwards;
671
+ }
672
+
673
+ // `jelly` — drops in with a squash-and-stretch landing.
674
+ .wui-toast--anim-jelly {
675
+ animation: wui-toast-jelly 500ms cubic-bezier(0.34, 1.56, 0.64, 1);
676
+ }
677
+
678
+ // `spring` — slides up from below with an elastic settle.
679
+ .wui-toast--anim-spring {
680
+ animation: wui-toast-spring 550ms cubic-bezier(0.34, 1.56, 0.64, 1);
681
+ }
682
+
683
+ @keyframes wui-toast-materialize {
684
+ from {
685
+ opacity: 0;
686
+ scale: 0.97;
687
+ }
688
+ to {
689
+ opacity: 1;
690
+ scale: 1;
691
+ }
692
+ }
693
+ @keyframes wui-toast-icon-grow {
694
+ 0% {
695
+ scale: 0;
696
+ }
697
+ 70% {
698
+ scale: 1.18;
699
+ }
700
+ 100% {
701
+ scale: 1;
702
+ }
703
+ }
704
+ @keyframes wui-toast-content-in {
705
+ from {
706
+ opacity: 0;
707
+ translate: 6px 0;
708
+ }
709
+ to {
710
+ opacity: 1;
711
+ translate: 0 0;
712
+ }
713
+ }
714
+ @keyframes wui-toast-jelly {
715
+ 0% {
716
+ opacity: 0;
717
+ translate: 0 -46px;
718
+ scale: 0.6 0.6;
719
+ }
720
+ 55% {
721
+ opacity: 1;
722
+ translate: 0 6px;
723
+ scale: 1.05 0.85;
724
+ }
725
+ 75% {
726
+ translate: 0 -2px;
727
+ scale: 0.97 1.05;
728
+ }
729
+ 100% {
730
+ opacity: 1;
731
+ translate: 0 0;
732
+ scale: 1 1;
733
+ }
734
+ }
735
+ @keyframes wui-toast-spring {
736
+ from {
737
+ opacity: 0;
738
+ translate: 0 64px;
739
+ scale: 0.92;
740
+ }
741
+ to {
742
+ opacity: 1;
743
+ translate: 0 0;
744
+ scale: 1;
745
+ }
746
+ }
747
+
748
+ // ── Inverse surface (toast/WUI.toast `surface: 'inverse'`) ──
749
+ // The toast renders on the OPPOSITE surface of the active mode — a dark card on a light
750
+ // page, a light card on a dark page — so the stack always contrasts with the content.
751
+ // Everything inside consumes --color-* vars, so re-pointing them here restyles the whole
752
+ // card (icon, text hierarchy, action pill, progress track) in one place.
753
+ .wui-toast--surface-inverse {
754
+ --color-surface: var(--wui-dark-bg-surface);
755
+ --color-border: var(--wui-dark-border);
756
+ --color-text: var(--wui-dark-text);
757
+ --color-text-muted: var(--wui-dark-text-muted);
758
+ --color-bg-muted: var(--wui-dark-bg-muted);
759
+ }
760
+ // Bootstrap's .btn-close glyph is ink-dark; on the dark card it needs the white filter.
761
+ .wui-toast--surface-inverse .wui-toast-close {
762
+ filter: invert(1) grayscale(100%) brightness(200%);
763
+ }
764
+ :root[data-theme="dark"] .wui-toast--surface-inverse,
765
+ :root[data-bs-theme="dark"] .wui-toast--surface-inverse {
766
+ --color-surface: var(--wui-bg-light);
767
+ --color-border: var(--wui-border-light);
768
+ --color-text: var(--wui-text-heading);
769
+ --color-text-muted: var(--wui-text-2);
770
+ --color-bg-muted: var(--wui-bg-muted);
771
+ }
772
+ :root[data-theme="dark"] .wui-toast--surface-inverse .wui-toast-close,
773
+ :root[data-bs-theme="dark"] .wui-toast--surface-inverse .wui-toast-close {
774
+ filter: none;
775
+ }
776
+
777
+ // ── Corner treatments (toast/WUI.toast `shape`) — `rounded` is the base radius above ──
778
+ .wui-toast--shape-pill {
779
+ border-radius: 999px;
780
+ align-items: center;
781
+ padding-block: 12px;
782
+ }
783
+ .wui-toast--shape-pill .wui-toast-icon,
784
+ .wui-toast--shape-pill .wui-toast-close {
785
+ margin-top: 0;
786
+ }
787
+ .wui-toast--shape-sharp {
788
+ border-radius: 4px;
789
+ }
790
+
597
791
  @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 {
792
+ // Drop the entrance motion; the countdown bar stays — it is the auto-dismiss timer, not decoration.
793
+ .wui-toast,
794
+ .wui-toast--anim-organic .wui-toast-icon,
795
+ .wui-toast--anim-organic .wui-toast-content {
600
796
  animation: none;
601
797
  transition: none;
602
798
  }
@@ -0,0 +1,138 @@
1
+ // ── Toolbar ──
2
+ // AppToolbar / <app-toolbar> — a Figma-style tool strip: a pill of icon tools where
3
+ // the active tool expands to reveal its label. Radio-backed (no JS needed on Razor);
4
+ // the expand/collapse is pure CSS off :checked.
5
+ //
6
+ // Part of the wui-components layer — see _wui-components.scss for load order.
7
+
8
+ .toolbar {
9
+ display: inline-flex;
10
+ align-items: center;
11
+ gap: 4px;
12
+ padding: 6px;
13
+ background: var(--color-surface);
14
+ border: 1px solid var(--color-border);
15
+ border-radius: var(--wui-radius-lg);
16
+ corner-shape: squircle;
17
+ box-shadow: var(--wui-shadow-overlay);
18
+ }
19
+ .toolbar.is-disabled {
20
+ opacity: 0.6;
21
+ pointer-events: none;
22
+ }
23
+
24
+ .toolbar-item {
25
+ display: inline-flex;
26
+ align-items: center;
27
+ height: 36px;
28
+ padding-inline: 9px;
29
+ border-radius: var(--wui-radius);
30
+ color: var(--color-text-muted);
31
+ cursor: pointer;
32
+ margin-bottom: 0;
33
+ transition:
34
+ background-color 160ms var(--wui-ease-ui),
35
+ color 160ms var(--wui-ease-ui);
36
+ }
37
+ .toolbar-item:hover {
38
+ background: var(--color-bg-muted);
39
+ color: var(--color-text);
40
+ }
41
+ // The active tool: brand fill + the label slides open beside the icon.
42
+ .toolbar-item:has(.toolbar-input:checked) {
43
+ background: var(--color-primary);
44
+ color: var(--color-primary-text);
45
+ }
46
+ .toolbar-item:has(.toolbar-input:disabled) {
47
+ opacity: 0.5;
48
+ cursor: default;
49
+ }
50
+ .toolbar-item:has(.toolbar-input:focus-visible) {
51
+ outline: 2px solid var(--color-primary);
52
+ outline-offset: 2px;
53
+ }
54
+
55
+ // Visually hidden but focusable/announceable — same trick as .segmented-input.
56
+ .toolbar-input {
57
+ position: absolute;
58
+ width: 1px;
59
+ height: 1px;
60
+ margin: 0;
61
+ padding: 0;
62
+ border: 0;
63
+ clip-path: inset(50%);
64
+ overflow: hidden;
65
+ white-space: nowrap;
66
+ appearance: none;
67
+ opacity: 0;
68
+ }
69
+
70
+ .toolbar-item-icon {
71
+ display: grid;
72
+ place-items: center;
73
+ line-height: 1;
74
+ }
75
+
76
+ .toolbar-item-label {
77
+ max-width: 0;
78
+ margin-left: 0;
79
+ opacity: 0;
80
+ overflow: hidden;
81
+ white-space: nowrap;
82
+ font-size: 0.875rem;
83
+ font-weight: 500;
84
+ transition:
85
+ max-width 240ms var(--wui-ease-ui),
86
+ margin-left 240ms var(--wui-ease-ui),
87
+ opacity 180ms var(--wui-ease-ui);
88
+ }
89
+ .toolbar-item:has(.toolbar-input:checked) .toolbar-item-label {
90
+ max-width: 10rem;
91
+ margin-left: 7px;
92
+ opacity: 1;
93
+ }
94
+
95
+ .toolbar-sep {
96
+ width: 1px;
97
+ align-self: stretch;
98
+ margin-block: 6px;
99
+ margin-inline: 3px;
100
+ background: var(--color-border);
101
+ }
102
+
103
+ .toolbar-action {
104
+ display: inline-flex;
105
+ align-items: center;
106
+ justify-content: center;
107
+ width: 36px;
108
+ height: 36px;
109
+ padding: 0;
110
+ border: none;
111
+ border-radius: var(--wui-radius);
112
+ background: transparent;
113
+ color: var(--color-text-muted);
114
+ cursor: pointer;
115
+ transition:
116
+ background-color 160ms var(--wui-ease-ui),
117
+ color 160ms var(--wui-ease-ui);
118
+ }
119
+ .toolbar-action:hover:not(:disabled) {
120
+ background: var(--color-bg-muted);
121
+ color: var(--color-text);
122
+ }
123
+ .toolbar-action:focus-visible {
124
+ outline: 2px solid var(--color-primary);
125
+ outline-offset: 2px;
126
+ }
127
+ .toolbar-action:disabled {
128
+ opacity: 0.5;
129
+ cursor: default;
130
+ }
131
+
132
+ @media (prefers-reduced-motion: reduce) {
133
+ .toolbar-item,
134
+ .toolbar-item-label,
135
+ .toolbar-action {
136
+ transition: none;
137
+ }
138
+ }