@webority/theme 0.7.7 → 0.7.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +50 -0
- package/dist/webority-theme.css +1 -1
- package/package.json +1 -1
- package/scss/_components.scss +184 -14
- package/scss/_tokens.scss +11 -3
- package/scss/_wui-aliases.scss +35 -5
- package/scss/_wui-components.scss +3 -0
- package/scss/wui/_card.scss +2 -2
- package/scss/wui/_choice.scss +11 -4
- package/scss/wui/_controls.scss +65 -24
- package/scss/wui/_data.scss +20 -0
- package/scss/wui/_media.scss +1 -1
- package/scss/wui/_overlays.scss +254 -25
- package/scss/wui/_shell.scss +37 -3
- package/scss/wui/_workspace.scss +239 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@webority/theme",
|
|
3
|
-
"version": "0.7.
|
|
3
|
+
"version": "0.7.8",
|
|
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
|
@@ -6,14 +6,15 @@
|
|
|
6
6
|
// ---- Button polish (used by both Razor + React) ---------------------
|
|
7
7
|
// Flat buttons — no drop shadow. Active-press feedback (1px nudge) is
|
|
8
8
|
// retained because it's a tactile signal, not a depth cue.
|
|
9
|
-
//
|
|
10
|
-
//
|
|
11
|
-
//
|
|
9
|
+
// Button heights are fixed to the design reference — large 55px, medium 45px,
|
|
10
|
+
// small 35px — via min-height, with the label flex-centred. (These are button-
|
|
11
|
+
// specific and intentionally a touch taller than the shared --wui-control-height
|
|
12
|
+
// input ladder; sm/lg pin their own height in their size blocks below.)
|
|
12
13
|
.btn:not(.btn-sm):not(.btn-lg):not(.btn-pill):not(.btn-pill-sm):not(.u-action):not(.btn-icon) {
|
|
13
14
|
display: inline-flex;
|
|
14
15
|
align-items: center;
|
|
15
16
|
justify-content: center;
|
|
16
|
-
min-height:
|
|
17
|
+
min-height: 45px;
|
|
17
18
|
}
|
|
18
19
|
|
|
19
20
|
// Tactile press feedback — every variant AND every size (sm/md/lg). Kept
|
|
@@ -37,7 +38,24 @@
|
|
|
37
38
|
--bs-btn-color: var(--wui-primary-text);
|
|
38
39
|
--bs-btn-hover-color: var(--wui-primary-text);
|
|
39
40
|
--bs-btn-active-color: var(--wui-primary-text);
|
|
40
|
-
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// Secondary — a soft brand tint (matches the design reference's secondary): a
|
|
44
|
+
// low-alpha wash of the brand rather than an opaque fill, so it reads as a
|
|
45
|
+
// quieter companion to the solid primary. Colour comes from the brand token;
|
|
46
|
+
// only the TRANSPARENCY is taken from the reference — the tint deepens on hover
|
|
47
|
+
// then press. No border (the tint carries the shape). Disabled is the shared
|
|
48
|
+
// neutral-grey state below, like every other variant.
|
|
49
|
+
.btn-secondary {
|
|
50
|
+
--bs-btn-color: var(--color-primary);
|
|
51
|
+
--bs-btn-bg: color-mix(in srgb, var(--color-primary) 12%, transparent);
|
|
52
|
+
--bs-btn-border-color: transparent;
|
|
53
|
+
--bs-btn-hover-color: var(--color-primary);
|
|
54
|
+
--bs-btn-hover-bg: color-mix(in srgb, var(--color-primary) 18%, transparent);
|
|
55
|
+
--bs-btn-hover-border-color: transparent;
|
|
56
|
+
--bs-btn-active-color: var(--color-primary);
|
|
57
|
+
--bs-btn-active-bg: color-mix(in srgb, var(--color-primary) 26%, transparent);
|
|
58
|
+
--bs-btn-active-border-color: transparent;
|
|
41
59
|
}
|
|
42
60
|
|
|
43
61
|
.btn-danger {
|
|
@@ -128,11 +146,13 @@
|
|
|
128
146
|
// instead, reusing the same tokens .input:disabled already uses so a disabled button and
|
|
129
147
|
// a disabled field read as the same state.
|
|
130
148
|
//
|
|
131
|
-
//
|
|
132
|
-
//
|
|
133
|
-
//
|
|
134
|
-
.
|
|
135
|
-
.
|
|
149
|
+
// Applies to EVERY variant except .btn-danger — matching the design reference, where a
|
|
150
|
+
// disabled button of any kind reads as one neutral greyed-out state, not a faded tint of
|
|
151
|
+
// its own colour. (Primary would otherwise fall back to a washed-out indigo, secondary to a
|
|
152
|
+
// faint brand tint.) .btn-danger keeps its own faded-red disabled (defined above), since the
|
|
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) {
|
|
136
156
|
background-color: var(--color-bg-muted);
|
|
137
157
|
border-color: var(--color-border);
|
|
138
158
|
color: var(--color-text-subtle);
|
|
@@ -146,19 +166,160 @@
|
|
|
146
166
|
// reached them. Re-drive the radius from the tokens — the way the dropdown above
|
|
147
167
|
// already does — so overriding the radius tokens once flows to every control.
|
|
148
168
|
// sm/lg are re-asserted so they keep their own rung instead of the base value.
|
|
169
|
+
// Per-size type + padding scale, matched to the design reference: the three
|
|
170
|
+
// sizes step font-size, horizontal padding and radius together (vertical size
|
|
171
|
+
// comes from --wui-control-height, so a button lines up with an input). Weight
|
|
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.
|
|
149
174
|
.btn {
|
|
150
175
|
--bs-btn-border-radius: var(--wui-radius);
|
|
176
|
+
--bs-btn-font-size: 0.9375rem;
|
|
177
|
+
--bs-btn-font-weight: 600;
|
|
178
|
+
--bs-btn-padding-x: 1.5rem;
|
|
151
179
|
// Progressive enhancement only (Chrome/Safari recent). Unsupported engines
|
|
152
180
|
// keep a plain border-radius arc — not a cross-browser design contract.
|
|
153
181
|
corner-shape: squircle;
|
|
154
182
|
}
|
|
155
|
-
.
|
|
156
|
-
.btn-
|
|
183
|
+
// Small: 14px, 18px side padding, 8px radius, 35px tall.
|
|
184
|
+
.btn-sm:not(.btn-icon),
|
|
185
|
+
.btn-group-sm > .btn:not(.btn-icon) {
|
|
157
186
|
--bs-btn-border-radius: var(--wui-radius-sm);
|
|
187
|
+
--bs-btn-font-size: 0.875rem;
|
|
188
|
+
--bs-btn-padding-x: 1.125rem;
|
|
189
|
+
display: inline-flex;
|
|
190
|
+
align-items: center;
|
|
191
|
+
justify-content: center;
|
|
192
|
+
min-height: 35px;
|
|
158
193
|
}
|
|
159
|
-
.
|
|
160
|
-
.btn-
|
|
194
|
+
// Large: 17px, 32px side padding, 14px radius, 55px tall — a spacious CTA.
|
|
195
|
+
.btn-lg:not(.btn-icon),
|
|
196
|
+
.btn-group-lg > .btn:not(.btn-icon) {
|
|
161
197
|
--bs-btn-border-radius: var(--wui-radius-lg);
|
|
198
|
+
--bs-btn-font-size: 1.0625rem;
|
|
199
|
+
--bs-btn-padding-x: 2rem;
|
|
200
|
+
display: inline-flex;
|
|
201
|
+
align-items: center;
|
|
202
|
+
justify-content: center;
|
|
203
|
+
min-height: 55px;
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
// ---- Hover animation options (opt-in modifiers) ---------------------
|
|
207
|
+
// Every .btn animates on hover. The DEFAULT is the royal sheen sweep; switch a
|
|
208
|
+
// button to a different feel with a .btn-anim-* modifier, or turn motion off
|
|
209
|
+
// with .btn-anim-none. Every option reads on EVERY variant — the moving layer
|
|
210
|
+
// is tinted from the button's OWN colour (color-mix on currentColor / the
|
|
211
|
+
// resolved --bs-btn fill), so it's a pale sheen on solid fills, a brand shimmer
|
|
212
|
+
// on outline/ghost, a neutral gloss on subtle/light. All snap off under
|
|
213
|
+
// prefers-reduced-motion (below). Icon-only / link-style actions never animate.
|
|
214
|
+
//
|
|
215
|
+
// (default) royal angled sheen sweep + a restrained −1px lift
|
|
216
|
+
// .btn-anim-sheen the same sheen, named (to override a product default)
|
|
217
|
+
// .btn-anim-fill a tint wipes in left→right
|
|
218
|
+
// .btn-anim-expand a radial glow blooms from the centre
|
|
219
|
+
// .btn-anim-glow a soft colour-matched glow ring + lift
|
|
220
|
+
// .btn-anim-none no hover motion
|
|
221
|
+
//
|
|
222
|
+
// Shared plumbing — a clipped, isolated box so a z-index:-1 layer can sit above
|
|
223
|
+
// the fill but below the label; overflow clips it to the squircle corners.
|
|
224
|
+
.btn:not(.btn-icon):not(.u-action) {
|
|
225
|
+
position: relative;
|
|
226
|
+
overflow: hidden;
|
|
227
|
+
isolation: isolate;
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
// --- Sheen (default + .btn-anim-sheen): slim angled glossy band sweeps across.
|
|
231
|
+
.btn:not(.btn-icon):not(.u-action):not([class*="btn-anim-"])::before,
|
|
232
|
+
.btn-anim-sheen::before {
|
|
233
|
+
content: "";
|
|
234
|
+
position: absolute;
|
|
235
|
+
top: 0;
|
|
236
|
+
bottom: 0;
|
|
237
|
+
left: 0;
|
|
238
|
+
z-index: -1;
|
|
239
|
+
width: 55%;
|
|
240
|
+
background: linear-gradient(
|
|
241
|
+
100deg,
|
|
242
|
+
transparent,
|
|
243
|
+
color-mix(in srgb, currentColor 16%, transparent),
|
|
244
|
+
transparent
|
|
245
|
+
);
|
|
246
|
+
transform: translateX(-200%) skewX(-18deg);
|
|
247
|
+
transition: transform 0.7s cubic-bezier(0.22, 1, 0.36, 1);
|
|
248
|
+
pointer-events: none;
|
|
249
|
+
}
|
|
250
|
+
.btn:not(.btn-icon):not(.u-action):not([class*="btn-anim-"]):hover:not(:disabled)::before,
|
|
251
|
+
.btn-anim-sheen:hover:not(:disabled)::before {
|
|
252
|
+
transform: translateX(320%) skewX(-18deg);
|
|
253
|
+
}
|
|
254
|
+
.btn:not(.btn-icon):not(.u-action):not([class*="btn-anim-"]):hover:not(:disabled),
|
|
255
|
+
.btn-anim-sheen:hover:not(:disabled) {
|
|
256
|
+
transform: translateY(-1px);
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
// --- Fill: a soft currentColor tint wipes in from the left.
|
|
260
|
+
.btn-anim-fill::before {
|
|
261
|
+
content: "";
|
|
262
|
+
position: absolute;
|
|
263
|
+
inset: 0;
|
|
264
|
+
z-index: -1;
|
|
265
|
+
background: currentColor;
|
|
266
|
+
opacity: 0;
|
|
267
|
+
transform: scaleX(0);
|
|
268
|
+
transform-origin: left center;
|
|
269
|
+
transition:
|
|
270
|
+
transform 0.35s cubic-bezier(0.22, 1, 0.36, 1),
|
|
271
|
+
opacity 0.35s ease;
|
|
272
|
+
pointer-events: none;
|
|
273
|
+
}
|
|
274
|
+
.btn-anim-fill:hover:not(:disabled)::before {
|
|
275
|
+
opacity: 0.12;
|
|
276
|
+
transform: scaleX(1);
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
// --- Expand: a radial glow blooms from the centre (aspect-ratio keeps it round).
|
|
280
|
+
.btn-anim-expand::before {
|
|
281
|
+
content: "";
|
|
282
|
+
position: absolute;
|
|
283
|
+
left: 50%;
|
|
284
|
+
top: 50%;
|
|
285
|
+
z-index: -1;
|
|
286
|
+
width: 0;
|
|
287
|
+
aspect-ratio: 1;
|
|
288
|
+
border-radius: 50%;
|
|
289
|
+
background: currentColor;
|
|
290
|
+
opacity: 0;
|
|
291
|
+
transform: translate(-50%, -50%);
|
|
292
|
+
transition:
|
|
293
|
+
width 0.45s cubic-bezier(0.22, 1, 0.36, 1),
|
|
294
|
+
opacity 0.45s ease;
|
|
295
|
+
pointer-events: none;
|
|
296
|
+
}
|
|
297
|
+
.btn-anim-expand:hover:not(:disabled)::before {
|
|
298
|
+
width: 250%;
|
|
299
|
+
opacity: 0.12;
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
// --- Glow: a soft ring in the button's own fill colour + lift. Opt-in premium
|
|
303
|
+
// look that deliberately steps outside the flat/no-shadow default. Falls back
|
|
304
|
+
// through the resolved --bs-btn fill to currentColor so it works on every variant.
|
|
305
|
+
.btn-anim-glow {
|
|
306
|
+
transition:
|
|
307
|
+
box-shadow 0.3s ease,
|
|
308
|
+
transform 0.3s ease;
|
|
309
|
+
}
|
|
310
|
+
.btn-anim-glow:hover:not(:disabled) {
|
|
311
|
+
box-shadow: 0 4px 18px
|
|
312
|
+
color-mix(in srgb, var(--bs-btn-hover-bg, var(--bs-btn-bg, currentColor)) 45%, transparent);
|
|
313
|
+
transform: translateY(-1px);
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
// --- None: opt out of hover motion entirely.
|
|
317
|
+
.btn-anim-none::before {
|
|
318
|
+
display: none;
|
|
319
|
+
}
|
|
320
|
+
.btn-anim-none:hover:not(:disabled) {
|
|
321
|
+
transform: none;
|
|
322
|
+
box-shadow: none;
|
|
162
323
|
}
|
|
163
324
|
|
|
164
325
|
// ---- Dropdown menu (AppDropdown / <app-dropdown>) -------------------
|
|
@@ -547,6 +708,15 @@ html.is-scrolling body::-webkit-scrollbar-thumb {
|
|
|
547
708
|
.shimmer::after {
|
|
548
709
|
animation: none;
|
|
549
710
|
}
|
|
711
|
+
// Hover animation options: hide the moving layer and drop lift/glow so no
|
|
712
|
+
// option animates, regardless of which .btn-anim-* is applied.
|
|
713
|
+
.btn:not(.btn-icon):not(.u-action)::before {
|
|
714
|
+
display: none;
|
|
715
|
+
}
|
|
716
|
+
.btn:not(.btn-icon):not(.u-action):hover:not(:disabled) {
|
|
717
|
+
transform: none;
|
|
718
|
+
box-shadow: none;
|
|
719
|
+
}
|
|
550
720
|
}
|
|
551
721
|
|
|
552
722
|
// ---- Theme-flipped typography utilities ------------------------------------
|
package/scss/_tokens.scss
CHANGED
|
@@ -64,6 +64,10 @@
|
|
|
64
64
|
--wui-shadow-overlay: #{$wui-shadow-overlay};
|
|
65
65
|
--wui-shadow-overlay-lg: #{$wui-shadow-overlay-lg};
|
|
66
66
|
|
|
67
|
+
// Component-specific shadows. Used by slider thumbs and other interactive elements.
|
|
68
|
+
--wui-shadow-thumb: 0 2px 6px rgba(0, 0, 0, 0.1);
|
|
69
|
+
--wui-shadow-thumb-hover: 0 2px 12px rgba(0, 0, 0, 0.2);
|
|
70
|
+
|
|
67
71
|
// One height for every form control. Buttons, inputs and selects share form
|
|
68
72
|
// rows, so they must line up — they were 46.8px and 45px, which reads as a
|
|
69
73
|
// misalignment on every filter bar. 44px is also the WCAG touch-target floor.
|
|
@@ -186,9 +190,9 @@
|
|
|
186
190
|
--wui-danger-5: #FEF4F6;
|
|
187
191
|
|
|
188
192
|
// Corner radii (DESIGN.md scale) — portal aliases as --radius-*
|
|
189
|
-
--wui-radius-sm:
|
|
190
|
-
--wui-radius:
|
|
191
|
-
--wui-radius-lg:
|
|
193
|
+
--wui-radius-sm: 8px;
|
|
194
|
+
--wui-radius: 10px;
|
|
195
|
+
--wui-radius-lg: 14px;
|
|
192
196
|
--wui-radius-xl: 16px;
|
|
193
197
|
|
|
194
198
|
// ---- Portal dark palette ----------------------------------------
|
|
@@ -206,6 +210,10 @@
|
|
|
206
210
|
--wui-dark-primary: #6B83EC;
|
|
207
211
|
--wui-dark-primary-hover:#93A6F2;
|
|
208
212
|
|
|
213
|
+
// Dark mode shadow remaps — lighter shadows (white ink) for visibility on dark surfaces.
|
|
214
|
+
--wui-dark-shadow-thumb: 0 2px 6px rgba(255, 255, 255, 0.1);
|
|
215
|
+
--wui-dark-shadow-thumb-hover: 0 2px 12px rgba(255, 255, 255, 0.2);
|
|
216
|
+
|
|
209
217
|
// ---- Fluid type scale --------------------------------------------
|
|
210
218
|
// clamp(min, preferred, max) so type scales smoothly between viewports instead of
|
|
211
219
|
// stepping at Bootstrap's breakpoints. `base` sits close to Bootstrap's own 1rem body
|
package/scss/_wui-aliases.scss
CHANGED
|
@@ -48,7 +48,8 @@
|
|
|
48
48
|
--color-info-20: var(--wui-info-20);
|
|
49
49
|
--color-info-10: var(--wui-info-10);
|
|
50
50
|
--color-info-5: var(--wui-info-5);
|
|
51
|
-
--
|
|
51
|
+
// No dedicated --wui-info-strong; active is the deepest info fill.
|
|
52
|
+
--color-info-strong: var(--wui-info-active);
|
|
52
53
|
--color-info-soft: var(--color-info-10);
|
|
53
54
|
// Use this — never --color-info — when the colour IS the text.
|
|
54
55
|
--color-info-text: var(--wui-info-text);
|
|
@@ -60,7 +61,7 @@
|
|
|
60
61
|
--color-success-20: var(--wui-success-20);
|
|
61
62
|
--color-success-10: var(--wui-success-10);
|
|
62
63
|
--color-success-5: var(--wui-success-5);
|
|
63
|
-
--color-success-strong: var(--
|
|
64
|
+
--color-success-strong: var(--wui-success-strong);
|
|
64
65
|
--color-success-soft: var(--color-success-10);
|
|
65
66
|
// Use this — never --color-success — when the colour IS the text.
|
|
66
67
|
--color-success-text: var(--wui-success-text);
|
|
@@ -72,7 +73,8 @@
|
|
|
72
73
|
--color-warning-20: var(--wui-warning-20);
|
|
73
74
|
--color-warning-10: var(--wui-warning-10);
|
|
74
75
|
--color-warning-5: var(--wui-warning-5);
|
|
75
|
-
--
|
|
76
|
+
// No dedicated --wui-warning-strong; active is the deepest warning fill.
|
|
77
|
+
--color-warning-strong: var(--wui-warning-active);
|
|
76
78
|
--color-warning-soft: var(--color-warning-10);
|
|
77
79
|
// Use this — never --color-warning — when the colour IS the text.
|
|
78
80
|
--color-warning-text: var(--wui-warning-text);
|
|
@@ -84,7 +86,7 @@
|
|
|
84
86
|
--color-danger-20: var(--wui-danger-20);
|
|
85
87
|
--color-danger-10: var(--wui-danger-10);
|
|
86
88
|
--color-danger-5: var(--wui-danger-5);
|
|
87
|
-
--color-danger-strong: var(--
|
|
89
|
+
--color-danger-strong: var(--wui-danger-strong);
|
|
88
90
|
--color-danger-soft: var(--color-danger-10);
|
|
89
91
|
// Use this — never --color-danger — when the colour IS the text.
|
|
90
92
|
--color-danger-text: var(--wui-danger-text);
|
|
@@ -115,7 +117,14 @@
|
|
|
115
117
|
--radius-xl: var(--wui-radius-xl);
|
|
116
118
|
}
|
|
117
119
|
|
|
118
|
-
|
|
120
|
+
// Dark mode tokens — shared by manual toggle ([data-theme="dark"], [data-bs-theme="dark"])
|
|
121
|
+
// and automatic detection (@media (prefers-color-scheme: dark) when [data-theme="auto"]).
|
|
122
|
+
// Extracted as a mixin to avoid duplication and ensure byte-identical output.
|
|
123
|
+
@mixin wui-dark-theme {
|
|
124
|
+
// Token-level remap so every consumer of the shadow tokens flips in ALL dark
|
|
125
|
+
// paths (manual attributes and the auto media query) without per-component overrides.
|
|
126
|
+
--wui-shadow-thumb: var(--wui-dark-shadow-thumb);
|
|
127
|
+
--wui-shadow-thumb-hover: var(--wui-dark-shadow-thumb-hover);
|
|
119
128
|
--color-bg: var(--wui-dark-bg-page);
|
|
120
129
|
--color-bg-subtle: var(--wui-dark-bg-elevated);
|
|
121
130
|
--color-bg-muted: var(--wui-dark-bg-muted);
|
|
@@ -170,6 +179,13 @@
|
|
|
170
179
|
--color-warning-text: var(--wui-warning);
|
|
171
180
|
--color-info-text: var(--wui-info);
|
|
172
181
|
|
|
182
|
+
// Light mode maps -strong to deep AA fills; on dark surfaces those sink into the page.
|
|
183
|
+
// Remap to the bright fills so timeline dots / strong accents stay visible (same as -text).
|
|
184
|
+
--color-success-strong: var(--wui-success);
|
|
185
|
+
--color-danger-strong: var(--wui-danger);
|
|
186
|
+
--color-info-strong: var(--wui-info);
|
|
187
|
+
--color-warning-strong: var(--wui-warning);
|
|
188
|
+
|
|
173
189
|
--color-success-soft: color-mix(in srgb, var(--wui-success) 18%, transparent);
|
|
174
190
|
--color-warning-soft: color-mix(in srgb, var(--wui-warning) 18%, transparent);
|
|
175
191
|
--color-danger-soft: color-mix(in srgb, var(--wui-danger) 18%, transparent);
|
|
@@ -179,6 +195,20 @@
|
|
|
179
195
|
--color-scrim: color-mix(in srgb, black 45%, transparent);
|
|
180
196
|
--color-scrim-strong: color-mix(in srgb, black 62%, transparent);
|
|
181
197
|
--color-on-scrim: var(--wui-bg-light);
|
|
198
|
+
}
|
|
182
199
|
|
|
200
|
+
// Manual dark mode — set data-theme="dark" and/or data-bs-theme="dark" on <html>
|
|
201
|
+
// to flip both wui and Bootstrap native components together.
|
|
202
|
+
:root[data-theme="dark"],
|
|
203
|
+
:root[data-bs-theme="dark"] {
|
|
204
|
+
@include wui-dark-theme;
|
|
183
205
|
color-scheme: dark;
|
|
184
206
|
}
|
|
207
|
+
|
|
208
|
+
// Automatic dark mode — when data-theme="auto", detect system preference and apply dark tokens.
|
|
209
|
+
:root[data-theme="auto"] {
|
|
210
|
+
@media (prefers-color-scheme: dark) {
|
|
211
|
+
@include wui-dark-theme;
|
|
212
|
+
color-scheme: dark;
|
|
213
|
+
}
|
|
214
|
+
}
|
|
@@ -35,6 +35,9 @@
|
|
|
35
35
|
@import "wui/display";
|
|
36
36
|
@import "wui/flow";
|
|
37
37
|
@import "wui/scrollbar";
|
|
38
|
+
// After `navigation`/`overlays`: the switcher restates dropdown-item chrome those partials
|
|
39
|
+
// set, so it has to win on equal specificity.
|
|
40
|
+
@import "wui/workspace";
|
|
38
41
|
|
|
39
42
|
// Flags belong here because <wui-phone> cannot render without them.
|
|
40
43
|
//
|
package/scss/wui/_card.scss
CHANGED
|
@@ -40,9 +40,9 @@
|
|
|
40
40
|
// ── Variants ──────────────────────────────────────────────────────────────────
|
|
41
41
|
// `default` is the flat surface above. The rest are opt-in visual treatments,
|
|
42
42
|
// chosen by the `variant` prop (React) / attribute (Razor).
|
|
43
|
+
// Flat elevation: no in-page drop shadow — stronger border is the edge cue.
|
|
43
44
|
.card-elevated {
|
|
44
|
-
border-color:
|
|
45
|
-
box-shadow: var(--shadow-md);
|
|
45
|
+
border-color: var(--color-border-strong);
|
|
46
46
|
}
|
|
47
47
|
.card-outlined {
|
|
48
48
|
border-width: 2px;
|
package/scss/wui/_choice.scss
CHANGED
|
@@ -171,12 +171,16 @@
|
|
|
171
171
|
cursor: not-allowed;
|
|
172
172
|
opacity: 0.6;
|
|
173
173
|
}
|
|
174
|
-
.search-select-control:hover:not(:disabled) {
|
|
174
|
+
.search-select-control:hover:not(:disabled):not(.open) {
|
|
175
|
+
// Not while open — an open control keeps its active (primary) border, not the grey hover one.
|
|
175
176
|
border-color: var(--color-border-strong);
|
|
176
177
|
}
|
|
177
178
|
.search-select-control.open {
|
|
178
|
-
|
|
179
|
-
|
|
179
|
+
// White fill + the active/focus border, matching a focused text input (which lifts off the
|
|
180
|
+
// muted resting fill onto white with a --wui-primary-30 border). Keeps the control seamless
|
|
181
|
+
// with the open menu and the floated label's white mask.
|
|
182
|
+
background: var(--color-surface);
|
|
183
|
+
border-color: var(--wui-primary-30);
|
|
180
184
|
}
|
|
181
185
|
.search-select-caret {
|
|
182
186
|
flex-shrink: 0;
|
|
@@ -1095,7 +1099,8 @@ button.datepicker-month:hover {
|
|
|
1095
1099
|
display: none !important;
|
|
1096
1100
|
}
|
|
1097
1101
|
.country-dropdown-item:hover,
|
|
1098
|
-
.country-dropdown-item:focus
|
|
1102
|
+
.country-dropdown-item:focus,
|
|
1103
|
+
.country-dropdown-item.is-active {
|
|
1099
1104
|
background: var(--color-primary-soft) !important;
|
|
1100
1105
|
color: var(--color-primary) !important;
|
|
1101
1106
|
}
|
|
@@ -1120,9 +1125,11 @@ button.datepicker-month:hover {
|
|
|
1120
1125
|
// On a tinted row the label must ride the row colour, not stay grey.
|
|
1121
1126
|
.country-dropdown-item:hover .country-name-text,
|
|
1122
1127
|
.country-dropdown-item:focus .country-name-text,
|
|
1128
|
+
.country-dropdown-item.is-active .country-name-text,
|
|
1123
1129
|
.country-dropdown-item.selected .country-name-text,
|
|
1124
1130
|
.country-dropdown-item:hover .country-code-text,
|
|
1125
1131
|
.country-dropdown-item:focus .country-code-text,
|
|
1132
|
+
.country-dropdown-item.is-active .country-code-text,
|
|
1126
1133
|
.country-dropdown-item.selected .country-code-text {
|
|
1127
1134
|
color: currentColor;
|
|
1128
1135
|
}
|
package/scss/wui/_controls.scss
CHANGED
|
@@ -191,6 +191,11 @@
|
|
|
191
191
|
// deltas on top of Bootstrap's $input-* vars: shared control height, hover
|
|
192
192
|
// fill, brand-border focus (no glow), soft invalid border.
|
|
193
193
|
//
|
|
194
|
+
// Soft-filled resting look (per the field design reference): the field rests on a
|
|
195
|
+
// muted fill with a barely-there border, then lifts to a white surface + brand
|
|
196
|
+
// border + a soft focus ring on focus. "Filled" is the same resting look with a
|
|
197
|
+
// value; the state ladder is default/filled → hover → focus → disabled → error.
|
|
198
|
+
//
|
|
194
199
|
// Keep shell utilities (.input-wrap, .input-icon, .input-sm, …) — those are
|
|
195
200
|
// structure, not a parallel visual system.
|
|
196
201
|
.form-control,
|
|
@@ -204,7 +209,7 @@
|
|
|
204
209
|
padding: 12px;
|
|
205
210
|
border-radius: var(--radius);
|
|
206
211
|
border: 1px solid var(--color-border);
|
|
207
|
-
background: var(--color-
|
|
212
|
+
background: var(--color-bg-muted);
|
|
208
213
|
font-size: 14px;
|
|
209
214
|
line-height: 1.5;
|
|
210
215
|
color: var(--color-text);
|
|
@@ -246,7 +251,7 @@ textarea.form-control.autosize {
|
|
|
246
251
|
// Hover/error guards: without them the hover fill repaints over an invalid border.
|
|
247
252
|
.form-control:hover:not(:focus):not(:disabled):not([readonly]):not([aria-invalid="true"]):not(.is-invalid):not(.error),
|
|
248
253
|
.form-select:hover:not(:focus):not(:disabled):not([aria-invalid="true"]):not(.is-invalid):not(.error),
|
|
249
|
-
.input:hover:not(:focus):not(:disabled):not([readonly]):not([aria-invalid="true"]):not(.is-invalid):not(.error),
|
|
254
|
+
.input:hover:not(:focus):not(:disabled):not([readonly]):not([aria-invalid="true"]):not(.is-invalid):not(.error):not(.open),
|
|
250
255
|
.select:hover:not(:focus):not(:disabled):not([aria-invalid="true"]):not(.is-invalid):not(.error),
|
|
251
256
|
.textarea:hover:not(:focus):not(:disabled):not([readonly]):not([aria-invalid="true"]):not(.is-invalid):not(.error) {
|
|
252
257
|
background-color: var(--color-bg-muted);
|
|
@@ -258,10 +263,11 @@ textarea.form-control.autosize {
|
|
|
258
263
|
.select:focus,
|
|
259
264
|
.textarea:focus {
|
|
260
265
|
outline: none;
|
|
261
|
-
|
|
266
|
+
// Lift off the muted resting fill onto a clean white surface while active.
|
|
267
|
+
background-color: var(--color-surface);
|
|
262
268
|
color: var(--color-text);
|
|
263
|
-
//
|
|
264
|
-
border-color: var(--
|
|
269
|
+
// Soft brand border is the focus cue (per the reference) — no ring/shadow.
|
|
270
|
+
border-color: var(--wui-primary-30);
|
|
265
271
|
box-shadow: none;
|
|
266
272
|
}
|
|
267
273
|
.form-control::placeholder,
|
|
@@ -297,8 +303,10 @@ textarea.form-control.autosize {
|
|
|
297
303
|
.input:disabled,
|
|
298
304
|
.select:disabled,
|
|
299
305
|
.textarea:disabled {
|
|
300
|
-
|
|
301
|
-
|
|
306
|
+
// A touch deeper than the resting fill so a disabled field stays distinct now that
|
|
307
|
+
// the resting state is itself filled (per the reference's disabled field).
|
|
308
|
+
background: color-mix(in srgb, var(--color-bg-muted) 72%, var(--color-border));
|
|
309
|
+
border-color: transparent;
|
|
302
310
|
color: var(--color-text-subtle);
|
|
303
311
|
cursor: not-allowed;
|
|
304
312
|
}
|
|
@@ -350,7 +358,9 @@ textarea.form-control.autosize {
|
|
|
350
358
|
.input.error:focus,
|
|
351
359
|
.textarea.error:focus {
|
|
352
360
|
background: var(--color-surface);
|
|
353
|
-
|
|
361
|
+
// Soft danger border (danger at 30%, mirroring the primary-30 focus border) — no ring,
|
|
362
|
+
// so the field reads as a quiet error, not a solid alarm box.
|
|
363
|
+
border-color: color-mix(in srgb, var(--color-danger) 30%, transparent);
|
|
354
364
|
box-shadow: none;
|
|
355
365
|
// Kill Bootstrap's default invalid SVG + extra padding when .is-invalid is used.
|
|
356
366
|
background-image: none;
|
|
@@ -618,17 +628,23 @@ textarea.form-control.autosize {
|
|
|
618
628
|
width: 100%;
|
|
619
629
|
border: 1px solid var(--color-border);
|
|
620
630
|
border-radius: var(--radius);
|
|
621
|
-
|
|
622
|
-
|
|
631
|
+
// Soft-filled at rest, white on focus — one shared fill for the whole control so the
|
|
632
|
+
// prefix/suffix and the input body are always the SAME colour combination as the field.
|
|
633
|
+
background: var(--color-bg-muted);
|
|
634
|
+
transition:
|
|
635
|
+
border-color 0.12s ease,
|
|
636
|
+
background-color 0.12s ease;
|
|
623
637
|
}
|
|
624
638
|
.input-affixed:hover:not(:focus-within) {
|
|
625
639
|
border-color: var(--color-border-strong);
|
|
626
640
|
}
|
|
627
641
|
.input-affixed:focus-within {
|
|
628
|
-
|
|
642
|
+
background: var(--color-surface);
|
|
643
|
+
border-color: var(--wui-primary-30);
|
|
629
644
|
}
|
|
630
645
|
.input-affixed:has(.input.error) {
|
|
631
|
-
|
|
646
|
+
background: var(--color-surface);
|
|
647
|
+
border-color: color-mix(in srgb, var(--color-danger) 30%, transparent);
|
|
632
648
|
}
|
|
633
649
|
.input-affixed .input {
|
|
634
650
|
flex: 1;
|
|
@@ -641,7 +657,9 @@ textarea.form-control.autosize {
|
|
|
641
657
|
display: flex;
|
|
642
658
|
align-items: center;
|
|
643
659
|
padding: 0 12px;
|
|
644
|
-
|
|
660
|
+
// Transparent so the affix inherits the wrapper fill — the addon and the input read as
|
|
661
|
+
// one continuous surface (only the separator border divides them).
|
|
662
|
+
background: transparent;
|
|
645
663
|
color: var(--color-text-subtle);
|
|
646
664
|
font-size: 14px;
|
|
647
665
|
white-space: nowrap;
|
|
@@ -669,19 +687,27 @@ textarea.form-control.autosize {
|
|
|
669
687
|
transform: translateY(-50%);
|
|
670
688
|
transform-origin: left center;
|
|
671
689
|
margin: 0;
|
|
672
|
-
padding: 0
|
|
690
|
+
padding: 0 6px;
|
|
691
|
+
// Rounded so the background mask reads as a soft pill sitting on the border, not a sharp
|
|
692
|
+
// rectangle. Invisible at rest (label fill == field fill); only shows once it floats up.
|
|
693
|
+
border-radius: var(--radius-sm);
|
|
673
694
|
max-width: calc(100% - 24px);
|
|
674
695
|
overflow: hidden;
|
|
675
696
|
text-overflow: ellipsis;
|
|
676
697
|
white-space: nowrap;
|
|
677
698
|
font-size: 14px;
|
|
678
699
|
color: var(--color-text-subtle);
|
|
679
|
-
background
|
|
700
|
+
// The floated label sits ON the top border, so its background masks the border under the
|
|
701
|
+
// text. It MUST match the field's own fill, or the mask reads as a separate patch. The
|
|
702
|
+
// resting/filled fill is --color-bg-muted (a white notch on the grey fill was the tell); it
|
|
703
|
+
// only lifts to --color-surface on focus/invalid, matched below.
|
|
704
|
+
background: var(--color-bg-muted);
|
|
680
705
|
pointer-events: none;
|
|
681
706
|
transition:
|
|
682
707
|
top 0.15s var(--wui-ease-ui),
|
|
683
708
|
transform 0.15s var(--wui-ease-ui),
|
|
684
|
-
color 0.15s var(--wui-ease-ui)
|
|
709
|
+
color 0.15s var(--wui-ease-ui),
|
|
710
|
+
background-color 0.12s;
|
|
685
711
|
}
|
|
686
712
|
.field-float .input:focus ~ .float-label,
|
|
687
713
|
.field-float .input:not(:placeholder-shown) ~ .float-label {
|
|
@@ -694,6 +720,14 @@ textarea.form-control.autosize {
|
|
|
694
720
|
.field-float .input.error ~ .float-label {
|
|
695
721
|
color: var(--color-danger-text);
|
|
696
722
|
}
|
|
723
|
+
// Track the field's fill: it lifts to --color-surface while focused or invalid, so the label
|
|
724
|
+
// mask follows to stay seamless in those states too.
|
|
725
|
+
.field-float .input:focus ~ .float-label,
|
|
726
|
+
.field-float .input.error ~ .float-label,
|
|
727
|
+
.field-float .input.is-invalid ~ .float-label,
|
|
728
|
+
.field-float .input[aria-invalid="true"] ~ .float-label {
|
|
729
|
+
background: var(--color-surface);
|
|
730
|
+
}
|
|
697
731
|
// Floating label for AppSelect — the trigger is a <button>, so there is no
|
|
698
732
|
// :placeholder-shown to key off. :has() raises the label when the control is
|
|
699
733
|
// focused/open, or once a value is chosen (the label span drops
|
|
@@ -707,6 +741,9 @@ textarea.form-control.autosize {
|
|
|
707
741
|
.field-float:has(.search-select-control:focus) .float-label,
|
|
708
742
|
.field-float:has(.search-select-control.open) .float-label {
|
|
709
743
|
color: var(--color-primary);
|
|
744
|
+
// The control turns white while focused/open, so the label mask follows (a filled-but-closed
|
|
745
|
+
// select keeps the default muted mask, matching its muted resting fill).
|
|
746
|
+
background: var(--color-surface);
|
|
710
747
|
}
|
|
711
748
|
|
|
712
749
|
.hint,
|
|
@@ -859,22 +896,24 @@ wui-select[compact] {
|
|
|
859
896
|
border: 1px solid var(--color-border);
|
|
860
897
|
border-radius: var(--radius-lg);
|
|
861
898
|
corner-shape: squircle;
|
|
862
|
-
|
|
899
|
+
// Soft-filled at rest, white on focus — the numeric input and its unit pill share the
|
|
900
|
+
// one wrapper fill, same colour combination as every other field.
|
|
901
|
+
background: var(--color-bg-muted);
|
|
863
902
|
transition:
|
|
864
903
|
border-color 0.12s,
|
|
865
904
|
background-color 0.12s;
|
|
866
905
|
overflow: hidden;
|
|
867
906
|
}
|
|
868
907
|
.input-with-unit:hover:not(:focus-within) {
|
|
869
|
-
background: var(--color-bg-muted);
|
|
870
908
|
border-color: var(--color-border-strong);
|
|
871
909
|
}
|
|
872
910
|
.input-with-unit:focus-within {
|
|
873
|
-
background: var(--color-
|
|
874
|
-
border-color:
|
|
911
|
+
background: var(--color-surface);
|
|
912
|
+
border-color: var(--wui-primary-30);
|
|
875
913
|
}
|
|
876
914
|
.input-with-unit:has(.num-input.error) {
|
|
877
|
-
|
|
915
|
+
background: var(--color-surface);
|
|
916
|
+
border-color: color-mix(in srgb, var(--color-danger) 30%, transparent);
|
|
878
917
|
}
|
|
879
918
|
|
|
880
919
|
// Numeric inputs are plain text fields — no native spinner, no stepper. The
|
|
@@ -889,7 +928,8 @@ wui-select[compact] {
|
|
|
889
928
|
box-shadow: none;
|
|
890
929
|
}
|
|
891
930
|
.input-with-unit .unit {
|
|
892
|
-
|
|
931
|
+
// Transparent so the pill inherits the wrapper fill — one continuous surface.
|
|
932
|
+
background: transparent;
|
|
893
933
|
border: none;
|
|
894
934
|
border-left: 1px solid var(--color-border);
|
|
895
935
|
padding: 9px 14px;
|
|
@@ -1311,7 +1351,7 @@ wui-otp {
|
|
|
1311
1351
|
background-color: var(--color-primary);
|
|
1312
1352
|
border: 2px solid var(--color-surface);
|
|
1313
1353
|
border-radius: 50%;
|
|
1314
|
-
box-shadow:
|
|
1354
|
+
box-shadow: var(--wui-shadow-thumb);
|
|
1315
1355
|
cursor: pointer;
|
|
1316
1356
|
transition:
|
|
1317
1357
|
background-color 0.15s ease,
|
|
@@ -1319,7 +1359,7 @@ wui-otp {
|
|
|
1319
1359
|
border-color 0.15s ease;
|
|
1320
1360
|
}
|
|
1321
1361
|
.slider-thumb:hover:not(:disabled) {
|
|
1322
|
-
box-shadow:
|
|
1362
|
+
box-shadow: var(--wui-shadow-thumb-hover);
|
|
1323
1363
|
}
|
|
1324
1364
|
.slider-thumb:focus-visible {
|
|
1325
1365
|
outline: 2px solid var(--color-primary);
|
|
@@ -1342,6 +1382,7 @@ wui-otp {
|
|
|
1342
1382
|
white-space: nowrap;
|
|
1343
1383
|
}
|
|
1344
1384
|
|
|
1385
|
+
// Dark mode: slider shadows need light ink for visibility.
|
|
1345
1386
|
// Size variants
|
|
1346
1387
|
.slider-sm {
|
|
1347
1388
|
height: 28px;
|