@webority/theme 0.9.2 → 0.9.4

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.9.2",
3
+ "version": "0.9.4",
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": {
@@ -1283,7 +1283,7 @@ wui-select[compact] {
1283
1283
  align-items: center;
1284
1284
  gap: 8px;
1285
1285
  padding: 9px 14px;
1286
- border: 1.5px solid var(--color-border);
1286
+ border: 1px solid var(--color-border);
1287
1287
  border-radius: 999px;
1288
1288
  font-size: 14px;
1289
1289
  font-weight: 500;
@@ -58,6 +58,19 @@
58
58
  align-items: center;
59
59
  text-align: center;
60
60
  padding: 40px 24px;
61
+ // An empty ledger still renders its header row, whose nowrap cells can push the
62
+ // table wider than the viewport, so `.table-wrap` scrolls horizontally with no
63
+ // data to justify it. This state row spans the full scroll width, so without a
64
+ // pin the message centres over that width and slides off screen on mobile.
65
+ // Stick it to the scrollport's left edge and size it to the visible wrap width
66
+ // (`100cqw`, from the container on `.table-wrap`), so the message stays centred
67
+ // and fully in view no matter how far the header scrolls. `left: 0` is relative
68
+ // to the scroll container; on a wide screen `100cqw` equals the content width, so
69
+ // this is a no-op there.
70
+ position: sticky;
71
+ left: 0;
72
+ width: 100cqw;
73
+ box-sizing: border-box;
61
74
  }
62
75
  .tbl-empty-icon {
63
76
  display: inline-grid;
@@ -96,10 +109,23 @@
96
109
  }
97
110
  // The empty state owns its own padding and is not a row you can act on — no
98
111
  // cell padding, no border, and no hover fill from .table / .table-ledger.
