srcdev-nuxt-components 9.4.4 → 9.4.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (116) hide show
  1. package/.claude/commands/migrate-component.md +106 -0
  2. package/.claude/component-ledger/artifact-url.txt +1 -0
  3. package/.claude/component-ledger/audit.json +1 -0
  4. package/.claude/component-ledger/build.mjs +111 -0
  5. package/.claude/component-ledger/output.html +559 -0
  6. package/.claude/component-ledger/template.html +559 -0
  7. package/.claude/hooks/refresh-component-ledger.sh +32 -0
  8. package/.claude/settings.json +10 -0
  9. package/.claude/skills/components/animated-svg-text.md +78 -0
  10. package/.claude/skills/components/breadcrumb.md +1 -0
  11. package/.claude/skills/components/canvas-switcher.md +61 -0
  12. package/.claude/skills/components/clip-element.md +67 -0
  13. package/.claude/skills/components/clipped-panel.md +64 -0
  14. package/.claude/skills/components/content-docs.md +2 -0
  15. package/.claude/skills/components/deep-expanding-menu-classic.md +94 -0
  16. package/.claude/skills/components/deep-expanding-menu.md +92 -0
  17. package/.claude/skills/components/display-banner.md +60 -0
  18. package/.claude/skills/components/display-tooltip-defined.md +139 -0
  19. package/.claude/skills/components/display-tooltip.md +85 -0
  20. package/.claude/skills/components/marquee-scroller.md +130 -0
  21. package/.claude/skills/components/navigation-items.md +1 -0
  22. package/.claude/skills/components/responsive-header.md +3 -0
  23. package/.claude/skills/components/site-header.md +4 -0
  24. package/.claude/skills/components/site-navigation.md +1 -0
  25. package/.claude/skills/components/tab-navigation.md +1 -0
  26. package/.claude/skills/composable-tooltips-guide.md +10 -0
  27. package/.claude/skills/index.md +8 -1
  28. package/.claude/skills/storybook-add-story.md +30 -0
  29. package/.vscode/srcdev-component-animated-svg-text.code-snippets +15 -0
  30. package/.vscode/srcdev-component-breadcrumb.code-snippets +13 -0
  31. package/.vscode/srcdev-component-canvas-switcher.code-snippets +9 -0
  32. package/.vscode/srcdev-component-clip-element.code-snippets +20 -0
  33. package/.vscode/srcdev-component-clipped-panel.code-snippets +23 -0
  34. package/.vscode/srcdev-component-content-docs.code-snippets +18 -0
  35. package/.vscode/srcdev-component-deep-expanding-menu-classic.code-snippets +9 -0
  36. package/.vscode/srcdev-component-deep-expanding-menu.code-snippets +9 -0
  37. package/.vscode/srcdev-component-display-banner.code-snippets +30 -0
  38. package/.vscode/srcdev-component-display-tooltip-defined.code-snippets +29 -0
  39. package/.vscode/srcdev-component-display-tooltip.code-snippets +38 -0
  40. package/.vscode/srcdev-component-marquee-scroller.code-snippets +93 -0
  41. package/.vscode/srcdev-component-responsive-header.code-snippets +12 -0
  42. package/.vscode/srcdev-component-site-header.code-snippets +17 -0
  43. package/.vscode/srcdev-component-site-navigation.code-snippets +30 -0
  44. package/.vscode/srcdev-component-slider-gallery.code-snippets +38 -0
  45. package/.vscode/srcdev-component-tab-navigation.code-snippets +30 -0
  46. package/app/components/01.atoms/animations/animated-svg-text/AnimatedSvgText.vue +58 -0
  47. package/app/components/01.atoms/animations/animated-svg-text/CONSUMER-STYLING.md +34 -0
  48. package/app/components/01.atoms/animations/animated-svg-text/stories/AnimatedSvgText.stories.ts +69 -0
  49. package/app/components/01.atoms/animations/animated-svg-text/tests/AnimatedSvgText.spec.ts +63 -0
  50. package/app/components/01.atoms/animations/animated-svg-text/tests/__snapshots__/AnimatedSvgText.spec.ts.snap +7 -0
  51. package/app/components/01.atoms/animations/clip-element/CONSUMER-STYLING.md +27 -0
  52. package/app/components/{clip-element → 01.atoms/animations/clip-element}/ClipElement.vue +16 -11
  53. package/app/components/01.atoms/animations/clip-element/stories/ClipElement.stories.ts +133 -0
  54. package/app/components/01.atoms/animations/clip-element/tests/ClipElement.spec.ts +70 -0
  55. package/app/components/01.atoms/animations/clip-element/tests/__snapshots__/ClipElement.spec.ts.snap +7 -0
  56. package/app/components/01.atoms/animations/marquee-scroller/CONSUMER-STYLING.md +94 -0
  57. package/app/components/01.atoms/animations/marquee-scroller/MarqueeScroller.vue +331 -0
  58. package/app/components/01.atoms/animations/marquee-scroller/stories/MarqueeScroller.stories.ts +151 -0
  59. package/app/components/01.atoms/animations/marquee-scroller/tests/MarqueeScroller.spec.ts +315 -0
  60. package/app/components/01.atoms/animations/marquee-scroller/tests/__snapshots__/MarqueeScroller.spec.ts.snap +28 -0
  61. package/app/components/01.atoms/canvas-switcher/CONSUMER-STYLING.md +27 -0
  62. package/app/components/01.atoms/canvas-switcher/CanvasSwitcher.vue +111 -0
  63. package/app/components/01.atoms/canvas-switcher/stories/CanvasSwitcher.stories.ts +75 -0
  64. package/app/components/01.atoms/canvas-switcher/tests/CanvasSwitcher.spec.ts +67 -0
  65. package/app/components/01.atoms/clipped-panel/CONSUMER-STYLING.md +39 -0
  66. package/app/components/01.atoms/clipped-panel/ClippedPanel.vue +68 -0
  67. package/app/components/01.atoms/clipped-panel/stories/ClippedPanel.stories.ts +105 -0
  68. package/app/components/01.atoms/clipped-panel/tests/ClippedPanel.spec.ts +67 -0
  69. package/app/components/01.atoms/clipped-panel/tests/__snapshots__/ClippedPanel.spec.ts.snap +3 -0
  70. package/app/components/01.atoms/content-wrappers/docs-pages/ContentDocs.vue +8 -2
  71. package/app/components/01.atoms/display-banner/CONSUMER-STYLING.md +35 -0
  72. package/app/components/{display-banner → 01.atoms/display-banner}/DisplayBanner.vue +13 -35
  73. package/app/components/01.atoms/display-banner/stories/DisplayBanner.stories.ts +100 -0
  74. package/app/components/01.atoms/display-banner/tests/DisplayBanner.spec.ts +78 -0
  75. package/app/components/01.atoms/display-banner/tests/__snapshots__/DisplayBanner.spec.ts.snap +8 -0
  76. package/app/components/01.atoms/display-tooltip/CONSUMER-STYLING.md +62 -0
  77. package/app/components/01.atoms/display-tooltip/DisplayTooltip.vue +188 -0
  78. package/app/components/01.atoms/display-tooltip/stories/DisplayTooltip.stories.ts +94 -0
  79. package/app/components/01.atoms/display-tooltip/tests/DisplayTooltip.spec.ts +75 -0
  80. package/app/components/01.atoms/display-tooltip/tests/__snapshots__/DisplayTooltip.spec.ts.snap +12 -0
  81. package/app/components/01.atoms/navigation/breadcrumb/Breadcrumb.vue +4 -1
  82. package/app/components/02.molecules/display-tooltip-defined/CONSUMER-STYLING.md +42 -0
  83. package/app/components/02.molecules/display-tooltip-defined/DisplayTooltipDefined.vue +116 -0
  84. package/app/components/02.molecules/display-tooltip-defined/stories/DisplayTooltipDefined.stories.ts +126 -0
  85. package/app/components/02.molecules/display-tooltip-defined/tests/DisplayTooltipDefined.spec.ts +66 -0
  86. package/app/components/02.molecules/display-tooltip-defined/tests/__snapshots__/DisplayTooltipDefined.spec.ts.snap +17 -0
  87. package/app/components/02.molecules/navigation/deep-expanding-menu/CONSUMER-STYLING.md +44 -0
  88. package/app/components/{deep-expanding-menu → 02.molecules/navigation/deep-expanding-menu}/DeepExpandingMenu.vue +53 -64
  89. package/app/components/02.molecules/navigation/deep-expanding-menu/stories/DeepExpandingMenu.stories.ts +79 -0
  90. package/app/components/02.molecules/navigation/deep-expanding-menu/tests/DeepExpandingMenu.spec.ts +92 -0
  91. package/app/components/02.molecules/navigation/deep-expanding-menu/tests/__snapshots__/DeepExpandingMenu.spec.ts.snap +17 -0
  92. package/app/components/02.molecules/navigation/deep-expanding-menu-classic/CONSUMER-STYLING.md +40 -0
  93. package/app/components/02.molecules/navigation/deep-expanding-menu-classic/DeepExpandingMenuClassic.vue +200 -0
  94. package/app/components/02.molecules/navigation/deep-expanding-menu-classic/stories/DeepExpandingMenuClassic.stories.ts +79 -0
  95. package/app/components/02.molecules/navigation/deep-expanding-menu-classic/tests/DeepExpandingMenuClassic.spec.ts +82 -0
  96. package/app/components/02.molecules/navigation/deep-expanding-menu-classic/tests/__snapshots__/DeepExpandingMenuClassic.spec.ts.snap +18 -0
  97. package/app/components/02.molecules/navigation/site-navigation/SiteNavigation.vue +4 -1
  98. package/app/components/02.molecules/navigation/tab-navigation/TabNavigation.vue +4 -1
  99. package/app/components/03.organisms/image-galleries/slider-gallery/SliderGallery.vue +16 -4
  100. package/app/components/03.organisms/responsive-header/NavigationItems.vue +4 -1
  101. package/app/components/03.organisms/responsive-header/ResponsiveHeader.vue +16 -3
  102. package/app/components/03.organisms/site-header/SiteHeader.vue +16 -1
  103. package/app/components/03.organisms/site-header/tests/__snapshots__/SiteHeader.spec.ts.snap +1 -1
  104. package/app/types/components/display-tooltip-defined.d.ts +10 -0
  105. package/app/types/components/index.ts +2 -0
  106. package/app/types/components/marquee-scroller.d.ts +10 -0
  107. package/package.json +1 -1
  108. package/app/components/animated-svg-text/AnimatedSvgText.vue +0 -89
  109. package/app/components/canvas-switcher/CanvasSwitcher.vue +0 -104
  110. package/app/components/clipped-panels/ClippedPanel.vue +0 -87
  111. package/app/components/deep-expanding-menu/DeepExpandingMenuOld.vue +0 -218
  112. package/app/components/display-details/DisplayDetailsCore.vue +0 -122
  113. package/app/components/display-tooltip/DisplayTooltip.vue +0 -170
  114. package/app/components/display-tooltip/DisplayTooltipDefined.vue +0 -103
  115. package/app/components/magnetic-navigation/MagneticNavigation.vue +0 -162
  116. package/app/components/marquee-scroller/MarqueeScroller.vue +0 -289
