@webority/theme 0.102.3 → 0.104.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.102.3",
3
+ "version": "0.104.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,7 +51,7 @@
51
51
  "country-flag-icons": "^1.6.20",
52
52
  "sass-embedded": "^1.97.0",
53
53
  "vite": "^8.0.12",
54
- "@webority/tokens": "0.102.3"
54
+ "@webority/tokens": "0.104.0"
55
55
  },
56
56
  "scripts": {
57
57
  "tokens": "node ../tokens/dist/webority.js brand --library --out ../tokens/dist --web scss/_tokens.generated.scss",
@@ -1,4 +1,4 @@
1
- // Generated by @webority/tokens 0.102.3 from its token source. Do not edit; run webority brand --library.
1
+ // Generated by @webority/tokens 0.104.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;
@@ -625,6 +625,15 @@
625
625
  scrollbar-color: var(--wui-scrollbar) transparent;
626
626
  }
627
627
  }
628
+ // AppDropdown menuWidth: a fixed menu width, so long rows wrap instead of widening the menu.
629
+ .wui-dropdown-menu-sm { width: 15rem; }
630
+ .wui-dropdown-menu-md { width: 20rem; }
631
+ .wui-dropdown-menu-lg { width: 25rem; }
632
+ .wui-dropdown-menu-sm .dropdown-item,
633
+ .wui-dropdown-menu-md .dropdown-item,
634
+ .wui-dropdown-menu-lg .dropdown-item {
635
+ white-space: normal;
636
+ }
628
637
 
629
638
  .dropdown-item {
630
639
  display: flex;
@@ -669,6 +678,15 @@
669
678
  }
670
679
  }
671
680
 
681
+ // Listed but unchoosable (AppDropdown disabled): quiet, no hover or press tint.
682
+ .dropdown-item.wui-dropdown-item-disabled,
683
+ .dropdown-item.wui-dropdown-item-disabled:hover,
684
+ .dropdown-item.wui-dropdown-item-disabled:active {
685
+ color: var(--color-text-subtle);
686
+ cursor: not-allowed;
687
+ background: transparent;
688
+ }
689
+
672
690
  // The form of a posting dropdown item takes no box, so its item stays a plain row of the menu.
