@webority/theme 0.35.2 → 0.37.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@webority/theme",
3
- "version": "0.35.2",
3
+ "version": "0.37.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/_tokens.scss CHANGED
@@ -41,6 +41,10 @@
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. They clear WCAG 1.4.11's 3:1
45
+ // against the field fill and the surface, so a field's edge is visible without focus.
46
+ --wui-field-border: #80899F;
47
+ --wui-field-border-hover: #687088;
44
48
  --wui-border-light: #EBEDF3;
45
49
  --wui-border-soft: #F4F6F8;
46
50
  // The unfilled part of a progress bar / meter. Darker than every surface it can sit on
@@ -253,6 +257,9 @@
253
257
  --wui-dark-bg-surface: #181b23;
254
258
  --wui-dark-border: #2a2f3a;
255
259
  --wui-dark-border-strong:#3a414f;
260
+ // Field borders on the dark fills: 3:1 or better against the dark field fill and surface.
261
+ --wui-dark-field-border: #6E7586;
262
+ --wui-dark-field-border-hover: #8a90a0;
256
263
  // Lighter than the dark page, surface and muted fill, so the empty track shows on all three.
257
264
  --wui-dark-progress-track: #323845;
258
265
  --wui-dark-text: #e6e8ee;
@@ -18,6 +18,10 @@
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 (3:1 against the field and surface).
22
+ // Decorative card and table borders stay on --color-border.
23
+ --color-field-border: var(--wui-field-border);
24
+ --color-field-border-hover: var(--wui-field-border-hover);
21
25
  --color-progress-track: var(--wui-progress-track);
22
26
  // Label text inside a progress bar fill.
23
27
  --color-progress-label: var(--wui-bg-light);
@@ -32,7 +36,9 @@
32
36
  // A dark-ink glyph such as Bootstrap's .btn-close needs no filter on a light surface.
33
37
  --color-close-filter: none;
34
38
  // The border of a focused / open / active control (input, select, OTP slot, dropzone).
35
- --color-focus-border: var(--wui-primary-30);
39
+ // It is the only focus cue on a field, so it must clear 3:1 against the field and surface
40
+ // (WCAG 1.4.11): the full primary, not a tint.
41
+ --color-focus-border: var(--wui-primary);
36
42
  --color-text-muted: var(--wui-text-2);
37
43
  --color-text-subtle: var(--wui-text-3);
38
44
 
@@ -153,6 +159,8 @@
153
159
  --color-border: var(--wui-dark-border);
154
160
  --color-border-light: color-mix(in srgb, var(--wui-dark-border) 50%, transparent);
155
161
  --color-border-strong: var(--wui-dark-border-strong);
162
+ --color-field-border: var(--wui-dark-field-border);
163
+ --color-field-border-hover: var(--wui-dark-field-border-hover);
156
164
  --color-progress-track: var(--wui-dark-progress-track);
157
165
  // Same near-black as --color-primary-text below: 4.97:1 or better on every bright fill.
158
166
  --color-progress-label: #0F0F0F;
@@ -244,7 +244,7 @@
244
244
  // Not while open — an open control keeps its active (primary) border, not the grey hover one.
245
245
  // Not while invalid — an errored control (aria-invalid mirrored from the host) holds its red
246
246
  // border on hover rather than reverting to the grey hover one.
247
- border-color: var(--color-border-strong);
247
+ border-color: var(--color-field-border-hover);
248
248
  }
