@webority/theme 0.7.7 → 0.7.9

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.
@@ -329,46 +329,275 @@
329
329
  font-weight: 500;
330
330
  }
331
331
 
332
+ // ── Tooltip ──
333
+ // Both surfaces render Bootstrap's .tooltip / .tooltip-inner (React via react-bootstrap
334
+ // <Tooltip>, Razor via Bootstrap's own tooltip JS). The wrappers tag the tooltip element with
335
+ // `wui-tooltip` (React className, Razor data-bs-custom-class), so this design lands on App
336
+ // tooltips only and never on a raw Bootstrap one. Every colour flows --wui-tooltip-* →
337
+ // Bootstrap's own --bs-tooltip-* vars, so the arrow (which Bootstrap paints from
338
+ // --bs-tooltip-bg) always tracks the body. Customisation seam: override a --wui-tooltip-* var
339
+ // from a caller className for any colour, or add a tone-* class for a preset.
340
+ //
341
+ // The --wui-tooltip-* defaults sit at ZERO specificity (:where) so a caller's own single class
342
+ // (or inline style) overrides them; the tone presets are ordinary (0,3,0) rules that win over
343
+ // the defaults but lose to nothing a caller would reasonably write.
344
+ :where(.tooltip.wui-tooltip) {
345
+ --wui-tooltip-bg: var(--color-surface);
346
+ --wui-tooltip-color: var(--color-text);
347
+ --wui-tooltip-border: var(--color-border-light);
348
+ --wui-tooltip-radius: var(--radius, 10px);
349
+ --wui-tooltip-shadow: var(--wui-shadow-overlay);
350
+ // Size is these four vars — the `md` default. A size-* class swaps them; a caller can
351
+ // override any one from its own className for a fully custom size.
352
+ --wui-tooltip-max-width: 240px;
353
+ --wui-tooltip-font-size: 0.8125rem;
354
+ --wui-tooltip-padding-y: 7px;
355
+ --wui-tooltip-padding-x: 11px;
356
+ }
357
+ .tooltip.wui-tooltip {
358
+ --bs-tooltip-bg: var(--wui-tooltip-bg);
359
+ --bs-tooltip-color: var(--wui-tooltip-color);
360
+ --bs-tooltip-max-width: var(--wui-tooltip-max-width);
361
+ --bs-tooltip-border-radius: var(--wui-tooltip-radius);
362
+ --bs-tooltip-font-size: var(--wui-tooltip-font-size);
363
+ --bs-tooltip-padding-y: var(--wui-tooltip-padding-y);
364
+ --bs-tooltip-padding-x: var(--wui-tooltip-padding-x);
365
+ --bs-tooltip-opacity: 1;
366
+ }
367
+ .tooltip.wui-tooltip .tooltip-inner {
368
+ // Padding + font-size come from the --bs-tooltip-* vars above (driven by --wui-tooltip-*),
369
+ // so a size-* class or a custom var resizes the tip without touching this rule.
370
+ font-weight: 500;
371
+ line-height: 1.4;
372
+ text-align: left;
373
+ border: 1px solid var(--wui-tooltip-border);
374
+ box-shadow: var(--wui-tooltip-shadow);
375
+ }
376
+
377
+ // Size presets — swap the four size vars. `md` is the :where default (no class).
378
+ .tooltip.wui-tooltip.size-sm {
379
+ --wui-tooltip-max-width: 200px;
380
+ --wui-tooltip-font-size: 0.75rem;
381
+ --wui-tooltip-padding-y: 4px;
382
+ --wui-tooltip-padding-x: 8px;
383
+ }
384
+ .tooltip.wui-tooltip.size-lg {
385
+ --wui-tooltip-max-width: 300px;
386
+ --wui-tooltip-font-size: 0.9375rem;
387
+ --wui-tooltip-padding-y: 10px;
388
+ --wui-tooltip-padding-x: 14px;
389
+ }
390
+ // A hairline shadow so the light default's white arrow still reads on a white page; on a tinted
391
+ // tone the arrow is coloured and this just adds subtle depth.
392
+ .tooltip.wui-tooltip .tooltip-arrow {
393
+ filter: drop-shadow(0 1px 1px color-mix(in srgb, var(--color-text) 14%, transparent));
394
+ }
395
+
396
+ // Tone presets — swap the three colour vars; the arrow follows --bs-tooltip-bg automatically.
397
+ // Fills that carry white text use the AA-cleared -strong tokens; warning stays dark-on-yellow.
398
+ .tooltip.wui-tooltip.tone-dark {
399
+ --wui-tooltip-bg: var(--color-text);
400
+ --wui-tooltip-color: #fff;
401
+ --wui-tooltip-border: transparent;
402
+ }
403
+ .tooltip.wui-tooltip.tone-primary {
404
+ --wui-tooltip-bg: var(--color-primary);
405
+ --wui-tooltip-color: var(--color-primary-text);
406
+ --wui-tooltip-border: transparent;
407
+ }
408
+ .tooltip.wui-tooltip.tone-success {
409
+ --wui-tooltip-bg: var(--wui-success-strong);
410
+ --wui-tooltip-color: #fff;
411
+ --wui-tooltip-border: transparent;
412
+ }
413
+ .tooltip.wui-tooltip.tone-danger {
414
+ --wui-tooltip-bg: var(--wui-danger-strong);
415
+ --wui-tooltip-color: #fff;
416
+ --wui-tooltip-border: transparent;
417
+ }
418
+ .tooltip.wui-tooltip.tone-warning {
419
+ --wui-tooltip-bg: var(--color-warning);
420
+ --wui-tooltip-color: var(--color-text);
421
+ --wui-tooltip-border: transparent;
422
+ }
423
+
332
424
  // ── Toast (notifications) ──
