srcdev-nuxt-components 9.4.4 → 9.4.6

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 (116) hide show
  1. package/.claude/commands/migrate-component.md +106 -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/breadcrumb.md +1 -0
  11. package/.claude/skills/components/canvas-switcher.md +61 -0
  12. package/.claude/skills/components/clip-element.md +67 -0
  13. package/.claude/skills/components/clipped-panel.md +64 -0
  14. package/.claude/skills/components/content-docs.md +2 -0
  15. package/.claude/skills/components/deep-expanding-menu-classic.md +94 -0
  16. package/.claude/skills/components/deep-expanding-menu.md +92 -0
  17. package/.claude/skills/components/display-banner.md +60 -0
  18. package/.claude/skills/components/display-tooltip-defined.md +139 -0
  19. package/.claude/skills/components/display-tooltip.md +85 -0
  20. package/.claude/skills/components/marquee-scroller.md +130 -0
  21. package/.claude/skills/components/navigation-items.md +1 -0
  22. package/.claude/skills/components/responsive-header.md +3 -0
  23. package/.claude/skills/components/site-header.md +4 -0
  24. package/.claude/skills/components/site-navigation.md +1 -0
  25. package/.claude/skills/components/tab-navigation.md +1 -0
  26. package/.claude/skills/composable-tooltips-guide.md +10 -0
  27. package/.claude/skills/index.md +8 -1
  28. package/.claude/skills/storybook-add-story.md +30 -0
  29. package/.vscode/srcdev-component-animated-svg-text.code-snippets +15 -0
  30. package/.vscode/srcdev-component-breadcrumb.code-snippets +13 -0
  31. package/.vscode/srcdev-component-canvas-switcher.code-snippets +9 -0
  32. package/.vscode/srcdev-component-clip-element.code-snippets +20 -0
  33. package/.vscode/srcdev-component-clipped-panel.code-snippets +23 -0
  34. package/.vscode/srcdev-component-content-docs.code-snippets +18 -0
  35. package/.vscode/srcdev-component-deep-expanding-menu-classic.code-snippets +9 -0
  36. package/.vscode/srcdev-component-deep-expanding-menu.code-snippets +9 -0
  37. package/.vscode/srcdev-component-display-banner.code-snippets +30 -0
  38. package/.vscode/srcdev-component-display-tooltip-defined.code-snippets +29 -0
  39. package/.vscode/srcdev-component-display-tooltip.code-snippets +38 -0
  40. package/.vscode/srcdev-component-marquee-scroller.code-snippets +93 -0
  41. package/.vscode/srcdev-component-responsive-header.code-snippets +12 -0
  42. package/.vscode/srcdev-component-site-header.code-snippets +17 -0
  43. package/.vscode/srcdev-component-site-navigation.code-snippets +30 -0
  44. package/.vscode/srcdev-component-slider-gallery.code-snippets +38 -0
  45. package/.vscode/srcdev-component-tab-navigation.code-snippets +30 -0
  46. package/app/components/01.atoms/animations/animated-svg-text/AnimatedSvgText.vue +58 -0
  47. package/app/components/01.atoms/animations/animated-svg-text/CONSUMER-STYLING.md +34 -0
  48. package/app/components/01.atoms/animations/animated-svg-text/stories/AnimatedSvgText.stories.ts +69 -0
  49. package/app/components/01.atoms/animations/animated-svg-text/tests/AnimatedSvgText.spec.ts +63 -0
  50. package/app/components/01.atoms/animations/animated-svg-text/tests/__snapshots__/AnimatedSvgText.spec.ts.snap +7 -0
  51. package/app/components/01.atoms/animations/clip-element/CONSUMER-STYLING.md +27 -0
  52. package/app/components/{clip-element → 01.atoms/animations/clip-element}/ClipElement.vue +16 -11
  53. package/app/components/01.atoms/animations/clip-element/stories/ClipElement.stories.ts +133 -0
  54. package/app/components/01.atoms/animations/clip-element/tests/ClipElement.spec.ts +70 -0
  55. package/app/components/01.atoms/animations/clip-element/tests/__snapshots__/ClipElement.spec.ts.snap +7 -0
  56. package/app/components/01.atoms/animations/marquee-scroller/CONSUMER-STYLING.md +94 -0
  57. package/app/components/01.atoms/animations/marquee-scroller/MarqueeScroller.vue +331 -0
  58. package/app/components/01.atoms/animations/marquee-scroller/stories/MarqueeScroller.stories.ts +151 -0
  59. package/app/components/01.atoms/animations/marquee-scroller/tests/MarqueeScroller.spec.ts +315 -0
  60. package/app/components/01.atoms/animations/marquee-scroller/tests/__snapshots__/MarqueeScroller.spec.ts.snap +28 -0
  61. package/app/components/01.atoms/canvas-switcher/CONSUMER-STYLING.md +27 -0
  62. package/app/components/01.atoms/canvas-switcher/CanvasSwitcher.vue +111 -0
  63. package/app/components/01.atoms/canvas-switcher/stories/CanvasSwitcher.stories.ts +75 -0
  64. package/app/components/01.atoms/canvas-switcher/tests/CanvasSwitcher.spec.ts +67 -0
  65. package/app/components/01.atoms/clipped-panel/CONSUMER-STYLING.md +39 -0
  66. package/app/components/01.atoms/clipped-panel/ClippedPanel.vue +68 -0
  67. package/app/components/01.atoms/clipped-panel/stories/ClippedPanel.stories.ts +105 -0
  68. package/app/components/01.atoms/clipped-panel/tests/ClippedPanel.spec.ts +67 -0
  69. package/app/components/01.atoms/clipped-panel/tests/__snapshots__/ClippedPanel.spec.ts.snap +3 -0
  70. package/app/components/01.atoms/content-wrappers/docs-pages/ContentDocs.vue +8 -2
  71. package/app/components/01.atoms/display-banner/CONSUMER-STYLING.md +35 -0
  72. package/app/components/{display-banner → 01.atoms/display-banner}/DisplayBanner.vue +13 -35
  73. package/app/components/01.atoms/display-banner/stories/DisplayBanner.stories.ts +100 -0
  74. package/app/components/01.atoms/display-banner/tests/DisplayBanner.spec.ts +78 -0
  75. package/app/components/01.atoms/display-banner/tests/__snapshots__/DisplayBanner.spec.ts.snap +8 -0
  76. package/app/components/01.atoms/display-tooltip/CONSUMER-STYLING.md +62 -0
  77. package/app/components/01.atoms/display-tooltip/DisplayTooltip.vue +188 -0
  78. package/app/components/01.atoms/display-tooltip/stories/DisplayTooltip.stories.ts +94 -0
  79. package/app/components/01.atoms/display-tooltip/tests/DisplayTooltip.spec.ts +75 -0
  80. package/app/components/01.atoms/display-tooltip/tests/__snapshots__/DisplayTooltip.spec.ts.snap +12 -0
  81. package/app/components/01.atoms/navigation/breadcrumb/Breadcrumb.vue +4 -1
  82. package/app/components/02.molecules/display-tooltip-defined/CONSUMER-STYLING.md +42 -0
  83. package/app/components/02.molecules/display-tooltip-defined/DisplayTooltipDefined.vue +116 -0
  84. package/app/components/02.molecules/display-tooltip-defined/stories/DisplayTooltipDefined.stories.ts +126 -0
  85. package/app/components/02.molecules/display-tooltip-defined/tests/DisplayTooltipDefined.spec.ts +66 -0
  86. package/app/components/02.molecules/display-tooltip-defined/tests/__snapshots__/DisplayTooltipDefined.spec.ts.snap +17 -0
  87. package/app/components/02.molecules/navigation/deep-expanding-menu/CONSUMER-STYLING.md +44 -0
  88. package/app/components/{deep-expanding-menu → 02.molecules/navigation/deep-expanding-menu}/DeepExpandingMenu.vue +53 -64
  89. package/app/components/02.molecules/navigation/deep-expanding-menu/stories/DeepExpandingMenu.stories.ts +79 -0
  90. package/app/components/02.molecules/navigation/deep-expanding-menu/tests/DeepExpandingMenu.spec.ts +92 -0
  91. package/app/components/02.molecules/navigation/deep-expanding-menu/tests/__snapshots__/DeepExpandingMenu.spec.ts.snap +17 -0
  92. package/app/components/02.molecules/navigation/deep-expanding-menu-classic/CONSUMER-STYLING.md +40 -0
  93. package/app/components/02.molecules/navigation/deep-expanding-menu-classic/DeepExpandingMenuClassic.vue +200 -0
  94. package/app/components/02.molecules/navigation/deep-expanding-menu-classic/stories/DeepExpandingMenuClassic.stories.ts +79 -0
  95. package/app/components/02.molecules/navigation/deep-expanding-menu-classic/tests/DeepExpandingMenuClassic.spec.ts +82 -0
  96. package/app/components/02.molecules/navigation/deep-expanding-menu-classic/tests/__snapshots__/DeepExpandingMenuClassic.spec.ts.snap +18 -0
  97. package/app/components/02.molecules/navigation/site-navigation/SiteNavigation.vue +4 -1
  98. package/app/components/02.molecules/navigation/tab-navigation/TabNavigation.vue +4 -1
  99. package/app/components/03.organisms/image-galleries/slider-gallery/SliderGallery.vue +16 -4
  100. package/app/components/03.organisms/responsive-header/NavigationItems.vue +4 -1
  101. package/app/components/03.organisms/responsive-header/ResponsiveHeader.vue +16 -3
  102. package/app/components/03.organisms/site-header/SiteHeader.vue +16 -1
  103. package/app/components/03.organisms/site-header/tests/__snapshots__/SiteHeader.spec.ts.snap +1 -1
  104. package/app/types/components/display-tooltip-defined.d.ts +10 -0
  105. package/app/types/components/index.ts +2 -0
  106. package/app/types/components/marquee-scroller.d.ts +10 -0
  107. package/package.json +1 -1
  108. package/app/components/animated-svg-text/AnimatedSvgText.vue +0 -89
  109. package/app/components/canvas-switcher/CanvasSwitcher.vue +0 -104
  110. package/app/components/clipped-panels/ClippedPanel.vue +0 -87
  111. package/app/components/deep-expanding-menu/DeepExpandingMenuOld.vue +0 -218
  112. package/app/components/display-details/DisplayDetailsCore.vue +0 -122
  113. package/app/components/display-tooltip/DisplayTooltip.vue +0 -170
  114. package/app/components/display-tooltip/DisplayTooltipDefined.vue +0 -103
  115. package/app/components/magnetic-navigation/MagneticNavigation.vue +0 -162
  116. package/app/components/marquee-scroller/MarqueeScroller.vue +0 -289
