srcdev-nuxt-components 9.4.4 → 9.4.5

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 (84) hide show
  1. package/.claude/commands/migrate-component.md +68 -0
  2. package/.claude/component-ledger/artifact-url.txt +1 -0
  3. package/.claude/component-ledger/audit.json +1 -0
  4. package/.claude/component-ledger/build.mjs +111 -0
  5. package/.claude/component-ledger/output.html +559 -0
  6. package/.claude/component-ledger/template.html +559 -0
  7. package/.claude/hooks/refresh-component-ledger.sh +32 -0
  8. package/.claude/settings.json +10 -0
  9. package/.claude/skills/components/animated-svg-text.md +78 -0
  10. package/.claude/skills/components/canvas-switcher.md +61 -0
  11. package/.claude/skills/components/clip-element.md +67 -0
  12. package/.claude/skills/components/clipped-panel.md +64 -0
  13. package/.claude/skills/components/deep-expanding-menu-classic.md +94 -0
  14. package/.claude/skills/components/deep-expanding-menu.md +92 -0
  15. package/.claude/skills/components/display-banner.md +60 -0
  16. package/.claude/skills/components/display-tooltip-defined.md +136 -0
  17. package/.claude/skills/components/display-tooltip.md +84 -0
  18. package/.claude/skills/composable-tooltips-guide.md +10 -0
  19. package/.claude/skills/index.md +7 -1
  20. package/.claude/skills/storybook-add-story.md +30 -0
  21. package/.vscode/srcdev-component-animated-svg-text.code-snippets +15 -0
  22. package/.vscode/srcdev-component-canvas-switcher.code-snippets +9 -0
  23. package/.vscode/srcdev-component-clip-element.code-snippets +20 -0
  24. package/.vscode/srcdev-component-clipped-panel.code-snippets +23 -0
  25. package/.vscode/srcdev-component-deep-expanding-menu-classic.code-snippets +9 -0
  26. package/.vscode/srcdev-component-deep-expanding-menu.code-snippets +9 -0
  27. package/.vscode/srcdev-component-display-banner.code-snippets +30 -0
  28. package/.vscode/srcdev-component-display-tooltip-defined.code-snippets +14 -0
  29. package/.vscode/srcdev-component-display-tooltip.code-snippets +27 -0
  30. package/app/components/01.atoms/animations/animated-svg-text/AnimatedSvgText.vue +58 -0
  31. package/app/components/01.atoms/animations/animated-svg-text/CONSUMER-STYLING.md +34 -0
  32. package/app/components/01.atoms/animations/animated-svg-text/stories/AnimatedSvgText.stories.ts +69 -0
  33. package/app/components/01.atoms/animations/animated-svg-text/tests/AnimatedSvgText.spec.ts +63 -0
  34. package/app/components/01.atoms/animations/animated-svg-text/tests/__snapshots__/AnimatedSvgText.spec.ts.snap +7 -0
  35. package/app/components/01.atoms/animations/clip-element/CONSUMER-STYLING.md +27 -0
  36. package/app/components/{clip-element → 01.atoms/animations/clip-element}/ClipElement.vue +16 -11
  37. package/app/components/01.atoms/animations/clip-element/stories/ClipElement.stories.ts +133 -0
  38. package/app/components/01.atoms/animations/clip-element/tests/ClipElement.spec.ts +70 -0
  39. package/app/components/01.atoms/animations/clip-element/tests/__snapshots__/ClipElement.spec.ts.snap +7 -0
  40. package/app/components/01.atoms/canvas-switcher/CONSUMER-STYLING.md +27 -0
  41. package/app/components/01.atoms/canvas-switcher/CanvasSwitcher.vue +111 -0
  42. package/app/components/01.atoms/canvas-switcher/stories/CanvasSwitcher.stories.ts +75 -0
  43. package/app/components/01.atoms/canvas-switcher/tests/CanvasSwitcher.spec.ts +67 -0
  44. package/app/components/01.atoms/clipped-panel/CONSUMER-STYLING.md +39 -0
  45. package/app/components/01.atoms/clipped-panel/ClippedPanel.vue +68 -0
  46. package/app/components/01.atoms/clipped-panel/stories/ClippedPanel.stories.ts +105 -0
  47. package/app/components/01.atoms/clipped-panel/tests/ClippedPanel.spec.ts +67 -0
  48. package/app/components/01.atoms/clipped-panel/tests/__snapshots__/ClippedPanel.spec.ts.snap +3 -0
  49. package/app/components/01.atoms/display-banner/CONSUMER-STYLING.md +35 -0
  50. package/app/components/{display-banner → 01.atoms/display-banner}/DisplayBanner.vue +13 -35
  51. package/app/components/01.atoms/display-banner/stories/DisplayBanner.stories.ts +100 -0
  52. package/app/components/01.atoms/display-banner/tests/DisplayBanner.spec.ts +78 -0
  53. package/app/components/01.atoms/display-banner/tests/__snapshots__/DisplayBanner.spec.ts.snap +8 -0
  54. package/app/components/01.atoms/display-tooltip/CONSUMER-STYLING.md +62 -0
  55. package/app/components/01.atoms/display-tooltip/DisplayTooltip.vue +185 -0
  56. package/app/components/01.atoms/display-tooltip/stories/DisplayTooltip.stories.ts +94 -0
  57. package/app/components/01.atoms/display-tooltip/tests/DisplayTooltip.spec.ts +75 -0
  58. package/app/components/01.atoms/display-tooltip/tests/__snapshots__/DisplayTooltip.spec.ts.snap +12 -0
  59. package/app/components/02.molecules/display-tooltip-defined/CONSUMER-STYLING.md +42 -0
  60. package/app/components/02.molecules/display-tooltip-defined/DisplayTooltipDefined.vue +103 -0
  61. package/app/components/02.molecules/display-tooltip-defined/stories/DisplayTooltipDefined.stories.ts +126 -0
  62. package/app/components/02.molecules/display-tooltip-defined/tests/DisplayTooltipDefined.spec.ts +66 -0
  63. package/app/components/02.molecules/display-tooltip-defined/tests/__snapshots__/DisplayTooltipDefined.spec.ts.snap +17 -0
  64. package/app/components/02.molecules/navigation/deep-expanding-menu/CONSUMER-STYLING.md +44 -0
  65. package/app/components/{deep-expanding-menu → 02.molecules/navigation/deep-expanding-menu}/DeepExpandingMenu.vue +53 -64
  66. package/app/components/02.molecules/navigation/deep-expanding-menu/stories/DeepExpandingMenu.stories.ts +79 -0
  67. package/app/components/02.molecules/navigation/deep-expanding-menu/tests/DeepExpandingMenu.spec.ts +92 -0
  68. package/app/components/02.molecules/navigation/deep-expanding-menu/tests/__snapshots__/DeepExpandingMenu.spec.ts.snap +17 -0
  69. package/app/components/02.molecules/navigation/deep-expanding-menu-classic/CONSUMER-STYLING.md +40 -0
  70. package/app/components/02.molecules/navigation/deep-expanding-menu-classic/DeepExpandingMenuClassic.vue +200 -0
  71. package/app/components/02.molecules/navigation/deep-expanding-menu-classic/stories/DeepExpandingMenuClassic.stories.ts +79 -0
  72. package/app/components/02.molecules/navigation/deep-expanding-menu-classic/tests/DeepExpandingMenuClassic.spec.ts +82 -0
  73. package/app/components/02.molecules/navigation/deep-expanding-menu-classic/tests/__snapshots__/DeepExpandingMenuClassic.spec.ts.snap +18 -0
  74. package/app/types/components/display-tooltip-defined.d.ts +10 -0
  75. package/app/types/components/index.ts +1 -0
  76. package/package.json +1 -1
  77. package/app/components/animated-svg-text/AnimatedSvgText.vue +0 -89
  78. package/app/components/canvas-switcher/CanvasSwitcher.vue +0 -104
  79. package/app/components/clipped-panels/ClippedPanel.vue +0 -87
  80. package/app/components/deep-expanding-menu/DeepExpandingMenuOld.vue +0 -218
  81. package/app/components/display-details/DisplayDetailsCore.vue +0 -122
  82. package/app/components/display-tooltip/DisplayTooltip.vue +0 -170
  83. package/app/components/display-tooltip/DisplayTooltipDefined.vue +0 -103
  84. package/app/components/magnetic-navigation/MagneticNavigation.vue +0 -162
