@webority/theme 0.7.7 → 0.7.9

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.7.7",
3
+ "version": "0.7.9",
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": {
@@ -6,14 +6,15 @@
6
6
  // ---- Button polish (used by both Razor + React) ---------------------
7
7
  // Flat buttons — no drop shadow. Active-press feedback (1px nudge) is
8
8
  // retained because it's a tactile signal, not a depth cue.
9
- // Default-size buttons match the shared control height so a button next to an
10
- // input in a filter bar or form row lines up exactly. sm/lg opt out — they are
11
- // deliberately a different size.
9
+ // Button heights are fixed to the design reference — large 55px, medium 45px,
10
+ // small 35px — via min-height, with the label flex-centred. (These are button-
11
+ // specific and intentionally a touch taller than the shared --wui-control-height
12
+ // input ladder; sm/lg pin their own height in their size blocks below.)
12
13
  .btn:not(.btn-sm):not(.btn-lg):not(.btn-pill):not(.btn-pill-sm):not(.u-action):not(.btn-icon) {
13
14
  display: inline-flex;
14
15
  align-items: center;
15
16
  justify-content: center;
16
- min-height: var(--wui-control-height);
17
+ min-height: 45px;
17
18
  }
18
19
 
19
20
  // Tactile press feedback — every variant AND every size (sm/md/lg). Kept
@@ -37,7 +38,24 @@
37
38
  --bs-btn-color: var(--wui-primary-text);
38
39
  --bs-btn-hover-color: var(--wui-primary-text);
39
40
  --bs-btn-active-color: var(--wui-primary-text);
40
- --bs-btn-disabled-color: var(--wui-primary-text);
41
+ }
42
+
43
+ // Secondary — a soft brand tint (matches the design reference's secondary): a
44
+ // low-alpha wash of the brand rather than an opaque fill, so it reads as a
45
+ // quieter companion to the solid primary. Colour comes from the brand token;
46
+ // only the TRANSPARENCY is taken from the reference — the tint deepens on hover
47
+ // then press. No border (the tint carries the shape). Disabled is the shared
48
+ // neutral-grey state below, like every other variant.
49
+ .btn-secondary {
50
+ --bs-btn-color: var(--color-primary);
51
+ --bs-btn-bg: color-mix(in srgb, var(--color-primary) 12%, transparent);
52
+ --bs-btn-border-color: transparent;
53
+ --bs-btn-hover-color: var(--color-primary);
54
+ --bs-btn-hover-bg: color-mix(in srgb, var(--color-primary) 18%, transparent);
55
+ --bs-btn-hover-border-color: transparent;
56
+ --bs-btn-active-color: var(--color-primary);
57
+ --bs-btn-active-bg: color-mix(in srgb, var(--color-primary) 26%, transparent);
58
+ --bs-btn-active-border-color: transparent;
41
59
  }
42
60
 
