@webority/theme 0.68.2 → 0.69.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/purge-safelist.json +8 -1
- package/dist/webority-theme.css +1 -1
- package/package.json +1 -1
- package/scss/_components.scss +46 -19
- 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/_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.69.0",
|
|
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
|
@@ -544,9 +544,9 @@
|
|
|
544
544
|
}
|
|
545
545
|
|
|
546
546
|
// ---- Ghost button: the tertiary action --------------------------------
|
|
547
|
-
// Brand-coloured
|
|
548
|
-
//
|
|
549
|
-
//
|
|
547
|
+
// Brand-coloured text with no box at rest and no underline. Hover lays a light grey fill behind
|
|
548
|
+
// it, pressing deepens that fill a step, and keyboard focus draws the shared ring. No sheen sweep,
|
|
549
|
+
// no lift or press nudge, and no grey box when disabled or loading.
|
|
550
550
|
// AppButton's `ghost` variant emits this class on both surfaces.
|
|
551
551
|
//
|
|
552
552
|
// No .btn-ghost-sm/-lg rungs: it is an ordinary .btn variant and sizes with Bootstrap's
|
|
@@ -555,32 +555,24 @@
|
|
|
555
555
|
--bs-btn-color: var(--color-primary-ink);
|
|
556
556
|
--bs-btn-bg: transparent;
|
|
557
557
|
--bs-btn-border-color: transparent;
|
|
558
|
-
--bs-btn-hover-color:
|
|
559
|
-
--bs-btn-hover-bg:
|
|
558
|
+
--bs-btn-hover-color: var(--color-primary-ink);
|
|
559
|
+
--bs-btn-hover-bg: var(--color-bg-muted);
|
|
560
560
|
--bs-btn-hover-border-color: transparent;
|
|
561
|
-
--bs-btn-active-color:
|
|
562
|
-
--bs-btn-active-bg:
|
|
561
|
+
--bs-btn-active-color: var(--color-primary-ink);
|
|
562
|
+
--bs-btn-active-bg: color-mix(in srgb, var(--color-bg-muted) 92%, var(--color-text));
|
|
563
563
|
--bs-btn-active-border-color: transparent;
|
|
564
564
|
--bs-btn-disabled-color: var(--color-primary-ink);
|
|
565
565
|
--bs-btn-disabled-bg: transparent;
|
|
566
566
|
--bs-btn-disabled-border-color: transparent;
|
|
567
|
-
text-decoration: underline;
|
|
568
|
-
text-decoration-thickness: 1px;
|
|
569
|
-
text-underline-offset: 3px;
|
|
570
567
|
}
|
|
571
|
-
// Bootstrap's
|
|
572
|
-
|
|
568
|
+
// Bootstrap's link-style buttons can carry an underline; the ghost never does.
|
|
569
|
+
.btn.btn-ghost,
|
|
573
570
|
.btn.btn-ghost:hover,
|
|
574
571
|
.btn.btn-ghost:active,
|
|
575
572
|
.btn.btn-ghost:focus-visible {
|
|
576
|
-
text-decoration:
|
|
577
|
-
text-decoration-thickness: 1px;
|
|
573
|
+
text-decoration: none;
|
|
578
574
|
}
|
|
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.
|
|
575
|
+
// No fill at rest, so keyboard focus gets the outline ring instead.
|
|
584
576
|
.btn.btn-ghost:focus-visible {
|
|
585
577
|
outline: var(--wui-focus-ring-width) solid var(--color-focus-ring);
|
|
586
578
|
outline-offset: var(--wui-focus-ring-offset);
|
|
@@ -634,6 +626,41 @@
|
|
|
634
626
|
margin-left: auto;
|
|
635
627
|
}
|
|
636
628
|
|
|
629
|
+
// Close button (React `onClose` / Razor `dismissible`): the last control in the header, pushed
|
|
630
|
+
// to the end, or just after the header actions when there are some.
|
|
631
|
+
.card-header:has(.card-close) {
|
|
632
|
+
display: flex;
|
|
633
|
+
align-items: center;
|
|
634
|
+
}
|
|
635
|
+
.card-close {
|
|
636
|
+
display: inline-grid;
|
|
637
|
+
flex-shrink: 0;
|
|
638
|
+
place-items: center;
|
|
639
|
+
width: 32px;
|
|
640
|
+
height: 32px;
|
|
641
|
+
margin: -6px -6px -6px auto;
|
|
642
|
+
padding: 0;
|
|
643
|
+
border: 0;
|
|
644
|
+
border-radius: var(--radius);
|
|
645
|
+
background: transparent;
|
|
646
|
+
color: var(--color-text-muted);
|
|
647
|
+
cursor: pointer;
|
|
648
|
+
transition:
|
|
649
|
+
background-color 0.12s ease,
|
|
650
|
+
color 0.12s ease;
|
|
651
|
+
}
|
|
652
|
+
.card-header-actions + .card-close {
|
|
653
|
+
margin-left: 0.25rem;
|
|
654
|
+
}
|
|
655
|
+
.card-close:hover {
|
|
656
|
+
background: var(--color-bg-muted);
|
|
657
|
+
color: var(--color-text);
|
|
658
|
+
}
|
|
659
|
+
.card-close:focus-visible {
|
|
660
|
+
outline: var(--wui-focus-ring-width) solid var(--color-focus-ring);
|
|
661
|
+
outline-offset: var(--wui-focus-ring-offset);
|
|
662
|
+
}
|
|
663
|
+
|
|
637
664
|
// ---- Inputs ---------------------------------------------------------
|
|
638
665
|
// Field chrome (height, hover, focus, invalid) lives in wui/_controls.scss on
|
|
639
666
|
// `.form-control` / `.form-select` with `.input` as an alias — one skin, not two
|
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);
|
package/scss/wui/_media.scss
CHANGED
|
@@ -723,6 +723,11 @@
|
|
|
723
723
|
padding: 0;
|
|
724
724
|
font-size: 12px;
|
|
725
725
|
}
|
|
726
|
+
.file-input.file-input-tall .file-input-text {
|
|
727
|
+
white-space: normal;
|
|
728
|
+
text-align: center;
|
|
729
|
+
overflow-wrap: anywhere;
|
|
730
|
+
}
|
|
726
731
|
|
|
727
732
|
// Large preview shown once an image exists; hover reveals a "Change image"
|
|
728
733
|
// overlay.
|
|
@@ -62,7 +62,7 @@
|
|
|
62
62
|
padding: 0;
|
|
63
63
|
}
|
|
64
64
|
.modal.wui-remote-drawer .modal-dialog.wui-remote-drawer-dialog {
|
|
65
|
-
margin: 0;
|
|
65
|
+
margin: 0 0 0 auto;
|
|
66
66
|
max-width: none;
|
|
67
67
|
width: min(var(--wui-modal-max-width, 480px), 100%);
|
|
68
68
|
height: 100%;
|
|
@@ -87,3 +87,39 @@
|
|
|
87
87
|
transition: none;
|
|
88
88
|
}
|
|
89
89
|
}
|
|
90
|
+
|
|
91
|
+
// On a phone the drawer rises from the bottom edge as a sheet instead of sliding in from the end.
|
|
92
|
+
@media (max-width: 767.98px) {
|
|
93
|
+
.modal.wui-remote-drawer .modal-dialog.wui-remote-drawer-dialog {
|
|
94
|
+
position: absolute;
|
|
95
|
+
inset: auto 0 0 0;
|
|
96
|
+
width: 100%;
|
|
97
|
+
height: auto;
|
|
98
|
+
max-height: 90dvh;
|
|
99
|
+
margin: 0;
|
|
100
|
+
transform: translateY(100%);
|
|
101
|
+
}
|
|
102
|
+
.modal.wui-remote-drawer.show .modal-dialog.wui-remote-drawer-dialog {
|
|
103
|
+
transform: none;
|
|
104
|
+
}
|
|
105
|
+
.modal.wui-remote-drawer .wui-remote-drawer-dialog .wui-modal {
|
|
106
|
+
height: auto;
|
|
107
|
+
max-height: 90dvh;
|
|
108
|
+
border-radius: var(--wui-radius-lg) var(--wui-radius-lg) 0 0;
|
|
109
|
+
}
|
|
110
|
+
.modal.wui-remote-drawer .wui-remote-drawer-dialog .wui-modal::before {
|
|
111
|
+
content: "";
|
|
112
|
+
display: block;
|
|
113
|
+
width: 36px;
|
|
114
|
+
height: 4px;
|
|
115
|
+
margin: 8px auto 0;
|
|
116
|
+
border-radius: 2px;
|
|
117
|
+
background: var(--color-border);
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
// A side panel dims the page behind it without blurring it, so the list stays readable.
|
|
122
|
+
body:has(> .modal.wui-remote-drawer.show) > .modal-backdrop {
|
|
123
|
+
backdrop-filter: none;
|
|
124
|
+
-webkit-backdrop-filter: none;
|
|
125
|
+
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
|
-
// ── Bar ── (`size="bar"`): one 40px header row. The breadcrumbs lead as an inline prefix
|
|
2
|
-
// title
|
|
3
|
-
// Below md the row wraps, so the actions drop under
|
|
1
|
+
// ── Bar ── (`size="bar"`): one 40px header row. The breadcrumbs lead as an inline prefix. The
|
|
2
|
+
// title keeps its content width and a spacer takes the spare width, so the views follow the title
|
|
3
|
+
// and the tools and actions sit at the far end. Below md the row wraps, so the actions drop under
|
|
4
|
+
// the title instead of overlapping it.
|
|
4
5
|
// Selectors name `.page-header.page-header-bar` because `.page-header-bar` alone is also the
|
|
5
6
|
// inner row of every other size.
|
|
6
7
|
.page-header.page-header-bar {
|
|
@@ -10,27 +11,58 @@
|
|
|
10
11
|
// The bar sits 4px above the first content block.
|
|
11
12
|
margin-bottom: 4px;
|
|
12
13
|
}
|
|
14
|
+
[data-density="compact"] .app-content:has(.page-header.page-header-bar) {
|
|
15
|
+
--app-content-padding-top: 8px;
|
|
16
|
+
}
|
|
13
17
|
.page-header.page-header-bar .page-title {
|
|
14
|
-
flex:
|
|
15
|
-
min-width:
|
|
18
|
+
flex: 0 1 auto;
|
|
19
|
+
min-width: min(12ch, max-content);
|
|
16
20
|
font-size: 20px;
|
|
17
21
|
overflow: hidden;
|
|
18
22
|
text-overflow: ellipsis;
|
|
19
23
|
white-space: nowrap;
|
|
20
24
|
}
|
|
25
|
+
.page-header.page-header-bar:has(> .page-header-bar-views) .page-title {
|
|
26
|
+
max-width: 280px;
|
|
27
|
+
}
|
|
21
28
|
.page-header.page-header-bar .page-status,
|
|
22
|
-
.page-header-bar-views,
|
|
23
|
-
.page-header-bar-meta,
|
|
24
29
|
.page-header-bar-tools,
|
|
30
|
+
.page-header-bar-meta,
|
|
25
31
|
.page-header.page-header-bar .page-actions {
|
|
26
32
|
flex: none;
|
|
27
33
|
}
|
|
34
|
+
.page-header-bar-views {
|
|
35
|
+
flex: 0 1 auto;
|
|
36
|
+
min-width: 0;
|
|
37
|
+
}
|
|
38
|
+
.page-header-bar-spacer {
|
|
39
|
+
flex: 1 1 0;
|
|
40
|
+
min-width: 0;
|
|
41
|
+
}
|
|
28
42
|
.page-header-bar-crumbs {
|
|
29
43
|
flex: none;
|
|
30
44
|
font-size: 13px;
|
|
31
45
|
color: var(--color-text-muted);
|
|
32
46
|
white-space: nowrap;
|
|
33
47
|
}
|
|
48
|
+
.page-header-bar-crumbs .breadcrumb {
|
|
49
|
+
--bs-breadcrumb-divider: "/";
|
|
50
|
+
margin: 0;
|
|
51
|
+
flex-wrap: nowrap;
|
|
52
|
+
}
|
|
53
|
+
.page-header-bar-crumbs .breadcrumb-item,
|
|
54
|
+
.page-header-bar-crumbs .breadcrumb-item a {
|
|
55
|
+
color: var(--color-text-muted);
|
|
56
|
+
text-decoration: none;
|
|
57
|
+
}
|
|
58
|
+
.page-header-bar-crumbs .breadcrumb-item a:hover {
|
|
59
|
+
text-decoration: underline;
|
|
60
|
+
}
|
|
61
|
+
.page-header-bar-crumbs .breadcrumb-item:last-child::after {
|
|
62
|
+
content: "/";
|
|
63
|
+
padding-inline-start: var(--bs-breadcrumb-item-padding-x, 0.5rem);
|
|
64
|
+
color: var(--color-text-muted);
|
|
65
|
+
}
|
|
34
66
|
.page-header-bar-views,
|
|
35
67
|
.page-header-bar-tools,
|
|
36
68
|
.page-header-bar-bulk {
|
|
@@ -51,6 +83,9 @@
|
|
|
51
83
|
display: none;
|
|
52
84
|
}
|
|
53
85
|
.page-header-bar-meta {
|
|
86
|
+
display: flex;
|
|
87
|
+
align-items: center;
|
|
88
|
+
column-gap: 16px;
|
|
54
89
|
font-size: 13px;
|
|
55
90
|
color: var(--color-text-muted);
|
|
56
91
|
white-space: nowrap;
|
|
@@ -63,6 +98,9 @@
|
|
|
63
98
|
}
|
|
64
99
|
.page-header.wui-page-header-meta-below > .page-header-bar-meta {
|
|
65
100
|
flex: 0 0 100%;
|
|
101
|
+
flex-wrap: wrap;
|
|
102
|
+
white-space: normal;
|
|
103
|
+
column-gap: 16px;
|
|
66
104
|
line-height: 16px;
|
|
67
105
|
}
|
|
68
106
|
|
|
@@ -74,4 +112,22 @@
|
|
|
74
112
|
.page-header.page-header-bar .page-title {
|
|
75
113
|
flex: 0 1 auto;
|
|
76
114
|
}
|
|
115
|
+
.page-header-bar-crumbs {
|
|
116
|
+
display: none;
|
|
117
|
+
}
|
|
118
|
+
.page-header-bar-views {
|
|
119
|
+
order: 5;
|
|
120
|
+
flex: 1 1 100%;
|
|
121
|
+
min-width: 0;
|
|
122
|
+
}
|
|
123
|
+
.page-header-bar-meta {
|
|
124
|
+
order: 6;
|
|
125
|
+
flex: 1 1 100%;
|
|
126
|
+
flex-wrap: wrap;
|
|
127
|
+
white-space: normal;
|
|
128
|
+
}
|
|
129
|
+
.page-header-bar-tools {
|
|
130
|
+
flex-wrap: wrap;
|
|
131
|
+
min-width: 0;
|
|
132
|
+
}
|
|
77
133
|
}
|