@@ -0,0 +1,105 @@
1
+ import ClippedPanel from "../ClippedPanel.vue";
2
+ import type { Meta, StoryObj } from "@nuxtjs/storybook";
3
+
4
+ const meta: Meta<typeof ClippedPanel> = {
5
+ title: "Atoms/ClippedPanel",
6
+ component: ClippedPanel,
7
+ argTypes: {
8
+ tag: {
9
+ control: { type: "select" },
10
+ options: ["div", "p", "span", "section", "article", "aside", "header", "footer", "main", "nav", "ul", "ol"],
11
+ description: "Root element tag",
12
+ table: { category: "Basic" },
13
+ },
14
+ variant: {
15
+ control: { type: "select" },
16
+ options: ["square", "rectangle", "circle-cutout"],
17
+ description: "Which fixed-size notched shape to clip the panel to",
18
+ table: { category: "Basic" },
19
+ },
20
+ styleClassPassthrough: {
21
+ table: { disable: true },
22
+ },
23
+ },
24
+ parameters: {
25
+ docs: {
26
+ description: {
27
+ component:
28
+ "A panel clipped to a fixed-size notched shape via `clip-path: path(...)`. Each variant is a specific pixel-size shape (see CONSUMER-STYLING.md) — colour and outline are the only overridable surface.",
29
+ },
30
+ },
31
+ },
32
+ };
33
+
34
+ export default meta;
35
+ type Story = StoryObj<typeof ClippedPanel>;
36
+
37
+ export const Square: Story = {
38
+ args: {
39
+ tag: "div",
40
+ variant: "square",
41
+ },
42
+ render: (args) => ({
43
+ components: { ClippedPanel },
44
+ setup() {
45
+ return { args };
46
+ },
47
+ template: `<ClippedPanel v-bind="args">Square</ClippedPanel>`,
48
+ }),
49
+ };
50
+
51
+ export const Rectangle: Story = {
52
+ args: {
53
+ tag: "div",
54
+ variant: "rectangle",
55
+ },
56
+ render: (args) => ({
57
+ components: { ClippedPanel },
58
+ setup() {
59
+ return { args };
60
+ },
61
+ template: `<ClippedPanel v-bind="args">Rectangle</ClippedPanel>`,
62
+ }),
63
+ };
64
+
65
+ export const CircleCutout: Story = {
66
+ args: {
67
+ tag: "div",
68
+ variant: "circle-cutout",
69
+ },
70
+ render: (args) => ({
71
+ components: { ClippedPanel },
72
+ setup() {
73
+ return { args };
74
+ },
75
+ template: `<ClippedPanel v-bind="args">Circle cutout</ClippedPanel>`,
76
+ }),
77
+ };
78
+
79
+ export const CustomColours: Story = {
80
+ args: {
81
+ tag: "div",
82
+ variant: "square",
83
+ },
84
+ render: (args) => ({
85
+ components: { ClippedPanel },
86
+ setup() {
87
+ return { args };
88
+ },
89
+ template: `
90
+ <ClippedPanel
91
+ v-bind="args"
92
+ style="--clipped-panel-background-colour: #fef3e7; --clipped-panel-outline-colour: #b5651d; --clipped-panel-outline-width: 2px;"
93
+ >
94
+ Custom colours
95
+ </ClippedPanel>
96
+ `,
97
+ }),
98
+ parameters: {
99
+ docs: {
100
+ description: {
101
+ story: "Overriding the public tokens via inline `--clipped-panel-*` custom properties.",
102
+ },
103
+ },
104
+ },
105
+ };
@@ -0,0 +1,67 @@
1
+ import { describe, it, expect } from "vitest";
2
+ import { mountSuspended } from "@nuxt/test-utils/runtime";
3
+ import ClippedPanel from "../ClippedPanel.vue";
4
+
5
+ describe("ClippedPanel", () => {
6
+ it("mounts without error", async () => {
7
+ const wrapper = await mountSuspended(ClippedPanel);
8
+ expect(wrapper.vm).toBeTruthy();
9
+ });
10
+
11
+ it("renders correct HTML structure (default props)", async () => {
12
+ const wrapper = await mountSuspended(ClippedPanel);
13
+ expect(wrapper.html()).toMatchSnapshot();
14
+ });
15
+
16
+ it("renders a <div> by default", async () => {
17
+ const wrapper = await mountSuspended(ClippedPanel);
18
+ expect(wrapper.element.tagName).toBe("DIV");
19
+ });
20
+
21
+ it("renders the given tag", async () => {
22
+ const wrapper = await mountSuspended(ClippedPanel, { props: { tag: "section" } });
23
+ expect(wrapper.element.tagName).toBe("SECTION");
24
+ });
25
+
26
+ it("defaults to the square variant class", async () => {
27
+ const wrapper = await mountSuspended(ClippedPanel);
28
+ expect(wrapper.classes()).toContain("clipped-panel");
29
+ expect(wrapper.classes()).toContain("square");
30
+ });
31
+
32
+ it("applies the rectangle variant class", async () => {
33
+ const wrapper = await mountSuspended(ClippedPanel, { props: { variant: "rectangle" } });
34
+ expect(wrapper.classes()).toContain("rectangle");
35
+ });
36
+
37
+ it("applies the circle-cutout variant class", async () => {
38
+ const wrapper = await mountSuspended(ClippedPanel, { props: { variant: "circle-cutout" } });
39
+ expect(wrapper.classes()).toContain("circle-cutout");
40
+ });
41
+
42
+ it("renders slot content", async () => {
43
+ const wrapper = await mountSuspended(ClippedPanel, {
44
+ slots: { default: "<p class='slot-child'>Hello</p>" },
45
+ });
46
+ expect(wrapper.find(".slot-child").exists()).toBe(true);
47
+ expect(wrapper.find(".slot-child").text()).toBe("Hello");
48
+ });
49
+
50
+ it("applies styleClassPassthrough classes", async () => {
51
+ const wrapper = await mountSuspended(ClippedPanel, {
52
+ props: { styleClassPassthrough: ["custom-class"] },
53
+ });
54
+ expect(wrapper.classes()).toContain("custom-class");
55
+ });
56
+
57
+ it("resets classes when styleClassPassthrough prop changes", async () => {
58
+ const wrapper = await mountSuspended(ClippedPanel, {
59
+ props: { styleClassPassthrough: ["initial-class"] },
60
+ });
61
+ expect(wrapper.classes()).toContain("initial-class");
62
+
63
+ await wrapper.setProps({ styleClassPassthrough: ["updated-class"] });
64
+ expect(wrapper.classes()).not.toContain("initial-class");
65
+ expect(wrapper.classes()).toContain("updated-class");
66
+ });
67
+ });
@@ -0,0 +1,3 @@
1
+ // Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
2
+
3
+ exports[`ClippedPanel > renders correct HTML structure (default props) 1`] = `"<div class="clipped-panel square"></div>"`;
@@ -0,0 +1,35 @@
1
+ # DisplayBanner — Consumer Styling Guide
2
+
3
+ ## Public token API
4
+
5
+ | Token | Default | Controls |
6
+ |---|---|---|
7
+ | `--display-banner-min-height` | `auto` | Minimum height of the banner |
8
+
9
+ ```css
10
+ .my-page {
11
+ --display-banner-min-height: 40rem;
12
+ }
13
+ ```
14
+
15
+ Or scope to a single instance via `styleClassPassthrough`:
16
+
17
+ ```vue
18
+ <DisplayBanner style-class-passthrough="hero-banner">...</DisplayBanner>
19
+ ```
20
+
21
+ ```css
22
+ .hero-banner {
23
+ --display-banner-min-height: 40rem;
24
+ }
25
+ ```
26
+
27
+ ## Layout
28
+
29
+ `DisplayBanner` stacks its `canvas` and `content` slots on top of each other in a single grid
30
+ area (`grid-template-areas: "banner"`). Whichever slot is rendered later in the DOM sits visually
31
+ on top — put background media (an image, video, `CanvasSwitcher`-style visual) in `canvas` and
32
+ overlaid text/CTAs in `content`, since `content` follows `canvas` in the template.
33
+
34
+ Both slots are conditionally rendered (`v-if="$slots.canvas"` / `v-if="$slots.content"`), so an
35
+ unused slot contributes no empty wrapper `<div>` to the DOM.
@@ -10,41 +10,24 @@
10
10
  </template>
