@webority/theme 0.68.3 → 0.70.1
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 +19 -0
- package/dist/purge-safelist.json +8 -1
- package/dist/webority-runtime-brand.css +1 -0
- package/dist/webority-theme-export.mjs +2 -0
- package/dist/webority-theme.css +1 -1
- package/package.json +8 -3
- package/scss/_components.scss +55 -29
- package/scss/_runtime-brand.scss +83 -0
- package/scss/_tokens.scss +8 -9
- package/scss/_wui-aliases.scss +3 -4
- package/scss/wui/_choice.scss +15 -4
- package/scss/wui/_controls.scss +46 -11
- package/scss/wui/_data.scss +14 -2
- package/scss/wui/_display.scss +2 -2
- package/scss/wui/_filter-popover.scss +25 -6
- package/scss/wui/_flow.scss +4 -0
- package/scss/wui/_interaction.scss +7 -37
- package/scss/wui/_media.scss +5 -0
- package/scss/wui/_modal-remote.scss +37 -1
- package/scss/wui/_overlays.scss +15 -0
- package/scss/wui/_page-header-bar.scss +63 -7
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@webority/theme",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.70.1",
|
|
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": {
|
|
@@ -28,9 +28,13 @@
|
|
|
28
28
|
"./css": "./dist/webority-theme.css",
|
|
29
29
|
"./css/flags": "./dist/webority-flags.css",
|
|
30
30
|
"./css/brand-signal": "./dist/webority-brand-signal.css",
|
|
31
|
+
"./css/runtime-brand": "./dist/webority-runtime-brand.css",
|
|
31
32
|
"./purge-safelist.json": "./dist/purge-safelist.json",
|
|
32
33
|
"./package.json": "./package.json"
|
|
33
34
|
},
|
|
35
|
+
"bin": {
|
|
36
|
+
"webority-theme-export": "./dist/webority-theme-export.mjs"
|
|
37
|
+
},
|
|
34
38
|
"sass": "index.scss",
|
|
35
39
|
"style": "dist/webority-theme.css",
|
|
36
40
|
"peerDependencies": {
|
|
@@ -39,9 +43,10 @@
|
|
|
39
43
|
"devDependencies": {
|
|
40
44
|
"bootstrap": "5.3.3",
|
|
41
45
|
"country-flag-icons": "^1.6.20",
|
|
42
|
-
"sass-embedded": "^1.97.0"
|
|
46
|
+
"sass-embedded": "^1.97.0",
|
|
47
|
+
"vite": "^8.0.12"
|
|
43
48
|
},
|
|
44
49
|
"scripts": {
|
|
45
|
-
"build": "sass index.scss dist/webority-theme.css --load-path=node_modules --style=compressed --no-source-map --quiet-deps --silence-deprecation=if-function && sass scss/_flags.scss dist/webority-flags.css --style=compressed --no-source-map --quiet && sass scss/_brand-signal.scss dist/webority-brand-signal.css --style=compressed --no-source-map --quiet && node ../../scripts/gen-purge-safelist.mjs && node ../../scripts/sync-rcl-assets.mjs theme"
|
|
50
|
+
"build": "sass index.scss dist/webority-theme.css --load-path=node_modules --style=compressed --no-source-map --quiet-deps --silence-deprecation=if-function && sass scss/_flags.scss dist/webority-flags.css --style=compressed --no-source-map --quiet && sass scss/_brand-signal.scss dist/webority-brand-signal.css --style=compressed --no-source-map --quiet && sass scss/_runtime-brand.scss dist/webority-runtime-brand.css --style=compressed --no-source-map --quiet && node ../../scripts/gen-purge-safelist.mjs && node ../../scripts/sync-rcl-assets.mjs theme && vite build --config vite.cli.config.js"
|
|
46
51
|
}
|
|
47
52
|
}
|
package/scss/_components.scss
CHANGED
|
@@ -6,15 +6,14 @@
|
|
|
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
|
-
//
|
|
12
|
-
// input ladder; sm/lg pin their own height in their size blocks below.)
|
|
9
|
+
// A button is exactly as tall as a field of the same size (the --wui-control-height ladder, 36/44/52px), so a
|
|
10
|
+
// button beside an input lines up in every form row. Heights are min-height with the label flex-centred; sm/lg
|
|
11
|
+
// set theirs in their size blocks below.
|
|
13
12
|
.btn:not(.btn-sm):not(.btn-lg):not(.btn-pill):not(.btn-pill-sm):not(.u-action):not(.btn-icon) {
|
|
14
13
|
display: inline-flex;
|
|
15
14
|
align-items: center;
|
|
16
15
|
justify-content: center;
|
|
17
|
-
min-height:
|
|
16
|
+
min-height: var(--wui-control-height);
|
|
18
17
|
}
|
|
19
18
|
|
|
20
19
|
// Tactile press feedback — every variant AND every size (sm/md/lg). Kept
|
|
@@ -266,7 +265,7 @@
|
|
|
266
265
|
// sizes step font-size, horizontal padding and radius together (vertical size
|
|
267
266
|
// comes from --wui-control-height, so a button lines up with an input). Weight
|
|
268
267
|
// is 600 on every rung — buttons read as a deliberate action, not body text.
|
|
269
|
-
// Medium (default): 14px / 600, 24px side padding, 18px squircle radius,
|
|
268
|
+
// Medium (default): 14px / 600, 24px side padding, 18px squircle radius, --wui-control-height tall.
|
|
270
269
|
.btn {
|
|
271
270
|
--bs-btn-border-radius: 18px;
|
|
272
271
|
--bs-btn-font-size: 0.875rem;
|
|
@@ -276,7 +275,7 @@
|
|
|
276
275
|
// keep a plain border-radius arc — not a cross-browser design contract.
|
|
277
276
|
corner-shape: squircle;
|
|
278
277
|
}
|
|
279
|
-
// Small: 12px, 18px side padding, 14px squircle radius (proportional to md),
|
|
278
|
+
// Small: 12px, 18px side padding, 14px squircle radius (proportional to md), as tall as a small field.
|
|
280
279
|
.btn-sm:not(.btn-icon),
|
|
281
280
|
.btn-group-sm > .btn:not(.btn-icon) {
|
|
282
281
|
--bs-btn-border-radius: 14px;
|
|
@@ -285,9 +284,9 @@
|
|
|
285
284
|
display: inline-flex;
|
|
286
285
|
align-items: center;
|
|
287
286
|
justify-content: center;
|
|
288
|
-
min-height:
|
|
287
|
+
min-height: var(--wui-control-height-sm);
|
|
289
288
|
}
|
|
290
|
-
// Large: 16px, 32px side padding, 22px squircle radius (proportional to md),
|
|
289
|
+
// Large: 16px, 32px side padding, 22px squircle radius (proportional to md), as tall as a large field.
|
|
291
290
|
.btn-lg:not(.btn-icon),
|
|
292
291
|
.btn-group-lg > .btn:not(.btn-icon) {
|
|
293
292
|
--bs-btn-border-radius: 22px;
|
|
@@ -296,7 +295,7 @@
|
|
|
296
295
|
display: inline-flex;
|
|
297
296
|
align-items: center;
|
|
298
297
|
justify-content: center;
|
|
299
|
-
min-height:
|
|
298
|
+
min-height: var(--wui-control-height-lg);
|
|
300
299
|
}
|
|
301
300
|
|
|
302
301
|
// ---- Hover animation options (opt-in modifiers) ---------------------
|
|
@@ -544,9 +543,9 @@
|
|
|
544
543
|
}
|
|
545
544
|
|
|
546
545
|
// ---- Ghost button: the tertiary action --------------------------------
|
|
547
|
-
// Brand-coloured
|
|
548
|
-
//
|
|
549
|
-
//
|
|
546
|
+
// Brand-coloured text with no box at rest and no underline. Hover lays a light grey fill behind
|
|
547
|
+
// it, pressing deepens that fill a step, and keyboard focus draws the shared ring. No sheen sweep,
|
|
548
|
+
// no lift or press nudge, and no grey box when disabled or loading.
|
|
550
549
|
// AppButton's `ghost` variant emits this class on both surfaces.
|
|
551
550
|
//
|
|
552
551
|
// No .btn-ghost-sm/-lg rungs: it is an ordinary .btn variant and sizes with Bootstrap's
|
|
@@ -555,32 +554,24 @@
|
|
|
555
554
|
--bs-btn-color: var(--color-primary-ink);
|
|
556
555
|
--bs-btn-bg: transparent;
|
|
557
556
|
--bs-btn-border-color: transparent;
|
|
558
|
-
--bs-btn-hover-color:
|
|
559
|
-
--bs-btn-hover-bg:
|
|
557
|
+
--bs-btn-hover-color: var(--color-primary-ink);
|
|
558
|
+
--bs-btn-hover-bg: var(--color-bg-muted);
|
|
560
559
|
--bs-btn-hover-border-color: transparent;
|
|
561
|
-
--bs-btn-active-color:
|
|
562
|
-
--bs-btn-active-bg:
|
|
560
|
+
--bs-btn-active-color: var(--color-primary-ink);
|
|
561
|
+
--bs-btn-active-bg: color-mix(in srgb, var(--color-bg-muted) 92%, var(--color-text));
|
|
563
562
|
--bs-btn-active-border-color: transparent;
|
|
564
563
|
--bs-btn-disabled-color: var(--color-primary-ink);
|
|
565
564
|
--bs-btn-disabled-bg: transparent;
|
|
566
565
|
--bs-btn-disabled-border-color: transparent;
|
|
567
|
-
text-decoration: underline;
|
|
568
|
-
text-decoration-thickness: 1px;
|
|
569
|
-
text-underline-offset: 3px;
|
|
570
566
|
}
|
|
571
|
-
// Bootstrap's
|
|
572
|
-
|
|
567
|
+
// Bootstrap's link-style buttons can carry an underline; the ghost never does.
|
|
568
|
+
.btn.btn-ghost,
|
|
573
569
|
.btn.btn-ghost:hover,
|
|
574
570
|
.btn.btn-ghost:active,
|
|
575
571
|
.btn.btn-ghost:focus-visible {
|
|
576
|
-
text-decoration:
|
|
577
|
-
text-decoration-thickness: 1px;
|
|
572
|
+
text-decoration: none;
|
|
578
573
|
}
|
|
579
|
-
.
|
|
580
|
-
.btn.btn-ghost:active:not(:disabled) {
|
|
581
|
-
text-decoration-thickness: 2px;
|
|
582
|
-
}
|
|
583
|
-
// No box to light up, so keyboard focus gets the outline ring instead.
|
|
574
|
+
// No fill at rest, so keyboard focus gets the outline ring instead.
|
|
584
575
|
.btn.btn-ghost:focus-visible {
|
|
585
576
|
outline: var(--wui-focus-ring-width) solid var(--color-focus-ring);
|
|
586
577
|
outline-offset: var(--wui-focus-ring-offset);
|
|
@@ -634,6 +625,41 @@
|
|
|
634
625
|
margin-left: auto;
|
|
635
626
|
}
|
|
636
627
|
|
|
628
|
+
// Close button (React `onClose` / Razor `dismissible`): the last control in the header, pushed
|
|
629
|
+
// to the end, or just after the header actions when there are some.
|
|
630
|
+
.card-header:has(.card-close) {
|
|
631
|
+
display: flex;
|
|
632
|
+
align-items: center;
|
|
633
|
+
}
|
|
634
|
+
.card-close {
|
|
635
|
+
display: inline-grid;
|
|
636
|
+
flex-shrink: 0;
|
|
637
|
+
place-items: center;
|
|
638
|
+
width: 32px;
|
|
639
|
+
height: 32px;
|
|
640
|
+
margin: -6px -6px -6px auto;
|
|
641
|
+
padding: 0;
|
|
642
|
+
border: 0;
|
|
643
|
+
border-radius: var(--radius);
|
|
644
|
+
background: transparent;
|
|
645
|
+
color: var(--color-text-muted);
|
|
646
|
+
cursor: pointer;
|
|
647
|
+
transition:
|
|
648
|
+
background-color 0.12s ease,
|
|
649
|
+
color 0.12s ease;
|
|
650
|
+
}
|
|
651
|
+
.card-header-actions + .card-close {
|
|
652
|
+
margin-left: 0.25rem;
|
|
653
|
+
}
|
|
654
|
+
.card-close:hover {
|
|
655
|
+
background: var(--color-bg-muted);
|
|
656
|
+
color: var(--color-text);
|
|
657
|
+
}
|
|
658
|
+
.card-close:focus-visible {
|
|
659
|
+
outline: var(--wui-focus-ring-width) solid var(--color-focus-ring);
|
|
660
|
+
outline-offset: var(--wui-focus-ring-offset);
|
|
661
|
+
}
|
|
662
|
+
|
|
637
663
|
// ---- Inputs ---------------------------------------------------------
|
|
638
664
|
// Field chrome (height, hover, focus, invalid) lives in wui/_controls.scss on
|
|
639
665
|
// `.form-control` / `.form-select` with `.input` as an alias — one skin, not two
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
// Runtime brand: an opt-in layer that lets a product's exported token file (webority-theme-export,
|
|
2
|
+
// format webority-theme-tokens/1) restyle the precompiled theme without recompiling it.
|
|
3
|
+
//
|
|
4
|
+
// Loaded AFTER webority-theme.css and BEFORE the token file's values. It holds no colour of its own.
|
|
5
|
+
// Bootstrap compiles $primary into a few component rules as literals; this layer re-points each of
|
|
6
|
+
// them at --bs-primary / --bs-primary-rgb, which the token file sets on :root. Every other brand
|
|
7
|
+
// value (--wui-*, links, fonts, the Bootstrap subtle and emphasis colours, dark mode) is already a
|
|
8
|
+
// custom property on :root and arrives with the token file unchanged.
|
|
9
|
+
//
|
|
10
|
+
// Each tint below is the colour-mix equivalent of the Bootstrap function that made the literal
|
|
11
|
+
// (tint-color = mix with white, shade-color = mix with black), so a product's own primary renders
|
|
12
|
+
// the same through this layer as through its compiled build. The compiled values are the same in
|
|
13
|
+
// light and dark, so there is no dark block.
|
|
14
|
+
|
|
15
|
+
.btn-primary {
|
|
16
|
+
--bs-btn-bg: var(--bs-primary);
|
|
17
|
+
--bs-btn-border-color: var(--bs-primary);
|
|
18
|
+
--bs-btn-disabled-bg: var(--bs-primary);
|
|
19
|
+
--bs-btn-disabled-border-color: var(--bs-primary);
|
|
20
|
+
// Bootstrap mixes 15% white into this triplet; a triplet cannot be mixed at runtime.
|
|
21
|
+
--bs-btn-focus-shadow-rgb: var(--bs-primary-rgb);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
.btn-outline-primary {
|
|
25
|
+
--bs-btn-focus-shadow-rgb: var(--bs-primary-rgb);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
.nav-pills {
|
|
29
|
+
--bs-nav-pills-link-active-bg: var(--bs-primary);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
.pagination {
|
|
33
|
+
--bs-pagination-active-bg: var(--bs-primary);
|
|
34
|
+
--bs-pagination-active-border-color: var(--bs-primary);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
.list-group {
|
|
38
|
+
--bs-list-group-active-bg: var(--bs-primary);
|
|
39
|
+
--bs-list-group-active-border-color: var(--bs-primary);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
.progress,
|
|
43
|
+
.progress-stacked {
|
|
44
|
+
--bs-progress-bar-bg: var(--bs-primary);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// Mouse focus only: the theme paints keyboard focus and checked boxes from --color-primary already,
|
|
48
|
+
// and a plain :focus rule here would win over both by source order.
|
|
49
|
+
.form-check-input:focus:not(:focus-visible):not(:checked):not([type="checkbox"]:indeterminate) {
|
|
50
|
+
border-color: color-mix(in srgb, var(--bs-primary) 50%, #fff);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
.form-range::-webkit-slider-thumb {
|
|
54
|
+
background-color: var(--bs-primary);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
.form-range::-webkit-slider-thumb:active {
|
|
58
|
+
background-color: color-mix(in srgb, var(--bs-primary) 30%, #fff);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
.form-range::-moz-range-thumb {
|
|
62
|
+
background-color: var(--bs-primary);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
.form-range::-moz-range-thumb:active {
|
|
66
|
+
background-color: color-mix(in srgb, var(--bs-primary) 30%, #fff);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
.link-primary:hover,
|
|
70
|
+
.link-primary:focus {
|
|
71
|
+
color: color-mix(in srgb, color-mix(in srgb, var(--bs-primary) 80%, #000) calc(var(--bs-link-opacity, 1) * 100%), transparent) !important;
|
|
72
|
+
text-decoration-color: color-mix(in srgb, color-mix(in srgb, var(--bs-primary) 80%, #000) calc(var(--bs-link-underline-opacity, 1) * 100%), transparent) !important;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// Bootstrap's table-variant mixin: the cell colour is black, and each state mixes it into the tint.
|
|
76
|
+
.table-primary {
|
|
77
|
+
--wui-table-primary-bg: color-mix(in srgb, var(--bs-primary) 20%, #fff);
|
|
78
|
+
--bs-table-bg: var(--wui-table-primary-bg);
|
|
79
|
+
--bs-table-border-color: color-mix(in srgb, #000 20%, var(--wui-table-primary-bg));
|
|
80
|
+
--bs-table-striped-bg: color-mix(in srgb, #000 5%, var(--wui-table-primary-bg));
|
|
81
|
+
--bs-table-active-bg: color-mix(in srgb, #000 10%, var(--wui-table-primary-bg));
|
|
82
|
+
--bs-table-hover-bg: color-mix(in srgb, #000 7.5%, var(--wui-table-primary-bg));
|
|
83
|
+
}
|
package/scss/_tokens.scss
CHANGED
|
@@ -41,12 +41,11 @@
|
|
|
41
41
|
// combobox/phone/date/number wrappers) — a touch lighter than --wui-bg-muted so fields read
|
|
42
42
|
// as their own surface. Change here to restyle every field's background at once.
|
|
43
43
|
--wui-field-bg: #F9FBFC;
|
|
44
|
-
// Resting and hover border of every input-like control
|
|
45
|
-
//
|
|
46
|
-
//
|
|
47
|
-
|
|
48
|
-
--wui-field-border: #
|
|
49
|
-
--wui-field-border-hover: #7C839A;
|
|
44
|
+
// Resting and hover border of every input-like control: a soft edge a step darker than
|
|
45
|
+
// --wui-border-light, and the --wui-text-5 hover. They sit under WCAG 1.4.11's 3:1 by
|
|
46
|
+
// design choice; check-contrast reports them as FAIL without failing the gate.
|
|
47
|
+
--wui-field-border: #DFE2EA;
|
|
48
|
+
--wui-field-border-hover: #C4CADA;
|
|
50
49
|
--wui-border-light: #EBEDF3;
|
|
51
50
|
--wui-border-soft: #F4F6F8;
|
|
52
51
|
// The unfilled part of a progress bar / meter. Darker than every surface it can sit on
|
|
@@ -266,9 +265,9 @@
|
|
|
266
265
|
--wui-dark-bg-surface: #181b23;
|
|
267
266
|
--wui-dark-border: #2a2f3a;
|
|
268
267
|
--wui-dark-border-strong:#3a414f;
|
|
269
|
-
// Field borders on the dark fills:
|
|
270
|
-
--wui-dark-field-border: #
|
|
271
|
-
--wui-dark-field-border-hover: #
|
|
268
|
+
// Field borders on the dark fills: the dark border and strong border values.
|
|
269
|
+
--wui-dark-field-border: #2a2f3a;
|
|
270
|
+
--wui-dark-field-border-hover: #3a414f;
|
|
272
271
|
// Lighter than the dark page, surface and muted fill, so the empty track shows on all three.
|
|
273
272
|
--wui-dark-progress-track: #323845;
|
|
274
273
|
--wui-dark-text: #e6e8ee;
|
package/scss/_wui-aliases.scss
CHANGED
|
@@ -18,7 +18,7 @@
|
|
|
18
18
|
--color-border: var(--wui-border-light);
|
|
19
19
|
--color-border-light: var(--wui-border-soft);
|
|
20
20
|
--color-border-strong: var(--wui-text-5);
|
|
21
|
-
// The resting and hover border of an input-like control
|
|
21
|
+
// The resting and hover border of an input-like control.
|
|
22
22
|
// Decorative card and table borders stay on --color-border.
|
|
23
23
|
--color-field-border: var(--wui-field-border);
|
|
24
24
|
--color-field-border-hover: var(--wui-field-border-hover);
|
|
@@ -40,9 +40,8 @@
|
|
|
40
40
|
// A dark-ink glyph such as Bootstrap's .btn-close needs no filter on a light surface.
|
|
41
41
|
--color-close-filter: none;
|
|
42
42
|
// The border of a focused / open / active control (input, select, OTP slot, dropzone).
|
|
43
|
-
//
|
|
44
|
-
|
|
45
|
-
--color-focus-border: var(--wui-primary);
|
|
43
|
+
// The soft primary tint, matching the soft resting border.
|
|
44
|
+
--color-focus-border: var(--wui-primary-30);
|
|
46
45
|
// The keyboard focus ring every control draws on :focus-visible: an outline of this colour,
|
|
47
46
|
// width and offset. The primary ink clears 4.5:1 on every light surface and the dark primary
|
|
48
47
|
// on every dark one, so the ring keeps 3:1 whatever the brand. A coloured surface (a scrim, a
|
package/scss/wui/_choice.scss
CHANGED
|
@@ -253,15 +253,22 @@
|
|
|
253
253
|
background: var(--color-surface);
|
|
254
254
|
border-color: var(--color-focus-border);
|
|
255
255
|
}
|
|
256
|
+
// Closed again (a value picked, Escape), focus stays on the trigger: it returns to the resting
|
|
257
|
+
// fill and border, as the date picker does. Only the open list shows the focus border; keyboard
|
|
258
|
+
// focus still draws the ring below.
|
|
259
|
+
.search-select-control:focus:not(.open):not([aria-invalid="true"]):not(.error):not(.is-invalid) {
|
|
260
|
+
background: var(--color-field-bg);
|
|
261
|
+
border-color: var(--color-field-border);
|
|
262
|
+
}
|
|
256
263
|
// Keyboard focus draws a ring, not just the 1px border change: a control that only recolours its
|
|
257
264
|
// border fails WCAG 2.4.7 / 2.4.11. A single-select button and a multi-select div hold focus
|
|
258
265
|
// themselves; the typing variants (autocomplete, tag input, allow-custom multi-select) keep it on
|
|
259
|
-
// an inner input, so the ring follows `:has(:focus-visible)`.
|
|
260
|
-
//
|
|
266
|
+
// an inner input, so the ring follows `:has(:focus-visible)`. It uses the shared keyboard ring
|
|
267
|
+
// colour, not the soft field focus border, so it clears 3:1 in both themes.
|
|
261
268
|
.search-select-control:focus-visible,
|
|
262
269
|
.search-select-control:has(> .search-select-input:focus-visible),
|
|
263
270
|
.search-select-control:has(.ms-input:focus-visible) {
|
|
264
|
-
outline: 2px solid var(--color-focus-
|
|
271
|
+
outline: 2px solid var(--color-focus-ring);
|
|
265
272
|
outline-offset: 2px;
|
|
266
273
|
}
|
|
267
274
|
.search-select-caret {
|
|
@@ -508,10 +515,14 @@
|
|
|
508
515
|
background 0.12s ease,
|
|
509
516
|
color 0.12s ease;
|
|
510
517
|
}
|
|
518
|
+
// The row under the pointer or the keyboard: a light tint and a light 1px edge, softer than the
|
|
519
|
+
// selected row's fill so the two stay apart.
|
|
511
520
|
.search-select-option:hover,
|
|
512
521
|
.search-select-option.is-active {
|
|
513
|
-
background: var(--color-primary-soft);
|
|
522
|
+
background: color-mix(in srgb, var(--color-primary-soft) 55%, transparent);
|
|
514
523
|
color: var(--color-primary-ink);
|
|
524
|
+
outline: 1px solid var(--color-focus-border);
|
|
525
|
+
outline-offset: -1px;
|
|
515
526
|
}
|
|
516
527
|
.search-select-option.selected {
|
|
517
528
|
background: var(--color-primary-soft);
|
package/scss/wui/_controls.scss
CHANGED
|
@@ -470,10 +470,7 @@ textarea.form-control.autosize {
|
|
|
470
470
|
.input[readonly],
|
|
471
471
|
.select[readonly],
|
|
472
472
|
.textarea[readonly] {
|
|
473
|
-
|
|
474
|
-
// value never looks typeable.
|
|
475
|
-
background: var(--color-bg-muted);
|
|
476
|
-
border-color: var(--color-border);
|
|
473
|
+
background: var(--color-bg-subtle);
|
|
477
474
|
color: var(--color-text-muted);
|
|
478
475
|
cursor: default;
|
|
479
476
|
}
|
|
@@ -689,24 +686,57 @@ $wui-field-error-glyph: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/
|
|
|
689
686
|
}
|
|
690
687
|
|
|
691
688
|
// Leading-icon input — icon turns brand on focus.
|
|
689
|
+
// The leading icon sits centred in a fixed slot, a grey divider closes the slot, and the text
|
|
690
|
+
// starts a gap after it. Slot, glyph and gap scale with the field size, so every icon in a
|
|
691
|
+
// field of one size lands in the same place.
|
|
692
692
|
.input-icon {
|
|
693
|
+
--wui-input-icon-slot: 40px;
|
|
694
|
+
--wui-input-icon-size: 16px;
|
|
695
|
+
--wui-input-icon-gap: 12px;
|
|
693
696
|
position: relative;
|
|
694
697
|
display: flex;
|
|
695
698
|
align-items: center;
|
|
696
699
|
width: 100%;
|
|
697
700
|
}
|
|
701
|
+
.input-icon:has(> .input-sm) {
|
|
702
|
+
--wui-input-icon-slot: 34px;
|
|
703
|
+
--wui-input-icon-size: 14px;
|
|
704
|
+
--wui-input-icon-gap: 10px;
|
|
705
|
+
}
|
|
706
|
+
.input-icon:has(> .input-lg) {
|
|
707
|
+
--wui-input-icon-slot: 48px;
|
|
708
|
+
--wui-input-icon-size: 18px;
|
|
709
|
+
--wui-input-icon-gap: 14px;
|
|
710
|
+
}
|
|
698
711
|
.input-icon > .ic {
|
|
699
712
|
position: absolute;
|
|
700
|
-
left:
|
|
701
|
-
top:
|
|
702
|
-
|
|
713
|
+
left: 0;
|
|
714
|
+
top: 0;
|
|
715
|
+
bottom: 0;
|
|
716
|
+
width: var(--wui-input-icon-slot);
|
|
703
717
|
display: grid;
|
|
704
718
|
place-items: center;
|
|
705
719
|
color: var(--color-text-subtle);
|
|
706
720
|
pointer-events: none;
|
|
707
721
|
}
|
|
722
|
+
.input-icon > .ic > :is(svg, img) {
|
|
723
|
+
width: var(--wui-input-icon-size);
|
|
724
|
+
height: var(--wui-input-icon-size);
|
|
725
|
+
}
|
|
726
|
+
// The divider runs the full height between the field's top and bottom border, in the field
|
|
727
|
+
// border colour.
|
|
728
|
+
.input-icon::after {
|
|
729
|
+
content: "";
|
|
730
|
+
position: absolute;
|
|
731
|
+
left: var(--wui-input-icon-slot);
|
|
732
|
+
top: 1px;
|
|
733
|
+
bottom: 1px;
|
|
734
|
+
width: 1px;
|
|
735
|
+
background: var(--color-field-border);
|
|
736
|
+
pointer-events: none;
|
|
737
|
+
}
|
|
708
738
|
.input-icon .input {
|
|
709
|
-
padding-left:
|
|
739
|
+
padding-left: calc(var(--wui-input-icon-slot) + var(--wui-input-icon-gap));
|
|
710
740
|
}
|
|
711
741
|
.input-icon:focus-within > .ic {
|
|
712
742
|
color: var(--color-primary-ink);
|
|
@@ -943,11 +973,10 @@ $wui-field-error-glyph: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/
|
|
|
943
973
|
top: 0;
|
|
944
974
|
transform: translateY(-50%) scale(0.86);
|
|
945
975
|
}
|
|
946
|
-
.field-float:has(.search-select-control:focus) .float-label,
|
|
947
976
|
.field-float:has(.search-select-control.open) .float-label {
|
|
948
977
|
color: var(--color-primary-ink);
|
|
949
|
-
// The control turns white while
|
|
950
|
-
//
|
|
978
|
+
// The control turns white while open, so the label mask follows (a closed select keeps the
|
|
979
|
+
// default muted mask, matching its muted resting fill, even while it holds focus).
|
|
951
980
|
background: var(--color-surface);
|
|
952
981
|
}
|
|
953
982
|
|
|
@@ -1014,6 +1043,12 @@ $wui-field-error-glyph: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/
|
|
|
1014
1043
|
gap: 4px;
|
|
1015
1044
|
min-width: 0;
|
|
1016
1045
|
}
|
|
1046
|
+
// Inside the flex column these two size to their content, so they stretch to the field instead.
|
|
1047
|
+
.field > wui-rich-text,
|
|
1048
|
+
.field > wui-mention-input {
|
|
1049
|
+
align-self: stretch;
|
|
1050
|
+
width: 100%;
|
|
1051
|
+
}
|
|
1017
1052
|
.field > .label,
|
|
1018
1053
|
.field label {
|
|
1019
1054
|
margin-bottom: 0;
|
package/scss/wui/_data.scss
CHANGED
|
@@ -135,6 +135,10 @@
|
|
|
135
135
|
width: 100%;
|
|
136
136
|
// `.tbl-empty` / `.tbl-empty-plain` inside own the centring and padding.
|
|
137
137
|
}
|
|
138
|
+
// An empty list shows its empty state with no column headers above it.
|
|
139
|
+
.table-wrap:has(> .tbl-empty-block:not([hidden])) > .table-ledger > thead {
|
|
140
|
+
display: none;
|
|
141
|
+
}
|
|
138
142
|
|
|
139
143
|
// ── Table (base) ──
|
|
140
144
|
// The shared data-table look, on the plain `.table` class Bootstrap already
|
|
@@ -292,9 +296,9 @@
|
|
|
292
296
|
// block above) — if it ever needs viewport-relative sizing again, do it without turning
|
|
293
297
|
// the scroll container into a query container.
|
|
294
298
|
}
|
|
295
|
-
// Flush:
|
|
299
|
+
// Flush: the table sits on the content edge on every side, with no inset.
|
|
296
300
|
.table-wrap.wui-table-flush:has(> .table-ledger) {
|
|
297
|
-
padding
|
|
301
|
+
padding: 0;
|
|
298
302
|
}
|
|
299
303
|
.table-ledger {
|
|
300
304
|
border-spacing: 0;
|
|
@@ -1002,6 +1006,14 @@
|
|
|
1002
1006
|
.table-wrap:has(> .table-ledger.wui-table-stack) .table-ledger thead th {
|
|
1003
1007
|
position: static;
|
|
1004
1008
|
}
|
|
1009
|
+
.table-wrap:has(> .table-ledger.wui-table-stack) {
|
|
1010
|
+
overflow-x: clip;
|
|
1011
|
+
}
|
|
1012
|
+
.table-ledger.wui-table-stack > tbody > tr > td {
|
|
1013
|
+
min-width: 0;
|
|
1014
|
+
max-width: 100%;
|
|
1015
|
+
overflow-wrap: anywhere;
|
|
1016
|
+
}
|
|
1005
1017
|
.table-ledger.wui-table-stack,
|
|
1006
1018
|
.table-ledger.wui-table-stack > tbody,
|
|
1007
1019
|
.table-ledger.wui-table-stack > tfoot,
|
package/scss/wui/_display.scss
CHANGED
|
@@ -1317,7 +1317,7 @@ a.info-row:hover .info-value {
|
|
|
1317
1317
|
background: currentColor;
|
|
1318
1318
|
transform: scaleX(0);
|
|
1319
1319
|
transform-origin: left center;
|
|
1320
|
-
transition: transform 0.
|
|
1320
|
+
transition: transform 0.5s var(--wui-ease-out-quart);
|
|
1321
1321
|
}
|
|
1322
1322
|
.wui-link--underline-center .wui-link__label::after {
|
|
1323
1323
|
transform-origin: center;
|
|
@@ -1417,7 +1417,7 @@ a.info-row:hover .info-value {
|
|
|
1417
1417
|
}
|
|
1418
1418
|
.wui-link--static:hover .wui-link__label::after,
|
|
1419
1419
|
.wui-link--static:focus-visible .wui-link__label::after {
|
|
1420
|
-
animation: wui-link-underline-slide 0.
|
|
1420
|
+
animation: wui-link-underline-slide 0.5s var(--wui-ease-out-quart);
|
|
1421
1421
|
}
|
|
1422
1422
|
@keyframes wui-link-underline-slide {
|
|
1423
1423
|
from {
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// The floating filter panel: <wui-filter-popover> with its Filters button, drawn by AppFilterBar
|
|
2
2
|
// `floating` and <app-filter-bar floating>. The element places the panel (anchorOverlay, fixed), so
|
|
3
|
-
// this partial only draws it: a 600 px panel
|
|
4
|
-
//
|
|
3
|
+
// this partial only draws it: a 600 px panel with a head (title and the Keep open switch), facets in
|
|
4
|
+
// two columns, and a foot (the note and Clear all).
|
|
5
5
|
|
|
6
6
|
.wui-filter-popover {
|
|
7
7
|
display: inline-block;
|
|
@@ -29,6 +29,29 @@
|
|
|
29
29
|
display: none;
|
|
30
30
|
}
|
|
31
31
|
|
|
32
|
+
.wui-filter-popover-head {
|
|
33
|
+
display: flex;
|
|
34
|
+
align-items: center;
|
|
35
|
+
justify-content: space-between;
|
|
36
|
+
margin-bottom: 0.75rem;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
.wui-filter-popover-title {
|
|
40
|
+
font-weight: 600;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
.wui-filter-popover-field {
|
|
44
|
+
display: flex;
|
|
45
|
+
flex-direction: column;
|
|
46
|
+
gap: 4px;
|
|
47
|
+
min-width: 0;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
.wui-filter-popover-note {
|
|
51
|
+
font-size: 13px;
|
|
52
|
+
color: var(--color-text-muted);
|
|
53
|
+
}
|
|
54
|
+
|
|
32
55
|
.wui-filter-popover-grid {
|
|
33
56
|
display: grid;
|
|
34
57
|
grid-template-columns: repeat(2, minmax(0, 1fr));
|
|
@@ -53,10 +76,6 @@
|
|
|
53
76
|
border-top: 1px solid var(--color-border);
|
|
54
77
|
}
|
|
55
78
|
|
|
56
|
-
.wui-filter-popover-count {
|
|
57
|
-
font-weight: 600;
|
|
58
|
-
}
|
|
59
|
-
|
|
60
79
|
.wui-filter-popover-more {
|
|
61
80
|
white-space: nowrap;
|
|
62
81
|
cursor: default;
|
package/scss/wui/_flow.scss
CHANGED
|
@@ -1027,6 +1027,10 @@ button.step.done:focus-visible .step-circle {
|
|
|
1027
1027
|
border-top: 1px solid var(--color-border);
|
|
1028
1028
|
background: var(--color-surface);
|
|
1029
1029
|
}
|
|
1030
|
+
// Below md the phone tab bar is fixed at the bottom, so sticky form actions sit on top of it.
|
|
1031
|
+
@media (max-width: 767.98px) {
|
|
1032
|
+
.app-shell:has(> .wui-tabbar) .form-actions-sticky { bottom: calc(var(--wui-tabbar-h) + env(safe-area-inset-bottom)); z-index: calc(var(--wui-z-fixed) + 1); }
|
|
1033
|
+
}
|
|
1030
1034
|
@media (prefers-reduced-motion: reduce) {
|
|
1031
1035
|
.form-actions-sticky {
|
|
1032
1036
|
transition: none;
|
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
// ---- Default ring -----------------------------------------------------------
|
|
12
12
|
// Anything focusable that no partial styles gets the shared ring instead of the browser's.
|
|
13
13
|
// Zero specificity, so every component rule (including a panel's deliberate `outline: none`)
|
|
14
|
-
// still wins. Text fields are left out:
|
|
14
|
+
// still wins. Text fields are left out: their focus cue is their own soft border.
|
|
15
15
|
:where(
|
|
16
16
|
a[href],
|
|
17
17
|
area[href],
|
|
@@ -90,53 +90,23 @@ html:has(.form-actions-sticky) {
|
|
|
90
90
|
scroll-padding-bottom: 96px;
|
|
91
91
|
}
|
|
92
92
|
|
|
93
|
-
// ----
|
|
94
|
-
// A
|
|
95
|
-
//
|
|
96
|
-
// selector repeats its partial's focus rule at the same specificity, so an invalid field's own
|
|
97
|
-
// border colour still wins and only the outer edge is added. The open state of a picker counts:
|
|
98
|
-
// focus has moved into its pop-up, but the field still reads as the one being edited.
|
|
99
|
-
.form-control:focus,
|
|
100
|
-
.form-select:focus,
|
|
101
|
-
.input:focus,
|
|
102
|
-
.select:focus,
|
|
103
|
-
.textarea:focus,
|
|
104
|
-
.search-select-control:focus-within,
|
|
105
|
-
.search-select-control.open,
|
|
106
|
-
.search-select-search:focus-within,
|
|
107
|
-
.datepicker.open .datepicker-trigger,
|
|
108
|
-
.daterange.open .daterange-trigger,
|
|
109
|
-
.timepicker.open .timepicker-trigger,
|
|
110
|
-
.input-affixed:focus-within,
|
|
111
|
-
.input-with-unit:focus-within,
|
|
112
|
-
.num-wrap:focus-within,
|
|
113
|
-
.app-phone-number-input .phone-input-wrap:focus-within,
|
|
114
|
-
.wui-input-group:focus-within,
|
|
115
|
-
.otp-slot.active,
|
|
116
|
-
.dropdown-search-input:focus,
|
|
117
|
-
.file-input:not(.has-files):focus-within,
|
|
118
|
-
.wui-rich-text-shell:focus-within,
|
|
119
|
-
.wui-rich-text-link-input:focus,
|
|
93
|
+
// ---- Workspace switcher trigger ------------------------------------------------
|
|
94
|
+
// A button styled like a field: its soft focus border alone is under 3:1, so keyboard focus
|
|
95
|
+
// adds the outline right outside it. Form fields keep the soft border without an outline.
|
|
120
96
|
.workspace-switcher-trigger:focus-visible,
|
|
121
97
|
.workspace-switcher-trigger[aria-expanded="true"] {
|
|
122
98
|
outline: calc(var(--wui-focus-ring-width) - 1px) solid var(--color-focus-ring);
|
|
123
99
|
outline-offset: 0;
|
|
124
100
|
}
|
|
125
|
-
// A control inside a wrapper that draws the border leaves the edge to the wrapper, and a bare
|
|
126
|
-
// input has no edge.
|
|
127
|
-
.input-bare:focus,
|
|
128
|
-
:is(.input-affixed, .input-with-unit, .num-wrap, .phone-input-wrap, .wui-input-group) :is(.form-control, .input, .textarea, .num-input):focus {
|
|
129
|
-
outline: none;
|
|
130
|
-
}
|
|
131
101
|
|
|
132
102
|
// ---- Active option in a pop-up list --------------------------------------------
|
|
133
|
-
// Search results, the country list,
|
|
103
|
+
// Search results, the country list, mentions and the command
|
|
134
104
|
// palette keep focus in their input and mark the keyboard's row (aria-activedescendant) with
|
|
135
105
|
// the hover tint alone, about 1.2:1. The inset ring makes that row plain; inset, because the
|
|
136
|
-
// list scrolls and would clip an outer ring.
|
|
106
|
+
// list scrolls and would clip an outer ring. Select and autocomplete options draw their own
|
|
107
|
+
// light edge in _choice.scss.
|
|
137
108
|
.wui-topsearch-hit.is-active,
|
|
138
109
|
.country-dropdown-item.is-active,
|
|
139
|
-
.search-select-option.is-active,
|
|
140
110
|
.wui-mention-option.is-active,
|
|
141
111
|
.wui-cmd-item[aria-selected="true"] {
|
|
142
112
|
outline: var(--wui-focus-ring-width) solid var(--color-focus-ring);
|