@webority/theme 0.7.5 → 0.7.6

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.
@@ -6,26 +6,55 @@
6
6
  // here may be overridden by a later partial. Never reorder the imports.
7
7
 
8
8
  // ── Avatar ──
9
- // A photo fills the avatar disc edge to edge; the circle is the frame, so
10
- // the image is cropped to it rather than letterboxed.
9
+ // The initials/icon face is the base layer; a photo overlays it edge to edge, cropped
10
+ // to the disc. While the photo loads — or if it breaks — the face shows through.
11
+ .avatar-face {
12
+ display: inline-flex;
13
+ align-items: center;
14
+ justify-content: center;
15
+ line-height: 1;
16
+ }
11
17
  .avatar > .avatar-img {
18
+ position: absolute;
19
+ inset: 0;
12
20
  width: 100%;
13
21
  height: 100%;
14
22
  border-radius: inherit;
15
23
  object-fit: cover;
16
24
  display: block;
25
+ // Photo fades + scales in so a late-arriving blob doesn't pop.
26
+ animation: wui-avatar-in 0.28s var(--wui-ease-out-quart, ease) both;
17
27
  }
18
28
  .avatar {
19
- width: 32px;
20
- height: 32px;
29
+ // `--avatar-size` drives the default (md, its 32px fallback) and any custom size. It is
30
+ // read via `var(… , 32px)` rather than declared here, so an avatar with no size INHERITS
31
+ // one from an AppAvatarGroup that sets it — while a preset class (explicit width below) or
32
+ // an avatar's own inline `--avatar-size` still wins. Never re-declare it on `.avatar`.
33
+ position: relative;
34
+ width: var(--avatar-size, 32px);
35
+ height: var(--avatar-size, 32px);
21
36
  border-radius: 999px;
22
37
  background: color-mix(in srgb, var(--color-primary) 15%, var(--color-surface));
23
38
  color: var(--color-primary);
24
- font-size: 12px;
39
+ font-size: calc(var(--avatar-size, 32px) * 0.375);
25
40
  font-weight: 600;
26
41
  display: inline-flex;
27
42
  align-items: center;
28
43
  justify-content: center;
44
+ flex: none;
45
+ user-select: none;
46
+ }
47
+
48
+ // ── Size scale ──
49
+ .avatar-xs {
50
+ width: 20px;
51
+ height: 20px;
52
+ font-size: 8px;
53
+ }
54
+ .avatar-sm {
55
+ width: 24px;
56
+ height: 24px;
57
+ font-size: 10px;
29
58
  }
30
59
  .avatar-lg {
31
60
  width: 48px;
@@ -37,10 +66,230 @@
37
66
  height: 64px;
38
67
  font-size: 22px;
39
68
  }