@@ -0,0 +1,85 @@
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
+ | `triggerAriaLabel` | `string` | `"Toggle the popover"` | aria-label on the trigger button — override for localisation. |
28
+ | `styleClassPassthrough` | `string \| string[]` | `[]` | Extra classes applied to the root element. |
29
+
30
+ ## Slots
31
+
32
+ | Slot | Description |
33
+ |------|-------------|
34
+ | `triggerContent` | Optional content rendered before the trigger button (e.g. a label the tooltip icon sits next to). |
35
+ | `tooltipContent` | Content rendered inside the popover panel. |
36
+
37
+ ## Basic usage
38
+
39
+ ```vue
40
+ <DisplayTooltip>
41
+ <template #triggerContent>
42
+ <span>Delivery cost</span>
43
+ </template>
44
+ <template #tooltipContent>
45
+ <p>Free delivery on orders over £50.</p>
46
+ </template>
47
+ </DisplayTooltip>
48
+ ```
49
+
50
+ ## CSS custom properties
51
+
52
+ See `CONSUMER-STYLING.md` for the full `--display-tooltip-*` token API (padding, gaps, icon size,
53
+ popover width/colours/radius, and the close-button tokens `DisplayTooltipDefined` uses).
54
+
55
+ ## Notes
56
+
57
+ - Auto-imported in Nuxt — no manual import needed.
58
+ - `tooltipId` is only reactive at mount — changing it after mount does not regenerate the linked
59
+ `popovertarget`/`id` pair reactively beyond Vue's normal re-render, since both derive from the
60
+ same computed.
61
+ - 2026-09-07 migration: moved from an unplaced top-level folder into `01.atoms/display-tooltip/`;
62
+ converted options-style `defineProps` to `interface Props` + `withDefaults`; fixed a real
63
+ accessibility bug where the trigger button's hover/focus-visible state set `outline-offset` but
64
+ never an `outline-color` (it stayed `transparent`), so the focus indicator was never actually
65
+ visible; fixed two custom properties (`--nuxt-text-header`, `--nuxt-text-accessibility-blue`)
66
+ that referenced tokens never declared anywhere in this repo (same class of bug as
67
+ `AnimatedSvgText`, CLAUDE.md pitfall #21) — both now default to the real `--theme-text` token;
68
+ removed a dead, unused `ref="popover1"` template ref; added the missing
69
+ `resetElementClasses`/`watch` pairing for `styleClassPassthrough` (present on every other
70
+ component in this library but missing here); promoted ~20 previously-hardcoded values to public
71
+ CSS tokens; the previously-dead `hideTooltipTrigger` ref (declared, never mutated) is now the
72
+ `hideTrigger` prop.
73
+ - 2026-09-07 (same day, follow-up): the initial migration carried over the original's bare,
74
+ under-styled look (raw `light-dark(black, white)` outline/text/background, no padding on the
75
+ content wrapper, no shadow) — it worked but didn't read as "a tooltip." Reworked the popover's
76
+ default appearance: background/text/border now use the library's neutral `--slate-*` scale
77
+ instead of pure black/white; `.display-tooltip-popover-content` gained default `padding`,
78
+ `display: flex; flex-direction: column;`, and `gap` (new `--display-tooltip-popover-padding`/
79
+ `-content-gap` tokens); the popover gained a default elevation `box-shadow` (new
80
+ `--display-tooltip-popover-shadow` token); the close button's permanent resting outline was
81
+ dropped in favour of a normal border, with the outline reserved for hover/focus (matching the
82
+ trigger button's own convention). All existing token names are unchanged — only their default
83
+ values and two new tokens were added, so this is non-breaking for anyone who already overrode a
84
+ token.
85
+ - File: `app/components/01.atoms/display-tooltip/DisplayTooltip.vue`
@@ -0,0 +1,130 @@
1
+ ---
2
+ name: MarqueeScroller
3
+ description: MarqueeScroller infinite horizontal logo/badge scroller — props, dynamic per-item slots, accessibility controls, CSS tokens, consumer styling
4
+ type: reference
5
+ ---
6
+
7
+ # MarqueeScroller
8
+
9
+ ## Overview
10
+
11
+ `MarqueeScroller` renders an infinite horizontal scroller for small repeating items — client
12
+ logos, badges, testimonial avatars. The track is duplicated once so the loop is seamless
13
+ (`marquee-group` renders twice: once real, once `aria-hidden="true"`), and the whole thing pauses
14
+ automatically on hover, on keyboard focus, and when the user has `prefers-reduced-motion` set.
15
+
16
+ ## Props
17
+
18
+ | Prop | Type | Default | Description |
19
+ |------|------|---------|-------------|
20
+ | `animationRuntime` | `string` | `"40s"` | CSS `animation-duration` for one full loop of the track. |
21
+ | `reverse` | `boolean` | `false` | Reverses the scroll direction. |
22
+ | `marqueeData` | `MarqueeItem[]` (`{ id: number; content: string }[]`) | `[]` | The items to render. Each item's `id` names the slot that renders its markup. `content` is not rendered by the component itself — it's a convenience field for the consumer to key off inside the slot. |
23
+ | `itemConfig` | `MarqueeItemConfig` (`{ width?, height?, gap? }`) | `{ width: "50px", height: "50px", gap: "16px" }` | Item box size and the gap between items. Also sets the track height. |
24
+ | `ariaLabel` | `string` | `"Scrolling content"` | `aria-label` on the root region — override for localisation. |
25
+ | `ariaDescription` | `string` | `"Use spacebar to pause or play the animation."` | Screen-reader-only instructions rendered inside the region — override for localisation. |
26
+ | `showControls` | `boolean` | `false` | Shows a visible pause/play button. Recommended (WCAG 2.2.2, Pause/Stop/Hide) whenever content scrolls automatically and indefinitely. |
27
+ | `respectReducedMotion` | `boolean` | `true` | Auto-pauses and disables the animation for users with `prefers-reduced-motion: reduce`, and pauses on keyboard focus. |
28
+ | `playIcon` | `string` | `"mdi:play"` | Iconify icon name shown on the control button while paused. Ignored if the `toggle-icon` slot is used. |
29
+ | `pauseIcon` | `string` | `"mdi:pause"` | Iconify icon name shown on the control button while playing. Ignored if the `toggle-icon` slot is used. |
30
+ | `playLabel` | `string` | `"Play animation"` | Control button `aria-label` while paused — override for localisation. |
31
+ | `pauseLabel` | `string` | `"Pause animation"` | Control button `aria-label` while playing — override for localisation. |
32
+
33
+ Import the item types from the library's type exports when building `marqueeData`/`itemConfig`
34
+ outside the component:
35
+
36
+ ```ts
37
+ import type { MarqueeItem, MarqueeItemConfig } from "srcdev-nuxt-components";
38
+ ```
39
+
40
+ ## Slots
41
+
42
+ One dynamically-named slot per `marqueeData` entry, named after that item's `id`:
43
+
44
+ ```vue
45
+ <MarqueeScroller :marquee-data="[{ id: 1, content: 'logo-a' }, { id: 2, content: 'logo-b' }]">
46
+ <template #1>
47
+ <img src="/logos/a.svg" alt="Logo A" />
48
+ </template>
49
+ <template #2>
50
+ <img src="/logos/b.svg" alt="Logo B" />
51
+ </template>
52
+ </MarqueeScroller>
53
+ ```
54
+
55
+ Each slot is rendered twice (once in the visible group, once in the `aria-hidden` duplicate group)
56
+ so the loop wraps seamlessly — don't put anything with side effects or unique ids inside a slot.
57
+
58
+ `toggle-icon` (scoped with `{ isPaused: boolean }`) replaces the control button's icon entirely —
59
+ use this instead of `playIcon`/`pauseIcon` when an Iconify name isn't enough (custom SVG, a
60
+ different icon set):
61
+
62
+ ```vue
63
+ <MarqueeScroller show-controls>
64
+ <template #toggle-icon="{ isPaused }">
65
+ <Icon :name="isPaused ? 'lucide:play' : 'lucide:pause'" />
66
+ </template>
67
+ </MarqueeScroller>
68
+ ```
69
+
70
+ ## Accessibility behaviour
71
+
72
+ - The root is `role="region"` with `tabindex="0"` so it's independently focusable.
73
+ - Spacebar toggles pause/play while the region is focused. Arrow keys do nothing — there is no
74
+ manual-stepping behaviour, so the default `ariaDescription` doesn't claim one (fixed 2026-09-08;
75
+ it previously announced "arrow keys ... for manual control" with no implementation behind it).
76
+ - Focusing the region pauses the animation when `respectReducedMotion` is `true` (the default);
77
+ blurring resumes it unless the user prefers reduced motion.
78
+ - Hovering anywhere over the scroller pauses the track and applies
79
+ `--marquee-scroller-group-hover-filter` (default `grayscale(1)`) to every item; hovering an
80
+ individual item restores it via `--marquee-scroller-item-hover-filter` (default `grayscale(0)`).
81
+ - `aria-live` switches to `"polite"` while paused so screen readers announce the paused content.
82
+ - The duplicate track group is always `aria-hidden="true"`.
83
+ - All user-visible copy (`ariaLabel`, `ariaDescription`, `playLabel`, `pauseLabel`) is a plain
84
+ string prop with an English default — there's no i18n framework dependency in this library, so
85
+ pass translated strings from the consumer's own i18n solution.
86
+
87
+ ## Basic usage
88
+
89
+ ```vue
90
+ <MarqueeScroller
91
+ :marquee-data="[
92
+ { id: 1, content: 'acme' },
93
+ { id: 2, content: 'globex' },
94
+ ]"
95
+ :item-config="{ width: '120px', height: '60px', gap: '24px' }"
96
+ show-controls
97
+ >
98
+ <template #1><img src="/logos/acme.svg" alt="Acme" /></template>
99
+ <template #2><img src="/logos/globex.svg" alt="Globex" /></template>
100
+ </MarqueeScroller>
101
+ ```
102
+
103
+ ## CSS custom properties
104
+
105
+ See `CONSUMER-STYLING.md` in this component's folder for the full public token table. Item size,
106
+ gap, and animation duration are prop-driven (`itemConfig`, `animationRuntime`), not tokens, since
107
+ they also drive the component's own layout math. The `prefers-contrast: high` override's
108
+ `ButtonFace`/`ButtonText` colours are deliberately not tokens (they track the OS high-contrast
109
+ theme); only its border width is (`--marquee-scroller-control-border-width`).
110
+
111
+ ## Seamless loop math
112
+
113
+ The track duplicates `marqueeData` into two `.marquee-group` children so the loop can wrap without
114
+ a visible cut. The animation must shift the track by exactly one group's width plus its connecting
115
+ gap — not `translateX(-50%)`, which overshoots short of the seam once the track's own `gap`
116
+ between the two groups is added to its total width. This is computed as a private
117
+ `--_track-shift: calc(itemCount * (itemWidth + gap))` token and used in the `marqueeMove`
118
+ keyframe. Fixed 2026-09-08 — the original `-50%` version produced a small but visible gap/overlap
119
+ at the loop point once more than a couple of items were in `marqueeData`.
120
+
121
+ ## Auto-repeating marqueeData in a wide container
122
+
123
+ If a single pass of `marqueeData` is narrower than the container (few items, or a full-bleed
124
+ section on a wide viewport), the loop briefly shows empty space before snapping back into view —
125
+ the visible track only spans as wide as the content, not the container. On mount (and on
126
+ container resize, via `ResizeObserver`), the component measures one rendered group's `scrollWidth`
127
+ against the root element's `offsetWidth` and repeats `marqueeData` internally (`repeatCount`)
128
+ enough times per `.marquee-group` so a single group's width always covers the container. This is
129
+ transparent to consumers — it doesn't change `marqueeData` or any prop, only how many times each
130
+ item's slot is rendered internally. Fixed 2026-09-08.
@@ -17,6 +17,7 @@ active-route highlighting, hover indicator).
17
17
  |------|------|---------|-------|
