srcdev-nuxt-components 9.4.3 → 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 (91) 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/components/hero-text.md +4 -0
  19. package/.claude/skills/composable-tooltips-guide.md +10 -0
  20. package/.claude/skills/index.md +7 -1
  21. package/.claude/skills/storybook-add-story.md +30 -0
  22. package/.vscode/srcdev-component-animated-svg-text.code-snippets +15 -0
  23. package/.vscode/srcdev-component-canvas-switcher.code-snippets +9 -0
  24. package/.vscode/srcdev-component-clip-element.code-snippets +20 -0
  25. package/.vscode/srcdev-component-clipped-panel.code-snippets +23 -0
  26. package/.vscode/srcdev-component-deep-expanding-menu-classic.code-snippets +9 -0
  27. package/.vscode/srcdev-component-deep-expanding-menu.code-snippets +9 -0
  28. package/.vscode/srcdev-component-display-banner.code-snippets +30 -0
  29. package/.vscode/srcdev-component-display-tooltip-defined.code-snippets +14 -0
  30. package/.vscode/srcdev-component-display-tooltip.code-snippets +27 -0
  31. package/.vscode/srcdev-component-hero-text.code-snippets +16 -0
  32. package/.vscode/srcdev-component-toggle-switch-core.code-snippets +13 -0
  33. package/app/components/01.atoms/animations/animated-svg-text/AnimatedSvgText.vue +58 -0
  34. package/app/components/01.atoms/animations/animated-svg-text/CONSUMER-STYLING.md +34 -0
  35. package/app/components/01.atoms/animations/animated-svg-text/stories/AnimatedSvgText.stories.ts +69 -0
  36. package/app/components/01.atoms/animations/animated-svg-text/tests/AnimatedSvgText.spec.ts +63 -0
  37. package/app/components/01.atoms/animations/animated-svg-text/tests/__snapshots__/AnimatedSvgText.spec.ts.snap +7 -0
  38. package/app/components/01.atoms/animations/clip-element/CONSUMER-STYLING.md +27 -0
  39. package/app/components/{clip-element → 01.atoms/animations/clip-element}/ClipElement.vue +16 -11
  40. package/app/components/01.atoms/animations/clip-element/stories/ClipElement.stories.ts +133 -0
  41. package/app/components/01.atoms/animations/clip-element/tests/ClipElement.spec.ts +70 -0
  42. package/app/components/01.atoms/animations/clip-element/tests/__snapshots__/ClipElement.spec.ts.snap +7 -0
  43. package/app/components/01.atoms/canvas-switcher/CONSUMER-STYLING.md +27 -0
  44. package/app/components/01.atoms/canvas-switcher/CanvasSwitcher.vue +111 -0
  45. package/app/components/01.atoms/canvas-switcher/stories/CanvasSwitcher.stories.ts +75 -0
  46. package/app/components/01.atoms/canvas-switcher/tests/CanvasSwitcher.spec.ts +67 -0
  47. package/app/components/01.atoms/clipped-panel/CONSUMER-STYLING.md +39 -0
  48. package/app/components/01.atoms/clipped-panel/ClippedPanel.vue +68 -0
  49. package/app/components/01.atoms/clipped-panel/stories/ClippedPanel.stories.ts +105 -0
  50. package/app/components/01.atoms/clipped-panel/tests/ClippedPanel.spec.ts +67 -0
  51. package/app/components/01.atoms/clipped-panel/tests/__snapshots__/ClippedPanel.spec.ts.snap +3 -0
  52. package/app/components/01.atoms/display-banner/CONSUMER-STYLING.md +35 -0
  53. package/app/components/{display-banner → 01.atoms/display-banner}/DisplayBanner.vue +13 -35
  54. package/app/components/01.atoms/display-banner/stories/DisplayBanner.stories.ts +100 -0
  55. package/app/components/01.atoms/display-banner/tests/DisplayBanner.spec.ts +78 -0
  56. package/app/components/01.atoms/display-banner/tests/__snapshots__/DisplayBanner.spec.ts.snap +8 -0
  57. package/app/components/01.atoms/display-tooltip/CONSUMER-STYLING.md +62 -0
  58. package/app/components/01.atoms/display-tooltip/DisplayTooltip.vue +185 -0
  59. package/app/components/01.atoms/display-tooltip/stories/DisplayTooltip.stories.ts +94 -0
  60. package/app/components/01.atoms/display-tooltip/tests/DisplayTooltip.spec.ts +75 -0
  61. package/app/components/01.atoms/display-tooltip/tests/__snapshots__/DisplayTooltip.spec.ts.snap +12 -0
  62. package/app/components/01.atoms/text-blocks/hero-text/HeroText.vue +7 -5
  63. package/app/components/02.molecules/display-tooltip-defined/CONSUMER-STYLING.md +42 -0
  64. package/app/components/02.molecules/display-tooltip-defined/DisplayTooltipDefined.vue +103 -0
  65. package/app/components/02.molecules/display-tooltip-defined/stories/DisplayTooltipDefined.stories.ts +126 -0
  66. package/app/components/02.molecules/display-tooltip-defined/tests/DisplayTooltipDefined.spec.ts +66 -0
  67. package/app/components/02.molecules/display-tooltip-defined/tests/__snapshots__/DisplayTooltipDefined.spec.ts.snap +17 -0
  68. package/app/components/02.molecules/navigation/deep-expanding-menu/CONSUMER-STYLING.md +44 -0
  69. package/app/components/{deep-expanding-menu → 02.molecules/navigation/deep-expanding-menu}/DeepExpandingMenu.vue +53 -64
  70. package/app/components/02.molecules/navigation/deep-expanding-menu/stories/DeepExpandingMenu.stories.ts +79 -0
  71. package/app/components/02.molecules/navigation/deep-expanding-menu/tests/DeepExpandingMenu.spec.ts +92 -0
  72. package/app/components/02.molecules/navigation/deep-expanding-menu/tests/__snapshots__/DeepExpandingMenu.spec.ts.snap +17 -0
  73. package/app/components/02.molecules/navigation/deep-expanding-menu-classic/CONSUMER-STYLING.md +40 -0
  74. package/app/components/02.molecules/navigation/deep-expanding-menu-classic/DeepExpandingMenuClassic.vue +200 -0
  75. package/app/components/02.molecules/navigation/deep-expanding-menu-classic/stories/DeepExpandingMenuClassic.stories.ts +79 -0
  76. package/app/components/02.molecules/navigation/deep-expanding-menu-classic/tests/DeepExpandingMenuClassic.spec.ts +82 -0
  77. package/app/components/02.molecules/navigation/deep-expanding-menu-classic/tests/__snapshots__/DeepExpandingMenuClassic.spec.ts.snap +18 -0
  78. package/app/components/05.forms/toggle-switch/CONSUMER-STYLING.md +17 -0
  79. package/app/components/05.forms/toggle-switch/ToggleSwitchCore.vue +18 -1
  80. package/app/components/05.forms/toggle-switch/stories/ToggleSwitchCore.stories.ts +17 -2
  81. package/app/types/components/display-tooltip-defined.d.ts +10 -0
  82. package/app/types/components/index.ts +1 -0
  83. package/package.json +1 -1
  84. package/app/components/animated-svg-text/AnimatedSvgText.vue +0 -89
  85. package/app/components/canvas-switcher/CanvasSwitcher.vue +0 -104
  86. package/app/components/clipped-panels/ClippedPanel.vue +0 -87
  87. package/app/components/deep-expanding-menu/DeepExpandingMenuOld.vue +0 -218
  88. package/app/components/display-details/DisplayDetailsCore.vue +0 -122
  89. package/app/components/display-tooltip/DisplayTooltip.vue +0 -170
  90. package/app/components/display-tooltip/DisplayTooltipDefined.vue +0 -103
  91. package/app/components/magnetic-navigation/MagneticNavigation.vue +0 -162
