@nubisco/ui 3.1.0 → 3.3.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.
Files changed (83) hide show
  1. package/dist/components/BlueprintCard.vue.d.ts.map +1 -1
  2. package/dist/components/Button.vue.d.ts.map +1 -1
  3. package/dist/components/Charts/GanttChart.vue.d.ts.map +1 -1
  4. package/dist/components/Charts/InterpolationChart.vue.d.ts.map +1 -1
  5. package/dist/components/Charts/shared/palette.d.ts +76 -1
  6. package/dist/components/Charts/shared/palette.d.ts.map +1 -1
  7. package/dist/components/CommandPalette.vue.d.ts.map +1 -1
  8. package/dist/components/Confirm.d.ts +199 -0
  9. package/dist/components/Confirm.env.d.ts +52 -0
  10. package/dist/components/Confirm.env.d.ts.map +1 -0
  11. package/dist/components/Confirm.vue.d.ts +61 -0
  12. package/dist/components/Confirm.vue.d.ts.map +1 -0
  13. package/dist/components/DatePicker.vue.d.ts +1 -1
  14. package/dist/components/InlineLoading.d.ts +37 -0
  15. package/dist/components/InlineLoading.vue.d.ts +30 -0
  16. package/dist/components/InlineLoading.vue.d.ts.map +1 -0
  17. package/dist/components/MenuItem.vue.d.ts +1 -1
  18. package/dist/components/Modal.d.ts +35 -1
  19. package/dist/components/Modal.vue.d.ts +10 -1
  20. package/dist/components/Modal.vue.d.ts.map +1 -1
  21. package/dist/components/NotificationCenter.d.ts +184 -0
  22. package/dist/components/NotificationCenter.vue.d.ts +130 -0
  23. package/dist/components/NotificationCenter.vue.d.ts.map +1 -0
  24. package/dist/components/NotificationCenterItem.d.ts +53 -0
  25. package/dist/components/NotificationCenterItem.vue.d.ts +36 -0
  26. package/dist/components/NotificationCenterItem.vue.d.ts.map +1 -0
  27. package/dist/components/ProgressBar.vue.d.ts.map +1 -1
  28. package/dist/components/Shell.d.ts +138 -8
  29. package/dist/components/Shell.vue.d.ts +30 -3
  30. package/dist/components/Shell.vue.d.ts.map +1 -1
  31. package/dist/components/ShellPanel.d.ts +16 -0
  32. package/dist/components/ShellPanel.vue.d.ts +1 -0
  33. package/dist/components/ShellPanel.vue.d.ts.map +1 -1
  34. package/dist/components/SidebarLink.d.ts +14 -1
  35. package/dist/components/Skeleton.d.ts +87 -0
  36. package/dist/components/Skeleton.vue.d.ts +17 -0
  37. package/dist/components/Skeleton.vue.d.ts.map +1 -0
  38. package/dist/components/Spinner.d.ts +80 -0
  39. package/dist/components/Spinner.overlay.d.ts +56 -0
  40. package/dist/components/Spinner.overlay.d.ts.map +1 -0
  41. package/dist/components/Spinner.vue.d.ts +25 -0
  42. package/dist/components/Spinner.vue.d.ts.map +1 -0
  43. package/dist/components/Stepper.context.d.ts +48 -0
  44. package/dist/components/Stepper.context.d.ts.map +1 -0
  45. package/dist/components/Stepper.d.ts +101 -0
  46. package/dist/components/Stepper.vue.d.ts +31 -0
  47. package/dist/components/Stepper.vue.d.ts.map +1 -0
  48. package/dist/components/StepperStep.vue.d.ts +24 -0
  49. package/dist/components/StepperStep.vue.d.ts.map +1 -0
  50. package/dist/components/TextInput.vue.d.ts +2 -2
  51. package/dist/components/TextInput.vue.d.ts.map +1 -1
  52. package/dist/components/Toast.vue.d.ts +43 -0
  53. package/dist/components/Toast.vue.d.ts.map +1 -1
  54. package/dist/components/Toaster.d.ts +93 -0
  55. package/dist/components/Toaster.vue.d.ts +30 -0
  56. package/dist/components/Toaster.vue.d.ts.map +1 -0
  57. package/dist/components/UserMenu.vue.d.ts.map +1 -1
  58. package/dist/components/index.d.ts.map +1 -1
  59. package/dist/composables/useConfirm.composable.d.ts +45 -0
  60. package/dist/composables/useConfirm.composable.d.ts.map +1 -0
  61. package/dist/composables/useInlineLoading.composable.d.ts +43 -0
  62. package/dist/composables/useInlineLoading.composable.d.ts.map +1 -0
  63. package/dist/composables/useLoadingAnnouncer.composable.d.ts +63 -0
  64. package/dist/composables/useLoadingAnnouncer.composable.d.ts.map +1 -0
  65. package/dist/composables/useScrollLock.composable.d.ts +47 -0
  66. package/dist/composables/useScrollLock.composable.d.ts.map +1 -0
  67. package/dist/composables/useShellSlot.composable.d.ts +193 -0
  68. package/dist/composables/useShellSlot.composable.d.ts.map +1 -0
  69. package/dist/composables/useToast.composable.d.ts +325 -0
  70. package/dist/composables/useToast.composable.d.ts.map +1 -0
  71. package/dist/directives/ToolTip.content.d.ts +20 -0
  72. package/dist/directives/ToolTip.content.d.ts.map +1 -0
  73. package/dist/directives/ToolTip.directive.d.ts +94 -8
  74. package/dist/directives/ToolTip.directive.d.ts.map +1 -1
  75. package/dist/global.d.ts +18 -0
  76. package/dist/index.cjs +8 -9
  77. package/dist/index.mjs +17653 -14503
  78. package/dist/main.d.ts +43 -2
  79. package/dist/main.d.ts.map +1 -1
  80. package/dist/ui.css +1 -1
  81. package/package.json +1 -1
  82. package/src/styles/_theme.scss +171 -0
  83. package/src/styles/directives/_tooltip.scss +143 -21
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nubisco/ui",
3
- "version": "3.1.0",
3
+ "version": "3.3.0",
4
4
  "description": "Vue 3 UI component library",
