@webority/theme 0.127.0 → 0.128.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.127.0",
3
+ "version": "0.128.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": {
@@ -47,7 +47,7 @@
47
47
  "postcss": "^8.5.31"
48
48
  },
49
49
  "devDependencies": {
50
- "@webority/tokens": "0.127.0",
50
+ "@webority/tokens": "0.128.0",
51
51
  "bootstrap": "5.3.3",
52
52
  "country-flag-icons": "^1.6.20",
53
53
  "sass-embedded": "^1.97.0",
@@ -1,4 +1,4 @@
1
- // Generated by @webority/tokens 0.127.0 from its token source. Do not edit; run webority brand --library.
1
+ // Generated by @webority/tokens 0.128.0 from its token source. Do not edit; run webority brand --library.
2
2
  $body-color: #252f4a !default;
3
3
  $body-secondary-color: #4b5675 !default;
4
4
  $body-tertiary-color: #626a82 !default;
@@ -877,8 +877,9 @@
877
877
  // compiled $card-border-radius. Background is an ELEVATED surface, not the page
878
878
  // colour — so a card stays visible on a tinted page, and flips correctly in dark
879
879
  // mode because --color-surface is themed in the alias layer.
880
+ // A brand that draws rounder cards than its controls sets --wui-card-radius in its token file.
880
881
  .card {
881
- --bs-card-border-radius: var(--wui-radius-lg);
882
+ --bs-card-border-radius: var(--wui-card-radius, var(--wui-radius-lg));
882
883
  --bs-card-bg: var(--color-surface);
883
884
  }
884
885
 
@@ -897,6 +898,13 @@
897
898
  align-items: center;
898
899
  gap: 0.5rem;
899
900
  margin-left: auto;
901
+ // Natural width: a long title wraps beside the actions instead of squeezing a button. The cap keeps
902
+ // wide actions (a segmented filter) inside a phone-width header, where they shrink as before.
903
+ flex: 0 0 auto;
904
+ max-width: 100%;
905
+ }
906
+ .card-header-actions .btn {
907
+ white-space: nowrap;
900
908
  }
901
909
 
902
910
  // Close button (React `onClose` / Razor `dismissible`): the last control in the header, pushed
@@ -1,4 +1,4 @@
1
- // Generated by @webority/tokens 0.127.0 from its token source. Do not edit; run webority brand --library.
1
+ // Generated by @webority/tokens 0.128.0 from its token source. Do not edit; run webority brand --library.
2
2
 
3
3
  :root {
4
4
  --wui-primary: #4F46E5;
@@ -46,11 +46,12 @@
46
46
  }
47
47
  // Bootstrap has its own `.card-title` (a bare margin-bottom) — these override it,
48
48
  // so they must stay in the layer that loads after Bootstrap.
49
- // A product sets its title type with --wui-card-title-fs and --wui-card-title-lh.
49
+ // A product sets its title type with --wui-card-title-font-family, -fs, -fw and -lh.
50
50
  .card-title {
51
51
  margin: 0;
52
+ font-family: var(--wui-card-title-font-family, inherit);
52
53
  font-size: var(--wui-card-title-fs, 16px);
53
- font-weight: 600;
54
+ font-weight: var(--wui-card-title-fw, 600);
54
55
  line-height: var(--wui-card-title-lh, 1.3);
55
56
  color: var(--color-text);
56
57
  }