99
- .table td.wui-table-empty:has(> .tbl-empty) {
112
+ .table td.wui-table-empty:has(> .tbl-empty),
113
+ .table td.wui-table-empty:has(> .tbl-empty-plain) {
100
114
  padding: 0;
101
115
  border-bottom: 0;
102
116
  }
117
+ // A plain-string empty (no `.tbl-empty` wrapper) — carries the padding the cell
118
+ // used to own, plus the same viewport pin as `.tbl-empty` so it stays on screen
119
+ // when a narrow ledger scrolls horizontally (see the pin note on `.tbl-empty`).
120
+ .tbl-empty-plain {
121
+ padding: 36px 24px;
122
+ text-align: center;
123
+ color: var(--color-text-muted);
124
+ position: sticky;
125
+ left: 0;
126
+ width: 100cqw;
127
+ box-sizing: border-box;
128
+ }
103
129
  .table tbody tr:hover td.wui-table-empty:has(> .tbl-empty),
104
130
  .table-ledger tbody tr:hover td.wui-table-empty:has(> .tbl-empty) {
105
131
  background: transparent;
@@ -253,6 +279,11 @@
253
279
  // class (set --wui-table-pad) without overriding this selector. Bottom padding is now
254
280
  // included (was 0) so the last row isn't flush against the card edge.
255
281
  padding: var(--wui-table-pad, 20px);
282
+ // Query container for the empty-state pin below. The header row can force the
283
+ // ledger wider than the viewport, so on a narrow screen `.table-wrap` scrolls
284
+ // horizontally; `100cqw` here is the *visible* wrap width regardless of that
285
+ // scroll, which is what keeps the empty message on screen (see `.tbl-empty`).
286
+ container-type: inline-size;
256
287
  }
257
288
  .table-ledger {
258
289
  border-spacing: 0;
@@ -893,6 +893,10 @@
893
893
  border: 1px dashed color-mix(in srgb, var(--color-border-strong) 75%, transparent);
894
894
  cursor: pointer;
895
895
  transition: border-color 0.18s;
896
+ // Query container so the overlay can drop its label on a small ring (see the @container
897
+ // rule after .set-uploader-overlay). container-type also makes the ring the containing
898
+ // block for the absolute overlay — same role its position:relative already plays.
899
+ container-type: inline-size;
896
900
  }
897
901
  .set-uploader-ring:hover,
898
902
  .set-uploader:hover .set-uploader-ring,
@@ -925,10 +929,16 @@
925
929
  flex-direction: column;
926
930
  align-items: center;
927
931
  justify-content: center;
928
- gap: 4px;
932
+ // Label + gap scale with the ring so a small (sm, 72px) ring's overlay stays legible and
933
+ // uncramped instead of wrapping a fixed 11px "Update photo" into a tight disc where it
934
+ // overlaps the initials. Clamped so md/lg land on the same 11px / 4px gap as before.
935
+ gap: clamp(2px, calc(var(--wui-logo-size) * 0.035), 4px);
936
+ padding: 0 6%;
929
937
  background: var(--color-scrim-strong);
930
938
  color: #fff;
931
- font-size: 11px;
939
+ font-size: clamp(9px, calc(var(--wui-logo-size) * 0.1), 11px);
940
+ line-height: 1.15;
941
+ text-align: center;
932
942
  font-weight: 600;
933
943
  opacity: 0;
934
944
  transition: opacity 0.18s;
@@ -938,6 +948,62 @@
938
948
  .set-uploader:focus-visible .set-uploader-overlay {
939
949
  opacity: 1;
940
950
  }
951
+ // A ring too small to fit "Update photo" without cramping it over the initials (sm = 72px,
952
+ // and any custom size below ~95px) drops the label and shows the icon alone — a compact edit
953
+ // affordance. The lone icon grows a little so it reads as the whole overlay's cue. Driven by a
954
+ // container query on the ring, so it works for every size, preset or custom px.
955
+ @container (max-width: 95px) {
956
+ .set-uploader-overlay > span {
957
+ display: none;
958
+ }
959
+ .set-uploader-overlay > .wui-icon {
960
+ width: calc(var(--wui-logo-size) * 0.34);
961
+ height: calc(var(--wui-logo-size) * 0.34);
962
+ }
963
+ // The label has no room in the ring, so surface it as a hover/focus tooltip below the ring
964
+ // (reusing the AppTooltip surface look). Text comes from data-overlay-label on the ring — a
965
+ // distinct attribute from data-wui-tip so the global tooltip rule never fires this on a large
966
+ // ring. Only rendered inside this container query, so md/lg (label visible) never get it.
967
+ .set-uploader-ring[data-overlay-label]::after {
968
+ content: attr(data-overlay-label);
969
+ position: absolute;
970
+ top: calc(100% + 8px);
971
+ left: 50%;
972
+ transform: translateX(-50%);
973
+ z-index: var(--wui-z-menu, 1080);
974
+ width: max-content;
975
+ max-width: 160px;
976
+ padding: 6px 10px;
977
+ border-radius: var(--radius, 10px);
978
+ corner-shape: squircle;
979
+ background: var(--color-surface);
980
+ color: var(--color-text);
981
+ border: 1px solid var(--color-border-light);
982
+ box-shadow: var(--wui-shadow-overlay);
983
+ font-size: 0.75rem;
984
+ font-weight: 500;
985
+ line-height: 1.3;
986
+ white-space: nowrap;
987
+ pointer-events: none;
988
+ opacity: 0;
989
+ visibility: hidden;
990
+ transition:
991
+ opacity 0.12s ease,
992
+ visibility 0.12s;
993
+ }
994
+ // :not(:disabled) — a disabled/loading picker can't be updated, so it shows no tooltip
995
+ // (mirrors the overlay, which isn't rendered in those states).
996
+ .set-uploader:not(:disabled):hover .set-uploader-ring[data-overlay-label]::after,
997
+ .set-uploader:not(:disabled):focus-visible .set-uploader-ring[data-overlay-label]::after {
998
+ opacity: 1;
999
+ visibility: visible;
1000
+ }
1001
+ @media (prefers-reduced-motion: reduce) {
1002
+ .set-uploader-ring[data-overlay-label]::after {
1003
+ transition: none;
1004
+ }
1005
+ }
1006
+ }
941
1007
  // While uploading: keep the overlay (spinner) visible and signal it's not
942
1008
  // clickable.
943
1009
  .set-uploader-overlay.is-loading {
@@ -213,14 +213,12 @@
213
213
  font-weight: 600;
214
214
  }
215
215
  // Static fallback underline (no-JS / first paint); the sliding ink bar takes over once
216
- // wired. Inset 10px (tab padding is 14px) so the bar runs the label width + 4px each side
217
- // — reads as belonging to the label yet never touches a rounded container edge. Matches
218
- // the wired ink bar's inset below.
216
+ // wired. Spans the tab's full width (inset defaults to 0), matching the wired ink bar below.
219
217
  .app-tab.active::after {
220
218
  content: "";
221
219
  position: absolute;
222
- left: var(--wui-tab-underline-inset, 10px);
223
- right: var(--wui-tab-underline-inset, 10px);
220
+ left: var(--wui-tab-underline-inset, 0px);
221
+ right: var(--wui-tab-underline-inset, 0px);
224
222
  bottom: -1px;
225
223
  height: 2px;
226
224
  border-radius: 2px;
@@ -268,18 +266,22 @@
268
266
  left: 0;
269
267
  bottom: -1px;
270
268
  height: 2px;
271
- // Inset 10px each side (tab padding is 14px) so the bar runs the label width + 4px per
272
- // side — clear of a rounded container edge; falls to 0 rather than going negative on a
273
- // tab narrower than the inset. Pills reset the inset to 0 (the pill fills the tab).
274
- width: max(0px, calc(var(--wui-tab-w, 0) - var(--wui-tab-underline-inset, 10px) * 2));
269
+ // Spans the tab's FULL width (the inset defaults to 0). A consumer can inset it via
270
+ // `--wui-tab-underline-inset`; falls to 0 rather than going negative on a tab narrower than
271
+ // any inset it sets. Pills keep the inset at 0 too (the pill fills the tab).
272
+ width: max(0px, calc(var(--wui-tab-w, 0) - var(--wui-tab-underline-inset, 0px) * 2));
275
273
  background: var(--color-primary);
276
274
  border-radius: 2px;
277
- transform: translateX(calc(var(--wui-tab-x, 0) + var(--wui-tab-underline-inset, 10px)));
275
+ transform: translateX(calc(var(--wui-tab-x, 0) + var(--wui-tab-underline-inset, 0px)));
278
276
  opacity: 0;
279
277
  pointer-events: none;
278
+ // Smooth ease-OUT (no overshoot), not the elastic bounce: a full-width bar sits flush to the
279
+ // edge tabs, so an elastic overshoot would push it past the strip and the `overflow-x: auto`
280
+ // clip would shear off its rounded corner. A decelerating slide reaches the tab exactly and
281
+ // never crosses the edge, so the radius is always intact.
280
282
  transition:
281
- transform 0.42s var(--wui-ease-elastic),
282
- width 0.42s var(--wui-ease-elastic),
283
+ transform 0.42s var(--wui-ease-out-quart),
284
+ width 0.42s var(--wui-ease-out-quart),
283
285
  opacity 0.15s ease;
284
286
  }
285
287
  .app-tabs[data-wui-tabs] .app-tab-indicator {
@@ -600,13 +600,17 @@
600
600
 
601
601
  display: flex;
602
602
  align-items: center;
603
+ // On phones the trailing action drops onto its own row BELOW the content (the flex `gap`
604
+ // supplies the vertical space) so a button never crushes the message into a sliver; from md
605
+ // up it sits inline at the right — a VIEWPORT rule, not the callout's own width. See the
606
+ // `.info-callout-action` breakpoint below.
607
+ flex-wrap: wrap;
603
608
  gap: 12px;
604
609
  padding: 14px 18px;
605
610
  border: 1px solid var(--callout-border);
606
611
  // A thick coloured left edge — the callout's signature aside marker.
607
612
  border-left: 4px solid var(--callout-accent);
608
- border-radius: 20px;
609
- corner-shape: squircle;
613
+ border-radius: 8px;
610
614
  background: var(--callout-bg);
611
615
  font-size: 14px;
612
616
  // Sub-content (the body/subtitle) reads at #252F4A / 400; the heading and the
@@ -690,14 +694,30 @@
690
694
  min-width: 0;
691
695
  font-size: 12px;
692
696
  }
693
- // Trailing action slot — a button (or any node) pinned to the right, vertically centred.
697
+ // Trailing action slot — a button (or any node). Mobile-first: on phones it takes its own full
698
+ // row below the content (left-aligned there); from md up it collapses to its natural width and
699
+ // sits inline at the right edge of the content row.
694
700
  .info-callout-action {
695
- flex: none;
701
+ flex: 1 1 100%;
696
702
  align-self: center;
697
- display: inline-flex;
703
+ display: flex;
704
+ justify-content: flex-start;
698
705
  align-items: center;
699
706
  gap: 8px;
700
707
  }
708
+ // From md up the row never wraps — the content shrinks to seat the action inline at the right,
709
+ // so a button stays beside the text on any large screen, even when the callout sits in a narrow
710
+ // column (the wrap-below is a phones-only treatment).
711
+ @include media-breakpoint-up(md) {
712
+ .info-callout {
713
+ flex-wrap: nowrap;
714
+ }
715
+ .info-callout-action {
716
+ flex: none;
717
+ justify-content: initial;
718
+ margin-left: auto;
719
+ }
720
+ }
701
721
  .info-callout-heading {
702
722
  font-size: 14px;
703
723
  font-weight: 600;
@@ -751,9 +771,8 @@
751
771
  // the heading to the variant. Only the heading takes the variant colour — the body
752
772
  // stays dark ink so the message reads calmly.
753
773
  .alert {
754
- --bs-alert-border-radius: 20px;
755
- border-radius: 20px;
756
- corner-shape: squircle;
774
+ --bs-alert-border-radius: 8px;
775
+ border-radius: 8px;
757
776
  padding: 16px 18px;
758
777
  border-width: 1px;
759
778
  }