@webority/theme 0.18.1 → 0.20.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.18.1",
3
+ "version": "0.20.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": {
@@ -51,6 +51,7 @@
51
51
  @import "wui/dock";
52
52
  @import "wui/consent-banner";
53
53
  @import "wui/toolbar";
54
+ @import "wui/theme-toggle";
54
55
  // After `overlays`: anchors the shared modal shell to the top when it holds the palette card.
55
56
  @import "wui/command-palette";
56
57
  // Last: restates the settled state of menus other partials style, so it must win.
@@ -993,11 +993,15 @@ $wui-field-error-glyph: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/
993
993
  // flush against the label.
994
994
  margin-inline-start: 0;
995
995
  }
996
+ // min-width: 0 lets a field shrink to its column in any flex or grid parent; left at auto, a
997
+ // control sized by its content (chips, a date range, OTP boxes) pushes the field, and the page,
998
+ // wider than a phone screen.
996
999
  .field {
997
1000
  display: flex;
998
1001
  flex-direction: column;
999
1002
  align-items: flex-start;
1000
1003
  gap: 4px;
1004
+ min-width: 0;
1001
1005
  }
1002
1006
  .field > .label,
1003
1007
  .field label {
@@ -1012,12 +1016,8 @@ $wui-field-error-glyph: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/
1012
1016
  .field-row-3 {
1013
1017
  grid-template-columns: 1fr 1fr 1fr;
1014
1018
  }
1015
- // min-width:0 — grid/flex items default to min-width:auto, which lets a long
1016
- // control (e.g. a search-select label) push its track past 1fr and defeat
1017
- // text truncation. Reset to 0 so the ellipsis engages instead of overflowing.
1018
1019
  .field-row .field {
1019
1020
  margin-bottom: 0;
1020
- min-width: 0;
1021
1021
  }
1022
1022
  // Controls fill their field even though .field aligns children flex-start.
1023
1023
  //
@@ -1050,6 +1050,7 @@ wui-date-range {
1050
1050
  wui-select[compact] {
1051
1051
  display: inline-block;
1052
1052
  width: auto;
1053
+ max-width: 100%;
1053
1054
  }
1054
1055
  .field > wui-select,
1055
1056
  .field > wui-country-select,
@@ -1496,10 +1497,12 @@ wui-select[compact] {
1496
1497
  // letting the segments do the drawing.
1497
1498
  wui-otp {
1498
1499
  display: block;
1500
+ max-width: 100%;
1499
1501
  }
1500
1502
  .otp {
1501
1503
  position: relative;
1502
1504
  display: inline-flex;
1505
+ max-width: 100%;
1503
1506
  }
1504
1507
  .otp-field {
1505
1508
  position: absolute;
@@ -1522,15 +1525,25 @@ wui-otp {
1522
1525
  .otp-field:disabled {
1523
1526
  cursor: not-allowed;
1524
1527
  }
1528
+ // The boxes narrow together (to 22px, still room for an 18px digit) rather than push past a
1529
+ // phone screen; 8 boxes at 44px need 408px.
1525
1530
  .otp-slots {
1526
1531
  display: inline-flex;
1527
1532
  gap: 8px;
1533
+ min-width: 0;
1534
+ }
1535
+ @include media-breakpoint-down(sm) {
1536
+ .otp-slots {
1537
+ gap: 4px;
1538
+ }
1528
1539
  }
1529
1540
  .otp-slot {
1530
1541
  display: inline-flex;
1531
1542
  align-items: center;
1532
1543
  justify-content: center;
1533
1544
  width: 44px;
1545
+ min-width: 22px;
1546
+ flex: 0 1 auto;
1534
1547
  height: var(--wui-control-height);
1535
1548
  // Colour model matches AppInput exactly: muted resting fill + soft border, same
1536
1549
  // field radius. State rules below mirror AppInput's hover/focus/invalid/disabled.
@@ -542,6 +542,8 @@
542
542
  }
543
543
  .tpg-nav {
544
544
  display: inline-flex;
545
+ flex-wrap: wrap;
546
+ justify-content: flex-end;
545
547
  align-items: center;
546
548
  gap: 4px;
547
549
  }
@@ -624,6 +626,13 @@
624
626
  display: flex;
625
627
  align-items: center;
626
628
  justify-content: center;
629
+ max-width: 100%;
630
+ }
631
+ // Many pages wrap onto a second row rather than push past a phone screen.
632
+ .pagination-nav .pagination {
633
+ flex-wrap: wrap;
634
+ justify-content: center;
635
+ row-gap: 4px;
627
636
  }
628
637
  .pagination-md {
629
638
  // Default Bootstrap pagination size (no-op, but explicit for parallelism with sm)
@@ -654,6 +663,13 @@
654
663
  scrollbar-width: thin;
655
664
  scrollbar-color: var(--color-border-strong) transparent;
656
665
  }
666
+ // Visually hidden text (a header's screen-reader label) is absolutely positioned at its static
667
+ // place. Its containing block sits outside the scroll box, so on a wide table it escaped the
668
+ // clipping and widened the page. Pin it to the corner of that containing block instead.
669
+ .table-wrap .visually-hidden:not(.visually-hidden-focusable) {
670
+ top: 0;
671
+ left: 0;
672
+ }
657
673
  .table-wrap::-webkit-scrollbar {
658
674
  height: 6px;
659
675
  }
@@ -743,6 +743,7 @@ a.info-row:hover .info-value {
743
743
  font-size: 14px;
744
744
  font-weight: 600;
745
745
  color: var(--color-text);
746
+ overflow-wrap: anywhere;
746
747
  }
747
748
  // Wraps rather than truncates: an audit line must never hide half the timestamp.
748
749
  .timeline-meta {
@@ -146,6 +146,9 @@
146
146
  display: flex;
147
147
  align-items: center;
148
148
  gap: 4px;
149
+ // Fits its parent so the tab strip scrolls inside it rather than widening the page.
150
+ min-width: 0;
151
+ max-width: 100%;
149
152
  }
150
153
 
151
154
  .app-tabs {
@@ -394,6 +397,11 @@
394
397
 
395
398
  // Wrapper for a self-contained tabs widget (strip + panels), as <app-tabs>
396
399
  // renders server-side. React composes the strip and its panels itself.
400
+ // The Razor root: fits its parent so the tab strip inside scrolls instead of widening the page.
401
+ .app-tabs-group {
402
+ min-width: 0;
403
+ max-width: 100%;
404
+ }
397
405
  .app-tabs-group > .tab-content {
398
406
  padding-top: 16px;
399
407
  }
@@ -0,0 +1,61 @@
1
+ // AppThemeToggle / <app-theme-toggle>: the light/dark switch and its page-wide transition.
2
+ //
3
+ // The button is the .u-action disc (see _data.scss). While a switch runs, the page is a View
4
+ // Transition: the old theme is a frozen snapshot and the new theme is clipped to the effect's
5
+ // moving shape (transitionTheme in @webority/ui-elements writes the keyframes). The rules below
6
+ // are scoped to the class the engine sets on <html> for the length of one switch, so an
7
+ // ordinary page transition elsewhere keeps the browser's defaults.
8
+ html.wui-theme-switching::view-transition-old(root),
9
+ html.wui-theme-switching::view-transition-new(root) {
10
+ // No default cross-fade: the old snapshot stays whole above the effect's edge and the new
11
+ // page whole below it, which is what makes the edge read as a wave or a wipe.
12
+ animation: none;
13
+ mix-blend-mode: normal;
14
+ }
15
+ html.wui-theme-switching::view-transition-new(root) {
16
+ // Hidden until the effect's first frame is attached (a zero-area clip), so a dark page never
17
+ // flashes in full for the frame or two between the snapshot and the animation start.
18
+ clip-path: inset(50%);
19
+ }
20
+ // The pressed control carries its own view-transition-name for the switch (the engine sets it),
21
+ // so it is rendered as its own live layer above the sweep: no old image, no cross-fade, just
22
+ // the control as it now is, with its ring sliding or its glyph swapped the moment it is pressed.
23
+ html.wui-theme-switching::view-transition-group(wui-theme-toggle),
24
+ html.wui-theme-switching::view-transition-new(wui-theme-toggle) {
25
+ animation: none;
26
+ }
27
+ html.wui-theme-switching::view-transition-old(wui-theme-toggle) {
28
+ animation: none;
29
+ opacity: 0;
30
+ }
31
+
32
+ // The new page is rendered live under the moving clip, so anything still changing on it repaints
33
+ // the whole page every frame. A theme flip starts a colour transition on hundreds of elements at
34
+ // once (Bootstrap's buttons, inputs and links all transition color and background), which is
35
+ // enough to stutter the sweep. For the length of the switch those transitions are off: the page
36
+ // jumps to its new colours under the old snapshot, and only the clip moves.
37
+ // The segmented toggle's own thumb and glyph are the one exception: a press on it is what
38
+ // started the switch, and its ring sliding to the pressed option is part of that press.
39
+ html.wui-theme-switching,
40
+ html.wui-theme-switching *:not(.segmented-thumb, .segmented-option > .wui-icon),
41
+ html.wui-theme-switching *::before,
42
+ html.wui-theme-switching *::after {
43
+ transition: none !important;
44
+ }
45
+
46
+ // The disc's glyph turns a touch on hover, the one cue that it changes something page-wide. The
47
+ // segmented variant (one option per mode) keeps AppSegmented's own look.
48
+ .u-action.wui-theme-toggle .wui-icon {
49
+ transition: transform 0.3s var(--wui-ease-out-quart, ease-out);
50
+ }
51
+ .u-action.wui-theme-toggle:hover .wui-icon {
52
+ transform: rotate(24deg);
53
+ }
54
+ @media (prefers-reduced-motion: reduce) {
55
+ .u-action.wui-theme-toggle .wui-icon {
56
+ transition: none;
57
+ }
58
+ .u-action.wui-theme-toggle:hover .wui-icon {
59
+ transform: none;
60
+ }
61
+ }
@@ -73,9 +73,8 @@
73
73
  line-height: 0;
74
74
  color: var(--color-text-subtle);
75
75
  }
76
- // Bootstrap Icons carry a -.125em baseline nudge meant for inline-with-text use;
77
- // inside the chip's flex row it just drops the glyph below the label's centre.
78
- // Reset it so a library-icon leading glyph lines up with the label.
76
+ // An icon's baseline nudge is meant for icons set in running text; inside the chip's flex row
77
+ // it drops the glyph below the label's centre. Reset it so a leading icon lines up with the label.
79
78
  .chip-ic .wui-icon {
80
79
  vertical-align: middle;
81
80
  }
@@ -273,6 +273,8 @@
273
273
  // one line while it fits — then `.page-header-bar`'s flex-wrap drops the WHOLE actions
274
274
  // block onto its own line only once the content genuinely can't share the row.
275
275
  flex: none;
276
+ // Never wider than the bar, so on a phone the buttons wrap among themselves.
277
+ max-width: 100%;
276
278
  }
277
279
 
278
280
  // ── Size scale ── (`size`): the title and the block's rhythm scale together.
@@ -885,10 +887,10 @@
885
887
  height: 24px;
886
888
  }
887
889
 
888
- // The leading glyph is a solid circular badge — variant fill, white glyph centred, with
890
+ // The leading glyph is a solid circular badge: variant fill, white glyph centred, with
889
891
  // a soft 3px same-colour halo ring around it. Base size is the SINGLE-line badge (20px,
890
892
  // 20px glyph); the .alert-stack override above enlarges it when a title and body appear
891
- // together. (The glyph itself is a Bootstrap Icons font `<i>` sized by the component.)
893
+ // together. (The glyph itself is a library icon, an inline SVG sized by the component.)
892
894
  // Custom image in the badge (icon replacement): the image fills the circle, so drop the
893
895
  // variant fill and the halo ring and clip it round. Compound selector so it beats the
894
896
  // .alert-icon rule below, which sets both.