5
5
  "packageManager": "pnpm@11.0.8",
6
6
  "repository": {
@@ -146,6 +146,23 @@
146
146
  --nb-c-primary-hover-a11y: var(--nb-c-grape-hyacinth-600-a11y);
147
147
  --nb-c-primary-active: var(--nb-c-grape-hyacinth-700);
148
148
  --nb-c-primary-active-a11y: var(--nb-c-grape-hyacinth-700-a11y);
149
+ // A pale wash of the brand, for an edge drawn ON a primary fill (the
150
+ // ghost button's hover border is the only consumer today). It was
151
+ // var(--nb-c-grape-hyacinth-200) at the call site, which forced a
152
+ // white-label product to name the vendor's ramp in its own stylesheet.
153
+ //
154
+ // Derived, not aliased: an alias would still be violet after a product
155
+ // sets --nb-c-primary, which is the exact leak this token exists to
156
+ // close. 39% of the brand on white measures dE2000 0.71 from the tint
157
+ // it replaced, under the ~1.0 just-noticeable threshold, so no shipped
158
+ // hover border visibly moves. The dark theme repeats the mix at 50%
159
+ // because its --nb-c-primary is already a lighter step of the same ramp
160
+ // and needs less white to land on the same wash (dE2000 0.00 there).
161
+ --nb-c-primary-subtle: color-mix(
162
+ in srgb,
163
+ var(--nb-c-primary) 39%,
164
+ var(--nb-c-white)
165
+ );
149
166
 
150
167
  --nb-c-secondary: var(--nb-c-plain-black-700);
151
168
  --nb-c-secondary-a11y: var(--nb-c-plain-black-700-a11y);
@@ -249,6 +266,59 @@
249
266
  --nb-c-status-warning: var(--nb-c-phoenix-flames-700);
250
267
  --nb-c-status-error: var(--nb-c-chicken-comb-600);
251
268
 
269
+ // == Data visualisation roles ==========================================
270
+ // Charts address colour by ROLE, never by ramp: a series is "chart-3",
271
+ // not "emerald". The eight defaults below alias exactly the ramps that
272
+ // Charts/shared/palette.ts used to name inline, so this theme repaints
273
+ // nothing, but a white-label product now retints every chart in the
274
+ // product by redefining eight custom properties in one place instead of
275
+ // hardcoding hex per app. (A green-branded insurance product did the
276
+ // hardcoding and shipped a violet placeholder series.)
277
+ //
278
+ // Order is part of the contract. colorAt() walks 1, 2, 3 ... and wraps
279
+ // at 8, so the sequence has to degrade gracefully: the low roles are the
280
+ // best separated pair we have. Minimum CIEDE2000 across the first five
281
+ // roles is 16.0 under normal vision; across all eight it falls to 6.5,
282
+ // because roles 1 and 7 are both violet-blues. Under simulated
283
+ // deuteranopia the same numbers are 5.2 and 0.8. That is a property of
284
+ // the brand ramps, not of this indirection, and it is why the docs cap
285
+ // colour-only encoding at five series. See charts/color.md.
286
+ //
287
+ // Contrast against layer-1, in order: 7.28, 5.25, 3.16, 2.06, 4.62,
288
+ // 5.68, 7.04, 4.63. Two roles miss WCAG 1.4.11 at 3:1 here: role 4
289
+ // (amber) on every layer, and role 3 on layer-0 and layer-2 (2.85 and
290
+ // 2.69, it only clears on layer-1). Both stay because raising them
291
+ // would repaint charts already in production, and both are documented
292
+ // as fill-only, never a 1px line or a legend dot on its own.
293
+ // Re-measure any change with tests/chartRoles.test.ts.
294
+ --nb-c-chart-1: var(--nb-c-grape-hyacinth-500);
295
+ --nb-c-chart-2: var(--nb-c-the-blues-brothers-500);
296
+ --nb-c-chart-3: var(--nb-c-emerald-reflection-600);
297
+ --nb-c-chart-4: var(--nb-c-phoenix-flames-500);
298
+ --nb-c-chart-5: var(--nb-c-chicken-comb-500);
299
+ --nb-c-chart-6: var(--nb-c-liberty-blue-500);
300
+ --nb-c-chart-7: var(--nb-c-powder-blue-500);
301
+ --nb-c-chart-8: var(--nb-c-nouveau-gray-500);
302
+
303
+ // Sequential ramp anchors: one hue, lightness carries the magnitude.
304
+ // Interpolated at paint time by sequentialAt() in the chart palette
305
+ // module, so a retint of these two ends retints the whole ramp. In this
306
+ // theme the largest value is the darkest end (L* 87.4 down to 21.2),
307
+ // which inverts under .dark.
308
+ --nb-c-chart-sequential-from: var(--nb-c-grape-hyacinth-100);
309
+ --nb-c-chart-sequential-to: var(--nb-c-grape-hyacinth-700);
310
+
311
+ // Diverging ramp anchors: two hues meeting at a neutral zero point.
312
+ // Cool to warm rather than green to red, because a green/red pair is
313
+ // the one axis a deuteranope cannot read at all, and a diverging scale
314
+ // has no legend position to fall back on. The two ends are matched in
315
+ // lightness (L* 44.4 and 47.9) so neither pole looks heavier than the
316
+ // other, and the midpoint is near the page ground so zero reads as
317
+ // "nothing here".
318
+ --nb-c-chart-diverging-low: var(--nb-c-the-blues-brothers-500);
319
+ --nb-c-chart-diverging-mid: var(--nb-c-nouveau-gray-150);
320
+ --nb-c-chart-diverging-high: var(--nb-c-chicken-comb-500);
321
+
252
322
  // ── Node graph (NbBlueprint) ──────────────────────────────────────────
253
323
  // A port is a control, and it sits OUTSIDE the card on the canvas
254
324
  // ground, so its outline is measured against layer-0 rather than the
@@ -454,6 +524,10 @@
454
524
  var(--nb-c-black)
455
525
  );
