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,61 @@
|
|
|
1
|
+
# CanvasSwitcher Component
|
|
2
|
+
|
|
3
|
+
## Overview
|
|
4
|
+
|
|
5
|
+
`CanvasSwitcher` renders a row of icon buttons (mobile / tablet / laptop / desktop / full width)
|
|
6
|
+
for switching a bound `MediaCanvas` value — typically used to drive a preview/demo wrapper's width
|
|
7
|
+
while working on a responsive component in Storybook. Each button is an `InputButtonCore`
|
|
8
|
+
(`variant="tertiary"`, icon-only), giving it the library's standard button focus ring and hover
|
|
9
|
+
state for free instead of a raw `<button>`.
|
|
10
|
+
|
|
11
|
+
## Model
|
|
12
|
+
|
|
13
|
+
| Model | Type | Required |
|
|
14
|
+
|---|---|---|
|
|
15
|
+
| `canvasName` (`v-model:canvas-name`) | `MediaCanvas` | no |
|
|
16
|
+
|
|
17
|
+
`MediaCanvas` is exported from `~/types/components/canvas-switcher` (and re-exported from
|
|
18
|
+
`~/types/components`):
|
|
19
|
+
|
|
20
|
+
```typescript
|
|
21
|
+
type MediaCanvas = "mobileCanvas" | "tabletCanvas" | "laptopCanvas" | "desktopCanvas" | "fullWidthCanvas";
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
## Props
|
|
25
|
+
|
|
26
|
+
| Prop | Type | Default | Required |
|
|
27
|
+
|------|------|---------|----------|
|
|
28
|
+
| `styleClassPassthrough` | `string \| string[]` | `[]` | no |
|
|
29
|
+
|
|
30
|
+
## Usage
|
|
31
|
+
|
|
32
|
+
```vue
|
|
33
|
+
<script setup lang="ts">
|
|
34
|
+
import type { MediaCanvas } from "~/types/components";
|
|
35
|
+
|
|
36
|
+
const canvasName = ref<MediaCanvas>("desktopCanvas");
|
|
37
|
+
</script>
|
|
38
|
+
|
|
39
|
+
<template>
|
|
40
|
+
<CanvasSwitcher v-model:canvas-name="canvasName" />
|
|
41
|
+
<div :class="canvasName">
|
|
42
|
+
<!-- preview content constrained to the selected canvas width -->
|
|
43
|
+
</div>
|
|
44
|
+
</template>
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
The selected value doubles as a utility class name: `CanvasSwitcher`'s stylesheet also declares
|
|
48
|
+
`.mobileCanvas`/`.tabletCanvas`/`.laptopCanvas`/`.desktopCanvas`/`.fullWidthCanvas` (max-width
|
|
49
|
+
utilities, not scoped to `.canvas-switcher`), so binding `:class="canvasName"` directly onto a
|
|
50
|
+
preview wrapper constrains it to match — see `LayoutGridA`/`LayoutGridB` stories for the pattern.
|
|
51
|
+
These classes only ship in the bundle once `CanvasSwitcher` (or another importer of them) is
|
|
52
|
+
actually imported somewhere, since Nuxt only bundles a component's SFC styles when it's used.
|
|
53
|
+
|
|
54
|
+
## Notes
|
|
55
|
+
|
|
56
|
+
- Component is auto-imported in Nuxt — no import needed.
|
|
57
|
+
- No accessible-name prop is needed on the buttons — each one's `button-text` ("Mobile", "Tablet",
|
|
58
|
+
etc.) is rendered screen-reader-only by `InputButtonCore`'s icon-only handling, and the selected
|
|
59
|
+
button is marked via `aria-pressed="true"` rather than a private CSS-only "current" class.
|
|
60
|
+
- See `CONSUMER-STYLING.md` in the component's own folder for the full `--canvas-switcher-*` token
|
|
61
|
+
API. Button-level tokens (surface/hover/focus) belong to `InputButtonCore`, not this component.
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: ClipElement
|
|
3
|
+
description: ClipElement scroll-driven clip-path reveal — props, slot API, tokens, when to use vs ScrollRevealFrame
|
|
4
|
+
type: reference
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# ClipElement
|
|
8
|
+
|
|
9
|
+
## Overview
|
|
10
|
+
|
|
11
|
+
`ClipElement` clips its slot content in from the top edge as it scrolls into the viewport, using a
|
|
12
|
+
`window` scroll listener to drive a `clip-path: inset(...)` value on the content. As the element's
|
|
13
|
+
top position moves through the range `[0, maxClip]`, the clip inset shrinks from `maxClip` to `0`;
|
|
14
|
+
once the element scrolls above the viewport (`top < 0`), the inset grows again past `maxClip`,
|
|
15
|
+
re-clipping the content as it exits.
|
|
16
|
+
|
|
17
|
+
This is a JS/scroll-listener implementation, unlike `ScrollRevealFrame`/`ScrollRevealImage` which
|
|
18
|
+
use CSS Scroll-driven Animations with no listeners. Prefer `ScrollRevealFrame` for a general
|
|
19
|
+
parallax pan effect with no JS overhead — reach for `ClipElement` specifically when you want this
|
|
20
|
+
top-edge clip-in/clip-out behaviour rather than a pan.
|
|
21
|
+
|
|
22
|
+
## Props
|
|
23
|
+
|
|
24
|
+
| Prop | Type | Default | Description |
|
|
25
|
+
|------|------|---------|-------------|
|
|
26
|
+
| `maxClip` | `number` | `100` | Scroll distance (px) over which the element clips in as it enters the viewport, and clips further out as it scrolls above it. |
|
|
27
|
+
| `styleClassPassthrough` | `string \| string[]` | `[]` | Extra classes applied to the root `.clip-element-wrapper`. |
|
|
28
|
+
|
|
29
|
+
## Slots
|
|
30
|
+
|
|
31
|
+
| Slot | Description |
|
|
32
|
+
|------|-------------|
|
|
33
|
+
| `default` | Content to clip. Rendered inside `.clipped-element`, which receives the computed `clip-path`. |
|
|
34
|
+
|
|
35
|
+
## Basic usage
|
|
36
|
+
|
|
37
|
+
```vue
|
|
38
|
+
<ClipElement :max-clip="150">
|
|
39
|
+
<img src="/images/hero.jpg" alt="Hero" style="width: 100%; display: block;" />
|
|
40
|
+
</ClipElement>
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
## How it works
|
|
44
|
+
|
|
45
|
+
- A `scroll` listener (added on `onMounted`, client-only, `{ passive: true }`) reads
|
|
46
|
+
`.clipped-element`'s `getBoundingClientRect().top` on every scroll event.
|
|
47
|
+
- `topPosition` between `0` and `maxClip`: clip inset = `maxClip - topPosition` (shrinks toward 0
|
|
48
|
+
as the element scrolls up into place).
|
|
49
|
+
- `topPosition < 0` (element scrolled above the viewport): clip inset = `maxClip + abs(topPosition)`
|
|
50
|
+
(grows past `maxClip`, re-clipping as it exits upward).
|
|
51
|
+
- `topPosition > maxClip` (element still below the visible clip range): clip inset stays `0`.
|
|
52
|
+
- The computed inset is written to a private `--_clip-path` custom property via inline `:style`,
|
|
53
|
+
consumed by `.clipped-element { clip-path: var(--_clip-path); }`.
|
|
54
|
+
|
|
55
|
+
## CSS custom properties
|
|
56
|
+
|
|
57
|
+
`--_clip-path` is private and JS-driven — there is no public token API for this component. See
|
|
58
|
+
`CONSUMER-STYLING.md` for the class-passthrough override surface.
|
|
59
|
+
|
|
60
|
+
## Notes
|
|
61
|
+
|
|
62
|
+
- `.clip-element-wrapper` has `overflow: hidden` — content that needs to escape (dropdowns,
|
|
63
|
+
tooltips) must be portalled outside.
|
|
64
|
+
- No `IntersectionObserver`; the scroll listener runs on every scroll event for the page's
|
|
65
|
+
lifetime while the component is mounted. Avoid mounting many instances on one long page.
|
|
66
|
+
- Client-only: the initial clip check and the listener are both gated behind `import.meta.client`,
|
|
67
|
+
so SSR renders the content unclipped until hydration.
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: ClippedPanel
|
|
3
|
+
description: ClippedPanel fixed-size notched/cutout shape panel — props, variants, CSS tokens
|
|
4
|
+
type: reference
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# ClippedPanel
|
|
8
|
+
|
|
9
|
+
## Overview
|
|
10
|
+
|
|
11
|
+
`ClippedPanel` renders its slot content inside a panel clipped to one of three fixed-size
|
|
12
|
+
notched shapes via `clip-path: path(...)`.
|
|
13
|
+
|
|
14
|
+
## Props
|
|
15
|
+
|
|
16
|
+
| Prop | Type | Default | Description |
|
|
17
|
+
|------|------|---------|-------------|
|
|
18
|
+
| `tag` | `"div" \| "p" \| "span" \| "section" \| "article" \| "aside" \| "header" \| "footer" \| "main" \| "nav" \| "ul" \| "ol"` | `"div"` | Root element tag. |
|
|
19
|
+
| `variant` | `"square" \| "rectangle" \| "circle-cutout"` | `"square"` | Which fixed-size shape to clip to. |
|
|
20
|
+
| `styleClassPassthrough` | `string \| string[]` | `[]` | Extra classes applied to the root element. |
|
|
21
|
+
|
|
22
|
+
## Variants
|
|
23
|
+
|
|
24
|
+
| Variant | Fixed size | Shape |
|
|
25
|
+
|---|---|---|
|
|
26
|
+
| `square` | 200×200px | Rounded square with a small pull-tab notch cut into the top edge. |
|
|
27
|
+
| `rectangle` | 300×200px | Same pull-tab notch shape, wider. |
|
|
28
|
+
| `circle-cutout` | 200×200px | Rounded square with a 50px-radius circular notch bitten out of the top-right corner. |
|
|
29
|
+
|
|
30
|
+
Each shape's `clip-path: path(...)` uses literal pixel coordinates matched to its fixed width —
|
|
31
|
+
see CONSUMER-STYLING.md for why these can't be resized via CSS.
|
|
32
|
+
|
|
33
|
+
## Basic usage
|
|
34
|
+
|
|
35
|
+
```vue
|
|
36
|
+
<ClippedPanel variant="square">
|
|
37
|
+
<p>Panel content</p>
|
|
38
|
+
</ClippedPanel>
|
|
39
|
+
|
|
40
|
+
<ClippedPanel variant="circle-cutout" tag="section">
|
|
41
|
+
<p>Panel content</p>
|
|
42
|
+
</ClippedPanel>
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
## CSS custom properties
|
|
46
|
+
|
|
47
|
+
| Property | Default | Controls |
|
|
48
|
+
|----------|---------|----------|
|
|
49
|
+
| `--clipped-panel-background-colour` | `light-dark(hsl(0, 0%, 96%), hsl(0, 0%, 12%))` | Panel background |
|
|
50
|
+
| `--clipped-panel-outline-colour` | `light-dark(hsl(0, 29%, 3%), hsl(0, 0%, 92%))` | Outline colour and text/icon colour |
|
|
51
|
+
| `--clipped-panel-outline-width` | `1px` | Outline width |
|
|
52
|
+
|
|
53
|
+
## Notes
|
|
54
|
+
|
|
55
|
+
- `aspect-ratio: 1` is set on the shared `.clipped-panel` base class, so height tracks whatever
|
|
56
|
+
`width` the active variant sets.
|
|
57
|
+
- Shape geometry (corner radii, notch size, overall dimensions) is fixed per variant — CSS custom
|
|
58
|
+
properties can't be interpolated into a `path()` string. A new size/shape needs a new variant
|
|
59
|
+
with its own `path()`, not a token override.
|
|
60
|
+
- 2026-09-07 migration: the previous `circle-cutout` variant shipped with an invalid, empty
|
|
61
|
+
`clip-path: path("M Z")` that rendered no clip at all. It now has a real shape (described
|
|
62
|
+
above). The unused `--_max-x-position`/`--_curve-radius` custom properties on `rectangle` were
|
|
63
|
+
also removed — they were declared but never actually referenced by the hardcoded path string,
|
|
64
|
+
so they did nothing.
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: DeepExpandingMenuClassic
|
|
3
|
+
description: DeepExpandingMenuClassic <details>-based top-level nav with click-outside close, CSS token API
|
|
4
|
+
type: reference
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# DeepExpandingMenuClassic
|
|
8
|
+
|
|
9
|
+
## Overview
|
|
10
|
+
|
|
11
|
+
`<details>`/`<summary>`-based fallback for [`DeepExpandingMenu`](deep-expanding-menu.md) — same
|
|
12
|
+
props, same `ResponsiveHeaderNavItem` data shape, same visual result, but built without CSS
|
|
13
|
+
anchor-positioning or the Popover API. Each nav item with `childLinks` renders as a native
|
|
14
|
+
`<details>` element; opening one and clicking outside it closes it again via `@vueuse/core`'s
|
|
15
|
+
`onClickOutside`.
|
|
16
|
+
|
|
17
|
+
Prefer `DeepExpandingMenu` unless the modern implementation's browser support isn't acceptable for
|
|
18
|
+
your target audience — see its own docs for the caveat.
|
|
19
|
+
|
|
20
|
+
## Props
|
|
21
|
+
|
|
22
|
+
| Prop | Type | Default | Description |
|
|
23
|
+
|------|------|---------|-------------|
|
|
24
|
+
| `tag` | `"div" \| "section" \| "nav" \| "ul" \| "ol"` | `"nav"` | Root element tag. |
|
|
25
|
+
| `navLinks` | `ResponsiveHeaderNavItem[]` | `[]` | Top-level nav items. An item with `path` renders as a direct link; an item with `childLinks` renders as a `<details>` group. |
|
|
26
|
+
| `styleClassPassthrough` | `string \| string[]` | `[]` | Extra classes applied to the root element. |
|
|
27
|
+
|
|
28
|
+
`ResponsiveHeaderNavItem` is exported from `~/types/components` (shared with `ResponsiveHeader`).
|
|
29
|
+
|
|
30
|
+
## Basic usage
|
|
31
|
+
|
|
32
|
+
```vue
|
|
33
|
+
<script setup lang="ts">
|
|
34
|
+
import type { ResponsiveHeaderNavItem } from "~/types/components";
|
|
35
|
+
|
|
36
|
+
const navLinks: ResponsiveHeaderNavItem[] = [
|
|
37
|
+
{ name: "Home", path: "/" },
|
|
38
|
+
{
|
|
39
|
+
name: "Services",
|
|
40
|
+
childLinksTitle: "Our services",
|
|
41
|
+
childLinks: [
|
|
42
|
+
{ name: "Haircuts", path: "/services/haircuts" },
|
|
43
|
+
{ name: "Colouring", path: "/services/colouring" },
|
|
44
|
+
],
|
|
45
|
+
},
|
|
46
|
+
];
|
|
47
|
+
</script>
|
|
48
|
+
|
|
49
|
+
<template>
|
|
50
|
+
<DeepExpandingMenuClassic :nav-links="navLinks" />
|
|
51
|
+
</template>
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
## CSS custom properties
|
|
55
|
+
|
|
56
|
+
| Property | Default | Controls |
|
|
57
|
+
|----------|---------|----------|
|
|
58
|
+
| `--deep-expanding-menu-classic-gap` | `1.2rem` | Gap on the root grid |
|
|
59
|
+
| `--deep-expanding-menu-classic-item-gap` | `2.4rem` | Gap between top-level items |
|
|
60
|
+
| `--deep-expanding-menu-classic-link-border-width` | `0.2rem` | Bottom border width on links/toggles |
|
|
61
|
+
| `--deep-expanding-menu-classic-link-padding-block` | `0.8rem` | Vertical padding on links/toggles |
|
|
62
|
+
| `--deep-expanding-menu-classic-link-border-colour-hover` | `light-dark(var(--blue-10), var(--slate-00))` | Bottom border colour on hover/focus |
|
|
63
|
+
| `--deep-expanding-menu-classic-icon-size` | `1.2rem` | Caret icon size |
|
|
64
|
+
| `--deep-expanding-menu-classic-panel-offset-top` | `4rem` | Panel offset from the toggle |
|
|
65
|
+
| `--deep-expanding-menu-classic-panel-width` | `20rem` | Panel width below `768px` |
|
|
66
|
+
| `--deep-expanding-menu-classic-panel-width-tablet` | `40rem` | Panel width from `768px` |
|
|
67
|
+
| `--deep-expanding-menu-classic-panel-width-desktop` | `60rem` | Panel width from `1024px` |
|
|
68
|
+
| `--deep-expanding-menu-classic-panel-background-colour` | `white` | Panel background |
|
|
69
|
+
| `--deep-expanding-menu-classic-panel-border-width` | `0.1rem` | Panel border width |
|
|
70
|
+
| `--deep-expanding-menu-classic-panel-border-colour` | `black` | Panel border colour |
|
|
71
|
+
| `--deep-expanding-menu-classic-panel-border-radius` | `1.2rem` | Panel corner radius |
|
|
72
|
+
| `--deep-expanding-menu-classic-panel-shadow` | `0 0 1rem rgba(0, 0, 0, 0.1)` | Panel box-shadow |
|
|
73
|
+
| `--deep-expanding-menu-classic-panel-padding` | `1.2rem` | Panel padding |
|
|
74
|
+
| `--deep-expanding-menu-classic-panel-heading-colour` | `var(--slate-10)` | `childLinksTitle` heading colour |
|
|
75
|
+
| `--deep-expanding-menu-classic-panel-list-gap` | `1.2rem` | Gap between child link grid items |
|
|
76
|
+
| `--deep-expanding-menu-classic-group-link-colour` | `var(--slate-10)` | Child link text colour |
|
|
77
|
+
| `--deep-expanding-menu-classic-group-link-border-colour-hover` | `var(--slate-10)` | Child link bottom border colour on hover/focus |
|
|
78
|
+
|
|
79
|
+
See `CONSUMER-STYLING.md` for the full token API.
|
|
80
|
+
|
|
81
|
+
## Notes
|
|
82
|
+
|
|
83
|
+
- Auto-imported in Nuxt — no manual import needed.
|
|
84
|
+
- Multiple `<details>` groups sharing `name="navigation-group"` means opening one does **not**
|
|
85
|
+
auto-close siblings (unlike a native accordion) — each group closes independently via
|
|
86
|
+
click-outside.
|
|
87
|
+
- 2026-09-07 migration: moved from an unplaced top-level folder (was `DeepExpandingMenuOld.vue`)
|
|
88
|
+
into `02.molecules/navigation/deep-expanding-menu-classic/`; converted options-style
|
|
89
|
+
`defineProps` to `interface Props` + `withDefaults`; switched its locally-declared
|
|
90
|
+
`ResponsiveHeaderNavItem` interface to the shared exported type from `~/types/components`
|
|
91
|
+
(was a duplicate, drift-prone copy); removed a dead, unused `:id="popovertarget-nav-1-..."`
|
|
92
|
+
attribute left over from copying the modern implementation (this component doesn't use the
|
|
93
|
+
Popover API); promoted 19 previously-hardcoded values to public CSS tokens.
|
|
94
|
+
- File: `app/components/02.molecules/navigation/deep-expanding-menu-classic/DeepExpandingMenuClassic.vue`
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: DeepExpandingMenu
|
|
3
|
+
description: DeepExpandingMenu top-level nav with anchor-positioned popover child-link panels, browser support caveat, CSS token API
|
|
4
|
+
type: reference
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# DeepExpandingMenu
|
|
8
|
+
|
|
9
|
+
## Overview
|
|
10
|
+
|
|
11
|
+
`DeepExpandingMenu` renders a top-level navigation row where each item is either a direct link
|
|
12
|
+
(`link.path` set) or a toggle button that opens a popover panel of child links. It's built on CSS
|
|
13
|
+
anchor-positioning (`anchor-name`/`position-anchor`/`anchor()`) and the native Popover API
|
|
14
|
+
(`popover`/`popovertarget`).
|
|
15
|
+
|
|
16
|
+
**Browser support caveat:** CSS anchor-positioning and the Popover API are both comparatively
|
|
17
|
+
recent — check current browser support before shipping this on a page that must work everywhere,
|
|
18
|
+
same caution as `ExpandingPanel`'s `::details-content` gap (CLAUDE.md pitfall #19). If broad
|
|
19
|
+
support is a hard requirement, use [`DeepExpandingMenuClassic`](deep-expanding-menu-classic.md)
|
|
20
|
+
instead — same API and visual result, built on `<details>`/`<summary>` +
|
|
21
|
+
`onClickOutside` instead.
|
|
22
|
+
|
|
23
|
+
## Props
|
|
24
|
+
|
|
25
|
+
| Prop | Type | Default | Description |
|
|
26
|
+
|------|------|---------|-------------|
|
|
27
|
+
| `tag` | `"div" \| "section" \| "nav" \| "ul" \| "ol"` | `"nav"` | Root element tag. |
|
|
28
|
+
| `navLinks` | `ResponsiveHeaderNavItem[]` | `[]` | Top-level nav items. An item with `path` renders as a direct link; an item with `childLinks` renders as a toggle + popover panel. |
|
|
29
|
+
| `styleClassPassthrough` | `string \| string[]` | `[]` | Extra classes applied to the root element. |
|
|
30
|
+
|
|
31
|
+
`ResponsiveHeaderNavItem` is exported from `~/types/components` (shared with `ResponsiveHeader`).
|
|
32
|
+
|
|
33
|
+
## Basic usage
|
|
34
|
+
|
|
35
|
+
```vue
|
|
36
|
+
<script setup lang="ts">
|
|
37
|
+
import type { ResponsiveHeaderNavItem } from "~/types/components";
|
|
38
|
+
|
|
39
|
+
const navLinks: ResponsiveHeaderNavItem[] = [
|
|
40
|
+
{ name: "Home", path: "/" },
|
|
41
|
+
{
|
|
42
|
+
name: "Services",
|
|
43
|
+
childLinksTitle: "Our services",
|
|
44
|
+
childLinks: [
|
|
45
|
+
{ name: "Haircuts", path: "/services/haircuts" },
|
|
46
|
+
{ name: "Colouring", path: "/services/colouring" },
|
|
47
|
+
],
|
|
48
|
+
},
|
|
49
|
+
];
|
|
50
|
+
</script>
|
|
51
|
+
|
|
52
|
+
<template>
|
|
53
|
+
<DeepExpandingMenu :nav-links="navLinks" />
|
|
54
|
+
</template>
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
## CSS custom properties
|
|
58
|
+
|
|
59
|
+
| Property | Default | Controls |
|
|
60
|
+
|----------|---------|----------|
|
|
61
|
+
| `--deep-expanding-menu-gap` | `2.4rem` | Gap between top-level items |
|
|
62
|
+
| `--deep-expanding-menu-link-border-width` | `0.2rem` | Bottom border width on links/toggles |
|
|
63
|
+
| `--deep-expanding-menu-link-padding-block` | `0.8rem` | Vertical padding on links/toggles |
|
|
64
|
+
| `--deep-expanding-menu-link-border-colour-hover` | `light-dark(var(--blue-10), var(--slate-00))` | Bottom border colour on hover/focus |
|
|
65
|
+
| `--deep-expanding-menu-icon-size` | `1.2rem` | Caret icon size |
|
|
66
|
+
| `--deep-expanding-menu-panel-width` | `min(100%, 50vw)` | Popover panel width |
|
|
67
|
+
| `--deep-expanding-menu-panel-background-colour` | `white` | Popover panel background |
|
|
68
|
+
| `--deep-expanding-menu-panel-border-width` | `0.1rem` | Popover panel border width |
|
|
69
|
+
| `--deep-expanding-menu-panel-border-colour` | `black` | Popover panel border colour |
|
|
70
|
+
| `--deep-expanding-menu-panel-border-radius` | `1.2rem` | Popover panel corner radius |
|
|
71
|
+
| `--deep-expanding-menu-panel-shadow` | `0 0 1rem rgba(0, 0, 0, 0.1)` | Popover panel box-shadow |
|
|
72
|
+
| `--deep-expanding-menu-panel-padding` | `1.2rem` | Popover panel padding |
|
|
73
|
+
| `--deep-expanding-menu-panel-heading-colour` | `var(--slate-10)` | `childLinksTitle` heading colour |
|
|
74
|
+
| `--deep-expanding-menu-panel-list-gap` | `1.2rem` | Gap between child link grid items |
|
|
75
|
+
| `--deep-expanding-menu-group-link-colour` | `var(--slate-10)` | Child link text colour |
|
|
76
|
+
| `--deep-expanding-menu-group-link-border-colour-hover` | `var(--slate-10)` | Child link bottom border colour on hover/focus |
|
|
77
|
+
|
|
78
|
+
See `CONSUMER-STYLING.md` for the full token API and what's not tokenised.
|
|
79
|
+
|
|
80
|
+
## Notes
|
|
81
|
+
|
|
82
|
+
- Auto-imported in Nuxt — no manual import needed.
|
|
83
|
+
- Anchor names and popover target ids are generated per-instance via `useId()`, so multiple
|
|
84
|
+
instances on one page don't collide.
|
|
85
|
+
- 2026-09-07 migration: moved from an unplaced top-level folder into
|
|
86
|
+
`02.molecules/navigation/deep-expanding-menu/`; converted options-style `defineProps` to
|
|
87
|
+
`interface Props` + `withDefaults`; the previous hardcoded `nav-1` anchor/popover id prefix
|
|
88
|
+
(a multi-instance collision bug) was replaced with a `useId()`-scoped prefix; removed a dead,
|
|
89
|
+
never-consumed `ref="detailsRef"` template ref; promoted 16 previously-hardcoded values (gap,
|
|
90
|
+
border widths/colours, panel background/border/radius/shadow/padding, heading and link colours)
|
|
91
|
+
to public CSS tokens.
|
|
92
|
+
- File: `app/components/02.molecules/navigation/deep-expanding-menu/DeepExpandingMenu.vue`
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: DisplayBanner
|
|
3
|
+
description: DisplayBanner canvas/content stacked overlay banner — props, slots, CSS tokens
|
|
4
|
+
type: reference
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# DisplayBanner
|
|
8
|
+
|
|
9
|
+
## Overview
|
|
10
|
+
|
|
11
|
+
`DisplayBanner` stacks a `canvas` slot (background media) and a `content` slot (overlaid
|
|
12
|
+
text/CTAs) into a single grid area, so content renders on top of the canvas. Either slot may be
|
|
13
|
+
omitted; an unused slot contributes no empty wrapper `<div>` to the DOM.
|
|
14
|
+
|
|
15
|
+
## Props
|
|
16
|
+
|
|
17
|
+
| Prop | Type | Default | Description |
|
|
18
|
+
|------|------|---------|-------------|
|
|
19
|
+
| `tag` | `"div" \| "p" \| "span" \| "section" \| "article" \| "aside" \| "header" \| "footer" \| "main" \| "nav" \| "ul" \| "ol"` | `"div"` | Root element tag. |
|
|
20
|
+
| `styleClassPassthrough` | `string \| string[]` | `[]` | Extra classes applied to the root element. |
|
|
21
|
+
|
|
22
|
+
## Slots
|
|
23
|
+
|
|
24
|
+
| Slot | Description |
|
|
25
|
+
|------|-------------|
|
|
26
|
+
| `canvas` | Background media (image, video, or any visual). Rendered first, so it sits behind `content`. |
|
|
27
|
+
| `content` | Overlaid content (text, CTAs). Rendered after `canvas`, so it sits on top. |
|
|
28
|
+
|
|
29
|
+
## Basic usage
|
|
30
|
+
|
|
31
|
+
```vue
|
|
32
|
+
<DisplayBanner>
|
|
33
|
+
<template #canvas>
|
|
34
|
+
<NuxtImg src="/hero.jpg" alt="" style="width: 100%; height: 100%; object-fit: cover;" />
|
|
35
|
+
</template>
|
|
36
|
+
<template #content>
|
|
37
|
+
<h2>Banner heading</h2>
|
|
38
|
+
</template>
|
|
39
|
+
</DisplayBanner>
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
## CSS custom properties
|
|
43
|
+
|
|
44
|
+
| Property | Default | Controls |
|
|
45
|
+
|----------|---------|----------|
|
|
46
|
+
| `--display-banner-min-height` | `auto` | Minimum height of the banner |
|
|
47
|
+
|
|
48
|
+
See `CONSUMER-STYLING.md` for the full token API.
|
|
49
|
+
|
|
50
|
+
## Notes
|
|
51
|
+
|
|
52
|
+
- `container-type: inline-size` is set on the root, so slot content can use container queries.
|
|
53
|
+
- No sizing/aspect-ratio is imposed on the `canvas` slot's content itself (e.g. an image needs its
|
|
54
|
+
own `width`/`height`/`object-fit`) — `DisplayBanner` only positions the two slots, it doesn't
|
|
55
|
+
style what's inside them.
|
|
56
|
+
- 2026-09-07 migration: removed a dead `.canvas .image { object-fit: cover; ... }` CSS rule that
|
|
57
|
+
assumed consumer-slotted content would use a generic `.image` class — nothing in the template
|
|
58
|
+
ever rendered that class, and styling a generic class name inside a slot risks colliding with a
|
|
59
|
+
consumer's own `.image` class (see CLAUDE.md pitfall #16). Style slotted media directly on the
|
|
60
|
+
element you put in the slot instead.
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: DisplayTooltipDefined
|
|
3
|
+
description: DisplayTooltipDefined structured title/body/action tooltip content with close button, composes DisplayTooltip
|
|
4
|
+
type: reference
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# DisplayTooltipDefined
|
|
8
|
+
|
|
9
|
+
## Overview
|
|
10
|
+
|
|
11
|
+
`DisplayTooltipDefined` composes [`DisplayTooltip`](display-tooltip.md), supplying a structured
|
|
12
|
+
title/body/action content shape (each rendered with a consumer-chosen tag) plus a built-in close
|
|
13
|
+
button wired to the popover's `popovertarget`. Use this when you want the standard tooltip content
|
|
14
|
+
layout without hand-building the `tooltipContent` slot markup each time; use plain `DisplayTooltip`
|
|
15
|
+
directly when you need full control over the popover's content.
|
|
16
|
+
|
|
17
|
+
## Props
|
|
18
|
+
|
|
19
|
+
| Prop | Type | Default | Description |
|
|
20
|
+
|------|------|---------|-------------|
|
|
21
|
+
| `tooltipId` | `string` | `""` | Base id for the popover. Prefixed with `nuxt-tooltip-` when provided; auto-generated via `useId()` (also prefixed) when omitted. |
|
|
22
|
+
| `contentText` | `TooltipContentText` | `{}` | Title/body/action content. Each field is optional — omitted fields render nothing. |
|
|
23
|
+
| `styleClassPassthrough` | `string \| string[]` | `[]` | Extra classes applied to the underlying `DisplayTooltip` root element. |
|
|
24
|
+
|
|
25
|
+
`TooltipContentText` is exported from `~/types/components`:
|
|
26
|
+
|
|
27
|
+
```ts
|
|
28
|
+
interface TooltipContentTextItem {
|
|
29
|
+
tag: string; // e.g. "h4", "p", "span"
|
|
30
|
+
text: string;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
interface TooltipContentText {
|
|
34
|
+
tooltipTitle?: TooltipContentTextItem;
|
|
35
|
+
tooltipContent?: TooltipContentTextItem;
|
|
36
|
+
tooltipAction?: TooltipContentTextItem;
|
|
37
|
+
}
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
## Slots
|
|
41
|
+
|
|
42
|
+
| Slot | Description |
|
|
43
|
+
|------|-------------|
|
|
44
|
+
| `triggerContent` | Passed through to the underlying `DisplayTooltip`'s `triggerContent` slot. |
|
|
45
|
+
|
|
46
|
+
## Basic usage
|
|
47
|
+
|
|
48
|
+
```vue
|
|
49
|
+
<script setup lang="ts">
|
|
50
|
+
import type { TooltipContentText } from "~/types/components";
|
|
51
|
+
|
|
52
|
+
const contentText: TooltipContentText = {
|
|
53
|
+
tooltipTitle: { tag: "h4", text: "Free delivery" },
|
|
54
|
+
tooltipContent: { tag: "p", text: "Orders over £50 qualify for free standard delivery." },
|
|
55
|
+
tooltipAction: { tag: "span", text: "See delivery policy" },
|
|
56
|
+
};
|
|
57
|
+
</script>
|
|
58
|
+
|
|
59
|
+
<template>
|
|
60
|
+
<DisplayTooltipDefined :content-text="contentText" />
|
|
61
|
+
</template>
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
## CSS custom properties
|
|
65
|
+
|
|
66
|
+
| Property | Default | Controls |
|
|
67
|
+
|----------|---------|----------|
|
|
68
|
+
| `--display-tooltip-defined-content-gap` | `1.2rem` | Gap between title/body/action/close-button |
|
|
69
|
+
| `--display-tooltip-defined-title-colour` | `inherit` | `tooltipTitle` text colour |
|
|
70
|
+
| `--display-tooltip-defined-title-margin-block` | `0 0` | `tooltipTitle` block margin |
|
|
71
|
+
| `--display-tooltip-defined-title-font-size` | `1.6rem` | `tooltipTitle` font size |
|
|
72
|
+
| `--display-tooltip-defined-title-font-weight` | `700` | `tooltipTitle` font weight |
|
|
73
|
+
| `--display-tooltip-defined-title-line-height` | `120%` | `tooltipTitle` line height |
|
|
74
|
+
| `--display-tooltip-defined-title-letter-spacing` | `normal` | `tooltipTitle` letter spacing |
|
|
75
|
+
| `--display-tooltip-defined-body-colour` | `inherit` | `tooltipContent` text colour |
|
|
76
|
+
| `--display-tooltip-defined-body-margin-block` | `0 0` | `tooltipContent` block margin |
|
|
77
|
+
| `--display-tooltip-defined-body-font-size` | `1.3rem` | `tooltipContent` font size |
|
|
78
|
+
| `--display-tooltip-defined-body-font-weight` | `inherit` | `tooltipContent` font weight |
|
|
79
|
+
| `--display-tooltip-defined-body-line-height` | `140%` | `tooltipContent` line height |
|
|
80
|
+
| `--display-tooltip-defined-body-letter-spacing` | `normal` | `tooltipContent` letter spacing |
|
|
81
|
+
| `--display-tooltip-defined-action-colour` | `inherit` | `tooltipAction` text colour |
|
|
82
|
+
| `--display-tooltip-defined-action-margin-block` | `0 0` | `tooltipAction` block margin |
|
|
83
|
+
| `--display-tooltip-defined-action-font-size` | `1.4rem` | `tooltipAction` font size |
|
|
84
|
+
| `--display-tooltip-defined-action-font-weight` | `700` | `tooltipAction` font weight |
|
|
85
|
+
| `--display-tooltip-defined-action-line-height` | `130%` | `tooltipAction` line height |
|
|
86
|
+
| `--display-tooltip-defined-action-letter-spacing` | `normal` | `tooltipAction` letter spacing |
|
|
87
|
+
| `--display-tooltip-defined-close-button-margin-block` | `1.2rem 0` | Space above the close button |
|
|
88
|
+
|
|
89
|
+
Also renders every `--display-tooltip-*` token from the underlying `DisplayTooltip` — see its
|
|
90
|
+
CONSUMER-STYLING.md for the full list.
|
|
91
|
+
|
|
92
|
+
## Pairing with useTooltipsGuide
|
|
93
|
+
|
|
94
|
+
For a sequential, auto-advancing "guide" walkthrough (the pattern used in some guide intros — show
|
|
95
|
+
step 1, wait for dismissal, show step 2, ...), pair this component with
|
|
96
|
+
[`useTooltipsGuide`](../composable-tooltips-guide.md): put a ref on the container wrapping your
|
|
97
|
+
`DisplayTooltipDefined`/`DisplayTooltip` instances and pass it to the composable. The composable
|
|
98
|
+
only needs native `[popover]`/`popovertarget` markup, which both components already emit, so no
|
|
99
|
+
extra wiring is required beyond the container ref. See the `GuidedTour` story in
|
|
100
|
+
`DisplayTooltipDefined.stories.ts` for a full working example (it recreates the old
|
|
101
|
+
`app/pages/ui/tooltips.vue` demo page, removed 2026-08-23 when Storybook became the only demo
|
|
102
|
+
surface).
|
|
103
|
+
|
|
104
|
+
## Notes
|
|
105
|
+
|
|
106
|
+
- Auto-imported in Nuxt — no manual import needed.
|
|
107
|
+
- 2026-09-07 migration: moved from an unplaced top-level folder into
|
|
108
|
+
`02.molecules/display-tooltip-defined/` (molecule tier, since it composes the `DisplayTooltip`
|
|
109
|
+
atom); converted options-style `defineProps` to `interface Props` + `withDefaults`; moved the
|
|
110
|
+
inline `TooltipContentText` interface out to `~/types/components/display-tooltip-defined.d.ts`
|
|
111
|
+
so consumers can import it (inline `.vue` types aren't importable); fixed a dead CSS selector —
|
|
112
|
+
the stylesheet targeted `.popover .popover-content .popover-content-defined`, but the actual
|
|
113
|
+
rendered classes are `.display-tooltip-popover`/`.display-tooltip-popover-content`, so the rule
|
|
114
|
+
never matched anything; replaced two custom properties referencing tokens never declared
|
|
115
|
+
anywhere in this repo (`--nuxt-text-white-header`, `--nuxt-text-white-body`) with real public
|
|
116
|
+
tokens defaulting to `inherit` (same class of bug as `AnimatedSvgText`, CLAUDE.md pitfall #21) —
|
|
117
|
+
the title/body/action text had been rendering only by accident, via CSS falling back to the
|
|
118
|
+
inherited colour when a `var()` reference is unresolvable, not by design.
|
|
119
|
+
- 2026-09-07 (same day, follow-up): `.popover-content-defined` gained `display: flex;
|
|
120
|
+
flex-direction: column;` and a `gap` (new `--display-tooltip-defined-content-gap` token), and
|
|
121
|
+
its close button is now right-aligned (`align-self: flex-end`) rather than sitting inline after
|
|
122
|
+
the action text — part of the same visual-polish pass as `DisplayTooltip` itself (see its skill
|
|
123
|
+
doc). Note `align-self` only works because `.popover-content-defined` (the button's actual DOM
|
|
124
|
+
parent) is the flex container — the shared `.display-tooltip-popover-content` wrapper one level
|
|
125
|
+
up is a different, outer flex context and can't align this button directly.
|
|
126
|
+
- 2026-09-07 (user follow-up): fixed a real spacing bug, not a preference tweak — `contentText.*.tag`
|
|
127
|
+
lets the consumer pick any tag (`h3`, `h4`, `p`, ...), and a heading tag carries its own
|
|
128
|
+
non-zero UA default `margin-block` (e.g. `1em`/`1em`), which was stacking on top of
|
|
129
|
+
`.popover-content-defined`'s flex `gap` and visually doubling the space between title/body/action.
|
|
130
|
+
`margin-block: 0 0` now resets that UA default on all three regardless of which tag is chosen, so
|
|
131
|
+
`gap` is the only spacing mechanism — see the comment in the component. Also gave each of
|
|
132
|
+
`tooltipTitle`/`tooltipContent`/`tooltipAction` a full typographic token set — `font-size`,
|
|
133
|
+
`font-weight`, `line-height`, `letter-spacing`, and `margin-block` (all overridable, in case a
|
|
134
|
+
consumer genuinely wants extra margin on top of `gap`) — rather than only `colour`. The close
|
|
135
|
+
button gained its own `margin-block` token to separate it from the action text above it.
|
|
136
|
+
- File: `app/components/02.molecules/display-tooltip-defined/DisplayTooltipDefined.vue`
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: DisplayTooltip
|
|
3
|
+
description: DisplayTooltip anchor-positioned popover trigger, browser support caveat, CSS token API
|
|
4
|
+
type: reference
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# DisplayTooltip
|
|
8
|
+
|
|
9
|
+
## Overview
|
|
10
|
+
|
|
11
|
+
`DisplayTooltip` renders a trigger button that opens an anchor-positioned popover panel, built on
|
|
12
|
+
the native Popover API (`popover`/`popovertarget`) and CSS anchor-positioning
|
|
13
|
+
(`anchor-name`/`position-anchor`/`anchor()`). For a structured title/body/action content shape
|
|
14
|
+
with a built-in close button, see [`DisplayTooltipDefined`](display-tooltip-defined.md), which
|
|
15
|
+
composes this component.
|
|
16
|
+
|
|
17
|
+
**Browser support caveat:** CSS anchor-positioning and the Popover API are both comparatively
|
|
18
|
+
recent — check current browser support before relying on this where broad support is a hard
|
|
19
|
+
requirement, same caution as `DeepExpandingMenu`'s equivalent gap.
|
|
20
|
+
|
|
21
|
+
## Props
|
|
22
|
+
|
|
23
|
+
| Prop | Type | Default | Description |
|
|
24
|
+
|------|------|---------|-------------|
|
|
25
|
+
| `tooltipId` | `string` | `""` | Id linking the trigger button (`popovertarget`) to the popover panel (`id`). Auto-generated via `useId()` when omitted. |
|
|
26
|
+
| `hideTrigger` | `boolean` | `false` | Visually hides the trigger button (kept in the DOM, not removed) — e.g. when an equivalent trigger is supplied via the `triggerContent` slot. |
|
|
27
|
+
| `styleClassPassthrough` | `string \| string[]` | `[]` | Extra classes applied to the root element. |
|
|
28
|
+
|
|
29
|
+
## Slots
|
|
30
|
+
|
|
31
|
+
| Slot | Description |
|
|
32
|
+
|------|-------------|
|
|
33
|
+
| `triggerContent` | Optional content rendered before the trigger button (e.g. a label the tooltip icon sits next to). |
|
|
34
|
+
| `tooltipContent` | Content rendered inside the popover panel. |
|
|
35
|
+
|
|
36
|
+
## Basic usage
|
|
37
|
+
|
|
38
|
+
```vue
|
|
39
|
+
<DisplayTooltip>
|
|
40
|
+
<template #triggerContent>
|
|
41
|
+
<span>Delivery cost</span>
|
|
42
|
+
</template>
|
|
43
|
+
<template #tooltipContent>
|
|
44
|
+
<p>Free delivery on orders over £50.</p>
|
|
45
|
+
</template>
|
|
46
|
+
</DisplayTooltip>
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
## CSS custom properties
|
|
50
|
+
|
|
51
|
+
See `CONSUMER-STYLING.md` for the full `--display-tooltip-*` token API (padding, gaps, icon size,
|
|
52
|
+
popover width/colours/radius, and the close-button tokens `DisplayTooltipDefined` uses).
|
|
53
|
+
|
|
54
|
+
## Notes
|
|
55
|
+
|
|
56
|
+
- Auto-imported in Nuxt — no manual import needed.
|
|
57
|
+
- `tooltipId` is only reactive at mount — changing it after mount does not regenerate the linked
|
|
58
|
+
`popovertarget`/`id` pair reactively beyond Vue's normal re-render, since both derive from the
|
|
59
|
+
same computed.
|
|
60
|
+
- 2026-09-07 migration: moved from an unplaced top-level folder into `01.atoms/display-tooltip/`;
|
|
61
|
+
converted options-style `defineProps` to `interface Props` + `withDefaults`; fixed a real
|
|
62
|
+
accessibility bug where the trigger button's hover/focus-visible state set `outline-offset` but
|
|
63
|
+
never an `outline-color` (it stayed `transparent`), so the focus indicator was never actually
|
|
64
|
+
visible; fixed two custom properties (`--nuxt-text-header`, `--nuxt-text-accessibility-blue`)
|
|
65
|
+
that referenced tokens never declared anywhere in this repo (same class of bug as
|
|
66
|
+
`AnimatedSvgText`, CLAUDE.md pitfall #21) — both now default to the real `--theme-text` token;
|
|
67
|
+
removed a dead, unused `ref="popover1"` template ref; added the missing
|
|
68
|
+
`resetElementClasses`/`watch` pairing for `styleClassPassthrough` (present on every other
|
|
69
|
+
component in this library but missing here); promoted ~20 previously-hardcoded values to public
|
|
70
|
+
CSS tokens; the previously-dead `hideTooltipTrigger` ref (declared, never mutated) is now the
|
|
71
|
+
`hideTrigger` prop.
|
|
72
|
+
- 2026-09-07 (same day, follow-up): the initial migration carried over the original's bare,
|
|
73
|
+
under-styled look (raw `light-dark(black, white)` outline/text/background, no padding on the
|
|
74
|
+
content wrapper, no shadow) — it worked but didn't read as "a tooltip." Reworked the popover's
|
|
75
|
+
default appearance: background/text/border now use the library's neutral `--slate-*` scale
|
|
76
|
+
instead of pure black/white; `.display-tooltip-popover-content` gained default `padding`,
|
|
77
|
+
`display: flex; flex-direction: column;`, and `gap` (new `--display-tooltip-popover-padding`/
|
|
78
|
+
`-content-gap` tokens); the popover gained a default elevation `box-shadow` (new
|
|
79
|
+
`--display-tooltip-popover-shadow` token); the close button's permanent resting outline was
|
|
80
|
+
dropped in favour of a normal border, with the outline reserved for hover/focus (matching the
|
|
81
|
+
trigger button's own convention). All existing token names are unchanged — only their default
|
|
82
|
+
values and two new tokens were added, so this is non-breaking for anyone who already overrode a
|
|
83
|
+
token.
|
|
84
|
+
- File: `app/components/01.atoms/display-tooltip/DisplayTooltip.vue`
|