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,200 @@
1
+ <template>
2
+ <component :is="tag" class="deep-expanding-menu-classic" :class="[elementClasses]">
3
+ <div class="inner">
4
+ <template v-for="(link, key) in navLinks" :key="key">
5
+ <NuxtLink v-if="link.path" :to="link.path" class="navigation-link">{{ link.name }}</NuxtLink>
6
+ <details v-else ref="navigationGroupRef" class="navigation-group" name="navigation-group">
7
+ <summary class="navigation-group-toggle">
8
+ <span>{{ link.name }}</span>
9
+ <Icon name="bi:caret-down-fill" class="icon" />
10
+ </summary>
11
+ <div class="navigation-group-panel">
12
+ <h4 class="page-heading-4 mb-6">{{ link.childLinksTitle }}</h4>
13
+ <ul class="navigation-group-list">
14
+ <li v-for="childLink in link.childLinks" :key="childLink.name" class="navigation-group-item">
15
+ <NuxtLink :to="childLink.path" class="navigation-group-link">{{ childLink.name }}</NuxtLink>
16
+ </li>
17
+ </ul>
18
+ </div>
19
+ </details>
20
+ </template>
21
+ </div>
22
+ </component>
23
+ </template>
24
+
25
+ <script setup lang="ts">
26
+ import { onClickOutside } from "@vueuse/core";
27
+ import type { ResponsiveHeaderNavItem } from "~/types/components";
28
+
29
+ interface Props {
30
+ tag?: "div" | "section" | "nav" | "ul" | "ol";
31
+ navLinks?: ResponsiveHeaderNavItem[];
32
+ styleClassPassthrough?: string | string[];
33
+ }
34
+
35
+ const props = withDefaults(defineProps<Props>(), {
36
+ tag: "nav",
37
+ navLinks: () => [],
38
+ styleClassPassthrough: () => [],
39
+ });
40
+
41
+ const { elementClasses, resetElementClasses } = useStyleClassPassthrough(props.styleClassPassthrough);
42
+
43
+ const navigationGroupRef = useTemplateRef<HTMLElement[]>("navigationGroupRef");
44
+
45
+ watch(
46
+ () => props.styleClassPassthrough,
47
+ () => {
48
+ resetElementClasses(props.styleClassPassthrough);
49
+ }
50
+ );
51
+
52
+ onMounted(() => {
53
+ navigationGroupRef.value?.forEach((element, index) => {
54
+ onClickOutside(element, () => {
55
+ navigationGroupRef.value?.[index]?.removeAttribute("open");
56
+ });
57
+ });
58
+ });
59
+ </script>
60
+
61
+ <style lang="css">
62
+ @layer components {
63
+ @layer popover-setup {
64
+ .deep-expanding-menu-classic {
65
+ container-type: inline-size;
66
+ display: grid;
67
+ grid-template-areas: "element-stack";
68
+ align-items: center;
69
+ gap: var(--deep-expanding-menu-classic-gap, 1.2rem);
70
+
71
+ .inner {
72
+ grid-area: element-stack;
73
+ display: flex;
74
+ gap: var(--deep-expanding-menu-classic-item-gap, 2.4rem);
75
+ align-items: center;
76
+ z-index: 1;
77
+
78
+ .navigation-link,
79
+ .navigation-group-toggle {
80
+ all: unset;
81
+ border-bottom: var(--deep-expanding-menu-classic-link-border-width, 0.2rem) solid transparent;
82
+ padding-block: var(--deep-expanding-menu-classic-link-padding-block, 0.8rem);
83
+
84
+ transition: border-color 200ms;
85
+
86
+ &:hover,
87
+ &:focus,
88
+ &:focus-visible {
89
+ cursor: pointer;
90
+ border-color: var(
91
+ --deep-expanding-menu-classic-link-border-colour-hover,
92
+ light-dark(var(--blue-10), var(--slate-00))
93
+ );
94
+ }
95
+ }
96
+
97
+ .navigation-group {
98
+ --_icon-transform: scaleY(1);
99
+
100
+ display: grid;
101
+ grid-template-areas: "details-stack";
102
+ z-index: 1;
103
+ position: relative;
104
+
105
+ summary::-webkit-details-marker,
106
+ summary::marker {
107
+ display: none;
108
+ }
109
+
110
+ &[open] {
111
+ --_icon-transform: scaleY(-1);
112
+ }
113
+
114
+ .navigation-group-toggle {
115
+ grid-area: details-stack;
116
+
117
+ display: flex !important;
118
+ align-items: center;
119
+ gap: 1.2rem;
120
+ list-style: none;
121
+
122
+ .icon {
123
+ display: block;
124
+ font-size: var(--deep-expanding-menu-classic-icon-size, 1.2rem);
125
+
126
+ transform: var(--_icon-transform);
127
+ transition: transform 200ms;
128
+ }
129
+ }
130
+
131
+ .navigation-group-panel {
132
+ display: grid;
133
+ grid-area: details-stack;
134
+ z-index: 2;
135
+ position: absolute;
136
+ inset: auto;
137
+ top: var(--deep-expanding-menu-classic-panel-offset-top, 4rem);
138
+ left: 0rem;
139
+ gap: var(--deep-expanding-menu-classic-panel-list-gap, 1.2rem);
140
+
141
+ width: var(--deep-expanding-menu-classic-panel-width, 20rem);
142
+
143
+ @media screen and (min-width: 768px) {
144
+ width: var(--deep-expanding-menu-classic-panel-width-tablet, 40rem);
145
+ }
146
+
147
+ @media screen and (min-width: 1024px) {
148
+ width: var(--deep-expanding-menu-classic-panel-width-desktop, 60rem);
149
+ }
150
+
151
+ background-color: var(--deep-expanding-menu-classic-panel-background-colour, white);
152
+ border: var(--deep-expanding-menu-classic-panel-border-width, 0.1rem) solid
153
+ var(--deep-expanding-menu-classic-panel-border-colour, black);
154
+ border-radius: var(--deep-expanding-menu-classic-panel-border-radius, 1.2rem);
155
+ box-shadow: var(--deep-expanding-menu-classic-panel-shadow, 0 0 1rem rgba(0, 0, 0, 0.1));
156
+ padding: var(--deep-expanding-menu-classic-panel-padding, 1.2rem);
157
+ overflow: clip;
158
+
159
+ h4 {
160
+ color: var(--deep-expanding-menu-classic-panel-heading-colour, var(--slate-10));
161
+ }
162
+
163
+ .navigation-group-list {
164
+ display: grid;
165
+ grid-template-columns: repeat(auto-fill, minmax(12rem, 1fr));
166
+ gap: var(--deep-expanding-menu-classic-panel-list-gap, 1.2rem);
167
+ padding-inline-start: 0;
168
+ margin-block-end: 0.8rem;
169
+
170
+ .navigation-group-item {
171
+ display: block;
172
+
173
+ a.navigation-group-link {
174
+ display: inline-block;
175
+ color: var(--deep-expanding-menu-classic-group-link-colour, var(--slate-10));
176
+ text-decoration: none;
177
+ padding-block: 0.8rem;
178
+
179
+ border-bottom: var(--deep-expanding-menu-classic-link-border-width, 0.2rem) solid transparent;
180
+
181
+ transition: border-color 200ms;
182
+
183
+ &:hover,
184
+ &:focus-visible {
185
+ cursor: pointer;
186
+ border-color: var(
187
+ --deep-expanding-menu-classic-group-link-border-colour-hover,
188
+ var(--slate-10)
189
+ );
190
+ }
191
+ }
192
+ }
193
+ }
194
+ }
195
+ }
196
+ }
197
+ }
198
+ }
199
+ }
200
+ </style>
@@ -0,0 +1,79 @@
1
+ import DeepExpandingMenuClassic from "../DeepExpandingMenuClassic.vue";
2
+ import type { Meta, StoryObj } from "@nuxtjs/storybook";
3
+ import type { ResponsiveHeaderNavItem } from "~/types/components";
4
+
5
+ const navLinks: ResponsiveHeaderNavItem[] = [
6
+ { name: "Home", path: "/" },
7
+ {
8
+ name: "Services",
9
+ childLinksTitle: "Our services",
10
+ childLinks: [
11
+ { name: "Haircuts", path: "/services/haircuts" },
12
+ { name: "Colouring", path: "/services/colouring" },
13
+ { name: "Styling", path: "/services/styling" },
14
+ ],
15
+ },
16
+ { name: "Contact", path: "/contact" },
17
+ ];
18
+
19
+ const meta: Meta<typeof DeepExpandingMenuClassic> = {
20
+ title: "Molecules/DeepExpandingMenuClassic",
21
+ component: DeepExpandingMenuClassic,
22
+ argTypes: {
23
+ tag: {
24
+ control: { type: "select" },
25
+ options: ["div", "section", "nav", "ul", "ol"],
26
+ description: "Root element tag",
27
+ table: { category: "Basic" },
28
+ },
29
+ navLinks: { table: { disable: true } },
30
+ styleClassPassthrough: { table: { disable: true } },
31
+ },
32
+ args: {
33
+ tag: "nav",
34
+ navLinks,
35
+ },
36
+ parameters: {
37
+ docs: {
38
+ description: {
39
+ component:
40
+ "`<details>`/`<summary>`-based fallback implementation of `DeepExpandingMenu`, using `onClickOutside` to close an open group instead of CSS anchor-positioning/Popover API — use this when the modern implementation's browser support (see its own docs) isn't acceptable.",
41
+ },
42
+ },
43
+ },
44
+ };
45
+
46
+ export default meta;
47
+ type Story = StoryObj<typeof DeepExpandingMenuClassic>;
48
+
49
+ export const Default: Story = {
50
+ render: (args) => ({
51
+ components: { DeepExpandingMenuClassic },
52
+ setup() {
53
+ return { args };
54
+ },
55
+ template: `<DeepExpandingMenuClassic v-bind="args" style="padding: 4rem;" />`,
56
+ }),
57
+ };
58
+
59
+ export const CustomColours: Story = {
60
+ render: (args) => ({
61
+ components: { DeepExpandingMenuClassic },
62
+ setup() {
63
+ return { args };
64
+ },
65
+ template: `
66
+ <DeepExpandingMenuClassic
67
+ v-bind="args"
68
+ style="padding: 4rem; --deep-expanding-menu-classic-panel-background-colour: #1a1a1a; --deep-expanding-menu-classic-panel-border-colour: transparent; --deep-expanding-menu-classic-group-link-colour: white; --deep-expanding-menu-classic-panel-heading-colour: #aaaaaa;"
69
+ />
70
+ `,
71
+ }),
72
+ parameters: {
73
+ docs: {
74
+ description: {
75
+ story: "Overriding the public tokens via inline `--deep-expanding-menu-classic-*` custom properties.",
76
+ },
77
+ },
78
+ },
79
+ };
@@ -0,0 +1,82 @@
1
+ import { describe, it, expect } from "vitest";
2
+ import { mountSuspended } from "@nuxt/test-utils/runtime";
3
+ import DeepExpandingMenuClassic from "../DeepExpandingMenuClassic.vue";
4
+ import type { ResponsiveHeaderNavItem } from "~/types/components";
5
+
6
+ const defaultNavLinks: ResponsiveHeaderNavItem[] = [
7
+ { name: "Home", path: "/" },
8
+ {
9
+ name: "Services",
10
+ childLinksTitle: "Our services",
11
+ childLinks: [
12
+ { name: "Haircuts", path: "/services/haircuts" },
13
+ { name: "Colouring", path: "/services/colouring" },
14
+ ],
15
+ },
16
+ { name: "Contact", path: "/contact" },
17
+ ];
18
+
19
+ describe("DeepExpandingMenuClassic", () => {
20
+ it("mounts without error", async () => {
21
+ const wrapper = await mountSuspended(DeepExpandingMenuClassic, { props: { navLinks: defaultNavLinks } });
22
+ expect(wrapper.vm).toBeTruthy();
23
+ });
24
+
25
+ it("renders correct HTML structure", async () => {
26
+ const wrapper = await mountSuspended(DeepExpandingMenuClassic, { props: { navLinks: defaultNavLinks } });
27
+ expect(wrapper.html()).toMatchSnapshot();
28
+ });
29
+
30
+ it("renders a <nav> by default", async () => {
31
+ const wrapper = await mountSuspended(DeepExpandingMenuClassic, { props: { navLinks: defaultNavLinks } });
32
+ expect(wrapper.element.tagName.toLowerCase()).toBe("nav");
33
+ });
34
+
35
+ it("renders the given tag", async () => {
36
+ const wrapper = await mountSuspended(DeepExpandingMenuClassic, {
37
+ props: { navLinks: defaultNavLinks, tag: "div" },
38
+ });
39
+ expect(wrapper.element.tagName.toLowerCase()).toBe("div");
40
+ });
41
+
42
+ it("renders a direct link for items with a path", async () => {
43
+ const wrapper = await mountSuspended(DeepExpandingMenuClassic, { props: { navLinks: defaultNavLinks } });
44
+ const links = wrapper.findAll(".navigation-link");
45
+ expect(links.map((l) => l.text())).toEqual(["Home", "Contact"]);
46
+ });
47
+
48
+ it("renders a <details> group for items with childLinks", async () => {
49
+ const wrapper = await mountSuspended(DeepExpandingMenuClassic, { props: { navLinks: defaultNavLinks } });
50
+ expect(wrapper.find("details.navigation-group").exists()).toBe(true);
51
+ expect(wrapper.find(".navigation-group-toggle").text()).toContain("Services");
52
+ });
53
+
54
+ it("renders each child link inside the group panel", async () => {
55
+ const wrapper = await mountSuspended(DeepExpandingMenuClassic, { props: { navLinks: defaultNavLinks } });
56
+ const childLinks = wrapper.findAll(".navigation-group-link");
57
+ expect(childLinks.map((l) => l.text())).toEqual(["Haircuts", "Colouring"]);
58
+ });
59
+
60
+ it("renders the childLinksTitle heading", async () => {
61
+ const wrapper = await mountSuspended(DeepExpandingMenuClassic, { props: { navLinks: defaultNavLinks } });
62
+ expect(wrapper.find(".navigation-group-panel h4").text()).toBe("Our services");
63
+ });
64
+
65
+ it("applies styleClassPassthrough classes", async () => {
66
+ const wrapper = await mountSuspended(DeepExpandingMenuClassic, {
67
+ props: { navLinks: defaultNavLinks, styleClassPassthrough: ["custom-class"] },
68
+ });
69
+ expect(wrapper.classes()).toContain("custom-class");
70
+ });
71
+
72
+ it("resets classes when styleClassPassthrough prop changes", async () => {
73
+ const wrapper = await mountSuspended(DeepExpandingMenuClassic, {
74
+ props: { navLinks: defaultNavLinks, styleClassPassthrough: ["initial-class"] },
75
+ });
76
+ expect(wrapper.classes()).toContain("initial-class");
77
+
78
+ await wrapper.setProps({ styleClassPassthrough: ["updated-class"] });
79
+ expect(wrapper.classes()).not.toContain("initial-class");
80
+ expect(wrapper.classes()).toContain("updated-class");
81
+ });
82
+ });
@@ -0,0 +1,18 @@
1
+ // Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
2
+
3
+ exports[`DeepExpandingMenuClassic > renders correct HTML structure 1`] = `
4
+ "<nav class="deep-expanding-menu-classic">
5
+ <div class="inner"><a href="/" class="navigation-link">Home</a>
6
+ <details class="navigation-group" name="navigation-group">
7
+ <summary class="navigation-group-toggle"><span>Services</span><span class="iconify i-bi:caret-down-fill icon" aria-hidden="true"></span></summary>
8
+ <div class="navigation-group-panel">
9
+ <h4 class="page-heading-4 mb-6">Our services</h4>
10
+ <ul class="navigation-group-list">
11
+ <li class="navigation-group-item"><a href="/services/haircuts" class="navigation-group-link">Haircuts</a></li>
12
+ <li class="navigation-group-item"><a href="/services/colouring" class="navigation-group-link">Colouring</a></li>
13
+ </ul>
14
+ </div>
15
+ </details><a href="/contact" class="navigation-link">Contact</a>
16
+ </div>
17
+ </nav>"
18
+ `;
@@ -7,7 +7,7 @@
7
7
  `site-navigation--${navAlign}`,