456
526
  --nb-shell-sidebar-width: calc(var(--nb-base-unit) * 7); // 56px
527
+ // The verbose variant's rail width. A token rather than a literal inside
528
+ // Shell.vue's variant rule, so a product with long navigation labels can
529
+ // widen the rail without re-declaring the variant.
530
+ --nb-shell-sidebar-width-verbose: calc(var(--nb-base-unit) * 30); // 240px
457
531
  --nb-shell-sidebar-link-color: color-mix(
458
532
  in srgb,
459
533
  var(--nb-c-white) 55%,
@@ -483,15 +557,70 @@
483
557
  var(--nb-c-white)
484
558
  );
485
559
  --nb-shell-topbar-height: calc(var(--nb-base-unit) * 6.25); // 50px
560
+
561
+ // Shell chrome. Every rule that paints the frame reads one of these, so a
562
+ // dark or white-label product retints the shell by setting a token on
563
+ // :root instead of out-specifying Shell.vue's scoped [data-v-] selectors
564
+ // from a built stylesheet. The defaults point at the semantic tokens the
565
+ // chrome already used, so the pixels are unchanged, and because var()
566
+ // resolves where it is used they follow the .dark theme with no dark-mode
567
+ // duplicate of this block.
568
+ //
569
+ // Three levels: --nb-shell-bg for the frame ground, --nb-shell-chrome-* as
570
+ // the one place to retint every strip at once, and the per-region tokens
571
+ // that default to the chrome ones for when only the topbar should differ.
572
+ --nb-shell-bg: var(--nb-c-surface-raised);
573
+ --nb-shell-chrome-bg: var(--nb-c-surface);
574
+ --nb-shell-chrome-color: var(--nb-c-text);
575
+ --nb-shell-chrome-border: 1px solid var(--nb-c-border);
576
+ --nb-shell-chrome-padding-x: calc(var(--nb-base-unit) * 3); // 24px
577
+ --nb-shell-topbar-bg: var(--nb-shell-chrome-bg);
578
+ --nb-shell-topbar-color: var(--nb-shell-chrome-color);
579
+ --nb-shell-topbar-border: var(--nb-shell-chrome-border);
580
+ --nb-shell-fixedbar-bg: var(--nb-shell-chrome-bg);
581
+ --nb-shell-fixedbar-color: var(--nb-shell-chrome-color);
582
+ --nb-shell-fixedbar-border: var(--nb-shell-chrome-border);
583
+ // Shared by the outer menu, the inner menu and the legacy menubar: they
584
+ // are one visual family and no application has ever wanted them to differ.
585
+ --nb-shell-menu-bg: var(--nb-shell-chrome-bg);
586
+ --nb-shell-menu-color: var(--nb-shell-chrome-color);
587
+ --nb-shell-menu-border: var(--nb-shell-chrome-border);
588
+ --nb-shell-body-bg: var(--nb-shell-chrome-bg);
589
+ // The page ground behind the main region. Kept separate from the chrome:
590
+ // a branded topbar usually sits above an unbranded page.
591
+ --nb-shell-main-bg: var(--nb-c-surface);
486
592
  --nb-shell-inspector-width: calc(var(--nb-base-unit) * 70); // 560px
