@webority/theme 0.7.0 → 0.7.2

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.
@@ -16,7 +16,7 @@
16
16
  --bs-offcanvas-border-width: 1px;
17
17
  --bs-offcanvas-padding-x: 0;
18
18
  --bs-offcanvas-padding-y: 0;
19
- max-width: 100vw;
19
+ max-width: var(--asm-max-width, 92vw);
20
20
  display: flex;
21
21
  flex-direction: column;
22
22
  }
@@ -202,6 +202,24 @@
202
202
  background: var(--color-primary-soft);
203
203
  color: var(--color-primary);
204
204
  }
205
+ // Semantic tone tints — React `tone` / Razor `Tone`, matching the vocabulary
206
+ // `.status-label` uses (success | warning | danger | info).
207
+ .app-tab-count.success {
208
+ background: color-mix(in srgb, var(--color-success) 16%, transparent);
209
+ color: var(--color-success-text);
210
+ }
211
+ .app-tab-count.warning {
212
+ background: color-mix(in srgb, var(--color-warning) 16%, transparent);
213
+ color: var(--color-warning-text);
214
+ }
215
+ .app-tab-count.danger {
216
+ background: color-mix(in srgb, var(--color-danger) 16%, transparent);
217
+ color: var(--color-danger-text);
218
+ }
219
+ .app-tab-count.info {
220
+ background: color-mix(in srgb, var(--color-info) 16%, transparent);
221
+ color: var(--color-info-text);
222
+ }
205
223
 
206
224
  // Wrapper for a self-contained tabs widget (strip + panels), as <app-tabs>
207
225
  // renders server-side. React composes the strip and its panels itself.
@@ -79,58 +79,10 @@
79
79
  flex-direction: column;
80
80
  overflow: hidden;
81
81
  }
82
- .wui-modal-wide {
83
- max-width: var(--wui-modal-max-width, 1240px);
84
- }
85
-
86
- // Side drawer — the dialog docks to the right edge at 30% of the viewport
87
- // and slides in, instead of sitting centred. Same chrome, different
88
- // geometry.
89
- .modal.wui-modal-shell.is-side {
90
- align-items: stretch;
91
- justify-content: flex-end;
92
- padding: 0;
93
- }
94
- .modal.wui-modal-shell.is-side .modal-dialog.wui-rb-dialog {
95
- height: 100%;
96
- width: auto;
97
- max-width: none;
98
- align-items: stretch;
99
- justify-content: flex-end;
100
- }
101
- .wui-modal-side {
102
- width: 30vw;
103
- min-width: 420px;
104
- max-width: none;
105
- max-height: 100vh;
106
- height: 100vh;
107
- border-radius: 0;
108
- corner-shape: normal;
109
- animation: wui-drawer-in 0.32s cubic-bezier(0.22, 1, 0.36, 1);
110
- }
111
- @media (max-width: 991.98px) {
112
- .wui-modal-side {
113
- width: 70vw;
114
- }
115
- }
116
- @media (max-width: 575.98px) {
117
- .wui-modal-side {
118
- width: 100vw;
119
- }
120
- }
121
- @keyframes wui-drawer-in {
122
- from {
123
- transform: translateX(100%);
124
- }
125
- to {
126
- transform: translateX(0);
127
- }
128
- }
129
- @media (prefers-reduced-motion: reduce) {
130
- .wui-modal-side {
131
- animation: none;
132
- }
133
- }
82
+ // The centred card keeps Bootstrap's own modal transition — a smooth fade + slide-down that
83
+ // Bootstrap already disables under prefers-reduced-motion. Nothing to override here; both
84
+ // react-bootstrap (animation on) and Bootstrap's JS drive it. Width comes from `size`, which
85
+ // both surfaces resolve to `--wui-modal-max-width` on the shell (it inherits to `.wui-modal`).
134
86
  .wui-modal-header {
135
87
  flex: 0 0 auto;
136
88
  display: flex;
@@ -210,6 +162,38 @@
210
162
  color: var(--color-text-muted);
211
163
  overflow-wrap: anywhere;
212
164
  }