333
- // Bootstrap toast styling + wui extensions for title/action buttons.
334
- // Used by both React AppToastContainer and Razor WUI.toast.
425
+ // Surface-card toasts shared by React <AppToast/> and Razor <app-toast>/WUI.toast: an elevated
426
+ // card with a left accent bar + variant icon, an auto-dismiss progress bar (pauses on hover/focus),
427
+ // and swipe-to-dismiss. The variant tint drives one custom prop so every part stays in step.
335
428
 
336
- // Toast header with title
337
- .wui-toast-header {
429
+ .wui-toast-container {
430
+ z-index: var(--wui-z-toast);
338
431
  display: flex;
339
- align-items: center;
340
- justify-content: space-between;
432
+ flex-direction: column;
433
+ gap: 10px;
434
+ max-width: min(92vw, 400px);
435
+ }
436
+
437
+ .wui-toast {
438
+ --wui-toast-accent: var(--color-primary);
439
+ position: relative;
440
+ display: flex;
441
+ align-items: flex-start;
341
442
  gap: 12px;
342
- padding: 10px 12px;
343
- border: none;
443
+ width: 100%;
444
+ padding: 14px 14px 14px 18px;
445
+ background: var(--color-surface);
446
+ color: var(--color-text);
447
+ border: 1px solid var(--color-border-light);
448
+ border-radius: var(--radius-lg, 12px);
449
+ box-shadow: var(--wui-shadow-overlay);
450
+ overflow: hidden;
451
+ touch-action: pan-y;
452
+ user-select: none;
453
+ transition:
454
+ transform 0.2s ease,
455
+ opacity 0.2s ease;
456
+ animation: wui-toast-in 0.24s ease;
344
457
  }
345
458
 
346
- .wui-toast-header strong {
347
- font-size: 14px;
459
+ // The left accent bar reads the variant tint.
460
+ .wui-toast::before {
461
+ content: "";
462
+ position: absolute;
463
+ inset: 0 auto 0 0;
464
+ width: 4px;
465
+ background: var(--wui-toast-accent);
466
+ }
467
+
468
+ // While a swipe is in progress the toast follows the pointer with no transition.
469
+ .wui-toast--dragging {
470
+ transition: none;
471
+ cursor: grabbing;
472
+ }
473
+
474
+ .wui-toast--success {
475
+ --wui-toast-accent: var(--color-success);
476
+ }
477
+ .wui-toast--info {
478
+ --wui-toast-accent: var(--color-info);
479
+ }
480
+ .wui-toast--warning {
481
+ --wui-toast-accent: var(--color-warning);
482
+ }
483
+ .wui-toast--error,
484
+ .wui-toast--danger {
485
+ --wui-toast-accent: var(--color-danger);
486
+ }
487
+
488
+ .wui-toast-icon {
489
+ flex: none;
490
+ display: inline-flex;
491
+ margin-top: 1px;
492
+ color: var(--wui-toast-accent);
493
+ line-height: 1;
494
+ }
495
+
496
+ .wui-toast-content {
497
+ flex: 1 1 auto;
498
+ min-width: 0;
499
+ display: flex;
500
+ flex-direction: column;
501
+ gap: 2px;
502
+ }
503
+
504
+ .wui-toast-title {
505
+ font-size: 0.9375rem;
348
506
  font-weight: 600;
349
- color: inherit;
507
+ color: var(--color-text);
350
508
  }
351
509
 
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;
510
+ .wui-toast-message {
511
+ font-size: 0.875rem;
512
+ color: var(--color-text);
513
+ overflow-wrap: anywhere;
514
+ }
515
+
516
+ // Action button — tinted with the variant accent, scoped to the toast so it never
517
+ // restyles an arbitrary .btn-link a rich body might contain.
518
+ .wui-toast-action {
519
+ flex: none;
520
+ align-self: center;
521
+ color: var(--wui-toast-accent);
356
522
  text-decoration: none;
357
- font-size: 13px;
358
- font-weight: 500;
523
+ font-size: 0.8125rem;
524
+ font-weight: 600;
359
525
  padding: 2px 8px;
360
526
  white-space: nowrap;
527
+ }
528
+ .wui-toast-action:hover {
529
+ color: var(--wui-toast-accent);
530
+ text-decoration: underline;
531
+ }
532
+ .wui-toast-action:focus-visible {
533
+ outline: 2px solid var(--wui-toast-accent);
534
+ outline-offset: 2px;
535
+ }
536
+
537
+ .wui-toast-close {
361
538
  flex: none;
362
- opacity: 0.9;
363
- transition: opacity 150ms;
539
+ margin-top: 1px;
540
+ font-size: 0.65rem;
364
541
  }
365
542
 
366
- .toast .wui-toast-action:hover {
367
- opacity: 1;
368
- text-decoration: underline;
543
+ // A pure-CSS auto-dismiss countdown: the bar shrinks over the toast's duration; the JS side
544
+ // listens for animationend to remove it. Because :hover/:focus-within pause the animation,
545
+ // animationend cannot fire while the toast is being read — a self-synchronising timer.
546
+ .wui-toast-progress {
547
+ position: absolute;
548
+ left: 0;
549
+ bottom: 0;
550
+ height: 3px;
551
+ width: 100%;
552
+ transform-origin: left center;
553
+ background: var(--wui-toast-accent);
554
+ opacity: 0.5;
555
+ animation-name: wui-toast-countdown;
556
+ animation-timing-function: linear;
557
+ animation-fill-mode: forwards;
558
+ }
559
+ .wui-toast:hover .wui-toast-progress,
560
+ .wui-toast:focus-within .wui-toast-progress {
561
+ animation-play-state: paused;
369
562
  }
370
563
 
371
- .toast .wui-toast-action:focus-visible {
372
- outline: 2px solid currentColor;
373
- outline-offset: 2px;
564
+ // "+N more" chip when the stack exceeds maxVisible.
565
+ .wui-toast-more {
566
+ align-self: center;
567
+ font-size: 0.75rem;
568
+ font-weight: 600;
569
+ color: var(--color-text-subtle);
570
+ background: var(--color-surface);
571
+ border: 1px solid var(--color-border-light);
572
+ border-radius: 999px;
573
+ padding: 2px 12px;
574
+ box-shadow: var(--wui-shadow-overlay);
575
+ }
576
+
577
+ @keyframes wui-toast-countdown {
578
+ from {
579
+ transform: scaleX(1);
580
+ }
581
+ to {
582
+ transform: scaleX(0);
583
+ }
584
+ }
585
+
586
+ @keyframes wui-toast-in {
587
+ from {
588
+ opacity: 0;
589
+ transform: translateY(8px);
590
+ }
591
+ to {
592
+ opacity: 1;
593
+ transform: translateY(0);
594
+ }
595
+ }
596
+
597
+ @media (prefers-reduced-motion: reduce) {
598
+ // Drop the entrance slide; the countdown bar stays — it is the auto-dismiss timer, not decoration.
599
+ .wui-toast {
600
+ animation: none;
601
+ transition: none;
602
+ }
374
603
  }
@@ -263,13 +263,25 @@
263
263
  height: 0;
264
264
  }
265
265
  // Between the brand row and the nav — a workspace/tenant switcher, an environment
266
- // badge. It must not shrink when the nav overflows, and it has no place on the
267
- // 88px rail, so the collapsed state removes it (the mobile drawer restores it).
266
+ // badge. It must not shrink when the nav overflows. The full card has no place on
267
+ // the 88px rail, so the collapsed state hides it (the mobile drawer restores it).
268
+ // The compact twin (`sidebarStartCompact` / `<app-shell-sidebar-start-compact>`) is
269
+ // the inverse: hidden expanded, shown only on the desktop collapsed rail, sized for
270
+ // a 36px monogram control (AppWorkspaceSwitcher compact).
268
271
  .app-sidebar-start {
269
272
  flex: none;
270
273
  margin-bottom: 8px;
271
274
  overflow: hidden;
272
275
  }
276
+ .app-sidebar-start-compact {
277
+ display: none;
278
+ flex: none;
279
+ margin-bottom: 8px;
280
+ // Centre the fixed square in the 88px rail so it lines up with the icon column.
281
+ justify-content: center;
282
+ align-items: center;
283
+ overflow: hidden;
284
+ }
273
285
  .app-sidebar-foot {
274
286
  flex: none;
275
287
  padding-top: 12px;
@@ -374,13 +386,31 @@
374
386
  gap: 0;
375
387
  padding: 9px 0;
376
388
  }
377
- .app-shell.collapsed .app-nav-text,
378
389
  .app-shell.collapsed .app-nav-badge,
379
390
  .app-shell.collapsed .app-nav-group-label,
380
391
  .app-shell.collapsed .app-sidebar-brand-text,
381
392
  .app-shell.collapsed .app-sidebar-start {
382
393
  display: none;
383
394
  }
395
+ // The label leaves the VISUAL flow but must stay in the accessibility tree — an
396
+ // icon-only rail link with display:none text has no accessible name (axe
397
+ // link-name, serious). Clip-rect keeps the name; no phantom gap since it takes
398
+ // no space.
399
+ .app-shell.collapsed .app-nav-text {
400
+ position: absolute;
401
+ width: 1px;
402
+ height: 1px;
403
+ margin: -1px;
404
+ padding: 0;
405
+ overflow: hidden;
406
+ clip-path: inset(50%);
407
+ white-space: nowrap;
408
+ border: 0;
409
+ }
410
+ // Swap: expanded slot off, compact monogram on — only when the author filled the compact slot.
411
+ .app-shell.collapsed .app-sidebar-start-compact {
412
+ display: flex;
413
+ }
384
414
 
385
415
  // ── Badge tones ──────────────────────────────────────────────────────────────
386
416
  // A nav badge can carry a status tone so an alert count reads red, not neutral.
@@ -651,6 +681,10 @@
651
681
  .app-shell.app-shell.collapsed .app-sidebar-start {
652
682
  display: revert;
653
683
  }
684
+ // Drawer always shows full labels — the compact rail control has no role here.
685
+ .app-shell.app-shell.collapsed .app-sidebar-start-compact {
686
+ display: none;
687
+ }
654
688
  .app-shell.app-shell .app-sidebar-toggle {
655
689
  display: none;
656
690
  }
@@ -0,0 +1,23 @@
1
+ // ── Shared tone-pill color mixin ──
2
+ // Consolidates the tone→color mapping used by .badge, .status-label, and .chip:
3
+ // background: var(--color-{tone}-soft)
4
+ // color: var(--color-{tone}-text) (override via $text — see below)
5
+ // border-color: transparent
6
+ //
7
+ // $text exists because the status tones and `primary` name their on-tint color
8
+ // differently: --color-{status}-text IS the on-tint hue, but --color-primary-text
9
+ // is the text-on-SOLID-primary color (near-black in dark mode) and fails AA on the
10
+ // soft tint — primary pills pass var(--color-primary) instead.
11
+ //
12
+ // Component-specific overrides (e.g. .badge-primary's colored border) are applied
13
+ // after the mixin call.
14
+
15
+ @mixin wui-tone-pill-colors($tone, $text: null) {
16
+ background: var(--color-#{$tone}-soft);
17
+ @if $text {
18
+ color: $text;
19
+ } @else {
20
+ color: var(--color-#{$tone}-text);
21
+ }
22
+ border-color: transparent;
23
+ }
@@ -121,29 +121,19 @@
121
121
 
122
122
  /* Tones — tinted surface + on-tint text; icon/remove inherit the tone colour. */
123
123
  .chip.primary {
124
- border-color: transparent;
125
- background: var(--color-primary-soft);
126
- color: var(--color-primary);
124
+ @include wui-tone-pill-colors(primary, var(--color-primary));
127
125
  }
128
126
  .chip.success {
129
- border-color: transparent;
130
- background: var(--color-success-soft);
131
- color: var(--color-success-text);
127
+ @include wui-tone-pill-colors(success);
132
128
  }
133
129
  .chip.warning {
134
- border-color: transparent;
135
- background: var(--color-warning-soft);
136
- color: var(--color-warning-text);
130
+ @include wui-tone-pill-colors(warning);
137
131
  }
138
132
  .chip.danger {
139
- border-color: transparent;
140
- background: var(--color-danger-soft);
141
- color: var(--color-danger-text);
133
+ @include wui-tone-pill-colors(danger);
142
134
  }
143
135
  .chip.info {
144
- border-color: transparent;
145
- background: var(--color-info-soft);
146
- color: var(--color-info-text);
136
+ @include wui-tone-pill-colors(info);
147
137
  }
148
138
  .chip.primary .chip-ic,
149
139
  .chip.success .chip-ic,
@@ -238,6 +228,25 @@
238
228
  min-width: 0;
239
229
  align-items: center;
240
230
  }
231
+ // Chips scale with the control size — the taller the field, the larger its tokens
232
+ // (the element always emits `.chip.chip-sm`; these override it per size tier). The
233
+ // two-class `.ms-chips .chip` outranks the single `.chip-sm`, so md is the base and
234
+ // the sm/lg tiers layer over it.
235
+ .ms-chips .chip {
236
+ gap: 5px;
237
+ padding: 3px 9px;
238
+ font-size: 0.8125rem;
239
+ }
240
+ .search-select-sm .ms-chips .chip {
241
+ gap: 4px;
242
+ padding: 2px 8px;
243
+ font-size: 0.75rem;
244
+ }
245
+ .search-select-lg .ms-chips .chip {
246
+ gap: 6px;
247
+ padding: 5px 12px;
248
+ font-size: 0.875rem;
249
+ }
241
250
  // Collapse mode (collapse-chips): chips stay on one row; the JS folds the overflow into
242
251
  // a "+N" counter (see WuiMultiSelect._collapseChips).
243
252
  wui-multiselect[collapse-chips] .ms-chips {
@@ -295,6 +304,27 @@ wui-multiselect[collapse-chips] .ms-chips {
295
304
  .ms-input:focus {
296
305
  outline: none;
297
306
  }
307
+ // AppTagInput lays the input on the LEFT with the chips filling and wrapping to its
308
+ // right (its template renders the input before the chips). Multiselect keeps its
309
+ // chips-first layout, so these overrides are scoped to `wui-taginput`.
310
+ wui-taginput .ms-input {
311
+ // Don't grow to fill — hold a modest resting width on the left; the chips take
312
+ // everything to the right. (align-items:center on the control centres the input
313
+ // against the chip row, so it needs no min-height of its own.)
314
+ flex: 0 1 auto;
315
+ flex-basis: 120px;
316
+ min-width: 96px;
317
+ }
318
+ wui-taginput .ms-chips {
319
+ flex: 1 1 auto;
320
+ }
321
+ // Match AppInput's per-size heights: the shared `.search-select-multi` floors md at
322
+ // 40px, but AppInput's md is the 44px control-height token — raise the floor so the
323
+ // sm/md/lg progression lines up with a text field beside it. (sm 36 / lg 52 already
324
+ // come from the size-specific `.search-select-{sm,lg} .search-select-control` rules.)
325
+ wui-taginput .search-select-multi {
326
+ min-height: var(--wui-control-height);
327
+ }
298
328
 
299
329
  /* ── Choice card: image / custom-template variant ── */
300
330
  .struct-card-img-card {
@@ -73,7 +73,6 @@
73
73
  background: transparent;
74
74
  color: var(--color-text-subtle);
75
75
  cursor: help;
76
- transform: scaleY(-1);
77
76
  transition: color 0.15s;
78
77
  }
79
78
  .infotip-btn:hover {
@@ -598,6 +597,35 @@
598
597
  color: var(--color-primary);
599
598
  }
600
599
 
600
+ // AppAlert extras — a leading icon and/or a bold heading line. Bootstrap already
601
+ // styles `.alert`/`.alert-{variant}`; these rules only lay out the icon column and
602
+ // give the plain `.alert-heading` div its type treatment (Bootstrap's own rule is
603
+ // just `color: inherit`, meant for an <h4>).
604
+ .alert-has-icon {
605
+ display: flex;
606
+ align-items: flex-start;
607
+ gap: 12px;
608
+ }
609
+ .alert-icon {
610
+ flex: none;
611
+ display: inline-flex;
612
+ // Nudge onto the first line's optical centre.
613
+ margin-top: 1px;
614
+ }
615
+ .alert-content {
616
+ flex: 1;
617
+ min-width: 0;
618
+ }
619
+ .alert .alert-heading {
620
+ margin-bottom: 2px;
621
+ font-size: 1rem;
622
+ font-weight: 700;
623
+ }
624
+ // The body's trailing paragraph shouldn't add space below the alert.
625
+ .alert .alert-content > :last-child {
626
+ margin-bottom: 0;
627
+ }
628
+
601
629
  // ============================================================================
602
630
  // Inline help glyph — a hint attached to a word rather than to a field label.
603
631
  // (For a field label use `.infotip`, which is a real popover button.)