11
11
 
12
12
  <script setup lang="ts">
13
- const props = defineProps({
14
- tag: {
15
- type: String,
16
- default: "div",
17
- validator(value: string) {
18
- return [
19
- "div",
20
- "p",
21
- "span",
22
- "section",
23
- "article",
24
- "aside",
25
- "header",
26
- "footer",
27
- "main",
28
- "nav",
29
- "ul",
30
- "ol",
31
- ].includes(value)
32
- },
33
- },
34
- styleClassPassthrough: {
35
- type: [String, Array] as PropType<string | string[]>,
36
- default: () => [],
37
- },
38
- })
13
+ interface Props {
14
+ tag?: "div" | "p" | "span" | "section" | "article" | "aside" | "header" | "footer" | "main" | "nav" | "ul" | "ol";
15
+ styleClassPassthrough?: string | string[];
16
+ }
17
+
18
+ const props = withDefaults(defineProps<Props>(), {
19
+ tag: "div",
20
+ styleClassPassthrough: () => [],
21
+ });
39
22
 
40
- const { elementClasses, resetElementClasses } = useStyleClassPassthrough(props.styleClassPassthrough)
23
+ const { elementClasses, resetElementClasses } = useStyleClassPassthrough(props.styleClassPassthrough);
41
24
 