165
+ // Eyebrow — a small kicker label above the title (a section / step / category marker).
166
+ .wui-modal-eyebrow {
167
+ font-size: 11px;
168
+ font-weight: 700;
169
+ letter-spacing: 0.06em;
170
+ text-transform: uppercase;
171
+ color: var(--color-primary);
172
+ margin-bottom: 2px;
173
+ }
174
+ // `tone` tints the header icon tile and the eyebrow, so a destructive or a success dialog
175
+ // carries its intent in the header. Default keeps the neutral primary tint above.
176
+ .wui-modal.tone-danger .wui-modal-headicon {
177
+ background: var(--color-danger-soft);
178
+ color: var(--color-danger-text);
179
+ }
180
+ .wui-modal.tone-danger .wui-modal-eyebrow {
181
+ color: var(--color-danger-text);
182
+ }
183
+ .wui-modal.tone-success .wui-modal-headicon {
184
+ background: var(--color-success-soft);
185
+ color: var(--color-success-text);
186
+ }
187
+ .wui-modal.tone-success .wui-modal-eyebrow {
188
+ color: var(--color-success-text);
189
+ }
190
+ .wui-modal.tone-warning .wui-modal-headicon {
191
+ background: var(--color-warning-soft);
192
+ color: var(--color-warning-text);
193
+ }
194
+ .wui-modal.tone-warning .wui-modal-eyebrow {
195
+ color: var(--color-warning-text);
196
+ }
213
197
  .wui-form-section {
214
198
  margin-top: 24px;
215
199
  }
@@ -0,0 +1,32 @@
1
+ // AppScrollbar — a thin, themed scrollbar drawn with NATIVE CSS scrollbar styling: no JavaScript
2
+ // and no dependency (it replaced a wrapped simplebar-react, whose foreign `.simplebar-*` markup
3
+ // and stylesheet had no place in the library). `scrollbar-width` / `scrollbar-color` style the
4
+ // track on Firefox; the `::-webkit-scrollbar` rules cover Chromium and Safari. The track stays
5
+ // transparent so the thumb reads as a slim overlay rather than filling a grey gutter.
6
+ .wui-scroll {
7
+ overflow: auto;
8
+ scrollbar-width: thin;
9
+ scrollbar-color: var(--wui-text-3) transparent;
10
+ }
11
+
12
+ .wui-scroll::-webkit-scrollbar {
13
+ width: 8px;
14
+ height: 8px;
15
+ }
16
+
17
+ .wui-scroll::-webkit-scrollbar-track {
18
+ background: transparent;
19
+ }
20
+
21
+ .wui-scroll::-webkit-scrollbar-thumb {
22
+ background-color: var(--wui-text-3);
23
+ border-radius: 8px;
24
+ // A 2px transparent border clipped to padding-box insets the thumb, so it sits as a slim pill
25
+ // in the middle of the track instead of filling the whole gutter width.
26
+ border: 2px solid transparent;
27
+ background-clip: padding-box;
28
+ }
29
+
30
+ .wui-scroll::-webkit-scrollbar-thumb:hover {
31
+ background-color: var(--wui-text-2);
32
+ }
@@ -25,31 +25,47 @@
25
25
  align-items: center;
26
26
  gap: 18px;
27
27
  text-align: center;
28
+ // Ease in on mount so the boot screen settles rather than snapping into place.
29
+ animation: wui-splash-enter 0.5s var(--wui-ease-ui) both;
28
30
  }
31
+ // One diameter drives the mark, its initials, the logo cap, and the bar width — the `size`
32
+ // preset or a custom pixel value resolves to `--wui-splash-size` (set inline by App*/<app-*>).
29
33
  .splash-logo {
30
- max-width: 180px;
34
+ max-width: calc(var(--wui-splash-size, 56px) * 3.2);
31
35
  height: auto;
36
+ // A slow breath keeps the brand mark feeling alive through the wait.
37
+ animation: wui-splash-breathe 2.6s ease-in-out infinite;
32
38
  }
33
39
  .splash-mark {
34
- width: 56px;
35
- height: 56px;
40
+ width: var(--wui-splash-size, 56px);
41
+ height: var(--wui-splash-size, 56px);
36
42
  display: inline-grid;
37
43
  place-items: center;
38
44
  border-radius: var(--radius-lg);
39
45
  background: var(--color-primary);
40
46
  color: var(--color-on-scrim);
41
- font-size: 22px;
47
+ font-size: calc(var(--wui-splash-size, 56px) * 0.39);
42
48
  font-weight: 700;
49
+ animation: wui-splash-breathe 2.6s ease-in-out infinite;
43
50
  }
44
51
  .splash-text {
45
52
  font-size: 14px;
46
53
  color: var(--color-text-muted);
47
54
  }