18
18
  | `mainNavigationState` | `ResponsiveHeaderState` | `{ clonedNavLinks: {}, navListVisibility: {}, hasSecondNav: false }` | Shared geometry/visibility state, normally supplied by the parent `ResponsiveHeader` — not something a consumer constructs by hand outside of tests/stories. |
19
19
  | `panelVariant` | `"modern" \| "classic"` | `"classic"` | Which panel component renders a dropdown's submenu: `ExpandingPanel` (`"modern"`) or `ExpandingPanelClassic` (`"classic"`, default). `"modern"` is known not to work correctly on WebKit. Normally set via `ResponsiveHeader`'s own `panelVariant` prop rather than directly. See CLAUDE.md pitfall #19. |
20
+ | `ariaLabel` | `string` | `"Overflow navigation menu"` | aria-label on the root `role="menu"` element — override for localisation. Normally set via `ResponsiveHeader`'s own `overflowMenuAriaLabel` prop rather than directly. |
20
21
  | `styleClassPassthrough` | `string \| string[]` | `[]` | Extra CSS classes applied to the root `.overflow-navigation-wrapper` element. |
21
22
 
22
23
  ---
@@ -107,6 +107,9 @@ buggy toggle-based version too.
107
107
  | `collapseAtMainNavIntersection` | `boolean` | `false` | Like `collapseBreakpoint`, but the breakpoint is derived automatically from the main nav's own measured width rather than a fixed number. |