8
8
  { 'is-collapsed': isCollapsed, 'is-loaded': isLoaded, 'menu-open': isMenuOpen, 'is-animated': isAnimated },
9
9
  ]"
10
- aria-label="Site navigation"
10
+ :aria-label="ariaLabel"
11
11
  >
12
12
  <ul
13
13
  v-if="!isCollapsed || !isLoaded"
@@ -106,11 +106,14 @@ interface Props {
106
106
  navItemData: NavItemData;
107
107
  navAlign?: "left" | "center" | "right";
108
108
  styleClassPassthrough?: string | string[];
109
+ /** aria-label on the nav landmark — override for localisation. */
110
+ ariaLabel?: string;
109
111
  }
110
112
 
111
113
  const props = withDefaults(defineProps<Props>(), {
112
114
  navAlign: "left",
113
115
  styleClassPassthrough: () => [],
116
+ ariaLabel: "Site navigation",
114
117
  });
115
118
 
116
119
  // ─── Animation gate — prevents indicator from transitioning on first paint ───
@@ -7,7 +7,7 @@
7
7
  `tab-navigation--${navAlign}`,
8
8
  { 'is-collapsed': isCollapsed, 'is-loaded': isLoaded, 'menu-open': isMenuOpen, 'is-animated': isAnimated },