48
- // A determinate-looking bar with no real progress value would lie about how far
49
- // along boot is, so this is an indeterminate sweep.
55
+ // A quieter second line — a reassurance under the main status. Uses --color-text-muted (not
56
+ // -subtle): -subtle is ~4.54:1 on the splash bg and flakes the AA contrast check at 12px; the
57
+ // smaller size (vs .splash-text) already sets the hierarchy without dropping below AA.
58
+ .splash-subtext {
59
+ margin-top: -12px;
60
+ font-size: 12px;
61
+ color: var(--color-text-muted);
62
+ }
63
+
64
+ // ── Boot indicators — one shows, chosen by `variant`. A determinate-looking value would lie
65
+ // about how far along boot is, so every variant is indeterminate. ──
50
66
  .splash-bar {
51
67
  position: relative;
52
- width: 180px;
68
+ width: calc(var(--wui-splash-size, 56px) * 3.2);
53
69
  height: 3px;
54
70
  overflow: hidden;
55
71
  border-radius: 999px;
@@ -62,9 +78,62 @@
62
78
  bottom: 0;
63
79
  width: 40%;
64
80
  border-radius: 999px;
65
- background: var(--color-primary);
81
+ // Feathered edges read smoother than a hard block sliding across.
82
+ background: linear-gradient(
83
+ 90deg,
84
+ transparent,
85
+ var(--color-primary) 35%,
86
+ var(--color-primary) 65%,
87
+ transparent
88
+ );
66
89
  animation: wui-splash-sweep 1.1s var(--wui-ease-ui) infinite;
67
90
  }
91
+ // A spinning arc — the circular take on the same "working, no percentage" idea.
92
+ .splash-ring {
93
+ width: 30px;
94
+ height: 30px;
95
+ border-radius: 50%;
96
+ border: 3px solid var(--color-bg-muted);
97
+ border-top-color: var(--color-primary);
98
+ animation: wui-splash-spin 0.8s linear infinite;
99
+ }
100
+ // Three dots taking turns rising — a lighter, friendlier wait.
101
+ .splash-dots {
102
+ display: inline-flex;
103
+ gap: 7px;
104
+ }
105
+ .splash-dots i {
106
+ width: 9px;
107
+ height: 9px;
108
+ border-radius: 50%;
109
+ background: var(--color-primary);
110
+ animation: wui-splash-dot 1.2s ease-in-out infinite;
111
+ }
112
+ .splash-dots i:nth-child(2) {
113
+ animation-delay: 0.16s;
114
+ }
115
+ .splash-dots i:nth-child(3) {
116
+ animation-delay: 0.32s;
117
+ }
118
+ @keyframes wui-splash-enter {
119
+ from {
120
+ opacity: 0;
121
+ transform: translateY(8px);
122
+ }
123
+ to {
124
+ opacity: 1;
125
+ transform: translateY(0);
126
+ }
127
+ }
128
+ @keyframes wui-splash-breathe {
129
+ 0%,
130
+ 100% {
131
+ transform: scale(1);
132
+ }
133
+ 50% {
134
+ transform: scale(1.045);
135
+ }
136
+ }
68
137
  @keyframes wui-splash-sweep {
69
138
  from {
70
139
  transform: translateX(-100%);
@@ -73,9 +142,37 @@
73
142
  transform: translateX(350%);
74
143
  }
75
144
  }