108
108
  | `allowExpandOnGesture` | `boolean` | `true` | When `true`, hovering/focusing a dropdown summary opens it (in addition to click). When `false`, only click toggles it. |
109
109
  | `panelVariant` | `"modern" \| "classic"` | `"classic"` | Forwarded to `NavigationItems` for its dropdown submenu panels: `ExpandingPanel` (`"modern"`) or `ExpandingPanelClassic` (`"classic"`, default). `"modern"` is known not to work correctly on WebKit. See CLAUDE.md pitfall #19. |
110
+ | `mainNavAriaLabel` | `string` | `"Main navigation"` | aria-label on the primary nav landmark — override for localisation. |
111
+ | `secondaryNavAriaLabel` | `string` | `"Secondary navigation"` | aria-label on the secondary (overflow) nav landmark — override for localisation. |
112
+ | `overflowMenuAriaLabel` | `string` | `"Overflow navigation menu"` | Forwarded to `NavigationItems`' `ariaLabel` — override for localisation. |
110
113
  | `styleClassPassthrough` | `string \| string[]` | `[]` | Extra CSS classes applied to the root `.navigation` element. |
111
114
 
112
115
  ## Slots
@@ -23,6 +23,10 @@ Branding (logo/wordmark) is entirely consumer-authored via the `#branding` slot
23
23
  | `pageRowVariant` | `"full" \| "popout" \| "content" \| "inset-content"` | `"content"` | Forwarded to the root `PageRow`'s `variant` prop. |