9
9
  ]"
10
- aria-label="Site navigation"
10
+ :aria-label="ariaLabel"
11
11
  >
12
12
  <ul v-if="!isCollapsed || !isLoaded" ref="navListRef" class="tab-nav-list" @mouseleave="hoveredItemHref = null">
13
13
  <li
@@ -131,12 +131,15 @@ interface Props {
131
131
  navAlign?: "left" | "center" | "right";
132
132
  styleClassPassthrough?: string | string[];
133
133
  anchorScrollOffset?: number | (() => number);
134
+ /** aria-label on the nav landmark — override for localisation. */
135
+ ariaLabel?: string;
134
136
  }
135
137
 
136
138
  const props = withDefaults(defineProps<Props>(), {
137
139
  navAlign: "left",
138
140
  styleClassPassthrough: () => [],
139
141
  anchorScrollOffset: undefined,
142
+ ariaLabel: "Site navigation",
140
143
  });
141
144
 
142
145
  const { navRef, navListRef, isCollapsed, isLoaded, isMenuOpen, isActiveItem, toggleMenu, closeMenu } =
@@ -2,7 +2,7 @@
2
2
  <div ref="sliderGalleryWrapper" class="slider-gallery" :class="[elementClasses]">
3
3
  <div class="loading-state" :class="[{ galleryLoaded: !galleryLoaded }]">
