srcdev-nuxt-components 9.4.6 → 9.4.8
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 +34 -3
- package/.claude/component-ledger/audit.json +1 -1
- package/.claude/component-ledger/build.mjs +27 -0
- package/.claude/component-ledger/output.html +8 -3
- package/.claude/component-ledger/template.html +7 -2
- package/.claude/skills/component-dynamic-slots.md +1 -1
- package/.claude/skills/components/column-flow-grid.md +93 -0
- package/.claude/skills/components/entry-animation.md +88 -0
- package/.claude/skills/components/header-block.md +92 -0
- package/.claude/skills/components/input-copy.md +2 -0
- package/.claude/skills/components/input-text-core.md +186 -0
- package/.claude/skills/components/masonry-grid.md +153 -0
- package/.claude/skills/components/pop-over.md +100 -0
- package/.claude/skills/components/rotating-carousel-image.md +101 -0
- package/.claude/skills/components/skip-links.md +82 -0
- package/.claude/skills/components/tabs-core.md +187 -0
- package/.claude/skills/index.md +10 -1
- package/.vscode/srcdev-component-column-flow-grid.code-snippets +39 -0
- package/.vscode/srcdev-component-entry-animation.code-snippets +29 -0
- package/.vscode/srcdev-component-header-block.code-snippets +41 -0
- package/.vscode/srcdev-component-input-text.code-snippets +107 -0
- package/.vscode/srcdev-component-masonry-grid.code-snippets +51 -0
- package/.vscode/srcdev-component-pop-over.code-snippets +44 -0
- package/.vscode/srcdev-component-rotating-carousel-image.code-snippets +74 -0
- package/.vscode/srcdev-component-skip-links.code-snippets +55 -0
- package/.vscode/srcdev-component-tabs-core.code-snippets +78 -0
- package/app/assets/styles/setup/05.typography/02.utility-classes/_font-classes-page-heading.css +2 -1
- package/app/components/01.atoms/animations/entry/EntryAnimation.vue +7 -1
- package/app/components/01.atoms/animations/entry/stories/EntryAnimation.stories.ts +47 -0
- package/app/components/01.atoms/animations/entry/tests/EntryAnimation.spec.ts +57 -0
- package/app/components/01.atoms/animations/rotating-carousel-image/CONSUMER-STYLING.md +54 -0
- package/app/components/01.atoms/animations/rotating-carousel-image/RotatingCarouselImage.vue +315 -0
- package/app/components/01.atoms/animations/rotating-carousel-image/stories/RotatingCarouselImage.stories.ts +156 -0
- package/app/components/01.atoms/animations/rotating-carousel-image/tests/RotatingCarouselImage.spec.ts +230 -0
- package/app/components/01.atoms/animations/rotating-carousel-image/tests/__snapshots__/RotatingCarouselImage.spec.ts.snap +19 -0
- package/app/components/01.atoms/canvas-switcher/stories/CanvasSwitcher.stories.ts +9 -10
- package/app/components/01.atoms/content-wrappers/docs-pages/stories/ContentDocs.stories.ts +12 -13
- package/app/components/01.atoms/grids/column-flow-grid/CONSUMER-STYLING.md +33 -0
- package/app/components/01.atoms/grids/column-flow-grid/ColumnFlowGrid.vue +55 -0
- package/app/components/01.atoms/grids/column-flow-grid/stories/ColumnFlowGrid.stories.ts +178 -0
- package/app/components/01.atoms/grids/column-flow-grid/tests/ColumnFlowGrid.spec.ts +75 -0
- package/app/components/01.atoms/grids/masonry-grid/CONSUMER-STYLING.md +35 -0
- package/app/components/01.atoms/grids/masonry-grid/MasonryGrid.vue +178 -0
- package/app/components/01.atoms/grids/masonry-grid/stories/MasonryGrid.stories.ts +199 -0
- package/app/components/01.atoms/grids/masonry-grid/tests/MasonryGrid.spec.ts +158 -0
- package/app/components/01.atoms/navigation/skip-links/CONSUMER-STYLING.md +31 -0
- package/app/components/01.atoms/navigation/skip-links/SkipLinks.vue +92 -0
- package/app/components/01.atoms/navigation/skip-links/stories/SkipLinks.stories.ts +113 -0
- package/app/components/01.atoms/navigation/skip-links/tests/SkipLinks.spec.ts +71 -0
- package/app/components/01.atoms/navigation/skip-links/tests/__snapshots__/SkipLinks.spec.ts.snap +15 -0
- package/app/components/01.atoms/navigation/tabs/CONSUMER-STYLING.md +99 -0
- package/app/components/01.atoms/navigation/tabs/TabsCore.vue +272 -0
- package/app/components/01.atoms/navigation/tabs/stories/TabsCore.stories.ts +199 -0
- package/app/components/01.atoms/navigation/tabs/tests/TabsCore.spec.ts +274 -0
- package/app/components/01.atoms/pop-over/CONSUMER-STYLING.md +54 -0
- package/app/components/01.atoms/pop-over/PopOver.vue +201 -0
- package/app/components/01.atoms/pop-over/stories/PopOver.stories.ts +141 -0
- package/app/components/01.atoms/pop-over/tests/PopOver.spec.ts +195 -0
- package/app/components/01.atoms/pop-over/tests/__snapshots__/PopOver.spec.ts.snap +11 -0
- package/app/components/01.atoms/text-blocks/header-block/CONSUMER-STYLING.md +43 -0
- package/app/components/01.atoms/text-blocks/header-block/HeaderBlock.vue +50 -0
- package/app/components/01.atoms/text-blocks/header-block/stories/HeaderBlock.stories.ts +111 -0
- package/app/components/01.atoms/text-blocks/header-block/tests/HeaderBlock.spec.ts +119 -0
- package/app/components/01.atoms/text-blocks/header-block/tests/__snapshots__/HeaderBlock.spec.ts.snap +5 -0
- package/app/components/02.molecules/action-menu/stories/ActionMenu.stories.ts +8 -2
- package/app/components/02.molecules/input-copy/InputCopy.vue +14 -0
- package/app/components/02.molecules/input-copy/stories/InputCopy.stories.ts +15 -0
- package/app/components/02.molecules/input-copy/tests/InputCopy.spec.ts +41 -0
- package/app/components/03.organisms/site-header/SiteHeader.vue +1 -1
- package/app/components/03.organisms/site-header/tests/SiteHeader.spec.ts +1 -1
- package/app/components/03.organisms/site-header/tests/__snapshots__/SiteHeader.spec.ts.snap +3 -3
- package/app/components/05.forms/input-select/InputSelectCore.vue +1 -1
- package/app/components/05.forms/input-text/InputTextCore.vue +6 -0
- package/app/components/05.forms/input-text/stories/InputPasswordWithLabel.stories.ts +49 -20
- package/app/components/05.forms/input-text/stories/InputTextAsNumberWithLabel.stories.ts +41 -22
- package/app/components/05.forms/input-text/stories/InputTextCore.stories.ts +48 -22
- package/app/components/05.forms/input-text/stories/InputTextWithLabel.stories.ts +43 -17
- package/app/components/05.forms/input-text/tests/InputPasswordWithLabel.spec.ts +56 -0
- package/app/components/05.forms/input-text/tests/InputTextAsNumberWithLabel.spec.ts +61 -0
- package/app/components/05.forms/input-text/tests/InputTextCore.spec.ts +60 -0
- package/app/components/05.forms/input-text/tests/InputTextWithLabel.spec.ts +46 -0
- package/app/components/05.forms/input-text/variants/InputPasswordWithLabel.vue +7 -1
- package/app/components/05.forms/input-text/variants/InputTextAsNumberWithLabel.vue +8 -2
- package/app/components/05.forms/input-text/variants/InputTextWithLabel.vue +6 -0
- package/app/components/05.forms/input-textarea/stories/InputTextareaCore.stories.ts +20 -20
- package/app/components/05.forms/input-textarea/stories/InputTextareaWithLabel.stories.ts +32 -26
- package/app/composables/useTabs.ts +225 -207
- package/app/types/components/index.ts +2 -0
- package/app/types/components/rotating-carousel-image.d.ts +4 -0
- package/app/types/components/skip-links.d.ts +4 -0
- package/app/types/forms/types.forms.d.ts +1 -1
- package/package.json +1 -1
- package/app/components/01.atoms/grids/data-grid/tests/__snapshots__/DataGrid.spec.ts.snap +0 -11
- package/app/components/masonry-grid/MasonryGrid.vue +0 -68
- package/app/components/masonry-grid-ordered/MasonryGridOrdered.vue +0 -163
- package/app/components/masonry-grid-ordered/MasonryGridOrderedGridExperiment.vue +0 -259
- package/app/components/masonry-grid-ordered/stories/MasonryGridOrdered.stories.ts +0 -354
- package/app/components/masonry-grid-sorted/MasonryGridSorted.vue +0 -120
- package/app/components/pop-over/PopOver.vue +0 -90
- package/app/components/rotating-carousel/RotatingCarouselImage.vue +0 -216
- package/app/components/skip-links/SkipLinks.vue +0 -60
- package/app/components/tabs/TabsCore.vue +0 -306
- package/app/components/typography/HeaderBlock.vue +0 -35
- package/app/layouts/default.vue +0 -308
- package/app/layouts/site-navigation-demo.vue +0 -188
- /package/app/components/01.atoms/grids/{data-grid → auto-grid}/AutoGrid.vue +0 -0
- /package/app/components/01.atoms/grids/{data-grid → auto-grid}/stories/AutoGrid.stories.ts +0 -0
- /package/app/components/01.atoms/grids/{data-grid → auto-grid}/tests/AutoGrid.spec.ts +0 -0
- /package/app/components/01.atoms/grids/{data-grid → auto-grid}/tests/__snapshots__/AutoGrid.spec.ts.snap +0 -0
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: PopOver
|
|
3
|
+
description: PopOver generic anchor-positioned disclosure panel with consumer-supplied trigger/content slots, placement prop, CSS token API
|
|
4
|
+
type: reference
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# PopOver
|
|
8
|
+
|
|
9
|
+
## Overview
|
|
10
|
+
|
|
11
|
+
`PopOver` renders a fully consumer-supplied trigger that opens an anchor-positioned popover panel
|
|
12
|
+
with fully consumer-supplied content, built on the native Popover API
|
|
13
|
+
(`popover`/`popovertarget`) and CSS anchor-positioning (`anchor-name`/`position-anchor`/
|
|
14
|
+
`anchor()`). Unlike [`DisplayTooltip`](display-tooltip.md) (a fixed icon trigger for inline help
|
|
15
|
+
text) or `ActionMenu` (menu semantics with arrow-key navigation), `PopOver` makes no assumption
|
|
16
|
+
about what the trigger or content look like, and always renders its own visible close button.
|
|
17
|
+
|
|
18
|
+
**Browser support caveat:** CSS anchor-positioning and the Popover API are both comparatively
|
|
19
|
+
recent — check current browser support before relying on this where broad support is a hard
|
|
20
|
+
requirement, same caution as `DisplayTooltip`/`DeepExpandingMenu`'s equivalent gap.
|
|
21
|
+
|
|
22
|
+
## Props
|
|
23
|
+
|
|
24
|
+
| Prop | Type | Default | Description |
|
|
25
|
+
|------|------|---------|-------------|
|
|
26
|
+
| `placement` | `"top" \| "right" \| "bottom" \| "left"` | `"right"` | Which side of the trigger the popover panel opens on. Each side has a `position-try-fallbacks` so the browser flips it automatically if it would overflow the viewport. |
|
|
27
|
+
| `triggerAriaLabel` | `string` | `""` | aria-label on the trigger button — set this when the `trigger` slot is icon-only. Unset (no attribute) by default, since the trigger slot usually carries its own visible/accessible content. |
|
|
28
|
+
| `popoverAriaLabel` | `string` | `""` | aria-label on the popover content region — set this when the `content` slot has no visible heading. |
|
|
29
|
+
| `closeButtonAriaLabel` | `string` | `"Close"` | aria-label on the close button — override for localisation. |
|
|
30
|
+
| `styleClassPassthrough` | `string \| string[]` | `[]` | Extra classes applied to the root element. |
|
|
31
|
+
|
|
32
|
+
## Slots
|
|
33
|
+
|
|
34
|
+
| Slot | Description |
|
|
35
|
+
|------|-------------|
|
|
36
|
+
| `trigger` | Content rendered inside the trigger button. |
|
|
37
|
+
| `content` | Content rendered inside the popover panel, below the close button. |
|
|
38
|
+
|
|
39
|
+
## Basic usage
|
|
40
|
+
|
|
41
|
+
```vue
|
|
42
|
+
<PopOver placement="bottom">
|
|
43
|
+
<template #trigger>
|
|
44
|
+
<span>Open popover</span>
|
|
45
|
+
</template>
|
|
46
|
+
<template #content>
|
|
47
|
+
<p>This is some popover content.</p>
|
|
48
|
+
</template>
|
|
49
|
+
</PopOver>
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
Icon-only trigger:
|
|
53
|
+
|
|
54
|
+
```vue
|
|
55
|
+
<PopOver trigger-aria-label="Show filters" popover-aria-label="Filter options">
|
|
56
|
+
<template #trigger>
|
|
57
|
+
<Icon name="lucide:filter" aria-hidden="true" />
|
|
58
|
+
</template>
|
|
59
|
+
<template #content>
|
|
60
|
+
<p>Filter controls would go here.</p>
|
|
61
|
+
</template>
|
|
62
|
+
</PopOver>
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
## Notes on content
|
|
66
|
+
|
|
67
|
+
The `content` slot's immediate children have their default UA top/bottom margins trimmed (via
|
|
68
|
+
`:first-child`/`:last-child`), so `--pop-over-content-padding`/`-content-padding-block-start` are
|
|
69
|
+
the only source of space above/below the slotted content — a plain `<p>` doesn't stack its own
|
|
70
|
+
`margin-block` on top of the panel's padding. Interior spacing between multiple slotted children
|
|
71
|
+
(e.g. two paragraphs) is untouched.
|
|
72
|
+
|
|
73
|
+
## Behaviour
|
|
74
|
+
|
|
75
|
+
- Opening/closing is handled natively by the Popover API: clicking the trigger toggles the panel,
|
|
76
|
+
clicking the close button or pressing Escape hides it, and focus returns to the trigger
|
|
77
|
+
automatically on close.
|
|
78
|
+
- On open, focus moves to the close button (`handleToggle`, driven by the popover's native
|
|
79
|
+
`toggle` event) so keyboard users land inside the panel immediately.
|
|
80
|
+
|
|
81
|
+
## CSS custom properties
|
|
82
|
+
|
|
83
|
+
See `CONSUMER-STYLING.md` for the full `--pop-over-*` token API (gap, trigger outline, panel
|
|
84
|
+
border/background/text/shadow/width, content padding, and close-button tokens).
|
|
85
|
+
|
|
86
|
+
## Notes
|
|
87
|
+
|
|
88
|
+
- Auto-imported in Nuxt — no manual import needed.
|
|
89
|
+
- 2026-09-13 migration: moved from an unplaced top-level folder (`app/components/pop-over/`) into
|
|
90
|
+
`01.atoms/pop-over/`; converted options-style `defineProps` to `interface Props` +
|
|
91
|
+
`withDefaults`; the slot named `popoverCotent` (typo) was renamed to `content`, and the required
|
|
92
|
+
`popovertarget` prop was replaced with an auto-generated id (`useId()`) so consumers no longer
|
|
93
|
+
need to wire up matching ids themselves; the `role="tooltip"` on the popover panel was removed
|
|
94
|
+
(it described a tooltip, not this component's actual arbitrary-content disclosure semantics);
|
|
95
|
+
added a real, visible close button (the original template referenced one with no styling or
|
|
96
|
+
accessible label — literal `x` text); added `placement` (`top`/`right`/`bottom`/`left`, all with
|
|
97
|
+
`position-try-fallbacks`) — the original only supported opening to the right, with several
|
|
98
|
+
`@position-try` blocks commented out and unused; promoted every hardcoded value to a public CSS
|
|
99
|
+
token; added tests, a Storybook story, this skill doc, and a VS Code snippet (all previously
|
|
100
|
+
missing).
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: RotatingCarouselImage
|
|
3
|
+
description: RotatingCarouselImage 3D rotating image carousel — scroll-parallax tilt, props, accessibility controls, CSS tokens, consumer styling
|
|
4
|
+
type: reference
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# RotatingCarouselImage
|
|
8
|
+
|
|
9
|
+
## Overview
|
|
10
|
+
|
|
11
|
+
`RotatingCarouselImage` renders a set of images arranged in a circle and continuously rotated with
|
|
12
|
+
a CSS animation, tilted in 3D via `perspective`/`rotateX`. By default (`useParallaxEffect: true`)
|
|
13
|
+
the tilt follows scroll position instead of a fixed value — the carousel leans more as it enters
|
|
14
|
+
and leaves the viewport centre. The rotation pauses automatically on keyboard focus and when the
|
|
15
|
+
user has `prefers-reduced-motion` set, and can optionally expose a visible pause/play button.
|
|
16
|
+
|
|
17
|
+
## Props
|
|
18
|
+
|
|
19
|
+
| Prop | Type | Default | Description |
|
|
20
|
+
|------|------|---------|-------------|
|
|
21
|
+
| `data` | `CarouselImageData[]` (`{ src: string; alt: string }[]`) | `[]` | Images to render around the rotation. |
|
|
22
|
+
| `tag` | `"div" \| "p" \| "span" \| "section" \| "article" \| "aside" \| "header" \| "footer" \| "main" \| "nav" \| "ul" \| "ol"` | `"div"` | Root element tag. |
|
|
23
|
+
| `rotateX` | `number` | `0` | Static X-axis tilt in degrees. Only applied while `useParallaxEffect` is `false`. |
|
|
24
|
+
| `perspective` | `number` | `1000` | CSS perspective distance in pixels. |
|
|
25
|
+
| `translateZ` | `number` | `1000` | Radius of the rotation — distance each item sits from the centre, in pixels. |
|
|
26
|
+
| `pauseOnHover` | `boolean` | `false` | Pauses the rotation while the pointer hovers over the carousel. |
|
|
27
|
+
| `useParallaxEffect` | `boolean` | `true` | Drives the tilt from scroll position instead of the static `rotateX` prop. |
|
|
28
|
+
| `showControls` | `boolean` | `false` | Shows a visible pause/play button. Recommended (WCAG 2.2.2) for any auto-rotating content. |
|
|
29
|
+
| `respectReducedMotion` | `boolean` | `true` | Auto-pauses and disables the animation for `prefers-reduced-motion: reduce`. |
|
|
30
|
+
| `ariaLabel` | `string` | `"Rotating image carousel"` | `aria-label` on the root region — override for localisation. |
|
|
31
|
+
| `ariaDescription` | `string` | `"Use spacebar to pause or play the rotation."` | Screen-reader-only instructions rendered inside the region — override for localisation. |
|
|
32
|
+
| `playIcon` | `string` | `"mdi:play"` | Iconify icon name shown on the control button while paused. Ignored if the `toggle-icon` slot is used. |
|
|
33
|
+
| `pauseIcon` | `string` | `"mdi:pause"` | Iconify icon name shown on the control button while playing. Ignored if the `toggle-icon` slot is used. |
|
|
34
|
+
| `playLabel` | `string` | `"Play rotation"` | Control button `aria-label` while paused — override for localisation. |
|
|
35
|
+
| `pauseLabel` | `string` | `"Pause rotation"` | Control button `aria-label` while playing — override for localisation. |
|
|
36
|
+
| `styleClassPassthrough` | `string \| string[]` | `[]` | Extra classes applied to the root element. |
|
|
37
|
+
|
|
38
|
+
Import the data type from the library's type exports when building `data` outside the component:
|
|
39
|
+
|
|
40
|
+
```ts
|
|
41
|
+
import type { CarouselImageData } from "srcdev-nuxt-components";
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
## Slots
|
|
45
|
+
|
|
46
|
+
`toggle-icon` (scoped with `{ isPaused: boolean }`) replaces the control button's icon entirely —
|
|
47
|
+
same pattern as `MarqueeScroller`:
|
|
48
|
+
|
|
49
|
+
```vue
|
|
50
|
+
<RotatingCarouselImage :data="images" show-controls>
|
|
51
|
+
<template #toggle-icon="{ isPaused }">
|
|
52
|
+
<Icon :name="isPaused ? 'lucide:play' : 'lucide:pause'" />
|
|
53
|
+
</template>
|
|
54
|
+
</RotatingCarouselImage>
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
## Accessibility behaviour
|
|
58
|
+
|
|
59
|
+
- The root is `role="region"` with `tabindex="0"` so it's independently focusable.
|
|
60
|
+
- Spacebar toggles pause/play while the region is focused.
|
|
61
|
+
- Focusing the region always pauses the rotation so keyboard users can inspect it; blurring
|
|
62
|
+
resumes it unless the user prefers reduced motion.
|
|
63
|
+
- Hovering the carousel pauses it only when `pauseOnHover` is `true` (default `false`, preserving
|
|
64
|
+
the component's original opt-in behaviour).
|
|
65
|
+
- All user-visible copy (`ariaLabel`, `ariaDescription`, `playLabel`, `pauseLabel`) is a plain
|
|
66
|
+
string prop with an English default — pass translated strings from the consumer's own i18n
|
|
67
|
+
solution.
|
|
68
|
+
|
|
69
|
+
## Basic usage
|
|
70
|
+
|
|
71
|
+
```vue
|
|
72
|
+
<RotatingCarouselImage
|
|
73
|
+
:data="[
|
|
74
|
+
{ src: '/carousel/1.jpg', alt: 'Product shot 1' },
|
|
75
|
+
{ src: '/carousel/2.jpg', alt: 'Product shot 2' },
|
|
76
|
+
]"
|
|
77
|
+
show-controls
|
|
78
|
+
/>
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
Disable scroll-driven tilt for a static, self-contained rotation:
|
|
82
|
+
|
|
83
|
+
```vue
|
|
84
|
+
<RotatingCarouselImage :data="images" :use-parallax-effect="false" :rotate-x="10" show-controls />
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
## CSS custom properties
|
|
88
|
+
|
|
89
|
+
See `CONSUMER-STYLING.md` in this component's folder for the full public token table (carousel
|
|
90
|
+
height, item size, rotation duration, control button styling). `rotateX`/`perspective`/
|
|
91
|
+
`translateZ` are prop-driven, not tokens, since they also drive the component's own scroll-parallax
|
|
92
|
+
math.
|
|
93
|
+
|
|
94
|
+
## Migration note (2026-09-15)
|
|
95
|
+
|
|
96
|
+
Previously lived at `app/components/rotating-carousel/RotatingCarouselImage.vue` with
|
|
97
|
+
options-style `defineProps`, no accessibility controls (no focus/keyboard pause, no visible pause
|
|
98
|
+
button), and several hardcoded CSS values (item size, rotation duration, carousel height) with no
|
|
99
|
+
override hook. Moved to `01.atoms/animations/rotating-carousel-image/`, migrated to
|
|
100
|
+
`interface Props` + `withDefaults`, and given the same pause/focus/reduced-motion/control-button
|
|
101
|
+
pattern as `MarqueeScroller`.
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: SkipLinks
|
|
3
|
+
description: SkipLinks accessibility skip-navigation utility — focus-revealed jump links, props, CSS tokens, consumer styling
|
|
4
|
+
type: reference
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# SkipLinks
|
|
8
|
+
|
|
9
|
+
## Overview
|
|
10
|
+
|
|
11
|
+
`SkipLinks` renders a set of visually-hidden links that become visible on keyboard focus, letting
|
|
12
|
+
keyboard and screen-reader users jump past repeated navigation straight to main content, the
|
|
13
|
+
footer, or any other in-page target. Place it first in the page layout, before the header/nav, so
|
|
14
|
+
it's the first focusable element on the page.
|
|
15
|
+
|
|
16
|
+
## Props
|
|
17
|
+
|
|
18
|
+
| Prop | Type | Default | Required |
|
|
19
|
+
|------|------|---------|----------|
|
|
20
|
+
| `links` | `SkipLink[]` (`{ href: string; label: string }[]`) | `[{ href: "#main-content", label: "Skip to main content" }, { href: "#footer-content", label: "Skip to footer" }]` | no |
|
|
21
|
+
| `ariaLabel` | `string` | `"Skip navigation"` | no — override for localisation |
|
|
22
|
+
| `styleClassPassthrough` | `string \| string[]` | `[]` | no |
|
|
23
|
+
|
|
24
|
+
`SkipLink` is exported from `~/types/components/skip-links` (and re-exported from
|
|
25
|
+
`~/types/components`):
|
|
26
|
+
|
|
27
|
+
```typescript
|
|
28
|
+
interface SkipLink {
|
|
29
|
+
href: string;
|
|
30
|
+
label: string;
|
|
31
|
+
}
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
## Slots
|
|
35
|
+
|
|
36
|
+
`homeLink` — optional content (typically a logo/home link) rendered before the skip-nav, wrapped
|
|
37
|
+
in `.skip-links__home`. Only rendered when the slot is actually used.
|
|
38
|
+
|
|
39
|
+
```vue
|
|
40
|
+
<SkipLinks>
|
|
41
|
+
<template #homeLink>
|
|
42
|
+
<NuxtLink to="/" class="logo">Acme</NuxtLink>
|
|
43
|
+
</template>
|
|
44
|
+
</SkipLinks>
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
## Usage
|
|
48
|
+
|
|
49
|
+
```vue
|
|
50
|
+
<SkipLinks
|
|
51
|
+
:links="[
|
|
52
|
+
{ href: '#site-navigation', label: 'Skip to navigation' },
|
|
53
|
+
{ href: '#main-content', label: 'Skip to main content' },
|
|
54
|
+
{ href: '#footer-content', label: 'Skip to footer' },
|
|
55
|
+
]"
|
|
56
|
+
/>
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
Each `href` must match the `id` of a real element on the page (e.g. `<main id="main-content">`) —
|
|
60
|
+
the component does not create those anchors itself.
|
|
61
|
+
|
|
62
|
+
## Accessibility behaviour
|
|
63
|
+
|
|
64
|
+
- Links are hidden via `opacity`/`transform`, not `display`/`visibility` — they stay in the tab
|
|
65
|
+
order and keyboard-focusable even while visually hidden.
|
|
66
|
+
- The panel reveals on `:focus-within`, so tabbing onto any link shows the whole group.
|
|
67
|
+
- Wrapped in a `<nav :aria-label="ariaLabel">` landmark, defaulting to `"Skip navigation"`.
|
|
68
|
+
- The reveal transition is disabled under `prefers-reduced-motion: reduce`.
|
|
69
|
+
- `ariaLabel` and every link's `label` are plain string props/fields — pass translated strings
|
|
70
|
+
from the consumer's own i18n solution.
|
|
71
|
+
|
|
72
|
+
## CSS custom properties
|
|
73
|
+
|
|
74
|
+
See `CONSUMER-STYLING.md` in this component's folder for the full `--skip-links-*` token API
|
|
75
|
+
(panel background/border, link text colour, spacing, transition duration, focus outline).
|
|
76
|
+
|
|
77
|
+
## Notes
|
|
78
|
+
|
|
79
|
+
- Component is auto-imported in Nuxt — no import needed.
|
|
80
|
+
- Migrated 2026-09-15 from the unplaced `skip-links/` folder: the link set was previously
|
|
81
|
+
hardcoded (two fixed `<a>` tags with hardcoded English copy) — now data-driven via `links` so
|
|
82
|
+
consumers can add/remove targets and localise the copy without forking the component.
|
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: TabsCore
|
|
3
|
+
description: TabsCore tab list/panel widget — indexed dynamic slots, arrow-key navigation, moving hover/active/underline indicators, CSS tokens
|
|
4
|
+
type: reference
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# TabsCore
|
|
8
|
+
|
|
9
|
+
## Overview
|
|
10
|
+
|
|
11
|
+
`TabsCore` renders a WAI-ARIA tablist from indexed slots. The number of tabs is set via
|
|
12
|
+
`itemCount`; each tab's trigger and content come from `tab-{n}-trigger`/`tab-{n}-content` slots.
|
|
13
|
+
Indexed slots (rather than named dynamic slots) are used here deliberately — `itemCount` is needed
|
|
14
|
+
for ARIA linking across the two parallel loops (triggers and panels), not just the slot loop
|
|
15
|
+
itself.
|
|
16
|
+
|
|
17
|
+
Three independent moving-highlight decorators track pointer/keyboard state: a hover highlight, an
|
|
18
|
+
active-tab highlight, and an underline/sideline indicator — each can be turned off individually.
|
|
19
|
+
|
|
20
|
+
## Props
|
|
21
|
+
|
|
22
|
+
| Prop | Type | Default | Required |
|
|
23
|
+
|------|------|---------|----------|
|
|
24
|
+
| `itemCount` | `number` | — | **yes** — number of tabs; drives both slot loops |
|
|
25
|
+
| `axis` | `"x" \| "y"` | `"x"` | no — horizontal row or vertical column layout |
|
|
26
|
+
| `transitionDuration` | `number` | `200` | no — ms duration of the moving indicators |
|
|
27
|
+
| `trackHover` | `boolean` | `true` | no — shows the moving hover highlight |
|
|
28
|
+
| `trackActive` | `boolean` | `true` | no — shows the moving active-tab highlight |
|
|
29
|
+
| `trackIndicator` | `boolean` | `true` | no — shows the moving underline/sideline indicator |
|
|
30
|
+
| `ariaLabel` | `string` | `"Tabs"` | no — override for localisation |
|
|
31
|
+
| `styleClassPassthrough` | `string \| string[]` | `[]` | no |
|
|
32
|
+
|
|
33
|
+
## Slots
|
|
34
|
+
|
|
35
|
+
For each index `0..itemCount-1`:
|
|
36
|
+
|
|
37
|
+
- `tab-{n}-trigger` — content of the tab button.
|
|
38
|
+
- `tab-{n}-content` — content of that tab's panel.
|
|
39
|
+
|
|
40
|
+
```vue
|
|
41
|
+
<TabsCore :item-count="2">
|
|
42
|
+
<template #tab-0-trigger>Overview</template>
|
|
43
|
+
<template #tab-0-content>Overview content…</template>
|
|
44
|
+
<template #tab-1-trigger>Specs</template>
|
|
45
|
+
<template #tab-1-content>Specs content…</template>
|
|
46
|
+
</TabsCore>
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
## Accessibility behaviour
|
|
50
|
+
|
|
51
|
+
- Renders `role="tablist"` (with `aria-label`), `role="tab"` triggers (`type="button"`), and
|
|
52
|
+
`role="tabpanel"` panels (`aria-labelledby` pointing at their trigger, `tabindex="0"` so keyboard
|
|
53
|
+
users can scroll into panel content per the WAI-ARIA Tabs Pattern).
|
|
54
|
+
Fixed 2026-09-15 — panels previously used `role="region"` and were hardcoded
|
|
55
|
+
`aria-hidden="true"` permanently, even the visible one, hiding all tab content from assistive
|
|
56
|
+
tech at all times.
|
|
57
|
+
- Roving tabindex: only the active trigger has `tabindex="0"`; the rest are `-1`.
|
|
58
|
+
- Arrow keys move focus and activate the target tab (automatic activation): `ArrowLeft`/`ArrowRight`
|
|
59
|
+
on `axis="x"`, `ArrowUp`/`ArrowDown` on `axis="y"`, wrapping at the ends; `Home`/`End` jump to the
|
|
60
|
+
first/last tab. Fixed 2026-09-15 — previously there was no keyboard navigation at all beyond
|
|
61
|
+
native Tab-key focus and click/Enter/Space activation on individual buttons.
|
|
62
|
+
- Focus is visible via `:focus-visible` on both triggers and panels.
|
|
63
|
+
|
|
64
|
+
## CSS custom properties
|
|
65
|
+
|
|
66
|
+
See `CONSUMER-STYLING.md` in this component's folder for the full `--tabs-*` token API (indicator
|
|
67
|
+
colours, list-item spacing/typography, content-panel border/background, axis-y gap). Whether each
|
|
68
|
+
indicator renders at all is controlled by `trackHover`/`trackActive`/`trackIndicator`, not tokens.
|
|
69
|
+
|
|
70
|
+
## Migration note (2026-09-15)
|
|
71
|
+
|
|
72
|
+
Previously lived at `tabs/TabsCore.vue` with options-style `defineProps`, no tier folder, and
|
|
73
|
+
several defects fixed in this pass:
|
|
74
|
+
|
|
75
|
+
- `querySelectorAll("[data-nav-item")` was missing its closing `]` — an invalid selector that
|
|
76
|
+
some browsers tolerate but that throws in strict DOM implementations (including this repo's test
|
|
77
|
+
environment). Fixed to `"[data-nav-item]"`.
|
|
78
|
+
- `aria-labelledby="channel-name"` was hardcoded on the tablist, pointing at an id that doesn't
|
|
79
|
+
exist anywhere in this library. Replaced with the new `ariaLabel` prop.
|
|
80
|
+
- `border-bottom`/`border-left` on the tab list referenced `--_tabs-border-bottom`, a private CSS
|
|
81
|
+
variable that was never set anywhere (dead reference — the border never rendered). Promoted to
|
|
82
|
+
the public `--tabs-nav-border` token with a real default.
|
|
83
|
+
- `tag`, `trackHover`, `trackActive`, and `trackIndicator` were all declared props with no effect
|
|
84
|
+
anywhere in the component or its composable (`useTabs`). `tag` had no coherent purpose (triggers
|
|
85
|
+
were always hardcoded `<button>`s) and was removed; the three `track*` flags were wired up to
|
|
86
|
+
actually gate their respective decorator elements, since removing them would have been a bigger
|
|
87
|
+
behavioural change than making them work as their names already implied.
|
|
88
|
+
- Almost every colour/spacing/typography value in the `<style>` block was hardcoded with no
|
|
89
|
+
override hook at all (not even a private token) — promoted to public `--tabs-*` tokens.
|
|
90
|
+
|
|
91
|
+
## Performance notes (2026-09-15)
|
|
92
|
+
|
|
93
|
+
`useTabs` had a few real perf issues, fixed alongside the migration:
|
|
94
|
+
|
|
95
|
+
- **Layout thrashing**: `moveActiveIndicator`/`moveHoveredIndicator`/`setFinalActivePositions`/
|
|
96
|
+
`setFinalHoveredPositions` used to interleave `offsetLeft`/`offsetWidth`/etc. reads with
|
|
97
|
+
`style.setProperty` writes, forcing a synchronous reflow between each pair. All layout reads are
|
|
98
|
+
now batched into local variables before any writes happen.
|
|
99
|
+
- **`@mouseover` → `@mouseenter`**: the hover trigger listener used `mouseover`, which bubbles and
|
|
100
|
+
re-fires as the pointer crosses any child element inside the button. `mouseenter` fires exactly
|
|
101
|
+
once per tab entered.
|
|
102
|
+
- **Debounced settle timers**: `moveActiveIndicator`/`moveHoveredIndicator` schedule a `setTimeout`
|
|
103
|
+
to snap the indicator to its exact final position once the CSS transition ends. Previously each
|
|
104
|
+
call scheduled a new timeout without clearing the last one, so moving the pointer quickly across
|
|
105
|
+
several tabs stacked up redundant pending timeouts. Now the previous timeout is cleared before
|
|
106
|
+
scheduling the next, and both are cleared in `onUnmounted`.
|
|
107
|
+
- `handleTransitioningClass` called `.indexOf()` on the same two tabs repeatedly inside its loop
|
|
108
|
+
condition and body — now computed once per call.
|
|
109
|
+
- `previousActiveTab` used `useState("previousActiveTab", ...)` with a fixed, unscoped key — every
|
|
110
|
+
`TabsCore` instance on a page would have shared that one piece of state. Not actually a
|
|
111
|
+
performance issue, but a correctness bug found while auditing this file; fixed by switching to a
|
|
112
|
+
plain per-instance `ref`, since this state doesn't need SSR/hydration sharing.
|
|
113
|
+
|
|
114
|
+
## Fixed: hover/active label text could become unreadable against the indicator (2026-09-15)
|
|
115
|
+
|
|
116
|
+
`--tabs-hover-indicator-text-colour` and `--tabs-active-indicator-text-colour` were set as `color`
|
|
117
|
+
on `.nav__hovered`/`.nav__active` — decorative, empty, absolutely-positioned divs with no text
|
|
118
|
+
content, so that `color` was dead CSS with no visible effect. The actual tab label
|
|
119
|
+
(`.tabs-list-item`) used one shared `--tabs-list-item-colour-selected` token for hover, active,
|
|
120
|
+
*and* transitioning alike, regardless of which of the two (potentially differently-coloured)
|
|
121
|
+
indicator backgrounds was actually behind it at that moment. A consumer overriding only the hover
|
|
122
|
+
indicator's background (or the active one) had no working lever to keep the label readable against
|
|
123
|
+
it, and could end up with text the same colour as the background sliding behind it — this was the
|
|
124
|
+
original defect noticed during this component's initial authoring, reported as "the button text
|
|
125
|
+
became the same colour as the track item background and became invisible."
|
|
126
|
+
|
|
127
|
+
Fixed by removing the dead `color` declarations from the two decorator divs and splitting
|
|
128
|
+
`.tabs-list-item`'s combined `:hover, [aria-selected="true"], .transitioning` rule so hover uses
|
|
129
|
+
`--tabs-hover-indicator-text-colour` and active uses `--tabs-list-item-colour-selected` falling
|
|
130
|
+
back to `--tabs-active-indicator-text-colour` — each state's text colour now tracks the background
|
|
131
|
+
actually behind it. See `CONSUMER-STYLING.md`'s "Tab-label text colour tracks the indicator behind
|
|
132
|
+
it" section: overriding an indicator background without its matching text-colour token can still
|
|
133
|
+
produce poor contrast (same tradeoff as any two independently-set colour tokens) — the fix makes
|
|
134
|
+
the tokens functional, it doesn't auto-compute contrast.
|
|
135
|
+
|
|
136
|
+
## Fixed: intermediate tab labels could go invisible during a multi-tab jump (2026-09-15)
|
|
137
|
+
|
|
138
|
+
The first fix above wasn't the whole story. The exact repro that surfaced the rest of it: with tab
|
|
139
|
+
1 active, hover then click tab 5 — the tabs in between briefly lost all visible text. Going the
|
|
140
|
+
other direction (tab 5 active, hover then click tab 1) looked fine.
|
|
141
|
+
|
|
142
|
+
Cause: `useTabs` had a `handleTransitioningClass` helper that added a `.transitioning` class to
|
|
143
|
+
every tab spanned by a jump (previously shared by the `[aria-selected="true"]` colour rule fixed
|
|
144
|
+
above), on the assumption the sliding indicator visually covers every spanned tab for the whole
|
|
145
|
+
transition — so it force-applied the active text colour to all of them, for the full
|
|
146
|
+
`transitionDuration`. It doesn't cover them the whole time: the indicator's "grow from anchor" CSS
|
|
147
|
+
transition (see `moveActiveIndicator`/`moveHoveredIndicator`) keeps the box's leading edge pinned
|
|
148
|
+
at the *old* tab's position on a forward move and only grows its width, so it only gradually sweeps
|
|
149
|
+
across later tabs — a tab several positions away isn't actually covered until *late* in the
|
|
150
|
+
transition. Recolouring it to the active text colour from the very start meant its label matched
|
|
151
|
+
neither its own background nor the not-yet-arrived indicator, going invisible. A backward move
|
|
152
|
+
animates position and width toward their final values together, so the box's rendered coverage is
|
|
153
|
+
more consistent throughout — which is why that direction looked fine and made the bug easy to miss
|
|
154
|
+
without a specific repro.
|
|
155
|
+
|
|
156
|
+
Fixed by removing `handleTransitioningClass` and the `.transitioning` class entirely — it had no
|
|
157
|
+
other purpose. Tabs spanned by a jump now keep their normal (inactive) styling throughout; only the
|
|
158
|
+
newly-active tab (`[aria-selected="true"]`) and the currently-hovered tab (`:hover`) get their
|
|
159
|
+
special colours. See `CONSUMER-STYLING.md`'s "Tabs spanned by a jump keep their normal styling".
|
|
160
|
+
|
|
161
|
+
## Fixed: passed-over tab text still invisible under the active indicator specifically (2026-09-15)
|
|
162
|
+
|
|
163
|
+
Removing `.transitioning` (previous fix) uncovered a second, pre-existing defect that
|
|
164
|
+
`.transitioning`'s forced recolour had been accidentally masking the whole time: a non-active tab's
|
|
165
|
+
normal text colour (`--tabs-list-item-colour`) was **exactly the same value** as the active
|
|
166
|
+
indicator's background (`--tabs-active-indicator-colour`, same default) — this collision existed in
|
|
167
|
+
the very first authored version of this component, not something introduced by any migration
|
|
168
|
+
change. So whenever the active pill visually passes under (or briefly sits under) any tab that
|
|
169
|
+
isn't itself marked active — which happens on every transition, single-step or distant, in either
|
|
170
|
+
direction — that tab's text became literally the same colour as the background sliding beneath it.
|
|
171
|
+
`--tabs-hover-indicator-colour`'s default never had this problem since it doesn't coincide with the
|
|
172
|
+
inactive text default.
|
|
173
|
+
|
|
174
|
+
Fixed by changing `--tabs-active-indicator-colour`'s default to one step in from the extreme,
|
|
175
|
+
still clearly the boldest of the three indicator colours, but no longer numerically identical to
|
|
176
|
+
`--tabs-list-item-colour`'s default. `--tabs-active-indicator-text-colour`/
|
|
177
|
+
`--tabs-list-item-colour-selected` (used by the truly active tab) still contrast fine against the
|
|
178
|
+
new value. See `CONSUMER-STYLING.md` for the note on keeping these two tokens from colliding again
|
|
179
|
+
if you override either one.
|
|
180
|
+
|
|
181
|
+
## Colour token defaults are flat values, not light-dark() (2026-09-15)
|
|
182
|
+
|
|
183
|
+
Every colour token's default was originally a `light-dark(lightValue, darkValue)` pair (as most of
|
|
184
|
+
this library's other components use). Deliberately changed to a single flat `--slate-*` value per
|
|
185
|
+
token for this component — it doesn't bake in automatic light/dark scheme-switching for its own
|
|
186
|
+
defaults; a consumer who wants a token to adapt to `color-scheme` wraps their own override value in
|
|
187
|
+
`light-dark()`. See `CONSUMER-STYLING.md`'s "Colour tokens are flat values, not light-dark() pairs".
|
package/.claude/skills/index.md
CHANGED
|
@@ -70,6 +70,7 @@ Each skill is a single markdown file named `<area>-<task>.md`.
|
|
|
70
70
|
├── layout-grid-by-cols.md — LayoutGridByCols dynamic slots (item-{n}), props, column/gap/breakpoint decisions
|
|
71
71
|
├── page-row.md — PageRow layout primitive: CSS grid named lines, nesting pattern, align prop, aria-labelledby, CSS token API
|
|
72
72
|
├── link-text.md — LinkText props, slots, usage patterns, styling
|
|
73
|
+
├── header-block.md — HeaderBlock: tagLevel/classLevel semantic-vs-visual heading decoupling, page-heading-N utility classes
|
|
73
74
|
├── page-hero-highlights.md — PageHeroHighlights template: hero + highlights strip grid, CSS custom property theming
|
|
74
75
|
├── services-card.md — ServicesCard props (incl. eyebrowConfig/heroConfig), actions slot, CSS tokens, page boilerplate
|
|
75
76
|
├── services-card-grid.md — ServicesCardGrid props, config pass-through, CSS tokens, full page boilerplate
|
|
@@ -77,6 +78,8 @@ Each skill is a single markdown file named `<area>-<task>.md`.
|
|
|
77
78
|
├── service-summary.md — ServiceSummary props, summary-link slot, DisplayPill duration/price (renamed/stripped from ServicesSection — full mode is now ServiceDetail)
|
|
78
79
|
├── service-detail.md — ServiceDetail: full service detail page (hero banner, sticky sidebar booking card + related services, closing CTA), headerTag vs subheadingTag split, book-cta/sidebar-note/related-service/final-cta slots
|
|
79
80
|
├── breadcrumb.md — Breadcrumb: items (BreadcrumbItem[]) trail, link vs current-page text, CSS token API
|
|
81
|
+
├── skip-links.md — SkipLinks: focus-revealed accessibility skip-nav, links (SkipLink[]) data-driven, homeLink slot, CSS token API
|
|
82
|
+
├── tabs-core.md — TabsCore: tablist from indexed slots (itemCount), arrow-key nav, moving hover/active/underline indicators, CSS token API
|
|
80
83
|
├── contact-section.md — ContactSection props (stepperIndicatorSize pass-through), 3-item info+form layout, slot API
|
|
81
84
|
├── stepper-list.md — StepperList dynamic slots (item-{n}/indicator-{n}), props, connector behaviour
|
|
82
85
|
├── expanding-panel.md — ExpandingPanel v-model, forceOpened, contentIsOnTop overlay mode, slots (summary/icon/content), ARIA wiring, CSS token API
|
|
@@ -90,6 +93,7 @@ Each skill is a single markdown file named `<area>-<task>.md`.
|
|
|
90
93
|
├── scroll-reveal-frame.md — ScrollRevealFrame: generic parallax clipping frame, slot API, image grid pattern, CSS tokens, browser support
|
|
91
94
|
├── scroll-reveal-image.md — ScrollRevealImage: single-image parallax reveal, focalX, imgWidth/imgHeight, responsive frame height
|
|
92
95
|
├── marquee-scroller.md — MarqueeScroller: infinite logo/badge scroller, per-item dynamic slots (marqueeData id), hover/focus/keyboard pause, reduced-motion, CSS tokens
|
|
96
|
+
├── rotating-carousel-image.md — RotatingCarouselImage: 3D rotating image carousel, scroll-parallax tilt, focus/keyboard/hover pause, reduced-motion, CSS tokens
|
|
93
97
|
├── site-navigation.md — SiteNavigation: responsive nav with auto-collapse, burger menu, decorator indicators, CSS token API
|
|
94
98
|
├── tab-navigation.md — TabNavigation: horizontal tab nav with CSS anchor-positioning indicators, anchor scroll, burger collapse, full CSS token API
|
|
95
99
|
├── social-icons-list.md — SocialIconsList: data-driven social icon links, ISocialIcon type, logos: icon names, CSS tokens
|
|
@@ -116,7 +120,12 @@ Each skill is a single markdown file named `<area>-<task>.md`.
|
|
|
116
120
|
├── deep-expanding-menu.md — DeepExpandingMenu: anchor-positioned popover nav panels, browser support caveat, CSS token API
|
|
117
121
|
├── deep-expanding-menu-classic.md — DeepExpandingMenuClassic: <details>-based fallback nav, click-outside close, CSS token API
|
|
118
122
|
├── display-tooltip.md — DisplayTooltip: anchor-positioned popover trigger, browser support caveat, CSS token API
|
|
119
|
-
|
|
123
|
+
├── display-tooltip-defined.md — DisplayTooltipDefined: structured title/body/action tooltip content with close button, composes DisplayTooltip
|
|
124
|
+
├── pop-over.md — PopOver: generic anchor-positioned disclosure panel, consumer-supplied trigger/content slots, placement prop, CSS token API
|
|
125
|
+
├── input-text-core.md — InputTextCore: native text/date/number input primitive, min/max pass-through (date-picker range), CSS token API; Variants section covers InputTextWithLabel/InputPasswordWithLabel/InputTextAsNumberWithLabel
|
|
126
|
+
├── entry-animation.md — EntryAnimation: scroll-driven entry animation wrapper (slide-in/zoom-reveal/exit-blur utility classes), skipAnimation for above-the-fold loop items, reduced-motion handled at the CSS layer
|
|
127
|
+
├── column-flow-grid.md — ColumnFlowGrid (renamed from MasonryGrid): CSS multi-column text-flow layout, named dynamic slots (no count/data prop), itemMinWidth/gap/unit sizing, CSS token API; not a true masonry, see "which one do I want?"
|
|
128
|
+
└── masonry-grid.md — MasonryGrid: real measured-height masonry (greedy shortest-column packing, animated resize) absorbed from the now-retired MasonryGridOrdered; named dynamic slots, fixedWidth/justify, CSS token API
|
|
120
129
|
```
|
|
121
130
|
|
|
122
131
|
## Skill file template
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
{
|
|
2
|
+
"SRCDEV ColumnFlowGrid Basic": {
|
|
3
|
+
"description": "Basic ColumnFlowGrid with a few named item slots",
|
|
4
|
+
"scope": "vue,html",
|
|
5
|
+
"body": [
|
|
6
|
+
"<ColumnFlowGrid :item-min-width=\"$1300\" :gap=\"$21.2\">",
|
|
7
|
+
" <template #item-1>",
|
|
8
|
+
" $3",
|
|
9
|
+
" </template>",
|
|
10
|
+
" <template #item-2>",
|
|
11
|
+
" $4",
|
|
12
|
+
" </template>",
|
|
13
|
+
"</ColumnFlowGrid>"
|
|
14
|
+
]
|
|
15
|
+
},
|
|
16
|
+
"SRCDEV ColumnFlowGrid Looped": {
|
|
17
|
+
"description": "ColumnFlowGrid rendering a v-for list via dynamic slot names (no count prop needed)",
|
|
18
|
+
"scope": "vue,html",
|
|
19
|
+
"body": [
|
|
20
|
+
"<ColumnFlowGrid :item-min-width=\"$1280\" :gap=\"$21.6\">",
|
|
21
|
+
" <template v-for=\"($3item, $4index) in $5items\" :key=\"$3item.id\" #[`item-${$4index}`]>",
|
|
22
|
+
" $6",
|
|
23
|
+
" </template>",
|
|
24
|
+
"</ColumnFlowGrid>"
|
|
25
|
+
]
|
|
26
|
+
},
|
|
27
|
+
"SRCDEV ColumnFlowGrid CSS Override": {
|
|
28
|
+
"description": "CSS override scaffold for ColumnFlowGrid tokens",
|
|
29
|
+
"scope": "css",
|
|
30
|
+
"body": [
|
|
31
|
+
".$1my-page {",
|
|
32
|
+
" .column-flow-grid-wrapper {",
|
|
33
|
+
" --column-flow-grid-item-border-colour: $2;",
|
|
34
|
+
" --column-flow-grid-item-padding: $3;",
|
|
35
|
+
" }",
|
|
36
|
+
"}"
|
|
37
|
+
]
|
|
38
|
+
}
|
|
39
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
{
|
|
2
|
+
"SRCDEV EntryAnimation Basic": {
|
|
3
|
+
"description": "Basic EntryAnimation wrapper with a default slide-in animation",
|
|
4
|
+
"scope": "vue,html",
|
|
5
|
+
"body": [
|
|
6
|
+
"<EntryAnimation animation-type=\"$1entry-slide-in\">",
|
|
7
|
+
" <template #default>",
|
|
8
|
+
" $2",
|
|
9
|
+
" </template>",
|
|
10
|
+
"</EntryAnimation>"
|
|
11
|
+
]
|
|
12
|
+
},
|
|
13
|
+
"SRCDEV EntryAnimation Looped, Skip First": {
|
|
14
|
+
"description": "EntryAnimation in a v-for loop, skipping the first (above-the-fold) item",
|
|
15
|
+
"scope": "vue,html",
|
|
16
|
+
"body": [
|
|
17
|
+
"<EntryAnimation",
|
|
18
|
+
" v-for=\"($1item, $2index) in $3items\"",
|
|
19
|
+
" :key=\"$1item.id\"",
|
|
20
|
+
" :skip-animation=\"$2index === 0\"",
|
|
21
|
+
" animation-type=\"$4entry-slide-in\"",
|
|
22
|
+
">",
|
|
23
|
+
" <template #default>",
|
|
24
|
+
" $5",
|
|
25
|
+
" </template>",
|
|
26
|
+
"</EntryAnimation>"
|
|
27
|
+
]
|
|
28
|
+
}
|
|
29
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
{
|
|
2
|
+
"SRCDEV HeaderBlock Basic": {
|
|
3
|
+
"description": "HeaderBlock — semantic heading tag decoupled from visual page-heading-N size",
|
|
4
|
+
"scope": "vue,html",
|
|
5
|
+
"body": [
|
|
6
|
+
"<HeaderBlock :tag-level=\"${1:1}\" :class-level=\"${2:1}\">",
|
|
7
|
+
" $3",
|
|
8
|
+
"</HeaderBlock>"
|
|
9
|
+
]
|
|
10
|
+
},
|
|
11
|
+
"SRCDEV HeaderBlock Decoupled Levels": {
|
|
12
|
+
"description": "HeaderBlock with a semantic tag lower than its visual size (e.g. hero heading below the page's real h1)",
|
|
13
|
+
"scope": "vue,html",
|
|
14
|
+
"body": [
|
|
15
|
+
"<HeaderBlock :tag-level=\"${1:2}\" :class-level=\"${2:1}\">",
|
|
16
|
+
" $3",
|
|
17
|
+
"</HeaderBlock>"
|
|
18
|
+
]
|
|
19
|
+
},
|
|
20
|
+
"SRCDEV HeaderBlock With Passthrough Class": {
|
|
21
|
+
"description": "HeaderBlock with a custom class for one-off styling overrides",
|
|
22
|
+
"scope": "vue,html",
|
|
23
|
+
"body": [
|
|
24
|
+
"<HeaderBlock :tag-level=\"${1:1}\" :class-level=\"${2:1}\" style-class-passthrough=\"${3:hero-title}\">",
|
|
25
|
+
" $4",
|
|
26
|
+
"</HeaderBlock>"
|
|
27
|
+
]
|
|
28
|
+
},
|
|
29
|
+
"SRCDEV HeaderBlock Paired With Section aria-labelledby": {
|
|
30
|
+
"description": "HeaderBlock with id bound to a wrapping section's aria-labelledby target",
|
|
31
|
+
"scope": "vue,html",
|
|
32
|
+
"body": [
|
|
33
|
+
"<section aria-labelledby=\"${1:section-heading}\">",
|
|
34
|
+
" <HeaderBlock id=\"${1:section-heading}\" :tag-level=\"${2:2}\" :class-level=\"${3:1}\">",
|
|
35
|
+
" $4",
|
|
36
|
+
" </HeaderBlock>",
|
|
37
|
+
" $5",
|
|
38
|
+
"</section>"
|
|
39
|
+
]
|
|
40
|
+
}
|
|
41
|
+
}
|