40
- .avatar-sm {
41
- width: 24px;
42
- height: 24px;
69
+ .avatar-2xl {
70
+ width: 96px;
71
+ height: 96px;
72
+ font-size: 34px;
73
+ }
74
+
75
+ // ── Shape ── (`shape`): the disc is the default; rounded/square are card-like.
76
+ .avatar-rounded {
77
+ border-radius: 25%;
78
+ }
79
+ .avatar-square {
80
+ border-radius: 12%;
81
+ }
82
+
83
+ // ── Tone ── (`tone`): the initials fill. `auto` derives a stable hue from the name.
84
+ .avatar-neutral {
85
+ background: color-mix(in srgb, var(--color-text-muted) 16%, var(--color-surface));
86
+ color: var(--color-text-muted);
87
+ }
88
+ .avatar-success {
89
+ background: var(--color-success-soft);
90
+ color: var(--color-success-text);
91
+ }
92
+ .avatar-warning {
93
+ background: var(--color-warning-soft);
94
+ color: var(--color-warning-text);
95
+ }
96
+ .avatar-danger {
97
+ background: var(--color-danger-soft);
98
+ color: var(--color-danger-text);
99
+ }
100
+ .avatar-info {
101
+ background: var(--color-info-soft);
102
+ color: var(--color-info-text);
103
+ }
104
+ .avatar-auto {
105
+ background: hsl(var(--avatar-hue, 0) 60% 90%);
106
+ color: hsl(var(--avatar-hue, 0) 42% 32%);
107
+ }
108
+
109
+ // ── Ring ── (`ring`): a tone-coloured ring with a surface gap, separating stacked
110
+ // avatars or marking an active one.
111
+ .avatar-ring {
112
+ box-shadow:
113
+ 0 0 0 2px var(--color-surface),
114
+ 0 0 0 4px currentColor;
115
+ }
116
+
117
+ // ── Corner badge ── (`badge`): a small count/marker at the top-right.
118
+ .avatar-badge {
119
+ position: absolute;
120
+ top: -2px;
121
+ right: -2px;
122
+ display: inline-flex;
123
+ align-items: center;
124
+ justify-content: center;
125
+ min-width: 16px;
126
+ height: 16px;
127
+ padding: 0 4px;
128
+ border-radius: 999px;
129
+ background: var(--color-danger);
130
+ color: #fff;
43
131
  font-size: 10px;
132
+ font-weight: 700;
133
+ line-height: 1;
134
+ box-shadow: 0 0 0 2px var(--color-surface);
135
+ }
136
+
137
+ // ── Clickable ── (`href` / `onClick`): the whole disc is one control.
138
+ .avatar-clickable {
139
+ padding: 0;
140
+ border: 0;
141
+ font: inherit;
142
+ text-decoration: none;
143
+ cursor: pointer;
144
+ -webkit-appearance: none;
145
+ appearance: none;
146
+ transition:
147
+ transform 0.15s var(--wui-ease-out-quart, ease),
148
+ box-shadow 0.15s ease;
149
+ }
150
+ .avatar-clickable:hover {
151
+ transform: scale(1.06);
152
+ }
153
+ .avatar-clickable:focus-visible {
154
+ outline: 2px solid var(--color-primary);
155
+ outline-offset: 2px;
156
+ }
157
+
158
+ // ── Presence status ── (`status`): a dot at the lower-right, ringed in the surface
159
+ // colour so it reads on a photo. Online quietly pulses.
160
+ .avatar-status {
161
+ position: absolute;
162
+ right: 2%;
163
+ bottom: 2%;
164
+ width: 28%;
165
+ height: 28%;
166
+ min-width: 8px;
167
+ min-height: 8px;
168
+ max-width: 16px;
169
+ max-height: 16px;
170
+ border-radius: 999px;
171
+ box-shadow: 0 0 0 2px var(--color-surface);
172
+ }
173
+ .avatar-status-online {
174
+ background: var(--color-success);
175
+ }
176
+ .avatar-status-away {
177
+ background: var(--color-warning);
178
+ }
179
+ .avatar-status-busy {
180
+ background: var(--color-danger);
181
+ }
182
+ .avatar-status-offline {
183
+ background: color-mix(in srgb, var(--color-text-muted) 55%, var(--color-surface));
184
+ }
185
+ .avatar-status-online::after {
186
+ content: "";
187
+ position: absolute;
188
+ inset: 0;
189
+ border-radius: inherit;
190
+ background: var(--color-success);
191
+ animation: wui-avatar-ping 1.8s cubic-bezier(0, 0, 0.2, 1) infinite;
192
+ }
193
+
194
+ // ── Hover label ── (`label`): a tooltip above the avatar, revealed on hover/focus.
195
+ .avatar-label {
196
+ position: absolute;
197
+ bottom: calc(100% + 8px);
198
+ left: 50%;
199
+ transform: translateX(-50%) translateY(4px);
200
+ padding: 4px 8px;
201
+ border-radius: var(--radius-sm);
202
+ background: var(--color-text);
203
+ color: var(--color-surface);
204
+ font-size: 12px;
205
+ font-weight: 500;
206
+ line-height: 1.2;
207
+ white-space: nowrap;
208
+ opacity: 0;
209
+ pointer-events: none;
210
+ z-index: 30;
211
+ transition:
212
+ opacity 0.16s ease,
213
+ transform 0.16s var(--wui-ease-out-quart, ease);
214
+ }
215
+ .avatar-label::after {
216
+ content: "";
217
+ position: absolute;
218
+ top: 100%;
219
+ left: 50%;
220
+ transform: translateX(-50%);
221
+ border: 4px solid transparent;
222
+ border-top-color: var(--color-text);
223
+ }
224
+ .avatar:hover > .avatar-label,
225
+ .avatar:focus-visible > .avatar-label,
226
+ .avatar:focus-within > .avatar-label {
227
+ opacity: 1;
228
+ transform: translateX(-50%) translateY(0);
229
+ }
230
+
231
+ // ── Avatar group ── (AppAvatarGroup): a row of overlapping avatars, each ringed in the
232
+ // surface colour so it reads against its neighbour. Hover raises one to the front.
233
+ .avatar-group {
234
+ display: inline-flex;
235
+ align-items: center;
236
+ }
237
+ .avatar-group > .avatar {
238
+ margin-left: -10px;
239
+ box-shadow: 0 0 0 2px var(--color-surface);
240
+ transition: transform 0.15s var(--wui-ease-out-quart, ease);
241
+ }
242
+ .avatar-group > .avatar:first-child {
243
+ margin-left: 0;
244
+ }
245
+ .avatar-group > .avatar:hover {
246
+ z-index: 1;
247
+ transform: translateY(-2px);
248
+ }
249
+ .avatar-group-tight > .avatar {
250
+ margin-left: -14px;
251
+ }
252
+ .avatar-group-loose > .avatar {
253
+ margin-left: -4px;
254
+ }
255
+ // The "+N" overflow chip — a muted avatar-shaped tail.
256
+ .avatar-more {
257
+ background: color-mix(in srgb, var(--color-text-muted) 16%, var(--color-surface));
258
+ color: var(--color-text-muted);
259
+ font-weight: 600;
260
+ }
261
+
262
+ @keyframes wui-avatar-in {
263
+ from {
264
+ opacity: 0;
265
+ transform: scale(0.92);
266
+ }
267
+ to {
268
+ opacity: 1;
269
+ transform: scale(1);
270
+ }
271
+ }
272
+ @keyframes wui-avatar-ping {
273
+ 0% {
274
+ opacity: 0.5;
275
+ transform: scale(1);
276
+ }
277
+ 70%,
278
+ 100% {
279
+ opacity: 0;
280
+ transform: scale(2.2);
281
+ }
282
+ }
283
+ @media (prefers-reduced-motion: reduce) {
284
+ .avatar > .avatar-img {
285
+ animation: none;
286
+ }
287
+ .avatar-status-online::after {
288
+ animation: none;
289
+ }
290
+ .avatar-label {
291
+ transition: opacity 0.16s ease;
292
+ }
44
293
  }
45
294
 
46
295
  // ── File attach ──
@@ -161,7 +410,7 @@
161
410
  width: 28px;
162
411
  height: 28px;
163
412
  border: 0;
164
- background: rgba(0, 0, 0, 0.62);
413
+ background: var(--color-scrim-strong);
165
414
  backdrop-filter: blur(10px);
166
415
  -webkit-backdrop-filter: blur(10px);
167
416
  color: #fff;
@@ -524,8 +773,9 @@
524
773
  display: flex;
525
774
  align-items: center;
526
775
  justify-content: center;
527
- // Subtle dim so the glassy CTA reads on any image, without hiding it.
528
- background: rgba(16, 24, 40, 0.22);
776
+ // Subtle dim so the glassy CTA reads on any image, without hiding it —
777
+ // half the standard scrim strength.
778
+ background: color-mix(in srgb, var(--color-scrim) 50%, transparent);
529
779
  opacity: 0;
530
780
  transition: opacity 0.15s ease;
531
781
  }