487
593
  --nb-shell-inspector-expanded-width: 50vw;
594
+ // Ceiling for a visible inspector, whatever set its width (a size preset,
595
+ // the expanded flag, or a dragged width). Half the viewport is the point
596
+ // at which the inspector stops being a side panel and starts being the
597
+ // page. Set it to `none` to opt out.
598
+ // `max()` with the resize floor is load-bearing: half a 560px-wide window
599
+ // is 280px, below the 288px floor the resize handle announces and clamps
600
+ // to, so a bare 50vw made the announced value, the clamped value and the
601
+ // rendered width disagree on a narrow viewport. The floor wins there.
602
+ --nb-shell-inspector-max-width: max(calc(var(--nb-base-unit) * 36), 50vw);
488
603
  --nb-shell-inspector-border: 1px solid var(--nb-c-border);
489
604
  --nb-shell-inspector-bg: var(--nb-c-surface);
605
+ // The grab-line on the inspector's resize handle. A token, not a literal
606
+ // brand hex: a white-label product should never have to name the vendor's
607
+ // ramp to restyle the frame.
608
+ --nb-shell-inspector-resize-color: var(--nb-c-primary);
609
+ // The dimmer behind the navigation drawer at collapsed widths. Black at
610
+ // 45% reads on both themes because it darkens whatever is under it rather
611
+ // than painting a colour of its own.
612
+ --nb-shell-scrim-bg: color-mix(in srgb, var(--nb-c-black) 45%, transparent);
490
613
 