24
24
  | `styleClassPassthrough` | `string \| string[]` | `[]` | Extra CSS classes on the root `PageRow` — targets `--site-header-*` tokens (see `CONSUMER-STYLING.md`). |
25
25
  | `navStyleClassPassthrough` | `string \| string[]` | `[]` | Extra CSS classes on the nested `ResponsiveHeader` — targets its own `--responsive-header-*`/`--overflow-nav-*` tokens. |
26
+ | `homeNavAriaLabel` | `string` | `"Home Navigation"` | aria-label on the home/branding nav landmark — override for localisation. |
27
+ | `mainNavAriaLabel` | `string` | `"Main navigation"` | Forwarded to `ResponsiveHeader`'s `mainNavAriaLabel` — override for localisation. |
28
+ | `secondaryNavAriaLabel` | `string` | `"Secondary navigation"` | Forwarded to `ResponsiveHeader`'s `secondaryNavAriaLabel` — override for localisation. |
29
+ | `overflowMenuAriaLabel` | `string` | `"Overflow navigation menu"` | Forwarded to `ResponsiveHeader`'s `overflowMenuAriaLabel` — override for localisation. |
26
30
 
27
31
  ---
28
32
 
@@ -16,6 +16,7 @@ Both the horizontal list and the panel include animated active/hover indicator d
16
16
  |---|---|---|---|
17
17
  | `navItemData` | `NavItemData` | — (required) | Navigation items — see type below |
18
18
  | `navAlign` | `"left" \| "center" \| "right"` | `"left"` | Alignment of the horizontal nav list |
19
+ | `ariaLabel` | `string` | `"Site navigation"` | aria-label on the root `<nav>` landmark — override for localisation |
19
20
  | `styleClassPassthrough` | `string \| string[]` | `[]` | Extra classes applied to the root `<nav>` |
20
21
 
21
22
  ## NavItemData type