4
4
  <div class="loading-spinner"></div>
5
- <p>Loading gallery...</p>
5
+ <p>{{ loadingText }}</p>
6
6
  </div>
7
7
 
8
8
  <div v-if="showGallery" class="gallery-content" :class="[{ galleryLoaded: !galleryLoaded }]">
@@ -17,7 +17,7 @@
17
17
  {{ item.description }}
18
18
  </div>
19
19
  <div class="buttons" :class="item.textBrightness">
20
- <button>SEE MORE</button>
20
+ <button>{{ seeMoreText }}</button>
21
21
  </div>
22
22
  </div>
23
23
  </div>
@@ -40,10 +40,10 @@
40
40
  </div>
41
41
 
42
42
  <div class="arrows">
43
- <button id="prev" ref="prevDom" aria-label="Previous image" @click.prevent="doPrevious()">
43
+ <button id="prev" ref="prevDom" :aria-label="prevAriaLabel" @click.prevent="doPrevious()">
44
44
  <Icon name="ic:outline-keyboard-arrow-left" class="arrows-icon" />
45
45
  </button>
46
- <button id="next" ref="nextDom" aria-label="Next image" @click.prevent="doNext()">
46
+ <button id="next" ref="nextDom" :aria-label="nextAriaLabel" @click.prevent="doNext()">
47
47
  <Icon name="ic:outline-keyboard-arrow-right" class="arrows-icon" />
