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