@webority/theme 0.6.13 → 0.7.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@webority/theme",
3
- "version": "0.6.13",
3
+ "version": "0.7.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": {
@@ -111,6 +111,25 @@
111
111
  }
112
112
 
113
113
 
114
+ // ---- Component radii from tokens ------------------------------------
115
+ // .btn/.card/.input capture Bootstrap's compiled $*-border-radius (the library
116
+ // ladder) before a product loads, so a product's --wui-radius* override never
117
+ // reached them. Re-drive the radius from the tokens — the way the dropdown above
118
+ // already does — so overriding the radius tokens once flows to every control.
119
+ // sm/lg are re-asserted so they keep their own rung instead of the base value.
120
+ .btn {
121
+ --bs-btn-border-radius: var(--wui-radius);
122
+ }
123
+ .btn-sm,
124
+ .btn-group-sm > .btn {
125
+ --bs-btn-border-radius: var(--wui-radius-sm);
126
+ }
127
+ .btn-lg,
128
+ .btn-group-lg > .btn {
129
+ --bs-btn-border-radius: var(--wui-radius-lg);
130
+ }
131
+
132
+
114
133
  // ---- Dropdown menu (AppDropdown / <app-dropdown>) -------------------
115
134
  // Bootstrap ships a bare .dropdown-item; the design system gives the menu real
116
135
  // padding, an icon–label gap, a rounded hover highlight tinted with the brand,
@@ -259,6 +278,32 @@
259
278
  }
260
279
 
261
280
 
281
+ // ---- Card surface + radius (token-driven) ---------------------------
282
+ // Radius follows the token ladder (see component-radii note) instead of the
283
+ // compiled $card-border-radius. Background is an ELEVATED surface, not the page
284
+ // colour — so a card stays visible on a tinted page, and flips correctly in dark
285
+ // mode because --color-surface is themed in the alias layer.
286
+ .card {
287
+ --bs-card-border-radius: var(--wui-radius-lg);
288
+ --bs-card-bg: var(--color-surface);
289
+ }
290
+
291
+ // Header actions slot (React `headerActions` prop / Razor <app-card-actions>). Both
292
+ // surfaces emit the same .card-header-actions class; the theme owns the right-push
293
+ // so the markup carries no layout utility. Scoped to headers that actually contain
294
+ // actions, so a plain text-only header stays block.
295
+ .card-header:has(.card-header-actions) {
296
+ display: flex;
297
+ align-items: center;
298
+ }
299
+ .card-header-actions {
300
+ display: inline-flex;
301
+ align-items: center;
302
+ gap: 0.5rem;
303
+ margin-left: auto;
304
+ }
305
+
306
+
262
307
  // ---- Inputs ---------------------------------------------------------
263
308
  // 5 locked states — default / hover / focus / disabled / error —
264
309
  // matching the reference `<Input>` component. Bootstrap drives the base
@@ -271,6 +316,10 @@
271
316
 
272
317
  .form-control,
273
318
  .form-select {
319
+ // Radius follows the token ladder (see component-radii note) rather than the
320
+ // compiled $input-border-radius, so a product's --wui-radius override reaches
321
+ // the raw Bootstrap inputs the way it already reaches the .input primitive.
322
+ border-radius: var(--wui-radius);
274
323
  transition: border-color 0.15s ease;
275
324
  }
276
325
  .form-control:hover:not(:focus):not(:disabled):not([readonly]):not([aria-invalid="true"]),
@@ -34,12 +34,29 @@ $theme-colors: (
34
34
  "dark": $dark,
35
35
  );
36
36
 
37
+ // Brand helper — set a product's primary ONCE and have the theme-colour map
38
+ // (so every .btn-primary / .bg-primary / .text-primary utility) and $link-color
39
+ // follow, instead of overriding each in turn. Call it in the product's
40
+ // pre-bootstrap variables, BEFORE the `bootstrap/scss/bootstrap` import, e.g.:
41
+ //
42
+ // @include wui-brand-primary(#0EA5E9);
43
+ // @import "bootstrap/scss/bootstrap";
44
+ //
45
+ // It rewrites the globals in place, so the whole Bootstrap colour system derives
46
+ // from the one value. Runtime CSS tokens (--wui-primary*) are still overridden
47
+ // separately, as usual.
48
+ @mixin wui-brand-primary($primary) {
49
+ $primary: $primary !global;
50
+ $theme-colors: map-merge($theme-colors, ("primary": $primary)) !global;
51
+ $link-color: $primary !global;
52
+ }
53
+
37
54
 
