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.
- 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/components/hero-text.md +4 -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/.vscode/srcdev-component-hero-text.code-snippets +16 -0
- package/.vscode/srcdev-component-toggle-switch-core.code-snippets +13 -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/01.atoms/text-blocks/hero-text/HeroText.vue +7 -5
- 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/components/05.forms/toggle-switch/CONSUMER-STYLING.md +17 -0
- package/app/components/05.forms/toggle-switch/ToggleSwitchCore.vue +18 -1
- package/app/components/05.forms/toggle-switch/stories/ToggleSwitchCore.stories.ts +17 -2
- 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,105 @@
|
|
|
1
|
+
import ClippedPanel from "../ClippedPanel.vue";
|
|
2
|
+
import type { Meta, StoryObj } from "@nuxtjs/storybook";
|
|
3
|
+
|
|
4
|
+
const meta: Meta<typeof ClippedPanel> = {
|
|
5
|
+
title: "Atoms/ClippedPanel",
|
|
6
|
+
component: ClippedPanel,
|
|
7
|
+
argTypes: {
|
|
8
|
+
tag: {
|
|
9
|
+
control: { type: "select" },
|
|
10
|
+
options: ["div", "p", "span", "section", "article", "aside", "header", "footer", "main", "nav", "ul", "ol"],
|
|
11
|
+
description: "Root element tag",
|
|
12
|
+
table: { category: "Basic" },
|
|
13
|
+
},
|
|
14
|
+
variant: {
|
|
15
|
+
control: { type: "select" },
|
|
16
|
+
options: ["square", "rectangle", "circle-cutout"],
|
|
17
|
+
description: "Which fixed-size notched shape to clip the panel to",
|
|
18
|
+
table: { category: "Basic" },
|
|
19
|
+
},
|
|
20
|
+
styleClassPassthrough: {
|
|
21
|
+
table: { disable: true },
|
|
22
|
+
},
|
|
23
|
+
},
|
|
24
|
+
parameters: {
|
|
25
|
+
docs: {
|
|
26
|
+
description: {
|
|
27
|
+
component:
|
|
28
|
+
"A panel clipped to a fixed-size notched shape via `clip-path: path(...)`. Each variant is a specific pixel-size shape (see CONSUMER-STYLING.md) — colour and outline are the only overridable surface.",
|
|
29
|
+
},
|
|
30
|
+
},
|
|
31
|
+
},
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
export default meta;
|
|
35
|
+
type Story = StoryObj<typeof ClippedPanel>;
|
|
36
|
+
|
|
37
|
+
export const Square: Story = {
|
|
38
|
+
args: {
|
|
39
|
+
tag: "div",
|
|
40
|
+
variant: "square",
|
|
41
|
+
},
|
|
42
|
+
render: (args) => ({
|
|
43
|
+
components: { ClippedPanel },
|
|
44
|
+
setup() {
|
|
45
|
+
return { args };
|
|
46
|
+
},
|
|
47
|
+
template: `<ClippedPanel v-bind="args">Square</ClippedPanel>`,
|
|
48
|
+
}),
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
export const Rectangle: Story = {
|
|
52
|
+
args: {
|
|
53
|
+
tag: "div",
|
|
54
|
+
variant: "rectangle",
|
|
55
|
+
},
|
|
56
|
+
render: (args) => ({
|
|
57
|
+
components: { ClippedPanel },
|
|
58
|
+
setup() {
|
|
59
|
+
return { args };
|
|
60
|
+
},
|
|
61
|
+
template: `<ClippedPanel v-bind="args">Rectangle</ClippedPanel>`,
|
|
62
|
+
}),
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
export const CircleCutout: Story = {
|
|
66
|
+
args: {
|
|
67
|
+
tag: "div",
|
|
68
|
+
variant: "circle-cutout",
|
|
69
|
+
},
|
|
70
|
+
render: (args) => ({
|
|
71
|
+
components: { ClippedPanel },
|
|
72
|
+
setup() {
|
|
73
|
+
return { args };
|
|
74
|
+
},
|
|
75
|
+
template: `<ClippedPanel v-bind="args">Circle cutout</ClippedPanel>`,
|
|
76
|
+
}),
|
|
77
|
+
};
|
|
78
|
+
|
|
79
|
+
export const CustomColours: Story = {
|
|
80
|
+
args: {
|
|
81
|
+
tag: "div",
|
|
82
|
+
variant: "square",
|
|
83
|
+
},
|
|
84
|
+
render: (args) => ({
|
|
85
|
+
components: { ClippedPanel },
|
|
86
|
+
setup() {
|
|
87
|
+
return { args };
|
|
88
|
+
},
|
|
89
|
+
template: `
|
|
90
|
+
<ClippedPanel
|
|
91
|
+
v-bind="args"
|
|
92
|
+
style="--clipped-panel-background-colour: #fef3e7; --clipped-panel-outline-colour: #b5651d; --clipped-panel-outline-width: 2px;"
|
|
93
|
+
>
|
|
94
|
+
Custom colours
|
|
95
|
+
</ClippedPanel>
|
|
96
|
+
`,
|
|
97
|
+
}),
|
|
98
|
+
parameters: {
|
|
99
|
+
docs: {
|
|
100
|
+
description: {
|
|
101
|
+
story: "Overriding the public tokens via inline `--clipped-panel-*` custom properties.",
|
|
102
|
+
},
|
|
103
|
+
},
|
|
104
|
+
},
|
|
105
|
+
};
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import { describe, it, expect } from "vitest";
|
|
2
|
+
import { mountSuspended } from "@nuxt/test-utils/runtime";
|
|
3
|
+
import ClippedPanel from "../ClippedPanel.vue";
|
|
4
|
+
|
|
5
|
+
describe("ClippedPanel", () => {
|
|
6
|
+
it("mounts without error", async () => {
|
|
7
|
+
const wrapper = await mountSuspended(ClippedPanel);
|
|
8
|
+
expect(wrapper.vm).toBeTruthy();
|
|
9
|
+
});
|
|
10
|
+
|
|
11
|
+
it("renders correct HTML structure (default props)", async () => {
|
|
12
|
+
const wrapper = await mountSuspended(ClippedPanel);
|
|
13
|
+
expect(wrapper.html()).toMatchSnapshot();
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
it("renders a <div> by default", async () => {
|
|
17
|
+
const wrapper = await mountSuspended(ClippedPanel);
|
|
18
|
+
expect(wrapper.element.tagName).toBe("DIV");
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
it("renders the given tag", async () => {
|
|
22
|
+
const wrapper = await mountSuspended(ClippedPanel, { props: { tag: "section" } });
|
|
23
|
+
expect(wrapper.element.tagName).toBe("SECTION");
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
it("defaults to the square variant class", async () => {
|
|
27
|
+
const wrapper = await mountSuspended(ClippedPanel);
|
|
28
|
+
expect(wrapper.classes()).toContain("clipped-panel");
|
|
29
|
+
expect(wrapper.classes()).toContain("square");
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
it("applies the rectangle variant class", async () => {
|
|
33
|
+
const wrapper = await mountSuspended(ClippedPanel, { props: { variant: "rectangle" } });
|
|
34
|
+
expect(wrapper.classes()).toContain("rectangle");
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
it("applies the circle-cutout variant class", async () => {
|
|
38
|
+
const wrapper = await mountSuspended(ClippedPanel, { props: { variant: "circle-cutout" } });
|
|
39
|
+
expect(wrapper.classes()).toContain("circle-cutout");
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
it("renders slot content", async () => {
|
|
43
|
+
const wrapper = await mountSuspended(ClippedPanel, {
|
|
44
|
+
slots: { default: "<p class='slot-child'>Hello</p>" },
|
|
45
|
+
});
|
|
46
|
+
expect(wrapper.find(".slot-child").exists()).toBe(true);
|
|
47
|
+
expect(wrapper.find(".slot-child").text()).toBe("Hello");
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
it("applies styleClassPassthrough classes", async () => {
|
|
51
|
+
const wrapper = await mountSuspended(ClippedPanel, {
|
|
52
|
+
props: { styleClassPassthrough: ["custom-class"] },
|
|
53
|
+
});
|
|
54
|
+
expect(wrapper.classes()).toContain("custom-class");
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
it("resets classes when styleClassPassthrough prop changes", async () => {
|
|
58
|
+
const wrapper = await mountSuspended(ClippedPanel, {
|
|
59
|
+
props: { styleClassPassthrough: ["initial-class"] },
|
|
60
|
+
});
|
|
61
|
+
expect(wrapper.classes()).toContain("initial-class");
|
|
62
|
+
|
|
63
|
+
await wrapper.setProps({ styleClassPassthrough: ["updated-class"] });
|
|
64
|
+
expect(wrapper.classes()).not.toContain("initial-class");
|
|
65
|
+
expect(wrapper.classes()).toContain("updated-class");
|
|
66
|
+
});
|
|
67
|
+
});
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
# DisplayBanner — Consumer Styling Guide
|
|
2
|
+
|
|
3
|
+
## Public token API
|
|
4
|
+
|
|
5
|
+
| Token | Default | Controls |
|
|
6
|
+
|---|---|---|
|
|
7
|
+
| `--display-banner-min-height` | `auto` | Minimum height of the banner |
|
|
8
|
+
|
|
9
|
+
```css
|
|
10
|
+
.my-page {
|
|
11
|
+
--display-banner-min-height: 40rem;
|
|
12
|
+
}
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
Or scope to a single instance via `styleClassPassthrough`:
|
|
16
|
+
|
|
17
|
+
```vue
|
|
18
|
+
<DisplayBanner style-class-passthrough="hero-banner">...</DisplayBanner>
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
```css
|
|
22
|
+
.hero-banner {
|
|
23
|
+
--display-banner-min-height: 40rem;
|
|
24
|
+
}
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
## Layout
|
|
28
|
+
|
|
29
|
+
`DisplayBanner` stacks its `canvas` and `content` slots on top of each other in a single grid
|
|
30
|
+
area (`grid-template-areas: "banner"`). Whichever slot is rendered later in the DOM sits visually
|
|
31
|
+
on top — put background media (an image, video, `CanvasSwitcher`-style visual) in `canvas` and
|
|
32
|
+
overlaid text/CTAs in `content`, since `content` follows `canvas` in the template.
|
|
33
|
+
|
|
34
|
+
Both slots are conditionally rendered (`v-if="$slots.canvas"` / `v-if="$slots.content"`), so an
|
|
35
|
+
unused slot contributes no empty wrapper `<div>` to the DOM.
|
|
@@ -10,41 +10,24 @@
|
|
|
10
10
|
</template>
|
|
11
11
|
|
|
12
12
|
<script setup lang="ts">
|
|
13
|
-
|
|
14
|
-
tag
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
"section",
|
|
23
|
-
"article",
|
|
24
|
-
"aside",
|
|
25
|
-
"header",
|
|
26
|
-
"footer",
|
|
27
|
-
"main",
|
|
28
|
-
"nav",
|
|
29
|
-
"ul",
|
|
30
|
-
"ol",
|
|
31
|
-
].includes(value)
|
|
32
|
-
},
|
|
33
|
-
},
|
|
34
|
-
styleClassPassthrough: {
|
|
35
|
-
type: [String, Array] as PropType<string | string[]>,
|
|
36
|
-
default: () => [],
|
|
37
|
-
},
|
|
38
|
-
})
|
|
13
|
+
interface Props {
|
|
14
|
+
tag?: "div" | "p" | "span" | "section" | "article" | "aside" | "header" | "footer" | "main" | "nav" | "ul" | "ol";
|
|
15
|
+
styleClassPassthrough?: string | string[];
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const props = withDefaults(defineProps<Props>(), {
|
|
19
|
+
tag: "div",
|
|
20
|
+
styleClassPassthrough: () => [],
|
|
21
|
+
});
|
|
39
22
|
|
|
40
|
-
const { elementClasses, resetElementClasses } = useStyleClassPassthrough(props.styleClassPassthrough)
|
|
23
|
+
const { elementClasses, resetElementClasses } = useStyleClassPassthrough(props.styleClassPassthrough);
|
|
41
24
|
|
|
42
25
|
watch(
|
|
43
26
|
() => props.styleClassPassthrough,
|
|
44
27
|
() => {
|
|
45
|
-
resetElementClasses(props.styleClassPassthrough)
|
|
28
|
+
resetElementClasses(props.styleClassPassthrough);
|
|
46
29
|
}
|
|
47
|
-
)
|
|
30
|
+
);
|
|
48
31
|
</script>
|
|
49
32
|
|
|
50
33
|
<style lang="css">
|
|
@@ -53,16 +36,11 @@ watch(
|
|
|
53
36
|
display: grid;
|
|
54
37
|
grid-template-areas: "banner";
|
|
55
38
|
container-type: inline-size;
|
|
39
|
+
min-height: var(--display-banner-min-height, auto);
|
|
56
40
|
overflow: hidden;
|
|
57
41
|
|
|
58
42
|
.canvas {
|
|
59
43
|
grid-area: banner;
|
|
60
|
-
|
|
61
|
-
.image {
|
|
62
|
-
object-fit: cover;
|
|
63
|
-
width: 100%;
|
|
64
|
-
height: 100%;
|
|
65
|
-
}
|
|
66
44
|
}
|
|
67
45
|
|
|
68
46
|
.content {
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
import DisplayBanner from "../DisplayBanner.vue";
|
|
2
|
+
import type { Meta, StoryObj } from "@nuxtjs/storybook";
|
|
3
|
+
|
|
4
|
+
const meta: Meta<typeof DisplayBanner> = {
|
|
5
|
+
title: "Atoms/DisplayBanner",
|
|
6
|
+
component: DisplayBanner,
|
|
7
|
+
argTypes: {
|
|
8
|
+
tag: {
|
|
9
|
+
control: { type: "select" },
|
|
10
|
+
options: ["div", "p", "span", "section", "article", "aside", "header", "footer", "main", "nav", "ul", "ol"],
|
|
11
|
+
description: "Root element tag",
|
|
12
|
+
table: { category: "Basic" },
|
|
13
|
+
},
|
|
14
|
+
styleClassPassthrough: {
|
|
15
|
+
table: { disable: true },
|
|
16
|
+
},
|
|
17
|
+
},
|
|
18
|
+
parameters: {
|
|
19
|
+
docs: {
|
|
20
|
+
description: {
|
|
21
|
+
component:
|
|
22
|
+
"Stacks a `canvas` slot (background media) and a `content` slot (overlaid text/CTAs) in a single grid area. Either slot may be omitted.",
|
|
23
|
+
},
|
|
24
|
+
},
|
|
25
|
+
},
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
export default meta;
|
|
29
|
+
type Story = StoryObj<typeof DisplayBanner>;
|
|
30
|
+
|
|
31
|
+
export const Default: Story = {
|
|
32
|
+
args: {
|
|
33
|
+
tag: "div",
|
|
34
|
+
},
|
|
35
|
+
render: (args) => ({
|
|
36
|
+
components: { DisplayBanner },
|
|
37
|
+
setup() {
|
|
38
|
+
return { args };
|
|
39
|
+
},
|
|
40
|
+
template: `
|
|
41
|
+
<DisplayBanner v-bind="args" style="--display-banner-min-height: 24rem;">
|
|
42
|
+
<template #canvas>
|
|
43
|
+
<div style="width: 100%; height: 100%; background: linear-gradient(135deg, #5b8def, #8f5be9);"></div>
|
|
44
|
+
</template>
|
|
45
|
+
<template #content>
|
|
46
|
+
<div style="display: grid; place-items: center; height: 100%; color: white;">
|
|
47
|
+
<h2 style="margin: 0;">Banner content</h2>
|
|
48
|
+
</div>
|
|
49
|
+
</template>
|
|
50
|
+
</DisplayBanner>
|
|
51
|
+
`,
|
|
52
|
+
}),
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
export const CanvasOnly: Story = {
|
|
56
|
+
args: {
|
|
57
|
+
tag: "div",
|
|
58
|
+
},
|
|
59
|
+
render: (args) => ({
|
|
60
|
+
components: { DisplayBanner },
|
|
61
|
+
setup() {
|
|
62
|
+
return { args };
|
|
63
|
+
},
|
|
64
|
+
template: `
|
|
65
|
+
<DisplayBanner v-bind="args" style="--display-banner-min-height: 16rem;">
|
|
66
|
+
<template #canvas>
|
|
67
|
+
<div style="width: 100%; height: 100%; background: linear-gradient(135deg, #5b8def, #8f5be9);"></div>
|
|
68
|
+
</template>
|
|
69
|
+
</DisplayBanner>
|
|
70
|
+
`,
|
|
71
|
+
}),
|
|
72
|
+
parameters: {
|
|
73
|
+
docs: {
|
|
74
|
+
description: {
|
|
75
|
+
story: "With no `content` slot, no empty `.content` wrapper is rendered.",
|
|
76
|
+
},
|
|
77
|
+
},
|
|
78
|
+
},
|
|
79
|
+
};
|
|
80
|
+
|
|
81
|
+
export const ContentOnly: Story = {
|
|
82
|
+
args: {
|
|
83
|
+
tag: "div",
|
|
84
|
+
},
|
|
85
|
+
render: (args) => ({
|
|
86
|
+
components: { DisplayBanner },
|
|
87
|
+
setup() {
|
|
88
|
+
return { args };
|
|
89
|
+
},
|
|
90
|
+
template: `
|
|
91
|
+
<DisplayBanner v-bind="args" style="--display-banner-min-height: 16rem; background: #1a1a1a;">
|
|
92
|
+
<template #content>
|
|
93
|
+
<div style="display: grid; place-items: center; height: 100%; color: white;">
|
|
94
|
+
<h2 style="margin: 0;">Content, no canvas</h2>
|
|
95
|
+
</div>
|
|
96
|
+
</template>
|
|
97
|
+
</DisplayBanner>
|
|
98
|
+
`,
|
|
99
|
+
}),
|
|
100
|
+
};
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import { describe, it, expect } from "vitest";
|
|
2
|
+
import { mountSuspended } from "@nuxt/test-utils/runtime";
|
|
3
|
+
import DisplayBanner from "../DisplayBanner.vue";
|
|
4
|
+
|
|
5
|
+
describe("DisplayBanner", () => {
|
|
6
|
+
it("mounts without error", async () => {
|
|
7
|
+
const wrapper = await mountSuspended(DisplayBanner);
|
|
8
|
+
expect(wrapper.vm).toBeTruthy();
|
|
9
|
+
});
|
|
10
|
+
|
|
11
|
+
it("renders correct HTML structure (default props)", async () => {
|
|
12
|
+
const wrapper = await mountSuspended(DisplayBanner);
|
|
13
|
+
expect(wrapper.html()).toMatchSnapshot();
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
it("renders a <div> by default", async () => {
|
|
17
|
+
const wrapper = await mountSuspended(DisplayBanner);
|
|
18
|
+
expect(wrapper.element.tagName).toBe("DIV");
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
it("renders the given tag", async () => {
|
|
22
|
+
const wrapper = await mountSuspended(DisplayBanner, { props: { tag: "section" } });
|
|
23
|
+
expect(wrapper.element.tagName).toBe("SECTION");
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
it("does not render a .canvas wrapper when the canvas slot is empty", async () => {
|
|
27
|
+
const wrapper = await mountSuspended(DisplayBanner);
|
|
28
|
+
expect(wrapper.find(".canvas").exists()).toBe(false);
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
it("does not render a .content wrapper when the content slot is empty", async () => {
|
|
32
|
+
const wrapper = await mountSuspended(DisplayBanner);
|
|
33
|
+
expect(wrapper.find(".content").exists()).toBe(false);
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
it("renders the canvas slot", async () => {
|
|
37
|
+
const wrapper = await mountSuspended(DisplayBanner, {
|
|
38
|
+
slots: { canvas: "<img class='slot-canvas' />" },
|
|
39
|
+
});
|
|
40
|
+
expect(wrapper.find(".canvas .slot-canvas").exists()).toBe(true);
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
it("renders the content slot", async () => {
|
|
44
|
+
const wrapper = await mountSuspended(DisplayBanner, {
|
|
45
|
+
slots: { content: "<p class='slot-content'>Hello</p>" },
|
|
46
|
+
});
|
|
47
|
+
expect(wrapper.find(".content .slot-content").text()).toBe("Hello");
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
it("renders both slots together", async () => {
|
|
51
|
+
const wrapper = await mountSuspended(DisplayBanner, {
|
|
52
|
+
slots: {
|
|
53
|
+
canvas: "<img class='slot-canvas' />",
|
|
54
|
+
content: "<p class='slot-content'>Hello</p>",
|
|
55
|
+
},
|
|
56
|
+
});
|
|
57
|
+
expect(wrapper.find(".canvas .slot-canvas").exists()).toBe(true);
|
|
58
|
+
expect(wrapper.find(".content .slot-content").exists()).toBe(true);
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
it("applies styleClassPassthrough classes", async () => {
|
|
62
|
+
const wrapper = await mountSuspended(DisplayBanner, {
|
|
63
|
+
props: { styleClassPassthrough: ["custom-class"] },
|
|
64
|
+
});
|
|
65
|
+
expect(wrapper.classes()).toContain("custom-class");
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
it("resets classes when styleClassPassthrough prop changes", async () => {
|
|
69
|
+
const wrapper = await mountSuspended(DisplayBanner, {
|
|
70
|
+
props: { styleClassPassthrough: ["initial-class"] },
|
|
71
|
+
});
|
|
72
|
+
expect(wrapper.classes()).toContain("initial-class");
|
|
73
|
+
|
|
74
|
+
await wrapper.setProps({ styleClassPassthrough: ["updated-class"] });
|
|
75
|
+
expect(wrapper.classes()).not.toContain("initial-class");
|
|
76
|
+
expect(wrapper.classes()).toContain("updated-class");
|
|
77
|
+
});
|
|
78
|
+
});
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
# DisplayTooltip — Consumer Styling Guide
|
|
2
|
+
|
|
3
|
+
## Public token API
|
|
4
|
+
|
|
5
|
+
| Token | Default | Controls |
|
|
6
|
+
|---|---|---|
|
|
7
|
+
| `--display-tooltip-padding-block` | `0.8rem` | Vertical padding around the trigger row |
|
|
8
|
+
| `--display-tooltip-trigger-gap` | `0.8rem` | Space between `triggerContent` slot and the trigger button |
|
|
9
|
+
| `--display-tooltip-trigger-icon-box-size` | `2rem` | Trigger icon's box width/height |
|
|
10
|
+
| `--display-tooltip-trigger-icon-font-size` | `1.8rem` | Trigger icon glyph size |
|
|
11
|
+
| `--display-tooltip-trigger-icon-colour` | `var(--theme-text)` | Trigger icon colour |
|
|
12
|
+
| `--display-tooltip-trigger-outline-width` | `0.1rem` | Trigger button focus/hover outline width |
|
|
13
|
+
| `--display-tooltip-trigger-outline-colour-hover` | `var(--theme-ring)` | Trigger button outline colour on hover/focus-visible |
|
|
14
|
+
| `--display-tooltip-popover-width` | `30rem` | Popover panel width |
|
|
15
|
+
| `--display-tooltip-popover-padding` | `1.2rem` | Popover panel content padding |
|
|
16
|
+
| `--display-tooltip-popover-content-gap` | `1.2rem` | Gap between stacked children in the `tooltipContent` slot |
|
|
17
|
+
| `--display-tooltip-popover-outline-width` | `0.1rem` | Popover panel outline (border) width |
|
|
18
|
+
| `--display-tooltip-popover-outline-colour` | `light-dark(var(--slate-02), var(--slate-06))` | Popover panel outline (border) colour |
|
|
19
|
+
| `--display-tooltip-popover-text-colour` | `light-dark(var(--slate-09), var(--slate-01))` | Popover panel text colour |
|
|
20
|
+
| `--display-tooltip-popover-background-colour` | `light-dark(var(--slate-00), var(--slate-07))` | Popover panel background |
|
|
21
|
+
| `--display-tooltip-popover-border-radius` | `0.8rem` | Popover panel corner radius |
|
|
22
|
+
| `--display-tooltip-popover-shadow` | `light-dark(0 0.4rem 1.6rem rgba(0,0,0,.12), 0 0.4rem 1.6rem rgba(0,0,0,.5))` | Popover panel elevation shadow |
|
|
23
|
+
| `--display-tooltip-popover-offset` | `0.1rem` | Gap between the trigger and the popover panel |
|
|
24
|
+
| `--display-tooltip-close-button-colour` | `light-dark(var(--slate-09), var(--slate-01))` | Close button text colour (used by `DisplayTooltipDefined`) |
|
|
25
|
+
| `--display-tooltip-close-button-border-width` | `0.1rem` | Close button border width |
|
|
26
|
+
| `--display-tooltip-close-button-border-colour` | `light-dark(var(--slate-03), var(--slate-06))` | Close button border colour |
|
|
27
|
+
| `--display-tooltip-close-button-outline-width` | `0.1rem` | Close button outline width |
|
|
28
|
+
| `--display-tooltip-close-button-outline-colour` | `transparent` | Close button outline colour at rest (no permanent ring) |
|
|
29
|
+
| `--display-tooltip-close-button-border-colour-hover` | `light-dark(var(--slate-06), var(--slate-03))` | Close button border colour on hover/focus |
|
|
30
|
+
| `--display-tooltip-close-button-outline-colour-hover` | `light-dark(var(--slate-06), var(--slate-03))` | Close button outline colour on hover/focus |
|
|
31
|
+
| `--display-tooltip-close-button-padding` | `0.4rem 1rem` | Close button padding |
|
|
32
|
+
|
|
33
|
+
```css
|
|
34
|
+
.my-page {
|
|
35
|
+
--display-tooltip-popover-background-colour: #1a1a1a;
|
|
36
|
+
--display-tooltip-popover-text-colour: white;
|
|
37
|
+
--display-tooltip-popover-outline-colour: transparent;
|
|
38
|
+
}
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
Or scope to a single instance via `styleClassPassthrough`:
|
|
42
|
+
|
|
43
|
+
```vue
|
|
44
|
+
<DisplayTooltip style-class-passthrough="promo-tooltip">...</DisplayTooltip>
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
## Notes
|
|
48
|
+
|
|
49
|
+
- Built on the native Popover API (`popover`/`popovertarget`) and CSS anchor-positioning
|
|
50
|
+
(`anchor-name`/`position-anchor`/`anchor()`) — check current browser support before relying on
|
|
51
|
+
it where broad support is a hard requirement.
|
|
52
|
+
- The `.display-tooltip-close-button` styled here is rendered by `DisplayTooltipDefined`, not this
|
|
53
|
+
component directly — `DisplayTooltip` only provides the `tooltipContent` slot, so a plain
|
|
54
|
+
`DisplayTooltip` usage supplies its own close affordance if it wants one (give it
|
|
55
|
+
`style="align-self: flex-end;"` or a class to right-align it within the content's flex column,
|
|
56
|
+
same as `DisplayTooltipDefined` does internally).
|
|
57
|
+
- 2026-09-07: default colours switched from raw `light-dark(black, white)` to the library's
|
|
58
|
+
neutral `--slate-*` scale, and `.display-tooltip-popover-content` gained default `padding`,
|
|
59
|
+
`display: flex; flex-direction: column;`, and `gap` (previously unstyled, meaning content sat
|
|
60
|
+
flush against the panel edges) plus the popover gained a default elevation `box-shadow`
|
|
61
|
+
(previously none) — the out-of-the-box look was flat and under-styled before this. All of these
|
|
62
|
+
remain overridable via the tokens above.
|