@@ -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
+ `;
@@ -23,6 +23,23 @@ token-rename pass, not a redesign.
23
23
  Geometry (`--input-toggle-*`, `--form-element-*`) is shared across every `05.forms` component and
24
24
  untouched by this — see `theming-form-geometry-tokens.md`.
25
25
 
26
+ ### Track width
27
+
28
+ `--toggle-switch-track-width` overrides the track's width directly. Left unset, the track derives
29
+ its width from the shared `--input-toggle-*`/`--form-element-*` geometry tokens so it always scales
30
+ with `--input-toggle-symbol-size` — resize the thumb and the track resizes to match, no separate
31
+ width to keep in sync:
32
+
33
+ ```css
34
+ .my-compact-toggle {
35
+ --input-toggle-symbol-size: 2.2rem;
36
+ /* Track width now follows automatically — no --toggle-switch-track-width override needed. */
37
+ }
38
+ ```
39
+
40
+ Only set `--toggle-switch-track-width` if you need a width the formula doesn't produce (e.g. a
41
+ fixed width independent of symbol size).
42
+
26
43
  > Five private locals (`--_icon-on-opacity`, `--_icon-off-opacity`, `--_symbol-background-color`,
27
44
  > `--_symbol-margin-inline-start`, `--_symbol-checked-offset`) were removed during this migration —
28
45
  > declared but never actually read by any property in the component, confirmed by grep. Not part
@@ -132,6 +132,23 @@ const toggleSwitchValue = () => {
132
132
 
133
133
  .symbol-wrapper {
134
134
  /* Geometry */
135
+
136
+ /* Track width has no direct public token — it's derived from the same geometry tokens
137
+ that size the thumb, so the track always scales with --input-toggle-symbol-size instead
138
+ of clipping/floating it. Breakdown at the default 0.1rem tokens (= 72px total):
139
+ 2×symbol-size (6rem) + 2×form-element-border-width (0.2rem, wrapper border) +
140
+ 2×wrapper-padding (0.2rem) + 3×symbol-outline-width (0.3rem) +
141
+ 3×symbol-border-width (0.3rem) + 2×symbol-margin (0.2rem). Override
142
+ --toggle-switch-track-width directly if you need a value the formula doesn't produce. */
143
+ --_track-width: var(
144
+ --toggle-switch-track-width,
145
+ calc(
146
+ (2 * var(--input-toggle-symbol-size)) + (2 * var(--form-element-border-width)) +
147
+ (2 * var(--input-toggle-wrapper-padding)) + (3 * var(--input-toggle-symbol-outline-width)) +
148
+ (3 * var(--input-toggle-symbol-border-width)) + (2 * var(--input-toggle-symbol-margin))
149
+ )
150
+ );
151
+
135
152
  display: inline-flex;
136
153
  align-items: center;
137
154
  justify-content: start;
@@ -148,7 +165,7 @@ const toggleSwitchValue = () => {
148
165
  border: var(--form-element-border-width) solid var(--_border);
149
166
  outline: var(--form-element-outline-width) solid transparent;
150
167
  border-radius: 100vw;
151
- width: 72px;
168
+ width: var(--_track-width);
152
169
  padding: calc(var(--input-toggle-wrapper-padding) + var(--input-toggle-symbol-outline-width));
153
170
 
154
171
  &:hover {
@@ -1,5 +1,5 @@
1
1
  import type { Meta, StoryFn } from "@nuxtjs/storybook";
2
- import { ref } from "vue";
2
+ import { computed, ref } from "vue";
3
3
  import StorybookComponent from "../ToggleSwitchCore.vue";
4
4
  import type { FormUiTheme } from "~/types/forms/types.forms.d";
5
5
 
@@ -17,6 +17,7 @@ interface ToggleSwitchCoreStoryArgs {
17
17
  useCustomIcons: boolean;
18
18
  iconOnContent: string;
19
19
  iconOffContent: string;
20
+ symbolSize: string;
20
21
  }
21
22
 
22
23
  export default {
@@ -126,6 +127,15 @@ export default {
126
127
  category: "Icons",
127
128
  },
128
129
  },
130
+ symbolSize: {
131
+ control: { type: "select" },
132
+ options: ["2rem", "2.2rem", "2.4rem", "3rem", "3.6rem"],
133
+ description:
134
+ "`--input-toggle-symbol-size` — thumb size. Track width has no separate control: it's derived from this token (see ToggleSwitchCore's `--_track-width` comment), so it scales automatically.",
135
+ table: {
136
+ category: "Styling",
137
+ },
138
+ },
129
139
  },
130
140
  } as Meta<ToggleSwitchCoreStoryArgs>;
131
141
 
@@ -134,7 +144,10 @@ const Template: StoryFn<ToggleSwitchCoreStoryArgs> = (_args, { argTypes }) => ({
134
144
  props: Object.keys(argTypes),
135
145
  setup() {
136
146
  const modelValue = ref(false);
137
- return { args: _args, modelValue };
147
+ const cssTokenStyle = computed(() => ({
148
+ "--input-toggle-symbol-size": _args.symbolSize,
149
+ }));
150
+ return { args: _args, modelValue, cssTokenStyle };
138
151
  },
139
152
  template: `