@@ -0,0 +1,188 @@
1
+ <template>
2
+ <div class="display-tooltip-core" :class="[elementClasses]" :style="{ '--_anchor-name': tooltipAnchorName }">
3
+ <div class="display-tooltip-trigger-wrapper body-md">
4
+ <slot v-if="$slots.triggerContent" name="triggerContent"></slot>
5
+ <button
6
+ :popovertarget="tooltipId"
7
+ popovertargetaction="toggle"
8
+ class="display-tooltip-trigger-button"
9
+ :class="{ hide: hideTrigger }"
10
+ :aria-label="triggerAriaLabel"
11
+ >
12
+ <Icon name="fa7-solid:circle-question" class="display-tooltip-trigger-icon" aria-hidden="true" />
13
+ </button>
14
+ </div>
15
+ <div :id="tooltipId" popover class="display-tooltip-popover">
16
+ <div class="display-tooltip-popover-content">
17
+ <slot name="tooltipContent"></slot>
18
+ </div>
19
+ </div>
20
+ </div>
21
+ </template>
22
+
23
+ <script setup lang="ts">
24
+ interface Props {
25
+ tooltipId?: string;
26
+ hideTrigger?: boolean;
27
+ /** aria-label on the trigger button — override for localisation. */
28
+ triggerAriaLabel?: string;
29
+ styleClassPassthrough?: string | string[];
30
+ }
31
+
32
+ const props = withDefaults(defineProps<Props>(), {
33
+ tooltipId: "",
34
+ hideTrigger: false,
35
+ triggerAriaLabel: "Toggle the popover",
36
+ styleClassPassthrough: () => [],
37
+ });
38
+
39
+ const tooltipId = computed(() => (props.tooltipId.length ? props.tooltipId : `nuxt-tooltip-${useId()}`));
40
+ const tooltipAnchorName = `--tooltip-anchor-${useId()}`;
41
+
42
+ const { elementClasses, resetElementClasses } = useStyleClassPassthrough(props.styleClassPassthrough);
43
+
44
+ watch(
45
+ () => props.styleClassPassthrough,
46
+ () => {
47
+ resetElementClasses(props.styleClassPassthrough);
48
+ }
49
+ );
50
+ </script>
51
+
52
+ <style lang="css">
53
+ @layer components {
54
+ .display-tooltip-core {
55
+ padding-block: var(--display-tooltip-padding-block, 0.8rem);
56
+ position: relative;
57
+
58
+ .display-tooltip-trigger-wrapper {
59
+ display: inline-flex;
60
+ align-items: flex-start;
61
+
62
+ .display-tooltip-trigger-button {
63
+ margin-left: var(--display-tooltip-trigger-gap, 0.8rem);
64
+
65
+ &.hide {
66
+ width: 0;
67
+ overflow: hidden;
68
+ opacity: 0;
69
+ }
70
+
71
+ .display-tooltip-trigger-icon {
72
+ display: inline-flex;
73
+ align-items: center;
74
+ justify-content: center;
75
+ width: var(--display-tooltip-trigger-icon-box-size, 2rem);
76
+ height: var(--display-tooltip-trigger-icon-box-size, 2rem);
77
+ color: var(--display-tooltip-trigger-icon-colour, var(--theme-text));
78
+ font-size: var(--display-tooltip-trigger-icon-font-size, 1.8rem);
79
+ line-height: var(--display-tooltip-trigger-icon-box-size, 2rem);
80
+ }
81
+ }
82
+ }
83
+
84
+ .display-tooltip-trigger-button {
85
+ all: unset;
86
+ aspect-ratio: 1 / 1;
87
+ width: 2.4rem;
88
+ display: grid;
89
+ place-items: center;
90
+
91
+ outline: var(--display-tooltip-trigger-outline-width, 0.1rem) solid transparent;
92
+ anchor-name: var(--_anchor-name);
93
+
94
+ @supports (corner-shape: squircle) {
95
+ corner-shape: squircle;
96
+ border-radius: 50%;
97
+ }
98
+
99
+ &:hover {
100
+ cursor: pointer;
101
+ text-decoration: underline;
102
+ }
103
+
104
+ &:hover,
105
+ &:focus-visible {
106
+ outline-color: var(--display-tooltip-trigger-outline-colour-hover, var(--theme-ring));
107
+ outline-offset: 0.2rem;
108
+ }
109
+ }
110
+
111
+ .display-tooltip-popover {
112
+ display: none;
113
+ position: absolute;
114
+ border: none;
115
+ width: var(--display-tooltip-popover-width, 30rem);
116
+
117
+ outline: var(--display-tooltip-popover-outline-width, 0.1rem) solid
118
+ var(--display-tooltip-popover-outline-colour, light-dark(var(--slate-02), var(--slate-06)));
119
+ color: var(--display-tooltip-popover-text-colour, light-dark(var(--slate-09), var(--slate-01)));
120
+ background-color: var(--display-tooltip-popover-background-colour, light-dark(var(--slate-00), var(--slate-07)));
121
+ border-radius: var(--display-tooltip-popover-border-radius, 0.8rem);
122
+ box-shadow: var(
123
+ --display-tooltip-popover-shadow,
124
+ light-dark(0 0.4rem 1.6rem rgba(0, 0, 0, 0.12), 0 0.4rem 1.6rem rgba(0, 0, 0, 0.5))
125
+ );
126
+
127
+ position-anchor: var(--_anchor-name);
128
+ margin: 0;
129
+ inset: auto;
130
+ top: calc(anchor(top) + 0rem);
131
+ left: calc(anchor(right) + var(--display-tooltip-popover-offset, 0.1rem));
132
+ opacity: 0;
133
+ transition:
134
+ opacity 200ms,
135
+ display 200ms,
136
+ overlay 200ms;
137
+
138
+ transition-behavior: allow-discrete;
139
+ position-try-fallbacks: flip-inline;
140
+
141
+ .display-tooltip-popover-content {
142
+ padding: var(--display-tooltip-popover-padding, 1.2rem);
143
+ display: flex;
144
+ flex-direction: column;
145
+ gap: var(--display-tooltip-popover-content-gap, 1.2rem);
146
+
147
+ .display-tooltip-close-button {
148
+ all: unset;
149
+ cursor: pointer;
150
+ color: var(--display-tooltip-close-button-colour, light-dark(var(--slate-09), var(--slate-01)));
151
+ border: var(--display-tooltip-close-button-border-width, 0.1rem) solid
152
+ var(--display-tooltip-close-button-border-colour, light-dark(var(--slate-03), var(--slate-06)));
153
+ outline: var(--display-tooltip-close-button-outline-width, 0.1rem) solid transparent;
154
+
155
+ font-weight: 600;
156
+ padding: var(--display-tooltip-close-button-padding, 0.4rem 1rem);
157
+
158
+ &:hover,
159
+ &:focus {
160
+ text-decoration: underline;
161
+ border-color: var(
162
+ --display-tooltip-close-button-border-colour-hover,
163
+ light-dark(var(--slate-06), var(--slate-03))
164
+ );
165
+ outline-color: var(
166
+ --display-tooltip-close-button-outline-colour-hover,
167
+ light-dark(var(--slate-06), var(--slate-03))
168
+ );
169
+ outline-offset: 0.2rem;
170
+ }
171
+ }
172
+ }
173
+
174
+ &:popover-open {
175
+ display: flex;
176
+ opacity: 1;
177
+
178
+ flex-direction: column;
179
+
180
+ @starting-style {
181
+ display: flex;
182
+ opacity: 0;
183
+ }
184
+ }
185
+ }
186
+ }
187
+ }
188
+ </style>
@@ -0,0 +1,94 @@
1
+ import DisplayTooltip from "../DisplayTooltip.vue";
2
+ import type { Meta, StoryObj } from "@nuxtjs/storybook";
3
+
4
+ const meta: Meta<typeof DisplayTooltip> = {
5
+ title: "Atoms/DisplayTooltip",
6
+ component: DisplayTooltip,
7
+ argTypes: {
8
+ tooltipId: {
9
+ control: "text",
10
+ description: "Id used to link the trigger button to the popover. Auto-generated when omitted.",
11
+ table: { category: "Basic" },
12
+ },
13
+ hideTrigger: {
14
+ control: "boolean",
15
+ description: "Visually hides the trigger button (kept in the DOM) — e.g. while an equivalent trigger is provided via the triggerContent slot.",
16
+ table: { category: "Basic" },
17
+ },
18
+ styleClassPassthrough: { table: { disable: true } },
19
+ },
20
+ args: {
21
+ hideTrigger: false,
22
+ },
23
+ parameters: {
24
+ docs: {
25
+ description: {
26
+ component:
27
+ "A trigger button that opens an anchor-positioned popover panel, built on the native Popover API and CSS anchor-positioning. See DisplayTooltipDefined for a structured title/body/action content variant.",
28
+ },
29
+ },
30
+ },
31
+ };
32
+
33
+ export default meta;
34
+ type Story = StoryObj<typeof DisplayTooltip>;
35
+
36
+ export const Default: Story = {
37
+ render: (args) => ({
38
+ components: { DisplayTooltip },
39
+ setup() {
40
+ return { args };
41
+ },
42
+ template: `
43
+ <div style="padding: 8rem;">
44
+ <DisplayTooltip v-bind="args">
45
+ <template #tooltipContent>
46
+ <p>This is some helpful additional information.</p>
47
+ </template>
48
+ </DisplayTooltip>
49
+ </div>
50
+ `,
51
+ }),
52
+ };
53
+
54
+ export const WithTriggerContent: Story = {
55
+ render: (args) => ({
56
+ components: { DisplayTooltip },
57
+ setup() {
58
+ return { args };
59
+ },
60
+ template: `
61
+ <div style="padding: 8rem;">
62
+ <DisplayTooltip v-bind="args">
63
+ <template #triggerContent>
64
+ <span>Delivery cost</span>
65
+ </template>
66
+ <template #tooltipContent>
67
+ <p>Free delivery on orders over £50.</p>
68
+ </template>
69
+ </DisplayTooltip>
70
+ </div>
71
+ `,
72
+ }),
73
+ };
74
+
75
+ export const CustomColours: Story = {
76
+ render: (args) => ({
77
+ components: { DisplayTooltip },
78
+ setup() {
79
+ return { args };
80
+ },
81
+ template: `
82
+ <div style="padding: 8rem;">
83
+ <DisplayTooltip
84
+ v-bind="args"
85
+ style="--display-tooltip-popover-background-colour: #1a1a1a; --display-tooltip-popover-text-colour: white; --display-tooltip-popover-outline-colour: transparent;"
86
+ >
87
+ <template #tooltipContent>
88
+ <p>Overriding the public tokens via inline custom properties.</p>
89
+ </template>
90
+ </DisplayTooltip>
91
+ </div>
92
+ `,
93
+ }),
94
+ };
@@ -0,0 +1,75 @@
1
+ import { describe, it, expect } from "vitest";
2
+ import { mountSuspended } from "@nuxt/test-utils/runtime";
3
+ import DisplayTooltip from "../DisplayTooltip.vue";
4
+
5
+ describe("DisplayTooltip", () => {
6
+ it("mounts without error", async () => {
7
+ const wrapper = await mountSuspended(DisplayTooltip);
8
+ expect(wrapper.vm).toBeTruthy();
9
+ });
10
+
11
+ it("renders correct HTML structure", async () => {
12
+ const wrapper = await mountSuspended(DisplayTooltip, { props: { tooltipId: "fixed-id" } });
13
+ expect(wrapper.html()).toMatchSnapshot();
14
+ });
15
+
16
+ it("generates a tooltip id when none is provided", async () => {
17
+ const wrapper = await mountSuspended(DisplayTooltip);
18
+ const button = wrapper.find(".display-tooltip-trigger-button");
19
+ expect(button.attributes("popovertarget")).toMatch(/^nuxt-tooltip-/);
20
+ });
21
+
22
+ it("uses the given tooltipId", async () => {
23
+ const wrapper = await mountSuspended(DisplayTooltip, { props: { tooltipId: "my-tooltip" } });
24
+ const button = wrapper.find(".display-tooltip-trigger-button");
25
+ expect(button.attributes("popovertarget")).toBe("my-tooltip");
26
+ expect(wrapper.find(".display-tooltip-popover").attributes("id")).toBe("my-tooltip");
27
+ });
28
+
29
+ it("does not render triggerContent slot content when unused", async () => {
30
+ const wrapper = await mountSuspended(DisplayTooltip);
31
+ expect(wrapper.find(".slot-trigger").exists()).toBe(false);
32
+ });
33
+
34
+ it("renders the triggerContent slot", async () => {
35
+ const wrapper = await mountSuspended(DisplayTooltip, {
36
+ slots: { triggerContent: "<span class='slot-trigger'>Label</span>" },
37
+ });
38
+ expect(wrapper.find(".slot-trigger").text()).toBe("Label");
39
+ });
40
+
41
+ it("renders the tooltipContent slot", async () => {
42
+ const wrapper = await mountSuspended(DisplayTooltip, {
43
+ slots: { tooltipContent: "<p class='slot-content'>Helpful info</p>" },
44
+ });
45
+ expect(wrapper.find(".display-tooltip-popover .slot-content").text()).toBe("Helpful info");
46
+ });
47
+
48
+ it("applies the hide class to the trigger button when hideTrigger is true", async () => {
49
+ const wrapper = await mountSuspended(DisplayTooltip, { props: { hideTrigger: true } });
50
+ expect(wrapper.find(".display-tooltip-trigger-button").classes()).toContain("hide");
51
+ });
52
+
53
+ it("does not apply the hide class by default", async () => {
54
+ const wrapper = await mountSuspended(DisplayTooltip);
55
+ expect(wrapper.find(".display-tooltip-trigger-button").classes()).not.toContain("hide");
56
+ });
57
+
58
+ it("applies styleClassPassthrough classes", async () => {
59
+ const wrapper = await mountSuspended(DisplayTooltip, {
60
+ props: { styleClassPassthrough: ["custom-class"] },
61
+ });
62
+ expect(wrapper.classes()).toContain("custom-class");
63
+ });
64
+
65
+ it("resets classes when styleClassPassthrough prop changes", async () => {
66
+ const wrapper = await mountSuspended(DisplayTooltip, {
67
+ props: { styleClassPassthrough: ["initial-class"] },
68
+ });
69
+ expect(wrapper.classes()).toContain("initial-class");
70
+
71
+ await wrapper.setProps({ styleClassPassthrough: ["updated-class"] });
72
+ expect(wrapper.classes()).not.toContain("initial-class");
73
+ expect(wrapper.classes()).toContain("updated-class");
74
+ });
75
+ });
@@ -0,0 +1,12 @@
1
+ // Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
2
+
3
+ exports[`DisplayTooltip > renders correct HTML structure 1`] = `
4
+ "<div class="display-tooltip-core" style="--_anchor-name: --tooltip-anchor-v-0-0;">
5
+ <div class="display-tooltip-trigger-wrapper body-md">
6
+ <!--v-if--><button popovertarget="fixed-id" popovertargetaction="toggle" class="display-tooltip-trigger-button" aria-label="Toggle the popover"><span class="iconify i-fa7-solid:circle-question display-tooltip-trigger-icon" aria-hidden="true"></span></button>
7
+ </div>
8
+ <div id="fixed-id" popover="" class="display-tooltip-popover">
9
+ <div class="display-tooltip-popover-content"></div>
10
+ </div>
11
+ </div>"
12
+ `;
@@ -1,5 +1,5 @@
1
1
  <template>