@@ -40,6 +40,7 @@ interface NavItemData {
40
40
  | `nav-item-data` | `NavItemData` | required | Navigation link data — only `main` key is read |
41
41
  | `nav-align` | `"left" \| "center" \| "right"` | `"left"` | Positions the tab list within the nav container |
42
42
  | `anchor-scroll-offset` | `number \| (() => number)` | — | Pixel offset subtracted from scroll target; pass a getter so it is re-evaluated at click time |
43
+ | `aria-label` | `string` | `"Site navigation"` | aria-label on the root `<nav>` landmark — override for localisation |
43
44
  | `:style-class-passthrough` | `string \| string[]` | `[]` | Extra classes on the root `<nav>` element |
44
45
 
45
46
  ---
@@ -89,6 +89,16 @@ await startGuide()
89
89
  | `stopGuide()` | `() => void` | Close any open popovers and stop the guide |
90
90
  | `initializePopovers()` | `() => void` | Re-scan the container for `[popover]` elements; call if popovers are added dynamically |
91
91
 
92
+ ## Example with DisplayTooltip / DisplayTooltipDefined
93
+
94
+ [`DisplayTooltip`](components/display-tooltip.md) and
95
+ [`DisplayTooltipDefined`](components/display-tooltip-defined.md) already emit the
96
+ `[popover]`/`popovertarget`/`popovertargetaction` markup this composable expects, so no extra
97
+ wiring is needed beyond passing their shared container's ref in. See the `GuidedTour` story in
98
+ `DisplayTooltipDefined.stories.ts` for a full working example — it recreates the old
99
+ `app/pages/ui/tooltips.vue` demo page (removed 2026-08-23 when Storybook became the only demo
100
+ surface), mixing one of each component as guide steps.
101
+
92
102
  ## Notes
93
103
 
94
104
  - If no trigger button is found for a popover, `togglePopover(true)` is called directly as a fallback — anchor positioning may not apply in this case.
@@ -63,6 +63,7 @@ Each skill is a single markdown file named `<area>-<task>.md`.
63
63
  ├── composable-analytics.md — useAnalytics: provider-agnostic trackEvent/page-view tracking (google-analytics only implemented), consent-gated, single call site for setup + firing events
64
64
  └── components/
65
65
  ├── alert-content-inner.md — AlertContentInner: shared icon/body/dismiss molecule under AlertContent + AlertMaskedContent; app.config icon map (alertContent.icons + dismissIcon) covers all consumers
66
+ ├── animated-svg-text.md — AnimatedSvgText: inline SVG stroke-draw-then-fill animation, text slot, CSS token API
66
67
  ├── accordian-core.md — AccordianCore indexed dynamic slots (accordian-{n}-summary/icon/content), exclusive-open grouping
67
68
  ├── eyebrow-text.md — EyebrowText props, usage patterns, styling
68
69
  ├── hero-text.md — HeroText props, usage patterns, styling
@@ -88,6 +89,7 @@ Each skill is a single markdown file named `<area>-<task>.md`.
88
89
  ├── grid-stack.md — GridStack: CSS Grid z-axis stacking, slot API, z-order rules, sizing, video+overlay and image+text patterns
89
90
  ├── scroll-reveal-frame.md — ScrollRevealFrame: generic parallax clipping frame, slot API, image grid pattern, CSS tokens, browser support
90
91
  ├── scroll-reveal-image.md — ScrollRevealImage: single-image parallax reveal, focalX, imgWidth/imgHeight, responsive frame height
92
+ ├── marquee-scroller.md — MarqueeScroller: infinite logo/badge scroller, per-item dynamic slots (marqueeData id), hover/focus/keyboard pause, reduced-motion, CSS tokens
91
93
  ├── site-navigation.md — SiteNavigation: responsive nav with auto-collapse, burger menu, decorator indicators, CSS token API
92
94
  ├── tab-navigation.md — TabNavigation: horizontal tab nav with CSS anchor-positioning indicators, anchor scroll, burger collapse, full CSS token API
93
95
  ├── social-icons-list.md — SocialIconsList: data-driven social icon links, ISocialIcon type, logos: icon names, CSS tokens
@@ -109,7 +111,12 @@ Each skill is a single markdown file named `<area>-<task>.md`.
109
111
  ├── site-header.md — SiteHeader: PageRow + SkipLinks + ResponsiveHeader composition, #branding/#secondaryNavigation slots, dual styleClassPassthrough hooks
110
112
  ├── responsive-header.md — ResponsiveHeader: overflow-collapsing adaptive nav, measurement-pipeline gotchas (unsized icons, vw font-size drift), full CSS token API
111
113
  ├── navigation-items.md — NavigationItems: internal overflow-panel renderer for ResponsiveHeader, complement-visibility logic, not used standalone
112
- └── cookie-consent-banner.md — CookieConsentBanner: fixed non-modal Accept/Reject banner driven by useCookieConsent, message/acceptLabel/rejectLabel slots, CSS token API
114
+ ├── cookie-consent-banner.md — CookieConsentBanner: fixed non-modal Accept/Reject banner driven by useCookieConsent, message/acceptLabel/rejectLabel slots, CSS token API
115
+ ├── display-banner.md — DisplayBanner: canvas/content stacked overlay banner, conditional slot wrappers, CSS token API
116
+ ├── deep-expanding-menu.md — DeepExpandingMenu: anchor-positioned popover nav panels, browser support caveat, CSS token API
117
+ ├── deep-expanding-menu-classic.md — DeepExpandingMenuClassic: <details>-based fallback nav, click-outside close, CSS token API
118
+ ├── display-tooltip.md — DisplayTooltip: anchor-positioned popover trigger, browser support caveat, CSS token API
119
+ └── display-tooltip-defined.md — DisplayTooltipDefined: structured title/body/action tooltip content with close button, composes DisplayTooltip
113
120
  ```
114
121
 
115
122
  ## Skill file template
@@ -240,3 +240,33 @@ Key points:
240
240
  them in the Storybook controls panel (e.g. `"Model"`, `"Basic"`, `"Validation"`, `"Styling"`, `"Slots"`).
241
241
  - Export multiple named stories (`Default`, `WithError`, `Outlined`, etc.) when you want
242
242
  Playwright to test distinct visual states via separate story URLs.
243
+
244
+ ## Scroll/animation-driven effects need surrounding chrome, not just the bare component
245
+
246
+ A story that renders a scroll- or timer-driven component with no other markup often fails to
247
+ show the effect at all — not because the component is broken, but because the demo gives the
248
+ viewer nothing to judge it against. Things that make an animated/scroll-driven effect illegible
249
+ in isolation:
250
+
251
+ - The effect resolves over a short distance/time relative to the page (e.g. a 100px scroll
252
+ window on an otherwise-long page) — easy to scroll straight past it.
253
+ - The visual change is a subtle crop/shift on photographic content, where the eye has no
254
+ reference point to notice a boundary moving.
255
+ - There's no indicator of *where* or *when* the effect completes.
256
+
257
+ Add scaffolding around the component to fix this, rather than assuming a bigger/slower prop
258
+ value alone solves it:
259
+
260
+ - A fixed marker (a line, label) at the point in the viewport where the effect's key transition
261
+ happens (e.g. `position: fixed; top: 0` for a component that resolves when its own top hits
262
+ the viewport top).
263
+ - A ruled/striped overlay or contrasting background behind the animated content so a boundary
264
+ (clip edge, wipe line, fade) is visible against it, not just a crop of photo pixels.
265
+ - Short on-page instructions telling the viewer what to do and what to watch for ("scroll
266
+ slowly — the image clips in over the last 100px before the red line").
267
+ - A second story with an exaggerated prop value (larger distance/duration) purely so the effect
268
+ is easy to preview without precise scrolling/timing — see `ClipElement`'s `LargeClipDistance`
269
+ story for the pattern (`.claude/skills/components/clip-element.md`).
270
+
271
+ This is a documentation-only concern — it doesn't change the component's default props, just
272
+ how the story demonstrates it.
@@ -0,0 +1,15 @@
1
+ {
2
+ "SRCDEV AnimatedSvgText": {
3
+ "description": "AnimatedSvgText — stroke-draw-then-fill animation for inline SVG text/path content",
4
+ "scope": "vue,html",
5
+ "body": [
6
+ "<AnimatedSvgText>",
7
+ " <template #text>",
8
+ " <svg viewBox=\"0 0 $1600 $2150\">",
9
+ " <path d=\"$3\" />",
10
+ " </svg>",
11
+ " </template>",
12
+ "</AnimatedSvgText>"
13
+ ]
14
+ }
15
+ }
@@ -10,5 +10,18 @@
10
10
  " ]\"",
11
11
  "/>"
12
12
  ]
13
+ },
14
+ "SRCDEV Breadcrumb Localised": {
15
+ "description": "Breadcrumb with a localised aria-label",
16
+ "scope": "vue,html",
17
+ "body": [
18
+ "<Breadcrumb",
19
+ " :items=\"[",
20
+ " { label: '$1Services', to: '$2/services' },",
21
+ " { label: '$3${TM_FILENAME_BASE}' },",
22
+ " ]\"",
23
+ " aria-label=\"${4:Breadcrumb}\"",
24
+ "/>"
25
+ ]
13
26
  }
14
27
  }
@@ -0,0 +1,9 @@
1
+ {
2
+ "SRCDEV CanvasSwitcher": {
3
+ "description": "CanvasSwitcher — v-model:canvas-name icon button group for MediaCanvas preview widths",
4
+ "scope": "vue,html",
5
+ "body": [
6
+ "<CanvasSwitcher v-model:canvas-name=\"${1:canvasName}\" />"
7
+ ]
8
+ }
9
+ }
@@ -0,0 +1,20 @@
1
+ {
2
+ "SRCDEV ClipElement Basic": {
3
+ "description": "ClipElement — scroll-driven clip-path reveal for a single image or block",
4
+ "scope": "vue,html",
5
+ "body": [
6
+ "<ClipElement :max-clip=\"$1100\">",
7
+ " <img src=\"$2/images/hero.jpg\" alt=\"$3Hero\" style=\"width: 100%; display: block;\" />",
8
+ "</ClipElement>"
9
+ ]
10
+ },
11
+ "SRCDEV ClipElement With Passthrough": {
12
+ "description": "ClipElement with styleClassPassthrough for a custom wrapper class",
13
+ "scope": "vue,html",
14
+ "body": [
15
+ "<ClipElement :max-clip=\"$1150\" style-class-passthrough=\"$2my-clip-wrapper\">",
16
+ " $0",
17
+ "</ClipElement>"
18
+ ]
19
+ }
20
+ }
@@ -0,0 +1,23 @@
1
+ {
2
+ "SRCDEV ClippedPanel Basic": {
3
+ "description": "ClippedPanel — fixed-size notched/cutout shape panel",
4
+ "scope": "vue,html",
5
+ "body": [
6
+ "<ClippedPanel variant=\"$1square\">",
7
+ " $0",
8
+ "</ClippedPanel>"
9
+ ]
10
+ },
11
+ "SRCDEV ClippedPanel Custom Colours": {
12
+ "description": "ClippedPanel with background/outline colour overrides",
13
+ "scope": "vue,html",
14
+ "body": [
15
+ "<ClippedPanel",
16
+ " variant=\"$1square\"",
17
+ " style=\"--clipped-panel-background-colour: $2#fef3e7; --clipped-panel-outline-colour: $3#b5651d;\"",
18
+ ">",
19
+ " $0",
20
+ "</ClippedPanel>"
21
+ ]
22
+ }
23
+ }
@@ -33,6 +33,24 @@
33
33
  "</ContentDocs>"
34
34
  ]
35
35
  },
36
+ "SRCDEV ContentDocs Localised Nav Labels": {
37
+ "description": "ContentDocs with localised aria-labels on the docsNav/docsPageNav <nav> landmarks",
38
+ "scope": "vue,html",
39
+ "body": [
40
+ "<ContentDocs",
41
+ " v-model:active-nav-item=\"$1activeNavItem\"",
42
+ " v-model:active-page-nav-item=\"$2activePageNavItem\"",
43
+ " :docs-nav-items=\"$3docsNavItems\"",
44
+ " :docs-page-nav-items=\"$4docsPageNavItems\"",
45
+ " docs-nav-aria-label=\"${5:Docs navigation}\"",
46
+ " docs-page-nav-aria-label=\"${6:On this page}\"",
47
+ ">",
48
+ " <template #docsContent>",
49
+ " <p>$7Page content goes here.</p>",
50
+ " </template>",
51
+ "</ContentDocs>"
52
+ ]
53
+ },
36
54
  "SRCDEV ContentDocs Items Script": {
37
55
  "description": "docsNavItems / docsPageNavItems + active-item refs for ContentDocs",
38
56
  "scope": "typescript,vue",
@@ -0,0 +1,9 @@
1
+ {
2
+ "SRCDEV DeepExpandingMenuClassic Basic": {
3
+ "description": "DeepExpandingMenuClassic — <details>-based fallback nav",
4
+ "scope": "vue,html",
5
+ "body": [
6
+ "<DeepExpandingMenuClassic :nav-links=\"$1navLinks\" />"
7
+ ]
8
+ }
9
+ }
@@ -0,0 +1,9 @@
1
+ {
2
+ "SRCDEV DeepExpandingMenu Basic": {
3
+ "description": "DeepExpandingMenu — anchor-positioned popover nav panels",
4
+ "scope": "vue,html",
5
+ "body": [
6
+ "<DeepExpandingMenu :nav-links=\"$1navLinks\" />"
7
+ ]
8
+ }
9
+ }
@@ -0,0 +1,30 @@
1
+ {
2
+ "SRCDEV DisplayBanner Basic": {
3
+ "description": "DisplayBanner — canvas/content stacked overlay banner",
4
+ "scope": "vue,html",
5
+ "body": [
6
+ "<DisplayBanner>",
7
+ " <template #canvas>",
8
+ " $1",
9
+ " </template>",
10
+ " <template #content>",
11
+ " $0",
12
+ " </template>",
13
+ "</DisplayBanner>"
14
+ ]
15
+ },
16
+ "SRCDEV DisplayBanner Min Height": {
17
+ "description": "DisplayBanner with min-height override",
18
+ "scope": "vue,html",
19
+ "body": [
20
+ "<DisplayBanner style=\"--display-banner-min-height: $140rem;\">",
21
+ " <template #canvas>",
22
+ " $2",
23
+ " </template>",
24
+ " <template #content>",
25
+ " $0",
26
+ " </template>",
27
+ "</DisplayBanner>"
28
+ ]
29
+ }
30
+ }
@@ -0,0 +1,29 @@
1
+ {
2
+ "SRCDEV DisplayTooltipDefined Basic": {
3
+ "description": "DisplayTooltipDefined — structured title/body/action tooltip",
4
+ "scope": "vue,html",
5
+ "body": [
6
+ "<DisplayTooltipDefined",
7
+ " :content-text=\"{",
8
+ " tooltipTitle: { tag: 'h4', text: '$1' },",
9
+ " tooltipContent: { tag: 'p', text: '$0' },",
10
+ " }\"",
11
+ "/>"
12
+ ]
13
+ },
14
+ "SRCDEV DisplayTooltipDefined Localised Copy": {
15
+ "description": "DisplayTooltipDefined with localised trigger/close-button copy",
16
+ "scope": "vue,html",
17
+ "body": [
18
+ "<DisplayTooltipDefined",
19
+ " :content-text=\"{",
20
+ " tooltipTitle: { tag: 'h4', text: '$1' },",
21
+ " tooltipContent: { tag: 'p', text: '$2' },",
22
+ " }\"",
23
+ " trigger-aria-label=\"${3:Toggle the popover}\"",
24
+ " close-button-text=\"${4:Close}\"",
25
+ " close-button-aria-label=\"${5:Close tool tip}\"",
26
+ "/>"
27
+ ]
28
+ }
29
+ }
@@ -0,0 +1,38 @@
1
+ {
2
+ "SRCDEV DisplayTooltip Basic": {
3
+ "description": "DisplayTooltip — anchor-positioned popover trigger",
4
+ "scope": "vue,html",
5
+ "body": [
6
+ "<DisplayTooltip>",
7
+ " <template #tooltipContent>",
8
+ " <p>$0</p>",
9
+ " </template>",
10
+ "</DisplayTooltip>"
11
+ ]
12
+ },
13
+ "SRCDEV DisplayTooltip With Trigger Content": {
14
+ "description": "DisplayTooltip with a triggerContent label",
15
+ "scope": "vue,html",
16
+ "body": [
17
+ "<DisplayTooltip>",
18
+ " <template #triggerContent>",
19
+ " <span>$1</span>",
20
+ " </template>",
21
+ " <template #tooltipContent>",
22
+ " <p>$0</p>",
23
+ " </template>",
24
+ "</DisplayTooltip>"
25
+ ]
26
+ },
27
+ "SRCDEV DisplayTooltip Localised Trigger Label": {
28
+ "description": "DisplayTooltip with a localised triggerAriaLabel",
29
+ "scope": "vue,html",
30
+ "body": [
31
+ "<DisplayTooltip trigger-aria-label=\"${1:Toggle the popover}\">",
32
+ " <template #tooltipContent>",
33
+ " <p>$0</p>",
34
+ " </template>",
35
+ "</DisplayTooltip>"
36
+ ]
37
+ }
38
+ }