@webority/theme 0.8.6 → 0.8.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 +34 -2
- package/scss/_tokens.scss +17 -8
- package/scss/_wui-aliases.scss +4 -0
- package/scss/wui/_choice.scss +44 -13
- package/scss/wui/_controls.scss +97 -24
- package/scss/wui/_display.scss +68 -11
- package/scss/wui/_media.scss +66 -4
- package/scss/wui/_navigation.scss +9 -1
- package/scss/wui/_shell.scss +17 -2
- package/scss/wui/_utilities.scss +21 -1
- package/scss/wui/_workspace.scss +46 -28
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@webority/theme",
|
|
3
|
-
"version": "0.8.
|
|
3
|
+
"version": "0.8.7",
|
|
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": {
|
package/scss/_components.scss
CHANGED
|
@@ -151,8 +151,8 @@
|
|
|
151
151
|
// its own colour. (Primary would otherwise fall back to a washed-out indigo, secondary to a
|
|
152
152
|
// faint brand tint.) .btn-danger keeps its own faded-red disabled (defined above), since the
|
|
153
153
|
// reference doesn't cover it and a greyed destructive button loses its warning read.
|
|
154
|
-
.btn:disabled:not(.btn-danger),
|
|
155
|
-
.btn.disabled:not(.btn-danger) {
|
|
154
|
+
.btn:disabled:not(.btn-danger):not(.is-loading),
|
|
155
|
+
.btn.disabled:not(.btn-danger):not(.is-loading) {
|
|
156
156
|
background-color: var(--color-bg-muted);
|
|
157
157
|
border-color: var(--color-border);
|
|
158
158
|
color: var(--color-text-subtle);
|
|
@@ -160,6 +160,38 @@
|
|
|
160
160
|
cursor: not-allowed;
|
|
161
161
|
}
|
|
162
162
|
|
|
163
|
+
// Loading is BUSY, not disabled-by-choice: the button keeps its own identity as a LIGHT tint
|
|
164
|
+
// of its variant, never the neutral disabled grey above. It stays non-interactive (still
|
|
165
|
+
// `disabled` for forms/a11y) but reads as "working". Solid variants (primary/danger/warning)
|
|
166
|
+
// tint from their fill and take the fill colour as the label + spinner (spinner is
|
|
167
|
+
// currentColor). Higher specificity than the grey rule, and placed after it, so it wins.
|
|
168
|
+
.btn.is-loading:disabled,
|
|
169
|
+
.btn.is-loading.disabled {
|
|
170
|
+
background-color: color-mix(in srgb, var(--bs-btn-bg) 15%, var(--color-surface));
|
|
171
|
+
border-color: color-mix(in srgb, var(--bs-btn-bg) 15%, var(--color-surface));
|
|
172
|
+
color: var(--bs-btn-bg);
|
|
173
|
+
opacity: 1;
|
|
174
|
+
cursor: progress;
|
|
175
|
+
}
|
|
176
|
+
// Transparent-fill variants (secondary/ghost/subtle/outline-*) have no fill to tint from —
|
|
177
|
+
// key the wash and the label off the accent (text) colour so the hue still shows.
|
|
178
|
+
.btn-secondary.is-loading:disabled,
|
|
179
|
+
.btn-secondary.is-loading.disabled,
|
|
180
|
+
.btn-ghost.is-loading:disabled,
|
|
181
|
+
.btn-ghost.is-loading.disabled,
|
|
182
|
+
.btn-subtle.is-loading:disabled,
|
|
183
|
+
.btn-subtle.is-loading.disabled,
|
|
184
|
+
.btn-outline-primary.is-loading:disabled,
|
|
185
|
+
.btn-outline-primary.is-loading.disabled,
|
|
186
|
+
.btn-outline-danger.is-loading:disabled,
|
|
187
|
+
.btn-outline-danger.is-loading.disabled,
|
|
188
|
+
.btn-outline-light.is-loading:disabled,
|
|
189
|
+
.btn-outline-light.is-loading.disabled {
|
|
190
|
+
background-color: color-mix(in srgb, var(--bs-btn-color) 12%, var(--color-surface));
|
|
191
|
+
border-color: color-mix(in srgb, var(--bs-btn-color) 12%, var(--color-surface));
|
|
192
|
+
color: var(--bs-btn-color);
|
|
193
|
+
}
|
|
194
|
+
|
|
163
195
|
// ---- Component radii from tokens ------------------------------------
|
|
164
196
|
// .btn/.card/.input capture Bootstrap's compiled $*-border-radius (the library
|
|
165
197
|
// ladder) before a product loads, so a product's --wui-radius* override never
|
package/scss/_tokens.scss
CHANGED
|
@@ -37,6 +37,10 @@
|
|
|
37
37
|
--wui-bg-light: #FFFFFF;
|
|
38
38
|
--wui-bg-elevated: #F9FBFC;
|
|
39
39
|
--wui-bg-muted: #F5F6FA;
|
|
40
|
+
// Resting fill for every input-like control (text field, select, textarea, and the
|
|
41
|
+
// combobox/phone/date/number wrappers) — a touch lighter than --wui-bg-muted so fields read
|
|
42
|
+
// as their own surface. Change here to restyle every field's background at once.
|
|
43
|
+
--wui-field-bg: #F9FBFC;
|
|
40
44
|
--wui-border-light: #EBEDF3;
|
|
41
45
|
--wui-border-soft: #F4F6F8;
|
|
42
46
|
|
|
@@ -175,7 +179,13 @@
|
|
|
175
179
|
--wui-warning-10: #FFF8E6;
|
|
176
180
|
--wui-warning-5: #FEFBF2;
|
|
177
181
|
|
|
178
|
-
--wui-danger-
|
|
182
|
+
// Error TEXT red. Shares the brand hue (350°) with --wui-danger-active (#E82646) but is
|
|
183
|
+
// darkened until it clears the 4.5:1 AA floor on every surface it lands on — the tightest
|
|
184
|
+
// is the .alert-danger tint (--wui-danger-10 #FFEAEF), where this is 4.77:1 and the brand
|
|
185
|
+
// #E82646 is only 3.81:1. Do NOT swap this back to --wui-danger-active: that has been tried
|
|
186
|
+
// three times and each time the a11y gate rejected the release. The brand red stays in use
|
|
187
|
+
// for non-text danger (fills, borders, icons), which carry a 3:1 floor instead.
|
|
188
|
+
--wui-danger-text: #CF1735;
|
|
179
189
|
// Deepened danger fill for surfaces that carry WHITE text (btn-danger). The brighter
|
|
180
190
|
// --wui-danger/-active below are too light for white (3.86:1 / 4.38:1, under AA); these
|
|
181
191
|
// clear it — #C81340 → 5.80:1, hover → higher. Keep white-on-danger off the other tokens.
|
|
@@ -195,13 +205,12 @@
|
|
|
195
205
|
--wui-radius-lg: 14px;
|
|
196
206
|
--wui-radius-xl: 16px;
|
|
197
207
|
|
|
198
|
-
// Field (input/select/datepicker/…) radius — a
|
|
199
|
-
//
|
|
200
|
-
//
|
|
201
|
-
|
|
202
|
-
--wui-field-radius:
|
|
203
|
-
--wui-field-radius-
|
|
204
|
-
--wui-field-radius-lg: 21px;
|
|
208
|
+
// Field (input/select/datepicker/…) radius — a flat 8px corner across every
|
|
209
|
+
// size, for a crisp plain-rounded field (no squircle). Change here to restyle
|
|
210
|
+
// every field's corner at once.
|
|
211
|
+
--wui-field-radius: 8px;
|
|
212
|
+
--wui-field-radius-sm: 8px;
|
|
213
|
+
--wui-field-radius-lg: 8px;
|
|
205
214
|
|
|
206
215
|
// ---- Portal dark palette ----------------------------------------
|
|
207
216
|
// Marketing stays light. Portal flips [data-theme="dark"] and maps
|
package/scss/_wui-aliases.scss
CHANGED
|
@@ -11,6 +11,7 @@
|
|
|
11
11
|
--color-bg: var(--wui-bg-page);
|
|
12
12
|
--color-bg-subtle: var(--wui-bg-elevated);
|
|
13
13
|
--color-bg-muted: var(--wui-bg-muted);
|
|
14
|
+
--color-field-bg: var(--wui-field-bg);
|
|
14
15
|
--color-surface: var(--wui-bg-light);
|
|
15
16
|
|
|
16
17
|
// Borders
|
|
@@ -128,6 +129,9 @@
|
|
|
128
129
|
--color-bg: var(--wui-dark-bg-page);
|
|
129
130
|
--color-bg-subtle: var(--wui-dark-bg-elevated);
|
|
130
131
|
--color-bg-muted: var(--wui-dark-bg-muted);
|
|
132
|
+
// Dark keeps the field on the muted fill (a light #F9FBFC would blow out the dark UI); the
|
|
133
|
+
// light-mode field-bg override lives on --wui-field-bg.
|
|
134
|
+
--color-field-bg: var(--wui-dark-bg-muted);
|
|
131
135
|
--color-surface: var(--wui-dark-bg-surface);
|
|
132
136
|
|
|
133
137
|
--color-border: var(--wui-dark-border);
|
package/scss/wui/_choice.scss
CHANGED
|
@@ -345,7 +345,6 @@
|
|
|
345
345
|
height: 40px;
|
|
346
346
|
border: 1px solid var(--color-border);
|
|
347
347
|
border-radius: var(--radius);
|
|
348
|
-
corner-shape: squircle;
|
|
349
348
|
background: var(--color-surface);
|
|
350
349
|
color: var(--color-text-subtle);
|
|
351
350
|
}
|
|
@@ -353,7 +352,7 @@
|
|
|
353
352
|
position: relative;
|
|
354
353
|
}
|
|
355
354
|
.search-select-search:focus-within {
|
|
356
|
-
background: var(--color-bg
|
|
355
|
+
background: var(--color-field-bg);
|
|
357
356
|
}
|
|
358
357
|
.search-select-search:focus-within svg {
|
|
359
358
|
color: var(--color-primary);
|
|
@@ -485,7 +484,7 @@
|
|
|
485
484
|
--wui-datepicker-width: 100%;
|
|
486
485
|
}
|
|
487
486
|
.datepicker.open .datepicker-trigger {
|
|
488
|
-
background-color: var(--color-bg
|
|
487
|
+
background-color: var(--color-field-bg);
|
|
489
488
|
border-color: color-mix(in srgb, var(--color-primary) 30%, transparent);
|
|
490
489
|
box-shadow: none;
|
|
491
490
|
}
|
|
@@ -806,7 +805,7 @@ button.datepicker-month:hover {
|
|
|
806
805
|
color: var(--color-text-subtle);
|
|
807
806
|
}
|
|
808
807
|
.daterange.open .daterange-trigger {
|
|
809
|
-
background-color: var(--color-bg
|
|
808
|
+
background-color: var(--color-field-bg);
|
|
810
809
|
border-color: color-mix(in srgb, var(--color-primary) 30%, transparent);
|
|
811
810
|
box-shadow: none;
|
|
812
811
|
}
|
|
@@ -895,10 +894,10 @@ button.datepicker-month:hover {
|
|
|
895
894
|
width: 100%;
|
|
896
895
|
height: var(--wui-control-height);
|
|
897
896
|
border: 1px solid var(--color-border);
|
|
898
|
-
|
|
899
|
-
|
|
897
|
+
// Match AppInput's field-radius ladder exactly (18px md), not the generic --radius (10px).
|
|
898
|
+
border-radius: var(--wui-field-radius);
|
|
900
899
|
// Match AppInput exactly: muted resting fill that lifts to a clean surface on focus.
|
|
901
|
-
background: var(--color-bg
|
|
900
|
+
background: var(--color-field-bg);
|
|
902
901
|
// Clip the inner control to the shell's rounded shape — a full-height child was painting
|
|
903
902
|
// over the squircle border at the bottom, which read as a cut-off bottom edge. The dial
|
|
904
903
|
// dropdown is position:fixed, so it is unaffected by this.
|
|
@@ -908,7 +907,7 @@ button.datepicker-month:hover {
|
|
|
908
907
|
background-color 0.12s;
|
|
909
908
|
}
|
|
910
909
|
.app-phone-number-input .phone-input-wrap:hover:not(:focus-within) {
|
|
911
|
-
background: var(--color-bg
|
|
910
|
+
background: var(--color-field-bg);
|
|
912
911
|
border-color: var(--color-border-strong);
|
|
913
912
|
}
|
|
914
913
|
// Match AppInput's focus: clean surface fill + a soft primary border.
|
|
@@ -922,7 +921,8 @@ button.datepicker-month:hover {
|
|
|
922
921
|
}
|
|
923
922
|
.app-phone-number-input .phone-input-wrap:has(.input.error),
|
|
924
923
|
.app-phone-number-input .phone-input-wrap:has(.input.is-invalid) {
|
|
925
|
-
border
|
|
924
|
+
// Match AppInput's soft invalid border exactly (danger at 30%, mirroring primary-30 focus).
|
|
925
|
+
border-color: color-mix(in srgb, var(--color-danger) 30%, transparent);
|
|
926
926
|
}
|
|
927
927
|
// The inner control drops its own chrome — the shell carries it.
|
|
928
928
|
.app-phone-number-input .phone-input-wrap > .input,
|
|
@@ -1017,6 +1017,38 @@ button.datepicker-month:hover {
|
|
|
1017
1017
|
padding-left: 10px;
|
|
1018
1018
|
}
|
|
1019
1019
|
|
|
1020
|
+
// ── Size variants ── mirror AppInput's sm/md/lg ladder (height + radius + font).
|
|
1021
|
+
// The size class rides on the .app-phone-number-input container (the App* wrapper
|
|
1022
|
+
// owns it, and it survives <wui-phone>'s innerHTML re-renders); it scales the
|
|
1023
|
+
// shell height/radius, the tel font, the dial-code text, and the adornment pad so
|
|
1024
|
+
// the whole field steps together — md is the base block above.
|
|
1025
|
+
.app-phone-number-input.phone-sm .phone-input-wrap {
|
|
1026
|
+
height: var(--wui-control-height-sm);
|
|
1027
|
+
border-radius: var(--wui-field-radius-sm);
|
|
1028
|
+
}
|
|
1029
|
+
.app-phone-number-input.phone-sm .input {
|
|
1030
|
+
font-size: 13px;
|
|
1031
|
+
}
|
|
1032
|
+
.app-phone-number-input.phone-sm .phone-dial {
|
|
1033
|
+
font-size: 12px;
|
|
1034
|
+
}
|
|
1035
|
+
.app-phone-number-input.phone-sm .phone-country-adornment {
|
|
1036
|
+
padding-left: 10px;
|
|
1037
|
+
}
|
|
1038
|
+
.app-phone-number-input.phone-lg .phone-input-wrap {
|
|
1039
|
+
height: var(--wui-control-height-lg);
|
|
1040
|
+
border-radius: var(--wui-field-radius-lg);
|
|
1041
|
+
}
|
|
1042
|
+
.app-phone-number-input.phone-lg .input {
|
|
1043
|
+
font-size: 16px;
|
|
1044
|
+
}
|
|
1045
|
+
.app-phone-number-input.phone-lg .phone-dial {
|
|
1046
|
+
font-size: 14px;
|
|
1047
|
+
}
|
|
1048
|
+
.app-phone-number-input.phone-lg .phone-country-adornment {
|
|
1049
|
+
padding-left: 14px;
|
|
1050
|
+
}
|
|
1051
|
+
|
|
1020
1052
|
// Menu, search field and rows all share the .search-select language so the
|
|
1021
1053
|
// country picker reads as the same component family as every other picker.
|
|
1022
1054
|
// Fixed, with top/left set from the trigger rect by <wui-phone>, so an
|
|
@@ -1058,20 +1090,19 @@ button.datepicker-month:hover {
|
|
|
1058
1090
|
padding: 0 12px 0 36px;
|
|
1059
1091
|
border: 1px solid var(--color-border);
|
|
1060
1092
|
border-radius: var(--radius);
|
|
1061
|
-
corner-shape: squircle;
|
|
1062
1093
|
font-size: 14px;
|
|
1063
1094
|
color: var(--color-text);
|
|
1064
1095
|
background: var(--color-surface);
|
|
1065
1096
|
outline: none;
|
|
1066
1097
|
}
|
|
1067
1098
|
.dropdown-search-input:hover:not(:focus) {
|
|
1068
|
-
background: var(--color-bg
|
|
1099
|
+
background: var(--color-field-bg);
|
|
1069
1100
|
border-color: var(--color-border-strong);
|
|
1070
1101
|
}
|
|
1071
1102
|
// Border is the focus cue everywhere else in the system — no glow ring here.
|
|
1072
1103
|
// Soft primary, matching AppInput / the phone field shell rather than a full-strength border.
|
|
1073
1104
|
.dropdown-search-input:focus {
|
|
1074
|
-
background: var(--color-bg
|
|
1105
|
+
background: var(--color-field-bg);
|
|
1075
1106
|
border-color: color-mix(in srgb, var(--color-primary) 30%, transparent);
|
|
1076
1107
|
}
|
|
1077
1108
|
.country-list-scroll {
|
|
@@ -1209,7 +1240,7 @@ button.datepicker-month:hover {
|
|
|
1209
1240
|
--wui-timepicker-width: 100%;
|
|
1210
1241
|
}
|
|
1211
1242
|
.timepicker.open .timepicker-trigger {
|
|
1212
|
-
background-color: var(--color-bg
|
|
1243
|
+
background-color: var(--color-field-bg);
|
|
1213
1244
|
border-color: color-mix(in srgb, var(--color-primary) 30%, transparent);
|
|
1214
1245
|
box-shadow: none;
|
|
1215
1246
|
}
|
package/scss/wui/_controls.scss
CHANGED
|
@@ -256,9 +256,8 @@
|
|
|
256
256
|
height: var(--wui-control-height);
|
|
257
257
|
padding: 12px;
|
|
258
258
|
border-radius: var(--wui-field-radius);
|
|
259
|
-
corner-shape: squircle;
|
|
260
259
|
border: 1px solid var(--color-border);
|
|
261
|
-
background: var(--color-bg
|
|
260
|
+
background: var(--color-field-bg);
|
|
262
261
|
font-size: 14px;
|
|
263
262
|
line-height: 1.5;
|
|
264
263
|
color: var(--color-text);
|
|
@@ -305,7 +304,7 @@ textarea.form-control.autosize {
|
|
|
305
304
|
.input:hover:not(:focus):not(:disabled):not([readonly]):not([aria-invalid="true"]):not(.is-invalid):not(.error):not(.open),
|
|
306
305
|
.select:hover:not(:focus):not(:disabled):not([aria-invalid="true"]):not(.is-invalid):not(.error),
|
|
307
306
|
.textarea:hover:not(:focus):not(:disabled):not([readonly]):not([aria-invalid="true"]):not(.is-invalid):not(.error) {
|
|
308
|
-
background-color: var(--color-bg
|
|
307
|
+
background-color: var(--color-field-bg);
|
|
309
308
|
border-color: var(--color-border-strong);
|
|
310
309
|
}
|
|
311
310
|
.form-control:focus,
|
|
@@ -349,14 +348,41 @@ textarea.form-control.autosize {
|
|
|
349
348
|
.input::-ms-clear {
|
|
350
349
|
display: none;
|
|
351
350
|
}
|
|
351
|
+
// Autofill — kill the browser's yellow/blue autofill background so a filled field keeps its
|
|
352
|
+
// own chrome (muted at rest, clean surface on focus), same as a hand-typed value. WebKit
|
|
353
|
+
// won't let `background` win here, so paint over it with a full-cover inset box-shadow; the
|
|
354
|
+
// 9999s background transition defers the UA's own repaint so there's no fill flash. Applies to
|
|
355
|
+
// the plain field AND the phone's inner tel input (both carry `.input`).
|
|
356
|
+
.form-control:-webkit-autofill,
|
|
357
|
+
.form-control:-webkit-autofill:hover,
|
|
358
|
+
.input:-webkit-autofill,
|
|
359
|
+
.input:-webkit-autofill:hover,
|
|
360
|
+
.textarea:-webkit-autofill,
|
|
361
|
+
.textarea:-webkit-autofill:hover {
|
|
362
|
+
-webkit-box-shadow: 0 0 0 1000px var(--color-field-bg) inset;
|
|
363
|
+
box-shadow: 0 0 0 1000px var(--color-field-bg) inset;
|
|
364
|
+
-webkit-text-fill-color: var(--color-text);
|
|
365
|
+
caret-color: var(--color-text);
|
|
366
|
+
transition: background-color 9999s ease-out 0s;
|
|
367
|
+
}
|
|
368
|
+
// Focus lifts the autofilled field onto the clean surface, matching the typed-value focus state.
|
|
369
|
+
.form-control:-webkit-autofill:focus,
|
|
370
|
+
.input:-webkit-autofill:focus,
|
|
371
|
+
.textarea:-webkit-autofill:focus {
|
|
372
|
+
-webkit-box-shadow: 0 0 0 1000px var(--color-surface) inset;
|
|
373
|
+
box-shadow: 0 0 0 1000px var(--color-surface) inset;
|
|
374
|
+
-webkit-text-fill-color: var(--color-text);
|
|
375
|
+
caret-color: var(--color-text);
|
|
376
|
+
transition: background-color 9999s ease-out 0s;
|
|
377
|
+
}
|
|
352
378
|
.form-control:disabled,
|
|
353
379
|
.form-select:disabled,
|
|
354
380
|
.input:disabled,
|
|
355
381
|
.select:disabled,
|
|
356
382
|
.textarea:disabled {
|
|
357
383
|
// A touch deeper than the resting fill so a disabled field stays distinct now that
|
|
358
|
-
// the resting state is itself filled (per the reference
|
|
359
|
-
background:
|
|
384
|
+
// the resting state is itself filled (--color-border = #EBEDF3, per the reference).
|
|
385
|
+
background: var(--color-border);
|
|
360
386
|
border-color: transparent;
|
|
361
387
|
color: var(--color-text-subtle);
|
|
362
388
|
cursor: not-allowed;
|
|
@@ -365,6 +391,14 @@ textarea.form-control.autosize {
|
|
|
365
391
|
.field:has(.input:disabled) .label {
|
|
366
392
|
color: var(--color-text-subtle);
|
|
367
393
|
}
|
|
394
|
+
// A disabled field's placeholder reads a touch darker than the resting subtle grey
|
|
395
|
+
// (--color-text-muted = #4B5675) so it stays legible against the deeper disabled fill.
|
|
396
|
+
.form-control:disabled::placeholder,
|
|
397
|
+
.form-select:disabled::placeholder,
|
|
398
|
+
.input:disabled::placeholder,
|
|
399
|
+
.textarea:disabled::placeholder {
|
|
400
|
+
color: var(--color-text-muted);
|
|
401
|
+
}
|
|
368
402
|
|
|
369
403
|
// Read-only is NOT disabled — still submitted/selectable/copyable, so no
|
|
370
404
|
// not-allowed cursor. Match `[readonly]`, never `:read-only` (that also hits
|
|
@@ -684,10 +718,9 @@ textarea.form-control.autosize {
|
|
|
684
718
|
width: 100%;
|
|
685
719
|
border: 1px solid var(--color-border);
|
|
686
720
|
border-radius: var(--wui-field-radius);
|
|
687
|
-
corner-shape: squircle;
|
|
688
721
|
// Soft-filled at rest, white on focus — one shared fill for the whole control so the
|
|
689
722
|
// prefix/suffix and the input body are always the SAME colour combination as the field.
|
|
690
|
-
background: var(--color-bg
|
|
723
|
+
background: var(--color-field-bg);
|
|
691
724
|
transition:
|
|
692
725
|
border-color 0.12s ease,
|
|
693
726
|
background-color 0.12s ease;
|
|
@@ -758,7 +791,7 @@ textarea.form-control.autosize {
|
|
|
758
791
|
// text. It MUST match the field's own fill, or the mask reads as a separate patch. The
|
|
759
792
|
// resting/filled fill is --color-bg-muted (a white notch on the grey fill was the tell); it
|
|
760
793
|
// only lifts to --color-surface on focus/invalid, matched below.
|
|
761
|
-
background: var(--color-bg
|
|
794
|
+
background: var(--color-field-bg);
|
|
762
795
|
pointer-events: none;
|
|
763
796
|
transition:
|
|
764
797
|
top 0.15s var(--wui-ease-ui),
|
|
@@ -840,7 +873,10 @@ textarea.form-control.autosize {
|
|
|
840
873
|
color: var(--color-text-subtle);
|
|
841
874
|
}
|
|
842
875
|
.required {
|
|
843
|
-
//
|
|
876
|
+
// Same red as .field-error. The mark is aria-hidden so it could carry the brighter brand
|
|
877
|
+
// hue, but then the asterisk and the message it belongs to sat two lines apart in visibly
|
|
878
|
+
// different reds — which read as a bug and got "fixed" three times by repainting the
|
|
879
|
+
// message with the mark's sub-AA red. One token for both ends that.
|
|
844
880
|
color: var(--color-danger-text);
|
|
845
881
|
// The gap is CSS, not a significant space in the markup. RequiredMark used to render
|
|
846
882
|
// `{' '}*` while AppDatePicker and every Razor helper rendered a bare `*`, so the same
|
|
@@ -954,10 +990,9 @@ wui-select[compact] {
|
|
|
954
990
|
align-items: stretch;
|
|
955
991
|
border: 1px solid var(--color-border);
|
|
956
992
|
border-radius: var(--wui-field-radius);
|
|
957
|
-
corner-shape: squircle;
|
|
958
993
|
// Soft-filled at rest, white on focus — the numeric input and its unit pill share the
|
|
959
994
|
// one wrapper fill, same colour combination as every other field.
|
|
960
|
-
background: var(--color-bg
|
|
995
|
+
background: var(--color-field-bg);
|
|
961
996
|
transition:
|
|
962
997
|
border-color 0.12s,
|
|
963
998
|
background-color 0.12s;
|
|
@@ -1008,17 +1043,17 @@ wui-select[compact] {
|
|
|
1008
1043
|
display: flex;
|
|
1009
1044
|
align-items: stretch;
|
|
1010
1045
|
border: 1px solid var(--color-border);
|
|
1011
|
-
|
|
1012
|
-
|
|
1046
|
+
// Field radius ladder (matches AppInput): md 18px; sm/lg step below.
|
|
1047
|
+
border-radius: var(--wui-field-radius);
|
|
1013
1048
|
// Match AppInput exactly: muted resting fill that lifts to a clean surface on focus.
|
|
1014
|
-
background: var(--color-bg
|
|
1049
|
+
background: var(--color-field-bg);
|
|
1015
1050
|
transition:
|
|
1016
1051
|
border-color 0.12s,
|
|
1017
1052
|
background-color 0.12s;
|
|
1018
1053
|
overflow: hidden;
|
|
1019
1054
|
}
|
|
1020
1055
|
.num-wrap:hover:not(:focus-within) {
|
|
1021
|
-
background: var(--color-bg
|
|
1056
|
+
background: var(--color-field-bg);
|
|
1022
1057
|
border-color: var(--color-border-strong);
|
|
1023
1058
|
}
|
|
1024
1059
|
.num-wrap:focus-within {
|
|
@@ -1098,12 +1133,18 @@ wui-select[compact] {
|
|
|
1098
1133
|
// ── Size variants ── the inner input carries .input-sm / .input-lg (height +
|
|
1099
1134
|
// font), the wrapper carries .num-wrap-sm / .num-wrap-lg to scale the button
|
|
1100
1135
|
// cells to match. Default (md) uses the base 44px cell above.
|
|
1136
|
+
.num-wrap-sm {
|
|
1137
|
+
border-radius: var(--wui-field-radius-sm);
|
|
1138
|
+
}
|
|
1101
1139
|
.num-wrap-sm .num-btn {
|
|
1102
1140
|
width: 36px;
|
|
1103
1141
|
}
|
|
1104
1142
|
.num-wrap-sm .num-input {
|
|
1105
1143
|
font-size: 13px;
|
|
1106
1144
|
}
|
|
1145
|
+
.num-wrap-lg {
|
|
1146
|
+
border-radius: var(--wui-field-radius-lg);
|
|
1147
|
+
}
|
|
1107
1148
|
.num-wrap-lg .num-btn {
|
|
1108
1149
|
width: 52px;
|
|
1109
1150
|
}
|
|
@@ -1113,7 +1154,7 @@ wui-select[compact] {
|
|
|
1113
1154
|
|
|
1114
1155
|
// Affix (unit) in the num-wrap — a trailing pill after the input.
|
|
1115
1156
|
.num-wrap .affix {
|
|
1116
|
-
background: var(--color-bg
|
|
1157
|
+
background: var(--color-field-bg);
|
|
1117
1158
|
border: none;
|
|
1118
1159
|
border-left: 1px solid var(--color-border);
|
|
1119
1160
|
padding: 9px 14px;
|
|
@@ -1276,6 +1317,24 @@ wui-select[compact] {
|
|
|
1276
1317
|
color: var(--color-primary);
|
|
1277
1318
|
}
|
|
1278
1319
|
|
|
1320
|
+
// Size scale (`size`): padding, gap and font step together; md is the base above.
|
|
1321
|
+
.choice-chip-sm {
|
|
1322
|
+
gap: 6px;
|
|
1323
|
+
padding: 6px 11px;
|
|
1324
|
+
font-size: 13px;
|
|
1325
|
+
}
|
|
1326
|
+
.choice-chip-sm .count {
|
|
1327
|
+
font-size: 10px;
|
|
1328
|
+
}
|
|
1329
|
+
.choice-chip-lg {
|
|
1330
|
+
gap: 10px;
|
|
1331
|
+
padding: 11px 18px;
|
|
1332
|
+
font-size: 15px;
|
|
1333
|
+
}
|
|
1334
|
+
.choice-chip-lg .count {
|
|
1335
|
+
font-size: 12px;
|
|
1336
|
+
}
|
|
1337
|
+
|
|
1279
1338
|
// Form section heading — a branded accent bar rather than a full-width hairline,
|
|
1280
1339
|
// so a long form reads as designed sections instead of raw rules.
|
|
1281
1340
|
//
|
|
@@ -1352,9 +1411,11 @@ wui-otp {
|
|
|
1352
1411
|
justify-content: center;
|
|
1353
1412
|
width: 44px;
|
|
1354
1413
|
height: var(--wui-control-height);
|
|
1414
|
+
// Colour model matches AppInput exactly: muted resting fill + soft border, same
|
|
1415
|
+
// field radius. State rules below mirror AppInput's hover/focus/invalid/disabled.
|
|
1355
1416
|
border: 1px solid var(--color-border);
|
|
1356
|
-
border-radius: var(--radius);
|
|
1357
|
-
background: var(--color-
|
|
1417
|
+
border-radius: var(--wui-field-radius);
|
|
1418
|
+
background: var(--color-field-bg);
|
|
1358
1419
|
font-size: 18px;
|
|
1359
1420
|
font-weight: 600;
|
|
1360
1421
|
font-variant-numeric: tabular-nums;
|
|
@@ -1363,20 +1424,32 @@ wui-otp {
|
|
|
1363
1424
|
border-color 0.12s,
|
|
1364
1425
|
box-shadow 0.12s;
|
|
1365
1426
|
}
|
|
1427
|
+
// Hover — the whole field lifts its border to strong (AppInput hover), but not while a
|
|
1428
|
+
// slot is active (focus wins) or the field is disabled.
|
|
1429
|
+
.otp:hover:not(:focus-within):not(:has(.otp-field:disabled)) .otp-slot {
|
|
1430
|
+
border-color: var(--color-border-strong);
|
|
1431
|
+
}
|
|
1366
1432
|
// The caret cannot be seen through a transparent input, so the ACTIVE box carries the
|
|
1367
|
-
// affordance instead — derived from selectionStart
|
|
1433
|
+
// affordance instead — derived from selectionStart. Matches AppInput's focus: clean
|
|
1434
|
+
// surface fill + soft brand border, no ring.
|
|
1368
1435
|
.otp-slot.active {
|
|
1369
|
-
|
|
1370
|
-
|
|
1436
|
+
background: var(--color-surface);
|
|
1437
|
+
border-color: var(--wui-primary-30);
|
|
1438
|
+
box-shadow: none;
|
|
1371
1439
|
}
|
|
1372
1440
|
.otp-field:disabled ~ .otp-slots .otp-slot {
|
|
1373
|
-
|
|
1374
|
-
|
|
1441
|
+
// Same disabled fill as AppInput: a touch deeper than the resting muted fill.
|
|
1442
|
+
background: color-mix(in srgb, var(--color-bg-muted) 72%, var(--color-border));
|
|
1443
|
+
border-color: transparent;
|
|
1375
1444
|
color: var(--color-text-subtle);
|
|
1376
1445
|
}
|
|
1446
|
+
// Invalid — soft danger border on a clean surface, mirroring AppInput's .error (danger
|
|
1447
|
+
// at 30%, no ring), not a solid alarm box.
|
|
1377
1448
|
.field-error ~ .otp .otp-slot,
|
|
1378
1449
|
.otp.error .otp-slot {
|
|
1379
|
-
|
|
1450
|
+
background: var(--color-surface);
|
|
1451
|
+
border-color: color-mix(in srgb, var(--color-danger) 30%, transparent);
|
|
1452
|
+
box-shadow: none;
|
|
1380
1453
|
}
|
|
1381
1454
|
@media (max-width: 575.98px) {
|
|
1382
1455
|
// Six 44px boxes plus gaps overflow a 360px screen; shrink the box, never the font.
|
package/scss/wui/_display.scss
CHANGED
|
@@ -64,8 +64,8 @@
|
|
|
64
64
|
.kpi-value {
|
|
65
65
|
margin-top: 6px;
|
|
66
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
|
|
68
|
-
font-size: var(--wui-kpi-value-size,
|
|
67
|
+
// spotlight) or a custom `valueSize` (px) can drive it. Default (md) is 24px.
|
|
68
|
+
font-size: var(--wui-kpi-value-size, 24px);
|
|
69
69
|
font-weight: 700;
|
|
70
70
|
// Large numerals need negative tracking to stop reading as separate digits;
|
|
71
71
|
// tabular figures stop a live-updating value from shifting width.
|
|
@@ -153,7 +153,7 @@
|
|
|
153
153
|
// chrome; orthogonal to variant/inline/attention, so it composes with all of them.
|
|
154
154
|
.kpi.kpi-lg {
|
|
155
155
|
padding: 20px;
|
|
156
|
-
--wui-kpi-value-size:
|
|
156
|
+
--wui-kpi-value-size: 28px;
|
|
157
157
|
}
|
|
158
158
|
.kpi.kpi-lg .kpi-label {
|
|
159
159
|
font-size: 14px;
|
|
@@ -737,7 +737,8 @@ a.info-row:hover .info-value {
|
|
|
737
737
|
// ----------------------------------------------------------------------------
|
|
738
738
|
.timeline--progress {
|
|
739
739
|
// Halo widths scale with the dot: the coloured ring and the thin blank ring.
|
|
740
|
-
|
|
740
|
+
// The active/status halo is a quarter of the dot, trimmed 2px for a tighter glow.
|
|
741
|
+
--wui-timeline-halo: calc(var(--wui-timeline-dot) / 4 - 2px);
|
|
741
742
|
--wui-timeline-halo-thin: calc(var(--wui-timeline-dot) / 12);
|
|
742
743
|
// Room on the left so a status dot's soft halo ring isn't clipped.
|
|
743
744
|
padding-left: calc(var(--wui-timeline-halo) + 1px);
|
|
@@ -766,17 +767,14 @@ a.info-row:hover .info-value {
|
|
|
766
767
|
.timeline--progress .timeline-item.is-done:has(+ .timeline-item.is-done)::before {
|
|
767
768
|
background: var(--color-success);
|
|
768
769
|
}
|
|
769
|
-
// Base / upcoming dot: a hollow ring for a not-yet-reached step.
|
|
770
|
-
//
|
|
771
|
-
//
|
|
772
|
-
// bleeding through the ring's halo.
|
|
770
|
+
// Base / upcoming dot: a hollow ring for a not-yet-reached step. Just the 2px
|
|
771
|
+
// grey ring outline — no outer halo. The dot's opaque surface fill already masks
|
|
772
|
+
// the connector within its bounds, so the rail stays clean without a halo ring.
|
|
773
773
|
.timeline--progress .timeline-dot,
|
|
774
774
|
.timeline--progress .timeline-item.is-upcoming .timeline-dot {
|
|
775
775
|
color: var(--color-text-subtle);
|
|
776
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));
|
|
777
|
+
box-shadow: inset 0 0 0 2px var(--color-border);
|
|
780
778
|
}
|
|
781
779
|
// Solid status discs with a white glyph and a soft concentric halo ring in the
|
|
782
780
|
// dot's own hue. The fill is softened a touch (mixed toward the surface) so the
|
|
@@ -834,6 +832,65 @@ a.info-row:hover .info-value {
|
|
|
834
832
|
font-weight: 500;
|
|
835
833
|
}
|
|
836
834
|
|
|
835
|
+
// ----------------------------------------------------------------------------
|
|
836
|
+
// Horizontal orientation — dots in a row with the body stacked beneath each,
|
|
837
|
+
// and the rail running left-to-right. Orthogonal to variant: works for both
|
|
838
|
+
// `feed` and `progress`. A per-item connector (like the progress variant's) is
|
|
839
|
+
// used so the rail is independent of the item count.
|
|
840
|
+
// ----------------------------------------------------------------------------
|
|
841
|
+
.timeline--horizontal {
|
|
842
|
+
display: flex;
|
|
843
|
+
flex-direction: row;
|
|
844
|
+
align-items: flex-start;
|
|
845
|
+
// Room above for a status dot's halo ring; no left inset — the dots are
|
|
846
|
+
// centred in their equal-width columns, so a halo never reaches the edge.
|
|
847
|
+
padding: calc(var(--wui-timeline-halo, 6px) + 1px) 0 0;
|
|
848
|
+
gap: 0;
|
|
849
|
+
}
|
|
850
|
+
// One equal-width column per step; the dot sits on top, the body stacks beneath.
|
|
851
|
+
.timeline--horizontal .timeline-item {
|
|
852
|
+
flex: 1 1 0;
|
|
853
|
+
min-width: 0;
|
|
854
|
+
flex-direction: column;
|
|
855
|
+
align-items: center;
|
|
856
|
+
text-align: center;
|
|
857
|
+
gap: 10px;
|
|
858
|
+
padding: 0 6px;
|
|
859
|
+
}
|
|
860
|
+
// Both variants drop their vertical rail; the connector below draws the horizontal one.
|
|
861
|
+
.timeline--horizontal::before {
|
|
862
|
+
display: none;
|
|
863
|
+
}
|
|
864
|
+
// The segment from this dot across to the next — a 2px line at the dot's vertical
|
|
865
|
+
// centre, spanning from this dot's centre to the next dot's centre. z-index 0 keeps
|
|
866
|
+
// it behind the dots, whose opaque fill masks it where they overlap. Overrides the
|
|
867
|
+
// progress variant's vertical connector (same specificity, later in source).
|
|
868
|
+
.timeline--horizontal .timeline-item:not(:last-child)::before {
|
|
869
|
+
content: "";
|
|
870
|
+
position: absolute;
|
|
871
|
+
top: calc(var(--wui-timeline-dot) / 2 - 1px);
|
|
872
|
+
bottom: auto;
|
|
873
|
+
left: 50%;
|
|
874
|
+
right: -50%;
|
|
875
|
+
width: auto;
|
|
876
|
+
height: 2px;
|
|
877
|
+
background: var(--color-border);
|
|
878
|
+
z-index: 0;
|
|
879
|
+
}
|
|
880
|
+
// Centre the body and its rows beneath the dot.
|
|
881
|
+
.timeline--horizontal .timeline-body {
|
|
882
|
+
width: 100%;
|
|
883
|
+
text-align: center;
|
|
884
|
+
}
|
|
885
|
+
.timeline--horizontal .timeline-head {
|
|
886
|
+
flex-direction: column;
|
|
887
|
+
align-items: center;
|
|
888
|
+
gap: 2px;
|
|
889
|
+
}
|
|
890
|
+
.timeline--horizontal .timeline-meta {
|
|
891
|
+
justify-content: center;
|
|
892
|
+
}
|
|
893
|
+
|
|
837
894
|
// ============================================================================
|
|
838
895
|
// Feature-comparison table — us vs them. The highlighted column is tinted so
|
|
839
896
|
// the reader's eye lands on it without a legend.
|