@webority/theme 0.7.8 → 0.8.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.7.8",
3
+ "version": "0.8.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": {
@@ -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
@@ -47,10 +51,14 @@
47
51
  // and nothing failing anywhere to point at the cause. The component shipped; its styling did
48
52
  // not. That is the same escape as th.sortable and .u-name, and the fix is the same principle:
49
53
  // a component and the CSS it requires travel together.
50
- //
54
+
51
55
  // It is not free — roughly 183KB raw, 45KB gzipped, mostly inlined SVG. A product that never
52
56
  // renders a phone field can skip it by importing the `wui/*` partials above individually
53
57
  // instead of this aggregate; that is the deliberate opt-out, and it is a choice rather than
54
58
  // an accident.
55
59
  @import "flags";
56
60
  @import "wui/shell";
61
+ @import "wui/dock";
62
+ @import "wui/toolbar";
63
+ // Last: restates the settled state of menus other partials style, so it must win.
64
+ @import "wui/menu-motion";
@@ -7,6 +7,23 @@
7
7
  // Part of the wui-components layer — see _wui-components.scss for the load
8
8
  // order. Order within the layer is LOAD-BEARING; never reorder the imports.
9
9
 
10
+ // Default (md) card inset — a uniform 20px on the header, body and footer, driven by one
11
+ // friendly token. A consumer class can retune the padding by setting --wui-card-pad (e.g.
12
+ // `.tight-card { --wui-card-pad: 12px }` with `className`/`class` on the card) without
13
+ // overriding a selector. The padding-scale classes (.card-compact, .card-pad-*) still win —
14
+ // they set `padding` directly on the header/body/footer.
15
+ .card {
16
+ --wui-card-pad: 20px;
17
+ --bs-card-spacer-y: var(--wui-card-pad);
18
+ --bs-card-spacer-x: var(--wui-card-pad);
19
+ --bs-card-cap-padding-y: var(--wui-card-pad);
20
+ --bs-card-cap-padding-x: var(--wui-card-pad);
21
+ // Default card is borderless — a flat surface. The border-STYLE stays solid (Bootstrap's
22
+ // `border: var(--bs-card-border-width) solid …`), so a variant or a consumer class can
23
+ // bring the edge back just by giving it a width (set --bs-card-border-width).
24
+ --bs-card-border-width: 0;
25
+ }
26
+
10
27
  // A card header that carries a title on the left and controls on the right — the
11
28
  // pattern nearly every panel header uses, so it belongs here rather than being
12
29
  // re-flexed with utilities on each one.
@@ -40,8 +57,10 @@
40
57
  // ── Variants ──────────────────────────────────────────────────────────────────
41
58
  // `default` is the flat surface above. The rest are opt-in visual treatments,
42
59
  // chosen by the `variant` prop (React) / attribute (Razor).
43
- // Flat elevation: no in-page drop shadow — stronger border is the edge cue.
60
+ // Flat elevation: no in-page drop shadow — stronger border is the edge cue. The default card
61
+ // is borderless, so re-assert a width here (this variant's whole cue is that border).
44
62
  .card-elevated {
63
+ --bs-card-border-width: 1px;
45
64
  border-color: var(--color-border-strong);
46
65
  }
47
66
  .card-outlined {
@@ -95,14 +95,29 @@
95
95
  .wui-check:hover:not(:disabled):not(:checked),
96
96
  .wui-radio:hover:not(:disabled):not(:checked) {
97
97
  border-color: var(--color-primary);
98
- box-shadow: 0 0 0 4px var(--color-primary-soft);
98
+ box-shadow: 0 0 0 2px var(--color-primary-soft);
99
99
  }
100
+ // Keyboard focus keeps a restrained brand ring — a keyboard user MUST see where focus is
101
+ // (a11y). :focus-visible matches keyboard/AT focus only, never a plain mouse click.
100
102
  .form-check-input:focus-visible,
101
103
  .wui-check:focus-visible,
102
104
  .wui-radio:focus-visible {
103
105
  outline: none;
104
106
  border-color: var(--color-primary);
105
- box-shadow: 0 0 0 3px var(--color-primary-soft);
107
+ box-shadow: 0 0 0 2px var(--color-primary-soft);
108
+ }
109
+ // A plain MOUSE click must leave NO lingering focus/active glow, ring, border tint or
110
+ // darkening — Bootstrap paints all of them on :focus/:active. Neutralise them for pointer
111
+ // focus + active while the keyboard ring above stays, so a mouse click just toggles cleanly.
112
+ .form-check-input:focus:not(:focus-visible),
113
+ .wui-check:focus:not(:focus-visible),
114
+ .wui-radio:focus:not(:focus-visible),
115
+ .form-check-input:active,
116
+ .wui-check:active,
117
+ .wui-radio:active {
118
+ outline: none;
119
+ box-shadow: none;
120
+ filter: none;
106
121
  }
107
122
  .form-check-input:checked,
108
123
  .wui-check:checked,
@@ -167,7 +182,7 @@
167
182
  }
168
183
  .form-switch .form-check-input:hover:not(:disabled):not(:checked) {
169
184
  border-color: transparent;
170
- box-shadow: 0 0 0 4px var(--color-primary-soft);
185
+ box-shadow: 0 0 0 2px var(--color-primary-soft);
171
186
  }
172
187
  .form-switch .form-check-input:checked {
173
188
  border-color: transparent;
@@ -176,6 +191,39 @@
176
191
  // Re-assert the knob — the checkbox :checked tick has equal specificity.
177
192
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 20 20'%3E%3Ccircle cx='10' cy='10' r='7' fill='white'/%3E%3C/svg%3E");
178
193
  }
194
+ // On a mouse click the switch's track border reverts to its resting transparent — Bootstrap's
195
+ // :focus otherwise tints it brand-blue, leaving a coloured outline after the click. The shared
196
+ // :focus:not(:focus-visible) rule already dropped the glow; this drops the border tint too.
197
+ .form-switch .form-check-input:focus:not(:focus-visible) {
198
+ border-color: transparent;
199
+ }
200
+
201
+ // Circular variant (shape="circle") — a compact round on/off button instead of a sliding
202
+ // pill. The track is a square-sized circle; the knob dot stays CENTRED in both states (no
203
+ // left/right slide) and the fill flips to primary when on. Placed after the base switch
204
+ // block so the equal-specificity :checked position override wins on source order.
205
+ .form-switch-circle .form-check-input {
206
+ width: 24px;
207
+ height: 24px;
208
+ // The switch is a type="checkbox", which carries `corner-shape: squircle` — on a square
209
+ // that renders a rounded square, not a circle. Reset to a true round corner.
210
+ corner-shape: round;
211
+ border-radius: 999px;
212
+ background-position: center;
213
+ background-size: 10px 10px;
214
+ }
215
+ .form-switch-circle .form-check-input:checked {
216
+ background-position: center;
217
+ }
218
+
219
+ // Rounded-pill variant (shape="round") — the same sliding pill, but with truly circular
220
+ // ends. The switch is a type=checkbox, so it inherits the checkbox's default
221
+ // `corner-shape: squircle`, which superellipses the pill's ends; reset it to `round` for
222
+ // the fully semicircular track in the reference. Only the corner shape changes — size,
223
+ // knob and slide are the base pill's.
224
+ .form-switch-round .form-check-input {
225
+ corner-shape: round;
226
+ }
179
227
  @media (prefers-reduced-motion: reduce) {
180
228
  .form-check-input,
181
229
  .wui-check,
@@ -1050,30 +1098,21 @@ wui-select[compact] {
1050
1098
  border: 1px solid var(--color-border);
1051
1099
  }
1052
1100
  .badge-primary {
1053
- background: var(--color-primary-soft);
1054
- color: var(--color-primary);
1101
+ @include wui-tone-pill-colors(primary, var(--color-primary));
1055
1102
  border-color: color-mix(in srgb, var(--color-primary) 25%, transparent);
1056
1103
  }
1057
1104
  .badge-success {
1058
- background: var(--color-success-soft);
1059
- color: var(--color-success-text);
1060
- border-color: transparent;
1105
+ @include wui-tone-pill-colors(success);
1061
1106
  }
1062
1107
  .badge-warning,
1063
1108
  .badge-warn {
1064
- background: var(--color-warning-soft);
1065
- color: var(--color-warning-text);
1066
- border-color: transparent;
1109
+ @include wui-tone-pill-colors(warning);
1067
1110
  }
1068
1111
  .badge-danger {
1069
- background: var(--color-danger-soft);
1070
- color: var(--color-danger-text);
1071
- border-color: transparent;
1112
+ @include wui-tone-pill-colors(danger);
1072
1113
  }
1073
1114
  .badge-info {
1074
- background: var(--color-info-soft);
1075
- color: var(--color-info-text);
1076
- border-color: transparent;
1115
+ @include wui-tone-pill-colors(info);
1077
1116
  }
1078
1117
  .badge-dot::before {
1079
1118
  content: "";
@@ -1109,29 +1148,24 @@ wui-select[compact] {
1109
1148
  .status-label.success,
1110
1149
  .status-label.tone-ok,
1111
1150
  .status-label.tone-green {
1112
- background: color-mix(in srgb, var(--color-success) 16%, transparent);
1113
- color: var(--color-success-text);
1151
+ @include wui-tone-pill-colors(success);
1114
1152
  }
1115
1153
  .status-label.warning,
1116
1154
  .status-label.tone-warn,
1117
1155
  .status-label.tone-amber {
1118
- background: color-mix(in srgb, var(--color-warning) 16%, transparent);
1119
- color: var(--color-warning-text);
1156
+ @include wui-tone-pill-colors(warning);
1120
1157
  }
1121
1158
  .status-label.danger,
1122
1159
  .status-label.tone-bad,
1123
1160
  .status-label.tone-red {
1124
- background: color-mix(in srgb, var(--color-danger) 16%, transparent);
1125
- color: var(--color-danger-text);
1161
+ @include wui-tone-pill-colors(danger);
1126
1162
  }
1127
1163
  .status-label.info,
1128
1164
  .status-label.tone-blue {
1129
- background: color-mix(in srgb, var(--color-info) 16%, transparent);
1130
- color: var(--color-info-text);
1165
+ @include wui-tone-pill-colors(info);
1131
1166
  }
1132
1167
  .status-label.primary {
1133
- background: var(--color-primary-soft);
1134
- color: var(--color-primary);
1168
+ @include wui-tone-pill-colors(primary, var(--color-primary));
1135
1169
  }
1136
1170
  // Default/neutral — also the bare `.status-label` look, so an unknown or omitted
1137
1171
  // tone still renders as a proper pill instead of transparent text.
@@ -249,7 +249,10 @@
249
249
  // floating 42px header band, airy rows on a hairline rule, and an inset from
250
250
  // the card edge. Column geometry is NOT set here.
251
251
  .table-wrap:has(> .table-ledger) {
252
- padding: 20px 20px 0;
252
+ // One token drives the whole inset so a consumer can retune the table's padding with a
253
+ // class (set --wui-table-pad) without overriding this selector. Bottom padding is now
254
+ // included (was 0) so the last row isn't flush against the card edge.
255
+ padding: var(--wui-table-pad, 20px);
253
256
  }
254
257
  .table-ledger {
255
258
  border-spacing: 0;
@@ -0,0 +1,102 @@
1
+ // ── Floating dock ──
2
+ // AppFloatingDock / <app-floating-dock> / <wui-dock> — a macOS-style pill of icon
3
+ // links. The element writes --wui-dock-mag (1 → max-scale) per item on pointermove;
4
+ // everything visual lives here. With no JS (or coarse pointer / reduced motion /
5
+ // `static`) the var stays 1 and the dock is a plain icon row.
6
+ //
7
+ // Part of the wui-components layer — see _wui-components.scss for load order.
8
+
9
+ wui-dock {
10
+ --dock-size: 44px;
11
+
12
+ display: inline-flex;
13
+ align-items: flex-end;
14
+ gap: 10px;
15
+ padding: 10px 14px;
16
+ background: var(--color-surface);
17
+ border: 1px solid var(--color-border);
18
+ border-radius: var(--wui-radius-xl);
19
+ corner-shape: squircle;
20
+ box-shadow: var(--wui-shadow-overlay);
21
+ // Item boxes grow on hover; anchoring the row height to the rest size keeps the
22
+ // pill from pushing surrounding layout while items magnify upward out of it.
23
+ height: calc(var(--dock-size) + 20px);
24
+ }
25
+
26
+ .dock-item {
27
+ position: relative;
28
+ display: grid;
29
+ place-items: center;
30
+ width: calc(var(--dock-size) * var(--wui-dock-mag, 1));
31
+ height: calc(var(--dock-size) * var(--wui-dock-mag, 1));
32
+ border: none;
33
+ border-radius: 50%;
34
+ background: var(--color-bg-muted);
35
+ color: var(--color-text-muted);
36
+ text-decoration: none;
37
+ cursor: pointer;
38
+ // Smooths the snap-back when the pointer leaves; during tracking the element
39
+ // updates per frame, so the short transition just softens the steps.
40
+ transition:
41
+ width 120ms var(--wui-ease-ui),
42
+ height 120ms var(--wui-ease-ui),
43
+ background-color 160ms var(--wui-ease-ui),
44
+ color 160ms var(--wui-ease-ui);
45
+ }
46
+ .dock-item:hover,
47
+ .dock-item:focus-visible {
48
+ background: var(--color-primary-soft);
49
+ color: var(--color-primary);
50
+ }
51
+ .dock-item:focus-visible {
52
+ outline: 2px solid var(--color-primary);
53
+ outline-offset: 2px;
54
+ // Keyboard users get no pointer tracking — grow the focused item itself.
55
+ --wui-dock-mag: 1.25;
56
+ }
57
+
58
+ .dock-item-icon {
59
+ display: grid;
60
+ place-items: center;
61
+ font-size: calc(18px * var(--wui-dock-mag, 1));
62
+ line-height: 1;
63
+ }
64
+ // Node icons (SVGs) don't inherit font-size — scale the box instead.
65
+ .dock-item-icon > svg {
66
+ transform: scale(var(--wui-dock-mag, 1));
67
+ }
68
+
69
+ .dock-item-tip {
70
+ position: absolute;
71
+ bottom: calc(100% + 10px);
72
+ left: 50%;
73
+ translate: -50% 4px;
74
+ padding: 4px 10px;
75
+ background: var(--color-surface);
76
+ border: 1px solid var(--color-border);
77
+ border-radius: var(--wui-radius-sm);
78
+ box-shadow: var(--wui-shadow-thumb);
79
+ color: var(--color-text);
80
+ font-size: 12px;
81
+ font-weight: 500;
82
+ line-height: 1.35;
83
+ white-space: nowrap;
84
+ pointer-events: none;
85
+ opacity: 0;
86
+ transition:
87
+ opacity 140ms var(--wui-ease-ui),
88
+ translate 140ms var(--wui-ease-ui);
89
+ z-index: var(--wui-z-tooltip);
90
+ }
91
+ .dock-item:hover .dock-item-tip,
92
+ .dock-item:focus-visible .dock-item-tip {
93
+ opacity: 1;
94
+ translate: -50% 0;
95
+ }
96
+
97
+ @media (prefers-reduced-motion: reduce) {
98
+ .dock-item,
99
+ .dock-item-tip {
100
+ transition: none;
101
+ }
102
+ }