38
55
  // ---- Body / text ----------------------------------------------------
39
56
  $body-color: #252F4A; // text-1 from locked palette
40
57
  $body-bg: #FFFFFF;
41
58
  $body-secondary-color: #4B5675;
42
- $body-tertiary-color: #78829D;
59
+ $body-tertiary-color: #687088; // AA on white (matches --wui-text-3); #78829D was only 3.5:1
43
60
  $body-secondary-bg: #F9FBFC;
44
61
  $body-tertiary-bg: #F5F6FA;
45
62
 
@@ -151,7 +168,11 @@ $btn-transition: color 0.15s ease, background-color 0.15s ease, border-c
151
168
  $input-bg: #FFFFFF;
152
169
  $input-disabled-bg: #F5F6FA; // --wui-bg-muted
153
170
  $input-border-color: $border-color;
154
- $input-focus-border-color: $primary;
171
+ // $input-focus-border-color is intentionally NOT overridden. Setting it to $primary here
172
+ // captures the LIBRARY's $primary (this file compiles before a product's brand override),
173
+ // leaking indigo into every product's raw .form-control focus. Bootstrap's !default derives
174
+ // it from the brand $primary after the override; the library's own .input:focus uses
175
+ // var(--wui-primary), which the product overrides at runtime.
155
176
  $input-focus-box-shadow: none; // border IS the focus cue
156
177
  $input-focus-color: $body-color;
157
178
  $input-padding-y: 0.5rem; // 8px → 40px total height with 1rem font + 1.5 line-height
@@ -197,8 +218,11 @@ $link-hover-decoration: underline;
197
218
 
198
219
 
199
220
  // ---- Focus ring -----------------------------------------------------
221
+ // $focus-ring-color is intentionally NOT overridden. rgba($primary, …) here captures the
222
+ // LIBRARY's $primary (this file compiles before a product's brand override), so the ring
223
+ // leaked indigo into every product. Bootstrap's !default `rgba($primary, $focus-ring-opacity)`
224
+ // is evaluated after the brand override, so it tracks the brand correctly.
200
225
  $focus-ring-width: 3px;
201
- $focus-ring-color: rgba($primary, 0.30); // tracks $primary automatically
202
226
  $focus-ring-blur: 0;
203
227
 
204
228
 
@@ -121,6 +121,32 @@
121
121
  --color-text-muted: var(--wui-dark-text-muted);
122
122
  --color-text-subtle: var(--wui-dark-text-subtle);
123
123
 
124
+ // Re-bridge the Bootstrap colour vars onto the (now dark) app tokens so the
125
+ // named-var text/body/border utilities — .text-body-secondary, .text-muted,
126
+ // .text-body-emphasis, .border, backgrounds — flip with the theme. Declared
127
+ // here (not only in :root) so the dark theming is explicit and cannot be lost
128
+ // to a later-loading consumer rule that scopes --bs-* under [data-theme].
129
+ --bs-body-color: var(--color-text);
130
+ --bs-emphasis-color: var(--color-text);
131
+ --bs-secondary-color: var(--color-text-muted);
132
+ --bs-tertiary-color: var(--color-text-subtle);
133
+ --bs-body-bg: var(--color-bg);
134
+ --bs-border-color: var(--color-border);
135
+ --bs-secondary-bg: var(--wui-dark-bg-surface);
136
+ --bs-tertiary-bg: var(--wui-dark-bg-muted);
137
+ // Bootstrap's .bg-* / .text-secondary / .bg-body-tertiary utilities read the --bs-*-RGB
138
+ // triplets (for -opacity support), NOT the plain vars above — without these overrides they
139
+ // keep their compiled LIGHT triplet in dark mode (a white .bg-body on a dark portal). The
140
+ // triplets mirror the dark hexes in _tokens.scss (Bootstrap's own --bs-x + --bs-x-rgb pattern).
141
+ --bs-body-bg-rgb: 15, 17, 23; // --wui-dark-bg-page #0f1117
142
+ --bs-body-color-rgb: 230, 232, 238; // --wui-dark-text #e6e8ee
143
+ --bs-emphasis-color-rgb: 230, 232, 238; // --wui-dark-text #e6e8ee
144
+ --bs-secondary-color-rgb: 180, 185, 198; // --wui-dark-text-muted #b4b9c6
145
+ --bs-tertiary-color-rgb: 138, 144, 160; // --wui-dark-text-subtle #8a90a0
146
+ --bs-secondary-bg-rgb: 24, 27, 35; // --wui-dark-bg-surface #181b23
147
+ --bs-tertiary-bg-rgb: 35, 39, 51; // --wui-dark-bg-muted #232733
148
+ --bs-border-color-rgb: 42, 47, 58; // --wui-dark-border #2a2f3a
149
+
124
150
  --color-primary: var(--wui-dark-primary);
