srcdev-nuxt-components 9.6.12 → 9.6.13

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 (60) hide show
  1. package/.claude/rules/consumer/ids-from-useid.md +2 -1
  2. package/.claude/rules/consumer/vfor-keys-from-data.md +14 -0
  3. package/.claude/skills/component-compliance-checklist.md +19 -1
  4. package/.claude/skills/component-naming.md +1 -0
  5. package/.claude/skills/components/banner-video.md +24 -30
  6. package/.claude/skills/components/content-docs.md +18 -5
  7. package/.claude/skills/components/display-dialog.md +16 -6
  8. package/.claude/skills/components/{display-tooltip-defined.md → display-tooltip-rich.md} +40 -42
  9. package/.claude/skills/components/display-tooltip.md +17 -6
  10. package/.claude/skills/components/skip-links.md +16 -0
  11. package/.claude/skills/composable-tooltips-guide.md +4 -4
  12. package/.claude/skills/index.md +6 -6
  13. package/.claude/skills/testing-add-unit-test.md +8 -0
  14. package/.vscode/srcdev-component-banner-video.code-snippets +38 -25
  15. package/.vscode/srcdev-component-content-docs.code-snippets +69 -60
  16. package/.vscode/srcdev-component-display-dialog.code-snippets +7 -5
  17. package/.vscode/srcdev-component-display-tooltip-rich.code-snippets +50 -0
  18. package/.vscode/srcdev-component-display-tooltip.code-snippets +31 -0
  19. package/.vscode/srcdev-component-skip-links.code-snippets +1 -1
  20. package/app/components/01.atoms/banner-video/BannerVideo.vue +51 -19
  21. package/app/components/01.atoms/banner-video/CONSUMER-STYLING.md +59 -40
  22. package/app/components/01.atoms/banner-video/stories/BannerVideo.stories.ts +88 -1
  23. package/app/components/01.atoms/banner-video/tests/BannerVideo.spec.ts +127 -78
  24. package/app/components/01.atoms/banner-video/tests/__snapshots__/BannerVideo.spec.ts.snap +12 -4
  25. package/app/components/01.atoms/content-wrappers/docs-pages/CONSUMER-STYLING.md +42 -14
  26. package/app/components/01.atoms/content-wrappers/docs-pages/ContentDocs.vue +86 -112
  27. package/app/components/01.atoms/content-wrappers/docs-pages/stories/ContentDocs.stories.ts +134 -8
  28. package/app/components/01.atoms/content-wrappers/docs-pages/tests/ContentDocs.spec.ts +131 -65
  29. package/app/components/01.atoms/display-dialog/CONSUMER-STYLING.md +28 -3
  30. package/app/components/01.atoms/display-dialog/DisplayDialog.vue +39 -31
  31. package/app/components/01.atoms/display-dialog/stories/DisplayDialog.stories.ts +92 -1
  32. package/app/components/01.atoms/display-dialog/tests/DisplayDialog.spec.ts +87 -41
  33. package/app/components/01.atoms/display-dialog/tests/__snapshots__/DisplayDialog.spec.ts.snap +5 -1
  34. package/app/components/01.atoms/display-tooltip/CONSUMER-STYLING.md +70 -24
  35. package/app/components/01.atoms/display-tooltip/DisplayTooltip.vue +24 -12
  36. package/app/components/01.atoms/display-tooltip/stories/DisplayTooltip.stories.ts +89 -2
  37. package/app/components/01.atoms/display-tooltip/tests/DisplayTooltip.spec.ts +65 -18
  38. package/app/components/01.atoms/display-tooltip/tests/__snapshots__/DisplayTooltip.spec.ts.snap +3 -3
  39. package/app/components/01.atoms/navigation/skip-links/CONSUMER-STYLING.md +65 -19
  40. package/app/components/01.atoms/navigation/skip-links/SkipLinks.vue +20 -14
  41. package/app/components/01.atoms/navigation/skip-links/stories/SkipLinks.stories.ts +89 -20
  42. package/app/components/01.atoms/navigation/skip-links/tests/SkipLinks.spec.ts +70 -10
  43. package/app/components/02.molecules/display-tooltip-rich/CONSUMER-STYLING.md +110 -0
  44. package/app/components/02.molecules/display-tooltip-rich/DisplayTooltipRich.vue +148 -0
  45. package/app/components/02.molecules/display-tooltip-rich/stories/DisplayTooltipRich.stories.ts +254 -0
  46. package/app/components/02.molecules/display-tooltip-rich/tests/DisplayTooltipRich.spec.ts +156 -0
  47. package/app/components/02.molecules/display-tooltip-rich/tests/__snapshots__/DisplayTooltipRich.spec.ts.snap +22 -0
  48. package/app/components/02.molecules/navigation/deep-expanding-menu/DeepExpandingMenu.vue +1 -0
  49. package/app/components/02.molecules/navigation/deep-expanding-menu/tests/__snapshots__/DeepExpandingMenu.spec.ts.snap +1 -1
  50. package/app/components/03.organisms/image-galleries/carousel-flip/CarouselFlip.vue +1 -0
  51. package/app/composables/tests/useTooltips.spec.ts +3 -3
  52. package/app/types/components/index.ts +1 -1
  53. package/package.json +1 -1
  54. package/.vscode/srcdev-component-display-tooltip-defined.code-snippets +0 -29
  55. package/app/components/02.molecules/display-tooltip-defined/CONSUMER-STYLING.md +0 -53
  56. package/app/components/02.molecules/display-tooltip-defined/DisplayTooltipDefined.vue +0 -117
  57. package/app/components/02.molecules/display-tooltip-defined/stories/DisplayTooltipDefined.stories.ts +0 -129
  58. package/app/components/02.molecules/display-tooltip-defined/tests/DisplayTooltipDefined.spec.ts +0 -85
  59. package/app/components/02.molecules/display-tooltip-defined/tests/__snapshots__/DisplayTooltipDefined.spec.ts.snap +0 -17
  60. /package/app/types/components/{display-tooltip-defined.d.ts → display-tooltip-rich.d.ts} +0 -0