43
61
  .btn-danger {
@@ -128,11 +146,13 @@
128
146
  // instead, reusing the same tokens .input:disabled already uses so a disabled button and
129
147
  // a disabled field read as the same state.
130
148
  //
131
- // Excludes .btn-primary and .btn-danger, which already theme their OWN disabled state
132
- // above (a faded version of their own fill, driven by --bs-btn-disabled-*) — :not() keeps
133
- // this rule from fighting theirs rather than relying on source order.
134
- .btn:disabled:not(.btn-primary):not(.btn-danger),
135
- .btn.disabled:not(.btn-primary):not(.btn-danger) {
149
+ // Applies to EVERY variant except .btn-danger — matching the design reference, where a
150
+ // disabled button of any kind reads as one neutral greyed-out state, not a faded tint of
151
+ // its own colour. (Primary would otherwise fall back to a washed-out indigo, secondary to a
152
+ // faint brand tint.) .btn-danger keeps its own faded-red disabled (defined above), since the
153
+ // reference doesn't cover it and a greyed destructive button loses its warning read.
154
+ .btn:disabled:not(.btn-danger),
155
+ .btn.disabled:not(.btn-danger) {
136
156
  background-color: var(--color-bg-muted);
137
157
  border-color: var(--color-border);
138
158
  color: var(--color-text-subtle);
@@ -146,19 +166,160 @@
146
166
  // reached them. Re-drive the radius from the tokens — the way the dropdown above
147
167
  // already does — so overriding the radius tokens once flows to every control.
148
168
  // sm/lg are re-asserted so they keep their own rung instead of the base value.
169
+ // Per-size type + padding scale, matched to the design reference: the three
170
+ // sizes step font-size, horizontal padding and radius together (vertical size
171
+ // comes from --wui-control-height, so a button lines up with an input). Weight
172
+ // is 600 on every rung — buttons read as a deliberate action, not body text.
173
+ // Medium (default): 15px / 600, 24px side padding, 10px radius, 44px tall.
149
174
  .btn {
150
175
  --bs-btn-border-radius: var(--wui-radius);
176
+ --bs-btn-font-size: 0.9375rem;
177
+ --bs-btn-font-weight: 600;
178
+ --bs-btn-padding-x: 1.5rem;
151
179
  // Progressive enhancement only (Chrome/Safari recent). Unsupported engines
152
180
  // keep a plain border-radius arc — not a cross-browser design contract.
153
181
  corner-shape: squircle;
154
182
  }
155
- .btn-sm,
156
- .btn-group-sm > .btn {
183
+ // Small: 14px, 18px side padding, 8px radius, 35px tall.
184
+ .btn-sm:not(.btn-icon),
185
+ .btn-group-sm > .btn:not(.btn-icon) {
157
186
  --bs-btn-border-radius: var(--wui-radius-sm);
187
+ --bs-btn-font-size: 0.875rem;
188
+ --bs-btn-padding-x: 1.125rem;
189
+ display: inline-flex;
190
+ align-items: center;
191
+ justify-content: center;
192
+ min-height: 35px;
158
193
  }
159
- .btn-lg,
160
- .btn-group-lg > .btn {
194
+ // Large: 17px, 32px side padding, 14px radius, 55px tall — a spacious CTA.
195
+ .btn-lg:not(.btn-icon),
196
+ .btn-group-lg > .btn:not(.btn-icon) {
161
197
  --bs-btn-border-radius: var(--wui-radius-lg);
198
+ --bs-btn-font-size: 1.0625rem;
199
+ --bs-btn-padding-x: 2rem;
200
+ display: inline-flex;
201
+ align-items: center;
202
+ justify-content: center;
203
+ min-height: 55px;
204
+ }
205
+
206
+ // ---- Hover animation options (opt-in modifiers) ---------------------
207
+ // Every .btn animates on hover. The DEFAULT is the royal sheen sweep; switch a
208
+ // button to a different feel with a .btn-anim-* modifier, or turn motion off
209
+ // with .btn-anim-none. Every option reads on EVERY variant — the moving layer
210
+ // is tinted from the button's OWN colour (color-mix on currentColor / the
211
+ // resolved --bs-btn fill), so it's a pale sheen on solid fills, a brand shimmer
212
+ // on outline/ghost, a neutral gloss on subtle/light. All snap off under
213
+ // prefers-reduced-motion (below). Icon-only / link-style actions never animate.
214
+ //
215
+ // (default) royal angled sheen sweep + a restrained −1px lift
216
+ // .btn-anim-sheen the same sheen, named (to override a product default)
217
+ // .btn-anim-fill a tint wipes in left→right
218
+ // .btn-anim-expand a radial glow blooms from the centre
219
+ // .btn-anim-glow a soft colour-matched glow ring + lift
220
+ // .btn-anim-none no hover motion
221
+ //
222
+ // Shared plumbing — a clipped, isolated box so a z-index:-1 layer can sit above
223
+ // the fill but below the label; overflow clips it to the squircle corners.
224
+ .btn:not(.btn-icon):not(.u-action) {
225
+ position: relative;
226
+ overflow: hidden;
227
+ isolation: isolate;
228
+ }
229
+
230
+ // --- Sheen (default + .btn-anim-sheen): slim angled glossy band sweeps across.
231
+ .btn:not(.btn-icon):not(.u-action):not([class*="btn-anim-"])::before,
232
+ .btn-anim-sheen::before {
233
+ content: "";
234
+ position: absolute;
235
+ top: 0;
236
+ bottom: 0;
237
+ left: 0;
238
+ z-index: -1;
239
+ width: 55%;
240
+ background: linear-gradient(
241
+ 100deg,
242
+ transparent,
243
+ color-mix(in srgb, currentColor 16%, transparent),
244
+ transparent
245
+ );
246
+ transform: translateX(-200%) skewX(-18deg);
247
+ transition: transform 0.7s cubic-bezier(0.22, 1, 0.36, 1);
248
+ pointer-events: none;
249
+ }
250
+ .btn:not(.btn-icon):not(.u-action):not([class*="btn-anim-"]):hover:not(:disabled)::before,
251
+ .btn-anim-sheen:hover:not(:disabled)::before {
252
+ transform: translateX(320%) skewX(-18deg);
253
+ }
254
+ .btn:not(.btn-icon):not(.u-action):not([class*="btn-anim-"]):hover:not(:disabled),
255
+ .btn-anim-sheen:hover:not(:disabled) {
256
+ transform: translateY(-1px);
257
+ }
258
+
259
+ // --- Fill: a soft currentColor tint wipes in from the left.
260
+ .btn-anim-fill::before {
261
+ content: "";
262
+ position: absolute;
263
+ inset: 0;
264
+ z-index: -1;
265
+ background: currentColor;
266
+ opacity: 0;
267
+ transform: scaleX(0);
268
+ transform-origin: left center;
269
+ transition:
270
+ transform 0.35s cubic-bezier(0.22, 1, 0.36, 1),
271
+ opacity 0.35s ease;
272
+ pointer-events: none;
273
+ }
274
+ .btn-anim-fill:hover:not(:disabled)::before {
275
+ opacity: 0.12;
276
+ transform: scaleX(1);
277
+ }
278
+
279
+ // --- Expand: a radial glow blooms from the centre (aspect-ratio keeps it round).
280
+ .btn-anim-expand::before {
281
+ content: "";
282
+ position: absolute;
283
+ left: 50%;
284
+ top: 50%;
285
+ z-index: -1;
286
+ width: 0;
287
+ aspect-ratio: 1;
288
+ border-radius: 50%;
289
+ background: currentColor;
290
+ opacity: 0;
291
+ transform: translate(-50%, -50%);
292
+ transition:
293
+ width 0.45s cubic-bezier(0.22, 1, 0.36, 1),
294
+ opacity 0.45s ease;
295
+ pointer-events: none;
296
+ }
297
+ .btn-anim-expand:hover:not(:disabled)::before {
298
+ width: 250%;
299
+ opacity: 0.12;
300
+ }
301
+
302
+ // --- Glow: a soft ring in the button's own fill colour + lift. Opt-in premium
303
+ // look that deliberately steps outside the flat/no-shadow default. Falls back
304
+ // through the resolved --bs-btn fill to currentColor so it works on every variant.
305
+ .btn-anim-glow {
306
+ transition:
307
+ box-shadow 0.3s ease,
308
+ transform 0.3s ease;
309
+ }
310
+ .btn-anim-glow:hover:not(:disabled) {
311
+ box-shadow: 0 4px 18px
312
+ color-mix(in srgb, var(--bs-btn-hover-bg, var(--bs-btn-bg, currentColor)) 45%, transparent);
313
+ transform: translateY(-1px);
314
+ }
315
+
316
+ // --- None: opt out of hover motion entirely.
317
+ .btn-anim-none::before {
318
+ display: none;
319
+ }
320
+ .btn-anim-none:hover:not(:disabled) {
321
+ transform: none;
322
+ box-shadow: none;
162
323
  }
163
324
 
164
325
  // ---- Dropdown menu (AppDropdown / <app-dropdown>) -------------------
@@ -547,6 +708,15 @@ html.is-scrolling body::-webkit-scrollbar-thumb {
547
708
  .shimmer::after {
548
709
  animation: none;
549
710
  }
711
+ // Hover animation options: hide the moving layer and drop lift/glow so no
712
+ // option animates, regardless of which .btn-anim-* is applied.
713
+ .btn:not(.btn-icon):not(.u-action)::before {
714
+ display: none;
715
+ }
716
+ .btn:not(.btn-icon):not(.u-action):hover:not(:disabled) {
717
+ transform: none;
718
+ box-shadow: none;
719
+ }
550
720
  }
551
721
 
552
722
  // ---- Theme-flipped typography utilities ------------------------------------
package/scss/_tokens.scss CHANGED
@@ -64,6 +64,10 @@
64
64
  --wui-shadow-overlay: #{$wui-shadow-overlay};
65
65
  --wui-shadow-overlay-lg: #{$wui-shadow-overlay-lg};
66
66
 
67
+ // Component-specific shadows. Used by slider thumbs and other interactive elements.
68
+ --wui-shadow-thumb: 0 2px 6px rgba(0, 0, 0, 0.1);
69
+ --wui-shadow-thumb-hover: 0 2px 12px rgba(0, 0, 0, 0.2);
70
+
67
71
  // One height for every form control. Buttons, inputs and selects share form
68
72
  // rows, so they must line up — they were 46.8px and 45px, which reads as a
69
73
  // misalignment on every filter bar. 44px is also the WCAG touch-target floor.
@@ -186,9 +190,9 @@
186
190
  --wui-danger-5: #FEF4F6;
187
191
 
188
192
  // Corner radii (DESIGN.md scale) — portal aliases as --radius-*
189
- --wui-radius-sm: 6px;
190
- --wui-radius: 8px;
191
- --wui-radius-lg: 12px;
193
+ --wui-radius-sm: 8px;
194
+ --wui-radius: 10px;
195
+ --wui-radius-lg: 14px;
192
196
  --wui-radius-xl: 16px;
193
197
 
194
198
  // ---- Portal dark palette ----------------------------------------
@@ -206,6 +210,10 @@
206
210
  --wui-dark-primary: #6B83EC;
207
211
  --wui-dark-primary-hover:#93A6F2;
208
212
 
213
+ // Dark mode shadow remaps — lighter shadows (white ink) for visibility on dark surfaces.
214
+ --wui-dark-shadow-thumb: 0 2px 6px rgba(255, 255, 255, 0.1);
215
+ --wui-dark-shadow-thumb-hover: 0 2px 12px rgba(255, 255, 255, 0.2);
216
+
209
217
  // ---- Fluid type scale --------------------------------------------
210
218
  // clamp(min, preferred, max) so type scales smoothly between viewports instead of
211
219
  // stepping at Bootstrap's breakpoints. `base` sits close to Bootstrap's own 1rem body
@@ -48,7 +48,8 @@
48
48
  --color-info-20: var(--wui-info-20);
49
49
  --color-info-10: var(--wui-info-10);
50
50
  --color-info-5: var(--wui-info-5);
51
- --color-info-strong: var(--color-info-active);
51
+ // No dedicated --wui-info-strong; active is the deepest info fill.
52
+ --color-info-strong: var(--wui-info-active);
52
53
  --color-info-soft: var(--color-info-10);
53
54
  // Use this — never --color-info — when the colour IS the text.
54
55
  --color-info-text: var(--wui-info-text);
@@ -60,7 +61,7 @@
60
61
  --color-success-20: var(--wui-success-20);
61
62
  --color-success-10: var(--wui-success-10);
62
63
  --color-success-5: var(--wui-success-5);
63
- --color-success-strong: var(--color-success-active);
64
+ --color-success-strong: var(--wui-success-strong);
64
65
  --color-success-soft: var(--color-success-10);
65
66
  // Use this — never --color-success — when the colour IS the text.
66
67
  --color-success-text: var(--wui-success-text);
@@ -72,7 +73,8 @@
72
73
  --color-warning-20: var(--wui-warning-20);
73
74
  --color-warning-10: var(--wui-warning-10);
74
75
  --color-warning-5: var(--wui-warning-5);
75
- --color-warning-strong: var(--color-warning-active);
76
+ // No dedicated --wui-warning-strong; active is the deepest warning fill.
77
+ --color-warning-strong: var(--wui-warning-active);
76
78
  --color-warning-soft: var(--color-warning-10);
77
79
  // Use this — never --color-warning — when the colour IS the text.
78
80
  --color-warning-text: var(--wui-warning-text);
@@ -84,7 +86,7 @@
84
86
  --color-danger-20: var(--wui-danger-20);
85
87
  --color-danger-10: var(--wui-danger-10);
86
88
  --color-danger-5: var(--wui-danger-5);
87
- --color-danger-strong: var(--color-danger-active);
89
+ --color-danger-strong: var(--wui-danger-strong);
88
90
  --color-danger-soft: var(--color-danger-10);
89
91
  // Use this — never --color-danger — when the colour IS the text.
90
92
  --color-danger-text: var(--wui-danger-text);
@@ -115,7 +117,14 @@
115
117
  --radius-xl: var(--wui-radius-xl);
116
118
  }
117
119
 
118
- :root[data-theme="dark"] {
120
+ // Dark mode tokens — shared by manual toggle ([data-theme="dark"], [data-bs-theme="dark"])
121
+ // and automatic detection (@media (prefers-color-scheme: dark) when [data-theme="auto"]).
122
+ // Extracted as a mixin to avoid duplication and ensure byte-identical output.
123
+ @mixin wui-dark-theme {
124
+ // Token-level remap so every consumer of the shadow tokens flips in ALL dark
125
+ // paths (manual attributes and the auto media query) without per-component overrides.
126
+ --wui-shadow-thumb: var(--wui-dark-shadow-thumb);
127
+ --wui-shadow-thumb-hover: var(--wui-dark-shadow-thumb-hover);
119
128
  --color-bg: var(--wui-dark-bg-page);
120
129
  --color-bg-subtle: var(--wui-dark-bg-elevated);
121
130
  --color-bg-muted: var(--wui-dark-bg-muted);
@@ -170,6 +179,13 @@
170
179
  --color-warning-text: var(--wui-warning);
171
180
  --color-info-text: var(--wui-info);
172
181
 
182
+ // Light mode maps -strong to deep AA fills; on dark surfaces those sink into the page.
183
+ // Remap to the bright fills so timeline dots / strong accents stay visible (same as -text).
184
+ --color-success-strong: var(--wui-success);
185
+ --color-danger-strong: var(--wui-danger);
186
+ --color-info-strong: var(--wui-info);
187
+ --color-warning-strong: var(--wui-warning);
188
+
173
189
  --color-success-soft: color-mix(in srgb, var(--wui-success) 18%, transparent);
174
190
  --color-warning-soft: color-mix(in srgb, var(--wui-warning) 18%, transparent);
175
191
  --color-danger-soft: color-mix(in srgb, var(--wui-danger) 18%, transparent);
@@ -179,6 +195,20 @@
179
195
  --color-scrim: color-mix(in srgb, black 45%, transparent);
180
196
  --color-scrim-strong: color-mix(in srgb, black 62%, transparent);
181
197
  --color-on-scrim: var(--wui-bg-light);
198
+ }
182
199
 
200
+ // Manual dark mode — set data-theme="dark" and/or data-bs-theme="dark" on <html>
201
+ // to flip both wui and Bootstrap native components together.
202
+ :root[data-theme="dark"],
203
+ :root[data-bs-theme="dark"] {
204
+ @include wui-dark-theme;
183
205
  color-scheme: dark;
184
206
  }
207
+
208
+ // Automatic dark mode — when data-theme="auto", detect system preference and apply dark tokens.
209
+ :root[data-theme="auto"] {
210
+ @media (prefers-color-scheme: dark) {
211
+ @include wui-dark-theme;
212
+ color-scheme: dark;
213
+ }
214
+ }
@@ -18,6 +18,10 @@
18
18
  // This file is now an INDEX. The rules live in ./wui/*.scss, split by domain
19
19
  // because one 5,000-line stylesheet made finding a component a grep.
20
20
  //
21
+ // Shared tone-pill mixin — used by .badge, .status-label, and .chip.
22
+ // Must be imported before components that use it.
23
+ @import "wui/tone-pill";
24
+
21
25
  // The order below is LOAD-BEARING. A later partial may deliberately override an
22
26
  // earlier one, so reordering these imports silently changes the cascade — and
23
27
  // nothing in the build will complain. `pnpm check:theme` measures the rules that
@@ -35,6 +39,9 @@
35
39
  @import "wui/display";
36
40
  @import "wui/flow";
37
41
  @import "wui/scrollbar";
42
+ // After `navigation`/`overlays`: the switcher restates dropdown-item chrome those partials
43
+ // set, so it has to win on equal specificity.
44
+ @import "wui/workspace";
38
45
 
39
46
  // Flags belong here because <wui-phone> cannot render without them.
40
47
  //
@@ -44,7 +51,7 @@
44
51
  // and nothing failing anywhere to point at the cause. The component shipped; its styling did
45
52
  // not. That is the same escape as th.sortable and .u-name, and the fix is the same principle:
46
53
  // a component and the CSS it requires travel together.
47
- //
54
+
48
55
  // It is not free — roughly 183KB raw, 45KB gzipped, mostly inlined SVG. A product that never
49
56
  // renders a phone field can skip it by importing the `wui/*` partials above individually
50
57
  // instead of this aggregate; that is the deliberate opt-out, and it is a choice rather than
@@ -40,9 +40,9 @@
40
40
  // ── Variants ──────────────────────────────────────────────────────────────────
41
41
  // `default` is the flat surface above. The rest are opt-in visual treatments,
42
42
  // chosen by the `variant` prop (React) / attribute (Razor).
43
+ // Flat elevation: no in-page drop shadow — stronger border is the edge cue.
43
44
  .card-elevated {
44
- border-color: transparent;
45
- box-shadow: var(--shadow-md);
45
+ border-color: var(--color-border-strong);
46
46
  }
47
47
  .card-outlined {
48
48
  border-width: 2px;
@@ -171,12 +171,16 @@
171
171
  cursor: not-allowed;
172
172
  opacity: 0.6;
173
173
  }
174
- .search-select-control:hover:not(:disabled) {
174
+ .search-select-control:hover:not(:disabled):not(.open) {
175
+ // Not while open — an open control keeps its active (primary) border, not the grey hover one.
175
176
  border-color: var(--color-border-strong);
176
177
  }
177
178
  .search-select-control.open {
178
- background: var(--color-bg-muted);
179
- border-color: color-mix(in srgb, var(--color-primary) 30%, transparent);
179
+ // White fill + the active/focus border, matching a focused text input (which lifts off the
180
+ // muted resting fill onto white with a --wui-primary-30 border). Keeps the control seamless
181
+ // with the open menu and the floated label's white mask.
182
+ background: var(--color-surface);
183
+ border-color: var(--wui-primary-30);
180
184
  }
181
185
  .search-select-caret {
182
186
  flex-shrink: 0;
@@ -1095,7 +1099,8 @@ button.datepicker-month:hover {
1095
1099
  display: none !important;
1096
1100
  }
1097
1101
  .country-dropdown-item:hover,
1098
- .country-dropdown-item:focus {
1102
+ .country-dropdown-item:focus,
1103
+ .country-dropdown-item.is-active {
1099
1104
  background: var(--color-primary-soft) !important;
1100
1105
  color: var(--color-primary) !important;
1101
1106
  }
@@ -1120,9 +1125,11 @@ button.datepicker-month:hover {
1120
1125
  // On a tinted row the label must ride the row colour, not stay grey.
1121
1126
  .country-dropdown-item:hover .country-name-text,
1122
1127
  .country-dropdown-item:focus .country-name-text,
1128
+ .country-dropdown-item.is-active .country-name-text,
1123
1129
  .country-dropdown-item.selected .country-name-text,
1124
1130
  .country-dropdown-item:hover .country-code-text,
1125
1131
  .country-dropdown-item:focus .country-code-text,
1132
+ .country-dropdown-item.is-active .country-code-text,
1126
1133
  .country-dropdown-item.selected .country-code-text {
1127
1134
  color: currentColor;
1128
1135
  }