491
614
  // Panels — emitted at :root so consumer apps can override on :root without
492
615
  // fighting the components' scoped-style specificity.
493
616
  --nb-shell-panel-header-height: calc(var(--nb-base-unit) * 3.5); // 28px
494
617
  --nb-shell-panel-gap: 1px; // hairline separator, not a spacing step
618
+ --nb-shell-panel-bg: var(--nb-c-surface);
619
+ --nb-shell-panel-border: 1px solid var(--nb-c-border);
620
+ // Transparent by default so the header reads as part of the panel; set it
621
+ // when a product wants the header bar tinted against its own content.
622
+ --nb-shell-panel-header-bg: transparent;
623
+ --nb-shell-panel-header-border: 1px solid var(--nb-c-border);
495
624
  --nb-bottom-panel-header-height: calc(var(--nb-base-unit) * 3.5); // 28px
496
625
  --nb-bottom-panel-bg: var(--nb-c-layer-0, var(--nb-c-bg));
497
626
  --nb-bottom-panel-border: var(--nb-c-border);
@@ -506,6 +635,13 @@
506
635
  .dark {
507
636
  // Status colors — lighter tints for dark backgrounds
508
637
  --nb-c-primary: #{colors.get-color('grape-hyacinth', 400)};
638
+ // Same wash, re-mixed: the dark --nb-c-primary is a 400, so it takes less
639
+ // white than the light 500 does to reach the tint this token used to alias.
640
+ --nb-c-primary-subtle: color-mix(
641
+ in srgb,
642
+ var(--nb-c-primary) 50%,
643
+ var(--nb-c-white)
644
+ );
509
645
  --nb-c-primary-a11y: #{colors.color-contrast(
510
646
  colors.get-color('grape-hyacinth', 400)
511
647
  )};
@@ -634,6 +770,41 @@
634
770
  --nb-c-status-warning: #{colors.get-color('phoenix-flames', 400)};
635
771
  --nb-c-status-error: #{colors.get-color('chicken-comb', 300)};
636
772
 