48
48
  </button>
49
49
  </div>
@@ -60,6 +60,14 @@ interface Props {
60
60
  autoRun?: boolean;
61
61
  autoRunInterval?: number;
62
62
  animationDuration?: number;
63
+ /** Loading-state copy — override for localisation. */
64
+ loadingText?: string;
65
+ /** Per-slide call-to-action button copy — override for localisation. */
66
+ seeMoreText?: string;
67
+ /** aria-label on the previous-image button — override for localisation. */
68
+ prevAriaLabel?: string;
69
+ /** aria-label on the next-image button — override for localisation. */
70
+ nextAriaLabel?: string;
63
71
  styleClassPassthrough?: string | string[];
64
72
  }
65
73
 
@@ -67,6 +75,10 @@ const props = withDefaults(defineProps<Props>(), {
67
75
  autoRun: true,
68
76
  autoRunInterval: 7000,
69
77
  animationDuration: 3000,
78
+ loadingText: "Loading gallery...",
79
+ seeMoreText: "SEE MORE",
80
+ prevAriaLabel: "Previous image",
81
+ nextAriaLabel: "Next image",
70
82
  styleClassPassthrough: () => [],
71
83
  });
72
84
 
@@ -3,7 +3,7 @@
3
3
  class="overflow-navigation-wrapper"