249
249
  .search-select-control.open {
250
250
  // White fill + the active/focus border, matching a focused text input (which lifts off the
@@ -414,7 +414,7 @@
414
414
  margin: 4px;
415
415
  padding: 0 12px;
416
416
  height: 40px;
417
- border: 1px solid var(--color-border);
417
+ border: 1px solid var(--color-field-border);
418
418
  border-radius: var(--radius);
419
419
  // AppInput's field model: muted resting fill that lifts to a clean surface on focus.
420
420
  background: var(--color-field-bg);
@@ -426,7 +426,7 @@
426
426
  // Hover and focus follow AppInput, as the phone country search does: a stronger border on hover,
427
427
  // then a clean surface fill + soft primary border on focus.
428
428
  .search-select-search:hover:not(:focus-within) {
429
- border-color: var(--color-border-strong);
429
+ border-color: var(--color-field-border-hover);
430
430
  }
431
431
  .search-select-search:focus-within {
432
432
  background: var(--color-surface);
@@ -581,7 +581,7 @@
581
581
  wui-datepicker:not([aria-invalid="true"]) .datepicker:not(.open) .datepicker-trigger:focus:not(:focus-visible),
582
582
  wui-date-range:not([aria-invalid="true"]) .daterange:not(.open) .daterange-trigger:focus:not(:focus-visible) {
583
583
  background-color: var(--color-field-bg);
584
- border-color: var(--color-border);
584
+ border-color: var(--color-field-border);
585
585
  }
586
586
  // Portalled to <body> and fixed-positioned from JS so a modal's
587
587
  // overflow:hidden can't clip it; top/left come from inline styles. z-index
@@ -1003,7 +1003,7 @@ button.datepicker-month:hover {
1003
1003
  align-items: center;
1004
1004
  width: 100%;
1005
1005
  height: var(--wui-control-height);
1006
- border: 1px solid var(--color-border);
1006
+ border: 1px solid var(--color-field-border);
1007
1007
  // Match AppInput's field-radius ladder exactly (18px md), not the generic --radius (10px).
1008
1008
  border-radius: var(--wui-field-radius);
1009
1009
  // Match AppInput exactly: muted resting fill that lifts to a clean surface on focus.
@@ -1024,7 +1024,7 @@ button.datepicker-month:hover {
1024
1024
  wui-phone:not([aria-invalid="true"])
1025
1025
  .phone-input-wrap:hover:not(:focus-within):not(:has(> .input.error)):not(:has(> .input.is-invalid)):not(:has(> .input[aria-invalid="true"])) {
1026
1026
  background: var(--color-field-bg);
1027
- border-color: var(--color-border-strong);
1027
+ border-color: var(--color-field-border-hover);
1028
1028
  }
1029
1029
  // Match AppInput's focus: clean surface fill + a soft primary border.
1030
1030
  .app-phone-number-input .phone-input-wrap:focus-within {
@@ -1238,7 +1238,7 @@ wui-timepicker[aria-invalid="true"] .timepicker.open .timepicker-trigger {
1238
1238
  width: 100%;
1239
1239
  height: 40px;
1240
1240
  padding: 0 12px 0 36px;
1241
- border: 1px solid var(--color-border);
1241
+ border: 1px solid var(--color-field-border);
1242
1242
  border-radius: var(--radius);
1243
1243
  font-size: 14px;
1244
1244
  color: var(--color-text);
@@ -1248,7 +1248,7 @@ wui-timepicker[aria-invalid="true"] .timepicker.open .timepicker-trigger {
1248
1248
  }
1249
1249
  .dropdown-search-input:hover:not(:focus) {
1250
1250
  background: var(--color-field-bg);
1251
- border-color: var(--color-border-strong);
1251
+ border-color: var(--color-field-border-hover);
1252
1252
  }
1253
1253
  // Border is the focus cue everywhere else in the system — no glow ring here.
1254
1254
  // AppInput's exact focus: clean surface fill + its soft primary border token.
@@ -301,7 +301,7 @@
301
301
  height: var(--wui-control-height);
302
302
  padding: 12px;
303
303
  border-radius: var(--wui-field-radius);
304
- border: 1px solid var(--color-border);
304
+ border: 1px solid var(--color-field-border);
305
305
  background: var(--color-field-bg);
306
306
  font-size: 14px;
307
307
  line-height: 1.5;
@@ -359,7 +359,7 @@ textarea.form-control.autosize {
359
359
  .select:hover:not(:focus):not(:disabled):not([aria-invalid="true"]):not(.is-invalid):not(.error),
360
360
  .textarea:hover:not(:focus):not(:disabled):not([readonly]):not([aria-invalid="true"]):not(.is-invalid):not(.error) {
361
361
  background-color: var(--color-field-bg);
362
- border-color: var(--color-border-strong);
362
+ border-color: var(--color-field-border-hover);
363
363
  }
364
364
  .form-control:focus,
365
365
  .form-select:focus,
@@ -462,7 +462,10 @@ textarea.form-control.autosize {
462
462
  .input[readonly],
463
463
  .select[readonly],
464
464
  .textarea[readonly] {
465
- background: var(--color-bg-subtle);
465
+ // The light decorative border and the muted fill, never the field border, so a read-only
466
+ // value never looks typeable.
467
+ background: var(--color-bg-muted);
468
+ border-color: var(--color-border);
466
469
  color: var(--color-text-muted);
467
470
  cursor: default;
468
471
  }
@@ -810,7 +813,7 @@ $wui-field-error-glyph: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/
810
813
  display: flex;
811
814
  align-items: stretch;
812
815
  width: 100%;
813
- border: 1px solid var(--color-border);
816
+ border: 1px solid var(--color-field-border);
814
817
  border-radius: var(--wui-field-radius);
815
818
  // Soft-filled at rest, white on focus — one shared fill for the whole control so the
816
819
  // prefix/suffix and the input body are always the SAME colour combination as the field.
@@ -820,7 +823,7 @@ $wui-field-error-glyph: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/
820
823
  background-color 0.12s ease;
821
824
  }
822
825
  .input-affixed:hover:not(:focus-within) {
823
- border-color: var(--color-border-strong);
826
+ border-color: var(--color-field-border-hover);
824
827
  }
825
828
  .input-affixed:focus-within {
826
829
  background: var(--color-surface);
@@ -1098,7 +1101,7 @@ wui-select[compact] {
1098
1101
  .input-with-unit {
1099
1102
  display: flex;
1100
1103
  align-items: stretch;
1101
- border: 1px solid var(--color-border);
1104
+ border: 1px solid var(--color-field-border);
1102
1105
  border-radius: var(--wui-field-radius);
1103
1106
  // Soft-filled at rest, white on focus — the numeric input and its unit pill share the
1104
1107
  // one wrapper fill, same colour combination as every other field.
@@ -1109,7 +1112,7 @@ wui-select[compact] {
1109
1112
  overflow: hidden;
1110
1113
  }
1111
1114
  .input-with-unit:hover:not(:focus-within) {
1112
- border-color: var(--color-border-strong);
1115
+ border-color: var(--color-field-border-hover);
1113
1116
  }
1114
1117
  .input-with-unit:focus-within {
1115
1118
  background: var(--color-surface);
@@ -1168,7 +1171,7 @@ wui-select[compact] {
1168
1171
  .num-wrap {
1169
1172
  display: flex;
1170
1173
  align-items: stretch;
1171
- border: 1px solid var(--color-border);
1174
+ border: 1px solid var(--color-field-border);
1172
1175
  // Field radius ladder (matches AppInput): md 18px; sm/lg step below.
1173
1176
  border-radius: var(--wui-field-radius);
1174
1177
  // Match AppInput exactly: muted resting fill that lifts to a clean surface on focus.
@@ -1182,7 +1185,7 @@ wui-select[compact] {
1182
1185
  // reverting to the grey hover one (matches AppInput, whose hover rule excludes the error state).
1183
1186
  .num-wrap:hover:not(:focus-within):not(:has(.num-input.error)):not(:has(.num-input.is-invalid)):not(:has(.num-input[aria-invalid="true"])) {
1184
1187
  background: var(--color-field-bg);
1185
- border-color: var(--color-border-strong);
1188
+ border-color: var(--color-field-border-hover);
1186
1189
  }
1187
1190
  .num-wrap:focus-within {
1188
1191
  background: var(--color-surface);
@@ -1547,7 +1550,7 @@ wui-otp {
1547
1550
  height: var(--wui-control-height);
1548
1551
  // Colour model matches AppInput exactly: muted resting fill + soft border, same
1549
1552
  // field radius. State rules below mirror AppInput's hover/focus/invalid/disabled.
1550
- border: 1px solid var(--color-border);
1553
+ border: 1px solid var(--color-field-border);
1551
1554
  border-radius: var(--wui-field-radius);
1552
1555
  background: var(--color-field-bg);
1553
1556
  font-size: 18px;
@@ -1561,7 +1564,7 @@ wui-otp {
1561
1564
  // Hover — the whole field lifts its border to strong (AppInput hover), but not while a
1562
1565
  // slot is active (focus wins) or the field is disabled.
1563
1566
  .otp:hover:not(:focus-within):not(:has(.otp-field:disabled)) .otp-slot {
1564
- border-color: var(--color-border-strong);
1567
+ border-color: var(--color-field-border-hover);
1565
1568
  }
1566
1569
  // The caret cannot be seen through a transparent input, so the ACTIVE box carries the
1567
1570
  // affordance instead — derived from selectionStart. Matches AppInput's focus: clean
@@ -8,14 +8,24 @@
8
8
  // above it that stays at the top of the viewport while the page scrolls. The board scrolls inside
9
9
  // itself, so the page never scrolls sideways.
10
10
  //
11
+ // A board with the `fill` attribute (React `fill`, Razor fill="true") fits the screen from md up:
12
+ // the board is the viewport's height less --wui-kanban-fill-offset, every column stretches to it,
13
+ // and each column's card list scrolls inside the column under its fixed head. A long column then
14
+ // never scrolls the page, so every column stays under the pointer while a card is carried. Below
15
+ // md a fill board keeps the phone layout unchanged: the page scrolls, which a thumb expects and
16
+ // which keeps the sticky switcher, rather than a short scroll box nested inside a short screen.
17
+ //
11
18
  // Sizing variables, set on .wui-kanban (or any ancestor) to override:
12
19
  // --wui-kanban-column-width basis width of an open column (288px)
13
20
  // --wui-kanban-collapsed-width width of a collapsed column strip (56px)
14
21
  // --wui-kanban-gap space between columns (16px)
15
22
  // --wui-kanban-column-min-height least height of a column, so short columns still read as
16
- // lanes (560px; not applied in the phone one-column mode)
23
+ // lanes (560px; not applied in the phone one-column mode or on
24
+ // a fill board)
17
25
  // --wui-kanban-sticky-top where the phone switcher sticks (0, or under the app shell's
18
26
  // topbar inside .app-shell)
27
+ // --wui-kanban-fill-offset the height a fill board leaves for the page around it: the
28
+ // topbar, the page head, filters (240px)
19
29
  //
20
30
  // Part of the wui-components layer, see _wui-components.scss for load order.
21
31
 
@@ -25,6 +35,7 @@
25
35
  --wui-kanban-gap: 16px;
26
36
  --wui-kanban-column-min-height: 560px;
27
37
  --wui-kanban-sticky-top: 0px;
38
+ --wui-kanban-fill-offset: 240px;
28
39
  display: block;
29
40
  position: relative;
30
41
  min-width: 0;
@@ -466,6 +477,30 @@
466
477
  }
467
478
  }
468
479
 
480
+ // ── Fill: the board fits the screen and each column scrolls inside itself (md and up) ──
481
+ @media (min-width: 768px) {
482
+ .wui-kanban[fill] .wui-kanban-board {
483
+ align-items: stretch;
484
+ height: calc(100dvh - var(--wui-kanban-fill-offset));
485
+ }
486
+ .wui-kanban[fill] .wui-kanban-column {
487
+ min-height: 0;
488
+ }
489
+ // 4px off the head and onto the list keeps the spacing, and leaves room inside the scroll box
490
+ // for the 2px focus ring of the first card.
491
+ .wui-kanban[fill] .wui-kanban-column:not([data-collapsed]) .wui-kanban-head {
492
+ flex: none;
493
+ padding-bottom: 4px;
494
+ }
495
+ .wui-kanban[fill] .wui-kanban-body {
496
+ flex: 1 1 auto;
497
+ min-height: 0;
498
+ overflow-y: auto;
499
+ overscroll-behavior-y: contain;
500
+ padding-top: 4px;
501
+ }
502
+ }
503
+
469
504
  // ── Phone column switcher and swipe hint (hidden from md up) ──
470
505
  .wui-kanban-switcher,
471
506
  .wui-kanban-hint {
@@ -537,7 +537,7 @@
537
537
  width: 100%;
538
538
  border-radius: var(--radius-lg);
539
539
  corner-shape: squircle;
540
- border: 1px solid var(--color-border);
540
+ border: 1px solid var(--color-field-border);
541
541
  // Match AppInput: muted resting fill, lifts to a clean surface on focus.
542
542
  background: var(--color-bg-muted);
543
543
  overflow: hidden;
@@ -548,7 +548,7 @@
548
548
  }
549
549
  .file-input:hover:not(:focus-within) {
550
550
  background: var(--color-bg-muted);
551
- border-color: var(--color-border-strong);
551
+ border-color: var(--color-field-border-hover);
552
552
  }
553
553
  .file-input:focus-within {
554
554
  background: var(--color-surface);
@@ -600,14 +600,14 @@
600
600
  // Match AppInput: muted resting fill + resting border (dashed kept as the dropzone
601
601
  // affordance), border-strong on hover, primary border + surface lift on focus/drag.
602
602
  background: var(--color-bg-muted);
603
- border: 1px dashed var(--color-border);
603
+ border: 1px dashed var(--color-field-border);
604
604
  transition:
605
605
  border-color 0.15s ease,
606
606
  background-color 0.15s ease;
607
607
  }
608
608
  // Hover mirrors AppInput hover — border-strong, fill unchanged.
609
609
  .file-input.file-input-tall:hover:not(:focus-within):not(.dragging) {
610
- border-color: var(--color-border-strong);
610
+ border-color: var(--color-field-border-hover);
611
611
  background: var(--color-bg-muted);
612
612
  }
613
613
  // Focus and drag-over mirror AppInput focus — soft primary border, lift onto a clean surface.
@@ -877,7 +877,7 @@
877
877
  }
878
878
  .file-input.file-input-tall[aria-disabled="true"]:hover:not(:focus-within):not(.dragging) {
879
879
  background: var(--color-bg-muted);
880
- border-color: var(--color-border);
880
+ border-color: var(--color-field-border);
881
881
  }
882
882
  .wui-file-busy {
883
883
  position: absolute;
@@ -21,7 +21,7 @@ wui-rich-text:not(:defined) > textarea {
21
21
  display: flex;
22
22
  flex-direction: column;
23
23
  width: 100%;
24
- border: 1px solid var(--color-border);
24
+ border: 1px solid var(--color-field-border);
25
25
  border-radius: var(--wui-field-radius);
26
26
  background: var(--color-field-bg);
27
27
  color: var(--color-text);
@@ -33,7 +33,7 @@ wui-rich-text:not(:defined) > textarea {
33
33
  background-color 0.12s;
34
34
  }
35
35
  .wui-rich-text-shell:hover {
36
- border-color: var(--color-border-strong);
36
+ border-color: var(--color-field-border-hover);
37
37
  }
38
38
  .wui-rich-text-shell:focus-within {
39
39
  background: var(--color-surface);
@@ -547,12 +547,25 @@
547
547
  .app-nav-sub.expanded {
548
548
  grid-template-rows: 1fr;
549
549
  }
550
- // Child link indented under the parent label (icon slot + gap), smaller, no icon.
551
- .app-nav-subitem {
550
+ // Child row indented under the parent label (icon slot + gap), smaller, no icon. A nested
551
+ // group's toggle is a sub-item too (`.app-nav-item.app-nav-subitem` outranks the group button's
552
+ // `font: inherit`), and each level below the first adds a 16px step through --wui-nav-depth
553
+ // (set on the row from depth 2), so a nested group's children sit visibly under it.
554
+ .app-nav-item.app-nav-subitem {
552
555
  min-height: 38px;
553
- padding-left: 46px;
556
+ padding-left: calc(46px + (var(--wui-nav-depth, 1) - 1) * 16px);
554
557
  font-size: 13px;
555
558
  }
559
+ // A sub-item label wraps to two lines before it is cut, so a long child label stays readable.
560
+ // Top-level labels keep the single-line ellipsis.
561
+ .app-nav-subitem .app-nav-text {
562
+ display: -webkit-box;
563
+ -webkit-box-orient: vertical;
564
+ -webkit-line-clamp: 2;
565
+ line-clamp: 2;
566
+ white-space: normal;
567
+ overflow-wrap: anywhere;
568
+ }
556
569
 
557
570
  // ── Collapsed rail: label tooltip ────────────────────────────────────────────
558
571
  // The rail hides labels, so surface each on hover/focus as a chip to the right —