42
25
  watch(
43
26
  () => props.styleClassPassthrough,
44
27
  () => {
45
- resetElementClasses(props.styleClassPassthrough)
28
+ resetElementClasses(props.styleClassPassthrough);
46
29
  }
47
- )
30
+ );
48
31
  </script>
49
32
 
50
33
  <style lang="css">
@@ -53,16 +36,11 @@ watch(
53
36
  display: grid;
54
37
  grid-template-areas: "banner";
55
38
  container-type: inline-size;
39
+ min-height: var(--display-banner-min-height, auto);
56
40
  overflow: hidden;
57
41
 
58
42
  .canvas {
59
43
  grid-area: banner;
60
-
61
- .image {
62
- object-fit: cover;
63
- width: 100%;
64
- height: 100%;
65
- }
66
44
  }
67
45
 
68
46
  .content {
@@ -0,0 +1,100 @@
1
+ import DisplayBanner from "../DisplayBanner.vue";
2
+ import type { Meta, StoryObj } from "@nuxtjs/storybook";
3
+
4
+ const meta: Meta<typeof DisplayBanner> = {
5
+ title: "Atoms/DisplayBanner",
6
+ component: DisplayBanner,
7
+ argTypes: {
8
+ tag: {
9
+ control: { type: "select" },
10
+ options: ["div", "p", "span", "section", "article", "aside", "header", "footer", "main", "nav", "ul", "ol"],
11
+ description: "Root element tag",
12
+ table: { category: "Basic" },
13
+ },
14
+ styleClassPassthrough: {
15
+ table: { disable: true },
16
+ },
17
+ },
18
+ parameters: {
19
+ docs: {
20
+ description: {
21
+ component:
22
+ "Stacks a `canvas` slot (background media) and a `content` slot (overlaid text/CTAs) in a single grid area. Either slot may be omitted.",
23
+ },
24
+ },
25
+ },
26
+ };
27
+
28
+ export default meta;
29
+ type Story = StoryObj<typeof DisplayBanner>;
30
+
31
+ export const Default: Story = {
32
+ args: {
33
+ tag: "div",
34
+ },
35
+ render: (args) => ({
36
+ components: { DisplayBanner },
37
+ setup() {
38
+ return { args };
39
+ },
40
+ template: `
41
+ <DisplayBanner v-bind="args" style="--display-banner-min-height: 24rem;">
42
+ <template #canvas>
43
+ <div style="width: 100%; height: 100%; background: linear-gradient(135deg, #5b8def, #8f5be9);"></div>
44
+ </template>
45
+ <template #content>
46
+ <div style="display: grid; place-items: center; height: 100%; color: white;">
47
+ <h2 style="margin: 0;">Banner content</h2>
48
+ </div>
49
+ </template>
50
+ </DisplayBanner>
51
+ `,
52
+ }),
53
+ };
54
+
55
+ export const CanvasOnly: Story = {
56
+ args: {
57
+ tag: "div",
58
+ },
59
+ render: (args) => ({
60
+ components: { DisplayBanner },
61
+ setup() {
62
+ return { args };
63
+ },
64
+ template: `
65
+ <DisplayBanner v-bind="args" style="--display-banner-min-height: 16rem;">
66
+ <template #canvas>
67
+ <div style="width: 100%; height: 100%; background: linear-gradient(135deg, #5b8def, #8f5be9);"></div>
68
+ </template>
69
+ </DisplayBanner>
70
+ `,
71
+ }),
72
+ parameters: {
73
+ docs: {
74
+ description: {
75
+ story: "With no `content` slot, no empty `.content` wrapper is rendered.",
76
+ },
77
+ },
78
+ },
79
+ };
80
+
81
+ export const ContentOnly: Story = {
82
+ args: {
83
+ tag: "div",
84
+ },
85
+ render: (args) => ({
86
+ components: { DisplayBanner },
87
+ setup() {
88
+ return { args };
89
+ },
90
+ template: `
91
+ <DisplayBanner v-bind="args" style="--display-banner-min-height: 16rem; background: #1a1a1a;">
92
+ <template #content>
93
+ <div style="display: grid; place-items: center; height: 100%; color: white;">
94
+ <h2 style="margin: 0;">Content, no canvas</h2>
95
+ </div>
96
+ </template>
97
+ </DisplayBanner>
98
+ `,
99
+ }),
100
+ };
@@ -0,0 +1,78 @@
1
+ import { describe, it, expect } from "vitest";
2
+ import { mountSuspended } from "@nuxt/test-utils/runtime";
3
+ import DisplayBanner from "../DisplayBanner.vue";
4
+
5
+ describe("DisplayBanner", () => {
6
+ it("mounts without error", async () => {
7
+ const wrapper = await mountSuspended(DisplayBanner);
8
+ expect(wrapper.vm).toBeTruthy();
9
+ });
10
+
11
+ it("renders correct HTML structure (default props)", async () => {
12
+ const wrapper = await mountSuspended(DisplayBanner);
13
+ expect(wrapper.html()).toMatchSnapshot();
14
+ });
15
+
16
+ it("renders a <div> by default", async () => {
17
+ const wrapper = await mountSuspended(DisplayBanner);
18
+ expect(wrapper.element.tagName).toBe("DIV");
19
+ });
20
+
21
+ it("renders the given tag", async () => {
22
+ const wrapper = await mountSuspended(DisplayBanner, { props: { tag: "section" } });
23
+ expect(wrapper.element.tagName).toBe("SECTION");
24
+ });
25
+
26
+ it("does not render a .canvas wrapper when the canvas slot is empty", async () => {
27
+ const wrapper = await mountSuspended(DisplayBanner);
28
+ expect(wrapper.find(".canvas").exists()).toBe(false);
29
+ });
30
+
31
+ it("does not render a .content wrapper when the content slot is empty", async () => {
32
+ const wrapper = await mountSuspended(DisplayBanner);
33
+ expect(wrapper.find(".content").exists()).toBe(false);
34
+ });
35
+
36
+ it("renders the canvas slot", async () => {
37
+ const wrapper = await mountSuspended(DisplayBanner, {
38
+ slots: { canvas: "<img class='slot-canvas' />" },
39
+ });
40
+ expect(wrapper.find(".canvas .slot-canvas").exists()).toBe(true);
41
+ });
42
+
43
+ it("renders the content slot", async () => {
44
+ const wrapper = await mountSuspended(DisplayBanner, {
45
+ slots: { content: "<p class='slot-content'>Hello</p>" },
46
+ });
47
+ expect(wrapper.find(".content .slot-content").text()).toBe("Hello");
48
+ });
49
+
50
+ it("renders both slots together", async () => {
51
+ const wrapper = await mountSuspended(DisplayBanner, {
52
+ slots: {
53
+ canvas: "<img class='slot-canvas' />",
54
+ content: "<p class='slot-content'>Hello</p>",
55
+ },
56
+ });
57
+ expect(wrapper.find(".canvas .slot-canvas").exists()).toBe(true);
58
+ expect(wrapper.find(".content .slot-content").exists()).toBe(true);
59
+ });
60
+
61
+ it("applies styleClassPassthrough classes", async () => {
62
+ const wrapper = await mountSuspended(DisplayBanner, {
63
+ props: { styleClassPassthrough: ["custom-class"] },
64
+ });
65
+ expect(wrapper.classes()).toContain("custom-class");
66
+ });
67
+
68
+ it("resets classes when styleClassPassthrough prop changes", async () => {
69
+ const wrapper = await mountSuspended(DisplayBanner, {
70
+ props: { styleClassPassthrough: ["initial-class"] },
71
+ });
72
+ expect(wrapper.classes()).toContain("initial-class");
73
+
74
+ await wrapper.setProps({ styleClassPassthrough: ["updated-class"] });
75
+ expect(wrapper.classes()).not.toContain("initial-class");
76
+ expect(wrapper.classes()).toContain("updated-class");
77
+ });
78
+ });
@@ -0,0 +1,8 @@
1
+ // Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
2
+
3
+ exports[`DisplayBanner > renders correct HTML structure (default props) 1`] = `
4
+ "<div class="display-banner">
5
+ <!--v-if-->
6
+ <!--v-if-->
7
+ </div>"
8
+ `;
@@ -0,0 +1,62 @@
1
+ # DisplayTooltip — Consumer Styling Guide
2
+
3
+ ## Public token API
4
+
5
+ | Token | Default | Controls |
6
+ |---|---|---|
7
+ | `--display-tooltip-padding-block` | `0.8rem` | Vertical padding around the trigger row |
8
+ | `--display-tooltip-trigger-gap` | `0.8rem` | Space between `triggerContent` slot and the trigger button |
9
+ | `--display-tooltip-trigger-icon-box-size` | `2rem` | Trigger icon's box width/height |
10
+ | `--display-tooltip-trigger-icon-font-size` | `1.8rem` | Trigger icon glyph size |
11
+ | `--display-tooltip-trigger-icon-colour` | `var(--theme-text)` | Trigger icon colour |
12
+ | `--display-tooltip-trigger-outline-width` | `0.1rem` | Trigger button focus/hover outline width |
13
+ | `--display-tooltip-trigger-outline-colour-hover` | `var(--theme-ring)` | Trigger button outline colour on hover/focus-visible |
14
+ | `--display-tooltip-popover-width` | `30rem` | Popover panel width |
15
+ | `--display-tooltip-popover-padding` | `1.2rem` | Popover panel content padding |
16
+ | `--display-tooltip-popover-content-gap` | `1.2rem` | Gap between stacked children in the `tooltipContent` slot |
17
+ | `--display-tooltip-popover-outline-width` | `0.1rem` | Popover panel outline (border) width |
18
+ | `--display-tooltip-popover-outline-colour` | `light-dark(var(--slate-02), var(--slate-06))` | Popover panel outline (border) colour |
19
+ | `--display-tooltip-popover-text-colour` | `light-dark(var(--slate-09), var(--slate-01))` | Popover panel text colour |
20
+ | `--display-tooltip-popover-background-colour` | `light-dark(var(--slate-00), var(--slate-07))` | Popover panel background |
21
+ | `--display-tooltip-popover-border-radius` | `0.8rem` | Popover panel corner radius |
22
+ | `--display-tooltip-popover-shadow` | `light-dark(0 0.4rem 1.6rem rgba(0,0,0,.12), 0 0.4rem 1.6rem rgba(0,0,0,.5))` | Popover panel elevation shadow |
23
+ | `--display-tooltip-popover-offset` | `0.1rem` | Gap between the trigger and the popover panel |
24
+ | `--display-tooltip-close-button-colour` | `light-dark(var(--slate-09), var(--slate-01))` | Close button text colour (used by `DisplayTooltipDefined`) |
25
+ | `--display-tooltip-close-button-border-width` | `0.1rem` | Close button border width |
26
+ | `--display-tooltip-close-button-border-colour` | `light-dark(var(--slate-03), var(--slate-06))` | Close button border colour |
27
+ | `--display-tooltip-close-button-outline-width` | `0.1rem` | Close button outline width |
28
+ | `--display-tooltip-close-button-outline-colour` | `transparent` | Close button outline colour at rest (no permanent ring) |
29
+ | `--display-tooltip-close-button-border-colour-hover` | `light-dark(var(--slate-06), var(--slate-03))` | Close button border colour on hover/focus |
30
+ | `--display-tooltip-close-button-outline-colour-hover` | `light-dark(var(--slate-06), var(--slate-03))` | Close button outline colour on hover/focus |
31
+ | `--display-tooltip-close-button-padding` | `0.4rem 1rem` | Close button padding |
32
+
33
+ ```css
34
+ .my-page {
35
+ --display-tooltip-popover-background-colour: #1a1a1a;
36
+ --display-tooltip-popover-text-colour: white;
37
+ --display-tooltip-popover-outline-colour: transparent;
38
+ }
39
+ ```
40
+
41
+ Or scope to a single instance via `styleClassPassthrough`:
42
+
43
+ ```vue
44
+ <DisplayTooltip style-class-passthrough="promo-tooltip">...</DisplayTooltip>
45
+ ```
46
+
47
+ ## Notes
48
+
49
+ - Built on the native Popover API (`popover`/`popovertarget`) and CSS anchor-positioning
50
+ (`anchor-name`/`position-anchor`/`anchor()`) — check current browser support before relying on
51
+ it where broad support is a hard requirement.
52
+ - The `.display-tooltip-close-button` styled here is rendered by `DisplayTooltipDefined`, not this
53
+ component directly — `DisplayTooltip` only provides the `tooltipContent` slot, so a plain
54
+ `DisplayTooltip` usage supplies its own close affordance if it wants one (give it
55
+ `style="align-self: flex-end;"` or a class to right-align it within the content's flex column,
56
+ same as `DisplayTooltipDefined` does internally).
57
+ - 2026-09-07: default colours switched from raw `light-dark(black, white)` to the library's
58
+ neutral `--slate-*` scale, and `.display-tooltip-popover-content` gained default `padding`,
59
+ `display: flex; flex-direction: column;`, and `gap` (previously unstyled, meaning content sat
60
+ flush against the panel edges) plus the popover gained a default elevation `box-shadow`
61
+ (previously none) — the out-of-the-box look was flat and under-styled before this. All of these
62
+ remain overridable via the tokens above.