@@ -1,117 +0,0 @@
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="{ close }">
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
- @click="close"
35
- >
36
- {{ closeButtonText }}
37
- </button>
38
- </div>
39
- </template>
40
- </DisplayTooltip>
41
- </template>
42
-
43
- <script setup lang="ts">
44
- import type { TooltipContentText } from "~/types/components";
45
-
46
- interface Props {
47
- tooltipId?: string;
48
- contentText?: TooltipContentText;
49
- /** aria-label on the trigger button — override for localisation. */
50
- triggerAriaLabel?: string;
51
- /** Visible text on the close button — override for localisation. */
52
- closeButtonText?: string;
53
- /** aria-label on the close button — override for localisation. */
54
- closeButtonAriaLabel?: string;
55
- styleClassPassthrough?: string | string[];
56
- }
57
-
58
- const props = withDefaults(defineProps<Props>(), {
59
- tooltipId: "",
60
- contentText: () => ({}),
61
- triggerAriaLabel: "Toggle the popover",
62
- closeButtonText: "Close",
63
- closeButtonAriaLabel: "Close tool tip",
64
- styleClassPassthrough: () => [],
65
- });
66
-
67
- const tooltipId = computed(() =>
68
- props.tooltipId.length ? `nuxt-tooltip-${props.tooltipId}` : `nuxt-tooltip-${useId()}`
69
- );
70
- </script>
71
-
72
- <style lang="css">
73
- @layer components {
74
- .display-tooltip-core {
75
- .popover-content-defined {
76
- display: flex;
77
- flex-direction: column;
78
- gap: var(--display-tooltip-defined-content-gap, 1.2rem);
79
-
80
- /* margin-block resets below neutralise the UA default margin of whatever tag the consumer
81
- picks via contentText.*.tag (e.g. h3/h4 carry their own margin-block) — gap is the only
82
- intended spacing mechanism between these three. */
83
- .tooltip-title {
84
- color: var(--display-tooltip-defined-title-colour, inherit);
85
- margin-block: var(--display-tooltip-defined-title-margin-block, 0 0);
86
- font-size: var(--display-tooltip-defined-title-font-size, 1.6rem);
87
- font-weight: var(--display-tooltip-defined-title-font-weight, 700);
88
- line-height: var(--display-tooltip-defined-title-line-height, 120%);
89
- letter-spacing: var(--display-tooltip-defined-title-letter-spacing, normal);
90
- }
91
-
92
- .tooltip-body {
93
- color: var(--display-tooltip-defined-body-colour, inherit);
94
- margin-block: var(--display-tooltip-defined-body-margin-block, 0 0);
95
- font-size: var(--display-tooltip-defined-body-font-size, 1.3rem);
96
- font-weight: var(--display-tooltip-defined-body-font-weight, inherit);
97
- line-height: var(--display-tooltip-defined-body-line-height, 140%);
98
- letter-spacing: var(--display-tooltip-defined-body-letter-spacing, normal);
99
- }
100
-
101
- .tooltip-action {
102
- color: var(--display-tooltip-defined-action-colour, inherit);
103
- margin-block: var(--display-tooltip-defined-action-margin-block, 0 0);
104
- font-size: var(--display-tooltip-defined-action-font-size, 1.4rem);
105
- font-weight: var(--display-tooltip-defined-action-font-weight, 700);
106
- line-height: var(--display-tooltip-defined-action-line-height, 130%);
107
- letter-spacing: var(--display-tooltip-defined-action-letter-spacing, normal);
108
- }
109
-
110
- .display-tooltip-close-button {
111
- align-self: flex-end;
112
- margin-block: var(--display-tooltip-defined-close-button-margin-block, 1.2rem 0);
113
- }
114
- }
115
- }
116
- }
117
- </style>
@@ -1,129 +0,0 @@
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 (its close button relabelled \"Next\" via `closeButtonText`) with a plain `DisplayTooltip` step. A hand-written close button must call the `tooltipContent` slot's `close()` as well as carrying `popovertargetaction=\"hide\"`: the attribute alone does nothing on Safari 16, which has no Popover API. 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 Next (Close on the last step) to advance
93
- </span>
94
- </div>
95
-
96
- <div ref="guideContainer" style="display: grid; gap: 2rem;">
97
- <DisplayTooltipDefined
98
- close-button-text="Next"
99
- close-button-aria-label="Next step"
100
- :content-text="{
101
- tooltipTitle: { tag: 'h3', text: 'Step 1' },
102
- tooltipContent: { tag: 'p', text: 'This step uses DisplayTooltipDefined — structured title/body/action content plus a built-in close button.' },
103
- tooltipAction: { tag: 'p', text: 'Step 1 of 2' },
104
- }"
105
- >
106
- <template #triggerContent>Trigger from DisplayTooltipDefined</template>
107
- </DisplayTooltipDefined>
108
-
109
- <DisplayTooltip tooltip-id="guided-tour-step-2">
110
- <template #triggerContent>Trigger from plain DisplayTooltip</template>
111
- <template #tooltipContent="{ close }">
112
- <p>This step uses plain DisplayTooltip — you supply the close button yourself via the tooltipContent slot.</p>
113
- <button
114
- popovertarget="guided-tour-step-2"
115
- popovertargetaction="hide"
116
- class="display-tooltip-close-button"
117
- style="align-self: flex-end;"
118
- aria-label="Close tool tip"
119
- @click="close"
120
- >
121
- Close
122
- </button>
123
- </template>
124
- </DisplayTooltip>
125
- </div>
126
- </div>
127
- `,
128
- }),
129
- };
@@ -1,85 +0,0 @@
1
- import { describe, it, expect, beforeEach, afterEach } from "vitest";
2
- import { mountSuspended } from "@nuxt/test-utils/runtime";
3
- import DisplayTooltipDefined from "../DisplayTooltipDefined.vue";
4
- import type { TooltipContentText } from "~/types/components";
5
-
6
- const contentText: TooltipContentText = {
7
- tooltipTitle: { tag: "h4", text: "Title" },
8
- tooltipContent: { tag: "p", text: "Body copy" },
9
- tooltipAction: { tag: "span", text: "Learn more" },
10
- };
11
-
12
- describe("DisplayTooltipDefined", () => {
13
- it("mounts without error", async () => {
14
- const wrapper = await mountSuspended(DisplayTooltipDefined);
15
- expect(wrapper.vm).toBeTruthy();
16
- });
17
-
18
- it("renders correct HTML structure", async () => {
19
- const wrapper = await mountSuspended(DisplayTooltipDefined, { props: { contentText, tooltipId: "fixed" } });
20
- expect(wrapper.html()).toMatchSnapshot();
21
- });
22
-
23
- it("renders the title, body, and action from contentText", async () => {
24
- const wrapper = await mountSuspended(DisplayTooltipDefined, { props: { contentText } });
25
- expect(wrapper.find(".tooltip-title").text()).toBe("Title");
26
- expect(wrapper.find(".tooltip-body").text()).toBe("Body copy");
27
- expect(wrapper.find(".tooltip-action").text()).toBe("Learn more");
28
- });
29
-
30
- it("renders each contentText field with its given tag", async () => {
31
- const wrapper = await mountSuspended(DisplayTooltipDefined, { props: { contentText } });
32
- expect(wrapper.find(".tooltip-title").element.tagName.toLowerCase()).toBe("h4");
33
- expect(wrapper.find(".tooltip-body").element.tagName.toLowerCase()).toBe("p");
34
- expect(wrapper.find(".tooltip-action").element.tagName.toLowerCase()).toBe("span");
35
- });
36
-
37
- it("omits a field when not present in contentText", async () => {
38
- const wrapper = await mountSuspended(DisplayTooltipDefined, {
39
- props: { contentText: { tooltipTitle: { tag: "h4", text: "Only a title" } } },
40
- });
41
- expect(wrapper.find(".tooltip-title").exists()).toBe(true);
42
- expect(wrapper.find(".tooltip-body").exists()).toBe(false);
43
- expect(wrapper.find(".tooltip-action").exists()).toBe(false);
44
- });
45
-
46
- it("renders a close button wired to the tooltip id", async () => {
47
- const wrapper = await mountSuspended(DisplayTooltipDefined, { props: { tooltipId: "my-tip" } });
48
- const closeButton = wrapper.find(".display-tooltip-close-button");
49
- expect(closeButton.attributes("popovertarget")).toBe("nuxt-tooltip-my-tip");
50
- expect(closeButton.attributes("popovertargetaction")).toBe("hide");
51
- });
52
-
53
- it("renders the triggerContent slot", async () => {
54
- const wrapper = await mountSuspended(DisplayTooltipDefined, {
55
- slots: { triggerContent: "<span class='slot-trigger'>?</span>" },
56
- });
57
- expect(wrapper.find(".slot-trigger").text()).toBe("?");
58
- });
59
-
60
- it("applies styleClassPassthrough classes to the underlying DisplayTooltip", async () => {
61
- const wrapper = await mountSuspended(DisplayTooltipDefined, {
62
- props: { styleClassPassthrough: ["custom-class"] },
63
- });
64
- expect(wrapper.classes()).toContain("custom-class");
65
- });
66
-
67
- describe("without the Popover API", () => {
68
- beforeEach(() => {
69
- Object.defineProperty(HTMLElement.prototype, "showPopover", { value: undefined, writable: true, configurable: true });
70
- });
71
-
72
- afterEach(() => {
73
- delete (HTMLElement.prototype as unknown as Record<string, unknown>)["showPopover"];
74
- });
75
-
76
- it("closes from the close button", async () => {
77
- const wrapper = await mountSuspended(DisplayTooltipDefined, { props: { contentText } });
78
- await wrapper.find(".display-tooltip-trigger-button").trigger("click");
79
- expect(wrapper.find(".display-tooltip-popover").classes()).toContain("display-tooltip-popover-open");
80
-
81
- await wrapper.find(".display-tooltip-close-button").trigger("click");
82
- expect(wrapper.find(".display-tooltip-popover").classes()).not.toContain("display-tooltip-popover-open");
83
- });
84
- });
85
- });
@@ -1,17 +0,0 @@
1
- // Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
2
-
3
- exports[`DisplayTooltipDefined > renders correct HTML structure 1`] = `
4
- "<div class="display-tooltip-core" style="--_anchor-name: --tooltip-anchor-v-0-0-0;">
5
- <div class="display-tooltip-trigger-wrapper body-md">
6
- <!--v-if--><button popovertarget="nuxt-tooltip-fixed" popovertargetaction="toggle" class="display-tooltip-trigger-button" aria-label="Toggle the popover" aria-expanded="false"><span class="iconify i-fa7-solid:circle-question display-tooltip-trigger-icon" aria-hidden="true"></span></button>
7
- </div>
8
- <div id="nuxt-tooltip-fixed" popover="" class="display-tooltip-popover">
9
- <div class="display-tooltip-popover-content">
10
- <div class="popover-content-defined">
11
- <h4 class="tooltip-title subtitle-sm">Title</h4>
12
- <p class="tooltip-body body-sm">Body copy</p><span class="tooltip-action input-value">Learn more</span><button popovertarget="nuxt-tooltip-fixed" popovertargetaction="hide" class="display-tooltip-close-button" aria-label="Close tool tip">Close</button>
13
- </div>
14
- </div>
15
- </div>
16
- </div>"
17
- `;