673
691
  .wui-dropdown-form {
674
692
  display: contents;
@@ -1,4 +1,4 @@
1
- // Generated by @webority/tokens 0.102.3 from its token source. Do not edit; run webority brand --library.
1
+ // Generated by @webority/tokens 0.104.0 from its token source. Do not edit; run webority brand --library.
2
2
 
3
3
  :root {
4
4
  --wui-primary: #4F46E5;
@@ -28,11 +28,11 @@
28
28
  outline: var(--wui-focus-ring-width) solid var(--color-focus-ring);
29
29
  outline-offset: var(--wui-focus-ring-offset);
30
30
  }
31
- .struct-card.checked {
31
+ .struct-card:has(.struct-card-input:checked) {
32
32
  border-color: color-mix(in srgb, var(--color-primary) 30%, transparent);
33
33
  background: color-mix(in srgb, var(--color-primary) 5%, transparent);
34
34
  }
35
- .struct-card.checked .struct-card-title {
35
+ .struct-card:has(.struct-card-input:checked) .struct-card-title {
36
36
  color: var(--color-primary-text);
37
37
  }
38
38
 
@@ -44,7 +44,7 @@
44
44
  .struct-card:has(.struct-card-input:disabled):hover {
45
45
  border-color: var(--color-border);
46
46
  }
47
- .struct-card.checked:has(.struct-card-input:disabled):hover {
47
+ .struct-card:has(.struct-card-input:checked):has(.struct-card-input:disabled):hover {
48
48
  border-color: color-mix(in srgb, var(--color-primary) 30%, transparent);
49
49
  }
50
50
  .struct-card:has(.struct-card-input:disabled):active {
@@ -69,10 +69,27 @@
69
69
  background 0.12s,
70
70
  color 0.12s;
71
71
  }
72
- .struct-card.checked .struct-card-ic {
72
+ .struct-card:has(.struct-card-input:checked) .struct-card-ic {
73
73
  background: var(--color-surface);
74
74
  color: var(--color-primary-text);
75
75
  }
76
+ // Opt-in (selected-style="filled"): the checked card's icon disc fills with primary. The hover
77
+ // rule above is more specific, so the hovered, enabled card repeats the selector to keep its glyph on-primary.
78
+ .wui-struct-card-filled:has(.struct-card-input:checked) .struct-card-ic,
79
+ .wui-struct-card-filled:has(.struct-card-input:checked):hover:not(:has(.struct-card-input:disabled)) .struct-card-ic {
80
+ background: var(--color-primary);
81
+ color: var(--color-on-primary);
82
+ }
83
+ // The linear and bold glyphs are both rendered; the input's checked state picks one.
84
+ .wui-struct-card-glyph-on {
85
+ display: none;
86
+ }
87
+ .struct-card:has(.struct-card-input:checked) .wui-struct-card-glyph {
88
+ display: none;
89
+ }
90
+ .struct-card:has(.struct-card-input:checked) .wui-struct-card-glyph-on {
91
+ display: inline-flex;
92
+ }
76
93
  // Hover previews the chosen glyph colour; a card that cannot change keeps its grey.
77
94
  .struct-card:hover:not(:has(.struct-card-input:disabled)) .struct-card-ic {
78
95
  color: var(--color-primary-text);
@@ -118,7 +135,7 @@
118
135
  opacity 0.12s ease,
119
136
  transform 0.12s ease;
120
137
  }
121
- .struct-card.checked .struct-card-check {
138
+ .struct-card:has(.struct-card-input:checked) .struct-card-check {
122
139
  opacity: 1;
123
140
  transform: scale(1);
124
141
  }
@@ -211,7 +228,7 @@
211
228
  }
212
229
  // Drop the icon disc — the illustration stands on its own, sized large.
213
230
  .struct-card-tile .struct-card-ic,
214
- .struct-card-tile.checked .struct-card-ic {
231
+ .struct-card-tile:has(.struct-card-input:checked) .struct-card-ic {
215
232
  width: auto;
216
233
  height: auto;
217
234
  border-radius: 0;
@@ -364,6 +364,14 @@
364
364
  .form-switch-pill .form-check-input {
365
365
  corner-shape: squircle;
366
366
  }
367
+
368
+ // Round checkbox (app-checkbox shape="circle"): the same glyph, ring, disabled and error states
369
+ // as the square box, drawn as a circle. The wrapper class and the type selector outrank the square
370
+ // box's own radius rule.
371
+ .form-check-circle .form-check-input[type="checkbox"] {
372
+ border-radius: 50%;
373
+ corner-shape: round;
374
+ }
367
375
  @media (prefers-reduced-motion: reduce) {
368
376
  .form-check-input,
369
377
  .wui-check,
@@ -1723,6 +1731,17 @@ wui-select[compact] {
1723
1731
  background: var(--color-surface);
1724
1732
  color: var(--color-primary-text);
1725
1733
  }
1734
+ // variant="solid": the checked chip fills with primary and its count turns a translucent on-primary pill.
1735
+ .wui-choice-chip-solid:has(input:checked),
1736
+ .wui-choice-chip-solid:has(input:checked):hover {
1737
+ border-color: transparent;
1738
+ background: var(--color-primary);
1739
+ color: var(--color-on-primary);
1740
+ }
1741
+ .wui-choice-chip-solid:has(input:checked) .count {
1742
+ background: color-mix(in srgb, var(--color-on-primary) 22%, transparent);
1743
+ color: inherit;
1744
+ }
1726
1745
 
1727
1746
  // Size scale (`size`): height, padding, gap and font step together; md is the base above.
1728
1747
  .choice-chip-sm {
@@ -1380,6 +1380,14 @@
1380
1380
  background: color-mix(in srgb, var(--color-danger) 10%, transparent);
1381
1381
  color: var(--color-danger-text);
1382
1382
  }
1383
+ // A neutral action: muted ink at rest, a muted surface and body ink on hover (an action on a card or a quiet surface).
1384
+ .u-action.neutral {
1385
+ color: var(--color-text-muted);
1386
+ }
1387
+ .u-action.neutral:hover:not(:disabled) {
1388
+ background: var(--color-bg-muted);
1389
+ color: var(--color-text);
1390
+ }
1383
1391
  .u-action.u-action-preview {
1384
1392
  background: var(--color-primary-soft);
1385
1393
  color: var(--color-primary-text);
@@ -318,6 +318,45 @@
318
318
  }
319
319
  }
320
320
 
321
+ // The back arrow before a modal's title: the close button's size, shape and colours, on the start side.
322
+ .wui-modal-back {
323
+ margin-inline-end: var(--wui-space-xs);
324
+ display: grid;
325
+ place-items: center;
326
+ flex: none;
327
+ width: var(--wui-control-md);
328
+ height: var(--wui-control-md);
329
+ padding: 0;
330
+ border: none;
331
+ cursor: pointer;
332
+ background: var(--color-bg-subtle);
333
+ color: var(--color-text-muted);
334
+ border-radius: var(--wui-radius-lg);
335
+ corner-shape: squircle;
336
+ transition:
337
+ background 180ms cubic-bezier(0.4, 0, 0.2, 1),
338
+ color 180ms cubic-bezier(0.4, 0, 0.2, 1);
339
+ }
340
+ .wui-modal-back:hover {
341
+ background: var(--color-bg-muted);
342
+ color: var(--color-text);
343
+ transition:
344
+ background 280ms cubic-bezier(0.16, 1, 0.3, 1),
345
+ color 280ms cubic-bezier(0.16, 1, 0.3, 1);
346
+ }
347
+ .wui-modal-back:focus-visible {
348
+ outline: var(--wui-focus-ring-width) solid var(--color-focus-ring);
349
+ outline-offset: var(--wui-focus-ring-offset);
350
+ }
351
+ @media (prefers-reduced-motion: reduce) {
352
+ .wui-modal-back,
353
+ .wui-modal-back:hover {
354
+ transition:
355
+ background 1ms linear,
356
+ color 1ms linear;
357
+ }
358
+ }
359
+
321
360
  // Fullscreen size: the card is the whole viewport. Both surfaces put `wui-modal-fullscreen` on the
322
361
  // `.modal` shell (Razor's size="fullscreen", React's size="fullscreen"); neither sets a max-width.
323
362
  // Header and footer are flex: none, so they stay pinned while the body scrolls between them, and
@@ -16,7 +16,9 @@
16
16
  }
17
17
  .page-header.page-header-bar .page-title {
18
18
  flex: 0 1 auto;
19
- min-width: min(12ch, max-content);
19
+ // Zero, not a floor: the tools are `flex: none`, so the title must be the part that gives way.
20
+ // `min()` cannot take `max-content`, and an invalid floor falls back to `auto`, which never shrinks.
21
+ min-width: 0;
20
22
  font-size: 20px;
21
23
  overflow: hidden;
22
24
  text-overflow: ellipsis;
@@ -360,22 +360,70 @@
360
360
  // the brand row and needs none.
361
361
  .app-nav-group + .app-nav-group {
362
362
  margin-top: 20px;
363
- padding-top: 12px;
363
+ padding-top: var(--wui-space-md);
364
364
  border-top: 1px solid var(--color-border);
365
365
  }
366
366
  // A node the author places right after a group's links (`groups[].after` /
367
367
  // `<app-nav-group-after>`). Like sidebarStart it has no place on the 88px rail.
368
368
  .wui-nav-group-after {
369
369
  flex: none;
370
- margin-top: 4px;
370
+ margin-top: var(--wui-space-xs);
371
371
  }
372
372
  .app-nav-group-label {
373
373
  padding: 6px 14px;
374
374
  font-size: 12px;
375
- font-weight: 600;
375
+ font-weight: var(--wui-fw-semibold);
376
+ text-transform: uppercase;
377
+ letter-spacing: 0.06em;
378
+ color: var(--color-text-subtle);
379
+ }
380
+ // A collapsible group folds from this heading: the label's type on a full-width row, with the caret at the end.
381
+ .wui-nav-group-toggle {
382
+ display: flex;
383
+ align-items: center;
384
+ justify-content: space-between;
385
+ gap: var(--wui-space-sm);
386
+ width: 100%;
387
+ margin: 0;
388
+ padding: 6px 14px;
389
+ border: 0;
390
+ border-radius: var(--wui-radius-sm);
391
+ background: transparent;
392
+ font-family: inherit;
393
+ font-size: 12px;
394
+ font-weight: var(--wui-fw-semibold);
395
+ text-align: left;
376
396
  text-transform: uppercase;
377
397
  letter-spacing: 0.06em;
378
398
  color: var(--color-text-subtle);
399
+ cursor: pointer;
400
+ }
401
+ .wui-nav-group-toggle:focus-visible {
402
+ outline: var(--wui-focus-ring-width) solid var(--color-focus-ring);
403
+ outline-offset: var(--wui-focus-ring-inset);
404
+ }
405
+ .wui-nav-group-collapsible,
406
+ .wui-nav-group-text {
407
+ min-width: 0;
408
+ }
409
+ .wui-nav-group-caret {
410
+ flex: none;
411
+ display: inline-grid;
412
+ place-items: center;
413
+ transition: transform 0.2s var(--wui-ease-ui);
414
+ }
415
+ .wui-nav-group-folded > .wui-nav-group-toggle .wui-nav-group-caret {
416
+ transform: rotate(-90deg);
417
+ }
418
+ .app-shell:not(.collapsed) .wui-nav-group-folded > .wui-nav-group-body {
419
+ display: none;
420
+ }
421
+ // A sub-heading between runs of links inside a group.
422
+ .wui-nav-header {
423
+ padding: 6px 10px 2px 12px;
424
+ font-size: var(--wui-fs-xs);
425
+ font-weight: var(--wui-fw-semibold);
426
+ color: var(--color-text-subtle);
379
427
  }
380
428
 
381
429
  .app-nav-item {
@@ -473,7 +521,10 @@
473
521
  padding: 9px 0;
474
522
  }
475
523
  .app-shell.collapsed .app-nav-badge,
524
+ .app-shell.collapsed .wui-nav-new,
476
525
  .app-shell.collapsed .app-nav-group-label,
526
+ .app-shell.collapsed .wui-nav-group-toggle,
527
+ .app-shell.collapsed .wui-nav-header,
477
528
  .app-shell.collapsed .wui-nav-group-after,
478
529
  .app-shell.collapsed .app-sidebar-brand-text,
479
530
  .app-shell.collapsed .app-sidebar-start {
@@ -539,6 +590,19 @@
539
590
  color: var(--color-primary-text);
540
591
  }
541
592
 
593
+ // A "New" pill after a label, shown through a date (`new-until`). It takes the success
594
+ // tint the tone badge uses, so it reads green in every theme.
595
+ .wui-nav-new {
596
+ flex: none;
597
+ padding: 0 var(--wui-space-xs);
598
+ border-radius: var(--wui-radius-pill);
599
+ background: var(--color-success-soft);
600
+ font-size: var(--wui-fs-xs);
601
+ font-weight: var(--wui-fw-bold);
602
+ line-height: var(--wui-lh-heading);
603
+ color: var(--color-success-text);
604
+ }
605
+
542
606
  // ── Expandable sub-items ─────────────────────────────────────────────────────
543
607
  // A parent row that toggles a nested list. It reuses the .app-nav-item chrome but
544
608
  // is a <button>; the caret rotates open. The nested list animates its height via
@@ -918,11 +982,20 @@
918
982
  }
919
983
  .app-shell.app-shell.collapsed .app-nav-text,
920
984
  .app-shell.app-shell.collapsed .app-nav-badge,
985
+ .app-shell.app-shell.collapsed .wui-nav-new,
921
986
  .app-shell.app-shell.collapsed .app-nav-group-label,
987
+ .app-shell.app-shell.collapsed .wui-nav-header,
922
988
  .app-shell.app-shell.collapsed .wui-nav-group-after,
923
989
  .app-shell.app-shell.collapsed .app-sidebar-start {
924
990
  display: revert;
925
991
  }
992
+ // The drawer shows full labels even on the desktop collapsed state, so a folded group still folds here.
993
+ .app-shell.app-shell.collapsed .wui-nav-group-toggle {
994
+ display: flex;
995
+ }
996
+ .app-shell.app-shell.collapsed .wui-nav-group-folded > .wui-nav-group-body {
997
+ display: none;
998
+ }
926
999
  // Drawer always shows full labels — the compact rail control has no role here.
927
1000
  .app-shell.app-shell.collapsed .app-sidebar-start-compact {
928
1001
  display: none;
@@ -273,6 +273,16 @@
273
273
  color: currentColor;
274
274
  }
275
275
 
276
+ // A pressed toggle chip (aria-pressed="true" on its button) takes the primary look whatever its tone,
277
+ // so a script flips the look by flipping the attribute it already sets for screen readers.
278
+ .chip-clickable:has(> .chip-body[aria-pressed="true"]) {
279
+ @include wui-tone-pill-colors(primary, var(--color-primary-text));
280
+ }
281
+ .chip-clickable:has(> .chip-body[aria-pressed="true"]) .chip-ic,
282
+ .chip-clickable:has(> .chip-body[aria-pressed="true"]) .chip-remove {
283
+ color: currentColor;
284
+ }
285
+
276
286
  // Disabled — deliberately NOT opacity, and deliberately after the tones.
277
287
  //
278
288
  // `opacity: .55` fades the whole chip, label included, so a faded label can fall under the
@@ -441,6 +441,41 @@
441
441
  outline-offset: var(--wui-focus-ring-offset);
442
442
  border-radius: var(--wui-radius-sm);
443
443
  }
444
+ // AppDropdown toggleVariant="neutral": a custom trigger that keeps the surrounding font and colour, with a muted
445
+ // surface on hover and while open. The negative start margin keeps its text aligned with the text above it.
446
+ .wui-dropdown-trigger-neutral {
447
+ gap: var(--wui-space-xs);
448
+ margin-inline-start: calc(-1 * var(--wui-space-xs));
449
+ padding: var(--wui-space-xxs) var(--wui-space-xs);
450
+ border-radius: var(--wui-radius-sm);
451
+ transition: background 0.12s ease;
452
+ }
453
+ .wui-dropdown-trigger-neutral:hover,
454
+ .wui-dropdown-trigger-neutral[aria-expanded="true"] {
455
+ background: var(--color-bg-muted);
456
+ }
457
+
458
+ // A line of short facts ("1 Oct to 14 Oct . 3 days left . 4/9 Done"): muted small text, a 3px dot (the dot's own size) drawn between items.
459
+ // The dot is a pseudo-element, so screen readers never read it and no separator markup is written.
460
+ .wui-meta-list {
461
+ display: inline-flex;
462
+ align-items: center;
463
+ gap: var(--wui-space-sm);
464
+ font-size: var(--wui-fs-sm);
465
+ color: var(--color-text-muted);
466
+ white-space: nowrap;
467
+ }
468
+ .wui-meta-list > * + *::before {
469
+ content: "";
470
+ display: inline-block;
471
+ width: 3px;
472
+ height: 3px;
473
+ margin-inline-end: var(--wui-space-sm);
474
+ border-radius: 50%;
475
+ background: var(--color-text-muted);
476
+ opacity: 0.6;
477
+ vertical-align: middle;
478
+ }
444
479
 
445
480
  // AppPopover wrapper (React panel styling comes from Bootstrap's .popover; this is only the
446
481
  // Razor anchor span carrying the data-bs-* wiring). inline-block so it hugs its trigger and