@@ -1238,7 +1238,8 @@ wui-timepicker[aria-invalid="true"] .timepicker.open .timepicker-trigger {
1238
1238
  // NOT scoped under `.country-dropdown` — <wui-phone> renders the toggle inside
1239
1239
  // `.phone-country-adornment`, with no such wrapper. Scoping it there left the
1240
1240
  // button on raw UA styling (2px outset border + grey fill) inside the field.
1241
- .country-toggle {
1241
+ .country-toggle,
1242
+ .wui-phone-prefix {
1242
1243
  display: flex;
1243
1244
  align-items: center;
1244
1245
  gap: 6px;
@@ -1276,6 +1277,10 @@ wui-timepicker[aria-invalid="true"] .timepicker.open .timepicker-trigger {
1276
1277
  cursor: not-allowed;
1277
1278
  opacity: var(--wui-disabled-opacity);
1278
1279
  }
1280
+ // A locked country: the dial code is a static prefix, laid out like the toggle but not a control.
1281
+ .wui-phone-prefix {
1282
+ cursor: default;
1283
+ }
1279
1284
  // Flag glyphs come from the generated .wui-flag-* classes (_flags.scss).
1280
1285
  .app-phone-number-input .phone-chevron {
1281
1286
  color: var(--color-text-muted);
@@ -676,6 +676,8 @@ textarea.form-control.autosize {
676
676
  // Tighten the space above the message: .field's 10px gap → effective 8px, without
677
677
  // touching the label→control gap.
678
678
  margin-top: -2px;
679
+ // Start-aligned, so a message under a centred container sits under the field's edge, not its middle.
680
+ text-align: start;
679
681
  }
680
682
 
681
683
  // The message line under a field: the hint and the error share one grid cell, so the error takes
@@ -1069,6 +1071,8 @@ $wui-field-error-glyph: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/
1069
1071
  // Soft-filled at rest, white on focus — one shared fill for the whole control so the
1070
1072
  // prefix/suffix and the input body are always the SAME colour combination as the field.
1071
1073
  background: var(--color-field-bg);
1074
+ // Clip the square inner input to the wrapper's rounded corners on hover and focus.
1075
+ overflow: clip;
1072
1076
  transition:
1073
1077
  border-color 0.12s ease,
1074
1078
  background-color 0.12s ease;
@@ -1091,6 +1095,8 @@ $wui-field-error-glyph: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/
1091
1095
  border: 0;
1092
1096
  border-radius: 0;
1093
1097
  background: transparent;
1098
+ // The wrapper's focus border is the cue (.input-affixed:focus-within above), so the inner outline would only poke past the corners.
1099
+ outline: none;
1094
1100
  }
1095
1101
  // A select in the affixed shell (AppSelect prefix/suffix): the custom element is the flex
1096
1102
  // item, and its inner .input control already loses its own border through the rule above.
@@ -1250,6 +1256,7 @@ $wui-field-error-glyph: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/
1250
1256
  margin-top: 2px;
1251
1257
  font-size: 12px;
1252
1258
  color: var(--color-text-subtle);
1259
+ text-align: start;
1253
1260
  }
1254
1261
  // Character counter — a right-aligned count under the field; turns danger at the limit.
1255
1262
  .field-count {
@@ -226,8 +226,12 @@
226
226
  .table-ledger tbody tr:focus-within {
227
227
  --bs-table-bg-state: var(--color-bg-muted);
228
228
  }
229
+ // The identity block is a flex child of its cell: without a zero floor it keeps a long email's full
230
+ // width inside a phone card and the subline's truncation never starts. Column sizing is unaffected,
231
+ // because a min-width of zero lowers no min-content contribution.
229
232
  .table-ledger .u-meta {
230
233
  gap: 2px;
234
+ min-width: 0;
231
235
  }
232
236
  .table-ledger .u-name {
233
237
  min-width: 0;
@@ -10,6 +10,7 @@
10
10
  // so the slide never runs underneath it. Close stays Bootstrap's own quick fade of the modal.
11
11
  // Side panels (offcanvas, drawers) keep their slide: they come from an edge by nature.
12
12
  //
13
+ // The status icon in a dialog header springs in with the dialog (rules below).
13
14
  // Imported after menu-motion, whose wui-menu-zoom keyframes it reuses.
14
15
 
15
16
  :root {
@@ -27,8 +28,97 @@
27
28
  animation: var(--wui-dialog-anim) var(--wui-dialog-anim-duration) var(--wui-dialog-anim-ease);
28
29
  }
29
30
 
31
+ // The three beats of an SVG tone (error) run 600ms end to end and overlap, so the icon reads as
32
+ // one movement. The rules key on position, so a new SVG tone inherits the same beats.
33
+ // The origin is the icon's centre, not the viewBox corner, so every scale grows in place.
34
+ :root:not([data-wui-dialog-motion="slide"]) .modal.show wui-status-icon {
35
+ display: inline-flex;
36
+ }
37
+
38
+ :root:not([data-wui-dialog-motion="slide"]) .modal.show wui-status-icon > svg > rect,
39
+ :root:not([data-wui-dialog-motion="slide"]) .modal.show wui-status-icon > svg > g {
40
+ transform-box: view-box;
41
+ transform-origin: 87px 87px;
42
+ }
43
+
44
+ :root:not([data-wui-dialog-motion="slide"]) .modal.show wui-status-icon > svg > rect:first-child {
45
+ animation: wui-dialog-icon-halo 260ms var(--wui-ease-out) 0ms both;
46
+ }
47
+
48
+ :root:not([data-wui-dialog-motion="slide"]) .modal.show wui-status-icon > svg > rect:nth-child(2) {
49
+ animation: wui-dialog-icon-ring 300ms var(--wui-ease-out) 140ms both;
50
+ }
51
+
52
+ :root:not([data-wui-dialog-motion="slide"]) .modal.show wui-status-icon > svg > g {
53
+ animation: wui-dialog-icon-core 340ms var(--wui-ease-elastic) 260ms both;
54
+ }
55
+
56
+ // Raster tones get one beat because they have no parts to stage.
57
+ :root:not([data-wui-dialog-motion="slide"]) .modal.show wui-status-icon > img {
58
+ animation: wui-dialog-icon-raster 340ms var(--wui-ease-elastic) 0ms both;
59
+ }
60
+
61
+ @keyframes wui-dialog-icon-halo {
62
+ from {
63
+ opacity: 0;
64
+ transform: scale(0.7);
65
+ }
66
+ to {
67
+ opacity: 1;
68
+ transform: scale(1);
69
+ }
70
+ }
71
+
72
+ @keyframes wui-dialog-icon-ring {
73
+ from {
74
+ opacity: 0;
75
+ transform: scale(0.8);
76
+ }
77
+ to {
78
+ opacity: 1;
79
+ transform: scale(1);
80
+ }
81
+ }
82
+
83
+ @keyframes wui-dialog-icon-core {
84
+ from {
85
+ opacity: 0;
86
+ transform: scale(0.55);
87
+ }
88
+ 60% {
89
+ opacity: 1;
90
+ }
91
+ to {
92
+ opacity: 1;
93
+ transform: scale(1);
94
+ }
95
+ }
96
+
97
+ @keyframes wui-dialog-icon-raster {
98
+ from {
99
+ opacity: 0;
100
+ transform: scale(0.55);
101
+ }
102
+ to {
103
+ opacity: 1;
104
+ transform: scale(1);
105
+ }
106
+ }
107
+
30
108
  @media (prefers-reduced-motion: reduce) {
31
109
  :root:not([data-wui-dialog-motion="slide"]) .modal.show .modal-dialog {
32
110
  animation: none;
33
111
  }
112
+
113
+ :root:not([data-wui-dialog-motion="slide"]) .modal.show wui-status-icon > svg > :is(rect, g) {
114
+ animation: none;
115
+ opacity: 1;
116
+ transform: none;
117
+ }
118
+
119
+ :root:not([data-wui-dialog-motion="slide"]) .modal.show wui-status-icon > img {
120
+ animation: none;
121
+ opacity: 1;
122
+ transform: none;
123
+ }
34
124
  }
@@ -63,6 +63,8 @@
63
63
  }
64
64
  .kpi-value {
65
65
  margin-top: 6px;
66
+ // A product sets the figure's face with --wui-kpi-value-font-family; unset, it inherits the page font.
67
+ font-family: var(--wui-kpi-value-font-family, inherit);
66
68
  // The value ("main heading") size flows from one var, so a size tier (sm/md/lg,
67
69
  // spotlight) or a custom `valueSize` (px) can drive it. Default (md) is 24px.
68
70
  font-size: var(--wui-kpi-value-size, 24px);
@@ -188,7 +190,7 @@
188
190
  }
189
191
  .kpi.kpi-spotlight .kpi-value {
190
192
  margin-top: 14px;
191
- // No font-family: the value inherits the product's own font like every other tile.
193
+ // The face comes from --wui-kpi-value-font-family on .kpi-value, as on every other tile.
192
194
  // Size comes from --wui-kpi-value-size (36px default, set on the spotlight root above), so a
193
195
  // custom `valueSize` (px) can tame a too-large headline.
194
196
  line-height: 1;
@@ -268,9 +268,17 @@ html:has(.form-actions-sticky) {
268
268
  .app-tabs-scroll-btn {
269
269
  min-width: 44px;
270
270
  }
271
- wui-select[compact] .search-select-control {
271
+ wui-select .search-select-control {
272
272
  min-height: 44px;
273
273
  }
274
+ .app-tabs-sm .app-tab {
275
+ min-height: 44px;
276
+ }
277
+ // An inline link grows its hit area, not its line: the negative margin pays the padding back.
278
+ .wui-link {
279
+ padding-block: var(--wui-space-md);
280
+ margin-block: calc(-1 * var(--wui-space-md));
281
+ }
274
282
  }
275
283
 
276
284
  // Touch pointers at any width: the same 44px floor for controls the phone block does not reach.
@@ -93,7 +93,15 @@
93
93
  max-height: calc(100vh - 40px);
94
94
  display: flex;
95
95
  flex-direction: column;
96
- overflow: hidden;
96
+ // clip, not hidden: hidden makes the card a scroll container, so a focused control near its foot scrolled the card, not .wui-modal-body.
97
+ overflow: clip;
98
+ }
99
+ // The document gutter is reserved (html, body in _components.scss) and AppModal locks the page itself
100
+ // (ui-react lib/pageScrollLock.js), so the modal manager's scrollbar padding on body only bares a strip of
101
+ // page beside the backdrop. Bootstrap and react-bootstrap write it inline, which only !important outranks.
102
+ body.modal-open,
103
+ body[data-rr-ui-modal-open] {
104
+ padding-right: 0 !important;
97
105
  }
98
106
  // The centred card opens with the shared dialog motion (wui/_dialog-motion.scss: soft zoom by
99
107
  // default). Width comes from `size`, which both surfaces resolve to `--wui-modal-max-width` on
@@ -119,6 +119,16 @@
119
119
  }
120
120
  // A finger needs a 44px target: the pager's controls grow to the control height on a phone.
121
121
  @media (max-width: 575.98px) {
122
+ // On a phone the parts spread across the row and the page buttons take their own centred line.
123
+ .table-pagination {
124
+ justify-content: space-between;
125
+ gap: var(--wui-space-sm) var(--wui-space-md);
126
+ padding: var(--wui-space-md);
127
+ }
128
+ .tpg-nav {
129
+ flex-basis: 100%;
130
+ justify-content: center;
131
+ }
122
132
  .select.tpg-select {
123
133
  height: var(--wui-control-md);
124
134
  min-height: var(--wui-control-md);
@@ -246,6 +246,23 @@
246
246
  color: var(--color-text-body);
247
247
  font-weight: var(--wui-fw-medium);
248
248
  }
249
+ .wui-usermenu-extra {
250
+ margin-top: var(--wui-space-xxs);
251
+ font-size: var(--wui-fs-xs);
252
+ line-height: var(--wui-lh-body);
253
+ color: var(--color-text-muted);
254
+ }
255
+ // 6px is the menu's row gap: no token sits between xs and sm, so it is the two below it.
256
+ .wui-usermenu-section {
257
+ margin-bottom: calc(var(--wui-space-xs) + var(--wui-space-xxs));
258
+ padding-bottom: calc(var(--wui-space-xs) + var(--wui-space-xxs));
259
+ border-bottom: 1px solid var(--color-border);
260
+ }
261
+ .wui-usermenu-foot {
262
+ margin-top: calc(var(--wui-space-xs) + var(--wui-space-xxs));
263
+ padding-top: var(--wui-space-sm);
264
+ border-top: 1px solid var(--color-border);
265
+ }
249
266
  .wui-usermenu-logout {
250
267
  margin: 0;
251
268
  }
@@ -222,6 +222,15 @@
222
222
  .app-shell.no-sidebar .app-footer {
223
223
  left: 0;
224
224
  }
225
+ // Below lg the drawer and the phone tab bar carry the navigation, so the topbar menu waits for lg.
226
+ .app-shell.wui-shell-top .wui-shell-top-nav {
227
+ display: none;
228
+ }
229
+ .wui-shell-top-brand {
230
+ display: flex;
231
+ align-items: center;
232
+ flex: none;
233
+ }
225
234
 
226
235
  // Sticky always makes a stacking context, so without a z-index here the page content after the
227
236
  // sidebar paints over anything the sidebar lets out, such as an open workspace switcher menu.
@@ -715,6 +724,21 @@
715
724
  display: none;
716
725
  }
717
726
  @include media-breakpoint-up(lg) {
727
+ .app-shell.wui-shell-top {
728
+ grid-template-columns: 1fr;
729
+ }
730
+ .app-shell.wui-shell-top .app-sidebar,
731
+ .app-shell.wui-shell-top .app-sidebar-toggle {
732
+ display: none;
733
+ }
734
+ .app-shell.wui-shell-top .app-footer {
735
+ left: 0;
736
+ }
737
+ .app-shell.wui-shell-top .wui-shell-top-nav {
738
+ display: flex;
739
+ align-items: center;
740
+ min-width: 0;
741
+ }
718
742
  .app-shell.collapsed .app-nav-item[data-label]::after {
719
743
  content: attr(data-label);
720
744
  position: absolute;
@@ -725,6 +725,10 @@
725
725
  // They read the --color-*-strong aliases, not --wui-*-strong, because dark mode remaps the
726
726
  // aliases to the lifted fills: the deep fills sink into the dark track (about 2:1). The label
727
727
  // flips to a dark ink there (--color-progress-label), which clears AA on all five dark fills.
728
+ // The product's own primary, for a bar that tracks a brand action.
729
+ .wui-progress-primary {
730
+ background-color: var(--color-primary);
731
+ }
728
732
  .wui-progress-info {
729
733
  background-color: var(--color-info-strong);
730
734
  }
@@ -121,6 +121,18 @@
121
121
  height: 32px;
122
122
  padding: 0;
123
123
  }
124
+ // A finger needs a 44px target: the small and compact switchers grow on a phone or a touch screen.
125
+ @media (max-width: 575.98px), (pointer: coarse) {
126
+ .workspace-switcher-sm .workspace-switcher-trigger {
127
+ min-height: 44px;
128
+ }
129
+ .workspace-switcher-trigger.is-compact,
130
+ .workspace-switcher-sm .workspace-switcher-trigger.is-compact {
131
+ width: 44px;
132
+ height: 44px;
133
+ min-height: 44px;
134
+ }
135
+ }
124
136
  .workspace-switcher-sm .workspace-switcher-mark {
125
137
  width: 24px;
126
138
  height: 24px;