@webority/theme 0.9.13 → 0.9.14

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.13",
3
+ "version": "0.9.14",
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": {
@@ -45,6 +45,9 @@
45
45
  }
46
46
  .form-check-label,
47
47
  .wui-control-label {
48
+ // `min-width: 0` lets the label wrap inside its column instead of forcing
49
+ // the track to overflow.
50
+ min-width: 0;
48
51
  cursor: pointer;
49
52
  font-size: 14px;
50
53
  line-height: 1.4;
@@ -52,21 +55,23 @@
52
55
  user-select: none;
53
56
  }
54
57
 
55
- // Checkbox / radio / switch hint & error sit BELOW the label on their own line, indented to
56
- // align under the label text (past the glyph + gap) — not inline beside it. `.form-check` is a
57
- // flex row for glyph+label; a full-width flex basis wraps the message to a second line.
58
+ // The block variant is a 2-column grid — glyph in column 1, label in column 2 — so a long
59
+ // label WRAPS inside its own column and stays beside the glyph, never dropping onto a second
60
+ // line below it (a flex-wrap row breaks lines by each item's max-content size, so any label
61
+ // longer than the remaining width is bumped below the glyph regardless of flex sizing). The
62
+ // hint/error occupy column 2 too, landing on the next row aligned under the label text.
58
63
  .form-check {
59
- flex-wrap: wrap;
60
- --wui-check-hint-indent: 28px; // 20px glyph + 8px gap
61
- }
62
- .form-switch {
63
- --wui-check-hint-indent: 48px; // 40px track + 8px gap
64
+ display: grid;
65
+ grid-template-columns: auto 1fr;
66
+ // Vertically centre the glyph against the whole label block, so it sits in
67
+ // the middle when the label wraps to multiple lines.
68
+ align-items: center;
69
+ column-gap: 12px;
70
+ row-gap: 2px;
64
71
  }
65
72
  .form-check > .field-hint,
66
73
  .form-check > .field-error {
67
- flex-basis: 100%;
68
- margin-top: 2px;
69
- padding-left: var(--wui-check-hint-indent);
74
+ grid-column: 2;
70
75
  }
71
76
 
72
77
  // Glyph — shared by BS form-check-input and the wui-check alias.
@@ -645,11 +645,15 @@
645
645
  white-space: nowrap;
646
646
  }
647
647
  .u-action {
648
+ // The disc size is a custom property so the wrapper can scale it with the glyph `size`
649
+ // (React AppActionIcon / Razor <app-action-icon> set it to `size + 16px` — a constant 8px
650
+ // ring). Default 36px keeps the historical disc for the default `size=20`.
651
+ --wui-action-size: 36px;
648
652
  display: inline-grid;
649
653
  place-items: center;
650
654
  flex: none;
651
- width: 36px;
652
- height: 36px;
655
+ width: var(--wui-action-size);
656
+ height: var(--wui-action-size);
653
657
  padding: 0;
654
658
  border: none;
655
659
  background: var(--color-bg-subtle);
@@ -662,9 +666,12 @@
662
666
  position: relative;
663
667
  }
664
668
  // Touch pointers need a 44px minimum target. Grow the hit area, not the