4
4
  :class="[elementClasses, { 'is-panel-animating': isPanelAnimating }]"
5
5
  role="menu"
6
- aria-label="Overflow navigation menu"
6
+ :aria-label="ariaLabel"
7
7
  @mouseleave="
8
8
  hoveredItemKey = null;
9
9
  hoveredChildKey = null;
@@ -115,6 +115,8 @@ interface Props {
115
115
  mainNavigationState?: ResponsiveHeaderState;
116
116
  panelVariant?: "modern" | "classic";
117
117
  styleClassPassthrough?: string | string[];
118
+ /** aria-label on the overflow menu — override for localisation. */
119
+ ariaLabel?: string;
118
120
  }
119
121
 
120
122
  const props = withDefaults(defineProps<Props>(), {
@@ -124,6 +126,7 @@ const props = withDefaults(defineProps<Props>(), {
124
126
  // "modern" is opt-in.
125
127
  panelVariant: "classic",
126
128
  styleClassPassthrough: () => [],
129
+ ariaLabel: "Overflow navigation menu",
127
130
  });
128
131
 
129
132
  const panelComponent = computed(() => (props.panelVariant === "modern" ? ExpandingPanel : ExpandingPanelClassic));
@@ -9,7 +9,7 @@
9
9
  ref="mainNav"
10
10
  class="main-navigation"
11
11
  :class="{ 'is-animated': isAnimated }"
12
- aria-label="Main navigation"
12
+ :aria-label="mainNavAriaLabel"
13
13
  @mouseleave="hoveredItemKey = null"
14
14
  @focusout="handleNavFocusout"
15
15
  >
@@ -78,7 +78,7 @@
78
78
  <div aria-hidden="true" class="nav-indicator-hovered"></div>
79
79
  <div aria-hidden="true" class="nav-indicator-active"></div>
80
80
  </nav>
81
- <nav ref="secondaryNav" class="secondary-navigation" aria-label="Secondary navigation">
81
+ <nav ref="secondaryNav" class="secondary-navigation" :aria-label="secondaryNavAriaLabel">
82
82
  <details
83
83
  ref="overflowDetails"
84
84
  class="overflow-details"
@@ -100,7 +100,11 @@
100
100
  />
101
101
  </summary>
102
102
  <div class="overflow-details-nav" role="menu">
103
- <NavigationItems :main-navigation-state="mainNavigationState" :panel-variant="panelVariant" />
103
+ <NavigationItems
104
+ :main-navigation-state="mainNavigationState"
105
+ :panel-variant="panelVariant"
106
+ :aria-label="overflowMenuAriaLabel"
107
+ />
104
108
  </div>
105
109
  </details>
106
110
  <slot v-if="slots.secondaryNavigation" name="secondaryNavigation"></slot>
@@ -126,6 +130,12 @@ interface Props {
126
130
  styleClassPassthrough?: string | string[];
127
131
  allowExpandOnGesture?: boolean;
128
132
  panelVariant?: "modern" | "classic";
133
+ /** aria-label on the primary nav landmark — override for localisation. */
134
+ mainNavAriaLabel?: string;
135
+ /** aria-label on the secondary (overflow) nav landmark — override for localisation. */
136
+ secondaryNavAriaLabel?: string;
137
+ /** aria-label on the overflow menu itself (forwarded to NavigationItems) — override for localisation. */
138
+ overflowMenuAriaLabel?: string;
129
139
  }
130
140
 
131
141
  const props = withDefaults(defineProps<Props>(), {
@@ -142,6 +152,9 @@ const props = withDefaults(defineProps<Props>(), {
142
152
  // Forwarded to NavigationItems' overflow submenu panels — see its own default for why
143
153
  // "classic" is the safe default (CLAUDE.md pitfall #19).
144
154
  panelVariant: "classic",
155
+ mainNavAriaLabel: "Main navigation",
156
+ secondaryNavAriaLabel: "Secondary navigation",
157
+ overflowMenuAriaLabel: "Overflow navigation menu",
145
158
  });
146
159
 
147
160
  const collapseNavigationBelowWidth = computed(
@@ -2,7 +2,7 @@
2
2
  <PageRow tag="div" :variant="pageRowVariant" :style-class-passthrough="styleClassPassthrough">
3
3
  <template #default>
4
4
  <header class="site-header">
5
- <nav class="home-navigation" aria-label="Home Navigation">
5
+ <nav class="home-navigation" :aria-label="homeNavAriaLabel">
6
6
  <SkipLinks>
7
7
  <template #homeLink>
8
8
  <slot name="branding"></slot>
@@ -17,6 +17,9 @@
17
17
  :collapse-at-main-nav-intersection="collapseAtMainNavIntersection"
18
18
  :allow-expand-on-gesture="allowExpandOnGesture"
19
19
  :style-class-passthrough="navStyleClassPassthrough"
20
+ :main-nav-aria-label="mainNavAriaLabel"
21
+ :secondary-nav-aria-label="secondaryNavAriaLabel"
22
+ :overflow-menu-aria-label="overflowMenuAriaLabel"
20
23
  >
21
24
  <template v-if="slots.secondaryNavigation" #secondaryNavigation>
22
25
  <slot name="secondaryNavigation"></slot>
@@ -43,6 +46,14 @@ interface Props {
43
46
  pageRowVariant?: "full" | "popout" | "content" | "inset-content";
44
47
  styleClassPassthrough?: string | string[];
45
48
  navStyleClassPassthrough?: string | string[];
49
+ /** aria-label on the home/branding nav landmark — override for localisation. */
50
+ homeNavAriaLabel?: string;
51
+ /** Forwarded to ResponsiveHeader's mainNavAriaLabel — override for localisation. */
52
+ mainNavAriaLabel?: string;
53
+ /** Forwarded to ResponsiveHeader's secondaryNavAriaLabel — override for localisation. */
54
+ secondaryNavAriaLabel?: string;
55
+ /** Forwarded to ResponsiveHeader's overflowMenuAriaLabel — override for localisation. */
56
+ overflowMenuAriaLabel?: string;
46
57
  }
47
58
 
48
59
  withDefaults(defineProps<Props>(), {
@@ -58,6 +69,10 @@ withDefaults(defineProps<Props>(), {
58
69
  pageRowVariant: "content",
59
70
  styleClassPassthrough: () => [],
60
71
  navStyleClassPassthrough: () => [],
72
+ homeNavAriaLabel: "Home Navigation",
73
+ mainNavAriaLabel: "Main navigation",
74
+ secondaryNavAriaLabel: "Secondary navigation",
75
+ overflowMenuAriaLabel: "Overflow navigation menu",
61
76
  });
62
77
 
63
78
  const slots = useSlots();
@@ -9,7 +9,7 @@ exports[`SiteHeader > renders correct HTML structure with default props 1`] = `
9
9
  <nav class="skip-links-nav" aria-label="Skip navigation"><a href="#main-content" class="skip-link">Skip to main content</a><a href="#footer-content" class="skip-link">Skip to footer</a></nav>
10
10
  </div>
11
11
  </nav>
12
- <div class="responsive-header-stub"></div>
12
+ <div class="responsive-header-stub" main-nav-aria-label="Main navigation" secondary-nav-aria-label="Secondary navigation" overflow-menu-aria-label="Overflow navigation menu"></div>
13
13
  </header>
14
14
  </div>"
15
15
  `;
@@ -0,0 +1,10 @@
1
+ export interface TooltipContentTextItem {
2
+ tag: string
3
+ text: string
4
+ }
5
+
6
+ export interface TooltipContentText {
7
+ tooltipTitle?: TooltipContentTextItem
8
+ tooltipContent?: TooltipContentTextItem
9
+ tooltipAction?: TooltipContentTextItem
10
+ }
@@ -16,3 +16,5 @@ export * from "./social-icons-list.d"
16
16
  export * from "./content-docs.d"
17
17
  export * from "./breadcrumb"
18
18
  export * from "./cookie-consent-banner.d"
19
+ export * from "./display-tooltip-defined.d"
20
+ export * from "./marquee-scroller.d"
@@ -0,0 +1,10 @@
1
+ export interface MarqueeItem {
2
+ id: number
3
+ content: string
4
+ }
5
+
6
+ export interface MarqueeItemConfig {
7
+ width?: string
8
+ height?: string
9
+ gap?: string
10
+ }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "srcdev-nuxt-components",
3
3
  "type": "module",
4
- "version": "9.4.4",
4
+ "version": "9.4.6",
5
5
  "main": "nuxt.config.ts",
6
6
  "types": "types.d.ts",
7
7
  "license": "MIT",