@@ -560,7 +810,7 @@
560
810
  padding: 0;
561
811
  border: 2px solid transparent;
562
812
  border-radius: 50%;
563
- background: rgba(16, 24, 40, 0.55);
813
+ background: var(--color-scrim-strong);
564
814
  color: #fff;
565
815
  cursor: pointer;
566
816
  opacity: 0;
@@ -646,7 +896,7 @@
646
896
  align-items: center;
647
897
  justify-content: center;
648
898
  gap: 4px;
649
- background: rgba(20, 26, 33, 0.62);
899
+ background: var(--color-scrim-strong);
650
900
  color: #fff;
651
901
  font-size: 11px;
652
902
  font-weight: 600;
@@ -794,7 +1044,7 @@
794
1044
  align-items: center;
795
1045
  justify-content: center;
796
1046
  gap: 4px;
797
- background: var(--color-scrim);
1047
+ background: var(--color-scrim-strong);
798
1048
  color: var(--color-on-scrim);
799
1049
  font-size: 12px;
800
1050
  font-weight: 600;
@@ -54,7 +54,7 @@
54
54
  .drawer-backdrop {
55
55
  position: fixed;
56
56
  inset: 0;
57
- background: rgba(15, 23, 42, 0.4);
57
+ background: var(--color-scrim);
58
58
  opacity: 0;
59
59
  pointer-events: none;
60
60
  transition: opacity 0.2s;
@@ -15,7 +15,7 @@
15
15
  // Do NOT put layout on backdropClassName — that targets a sibling, not
16
16
  // .modal.
17
17
  .modal-backdrop.wui-modal-dim {
18
- background: rgba(15, 23, 42, 0.45);
18
+ background: var(--color-scrim);
19
19
  z-index: var(--wui-z-modal-backdrop);
20
20
  }
21
21
  .modal.wui-modal-shell {
@@ -328,3 +328,47 @@
328
328
  font-size: 14px;
329
329
  font-weight: 500;
330
330
  }
331
+
332
+ // ── Toast (notifications) ──
333
+ // Bootstrap toast styling + wui extensions for title/action buttons.
334
+ // Used by both React AppToastContainer and Razor WUI.toast.
335
+
336
+ // Toast header with title
337
+ .wui-toast-header {
338
+ display: flex;
339
+ align-items: center;
340
+ justify-content: space-between;
341
+ gap: 12px;
342
+ padding: 10px 12px;
343
+ border: none;
344
+ }
345
+
346
+ .wui-toast-header strong {
347
+ font-size: 14px;
348
+ font-weight: 600;
349
+ color: inherit;
350
+ }
351
+
352
+ // Toast action button — inherits text color from the variant tone. Scoped to the
353
+ // component class, not every .btn-link a consumer might place in a toast body.
354
+ .toast .wui-toast-action {
355
+ color: inherit;
356
+ text-decoration: none;
357
+ font-size: 13px;
358
+ font-weight: 500;
359
+ padding: 2px 8px;
360
+ white-space: nowrap;
361
+ flex: none;
362
+ opacity: 0.9;
363
+ transition: opacity 150ms;
364
+ }
365
+
366
+ .toast .wui-toast-action:hover {
367
+ opacity: 1;
368
+ text-decoration: underline;
369
+ }
370
+
371
+ .toast .wui-toast-action:focus-visible {
372
+ outline: 2px solid currentColor;
373
+ outline-offset: 2px;
374
+ }
@@ -279,6 +279,22 @@ wui-multiselect[collapse-chips] .ms-chips {
279
279
  align-items: center;
280
280
  gap: 8px;
281
281
  }
282
+ .ms-input {
283
+ flex: 1;
284
+ min-width: 60px;
285
+ border: none;
286
+ background: transparent;
287
+ font: inherit;
288
+ color: inherit;
289
+ padding: 0;
290
+ margin: 0;
291
+ }
292
+ .ms-input::placeholder {
293
+ color: var(--color-text-subtle);
294
+ }
295
+ .ms-input:focus {
296
+ outline: none;
297
+ }
282
298
 
283
299
  /* ── Choice card: image / custom-template variant ── */
284
300
  .struct-card-img-card {
@@ -76,10 +76,13 @@
76
76
  transform: scaleY(-1);
77
77
  transition: color 0.15s;
78
78
  }
79
- .infotip-btn:hover,
79
+ .infotip-btn:hover {
80
+ color: var(--color-primary);
81
+ }
80
82
  .infotip-btn:focus-visible {
81
83
  color: var(--color-primary);
82
- outline: none;
84
+ outline: 2px solid var(--color-primary);
85
+ outline-offset: 2px;
83
86
  }
84
87
  .infotip-pop {
85
88
  position: absolute;
@@ -141,9 +144,91 @@
141
144
  }
142
145
 
143
146
  // ── Page header ──
147
+ // `.page-header` stacks an optional breadcrumbs row above `.page-header-bar` — the
148
+ // flex row that holds the title block on the left and the page actions on the right.
144
149
  .page-header {
145
150
  margin-bottom: 24px;
146
151
  }
152
+ .page-header-bar {
153
+ display: flex;
154
+ align-items: flex-end;
155
+ justify-content: space-between;
156
+ gap: 16px;
157
+ }
158
+ // Left block: an optional leading icon tile + the title stack, kept together.
159
+ .page-header-lead {
160
+ display: flex;
161
+ align-items: center;
162
+ gap: 12px;
163
+ min-width: 0;
164
+ }
165
+ // A breadcrumbs row above the masthead row.
166
+ .page-breadcrumbs {
167
+ margin-bottom: 10px;
168
+ }
169
+ // Leading icon tile — tinted by `tone` (default primary), matching the modal head icon.
170
+ .page-header-icon {
171
+ flex: none;
172
+ display: inline-grid;
173
+ place-items: center;
174
+ width: 40px;
175
+ height: 40px;
176
+ border-radius: var(--radius);
177
+ corner-shape: squircle;
178
+ background: var(--color-primary-soft);
179
+ color: var(--color-primary);
180
+ }
181
+ .page-header.tone-success .page-header-icon {
182
+ background: var(--color-success-soft);
183
+ color: var(--color-success-text);
184
+ }
185
+ .page-header.tone-warning .page-header-icon {
186
+ background: var(--color-warning-soft);
187
+ color: var(--color-warning-text);
188
+ }
189
+ .page-header.tone-danger .page-header-icon {
190
+ background: var(--color-danger-soft);
191
+ color: var(--color-danger-text);
192
+ }
193
+ .page-header.tone-info .page-header-icon {
194
+ background: var(--color-info-soft);
195
+ color: var(--color-info-text);
196
+ }
197
+ .page-header-titles {
198
+ min-width: 0;
199
+ }
200
+ // Eyebrow — a small kicker label above the title (a section / category marker).
201
+ .page-eyebrow {
202
+ font-size: 12px;
203
+ font-weight: 700;
204
+ letter-spacing: 0.06em;
205
+ text-transform: uppercase;
206
+ color: var(--color-primary);
207
+ margin-bottom: 3px;
208
+ }
209
+ .page-header.tone-success .page-eyebrow {
210
+ color: var(--color-success-text);
211
+ }
212
+ .page-header.tone-warning .page-eyebrow {
213
+ color: var(--color-warning-text);
214
+ }
215
+ .page-header.tone-danger .page-eyebrow {
216
+ color: var(--color-danger-text);
217
+ }
218
+ .page-header.tone-info .page-eyebrow {
219
+ color: var(--color-info-text);
220
+ }
221
+ // Title + an optional inline status pill, on one baseline.
222
+ .page-titlebar {
223
+ display: flex;
224
+ align-items: center;
225
+ gap: 10px;
226
+ flex-wrap: wrap;
227
+ }
228
+ .page-status {
229
+ display: inline-flex;
230
+ align-items: center;
231
+ }
147
232
  .page-title {
148
233
  font-size: 24px;
149
234
  font-weight: 600;
@@ -162,17 +247,67 @@
162
247
  align-items: center;
163
248
  }
164
249
 
250
+ // ── Size scale ── (`size`): the title and the block's rhythm scale together.
251
+ .page-header-sm {
252
+ margin-bottom: 16px;
253
+ }
254
+ .page-header-sm .page-title {
255
+ font-size: 20px;
256
+ }
257
+ .page-header-sm .page-header-icon {
258
+ width: 34px;
259
+ height: 34px;
260
+ }
261
+ .page-header-lg {
262
+ margin-bottom: 32px;
263
+ }
264
+ .page-header-lg .page-title {
265
+ font-size: 30px;
266
+ }
267
+ .page-header-lg .page-header-icon {
268
+ width: 48px;
269
+ height: 48px;
270
+ }
271
+
272
+ // ── Centre alignment ── (`align="center"`): for empty-state and wizard mastheads.
273
+ .page-header-center .page-header-bar {
274
+ justify-content: center;
275
+ text-align: center;
276
+ }
277
+ .page-header-center .page-header-lead {
278
+ flex-direction: column;
279
+ align-items: center;
280
+ }
281
+ .page-header-center .page-titlebar {
282
+ justify-content: center;
283
+ }
284
+
285
+ // ── Divider ── (`divider`): a hairline rule under the masthead.
286
+ .page-header-divider {
287
+ border-bottom: 1px solid var(--color-border);
288
+ padding-bottom: 16px;
289
+ }
290
+
291
+ // ── Sticky ── (`sticky`): pins the masthead to the top of its scroll container.
292
+ .page-header-sticky {
293
+ position: sticky;
294
+ top: 0;
295
+ z-index: 20;
296
+ background: var(--color-surface);
297
+ padding-top: 12px;
298
+ }
299
+
165
300
  // Tablet and below: the masthead stops being a single row — the title block and
166
301
  // its actions stack, and the actions stretch to the full width rather than
167
302
  // crowding the title. Bootstrap's lg tier (991.98px), not an invented breakpoint.
168
303
  //
169
- // Selectors are DOUBLED (`.page-header.page-header`) on purpose. A media query
170
- // adds no specificity, so a consumer's own single-class rule — written later in
171
- // the cascade, as a consuming app's stylesheet always is — silently beats the
304
+ // Selectors are DOUBLED (`.page-header-bar.page-header-bar`) on purpose. A media
305
+ // query adds no specificity, so a consumer's own single-class rule — written later
306
+ // in the cascade, as a consuming app's stylesheet always is — silently beats the
172
307
  // responsive rule and the layout never collapses. Doubling puts these at (0,2,0)
173
308
  // so they hold, while still leaving a consumer free to override deliberately.
174
309
  @media (max-width: 991.98px) {
175
- .page-header.page-header {
310
+ .page-header-bar.page-header-bar {
176
311
  flex-direction: column;
177
312
  align-items: stretch;
178
313
  gap: 12px;