665
- // ink, so row density and alignment are untouched.
669
+ // ink, so row density and alignment are untouched. This lives on ::before, NOT
670
+ // ::after: an AppActionIcon carries `data-wui-tip` for its tooltip, whose bubble
671
+ // owns `[data-wui-tip]::after` (imported later, same specificity) — sharing ::after
672
+ // would let the tooltip overwrite the hit-area box and silently drop the 44px target.
666
673
  @media (pointer: coarse) {
667
- .u-action::after {
674
+ .u-action::before {
668
675
  content: "";
669
676
  position: absolute;
670
677
  top: 50%;
@@ -91,17 +91,21 @@
91
91
  .faq-a-inner {
92
92
  min-height: 0;
93
93
  overflow: hidden;
94
+ // Horizontal padding is CONSTANT in both states. Toggling it with `.open` made the content
95
+ // jump 18px sideways the instant a pane collapsed (padding isn't transitioned, unlike the
96
+ // grid-row height) — the "slide left" bug. Side padding adds no height, so keeping it on the
97
+ // closed pane is safe; only the BOTTOM padding stays conditional (below), because a constant
98
+ // one leaks height on the collapsed 0fr row.
99
+ padding: 0 18px;
94
100
  font-size: 14px;
95
101
  line-height: 1.6;
96
102
  color: var(--color-text-muted);
97
103
  }
98
- // Padding lives on a child of the clipped element so it does not add height while
99
- // the row is collapsed to 0fr.
100
104
  .faq-a-inner > :first-child {
101
105
  margin-top: 0;
102
106
  }
103
107
  .faq-item.open .faq-a-inner {
104
- padding: 0 18px 16px;
108
+ padding-bottom: 16px;
105
109
  }
106
110
 
107
111
  @media (prefers-reduced-motion: reduce) {
@@ -1121,4 +1121,307 @@ a.info-row:hover .info-value {
1121
1121
  }
1122
1122
  .result-muted .result-code {
1123
1123
  color: var(--color-text-muted);
1124
- }
1124
+ }
1125
+
1126
+ // ─────────────────────────────────────────────────────────────────────────────
1127
+ // AppLink / <app-link> — a text link with a choice of hover animation.
1128
+ // Base is a flat accent-coloured anchor; `.wui-link--<animation>` layers the
1129
+ // hover treatment. Every animation is disabled under prefers-reduced-motion,
1130
+ // where the link falls back to a static underline. React AppLink + Razor
1131
+ // <app-link> emit identical class names, so the two surfaces share this block.
1132
+ // ─────────────────────────────────────────────────────────────────────────────
1133
+ // The line each animation draws is our own thin ::after (or, for `none`, nothing) —
1134
+ // never the browser's text-decoration. Bootstrap's global `a:hover` re-adds
1135
+ // `text-decoration: underline` to every anchor, and `.wui-link` is an <a>, so without
1136
+ // pinning it off here that global underline bleeds onto the link on hover, sitting under
1137
+ // our animated line as a second, thicker underline. Force it off in every state.
1138
+ .wui-link,
1139
+ .wui-link:hover,
1140
+ .wui-link:focus,
1141
+ .wui-link:focus-visible,
1142
+ .wui-link:active {
1143
+ text-decoration: none;
1144
+ }
1145
+ .wui-link {
1146
+ display: inline-flex;
1147
+ align-items: center;
1148
+ gap: 0.35em;
1149
+ position: relative;
1150
+ color: var(--color-primary);
1151
+ font-weight: 500;
1152
+ cursor: pointer;
1153
+ border-radius: 4px;
1154
+ transition: color 0.25s var(--wui-ease-out-quart);
1155
+ }
1156
+ .wui-link:hover {
1157
+ color: var(--color-primary);
1158
+ }
1159
+ .wui-link:focus-visible {
1160
+ outline: 2px solid var(--color-primary);
1161
+ outline-offset: 3px;
1162
+ }
1163
+ .wui-link.is-disabled {
1164
+ color: var(--color-text-muted);
1165
+ cursor: not-allowed;
1166
+ opacity: 0.65;
1167
+ pointer-events: none;
1168
+ }
1169
+ .wui-link__label {
1170
+ position: relative;
1171
+ z-index: 0;
1172
+ white-space: nowrap;
1173
+ }
1174
+ .wui-link__ic {
1175
+ display: inline-flex;
1176
+ flex: none;
1177
+ }
1178
+ .wui-link__ic--left,
1179
+ .wui-link__ic--right {
1180
+ line-height: 0;
1181
+ }
1182
+ .wui-link--sm {
1183
+ font-size: 0.8125rem;
1184
+ }
1185
+ .wui-link--lg {
1186
+ font-size: 1.125rem;
1187
+ }
1188
+
1189
+ // underline family — one thin single line, drawn with the same 1px ::after on every
1190
+ // variant so `static`, `underline`, and `underline-center` never differ in weight.
1191
+ // `underline` slides it in from the left, `underline-center` grows it from the middle,
1192
+ // `static` shows it always (see below).
1193
+ .wui-link--underline .wui-link__label::after,
1194
+ .wui-link--underline-center .wui-link__label::after,
1195
+ .wui-link--static .wui-link__label::after {
1196
+ content: "";
1197
+ position: absolute;
1198
+ left: 0;
1199
+ right: 0;
1200
+ bottom: -2px;
1201
+ height: 1px;
1202
+ background: currentColor;
1203
+ transform: scaleX(0);
1204
+ transform-origin: left center;
1205
+ transition: transform 0.28s var(--wui-ease-out-quart);
1206
+ }
1207
+ .wui-link--underline-center .wui-link__label::after {
1208
+ transform-origin: center;
1209
+ }
1210
+ .wui-link--underline:hover .wui-link__label::after,
1211
+ .wui-link--underline:focus-visible .wui-link__label::after,
1212
+ .wui-link--underline-center:hover .wui-link__label::after,
1213
+ .wui-link--underline-center:focus-visible .wui-link__label::after {
1214
+ transform: scaleX(1);
1215
+ }
1216
+
1217
+ // slide — the whole link (label + icons) nudges to the right.
1218
+ .wui-link--slide {
1219
+ transition: color 0.25s var(--wui-ease-out-quart), transform 0.25s var(--wui-ease-out-quart);
1220
+ }
1221
+ .wui-link--slide:hover,
1222
+ .wui-link--slide:focus-visible {
1223
+ transform: translateX(3px);
1224
+ }
1225
+
1226
+ // icon — a trailing icon slides and fades in on hover.
1227
+ .wui-link--icon .wui-link__ic--right {
1228
+ margin-inline-start: -0.2em;
1229
+ opacity: 0;
1230
+ transform: translateX(-4px);
1231
+ transition: opacity 0.25s var(--wui-ease-out-quart), transform 0.25s var(--wui-ease-out-quart), margin 0.25s var(--wui-ease-out-quart);
1232
+ }
1233
+ .wui-link--icon:hover .wui-link__ic--right,
1234
+ .wui-link--icon:focus-visible .wui-link__ic--right {
1235
+ margin-inline-start: 0;
1236
+ opacity: 1;
1237
+ transform: translateX(2px);
1238
+ }
1239
+
1240
+ // highlight — a tinted background wipes across behind the label.
1241
+ .wui-link--highlight .wui-link__label::before {
1242
+ content: "";
1243
+ position: absolute;
1244
+ inset: -0.12em -0.3em;
1245
+ z-index: -1;
1246
+ border-radius: 4px;
1247
+ background: var(--color-primary-soft);
1248
+ transform: scaleX(0);
1249
+ transform-origin: left center;
1250
+ transition: transform 0.3s var(--wui-ease-out-quart);
1251
+ }
1252
+ .wui-link--highlight:hover .wui-link__label::before,
1253
+ .wui-link--highlight:focus-visible .wui-link__label::before {
1254
+ transform: scaleX(1);
1255
+ }
1256
+
1257
+ // bracket — [ ] brackets slide inward around the label on hover.
1258
+ .wui-link--bracket .wui-link__label {
1259
+ padding-inline: 0.45em;
1260
+ }
1261
+ .wui-link--bracket .wui-link__label::before,
1262
+ .wui-link--bracket .wui-link__label::after {
1263
+ position: absolute;
1264
+ top: 0;
1265
+ opacity: 0;
1266
+ transition: opacity 0.25s var(--wui-ease-out-quart), transform 0.25s var(--wui-ease-out-quart);
1267
+ }
1268
+ .wui-link--bracket .wui-link__label::before {
1269
+ content: "[";
1270
+ left: 0;
1271
+ transform: translateX(0.35em);
1272
+ }
1273
+ .wui-link--bracket .wui-link__label::after {
1274
+ content: "]";
1275
+ right: 0;
1276
+ transform: translateX(-0.35em);
1277
+ }
1278
+ .wui-link--bracket:hover .wui-link__label::before,
1279
+ .wui-link--bracket:focus-visible .wui-link__label::before,
1280
+ .wui-link--bracket:hover .wui-link__label::after,
1281
+ .wui-link--bracket:focus-visible .wui-link__label::after {
1282
+ opacity: 1;
1283
+ transform: translateX(0);
1284
+ }
1285
+
1286
+ // fade — starts in body text colour, resolves to the accent on hover.
1287
+ .wui-link--fade {
1288
+ color: var(--color-text);
1289
+ }
1290
+ .wui-link--fade:hover,
1291
+ .wui-link--fade:focus-visible {
1292
+ color: var(--color-primary);
1293
+ }
1294
+
1295
+ // static — the same thin single line, always visible (no text-decoration, so it matches
1296
+ // the animated underline's weight exactly). At rest the line is fully drawn; on hover it
1297
+ // re-draws with a left→right slide sweep, so the single line still animates.
1298
+ .wui-link--static .wui-link__label::after {
1299
+ transform: scaleX(1);
1300
+ transform-origin: left center;
1301
+ transition: none;
1302
+ }
1303
+ .wui-link--static:hover .wui-link__label::after,
1304
+ .wui-link--static:focus-visible .wui-link__label::after {
1305
+ animation: wui-link-underline-slide 0.32s var(--wui-ease-out-quart);
1306
+ }
1307
+ @keyframes wui-link-underline-slide {
1308
+ from {
1309
+ transform: scaleX(0);
1310
+ }
1311
+ to {
1312
+ transform: scaleX(1);
1313
+ }
1314
+ }
1315
+
1316
+ // grow — the label's letters open up on hover.
1317
+ .wui-link--grow {
1318
+ transition: color 0.25s var(--wui-ease-out-quart), letter-spacing 0.25s var(--wui-ease-out-quart);
1319
+ }
1320
+ .wui-link--grow:hover,
1321
+ .wui-link--grow:focus-visible {
1322
+ letter-spacing: 0.035em;
1323
+ }
1324
+
1325
+ // glow — a soft accent glow blooms behind the text on hover.
1326
+ .wui-link--glow {
1327
+ transition: color 0.25s var(--wui-ease-out-quart), text-shadow 0.3s var(--wui-ease-out-quart);
1328
+ }
1329
+ .wui-link--glow:hover,
1330
+ .wui-link--glow:focus-visible {
1331
+ text-shadow: 0 0 10px color-mix(in srgb, var(--color-primary) 55%, transparent);
1332
+ }
1333
+
1334
+ // shine — a light shimmer sweeps across the label on hover. The text is painted with a
1335
+ // gradient (mostly the accent, with a lighter band) clipped to the glyphs; hovering
1336
+ // animates that band from right to left across the word.
1337
+ .wui-link--shine .wui-link__label {
1338
+ background-image: linear-gradient(
1339
+ 110deg,
1340
+ var(--color-primary) 0%,
1341
+ var(--color-primary) 42%,
1342
+ color-mix(in srgb, var(--color-primary) 25%, white) 50%,
1343
+ var(--color-primary) 58%,
1344
+ var(--color-primary) 100%
1345
+ );
1346
+ background-size: 220% 100%;
1347
+ background-position: 100% 0;
1348
+ background-repeat: no-repeat;
1349
+ -webkit-background-clip: text;
1350
+ background-clip: text;
1351
+ -webkit-text-fill-color: transparent;
1352
+ color: transparent;
1353
+ }
1354
+ .wui-link--shine:hover .wui-link__label,
1355
+ .wui-link--shine:focus-visible .wui-link__label {
1356
+ animation: wui-link-shine 0.9s var(--wui-ease-out-quart);
1357
+ }
1358
+ @keyframes wui-link-shine {
1359
+ from {
1360
+ background-position: 100% 0;
1361
+ }
1362
+ to {
1363
+ background-position: 0 0;
1364
+ }
1365
+ }
1366
+
1367
+ // none — a plain coloured link, no underline, no motion.
1368
+ .wui-link--none .wui-link__label {
1369
+ text-decoration: none;
1370
+ }
1371
+
1372
+ @media (prefers-reduced-motion: reduce) {
1373
+ .wui-link,
1374
+ .wui-link--slide,
1375
+ .wui-link__label::after,
1376
+ .wui-link__label::before,
1377
+ .wui-link--icon .wui-link__ic--right {
1378
+ transition: none;
1379
+ }
1380
+ // Animated underlines settle to a visible static line rather than a hidden one.
1381
+ .wui-link--underline .wui-link__label::after,
1382
+ .wui-link--underline-center .wui-link__label::after {
1383
+ transform: scaleX(1);
1384
+ }
1385
+ // The static line drops its hover re-draw sweep.
1386
+ .wui-link--static:hover .wui-link__label::after,
1387
+ .wui-link--static:focus-visible .wui-link__label::after {
1388
+ animation: none;
1389
+ }
1390
+ .wui-link--slide:hover,
1391
+ .wui-link--slide:focus-visible {
1392
+ transform: none;
1393
+ }
1394
+ .wui-link--icon .wui-link__ic--right {
1395
+ opacity: 1;
1396
+ transform: none;
1397
+ margin-inline-start: 0;
1398
+ }
1399
+ .wui-link--highlight:hover .wui-link__label::before,
1400
+ .wui-link--highlight:focus-visible .wui-link__label::before {
1401
+ transform: none;
1402
+ }
1403
+ .wui-link--bracket .wui-link__label::before,
1404
+ .wui-link--bracket .wui-link__label::after {
1405
+ opacity: 1;
1406
+ transform: none;
1407
+ }
1408
+ // grow / glow settle to their rest state — no letter-spacing shift, no glow bloom.
1409
+ .wui-link--grow:hover,
1410
+ .wui-link--grow:focus-visible {
1411
+ letter-spacing: normal;
1412
+ }
1413
+ .wui-link--glow:hover,
1414
+ .wui-link--glow:focus-visible {
1415
+ text-shadow: none;
1416
+ }
1417
+ // shine drops the shimmer and paints the label a solid accent colour.
1418
+ .wui-link--shine:hover .wui-link__label,
1419
+ .wui-link--shine:focus-visible .wui-link__label {
1420
+ animation: none;
1421
+ }
1422
+ .wui-link--shine .wui-link__label {
1423
+ background-image: none;
1424
+ -webkit-text-fill-color: var(--color-primary);
1425
+ color: var(--color-primary);
1426
+ }
1427
+ }
@@ -676,10 +676,13 @@ button.step:focus-visible .step-circle {
676
676
  gap: 12px;
677
677
  }
678
678
  .repeater-row {
679
+ position: relative;
679
680
  display: flex;
680
681
  align-items: flex-start;
681
682
  gap: 12px;
682
- padding: 14px 16px;
683
+ // Extra top room so the remove control (absolute, top-right) sits in a header
684
+ // band clear of the fields — the fields start below it and still run full width.
685
+ padding: 28px 16px 16px;
683
686
  border: 1px solid var(--color-border);
684
687
  border-radius: var(--radius-lg);
685
688
  corner-shape: squircle;
@@ -705,8 +708,12 @@ button.step:focus-visible .step-circle {
705
708
  min-width: 0;
706
709
  }
707
710
  .repeater-remove {
708
- flex: none;
709
- margin-top: 4px;
711
+ // Pulled out of the row's flex flow and parked in the top-right corner, level
712
+ // with the field labels, so it never steals horizontal room from the fields —
713
+ // the last field can now run to the row's right edge instead of stopping short.
714
+ position: absolute;
715
+ top: 6px;
716
+ right: 8px;
710
717
  }
711
718
  .repeater-add {
712
719
  align-self: flex-start;
@@ -863,10 +863,8 @@
863
863
  // Round dropzone uploader (workspace/brand logo) and the settings-page photo
864
864
  // ring share this same interactive-uploader pattern.
865
865
  .set-uploader {
866
- // One knob for the whole control — the ring diameter. Presets and the numeric `size`
867
- // prop set it (inline); the ring, initials font and icons all derive from it, so any
868
- // px value scales the picker proportionally. md (120px) is the default.
869
- --wui-logo-size: 120px;
866
+ // The ring diameter (--wui-logo-size) is set on the shell, not here, so the sibling remove
867
+ // control inherits it too; the ring, initials font and icons all derive from it.
870
868
  display: inline-flex;
871
869
  padding: 0;
872
870
  border: none;
@@ -1026,8 +1024,11 @@
1026
1024
  width: max-content;
1027
1025
  max-width: 100%;
1028
1026
  }
1029
- // Positioning context for the remove control, which is a sibling of the button.
1027
+ // Positioning context for the remove control, which is a sibling of the button. It also owns the
1028
+ // one sizing knob — the ring diameter — so BOTH children (the button and the remove control)
1029
+ // inherit it. Presets and the numeric `size` prop set it inline; md (120px) is the default.
1030
1030
  .set-uploader-shell {
1031
+ --wui-logo-size: 120px;
1031
1032
  position: relative;
1032
1033
  display: inline-block;
1033
1034
  }
@@ -1105,8 +1106,11 @@
1105
1106
  display: inline-flex;
1106
1107
  align-items: center;
1107
1108
  justify-content: center;
1108
- width: 28px;
1109
- height: 28px;
1109
+ // Scales with the ring (~23% of the diameter reads well) so it stays proportional at every
1110
+ // size instead of dwarfing a small (sm) ring and vanishing on a large (lg) one. Clamped so it
1111
+ // never shrinks below a comfortable tap target or grows past the badge look; md lands on 28px.
1112
+ width: clamp(20px, calc(var(--wui-logo-size) * 0.23), 34px);
1113
+ height: clamp(20px, calc(var(--wui-logo-size) * 0.23), 34px);
1110
1114
  padding: 0;
1111
1115
  border: 2px solid var(--color-surface);
1112
1116
  border-radius: 50%;
@@ -1115,6 +1119,12 @@
1115
1119
  cursor: pointer;
1116
1120
  transition: transform 0.15s;
1117
1121
  }
1122
+ // The × glyph scales with the button (its width/height attributes are set in JS/Razor as a
1123
+ // baseline; this overrides them, the way the overlay icon is scaled under its container query).
1124
+ .set-uploader-remove > .wui-icon {
1125
+ width: clamp(11px, calc(var(--wui-logo-size) * 0.115), 17px);
1126
+ height: clamp(11px, calc(var(--wui-logo-size) * 0.115), 17px);
1127
+ }
1118
1128
  .set-uploader-remove:hover {
1119
1129
  transform: scale(1.08);
1120
1130
  }
@@ -193,6 +193,9 @@
193
193
  color: var(--color-text-muted);
194
194
  white-space: nowrap;
195
195
  cursor: pointer;
196
+ // A tab can be a real <a href> (link tab) — strip the browser's link underline so a link
197
+ // tab is visually identical to a button tab.
198
+ text-decoration: none;
196
199
  transition:
197
200
  color 0.12s ease,
198
201
  border-color 0.12s ease;
@@ -210,9 +213,18 @@
210
213
  object-fit: cover;
211
214
  border-radius: var(--radius-sm);
212
215
  }
213
- .app-tab:hover:not(:disabled):not(.active) {
216
+ .app-tab:hover:not(:disabled):not(.disabled):not(.active) {
214
217
  color: var(--color-text);
215
218
  }
219
+ // A link tab is a real <a>, and Bootstrap's `a:hover` (plus the theme's link hover) re-add an
220
+ // underline that the base `text-decoration: none` above (lower specificity) can't hold off. Scope
221
+ // under `.app-tabs` so these win in every state — a tab must read as a tab, never a link.
222
+ .app-tabs .app-tab,
223
+ .app-tabs .app-tab:hover,
224
+ .app-tabs .app-tab:focus,
225
+ .app-tabs .app-tab:focus-visible {
226
+ text-decoration: none;
227
+ }
216
228
  .app-tab.active {
217
229
  color: var(--color-primary);
218
230
  // The active tab carries weight as well as colour — colour alone is a weak
@@ -238,10 +250,20 @@
238
250
  .app-tabs-vertical .app-tab.active::after {
239
251
  display: none;
240
252
  }
241
- .app-tab:disabled {
253
+ // `:disabled` matches a disabled <button> tab; `.disabled`/[aria-disabled] cover a disabled
254
+ // link tab (an <a> can't be truly :disabled). Same inert, muted treatment for both.
255
+ .app-tab:disabled,
256
+ .app-tab.disabled,
257
+ .app-tab[aria-disabled="true"] {
242
258
  color: var(--color-text-subtle);
243
259
  cursor: not-allowed;
244
260
  }
261
+ // A disabled link tab already has no href; also stop it swallowing clicks (a <button:disabled>
262
+ // gets this from the UA — an <a> does not).
263
+ .app-tab.disabled,
264
+ .app-tab[aria-disabled="true"] {
265
+ pointer-events: none;
266
+ }
245
267
  .app-tab:focus-visible {
246
268
  outline: 2px solid var(--color-primary);
247
269
  outline-offset: -2px;
@@ -251,7 +273,7 @@
251
273
  .app-tab.danger {
252
274
  color: var(--color-danger-text);
253
275
  }
254
- .app-tab.danger:hover:not(:disabled):not(.active) {
276
+ .app-tab.danger:hover:not(:disabled):not(.disabled):not(.active) {
255
277
  color: var(--color-danger-text);
256
278
  }
257
279
  .app-tab.danger.active {
@@ -404,16 +404,18 @@
404
404
  // It sits above the element; for a collision-aware / richer tooltip, use AppTooltip /
405
405
  // <app-tooltip> instead.
406
406
  //
407
- // Two users, for different reasons:
408
- // - `.wui-icon-tip`, the wrapper the `tooltip` prop on AppIcon / <app-icon> puts around a
409
- // glyph (it adds the inline-flex layout below).
410
- // - <app-tabs> tab buttons, which CANNOT use a Bootstrap tooltip: the button already owns a
411
- // `bs.tab` instance and Bootstrap permits only one instance per element, so binding
412
- // `bs.Tooltip` to it logs "Bootstrap doesn't allow more than one instance per element"
413
- // and the tab instance is never registered.
407
+ // Sole remaining user: <app-tabs> tab buttons, which CANNOT use a Bootstrap tooltip — the button
408
+ // already owns a `bs.tab` instance and Bootstrap permits only one instance per element, so binding
409
+ // `bs.Tooltip` to it logs "Bootstrap doesn't allow more than one instance per element" and the tab
410
+ // instance is never registered. Everything else with a `tooltip` prop (AppIcon / <app-icon>
411
+ // included) now routes through the shared collision-aware AppTooltip / Bootstrap-tooltip (Popper)
412
+ // engine instead.
414
413
  [data-wui-tip] {
415
414
  position: relative;
416
415
  }
416
+ // The wrapper the `tooltip` prop on AppIcon / <app-icon> puts around a glyph — now just the
417
+ // inline-flex layout; the tooltip itself rides the shared Popper engine (Bootstrap-tooltip
418
+ // data-attributes on this span, AppTooltip on React), not the CSS bubble above.
417
419
  .wui-icon-tip {
418
420
  display: inline-flex;
419
421
  vertical-align: -0.125em;
@@ -424,7 +426,11 @@
424
426
  bottom: calc(100% + 8px);
425
427
  left: 50%;
426
428
  transform: translateX(-50%);
427
- z-index: var(--wui-z-menu, 1080);
429
+ // Sits on the dedicated tooltip rung — above menus/popovers/modals, below toasts — so it is
430
+ // never stacked under an overlay it is meant to annotate. This is a fixed-position CSS bubble
431
+ // (always above); for a collision-aware tooltip that flips to stay on-screen, use AppTooltip /
432
+ // <app-tooltip> (Popper), which AppActionIcon wraps for exactly that reason.
433
+ z-index: var(--wui-z-tooltip, 1650);
428
434
  width: max-content;
429
435
  max-width: 240px;
430
436
  padding: 7px 11px;