145
+ @keyframes wui-splash-spin {
146
+ to {
147
+ transform: rotate(360deg);
148
+ }
149
+ }
150
+ @keyframes wui-splash-dot {
151
+ 0%,
152
+ 60%,
153
+ 100% {
154
+ transform: translateY(0);
155
+ opacity: 0.45;
156
+ }
157
+ 30% {
158
+ transform: translateY(-7px);
159
+ opacity: 1;
160
+ }
161
+ }
76
162
  @media (prefers-reduced-motion: reduce) {
163
+ // Motion off: settle instantly, drop breathing and the spinning/bouncing indicators.
164
+ .splash-inner,
165
+ .splash-logo,
166
+ .splash-mark,
167
+ .splash-ring,
168
+ .splash-dots i {
169
+ animation: none;
170
+ }
171
+ .splash-dots i {
172
+ opacity: 0.7;
173
+ }
77
174
  .splash-bar::after {
78
- // Motion off, but keep a visible bar — an empty track would read as stalled.
175
+ // Keep a visible bar — an empty track would read as stalled.
79
176
  animation: none;
80
177
  width: 100%;
81
178
  opacity: 0.55;
@@ -237,13 +334,17 @@
237
334
  color: var(--color-primary);
238
335
  }
239
336
  .app-nav-text {
337
+ // Grows to fill the row so every trailing element — badge, trailing marker,
338
+ // expand caret — lines up at the end in source order without each needing its
339
+ // own `margin-left:auto` (which would fight when more than one is present).
340
+ flex: 1 1 auto;
240
341
  min-width: 0;
241
342
  overflow: hidden;
242
343
  text-overflow: ellipsis;
243
344
  white-space: nowrap;
244
345
  }
245
346
  .app-nav-badge {
246
- margin-left: auto;
347
+ flex: none;
247
348
  padding: 1px 7px;
248
349
  border-radius: 999px;
249
350
  background: var(--color-bg-muted);
@@ -272,6 +373,117 @@
272
373
  display: none;
273
374
  }
274
375
 
376
+ // ── Badge tones ──────────────────────────────────────────────────────────────
377
+ // A nav badge can carry a status tone so an alert count reads red, not neutral.
378
+ .app-nav-badge.tone-danger {
379
+ background: var(--color-danger-soft);
380
+ color: var(--color-danger);
381
+ }
382
+ .app-nav-badge.tone-success {
383
+ background: var(--color-success-soft);
384
+ color: var(--color-success);
385
+ }
386
+ .app-nav-badge.tone-warning {
387
+ background: var(--color-warning-soft);
388
+ color: var(--color-warning);
389
+ }
390
+ // The existing `.app-nav-item.active .app-nav-badge` inverts a tinted badge on an
391
+ // active row (higher specificity), so an active row's badge stays legible.
392
+
393
+ // ── Trailing marker ──────────────────────────────────────────────────────────
394
+ // A right-aligned secondary glyph after the label (a lock, an external arrow, a
395
+ // "new" dot) — distinct from the count badge, and may sit beside one.
396
+ .app-nav-trailing {
397
+ flex: none;
398
+ display: inline-grid;
399
+ place-items: center;
400
+ color: var(--color-text-subtle);
401
+ }
402
+ .app-nav-item.active .app-nav-trailing {
403
+ color: var(--color-primary);
404
+ }
405
+
406
+ // ── Expandable sub-items ─────────────────────────────────────────────────────
407
+ // A parent row that toggles a nested list. It reuses the .app-nav-item chrome but
408
+ // is a <button>; the caret rotates open. The nested list animates its height via
409
+ // grid-template-rows 0fr→1fr, so there is no fixed max-height to guess.
410
+ .app-nav-item.has-children {
411
+ width: 100%;
412
+ border: 0;
413
+ background: transparent;
414
+ font: inherit;
415
+ text-align: left;
416
+ cursor: pointer;
417
+ }
418
+ .app-nav-caret {
419
+ flex: none;
420
+ display: inline-grid;
421
+ place-items: center;
422
+ color: var(--color-text-subtle);
423
+ transition: transform 0.2s var(--wui-ease-ui);
424
+ }
425
+ .app-nav-item.has-children[aria-expanded="true"] .app-nav-caret {
426
+ transform: rotate(90deg);
427
+ }
428
+ .app-nav-sub {
429
+ display: grid;
430
+ grid-template-rows: 0fr;
431
+ transition: grid-template-rows 0.24s var(--wui-ease-ui);
432
+ }
433
+ .app-nav-sub-inner {
434
+ overflow: hidden;
435
+ display: flex;
436
+ flex-direction: column;
437
+ gap: 2px;
438
+ padding-top: 2px;
439
+ }
440
+ .app-nav-sub.expanded {
441
+ grid-template-rows: 1fr;
442
+ }
443
+ // Child link indented under the parent label (icon slot + gap), smaller, no icon.
444
+ .app-nav-subitem {
445
+ min-height: 38px;
446
+ padding-left: 46px;
447
+ font-size: 13px;
448
+ }
449
+
450
+ // ── Collapsed rail: label tooltip ────────────────────────────────────────────
451
+ // The rail hides labels, so surface each on hover/focus as a chip to the right —
452
+ // otherwise the icons are unlabelled. Sub-lists and carets have no place in a rail.
453
+ .app-shell.collapsed .app-nav-sub,
454
+ .app-shell.collapsed .app-nav-caret {
455
+ display: none;
456
+ }
457
+ @include media-breakpoint-up(lg) {
458
+ .app-shell.collapsed .app-nav-item[data-label]::after {
459
+ content: attr(data-label);
460
+ position: absolute;
461
+ left: calc(100% + 12px);
462
+ top: 50%;
463
+ z-index: 40;
464
+ padding: 5px 10px;
465
+ border-radius: var(--radius-lg);
466
+ background: var(--color-text);
467
+ color: var(--color-surface);
468
+ font-size: 12px;
469
+ font-weight: 500;
470
+ white-space: nowrap;
471
+ box-shadow: var(--shadow-md);
472
+ opacity: 0;
473
+ transform: translateY(-50%) scale(0.96);
474
+ transform-origin: left center;
475
+ pointer-events: none;
476
+ transition:
477
+ opacity 0.12s ease,
478
+ transform 0.12s ease;
479
+ }
480
+ .app-shell.collapsed .app-nav-item[data-label]:hover::after,
481
+ .app-shell.collapsed .app-nav-item[data-label]:focus-visible::after {
482
+ opacity: 1;
483
+ transform: translateY(-50%) scale(1);
484
+ }
485
+ }
486
+
275
487
  // A circular button centred on the sidebar's right edge. It is fixed to the
276
488
  // viewport, not a sidebar child, because the sidebar clips its own overflow and
277
489
  // would cut the button in half.