2
- <nav class="breadcrumb" :class="[elementClasses]" aria-label="Breadcrumb">
2
+ <nav class="breadcrumb" :class="[elementClasses]" :aria-label="ariaLabel">
3
3
  <ol class="breadcrumb__list">
4
4
  <li v-for="(item, index) in items" :key="`${item.label}-${index}`" class="breadcrumb__item">
5
5
  <NuxtLink v-if="item.to" :to="item.to" class="breadcrumb__link">{{ item.label }}</NuxtLink>
@@ -18,11 +18,14 @@ import type { BreadcrumbItem } from "~/types/components/breadcrumb";
18
18
  interface Props {
19
19
  items: BreadcrumbItem[];
20
20
  separator?: string;
21
+ /** aria-label on the nav landmark — override for localisation. */
22
+ ariaLabel?: string;
21
23
  styleClassPassthrough?: string | string[];
22
24
  }
23
25
 
24
26
  const props = withDefaults(defineProps<Props>(), {
25
27
  separator: "/",
28
+ ariaLabel: "Breadcrumb",
26
29
  styleClassPassthrough: () => [],
27
30
  });
28
31
 
@@ -0,0 +1,42 @@
1
+ # DisplayTooltipDefined — Consumer Styling Guide
2
+
3
+ ## Public token API
4
+
5
+ | Token | Default | Controls |
6
+ |---|---|---|
7
+ | `--display-tooltip-defined-content-gap` | `1.2rem` | Gap between title/body/action/close-button |
8
+ | `--display-tooltip-defined-title-colour` | `inherit` | `tooltipTitle` text colour |
9
+ | `--display-tooltip-defined-title-margin-block` | `0 0` | `tooltipTitle` block margin |
10
+ | `--display-tooltip-defined-title-font-size` | `1.6rem` | `tooltipTitle` font size |
11
+ | `--display-tooltip-defined-title-font-weight` | `700` | `tooltipTitle` font weight |
12
+ | `--display-tooltip-defined-title-line-height` | `120%` | `tooltipTitle` line height |
13
+ | `--display-tooltip-defined-title-letter-spacing` | `normal` | `tooltipTitle` letter spacing |
14
+ | `--display-tooltip-defined-body-colour` | `inherit` | `tooltipContent` text colour |
15
+ | `--display-tooltip-defined-body-margin-block` | `0 0` | `tooltipContent` block margin |
16
+ | `--display-tooltip-defined-body-font-size` | `1.3rem` | `tooltipContent` font size |
17
+ | `--display-tooltip-defined-body-font-weight` | `inherit` | `tooltipContent` font weight |
18
+ | `--display-tooltip-defined-body-line-height` | `140%` | `tooltipContent` line height |
19
+ | `--display-tooltip-defined-body-letter-spacing` | `normal` | `tooltipContent` letter spacing |
20
+ | `--display-tooltip-defined-action-colour` | `inherit` | `tooltipAction` text colour |
21
+ | `--display-tooltip-defined-action-margin-block` | `0 0` | `tooltipAction` block margin |
22
+ | `--display-tooltip-defined-action-font-size` | `1.4rem` | `tooltipAction` font size |
23
+ | `--display-tooltip-defined-action-font-weight` | `700` | `tooltipAction` font weight |
24
+ | `--display-tooltip-defined-action-line-height` | `130%` | `tooltipAction` line height |
25
+ | `--display-tooltip-defined-action-letter-spacing` | `normal` | `tooltipAction` letter spacing |
26
+ | `--display-tooltip-defined-close-button-margin-block` | `1.2rem 0` | Space above the close button |
27
+
28
+ The three `*-colour` tokens default to `inherit`, which resolves to the underlying
29
+ `DisplayTooltip` popover's own `--display-tooltip-popover-text-colour` (see its
30
+ CONSUMER-STYLING.md) — override there for a uniform colour change, or override one of these three
31
+ for a single field.
32
+
33
+ ```css
34
+ .my-page {
35
+ --display-tooltip-defined-title-colour: var(--brand-accent);
36
+ --display-tooltip-defined-title-font-size: 1.8rem;
37
+ }
38
+ ```
39
+
40
+ This component also renders (and can override) every `--display-tooltip-*` token from the
41
+ underlying `DisplayTooltip` — see its CONSUMER-STYLING.md for the full popover/trigger/close-button
42
+ token API.
@@ -0,0 +1,116 @@
1
+ <template>
2
+ <DisplayTooltip
3
+ :tooltip-id="tooltipId"
4
+ :trigger-aria-label="triggerAriaLabel"
5
+ :style-class-passthrough="styleClassPassthrough"
6
+ >
7
+ <template v-if="$slots.triggerContent" #triggerContent>
8
+ <slot name="triggerContent"></slot>
9
+ </template>
10
+ <template #tooltipContent>
11
+ <div class="popover-content-defined">
12
+ <component
13
+ :is="contentText.tooltipTitle?.tag"
14
+ v-if="contentText.tooltipTitle"
15
+ class="tooltip-title subtitle-sm"
16
+ >
17
+ {{ contentText.tooltipTitle.text }}
18
+ </component>
19
+ <component :is="contentText.tooltipContent?.tag" v-if="contentText.tooltipContent" class="tooltip-body body-sm">
20
+ {{ contentText.tooltipContent.text }}
21
+ </component>
22
+ <component
23
+ :is="contentText.tooltipAction?.tag"
24
+ v-if="contentText.tooltipAction"
25
+ class="tooltip-action input-value"
26
+ >
27
+ {{ contentText.tooltipAction.text }}
28
+ </component>
29
+ <button
30
+ :popovertarget="tooltipId"
31
+ popovertargetaction="hide"
32
+ class="display-tooltip-close-button"
33
+ :aria-label="closeButtonAriaLabel"
34
+ >
35
+ {{ closeButtonText }}
36
+ </button>
37
+ </div>
38
+ </template>
39
+ </DisplayTooltip>
40
+ </template>
41
+
42
+ <script setup lang="ts">
43
+ import type { TooltipContentText } from "~/types/components";
44
+
45
+ interface Props {
46
+ tooltipId?: string;
47
+ contentText?: TooltipContentText;
48
+ /** aria-label on the trigger button — override for localisation. */
49
+ triggerAriaLabel?: string;
50
+ /** Visible text on the close button — override for localisation. */
51
+ closeButtonText?: string;
52
+ /** aria-label on the close button — override for localisation. */
53
+ closeButtonAriaLabel?: string;
54
+ styleClassPassthrough?: string | string[];
55
+ }
56
+
57
+ const props = withDefaults(defineProps<Props>(), {
58
+ tooltipId: "",
59
+ contentText: () => ({}),
60
+ triggerAriaLabel: "Toggle the popover",
61
+ closeButtonText: "Close",
62
+ closeButtonAriaLabel: "Close tool tip",
63
+ styleClassPassthrough: () => [],
64
+ });
65
+
66
+ const tooltipId = computed(() =>
67
+ props.tooltipId.length ? `nuxt-tooltip-${props.tooltipId}` : `nuxt-tooltip-${useId()}`
68
+ );
69
+ </script>
70
+
71
+ <style lang="css">
72
+ @layer components {
73
+ .display-tooltip-core {
74
+ .popover-content-defined {
75
+ display: flex;
76
+ flex-direction: column;
77
+ gap: var(--display-tooltip-defined-content-gap, 1.2rem);
78
+
79
+ /* margin-block resets below neutralise the UA default margin of whatever tag the consumer
80
+ picks via contentText.*.tag (e.g. h3/h4 carry their own margin-block) — gap is the only
81
+ intended spacing mechanism between these three. */
82
+ .tooltip-title {
83
+ color: var(--display-tooltip-defined-title-colour, inherit);
84
+ margin-block: var(--display-tooltip-defined-title-margin-block, 0 0);
85
+ font-size: var(--display-tooltip-defined-title-font-size, 1.6rem);
86
+ font-weight: var(--display-tooltip-defined-title-font-weight, 700);
87
+ line-height: var(--display-tooltip-defined-title-line-height, 120%);
88
+ letter-spacing: var(--display-tooltip-defined-title-letter-spacing, normal);
89
+ }
90
+
91
+ .tooltip-body {
92
+ color: var(--display-tooltip-defined-body-colour, inherit);
93
+ margin-block: var(--display-tooltip-defined-body-margin-block, 0 0);
94
+ font-size: var(--display-tooltip-defined-body-font-size, 1.3rem);
95
+ font-weight: var(--display-tooltip-defined-body-font-weight, inherit);
96
+ line-height: var(--display-tooltip-defined-body-line-height, 140%);
97
+ letter-spacing: var(--display-tooltip-defined-body-letter-spacing, normal);
98
+ }
99
+
100
+ .tooltip-action {
101
+ color: var(--display-tooltip-defined-action-colour, inherit);
102
+ margin-block: var(--display-tooltip-defined-action-margin-block, 0 0);
103
+ font-size: var(--display-tooltip-defined-action-font-size, 1.4rem);
104
+ font-weight: var(--display-tooltip-defined-action-font-weight, 700);
105
+ line-height: var(--display-tooltip-defined-action-line-height, 130%);
106
+ letter-spacing: var(--display-tooltip-defined-action-letter-spacing, normal);
107
+ }
108
+
109
+ .display-tooltip-close-button {
110
+ align-self: flex-end;
111
+ margin-block: var(--display-tooltip-defined-close-button-margin-block, 1.2rem 0);
112
+ }
113
+ }
114
+ }
115
+ }
116
+ </style>
@@ -0,0 +1,126 @@
1
+ import { ref } from "vue";
2
+ import DisplayTooltipDefined from "../DisplayTooltipDefined.vue";
3
+ import DisplayTooltip from "../../../01.atoms/display-tooltip/DisplayTooltip.vue";
4
+ import { useTooltipsGuide } from "~/composables/useTooltips";
5
+ import type { Meta, StoryObj } from "@nuxtjs/storybook";
6
+ import type { TooltipContentText } from "~/types/components";
7
+
8
+ const contentText: TooltipContentText = {
9
+ tooltipTitle: { tag: "h4", text: "Free delivery" },
10
+ tooltipContent: { tag: "p", text: "Orders over £50 qualify for free standard delivery." },
11
+ tooltipAction: { tag: "span", text: "See delivery policy" },
12
+ };
13
+
14
+ const meta: Meta<typeof DisplayTooltipDefined> = {
15
+ title: "Molecules/DisplayTooltipDefined",
16
+ component: DisplayTooltipDefined,
17
+ argTypes: {
18
+ tooltipId: {
19
+ control: "text",
20
+ description: "Id used to link the trigger button to the popover. Auto-generated when omitted.",
21
+ table: { category: "Basic" },
22
+ },
23
+ contentText: { table: { disable: true } },
24
+ styleClassPassthrough: { table: { disable: true } },
25
+ },
26
+ args: {
27
+ contentText,
28
+ },
29
+ parameters: {
30
+ docs: {
31
+ description: {
32
+ component:
33
+ "Structured title/body/action content variant of DisplayTooltip, plus a built-in close button wired to the popover.",
34
+ },
35
+ },
36
+ },
37
+ };
38
+
39
+ export default meta;
40
+ type Story = StoryObj<typeof DisplayTooltipDefined>;
41
+
42
+ export const Default: Story = {
43
+ render: (args) => ({
44
+ components: { DisplayTooltipDefined },
45
+ setup() {
46
+ return { args };
47
+ },
48
+ template: `<div style="padding: 8rem;"><DisplayTooltipDefined v-bind="args" /></div>`,
49
+ }),
50
+ };
51
+
52
+ export const TitleOnly: Story = {
53
+ args: {
54
+ contentText: { tooltipTitle: { tag: "h4", text: "Just a title" } },
55
+ },
56
+ render: (args) => ({
57
+ components: { DisplayTooltipDefined },
58
+ setup() {
59
+ return { args };
60
+ },
61
+ template: `<div style="padding: 8rem;"><DisplayTooltipDefined v-bind="args" /></div>`,
62
+ }),
63
+ };
64
+
65
+ export const GuidedTour: Story = {
66
+ parameters: {
67
+ docs: {
68
+ description: {
69
+ story:
70
+ "Recreates the deleted `app/pages/ui/tooltips.vue` demo: `useTooltipsGuide` walks a container's `[popover]` elements in DOM order, opening each and waiting for its close button before advancing — the autoplay-through-steps pattern seen on some guide intros. Mixes a `DisplayTooltipDefined` step with a plain `DisplayTooltip` step (the guide works with either, since it only needs native `[popover]`/`popovertarget` markup). Guide auto-starts 800ms after mount in this story; use \"Run guide again\" to replay it.",
71
+ },
72
+ },
73
+ },
74
+ render: () => ({
75
+ components: { DisplayTooltipDefined, DisplayTooltip },
76
+ setup() {
77
+ const guideContainer = ref<HTMLElement | null>(null);
78
+ const { isGuideRunning, restartGuide, currentTooltipIndex, totalPopovers } = useTooltipsGuide(guideContainer, {
79
+ autoStart: true,
80
+ startDelay: 800,
81
+ });
82
+
83
+ return { guideContainer, isGuideRunning, restartGuide, currentTooltipIndex, totalPopovers };
84
+ },
85
+ template: `
86
+ <div style="padding: 4rem; display: grid; gap: 2rem; max-width: 48rem;">
87
+ <div style="display: flex; align-items: center; gap: 1.2rem;">
88
+ <button @click="restartGuide" :disabled="isGuideRunning" style="padding: 0.6rem 1.2rem;">
89
+ {{ isGuideRunning ? 'Guide running…' : 'Run guide again' }}
90
+ </button>
91
+ <span v-if="isGuideRunning" style="font-size: 1.3rem; color: #666;">
92
+ Step {{ currentTooltipIndex + 1 }} of {{ totalPopovers }} — click each popover's Close button to advance
93
+ </span>
94
+ </div>
95
+
96
+ <div ref="guideContainer" style="display: grid; gap: 2rem;">
97
+ <DisplayTooltipDefined
98
+ :content-text="{
99
+ tooltipTitle: { tag: 'h3', text: 'Step 1' },
100
+ tooltipContent: { tag: 'p', text: 'This step uses DisplayTooltipDefined — structured title/body/action content plus a built-in close button.' },
101
+ tooltipAction: { tag: 'p', text: 'Step 1 of 2' },
102
+ }"
103
+ >
104
+ <template #triggerContent>Trigger from DisplayTooltipDefined</template>
105
+ </DisplayTooltipDefined>
106
+
107
+ <DisplayTooltip tooltip-id="guided-tour-step-2">
108
+ <template #triggerContent>Trigger from plain DisplayTooltip</template>
109
+ <template #tooltipContent>
110
+ <p style="margin: 0;">This step uses plain DisplayTooltip — you supply the close button yourself via the tooltipContent slot.</p>
111
+ <button
112
+ popovertarget="guided-tour-step-2"
113
+ popovertargetaction="hide"
114
+ class="display-tooltip-close-button"
115
+ style="align-self: flex-end;"
116
+ aria-label="Close tool tip"
117
+ >
118
+ Close
119
+ </button>
120
+ </template>
121
+ </DisplayTooltip>
122
+ </div>
123
+ </div>
124
+ `,
125
+ }),
126
+ };