140
153
  <StorybookComponent
@@ -149,6 +162,7 @@ const Template: StoryFn<ToggleSwitchCoreStoryArgs> = (_args, { argTypes }) => ({
149
162
  :theme="args.theme"
150
163
  :round="args.round"
151
164
  :aria-describedby="args.ariaDescribedby"
165
+ :style="cssTokenStyle"
152
166
  >
153
167
  <template v-if="args.useCustomIcons" #iconOn>
154
168
  <span v-html="args.iconOnContent"></span>
@@ -178,6 +192,7 @@ Default.args = {
178
192
  useCustomIcons: false,
179
193
  iconOnContent: "✓",
180
194
  iconOffContent: "✗",
195
+ symbolSize: "3rem",
181
196
  };
182
197
 
183
198
  export const WithCustomIcons = Template.bind({});
@@ -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,4 @@ 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"
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "srcdev-nuxt-components",
3
3
  "type": "module",
4
- "version": "9.4.3",
4
+ "version": "9.4.5",
5
5
  "main": "nuxt.config.ts",
6
6
  "types": "types.d.ts",
7
7
  "license": "MIT",
@@ -1,89 +0,0 @@
1
- <template>
2
- <div class="animated-svg-text" :class="[elementClasses]">
3
- <slot name="text"></slot>
4
- </div>
5
- </template>
6
-
7
- <script setup lang="ts">
8
- const props = defineProps({
9
- styleClassPassthrough: {
10
- type: [String, Array] as PropType<string | string[]>,
11
- default: () => [],
12
- },
13
- })
14
-
15
- const { elementClasses, resetElementClasses } = useStyleClassPassthrough(props.styleClassPassthrough)
16
-
17
- watch(
18
- () => props.styleClassPassthrough,
19
- () => {
20
- resetElementClasses(props.styleClassPassthrough)
21
- }
22
- )
23
- </script>
24
-
25
- <style lang="css">
26
- @layer components {
27
-
28
- @keyframes animatedSvgText {
29
- 0% {
30
- fill: transparent;
31
- stroke-dashoffset: var(--_animated-svg-text-stroke-dasharray);
32
- stroke-width: 0.3;
33
- }
34
- 70% {
35
- fill: transparent;
36
- stroke-width: 0.3;
37
- }
38
- 100% {
39
- fill: var(--_animated-svg-fill-color);
40
- stroke-dashoffset: 0;
41
- stroke-width: 0;
42
- }
43
- }
44
-
45
- html {
46
-
47
- &[data-color-scheme='light'] {
48
- .animated-svg-text {
49
- --_animated-svg-text-color: var(--_animated-svg-text-stroke-light);
50
- --_animated-svg-fill-color: var(--_animated-svg-text-fill-light);
51
- }
52
- }
53
- &[data-color-scheme='dark'] {
54
- .animated-svg-text {
55
- --_animated-svg-text-color: var(--_animated-svg-text-stroke-dark);
56
- --_animated-svg-fill-color: var(--_animated-svg-text-fill-dark);
57
- }
58
- }
59
- &[data-color-scheme='auto'] {
60
- .animated-svg-text {
61
- @media (prefers-color-scheme: light) {
62
- --_animated-svg-text-color: var(--_animated-svg-text-stroke-light);
63
- --_animated-svg-fill-color: var(--_animated-svg-text-fill-light);
64
- }
65
-
66
- @media (prefers-color-scheme: dark) {
67
- --_animated-svg-text-color: var(--_animated-svg-text-stroke-dark);
68
- --_animated-svg-fill-color: var(--_animated-svg-text-fill-dark);
69
- }
70
- }
71
- }
72
-
73
- .animated-svg-text {
74
-
75
- --_animated-svg-text-color: var(--_animated-svg-text-stroke-light):
76
- --_animated-svg-fill-color: var(--_animated-svg-text-fill-light);
77
-
78
- svg path {
79
- stroke: var(--_animated-svg-text-color);
80
- stroke-width: 0.3;
81
- stroke-dasharray: var(--_animated-svg-text-stroke-dasharray);
82
- stroke-dashoffset: var(--_animated-svg-text-stroke-dasharray);
83
- animation: animatedSvgText var(--_animated-svg-animation-duration) linear 1 forwards;
84
- }
85
- }
86
-
87
- }
88
- }
89
- </style>