773
+ // Data visualisation roles, dark. Same eight hues, stepped up their
774
+ // ramps, exactly as --nb-c-primary and the status colours are stepped
775
+ // here. The light values are mid-ramp fills chosen against a white
776
+ // page: three of them (roles 1, 6 and 7 at 2.17, 2.78 and 2.25 against
777
+ // layer-1) sink into a dark one and were failing WCAG 1.4.11 in every
778
+ // dark-mode chart the fleet renders. The tints below measure 5.48,
779
+ // 4.42, 7.69, 8.89, 4.32, 4.30, 4.49 and 5.56 against layer-1, and all
780
+ // eight clear 3:1 on layer-0 through layer-2, which the light set does
781
+ // not manage. They do NOT clear it on layer-3, the lightest dark
782
+ // surface (roles 2, 5, 6 and 7 land between 2.79 and 2.92), so do not
783
+ // paint a chart on layer-3 in dark without overriding these. Role 7 is
784
+ // a 350 rather than the 400 its neighbours use: at 400 it measured
785
+ // 2.95 on layer-2, and contrast wins over separation on the role that
786
+ // is already the weakest of the eight. Minimum CIEDE2000 is 15.4 over
787
+ // the first five roles and 5.5 over all eight.
788
+ --nb-c-chart-1: var(--nb-c-grape-hyacinth-300);
789
+ --nb-c-chart-2: var(--nb-c-the-blues-brothers-400);
790
+ --nb-c-chart-3: var(--nb-c-emerald-reflection-500);
791
+ --nb-c-chart-4: var(--nb-c-phoenix-flames-400);
792
+ --nb-c-chart-5: var(--nb-c-chicken-comb-400);
793
+ --nb-c-chart-6: var(--nb-c-liberty-blue-400);
794
+ --nb-c-chart-7: var(--nb-c-powder-blue-350);
795
+ --nb-c-chart-8: var(--nb-c-nouveau-gray-400);
796
+
797
+ // Sequential runs the other way on a dark page: the largest value is
798
+ // the brightest end, so "from" is the tint nearest the background.
799
+ --nb-c-chart-sequential-from: var(--nb-c-grape-hyacinth-800);
800
+ --nb-c-chart-sequential-to: var(--nb-c-grape-hyacinth-200);
801
+
802
+ // Diverging keeps its two hues and moves the zero point to a dark
803
+ // neutral. Ends stepped one tint lighter to hold 4.3:1 on layer-1.
804
+ --nb-c-chart-diverging-low: var(--nb-c-the-blues-brothers-400);
805
+ --nb-c-chart-diverging-mid: var(--nb-c-nouveau-gray-800);
806
+ --nb-c-chart-diverging-high: var(--nb-c-chicken-comb-400);
807
+
637
808
  // Discrete
638
809
  --nb-c-discrete: #{colors.get-color('nouveau-gray', 700)};
639
810
  --nb-c-component-discrete-text: #{colors.get-color('nouveau-gray', 300)};
