@webority/theme 0.8.0 → 0.8.2

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@webority/theme",
3
- "version": "0.8.0",
3
+ "version": "0.8.2",
4
4
  "description": "Webority shared design system — Bootstrap 5.3 SCSS overrides, --wui-* tokens, and shared component CSS. The single source of brand chrome for React portals and Razor sites.",
5
5
  "license": "SEE LICENSE IN LICENSE",
6
6
  "publishConfig": {
@@ -170,9 +170,9 @@
170
170
  // sizes step font-size, horizontal padding and radius together (vertical size
171
171
  // comes from --wui-control-height, so a button lines up with an input). Weight
172
172
  // is 600 on every rung — buttons read as a deliberate action, not body text.
173
- // Medium (default): 15px / 600, 24px side padding, 10px radius, 44px tall.
173
+ // Medium (default): 15px / 600, 24px side padding, 22px squircle radius, 45px tall.
174
174
  .btn {
175
- --bs-btn-border-radius: var(--wui-radius);
175
+ --bs-btn-border-radius: 22px;
176
176
  --bs-btn-font-size: 0.9375rem;
177
177
  --bs-btn-font-weight: 600;
178
178
  --bs-btn-padding-x: 1.5rem;
@@ -180,10 +180,10 @@
180
180
  // keep a plain border-radius arc — not a cross-browser design contract.
181
181
  corner-shape: squircle;
182
182
  }
183
- // Small: 14px, 18px side padding, 8px radius, 35px tall.
183
+ // Small: 14px, 18px side padding, 17px squircle radius (proportional to md), 35px tall.
184
184
  .btn-sm:not(.btn-icon),
185
185
  .btn-group-sm > .btn:not(.btn-icon) {
186
- --bs-btn-border-radius: var(--wui-radius-sm);
186
+ --bs-btn-border-radius: 17px;
187
187
  --bs-btn-font-size: 0.875rem;
188
188
  --bs-btn-padding-x: 1.125rem;
189
189
  display: inline-flex;
@@ -191,10 +191,10 @@
191
191
  justify-content: center;
192
192
  min-height: 35px;
193
193
  }
194
- // Large: 17px, 32px side padding, 14px radius, 55px tall — a spacious CTA.
194
+ // Large: 17px, 32px side padding, 27px squircle radius (proportional to md), 55px tall — a spacious CTA.
195
195
  .btn-lg:not(.btn-icon),
196
196
  .btn-group-lg > .btn:not(.btn-icon) {
197
- --bs-btn-border-radius: var(--wui-radius-lg);
197
+ --bs-btn-border-radius: 27px;
198
198
  --bs-btn-font-size: 1.0625rem;
199
199
  --bs-btn-padding-x: 2rem;
200
200
  display: inline-flex;
@@ -894,7 +894,8 @@ button.datepicker-month:hover {
894
894
  border: 1px solid var(--color-border);
895
895
  border-radius: var(--radius);
896
896
  corner-shape: squircle;
897
- background: var(--color-surface);
897
+ // Match AppInput exactly: muted resting fill that lifts to a clean surface on focus.
898
+ background: var(--color-bg-muted);
898
899
  // Clip the inner control to the shell's rounded shape — a full-height child was painting
899
900
  // over the squircle border at the bottom, which read as a cut-off bottom edge. The dial
900
901
  // dropdown is position:fixed, so it is unaffected by this.
@@ -907,10 +908,10 @@ button.datepicker-month:hover {
907
908
  background: var(--color-bg-muted);
908
909
  border-color: var(--color-border-strong);
909
910
  }
910
- // Match AppInput's focus: a soft primary border, not a full-strength one.
911
+ // Match AppInput's focus: clean surface fill + a soft primary border.
911
912
  .app-phone-number-input .phone-input-wrap:focus-within {
912
- background: var(--color-bg-muted);
913
- border-color: color-mix(in srgb, var(--color-primary) 30%, transparent);
913
+ background: var(--color-surface);
914
+ border-color: var(--wui-primary-30);
914
915
  }
915
916
  .app-phone-number-input .phone-input-wrap:has(> .input:disabled) {
916
917
  background: var(--color-bg-muted);
@@ -998,6 +999,20 @@ button.datepicker-month:hover {
998
999
  font-weight: 500;
999
1000
  color: var(--color-text);
1000
1001
  }
1002
+ // Country-adornment toggles (AppPhoneNumberInput showFlag / showDialCode). Driven
1003
+ // by a host attribute so it survives <wui-phone>'s innerHTML re-renders; the flex
1004
+ // gap collapses on its own, leaving no gap where a hidden glyph sat.
1005
+ .app-phone-number-input wui-phone[hide-flag] [data-flag] {
1006
+ display: none;
1007
+ }
1008
+ .app-phone-number-input wui-phone[hide-dial] [data-dial] {
1009
+ display: none;
1010
+ }
1011
+ // Both hidden → the country toggle is just the chevron; drop its leading pad so
1012
+ // the number sits flush like a plain input's.
1013
+ .app-phone-number-input wui-phone[hide-flag][hide-dial] .phone-country-adornment {
1014
+ padding-left: 10px;
1015
+ }
1001
1016
 
1002
1017
  // Menu, search field and rows all share the .search-select language so the
1003
1018
  // country picker reads as the same component family as every other picker.
@@ -999,7 +999,8 @@ wui-select[compact] {
999
999
  border: 1px solid var(--color-border);
1000
1000
  border-radius: var(--radius-lg);
1001
1001
  corner-shape: squircle;
1002
- background: var(--color-surface);
1002
+ // Match AppInput exactly: muted resting fill that lifts to a clean surface on focus.
1003
+ background: var(--color-bg-muted);
1003
1004
  transition:
1004
1005
  border-color 0.12s,
1005
1006
  background-color 0.12s;
@@ -1010,8 +1011,8 @@ wui-select[compact] {
1010
1011
  border-color: var(--color-border-strong);
1011
1012
  }
1012
1013
  .num-wrap:focus-within {
1013
- background: var(--color-bg-muted);
1014
- border-color: color-mix(in srgb, var(--color-primary) 30%, transparent);
1014
+ background: var(--color-surface);
1015
+ border-color: var(--wui-primary-30);
1015
1016
  }
1016
1017
  .num-wrap:has(.num-input.error) {
1017
1018
  border-color: var(--color-danger);
@@ -1019,25 +1020,34 @@ wui-select[compact] {
1019
1020
 
1020
1021
  // Numeric input inside the stepper — plain text field, no native spinner.
1021
1022
  // Flows seamlessly between the stepper buttons; the wrapper carries the border.
1023
+ // The value reads as the segmented centre cell — centred + semibold like the
1024
+ // reference: minus | value | plus, three even cells divided by the button borders.
1022
1025
  .num-wrap .num-input {
1023
1026
  flex: 1;
1024
1027
  border: none;
1025
1028
  border-radius: 0;
1026
1029
  background: transparent;
1027
1030
  min-width: 0;
1031
+ text-align: center;
1032
+ font-weight: 600;
1033
+ color: var(--color-text);
1028
1034
  }
1029
1035
  .num-wrap .num-input:focus {
1030
1036
  box-shadow: none;
1031
1037
  }
1032
1038
 
1033
1039
  // Stepper buttons — decrease/increase the value by step.
1034
- // Positioned left and right of the input, disabled at min/max or when the
1035
- // control is disabled.
1040
+ // Positioned left (minus, :first-child) and right (plus, :last-child) of the
1041
+ // input; disabled at min/max or when the control is disabled. Each carries the
1042
+ // divider on its input-facing edge so the value sits in its own segment.
1036
1043
  .num-btn {
1037
1044
  display: grid;
1038
1045
  place-items: center;
1039
- width: 40px;
1040
- height: 100%;
1046
+ width: 44px;
1047
+ // Fill the field height and centre the glyph. `height: 100%` resolved to auto
1048
+ // against the wrap's content-based height and left the button icon-tall, pinned
1049
+ // to the top; align-self:stretch fills the full field so place-items centres it.
1050
+ align-self: stretch;
1041
1051
  border: none;
1042
1052
  background: transparent;
1043
1053
  padding: 0;
@@ -1064,10 +1074,32 @@ wui-select[compact] {
1064
1074
  cursor: not-allowed;
1065
1075
  opacity: 0.5;
1066
1076
  }
1067
- .num-btn:not(:first-child) {
1077
+ // Segment dividers: minus divides on its right, plus on its left — so the value
1078
+ // cell is bounded on both sides. (DOM order is minus, input, [unit], plus on both
1079
+ // surfaces, so :first-child / :last-child land on the two buttons.)
1080
+ .num-btn:first-child {
1081
+ border-right: 1px solid var(--color-border);
1082
+ }
1083
+ .num-btn:last-child {
1068
1084
  border-left: 1px solid var(--color-border);
1069
1085
  }
1070
1086
 
1087
+ // ── Size variants ── the inner input carries .input-sm / .input-lg (height +
1088
+ // font), the wrapper carries .num-wrap-sm / .num-wrap-lg to scale the button
1089
+ // cells to match. Default (md) uses the base 44px cell above.
1090
+ .num-wrap-sm .num-btn {
1091
+ width: 36px;
1092
+ }
1093
+ .num-wrap-sm .num-input {
1094
+ font-size: 13px;
1095
+ }
1096
+ .num-wrap-lg .num-btn {
1097
+ width: 52px;
1098
+ }
1099
+ .num-wrap-lg .num-input {
1100
+ font-size: 16px;
1101
+ }
1102
+
1071
1103
  // Affix (unit) in the num-wrap — a trailing pill after the input.
1072
1104
  .num-wrap .affix {
1073
1105
  background: var(--color-bg-muted);
@@ -12,7 +12,6 @@
12
12
  display: flex;
13
13
  flex-direction: column;
14
14
  padding: 18px 20px;
15
- border: 1px solid var(--color-border);
16
15
  border-radius: var(--radius-lg);
17
16
  corner-shape: squircle;
18
17
  background: var(--color-surface);
@@ -64,7 +63,9 @@
64
63
  }
65
64
  .kpi-value {
66
65
  margin-top: 6px;
67
- font-size: 28px;
66
+ // The value ("main heading") size flows from one var, so a size tier (sm/md/lg,
67
+ // spotlight) or a custom `valueSize` (px) can drive it. Default (md) is 28px.
68
+ font-size: var(--wui-kpi-value-size, 28px);
68
69
  font-weight: 700;
69
70
  // Large numerals need negative tracking to stop reading as separate digits;
70
71
  // tabular figures stop a live-updating value from shifting width.
@@ -119,52 +120,109 @@
119
120
  margin-top: 2px;
120
121
  }
121
122
 
122
- // Surface variants — plain (above) is the bordered card; these swap the fill and drop the
123
- // border so a dashboard can mix a KPI strip with the surrounding card without every tile
124
- // drawing its own outline.
123
+ // Surface variants — plain (above) is borderless on the surface fill; these just swap the
124
+ // fill so a dashboard can mix a KPI strip with the surrounding card.
125
125
  .kpi.kpi-filled {
126
- border-color: transparent;
127
126
  background: var(--color-bg-subtle);
128
127
  }
129
128
  .kpi.kpi-accent {
130
- border-color: transparent;
131
129
  background: var(--color-primary-soft);
132
130
  }
133
131
  // Needs-action state — orthogonal to variant, so it composes with plain/filled/accent
134
- // rather than replacing the surface.
132
+ // rather than replacing the surface. Draws its own outline since the base tile no longer does.
135
133
  .kpi.kpi-attention {
136
- border-color: var(--color-warning);
134
+ border: 1px solid var(--color-warning);
137
135
  background: var(--color-warning-soft);
138
136
  }
139
137
 
138
+ // Compact size — dense dashboards and tight cards. Shrinks the value + chrome.
139
+ .kpi.kpi-sm {
140
+ padding: 12px 14px;
141
+ --wui-kpi-value-size: 22px;
142
+ }
143
+ .kpi.kpi-sm .kpi-label {
144
+ font-size: 12px;
145
+ }
146
+ .kpi.kpi-sm .kpi-icon {
147
+ width: 32px;
148
+ height: 32px;
149
+ margin-bottom: 8px;
150
+ }
151
+
140
152
  // Hero size — the single headline stat above a dashboard. Enlarges the value and its
141
153
  // chrome; orthogonal to variant/inline/attention, so it composes with all of them.
142
154
  .kpi.kpi-lg {
143
155
  padding: 20px;
156
+ --wui-kpi-value-size: 40px;
144
157
  }
145
158
  .kpi.kpi-lg .kpi-label {
146
159
  font-size: 14px;
147
160
  }
148
- .kpi.kpi-lg .kpi-value {
149
- font-size: 40px;
150
- }
151
161
  .kpi.kpi-lg .kpi-icon {
152
162
  width: 44px;
153
163
  height: 44px;
154
164
  }
155
165
 
166
+ // Spotlight — an editorial feature tile: the label sits top-left with the icon chip pinned
167
+ // top-right, the value is set in the display face (a product's serif brand face when it
168
+ // overrides --wui-font-display), and an optional caption reads under the number. For the
169
+ // single showcase stat on a marketing/portal surface where the number is the hero.
170
+ .kpi.kpi-spotlight {
171
+ display: grid;
172
+ grid-template-columns: minmax(0, 1fr) auto;
173
+ column-gap: 16px;
174
+ padding: 22px 24px;
175
+ --wui-kpi-value-size: 44px;
176
+ }
177
+ .kpi.kpi-spotlight .kpi-icon {
178
+ grid-column: 2;
179
+ grid-row: 1;
180
+ align-self: start;
181
+ justify-self: end;
182
+ margin-bottom: 0;
183
+ }
184
+ .kpi.kpi-spotlight .kpi-label,
185
+ .kpi.kpi-spotlight .kpi-value,
186
+ .kpi.kpi-spotlight .kpi-caption,
187
+ .kpi.kpi-spotlight .kpi-delta {
188
+ grid-column: 1;
189
+ }
190
+ .kpi.kpi-spotlight .kpi-label {
191
+ align-self: center;
192
+ text-transform: uppercase;
193
+ letter-spacing: 0.08em;
194
+ font-weight: 600;
195
+ }
196
+ .kpi.kpi-spotlight .kpi-value {
197
+ margin-top: 14px;
198
+ // The editorial serif is this variant's signature (see the reference tile), so the value gets
199
+ // a serif stack directly — not --wui-font-display, which defaults to the body sans and so would
200
+ // never read as serif. A product that wants its own face overrides this one selector.
201
+ // Size comes from --wui-kpi-value-size (44px default, set on the spotlight root above), so a
202
+ // custom `valueSize` (px) can tame a too-large headline.
203
+ font-family: Georgia, 'Times New Roman', 'Noto Serif', serif;
204
+ line-height: 1;
205
+ font-weight: 600;
206
+ }
207
+ // Caption — the sub-line under the value ("Total sent"). Spotlight-only today, but the class
208
+ // is variant-agnostic so a plain tile can carry one too.
209
+ .kpi-caption {
210
+ margin-top: 8px;
211
+ font-size: 14px;
212
+ font-weight: 400;
213
+ color: var(--color-text-muted);
214
+ }
215
+
156
216
  // The whole tile as a link (href). Colour/underline reset so the anchor reads as the
157
217
  // card, not as text; the hover lift matches .res-card's tint-and-lift treatment.
158
218
  a.kpi {
159
219
  text-decoration: none;
160
220
  color: inherit;
161
221
  transition:
162
- border-color 0.15s ease,
163
222
  box-shadow 0.15s ease,
164
223
  transform 0.15s ease;
165
224
  }
166
225
  a.kpi:hover {
167
- border-color: var(--color-primary);
168
226
  box-shadow: var(--shadow-md);
169
227
  transform: translateY(-2px);
170
228
  }
@@ -566,6 +624,9 @@ a.info-row:hover .info-value {
566
624
  // cannot break into segments when an entry wraps to two lines.
567
625
  // ============================================================================
568
626
  .timeline {
627
+ // One knob for the whole component: the dot diameter. The rail, halo and
628
+ // glyph all derive from it, so `size` (px) scales everything in proportion.
629
+ --wui-timeline-dot: 24px;
569
630
  position: relative;
570
631
  margin: 0;
571
632
  padding: 0 0 0 6px;
@@ -575,9 +636,9 @@ a.info-row:hover .info-value {
575
636
  content: "";
576
637
  position: absolute;
577
638
  // Inset top and bottom so the rail starts and ends inside the first/last dot.
578
- top: 10px;
579
- bottom: 10px;
580
- left: 17px;
639
+ top: calc(var(--wui-timeline-dot) / 2 - 2px);
640
+ bottom: calc(var(--wui-timeline-dot) / 2 - 2px);
641
+ left: calc(6px + var(--wui-timeline-dot) / 2 - 1px);
581
642
  width: 2px;
582
643
  background: var(--color-border);
583
644
  }
@@ -594,8 +655,8 @@ a.info-row:hover .info-value {
594
655
  position: relative;
595
656
  z-index: 1;
596
657
  flex: none;
597
- width: 24px;
598
- height: 24px;
658
+ width: var(--wui-timeline-dot);
659
+ height: var(--wui-timeline-dot);
599
660
  display: inline-grid;
600
661
  place-items: center;
601
662
  border-radius: 999px;
@@ -659,6 +720,119 @@ a.info-row:hover .info-value {
659
720
  white-space: pre-wrap;
660
721
  overflow-wrap: anywhere;
661
722
  }
723
+ // Plain inline subtext under the title — a muted line (distinct from the boxed
724
+ // .timeline-note). Works in both variants; used heavily by the progress variant.
725
+ .timeline-desc {
726
+ margin-top: 2px;
727
+ font-size: 13px;
728
+ line-height: 1.45;
729
+ color: var(--color-text-muted);
730
+ overflow-wrap: anywhere;
731
+ }
732
+
733
+ // ----------------------------------------------------------------------------
734
+ // Progress variant — a status tracker (Initiated → Submitted → … → Disbursed).
735
+ // Solid status dots (done/current/rejected) or a hollow ring (upcoming); the
736
+ // rail is drawn PER SEGMENT so a completed step's outgoing line reads filled.
737
+ // ----------------------------------------------------------------------------
738
+ .timeline--progress {
739
+ // Halo widths scale with the dot: the coloured ring and the thin blank ring.
740
+ --wui-timeline-halo: calc(var(--wui-timeline-dot) / 4);
741
+ --wui-timeline-halo-thin: calc(var(--wui-timeline-dot) / 12);
742
+ // Room on the left so a status dot's soft halo ring isn't clipped.
743
+ padding-left: calc(var(--wui-timeline-halo) + 1px);
744
+ }
745
+ // No single list rail — each item owns the segment down to the next dot so it
746
+ // can be tinted independently.
747
+ .timeline--progress::before {
748
+ display: none;
749
+ }
750
+ .timeline--progress .timeline-item {
751
+ padding-bottom: 22px;
752
+ }
753
+ // The connector from this dot down into the next one — anchored to the dot's
754
+ // bottom edge and centre, so it stays aligned at any dot size.
755
+ .timeline--progress .timeline-item:not(:last-child)::before {
756
+ content: "";
757
+ position: absolute;
758
+ top: var(--wui-timeline-dot);
759
+ bottom: -2px;
760
+ left: calc(var(--wui-timeline-dot) / 2 - 1px);
761
+ width: 2px;
762
+ background: var(--color-border);
763
+ }
764
+ // The rail fills green only BETWEEN two completed steps, so a lone/last done
765
+ // step doesn't leave a green stub dangling into an incomplete step below it.
766
+ .timeline--progress .timeline-item.is-done:has(+ .timeline-item.is-done)::before {
767
+ background: var(--color-success);
768
+ }
769
+ // Base / upcoming dot: a hollow ring for a not-yet-reached step. Its 2px grey
770
+ // halo is OPAQUE (mixed toward the surface, not transparent) so it masks the
771
+ // rail behind the dot — the connector only shows in the clear gap, never
772
+ // bleeding through the ring's halo.
773
+ .timeline--progress .timeline-dot,
774
+ .timeline--progress .timeline-item.is-upcoming .timeline-dot {
775
+ color: var(--color-text-subtle);
776
+ background: var(--color-surface);
777
+ box-shadow:
778
+ inset 0 0 0 2px var(--color-border),
779
+ 0 0 0 var(--wui-timeline-halo-thin) color-mix(in srgb, var(--color-text-subtle) 12%, var(--color-surface));
780
+ }
781
+ // Solid status discs with a white glyph and a soft concentric halo ring in the
782
+ // dot's own hue. The fill is softened a touch (mixed toward the surface) so the
783
+ // states read gentler than the raw brand tones.
784
+ .timeline--progress .timeline-item.is-done .timeline-dot {
785
+ color: var(--color-on-scrim);
786
+ background: color-mix(in srgb, var(--color-success) 82%, var(--color-surface));
787
+ box-shadow: 0 0 0 var(--wui-timeline-halo) var(--color-success-soft);
788
+ }
789
+ .timeline--progress .timeline-item.is-current .timeline-dot {
790
+ color: var(--color-on-scrim);
791
+ background: color-mix(in srgb, var(--color-warning) 82%, var(--color-surface));
792
+ box-shadow: 0 0 0 var(--wui-timeline-halo) var(--color-warning-soft);
793
+ }
794
+ .timeline--progress .timeline-item.is-rejected .timeline-dot {
795
+ color: var(--color-on-scrim);
796
+ background: color-mix(in srgb, var(--color-danger) 82%, var(--color-surface));
797
+ box-shadow: 0 0 0 var(--wui-timeline-halo) var(--color-danger-soft);
798
+ }
799
+ // Blocked / waiting on an external action — informational blue.
800
+ .timeline--progress .timeline-item.is-on-hold .timeline-dot {
801
+ color: var(--color-on-scrim);
802
+ background: color-mix(in srgb, var(--color-info) 82%, var(--color-surface));
803
+ box-shadow: 0 0 0 var(--wui-timeline-halo) var(--color-info-soft);
804
+ }
805
+ // Cancelled / skipped — resolved-but-inactive outcomes read as muted slate,
806
+ // with a soft neutral halo, so they sit visually below the live states.
807
+ .timeline--progress .timeline-item.is-cancelled .timeline-dot,
808
+ .timeline--progress .timeline-item.is-skipped .timeline-dot {
809
+ color: var(--color-on-scrim);
810
+ background: color-mix(in srgb, var(--color-text-subtle) 82%, var(--color-surface));
811
+ box-shadow: 0 0 0 var(--wui-timeline-halo) color-mix(in srgb, var(--color-text-subtle) 16%, transparent);
812
+ }
813
+ // A cancelled step's title is struck through — the plan changed.
814
+ .timeline--progress .timeline-item.is-cancelled .timeline-title {
815
+ text-decoration: line-through;
816
+ color: var(--color-text-muted);
817
+ }
818
+ // Title row: title on the left, the timestamp pinned to the right.
819
+ .timeline--progress .timeline-head {
820
+ display: flex;
821
+ align-items: baseline;
822
+ justify-content: space-between;
823
+ gap: 12px;
824
+ }
825
+ .timeline--progress .timeline-head .timeline-at {
826
+ flex: none;
827
+ font-size: 12.5px;
828
+ color: var(--color-text-subtle);
829
+ white-space: nowrap;
830
+ }
831
+ // An upcoming step greys down — it hasn't happened yet.
832
+ .timeline--progress .timeline-item.is-upcoming .timeline-title {
833
+ color: var(--color-text-muted);
834
+ font-weight: 500;
835
+ }
662
836
 
663
837
  // ============================================================================
664
838
  // Feature-comparison table — us vs them. The highlighted column is tinted so
@@ -830,16 +830,24 @@ wui-wizard-step[hidden] {
830
830
  // A resting min-height so the footer doesn't jump as steps of different length
831
831
  // swap in; content taller than this simply grows the panel.
832
832
  min-height: 96px;
833
+ // A rounded squircle card around the step content. The surface (border + fill +
834
+ // padding) is what makes the radius visible — a radius on a border/background-less
835
+ // box shows nothing.
836
+ padding: 20px 24px;
837
+ border: 1px solid var(--color-border);
838
+ border-radius: 22px;
839
+ corner-shape: squircle;
840
+ background: var(--color-surface);
833
841
  }
834
842
  .wizard-footer {
835
- // The sticky Back/Skip/Next/Finish bar — pinned to the bottom of a scrolling flow
836
- // so the actions (and the Skip on an optional step) are always in reach. Runs flush on
837
- // the flow (no divider or fill) — the buttons alone read as the footer.
838
- position: sticky;
839
- bottom: 0;
843
+ // The Back/Skip/Next/Finish bar sits at the END of the wizard content — NOT
844
+ // viewport-pinned. A transparent sticky bar (the old behaviour) pinned to the
845
+ // viewport bottom collided with a page/app footer below the wizard, burying the
846
+ // buttons behind it. Inline keeps them in the flow, always clear of a page footer.
847
+ // (For a wizard inside its OWN fixed-height scroll panel, a product can opt back
848
+ // into sticky on its container.)
840
849
  margin-top: 24px;
841
850
  padding: 16px 0;
842
- z-index: 1;
843
851
  }
844
852
  .wizard-footer-actions {
845
853
  display: flex;
@@ -527,16 +527,23 @@
527
527
  border-radius: var(--radius-lg);
528
528
  corner-shape: squircle;
529
529
  border: 1px solid var(--color-border);
530
- background: var(--color-surface);
530
+ // Match AppInput: muted resting fill, lifts to a clean surface on focus.
531
+ background: var(--color-bg-muted);
531
532
  overflow: hidden;
532
533
  cursor: pointer;
533
534
  transition:
534
535
  border-color 0.12s,
535
536
  box-shadow 0.12s;
536
537
  }
537
- .file-input:hover {
538
+ .file-input:hover:not(:focus-within) {
539
+ background: var(--color-bg-muted);
538
540
  border-color: var(--color-border-strong);
539
541
  }
542
+ .file-input:focus-within {
543
+ background: var(--color-surface);
544
+ border-color: var(--wui-primary-30);
545
+ box-shadow: none;
546
+ }
540
547
  .file-input-btn {
541
548
  display: inline-flex;
542
549
  align-items: center;
@@ -579,21 +586,25 @@
579
586
  width: 100%;
580
587
  padding: 20px;
581
588
  text-align: center;
582
- background: var(--color-bg-subtle);
583
- border: 1px dashed var(--color-border-strong);
589
+ // Match AppInput: muted resting fill + resting border (dashed kept as the dropzone
590
+ // affordance), border-strong on hover, primary border + surface lift on focus/drag.
591
+ background: var(--color-bg-muted);
592
+ border: 1px dashed var(--color-border);
584
593
  transition:
585
594
  border-color 0.15s ease,
586
595
  background-color 0.15s ease;
587
596
  }
588
- // Hover, focus and drag-over all take the same soft primary-30% border AppInput shows on
589
- // focus — a clear hue shift from the resting grey dashed border, without the loud full
590
- // --color-primary the dropzone used before.
591
- .file-input.file-input-tall:hover,
597
+ // Hover mirrors AppInput hover — border-strong, fill unchanged.
598
+ .file-input.file-input-tall:hover:not(:focus-within):not(.dragging) {
599
+ border-color: var(--color-border-strong);
600
+ background: var(--color-bg-muted);
601
+ }
602
+ // Focus and drag-over mirror AppInput focus — soft primary border, lift onto a clean surface.
592
603
  .file-input.file-input-tall:focus-within,
593
604
  .file-input.file-input-tall:active,
594
605
  .file-input.file-input-tall.dragging {
595
- border-color: color-mix(in srgb, var(--color-primary) 30%, transparent);
596
- background: var(--color-bg-muted);
606
+ border-color: var(--wui-primary-30);
607
+ background: var(--color-surface);
597
608
  box-shadow: none;
598
609
  }
599
610
  // A rejected file (wrong type / too large) turns the dropzone border danger to match the
@@ -639,11 +650,11 @@
639
650
  .file-input.file-input-tall.has-files:hover,
640
651
  .file-input.file-input-tall.has-files:focus-within {
641
652
  border-color: var(--color-border-strong);
642
- background: var(--color-bg-subtle);
653
+ background: var(--color-bg-muted);
643
654
  }
644
655
  .file-input.file-input-tall.has-files.dragging {
645
- border-color: color-mix(in srgb, var(--color-primary) 30%, transparent);
646
- background: var(--color-bg-muted);
656
+ border-color: var(--wui-primary-30);
657
+ background: var(--color-surface);
647
658
  }
648
659
  // Width presets — put a .file-w-* class on the field (React className / Razor class) to cap the
649
660
  // dropzone to a fixed width instead of the full column. Mirror of the datepicker .date-w-* set.
@@ -15,8 +15,15 @@
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: var(--color-scrim);
18
+ // Frost the page behind the dialog — the blur is the star, so the dim is kept
19
+ // SOFT (a heavy scrim would flatten the blur into a muddy grey). A lighter dim
20
+ // over a strong blur reads as clean frosted glass; the modal is still clearly
21
+ // the one live surface. The dialog sits in `.modal` (a higher z-index sibling),
22
+ // so it is never blurred.
23
+ background: color-mix(in srgb, var(--color-scrim) 55%, transparent);
19
24
  z-index: var(--wui-z-modal-backdrop);
25
+ backdrop-filter: blur(12px);
26
+ -webkit-backdrop-filter: blur(12px);
20
27
  }
21
28
  .modal.wui-modal-shell {
22
29
  --bs-modal-bg: transparent;
@@ -68,8 +68,17 @@
68
68
  }
69
69
  .chip-ic {
70
70
  display: inline-flex;
71
+ align-items: center;
72
+ justify-content: center;
73
+ line-height: 0;
71
74
  color: var(--color-text-subtle);
72
75
  }
76
+ // Bootstrap Icons carry a -.125em baseline nudge meant for inline-with-text use;
77
+ // inside the chip's flex row it just drops the glyph below the label's centre.
78
+ // Reset it so a library-icon leading glyph lines up with the label.
79
+ .chip-ic .bi::before {
80
+ vertical-align: middle;
81
+ }
73
82
  .chip-avatar {
74
83
  display: inline-flex;
75
84
  width: 18px;