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.
- package/.claude/rules/consumer/ids-from-useid.md +2 -1
- package/.claude/rules/consumer/vfor-keys-from-data.md +14 -0
- package/.claude/skills/component-compliance-checklist.md +19 -1
- package/.claude/skills/component-naming.md +1 -0
- package/.claude/skills/components/banner-video.md +24 -30
- package/.claude/skills/components/content-docs.md +18 -5
- package/.claude/skills/components/display-dialog.md +16 -6
- package/.claude/skills/components/{display-tooltip-defined.md → display-tooltip-rich.md} +40 -42
- package/.claude/skills/components/display-tooltip.md +17 -6
- package/.claude/skills/components/skip-links.md +16 -0
- package/.claude/skills/composable-tooltips-guide.md +4 -4
- package/.claude/skills/index.md +6 -6
- package/.claude/skills/testing-add-unit-test.md +8 -0
- package/.vscode/srcdev-component-banner-video.code-snippets +38 -25
- package/.vscode/srcdev-component-content-docs.code-snippets +69 -60
- package/.vscode/srcdev-component-display-dialog.code-snippets +7 -5
- package/.vscode/srcdev-component-display-tooltip-rich.code-snippets +50 -0
- package/.vscode/srcdev-component-display-tooltip.code-snippets +31 -0
- package/.vscode/srcdev-component-skip-links.code-snippets +1 -1
- package/app/components/01.atoms/banner-video/BannerVideo.vue +51 -19
- package/app/components/01.atoms/banner-video/CONSUMER-STYLING.md +59 -40
- package/app/components/01.atoms/banner-video/stories/BannerVideo.stories.ts +88 -1
- package/app/components/01.atoms/banner-video/tests/BannerVideo.spec.ts +127 -78
- package/app/components/01.atoms/banner-video/tests/__snapshots__/BannerVideo.spec.ts.snap +12 -4
- package/app/components/01.atoms/content-wrappers/docs-pages/CONSUMER-STYLING.md +42 -14
- package/app/components/01.atoms/content-wrappers/docs-pages/ContentDocs.vue +86 -112
- package/app/components/01.atoms/content-wrappers/docs-pages/stories/ContentDocs.stories.ts +134 -8
- package/app/components/01.atoms/content-wrappers/docs-pages/tests/ContentDocs.spec.ts +131 -65
- package/app/components/01.atoms/display-dialog/CONSUMER-STYLING.md +28 -3
- package/app/components/01.atoms/display-dialog/DisplayDialog.vue +39 -31
- package/app/components/01.atoms/display-dialog/stories/DisplayDialog.stories.ts +92 -1
- package/app/components/01.atoms/display-dialog/tests/DisplayDialog.spec.ts +87 -41
- package/app/components/01.atoms/display-dialog/tests/__snapshots__/DisplayDialog.spec.ts.snap +5 -1
- package/app/components/01.atoms/display-tooltip/CONSUMER-STYLING.md +70 -24
- package/app/components/01.atoms/display-tooltip/DisplayTooltip.vue +24 -12
- package/app/components/01.atoms/display-tooltip/stories/DisplayTooltip.stories.ts +89 -2
- package/app/components/01.atoms/display-tooltip/tests/DisplayTooltip.spec.ts +65 -18
- package/app/components/01.atoms/display-tooltip/tests/__snapshots__/DisplayTooltip.spec.ts.snap +3 -3
- package/app/components/01.atoms/navigation/skip-links/CONSUMER-STYLING.md +65 -19
- package/app/components/01.atoms/navigation/skip-links/SkipLinks.vue +20 -14
- package/app/components/01.atoms/navigation/skip-links/stories/SkipLinks.stories.ts +89 -20
- package/app/components/01.atoms/navigation/skip-links/tests/SkipLinks.spec.ts +70 -10
- package/app/components/02.molecules/display-tooltip-rich/CONSUMER-STYLING.md +110 -0
- package/app/components/02.molecules/display-tooltip-rich/DisplayTooltipRich.vue +148 -0
- package/app/components/02.molecules/display-tooltip-rich/stories/DisplayTooltipRich.stories.ts +254 -0
- package/app/components/02.molecules/display-tooltip-rich/tests/DisplayTooltipRich.spec.ts +156 -0
- package/app/components/02.molecules/display-tooltip-rich/tests/__snapshots__/DisplayTooltipRich.spec.ts.snap +22 -0
- package/app/components/02.molecules/navigation/deep-expanding-menu/DeepExpandingMenu.vue +1 -0
- package/app/components/02.molecules/navigation/deep-expanding-menu/tests/__snapshots__/DeepExpandingMenu.spec.ts.snap +1 -1
- package/app/components/03.organisms/image-galleries/carousel-flip/CarouselFlip.vue +1 -0
- package/app/composables/tests/useTooltips.spec.ts +3 -3
- package/app/types/components/index.ts +1 -1
- package/package.json +1 -1
- package/.vscode/srcdev-component-display-tooltip-defined.code-snippets +0 -29
- package/app/components/02.molecules/display-tooltip-defined/CONSUMER-STYLING.md +0 -53
- package/app/components/02.molecules/display-tooltip-defined/DisplayTooltipDefined.vue +0 -117
- package/app/components/02.molecules/display-tooltip-defined/stories/DisplayTooltipDefined.stories.ts +0 -129
- package/app/components/02.molecules/display-tooltip-defined/tests/DisplayTooltipDefined.spec.ts +0 -85
- package/app/components/02.molecules/display-tooltip-defined/tests/__snapshots__/DisplayTooltipDefined.spec.ts.snap +0 -17
- /package/app/types/components/{display-tooltip-defined.d.ts → display-tooltip-rich.d.ts} +0 -0
package/app/components/02.molecules/display-tooltip-rich/stories/DisplayTooltipRich.stories.ts
ADDED
|
@@ -0,0 +1,254 @@
|
|
|
1
|
+
import { computed, ref } from "vue";
|
|
2
|
+
import DisplayTooltipRich from "../DisplayTooltipRich.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
|
+
type LineClamp = "none" | "1" | "2" | "3";
|
|
9
|
+
|
|
10
|
+
type StoryArgs = {
|
|
11
|
+
tooltipId: string;
|
|
12
|
+
contentText: TooltipContentText;
|
|
13
|
+
triggerAriaLabel: string;
|
|
14
|
+
triggerIcon: string;
|
|
15
|
+
closeButtonText: string;
|
|
16
|
+
closeButtonAriaLabel: string;
|
|
17
|
+
titleLineClamp: LineClamp;
|
|
18
|
+
bodyLineClamp: LineClamp;
|
|
19
|
+
actionLineClamp: LineClamp;
|
|
20
|
+
styleClassPassthrough?: string | string[];
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
const contentText: TooltipContentText = {
|
|
24
|
+
tooltipTitle: { tag: "h4", text: "Free delivery" },
|
|
25
|
+
tooltipContent: { tag: "p", text: "Orders over £50 qualify for free standard delivery." },
|
|
26
|
+
tooltipAction: { tag: "span", text: "See delivery policy" },
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
const lineClampControl = (element: string) => ({
|
|
30
|
+
control: { type: "select" as const },
|
|
31
|
+
options: ["none", "1", "2", "3"],
|
|
32
|
+
description: `Story-only CSS token, not a prop: sets \`--display-tooltip-rich-${element}-line-clamp\` on a wrapper (\`1\` is single-line ellipsis).`,
|
|
33
|
+
table: { category: "CSS tokens (story-only)" },
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
const meta: Meta<StoryArgs> = {
|
|
37
|
+
title: "Molecules/DisplayTooltipRich",
|
|
38
|
+
component: DisplayTooltipRich,
|
|
39
|
+
argTypes: {
|
|
40
|
+
tooltipId: {
|
|
41
|
+
control: "text",
|
|
42
|
+
description: "Base id for the popover, prefixed with `nuxt-tooltip-`. Auto-generated when omitted.",
|
|
43
|
+
table: { category: "Basic" },
|
|
44
|
+
},
|
|
45
|
+
triggerIcon: {
|
|
46
|
+
control: "text",
|
|
47
|
+
description: "Iconify name for the trigger button icon (DisplayTooltip's default when empty)",
|
|
48
|
+
table: { category: "Basic" },
|
|
49
|
+
},
|
|
50
|
+
triggerAriaLabel: {
|
|
51
|
+
control: "text",
|
|
52
|
+
description: "aria-label on the trigger button (override for localisation)",
|
|
53
|
+
table: { category: "Accessibility" },
|
|
54
|
+
},
|
|
55
|
+
closeButtonText: {
|
|
56
|
+
control: "text",
|
|
57
|
+
description: "Visible close button text (override for localisation)",
|
|
58
|
+
table: { category: "Content" },
|
|
59
|
+
},
|
|
60
|
+
closeButtonAriaLabel: {
|
|
61
|
+
control: "text",
|
|
62
|
+
description: "aria-label on the close button; keep the visible text inside it",
|
|
63
|
+
table: { category: "Accessibility" },
|
|
64
|
+
},
|
|
65
|
+
titleLineClamp: lineClampControl("title"),
|
|
66
|
+
bodyLineClamp: lineClampControl("body"),
|
|
67
|
+
actionLineClamp: lineClampControl("action"),
|
|
68
|
+
contentText: { table: { disable: true } },
|
|
69
|
+
styleClassPassthrough: { table: { disable: true } },
|
|
70
|
+
},
|
|
71
|
+
args: {
|
|
72
|
+
contentText,
|
|
73
|
+
triggerAriaLabel: "Toggle the popover",
|
|
74
|
+
closeButtonText: "Close",
|
|
75
|
+
closeButtonAriaLabel: "Close tool tip",
|
|
76
|
+
titleLineClamp: "none",
|
|
77
|
+
bodyLineClamp: "none",
|
|
78
|
+
actionLineClamp: "none",
|
|
79
|
+
},
|
|
80
|
+
parameters: {
|
|
81
|
+
docs: {
|
|
82
|
+
description: {
|
|
83
|
+
component:
|
|
84
|
+
"Material 3's rich tooltip, built on DisplayTooltip: a structured panel where `contentText` supplies an optional title, body and action line (each with its own tag), followed by a built-in close button (an `InputButton`) wired to the popover and to DisplayTooltip's `close()`. Props localise the trigger and close button (`triggerAriaLabel`, `triggerIcon`, `closeButtonText`, `closeButtonAriaLabel`); the `triggerContent` slot passes through. For free-form panel content use DisplayTooltip directly.",
|
|
85
|
+
},
|
|
86
|
+
},
|
|
87
|
+
},
|
|
88
|
+
};
|
|
89
|
+
|
|
90
|
+
export default meta;
|
|
91
|
+
type Story = StoryObj<StoryArgs>;
|
|
92
|
+
|
|
93
|
+
const clampSetup = (args: StoryArgs) => ({
|
|
94
|
+
componentArgs: computed(() => {
|
|
95
|
+
const { titleLineClamp: _t, bodyLineClamp: _b, actionLineClamp: _a, ...rest } = args;
|
|
96
|
+
return { ...rest, triggerIcon: rest.triggerIcon || undefined };
|
|
97
|
+
}),
|
|
98
|
+
clampStyle: computed(() => ({
|
|
99
|
+
"--display-tooltip-rich-title-line-clamp": args.titleLineClamp,
|
|
100
|
+
"--display-tooltip-rich-body-line-clamp": args.bodyLineClamp,
|
|
101
|
+
"--display-tooltip-rich-action-line-clamp": args.actionLineClamp,
|
|
102
|
+
})),
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
const renderWithClampTokens: Story["render"] = (args) => ({
|
|
106
|
+
components: { DisplayTooltipRich },
|
|
107
|
+
setup: () => clampSetup(args),
|
|
108
|
+
template: `<div style="padding: 8rem;" :style="clampStyle"><DisplayTooltipRich v-bind="componentArgs" /></div>`,
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
export const Default: Story = {
|
|
112
|
+
render: renderWithClampTokens,
|
|
113
|
+
};
|
|
114
|
+
|
|
115
|
+
export const TitleOnly: Story = {
|
|
116
|
+
args: {
|
|
117
|
+
contentText: { tooltipTitle: { tag: "h4", text: "Just a title" } },
|
|
118
|
+
},
|
|
119
|
+
render: renderWithClampTokens,
|
|
120
|
+
};
|
|
121
|
+
|
|
122
|
+
export const StressTest: Story = {
|
|
123
|
+
name: "Stress Test (Worst-Case Data)",
|
|
124
|
+
args: {
|
|
125
|
+
contentText: {
|
|
126
|
+
tooltipTitle: {
|
|
127
|
+
tag: "h4",
|
|
128
|
+
text: "😀 Kostenloselieferungfürallebestellungenübereinemmindestbestellwert <b>not bold</b> التوصيل المجاني",
|
|
129
|
+
},
|
|
130
|
+
tooltipContent: {
|
|
131
|
+
tag: "",
|
|
132
|
+
text: "https://example.invalid/a/very/long/unbroken/url/that/never/offers/a/break/opportunity/anywhere ".repeat(2) +
|
|
133
|
+
"Bestellungen über fünfzig Pfund qualifizieren sich für die kostenlose Standardlieferung innerhalb des Vereinigten Königreichs, ausgenommen sind Inseln und abgelegene Regionen.",
|
|
134
|
+
},
|
|
135
|
+
tooltipAction: { tag: "span", text: "Lieferrichtliniensowierückgabebedingungenansehen" },
|
|
136
|
+
},
|
|
137
|
+
triggerAriaLabel: "Weitereinformationenzurlieferunganzeigen 😀",
|
|
138
|
+
closeButtonText: "Schließenundzurückzurbestellübersicht",
|
|
139
|
+
closeButtonAriaLabel: "Schließenundzurückzurbestellübersicht",
|
|
140
|
+
titleLineClamp: "2",
|
|
141
|
+
bodyLineClamp: "none",
|
|
142
|
+
actionLineClamp: "1",
|
|
143
|
+
},
|
|
144
|
+
render: (args) => ({
|
|
145
|
+
components: { DisplayTooltipRich },
|
|
146
|
+
setup: () => clampSetup(args),
|
|
147
|
+
template: `
|
|
148
|
+
<div style="display: grid; gap: 2.4rem; padding: 2.4rem; font-size: 1.4rem;" :style="clampStyle">
|
|
149
|
+
<p style="margin: 0;">Every field at its worst, an empty body tag (falls back to <code><p></code>) and a long unbroken close label. The line-clamp controls apply to this tooltip.</p>
|
|
150
|
+
<div style="inline-size: 18rem; outline: 0.1rem dashed currentColor;">
|
|
151
|
+
<DisplayTooltipRich v-bind="componentArgs">
|
|
152
|
+
<template #triggerContent>
|
|
153
|
+
<span>Lieferkostenundversandbedingungen</span>
|
|
154
|
+
</template>
|
|
155
|
+
</DisplayTooltipRich>
|
|
156
|
+
</div>
|
|
157
|
+
|
|
158
|
+
<p style="margin: 0;">Empty <code>contentText</code>: the panel holds only the close button.</p>
|
|
159
|
+
<DisplayTooltipRich :content-text="{}"></DisplayTooltipRich>
|
|
160
|
+
|
|
161
|
+
<p style="margin: 0;">Single characters in every field.</p>
|
|
162
|
+
<DisplayTooltipRich
|
|
163
|
+
:content-text="{ tooltipTitle: { tag: 'h4', text: 'T' }, tooltipContent: { tag: 'p', text: 'B' }, tooltipAction: { tag: 'span', text: 'A' } }"
|
|
164
|
+
close-button-text="X"
|
|
165
|
+
close-button-aria-label="X"
|
|
166
|
+
></DisplayTooltipRich>
|
|
167
|
+
|
|
168
|
+
<p style="margin: 0;">Right-to-left content at the right edge: the panel flips to the left of the trigger.</p>
|
|
169
|
+
<div style="display: flex; justify-content: flex-end;">
|
|
170
|
+
<DisplayTooltipRich
|
|
171
|
+
:content-text="{ tooltipTitle: { tag: 'h4', text: 'التوصيل المجاني' }, tooltipContent: { tag: 'p', text: 'الطلبات التي تزيد عن خمسين جنيهًا مؤهلة للتوصيل المجاني.' } }"
|
|
172
|
+
></DisplayTooltipRich>
|
|
173
|
+
</div>
|
|
174
|
+
</div>
|
|
175
|
+
`,
|
|
176
|
+
}),
|
|
177
|
+
parameters: {
|
|
178
|
+
docs: {
|
|
179
|
+
story: { inline: false, height: "900px" },
|
|
180
|
+
description: {
|
|
181
|
+
story:
|
|
182
|
+
"Worst-case `contentText`. Open each tooltip and check: unbroken title, body, action and close-button text wraps inside the panel and the panel never gets wider than the viewport; HTML-like text renders as text; the title is capped at two lines and the action at one with an ellipsis (change them with the story-only line-clamp controls); the close button stays right-aligned at its natural width instead of stretching; an empty `contentText` leaves just the close button; a trigger at the right edge opens its panel to the left.",
|
|
183
|
+
},
|
|
184
|
+
},
|
|
185
|
+
},
|
|
186
|
+
};
|
|
187
|
+
|
|
188
|
+
export const GuidedTour: Story = {
|
|
189
|
+
parameters: {
|
|
190
|
+
docs: {
|
|
191
|
+
story: { inline: false, height: "500px" },
|
|
192
|
+
description: {
|
|
193
|
+
story:
|
|
194
|
+
"`useTooltipsGuide` walks a container's `[popover]` elements in DOM order, opening each and waiting for its close button before advancing. Mixes a `DisplayTooltipRich` 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. The guide auto-starts 800ms after mount; use \"Run guide again\" to replay it.",
|
|
195
|
+
},
|
|
196
|
+
},
|
|
197
|
+
},
|
|
198
|
+
render: () => ({
|
|
199
|
+
components: { DisplayTooltipRich, DisplayTooltip },
|
|
200
|
+
setup() {
|
|
201
|
+
const guideContainer = ref<HTMLElement | null>(null);
|
|
202
|
+
const { isGuideRunning, restartGuide, currentTooltipIndex, totalPopovers } = useTooltipsGuide(guideContainer, {
|
|
203
|
+
autoStart: true,
|
|
204
|
+
startDelay: 800,
|
|
205
|
+
});
|
|
206
|
+
|
|
207
|
+
return { guideContainer, isGuideRunning, restartGuide, currentTooltipIndex, totalPopovers };
|
|
208
|
+
},
|
|
209
|
+
template: `
|
|
210
|
+
<div style="padding: 4rem; display: grid; gap: 2rem; max-width: 48rem;">
|
|
211
|
+
<div style="display: flex; align-items: center; gap: 1.2rem;">
|
|
212
|
+
<button type="button" @click="restartGuide" :disabled="isGuideRunning" style="padding: 0.6rem 1.2rem;">
|
|
213
|
+
{{ isGuideRunning ? 'Guide running…' : 'Run guide again' }}
|
|
214
|
+
</button>
|
|
215
|
+
<span v-if="isGuideRunning" style="font-size: 1.3rem; color: #666;">
|
|
216
|
+
Step {{ currentTooltipIndex + 1 }} of {{ totalPopovers }}: click Next (Close on the last step) to advance
|
|
217
|
+
</span>
|
|
218
|
+
</div>
|
|
219
|
+
|
|
220
|
+
<div ref="guideContainer" style="display: grid; gap: 2rem;">
|
|
221
|
+
<DisplayTooltipRich
|
|
222
|
+
close-button-text="Next"
|
|
223
|
+
close-button-aria-label="Next step"
|
|
224
|
+
:content-text="{
|
|
225
|
+
tooltipTitle: { tag: 'h3', text: 'Step 1' },
|
|
226
|
+
tooltipContent: { tag: 'p', text: 'This step uses DisplayTooltipRich: structured title/body/action content plus a built-in close button.' },
|
|
227
|
+
tooltipAction: { tag: 'p', text: 'Step 1 of 2' },
|
|
228
|
+
}"
|
|
229
|
+
>
|
|
230
|
+
<template #triggerContent>Trigger from DisplayTooltipRich</template>
|
|
231
|
+
</DisplayTooltipRich>
|
|
232
|
+
|
|
233
|
+
<DisplayTooltip tooltip-id="guided-tour-step-2">
|
|
234
|
+
<template #triggerContent>Trigger from plain DisplayTooltip</template>
|
|
235
|
+
<template #tooltipContent="{ close }">
|
|
236
|
+
<p>This step uses plain DisplayTooltip: you supply the close button yourself via the tooltipContent slot.</p>
|
|
237
|
+
<button
|
|
238
|
+
type="button"
|
|
239
|
+
popovertarget="guided-tour-step-2"
|
|
240
|
+
popovertargetaction="hide"
|
|
241
|
+
class="display-tooltip-close-button"
|
|
242
|
+
style="align-self: flex-end;"
|
|
243
|
+
aria-label="Close tool tip"
|
|
244
|
+
@click="close"
|
|
245
|
+
>
|
|
246
|
+
Close
|
|
247
|
+
</button>
|
|
248
|
+
</template>
|
|
249
|
+
</DisplayTooltip>
|
|
250
|
+
</div>
|
|
251
|
+
</div>
|
|
252
|
+
`,
|
|
253
|
+
}),
|
|
254
|
+
};
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";
|
|
2
|
+
import { mountSuspended } from "@nuxt/test-utils/runtime";
|
|
3
|
+
import DisplayTooltipRich from "../DisplayTooltipRich.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
|
+
const mounted: { unmount: () => void }[] = [];
|
|
13
|
+
|
|
14
|
+
const mountDefined = async (options: Parameters<typeof mountSuspended<typeof DisplayTooltipRich>>[1] = {}) => {
|
|
15
|
+
const wrapper = await mountSuspended(DisplayTooltipRich, options);
|
|
16
|
+
mounted.push(wrapper);
|
|
17
|
+
return wrapper;
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
describe("DisplayTooltipRich", () => {
|
|
21
|
+
afterEach(() => {
|
|
22
|
+
mounted.splice(0).forEach((wrapper) => wrapper.unmount());
|
|
23
|
+
vi.restoreAllMocks();
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
it("mounts without error", async () => {
|
|
27
|
+
const wrapper = await mountDefined();
|
|
28
|
+
expect(wrapper.vm).toBeTruthy();
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
it("renders correct HTML structure", async () => {
|
|
32
|
+
const wrapper = await mountDefined({ props: { contentText, tooltipId: "fixed" } });
|
|
33
|
+
expect(wrapper.html()).toMatchSnapshot();
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
it("renders the title, body, and action from contentText", async () => {
|
|
37
|
+
const wrapper = await mountDefined({ props: { contentText } });
|
|
38
|
+
expect(wrapper.find(".display-tooltip-rich-title").text()).toBe("Title");
|
|
39
|
+
expect(wrapper.find(".display-tooltip-rich-body").text()).toBe("Body copy");
|
|
40
|
+
expect(wrapper.find(".display-tooltip-rich-action").text()).toBe("Learn more");
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
it("renders each contentText field with its given tag", async () => {
|
|
44
|
+
const wrapper = await mountDefined({ props: { contentText } });
|
|
45
|
+
expect(wrapper.find(".display-tooltip-rich-title").element.tagName.toLowerCase()).toBe("h4");
|
|
46
|
+
expect(wrapper.find(".display-tooltip-rich-body").element.tagName.toLowerCase()).toBe("p");
|
|
47
|
+
expect(wrapper.find(".display-tooltip-rich-action").element.tagName.toLowerCase()).toBe("span");
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
it("omits a field when not present in contentText", async () => {
|
|
51
|
+
const wrapper = await mountDefined({
|
|
52
|
+
props: { contentText: { tooltipTitle: { tag: "h4", text: "Only a title" } } },
|
|
53
|
+
});
|
|
54
|
+
expect(wrapper.find(".display-tooltip-rich-title").exists()).toBe(true);
|
|
55
|
+
expect(wrapper.find(".display-tooltip-rich-body").exists()).toBe(false);
|
|
56
|
+
expect(wrapper.find(".display-tooltip-rich-action").exists()).toBe(false);
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
it("renders a close button wired to the tooltip id", async () => {
|
|
60
|
+
const wrapper = await mountDefined({ props: { tooltipId: "my-tip" } });
|
|
61
|
+
const closeButton = wrapper.find(".display-tooltip-close-button");
|
|
62
|
+
expect(closeButton.attributes("popovertarget")).toBe("nuxt-tooltip-my-tip");
|
|
63
|
+
expect(closeButton.attributes("popovertargetaction")).toBe("hide");
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
it("renders the close button as an inline InputButton of type button with localisable copy", async () => {
|
|
67
|
+
const wrapper = await mountDefined({ props: { closeButtonText: "Fermer", closeButtonAriaLabel: "Fermer l'info-bulle" } });
|
|
68
|
+
const closeButton = wrapper.find(".display-tooltip-close-button");
|
|
69
|
+
expect(closeButton.element.tagName).toBe("BUTTON");
|
|
70
|
+
expect(closeButton.classes()).toContain("input-button");
|
|
71
|
+
expect(closeButton.attributes("type")).toBe("button");
|
|
72
|
+
expect(closeButton.attributes("data-variant")).toBe("inline");
|
|
73
|
+
expect(closeButton.text()).toBe("Fermer");
|
|
74
|
+
expect(closeButton.attributes("aria-label")).toBe("Fermer l'info-bulle");
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
it("adds its own root class alongside DisplayTooltip's", async () => {
|
|
78
|
+
const wrapper = await mountDefined();
|
|
79
|
+
expect(wrapper.classes()).toEqual(expect.arrayContaining(["display-tooltip", "display-tooltip-rich"]));
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
it("keeps its root class when styleClassPassthrough changes", async () => {
|
|
83
|
+
const wrapper = await mountDefined({ props: { styleClassPassthrough: "first" } });
|
|
84
|
+
await wrapper.setProps({ styleClassPassthrough: ["second"] });
|
|
85
|
+
expect(wrapper.classes()).toEqual(expect.arrayContaining(["display-tooltip-rich", "second"]));
|
|
86
|
+
expect(wrapper.classes()).not.toContain("first");
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
it("forwards triggerIcon and triggerAriaLabel to the trigger", async () => {
|
|
90
|
+
const wrapper = await mountDefined({ props: { triggerIcon: "lucide:info", triggerAriaLabel: "Plus d'informations" } });
|
|
91
|
+
const trigger = wrapper.find(".display-tooltip-trigger-button");
|
|
92
|
+
expect(trigger.attributes("aria-label")).toBe("Plus d'informations");
|
|
93
|
+
expect(trigger.find(".display-tooltip-trigger-icon").classes().join(" ")).toContain("lucide:info");
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
it("falls back to a default tag when a contentText tag is empty", async () => {
|
|
97
|
+
const wrapper = await mountDefined({
|
|
98
|
+
props: {
|
|
99
|
+
contentText: {
|
|
100
|
+
tooltipTitle: { tag: "", text: "T" },
|
|
101
|
+
tooltipContent: { tag: " ", text: "B" },
|
|
102
|
+
tooltipAction: { tag: "", text: "A" },
|
|
103
|
+
},
|
|
104
|
+
},
|
|
105
|
+
});
|
|
106
|
+
expect(wrapper.find(".display-tooltip-rich-title").element.tagName).toBe("P");
|
|
107
|
+
expect(wrapper.find(".display-tooltip-rich-body").element.tagName).toBe("P");
|
|
108
|
+
expect(wrapper.find(".display-tooltip-rich-action").element.tagName).toBe("SPAN");
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
it("keeps the generated id stable and links the trigger, panel and close button", async () => {
|
|
112
|
+
const wrapper = await mountDefined();
|
|
113
|
+
const target = wrapper.find(".display-tooltip-trigger-button").attributes("popovertarget");
|
|
114
|
+
expect(target).toMatch(/^nuxt-tooltip-/);
|
|
115
|
+
expect(wrapper.find(".display-tooltip-popover").attributes("id")).toBe(target);
|
|
116
|
+
expect(wrapper.find(".display-tooltip-close-button").attributes("popovertarget")).toBe(target);
|
|
117
|
+
|
|
118
|
+
await wrapper.setProps({ tooltipId: "tip" });
|
|
119
|
+
expect(wrapper.find(".display-tooltip-close-button").attributes("popovertarget")).toBe("nuxt-tooltip-tip");
|
|
120
|
+
await wrapper.setProps({ tooltipId: "" });
|
|
121
|
+
expect(wrapper.find(".display-tooltip-close-button").attributes("popovertarget")).toBe(target);
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
it("renders the triggerContent slot", async () => {
|
|
125
|
+
const wrapper = await mountDefined({
|
|
126
|
+
slots: { triggerContent: "<span class='slot-trigger'>?</span>" },
|
|
127
|
+
});
|
|
128
|
+
expect(wrapper.find(".slot-trigger").text()).toBe("?");
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
it("applies styleClassPassthrough classes to the underlying DisplayTooltip", async () => {
|
|
132
|
+
const wrapper = await mountDefined({
|
|
133
|
+
props: { styleClassPassthrough: ["custom-class"] },
|
|
134
|
+
});
|
|
135
|
+
expect(wrapper.classes()).toContain("custom-class");
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
describe("without the Popover API", () => {
|
|
139
|
+
beforeEach(() => {
|
|
140
|
+
Object.defineProperty(HTMLElement.prototype, "showPopover", { value: undefined, writable: true, configurable: true });
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
afterEach(() => {
|
|
144
|
+
delete (HTMLElement.prototype as unknown as Record<string, unknown>)["showPopover"];
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
it("closes from the close button", async () => {
|
|
148
|
+
const wrapper = await mountDefined({ props: { contentText } });
|
|
149
|
+
await wrapper.find(".display-tooltip-trigger-button").trigger("click");
|
|
150
|
+
expect(wrapper.find(".display-tooltip-popover").classes()).toContain("display-tooltip-popover-open");
|
|
151
|
+
|
|
152
|
+
await wrapper.find(".display-tooltip-close-button").trigger("click");
|
|
153
|
+
expect(wrapper.find(".display-tooltip-popover").classes()).not.toContain("display-tooltip-popover-open");
|
|
154
|
+
});
|
|
155
|
+
});
|
|
156
|
+
});
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
|
|
2
|
+
|
|
3
|
+
exports[`DisplayTooltipRich > renders correct HTML structure 1`] = `
|
|
4
|
+
"<div class="display-tooltip display-tooltip-rich" style="--_anchor-name: --tooltip-anchor-v-0-0-1;">
|
|
5
|
+
<div class="display-tooltip-trigger-wrapper">
|
|
6
|
+
<!--v-if--><button popovertarget="nuxt-tooltip-fixed" popovertargetaction="toggle" type="button" 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="display-tooltip-rich-content">
|
|
11
|
+
<h4 class="display-tooltip-rich-title">Title</h4>
|
|
12
|
+
<p class="display-tooltip-rich-body">Body copy</p><span class="display-tooltip-rich-action">Learn more</span><button type="button" data-testid="input-button" data-theme="default" data-variant="inline" class="input-button display-tooltip-close-button" popovertarget="nuxt-tooltip-fixed" popovertargetaction="hide" aria-label="Close tool tip">
|
|
13
|
+
<!--v-if-->
|
|
14
|
+
<!--v-if--><span class="input-button-text">Close</span>
|
|
15
|
+
<!--v-if-->
|
|
16
|
+
<!--v-if-->
|
|
17
|
+
</button>
|
|
18
|
+
</div>
|
|
19
|
+
</div>
|
|
20
|
+
</div>
|
|
21
|
+
</div>"
|
|
22
|
+
`;
|
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
<div v-else class="navigation-group" :style="`--_anchor-name: --anchor-${uid}-${key};`">
|
|
8
8
|
<button
|
|
9
9
|
:ref="(el) => setElement(triggerEls, key, el)"
|
|
10
|
+
type="button"
|
|
10
11
|
:popovertarget="`popovertarget-${uid}-${key}`"
|
|
11
12
|
class="navigation-group-toggle"
|
|
12
13
|
:aria-expanded="isOpen && activeKey === key"
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
exports[`DeepExpandingMenu > renders correct HTML structure 1`] = `
|
|
4
4
|
"<nav class="deep-expanding-menu">
|
|
5
5
|
<div class="inner"><a href="/" class="navigation-link">Home</a>
|
|
6
|
-
<div class="navigation-group" style="--_anchor-name: --anchor-v-0-0-1;"><button popovertarget="popovertarget-v-0-0-1" class="navigation-group-toggle" aria-expanded="false"><span>Services</span><span class="iconify i-bi:caret-down-fill icon" aria-hidden="true"></span></button>
|
|
6
|
+
<div class="navigation-group" style="--_anchor-name: --anchor-v-0-0-1;"><button type="button" popovertarget="popovertarget-v-0-0-1" class="navigation-group-toggle" aria-expanded="false"><span>Services</span><span class="iconify i-bi:caret-down-fill icon" aria-hidden="true"></span></button>
|
|
7
7
|
<div id="popovertarget-v-0-0-1" class="navigation-group-panel" popover="">
|
|
8
8
|
<h4 class="page-heading-4 mb-6">Our services</h4>
|
|
9
9
|
<ul class="navigation-group-list">
|
|
@@ -2,7 +2,7 @@ import { describe, it, expect, beforeEach, afterEach } from "vitest";
|
|
|
2
2
|
import { mountSuspended } from "@nuxt/test-utils/runtime";
|
|
3
3
|
import { defineComponent, h, nextTick, ref } from "vue";
|
|
4
4
|
import { useTooltipsGuide } from "../useTooltips";
|
|
5
|
-
import
|
|
5
|
+
import DisplayTooltipRich from "~/components/02.molecules/display-tooltip-rich/DisplayTooltipRich.vue";
|
|
6
6
|
|
|
7
7
|
const flush = async () => {
|
|
8
8
|
for (let i = 0; i < 5; i++) await nextTick();
|
|
@@ -18,8 +18,8 @@ const mountGuide = () =>
|
|
|
18
18
|
},
|
|
19
19
|
render() {
|
|
20
20
|
return h("div", { ref: "containerRef" }, [
|
|
21
|
-
h(
|
|
22
|
-
h(
|
|
21
|
+
h(DisplayTooltipRich, { tooltipId: "one", contentText: { tooltipTitle: { tag: "h4", text: "One" } } }),
|
|
22
|
+
h(DisplayTooltipRich, { tooltipId: "two", contentText: { tooltipTitle: { tag: "h4", text: "Two" } } }),
|
|
23
23
|
]);
|
|
24
24
|
},
|
|
25
25
|
}),
|
|
@@ -15,7 +15,7 @@ export * from "./social-icons-list.d"
|
|
|
15
15
|
export * from "./content-docs.d"
|
|
16
16
|
export * from "./breadcrumb"
|
|
17
17
|
export * from "./cookie-consent-banner.d"
|
|
18
|
-
export * from "./display-tooltip-
|
|
18
|
+
export * from "./display-tooltip-rich.d"
|
|
19
19
|
export * from "./marquee-scroller.d"
|
|
20
20
|
export * from "./rotating-carousel-image.d"
|
|
21
21
|
export * from "./skip-links.d"
|
package/package.json
CHANGED
|
@@ -1,29 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"SRCDEV DisplayTooltipDefined Basic": {
|
|
3
|
-
"description": "DisplayTooltipDefined — structured title/body/action tooltip",
|
|
4
|
-
"scope": "vue,html",
|
|
5
|
-
"body": [
|
|
6
|
-
"<DisplayTooltipDefined",
|
|
7
|
-
" :content-text=\"{",
|
|
8
|
-
" tooltipTitle: { tag: 'h4', text: '$1' },",
|
|
9
|
-
" tooltipContent: { tag: 'p', text: '$0' },",
|
|
10
|
-
" }\"",
|
|
11
|
-
"/>"
|
|
12
|
-
]
|
|
13
|
-
},
|
|
14
|
-
"SRCDEV DisplayTooltipDefined Localised Copy": {
|
|
15
|
-
"description": "DisplayTooltipDefined with localised trigger/close-button copy",
|
|
16
|
-
"scope": "vue,html",
|
|
17
|
-
"body": [
|
|
18
|
-
"<DisplayTooltipDefined",
|
|
19
|
-
" :content-text=\"{",
|
|
20
|
-
" tooltipTitle: { tag: 'h4', text: '$1' },",
|
|
21
|
-
" tooltipContent: { tag: 'p', text: '$2' },",
|
|
22
|
-
" }\"",
|
|
23
|
-
" trigger-aria-label=\"${3:Toggle the popover}\"",
|
|
24
|
-
" close-button-text=\"${4:Close}\"",
|
|
25
|
-
" close-button-aria-label=\"${5:Close tool tip}\"",
|
|
26
|
-
"/>"
|
|
27
|
-
]
|
|
28
|
-
}
|
|
29
|
-
}
|
|
@@ -1,53 +0,0 @@
|
|
|
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.
|
|
43
|
-
|
|
44
|
-
---
|
|
45
|
-
|
|
46
|
-
## Local overrides
|
|
47
|
-
|
|
48
|
-
Set the tokens above on an element you own (a page or section class, or a class added with
|
|
49
|
-
`:style-class-passthrough`): they inherit down into the component. Keep the block **unlayered** (no
|
|
50
|
-
`@layer` wrapper) so it beats the library's `@layer components`. If your own file uses
|
|
51
|
-
`<style scoped>`, tokens set on your element still work, but selectors that reach inside the component need
|
|
52
|
-
`:deep()`. Patterns and examples: `.claude/skills/component-local-style-override.md`.
|
|
53
|
-
|