@@ -1,24 +1,101 @@
1
1
  @mixin tooltip {
2
+ // The description node. Not a chip: it is the text of the tooltip as
3
+ // assistive technology sees it, present from the moment the binding mounts,
4
+ // referenced by the anchor's `aria-describedby` the whole time.
5
+ //
6
+ // Visually hidden, never `display: none` and never the `hidden` attribute.
7
+ // Both of those remove it from the accessibility tree, which is the one
8
+ // place it has to be; the whole reason it exists is that a description
9
+ // computed at focus time cannot be waiting on a hover-intent timer.
10
+ .nb-tooltip-description {
11
+ position: absolute;
12
+ inset-block-start: 0;
13
+ inset-inline-start: 0;
14
+ inline-size: 1px;
15
+ block-size: 1px;
16
+ padding: 0;
17
+ margin: 0;
18
+ border: 0;
19
+ overflow: hidden;
20
+ // `clip-path` rather than the legacy `clip`: it is not deprecated and it
21
+ // does not force the element out of the flow in a way that some engines
22
+ // use to skip it entirely.
23
+ clip-path: inset(50%);
24
+ white-space: nowrap;
25
+ pointer-events: none;
26
+ }
27
+
2
28
  .nb-tooltip {
3
29
  position: fixed;
4
30
  overflow: visible;
5
- padding: var(--nb-base-unit);
31
+ // Logical, so a chip in an Arabic or Hebrew build gets its padding on the
32
+ // sides the text actually starts and ends on. The arrow offsets further
33
+ // down stay physical on purpose: placement is computed from geometry in
34
+ // the directive (which side has room), and geometry has no direction.
35
+ padding-block: var(--nb-base-unit);
36
+ padding-inline: var(--nb-base-unit);
6
37
  border-radius: calc(var(--nb-base-unit) / 1.333);
7
- background: var(--nb-c-discrete);
8
- color: var(--nb-c-discrete-a11y);
38
+ // Inverted surface, not the discrete grey pair. Two reasons: the grey
39
+ // pair only flips its background between themes (its -a11y foreground
40
+ // stays the light-theme value, so a dark-theme chip was dark text on a
41
+ // dark chip), and every hand-rolled tooltip in the fleet, including the
42
+ // two inside this library, independently chose an inverted chip. The two
43
+ // vars let a consumer retint one chip without touching the theme.
44
+ background: var(--nb-tooltip-bg, var(--nb-c-contrast));
45
+ color: var(--nb-tooltip-fg, var(--nb-c-surface));
9
46
  z-index: var(--nb-zindex-tooltip);
10
47
  text-transform: none;
11
48
  font-size: var(--nb-font-size-13);
12
- line-height: 1;
13
- user-select: none;
14
- pointer-events: none;
49
+ // A chip has no width of its own: it shrink-wraps its text. Without a
50
+ // ceiling, a 400-character body renders as ONE line wider than the
51
+ // viewport, and the directive's clamp cannot rescue it (the maximum left
52
+ // goes negative, so the chip is pushed back to the inset and runs off the
53
+ // right edge). 34 base units is about 55 characters at this size, a
54
+ // comfortable measure. `--nb-tooltip-max-width` widens or narrows it for
55
+ // one chip; the answer for anything that needs much more is not a
56
+ // tooltip.
57
+ max-inline-size: var(
58
+ --nb-tooltip-max-width,
59
+ calc(var(--nb-base-unit) * 34)
60
+ );
61
+ // Now that the text can wrap, it needs leading. At `line-height: 1` a
62
+ // wrapped body, a documented `<br>` and an embedded newline all render as
63
+ // touching lines.
64
+ line-height: var(--nb-line-height-snug);
65
+ // A single unbroken token (a URL, a hash, a file path) would otherwise
66
+ // blow straight through the max-width.
67
+ overflow-wrap: anywhere;
68
+ // The chip is reachable by the pointer. WCAG 2.1 AA 1.4.13 ("Hoverable")
69
+ // requires that content revealed on hover can be hovered without
70
+ // disappearing, and `pointer-events: none` makes that impossible by
71
+ // construction: nothing the user does with the mouse can ever land on the
72
+ // chip. That is not a defensible default for the one affordance in this
73
+ // library whose whole purpose is labelling controls, and it is the
74
+ // difference between readable and unreadable for anyone at 200% zoom, who
75
+ // has to pan onto a clamped chip to finish the sentence.
76
+ //
77
+ // The directive supplies the other half: entering the chip cancels the
78
+ // pending hide, leaving it restarts it. The chip stays out of the tab
79
+ // order and holds nothing operable, so it is still not a container.
80
+ pointer-events: auto;
81
+ // Text in a chip is often the thing worth copying: an error code, a full
82
+ // path, a signature. There is no reason to forbid selecting it now that
83
+ // the pointer can reach it.
84
+ user-select: text;
85
+ // Structural containment, defence in depth for caller-supplied content.
86
+ // `contain: layout` makes the chip the containing block for any
87
+ // absolutely or fixed-positioned descendant, so a class or a style that
88
+ // reached the chip from a CMS field or an API payload cannot escape the
89
+ // chip's box to become a full-viewport overlay. The content sanitiser
90
+ // already refuses `position`; this is the belt to that pair of braces,
91
+ // and it does not clip (the arrow pseudo-elements still overflow).
92
+ contain: layout;
15
93
  opacity: 0;
16
- text-align: center;
17
- --tooltip-arrow-border-color: var(--nb-c-discrete);
18
- transform: scale(0.95);
19
- transition:
20
- opacity 0.2s ease,
21
- transform 0.2s ease;
94
+ // `start`, not `center`. A one-line chip shrink-wraps its text so the two
95
+ // are indistinguishable, and a wrapped chip is far easier to read ragged
96
+ // right than centred.
97
+ text-align: start;
98
+ --tooltip-arrow-border-color: var(--nb-tooltip-bg, var(--nb-c-contrast));
22
99
  &:before,
23
100
  &:after {
24
101
  position: absolute;
@@ -47,32 +124,64 @@
47
124
  }
48
125
  &-tip {
49
126
  font-weight: var(--nb-font-weight-regular);
50
- color: var(--nb-c-component-discrete-text);
127
+ // Demoted by transparency rather than by a second colour token: on an
128
+ // inverted chip any fixed grey is right in one theme and wrong in the
129
+ // other, while 78% of an already-contrasting foreground stays legible
130
+ // in both.
131
+ opacity: 0.78;
51
132
  }
52
133
 
134
+ // Every flavour foreground is the theme's auto-contrast companion for its
135
+ // own background, never a literal. `--nb-c-danger` and friends are not the
136
+ // same colour in both themes (the dark theme uses a lighter step of the
137
+ // same ramp), so a hardcoded white is correct in at most one of them and
138
+ // is the WORST available choice in the other. The `-a11y` companions are
139
+ // computed from the background they sit on, which is why the rest of the
140
+ // library uses them.
53
141
  &-flavor {
54
142
  &-primary {
55
143
  background: var(--nb-c-primary);
56
- color: var(--nb-c-white);
144
+ color: var(--nb-c-primary-a11y);
57
145
  --tooltip-arrow-border-color: var(--nb-c-primary);
58
146
  .nb-tooltip-tip {
59
- color: var(--nb-c-white);
147
+ color: var(--nb-c-primary-a11y);
60
148
  }
61
149
  }
62
150
  &-danger {
63
151
  background: var(--nb-c-danger);
64
- color: var(--nb-c-white);
152
+ color: var(--nb-c-danger-a11y);
65
153
  --tooltip-arrow-border-color: var(--nb-c-danger);
66
154
  .nb-tooltip-tip {
67
- color: var(--nb-c-white);
155
+ color: var(--nb-c-danger-a11y);
68
156
  }
69
157
  }
70
158
  &-info {
71
159
  background: var(--nb-c-info);
72
- color: var(--nb-c-white);
160
+ color: var(--nb-c-info-a11y);
73
161
  --tooltip-arrow-border-color: var(--nb-c-info);
74
162
  .nb-tooltip-tip {
75
- color: var(--nb-c-white);
163
+ color: var(--nb-c-info-a11y);
164
+ }
165
+ }
166
+ // `success` and `warning` exist because the library's status vocabulary
167
+ // has them (NbBanner.status, EVariant) and the tooltip was the one place
168
+ // that silently did not: `flavor: 'warning'` used to compile and emit an
169
+ // unstyled chip. Same construction as the three above, so a themed build
170
+ // that moves the ramp moves these with it.
171
+ &-success {
172
+ background: var(--nb-c-success);
173
+ color: var(--nb-c-success-a11y);
174
+ --tooltip-arrow-border-color: var(--nb-c-success);
175
+ .nb-tooltip-tip {
176
+ color: var(--nb-c-success-a11y);
177
+ }
178
+ }
179
+ &-warning {
180
+ background: var(--nb-c-warning);
181
+ color: var(--nb-c-warning-a11y);
182
+ --tooltip-arrow-border-color: var(--nb-c-warning);
183
+ .nb-tooltip-tip {
184
+ color: var(--nb-c-warning-a11y);
76
185
  }
77
186
  }
78
187
  }
@@ -164,23 +273,36 @@
164
273
  }
165
274
  }
166
275
 
167
- // Animation for appearing
276
+ @media (prefers-reduced-motion: reduce) {
277
+ .nb-tooltip {
278
+ animation: none !important;
279
+ transform: none;
280
+ }
281
+ }
282
+
283
+ // Entrance and exit. Both the fade and the small rise live in the keyframes
284
+ // rather than in a base `transform` plus a `transition`: the directive only
285
+ // ever sets `animation`, so a base transform had nothing to transition back
286
+ // from and every chip rendered permanently scaled down.
168
287
  @keyframes tooltipAppear {
169
288
  0% {
170
289
  opacity: 0;
290
+ transform: scale(0.96);
171
291
  }
172
292
  100% {
173
293
  opacity: 1;
294
+ transform: scale(1);
174
295
  }
175
296
  }
176
297
 
177
- // Animation for disappearing
178
298
  @keyframes tooltipDisappear {
179
299
  0% {
180
300
  opacity: 1;
301
+ transform: scale(1);
181
302
  }
182
303
  100% {
183
304
  opacity: 0;
305
+ transform: scale(0.98);
184
306
  }
185
307
  }
186
308
  }