125
151
  --color-primary-hover: var(--wui-dark-primary-hover);
126
152
  --color-primary-soft: color-mix(in srgb, var(--wui-primary) 20%, transparent);
@@ -11,6 +11,7 @@
11
11
  // pattern nearly every panel header uses, so it belongs here rather than being
12
12
  // re-flexed with utilities on each one.
13
13
  .card-header {
14
+ --bs-card-cap-bg: var(--color-surface);
14
15
  display: flex;
15
16
  align-items: center;
16
17
  justify-content: space-between;
@@ -1,3 +1,4 @@
1
+ // Component classes use the .wui-* prefix.
1
2
  // Checkbox & radio, field/input, status badge, choice chip.
2
3
  //
3
4
  // Part of the wui-components layer — see _wui-components.scss for the whole
@@ -14,12 +15,12 @@
14
15
  // accessible.
15
16
  //
16
17
  // Markup:
17
- // <label class="lx-control">
18
- // <input type="checkbox" class="lx-check"> (or type="radio" class="lx-radio")
19
- // <span class="lx-control-label">Full-time</span>
18
+ // <label class="wui-control">
19
+ // <input type="checkbox" class="wui-check"> (or type="radio" class="wui-radio")
20
+ // <span class="wui-control-label">Full-time</span>
20
21
  // </label>
21
- // Group rows in a `.lx-control-group` (horizontal, wraps) or stack them.
22
- .lx-control {
22
+ // Group rows in a `.wui-control-group` (horizontal, wraps) or stack them.
23
+ .wui-control {
23
24
  display: inline-flex;
24
25
  align-items: center;
25
26
  gap: 6px;
@@ -29,19 +30,19 @@
29
30
  color: var(--color-text);
30
31
  line-height: 1.4;
31
32
  }
32
- .lx-control-group {
33
+ .wui-control-group {
33
34
  display: flex;
34
35
  flex-wrap: wrap;
35
36
  align-items: center;
36
37
  gap: 8px 20px;
37
38
  }
38
- .lx-control-label {
39
+ .wui-control-label {
39
40
  padding-right: 2px;
40
41
  }
41
42
 
42
43
  // The control glyph: 20px box inside a 36px circular tap target (8px ring).
43
- .lx-check,
44
- .lx-radio {
44
+ .wui-check,
45
+ .wui-radio {
45
46
  appearance: none;
46
47
  -webkit-appearance: none;
47
48
  flex: none;
@@ -58,17 +59,17 @@
58
59
  border-color 0.15s ease,
59
60
  background-color 0.15s ease;
60
61
  }
61
- .lx-check {
62
+ .wui-check {
62
63
  border-radius: var(--radius-sm);
63
64
  corner-shape: squircle;
64
65
  }
65
- .lx-radio {
66
+ .wui-radio {
66
67
  border-radius: 50%;
67
68
  }
68
69
 
69
70
  // Soft primary circular ripple behind the glyph on hover (the Minimals halo).
70
- .lx-check::after,
71
- .lx-radio::after {
71
+ .wui-check::after,
72
+ .wui-radio::after {
72
73
  content: "";
73
74
  position: absolute;
74
75
  left: 50%;
@@ -85,16 +86,16 @@
85
86
  opacity 0.15s ease,
86
87
  transform 0.15s ease;
87
88
  }
88
- .lx-control:hover .lx-check::after,
89
- .lx-control:hover .lx-radio::after,
90
- .lx-check:hover::after,
91
- .lx-radio:hover::after {
89
+ .wui-control:hover .wui-check::after,
90
+ .wui-control:hover .wui-radio::after,
91
+ .wui-check:hover::after,
92
+ .wui-radio:hover::after {
92
93
  opacity: 1;
93
94
  transform: translate(-50%, -50%) scale(1);
94
95
  }
95
96
 
96
97
  // Checked — primary fill (checkbox) / primary ring + dot (radio)
97
- .lx-check:checked {
98
+ .wui-check:checked {
98
99
  border-color: var(--color-primary);
99
100
  background: var(--color-primary);
100
101
  // white tick drawn with a rotated box-shadow-free pseudo
@@ -103,10 +104,10 @@
103
104
  background-position: center;
104
105
  background-repeat: no-repeat;
105
106
  }
106
- .lx-radio:checked {
107
+ .wui-radio:checked {
107
108
  border-color: var(--color-primary);
108
109
  }
109
- .lx-radio:checked::before {
110
+ .wui-radio:checked::before {
110
111
  content: "";
111
112
  position: absolute;
112
113
  left: 50%;
@@ -119,52 +120,52 @@
119
120
  }
120
121
 
121
122
  // Keyboard focus ring (only on focus-visible, not mouse)
122
- .lx-check:focus-visible,
123
- .lx-radio:focus-visible {
123
+ .wui-check:focus-visible,
124
+ .wui-radio:focus-visible {
124
125
  outline: none;
125
126
  box-shadow: 0 0 0 3px var(--color-primary-soft);
126
127
  }
127
128
 
128
129
  // Disabled
129
- .lx-check:disabled,
130
- .lx-radio:disabled,
131
- .lx-control:has(:disabled) {
130
+ .wui-check:disabled,
131
+ .wui-radio:disabled,
132
+ .wui-control:has(:disabled) {
132
133
  cursor: not-allowed;
133
134
  }
134
- .lx-check:disabled,
135
- .lx-radio:disabled {
135
+ .wui-check:disabled,
136
+ .wui-radio:disabled {
136
137
  border-color: var(--color-border);
137
138
  background-color: var(--color-bg-muted);
138
139
  }
139
- .lx-check:disabled:checked,
140
- .lx-radio:disabled:checked {
140
+ .wui-check:disabled:checked,
141
+ .wui-radio:disabled:checked {
141
142
  border-color: var(--color-border-strong);
142
143
  }
143
- .lx-check:disabled:checked {
144
+ .wui-check:disabled:checked {
144
145
  background-color: var(--color-border-strong);
145
146
  }
146
- .lx-radio:disabled:checked::before {
147
+ .wui-radio:disabled:checked::before {
147
148
  background: var(--color-border-strong);
148
149
  }
149
- .lx-control:has(:disabled) {
150
+ .wui-control:has(:disabled) {
150
151
  color: var(--color-text-subtle);
151
152
  }
152
- .lx-check:disabled::after,
153
- .lx-radio:disabled::after {
153
+ .wui-check:disabled::after,
154
+ .wui-radio:disabled::after {
154
155
  display: none;
155
156
  }
156
157
 
157
158
  @media (prefers-reduced-motion: reduce) {
158
- .lx-check,
159
- .lx-radio,
160
- .lx-check::after,
161
- .lx-radio::after {
159
+ .wui-check,
160
+ .wui-radio,
161
+ .wui-check::after,
162
+ .wui-radio::after {
162
163
  transition: none;
163
164
  }
164
165
  }
165
166
 
166
167
  // Row-selection checkbox variant used inside a .table-ledger select column.
167
- .lx-check {
168
+ .wui-check {
168
169
  width: 16px;
169
170
  height: 16px;
170
171
  accent-color: var(--color-primary);
@@ -175,7 +176,7 @@
175
176
  // ── Premium checkbox / radio / switch ──
176
177
  // AppCheckbox / AppRadio / AppSwitch render Bootstrap's .form-check markup, whose default
177
178
  // input is a plain 16px box. This restyles it to the Webority selection-control language
178
- // (see .lx-check above): a 20px squircle box, brand fill, a crisp white tick, a soft primary
179
+ // (see .wui-check above): a 20px squircle box, brand fill, a crisp white tick, a soft primary
179
180
  // hover ring + focus ring, and a themed switch. One block upgrades all three, both surfaces.
180
181
  .form-check {
181
182
  display: flex;
@@ -888,9 +889,19 @@ wui-select[compact] {
888
889
  .field > .input-with-unit,
889
890
  .field > .input,
890
891
  .field > .datepicker,
892
+ .field > .file-input,
893
+ .field > .file-preview,
891
894
  .field > .field-error {
892
895
  align-self: stretch;
893
896
  }
897
+ // A file picker fills its column by default (align-self:stretch fills the field on the cross
898
+ // axis, but the field itself collapses to its content when it is a flex item — the showcase
899
+ // lays fields out in a flex row — so stretch the field to its container first). Override per
900
+ // use with a .file-w-* class or the --wui-file-width variable.
901
+ .field:has(> .file-input),
902
+ .field:has(> .file-preview) {
903
+ width: var(--wui-file-width, 100%);
904
+ }
894
905
  // Doubled selectors — see the lg-tier block below: a media query carries no
895
906
  // extra specificity, so a consumer's later single-class rule would win.
896
907
  @media (max-width: 575.98px) {
@@ -1025,7 +1036,7 @@ wui-select[compact] {
1025
1036
  // Two other vocabularies existed and are accepted as DEPRECATED ALIASES so a
1026
1037
  // consumer can migrate without a flag day:
1027
1038
  // - colour-named .tone-green / -amber / -red / -blue / -grey (was the library's)
1028
- // - short semantic .tone-ok / -warn / -bad (was lumex's)
1039
+ // - short semantic .tone-ok / -warn / -bad (a short-semantic set)
1029
1040
  // Author new markup with the semantic names; the aliases will be removed.
1030
1041
  .status-label.success,
1031
1042
  .status-label.tone-ok,
@@ -1,3 +1,4 @@
1
+ // Component classes use the .wui-* prefix.
1
2
  // Empty state, base table, ledger table + pagination.
2
3
  //
3
4
  // Part of the wui-components layer — see _wui-components.scss for the whole
@@ -88,19 +89,19 @@
88
89
  margin-top: 20px;
89
90
  }
90
91
 
91
- .lx-table-empty {
92
+ .wui-table-empty {
92
93
  padding: 36px 24px;
93
94
  text-align: center;
94
95
  color: var(--color-text-muted);
95
96
  }
96
97
  // The empty state owns its own padding and is not a row you can act on — no
97
98
  // cell padding, no border, and no hover fill from .table / .table-ledger.
98
- .table td.lx-table-empty:has(> .tbl-empty) {
99
+ .table td.wui-table-empty:has(> .tbl-empty) {
99
100
  padding: 0;
100
101
  border-bottom: 0;
101
102
  }
102
- .table tbody tr:hover td.lx-table-empty:has(> .tbl-empty),
103
- .table-ledger tbody tr:hover td.lx-table-empty:has(> .tbl-empty) {
103
+ .table tbody tr:hover td.wui-table-empty:has(> .tbl-empty),
104
+ .table-ledger tbody tr:hover td.wui-table-empty:has(> .tbl-empty) {
104
105
  background: transparent;
105
106
  }
106
107
 
@@ -196,8 +197,8 @@
196
197
 
197
198
  // ── Table + pagination ──
198
199
  // DataTable cell helpers
199
- .table-ledger .lx-cell-end,
200
- .table-ledger thead th.lx-cell-end {
200
+ .table-ledger .wui-cell-end,
201
+ .table-ledger thead th.wui-cell-end {
201
202
  text-align: right;
202
203
  }
203
204
 
@@ -309,9 +310,9 @@
309
310
  text-decoration: none;
310
311
  }
311
312
  // Identity-cell subline. `.u-email` is in the group because an identity cell
312
- // renders the secondary line as either a generic .lx-text-xs or a .u-email.
313
+ // renders the secondary line as either a generic .wui-text-xs or a .u-email.
313
314
  .table-ledger .u-meta > .u-email,
314
- .table-ledger .u-meta > .lx-text-xs {
315
+ .table-ledger .u-meta > .wui-text-xs {
315
316
  font-size: 12px;
316
317
  font-weight: 400;
317
318
  line-height: 15px;
@@ -432,7 +433,7 @@
432
433
 
433
434
  // The identity/name column — a STABLE hook, because position is not one.
434
435
  //
435
- // The library owns .lx-select-cell, so a ledger's first cell is the 44px checkbox when
436
+ // The library owns .wui-select-cell, so a ledger's first cell is the 44px checkbox when
436
437
  // selection is on and the name when it is off. `:first-child` therefore changes meaning
437
438
  // with a permission flag: a consumer's 360px name-column floor landed on the 44px checkbox
438
439
  // for any user who could select rows, inflating that column 8x and pushing the table into
@@ -442,14 +443,14 @@
442
443
  // min-width:0 is the rule rather than a placeholder: a table cell defaults to min-width:auto,
443
444
  // which refuses to shrink below its content and is what makes truncation impossible in the
444
445
  // first place.
445
- .table-ledger th.lx-name-cell,
446
- .table-ledger td.lx-name-cell {
446
+ .table-ledger th.wui-name-cell,
447
+ .table-ledger td.wui-name-cell {
447
448
  min-width: 0;
448
449
  }
449
450
 
450
451
  // Row-selection checkbox column.
451
- .table-ledger th.lx-select-cell,
452
- .table-ledger td.lx-select-cell {
452
+ .table-ledger th.wui-select-cell,
453
+ .table-ledger td.wui-select-cell {
453
454
  width: 44px;
454
455
  min-width: 44px;
455
456
  max-width: 44px;