srcdev-nuxt-components 9.5.7 → 9.6.1

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 (159) hide show
  1. package/.claude/skills/component-compliance-checklist.md +8 -2
  2. package/.claude/skills/component-dynamic-slots.md +3 -3
  3. package/.claude/skills/component-export-types.md +10 -16
  4. package/.claude/skills/component-naming.md +1 -1
  5. package/.claude/skills/components/alert-content.md +9 -1
  6. package/.claude/skills/components/breadcrumb.md +5 -0
  7. package/.claude/skills/components/container-glow.md +21 -8
  8. package/.claude/skills/components/display-avatar.md +13 -7
  9. package/.claude/skills/components/display-chip.md +37 -24
  10. package/.claude/skills/components/display-pill.md +58 -8
  11. package/.claude/skills/components/display-prompt.md +8 -5
  12. package/.claude/skills/components/display-toast.md +21 -4
  13. package/.claude/skills/components/glowing-border.md +20 -2
  14. package/.claude/skills/components/input-button.md +3 -0
  15. package/.claude/skills/components/responsive-header.md +59 -0
  16. package/.claude/skills/components/select-menu.md +54 -5
  17. package/.claude/skills/components/service-detail.md +32 -0
  18. package/.claude/skills/components/service-summary.md +8 -0
  19. package/.claude/skills/components/services-card-grid.md +5 -3
  20. package/.claude/skills/components/services-card.md +22 -12
  21. package/.claude/skills/components/site-header.md +2 -2
  22. package/.claude/skills/components/slider-gallery.md +10 -4
  23. package/.claude/skills/components/tabbed-content.md +300 -0
  24. package/.claude/skills/composable-anchor-scroll.md +16 -15
  25. package/.claude/skills/index.md +4 -5
  26. package/.claude/skills/storybook-add-story.md +53 -0
  27. package/.claude/skills/theming-status-tokens.md +60 -0
  28. package/.vscode/srcdev-component-display-avatar.code-snippets +2 -1
  29. package/.vscode/srcdev-component-display-chip.code-snippets +3 -2
  30. package/.vscode/srcdev-component-display-pill.code-snippets +10 -0
  31. package/.vscode/srcdev-component-responsive-header.code-snippets +16 -0
  32. package/.vscode/srcdev-component-select-menu.code-snippets +28 -0
  33. package/.vscode/srcdev-component-service-detail.code-snippets +1 -0
  34. package/.vscode/srcdev-component-service-summary.code-snippets +3 -1
  35. package/.vscode/srcdev-component-services-card.code-snippets +38 -36
  36. package/.vscode/srcdev-component-services-grids.code-snippets +2 -1
  37. package/.vscode/{srcdev-component-tabs-core.code-snippets → srcdev-component-tabbed-content.code-snippets} +35 -16
  38. package/app/assets/styles/setup/03.theming/_status.css +24 -0
  39. package/app/assets/styles/setup/03.theming/index.css +1 -0
  40. package/app/components/01.atoms/animations/container-glow/CONSUMER-STYLING.md +39 -35
  41. package/app/components/01.atoms/animations/container-glow/ContainerGlow.vue +25 -15
  42. package/app/components/01.atoms/animations/container-glow/stories/ContainerGlow.stories.ts +130 -24
  43. package/app/components/01.atoms/animations/container-glow/tests/ContainerGlow.spec.ts +45 -0
  44. package/app/components/01.atoms/animations/container-glow/tests/__snapshots__/ContainerGlow.spec.ts.snap +2 -2
  45. package/app/components/01.atoms/animations/glowing-border/CONSUMER-STYLING.md +76 -19
  46. package/app/components/01.atoms/animations/glowing-border/GlowingBorder.vue +12 -10
  47. package/app/components/01.atoms/animations/glowing-border/stories/GlowingBorder.stories.ts +201 -42
  48. package/app/components/01.atoms/animations/glowing-border/tests/GlowingBorder.spec.ts +15 -4
  49. package/app/components/01.atoms/animations/glowing-border/tests/__snapshots__/GlowingBorder.spec.ts.snap +1 -1
  50. package/app/components/01.atoms/display-pill/CONSUMER-STYLING.md +78 -9
  51. package/app/components/01.atoms/display-pill/DisplayPill.vue +59 -23
  52. package/app/components/01.atoms/display-pill/stories/DisplayPill.stories.ts +240 -16
  53. package/app/components/01.atoms/display-pill/tests/DisplayPill.spec.ts +43 -14
  54. package/app/components/01.atoms/display-pill/tests/__snapshots__/DisplayPill.spec.ts.snap +3 -3
  55. package/app/components/01.atoms/navigation/breadcrumb/Breadcrumb.vue +16 -4
  56. package/app/components/01.atoms/navigation/breadcrumb/CONSUMER-STYLING.md +28 -5
  57. package/app/components/01.atoms/navigation/breadcrumb/stories/Breadcrumb.stories.ts +94 -0
  58. package/app/components/01.atoms/navigation/breadcrumb/tests/Breadcrumb.spec.ts +27 -0
  59. package/app/components/01.atoms/tabbed-content/CONSUMER-STYLING.md +201 -0
  60. package/app/components/01.atoms/tabbed-content/TabbedContent.vue +750 -0
  61. package/app/components/01.atoms/tabbed-content/stories/TabbedContent.stories.ts +279 -0
  62. package/app/components/01.atoms/tabbed-content/tests/TabbedContent.spec.ts +816 -0
  63. package/app/components/02.molecules/alert-content/AlertContent.vue +11 -2
  64. package/app/components/02.molecules/alert-content/AlertContentInner.vue +19 -0
  65. package/app/components/02.molecules/alert-content/CONSUMER-STYLING.md +17 -4
  66. package/app/components/02.molecules/alert-content/stories/AlertContent.stories.ts +200 -17
  67. package/app/components/02.molecules/alert-content/tests/AlertContent.spec.ts +33 -0
  68. package/app/components/02.molecules/display-avatar/CONSUMER-STYLING.md +7 -5
  69. package/app/components/02.molecules/display-avatar/DisplayAvatar.vue +6 -2
  70. package/app/components/02.molecules/display-avatar/stories/DisplayAvatar.stories.ts +24 -3
  71. package/app/components/02.molecules/display-avatar/tests/DisplayAvatar.spec.ts +19 -0
  72. package/app/components/02.molecules/display-avatar/tests/__snapshots__/DisplayAvatar.spec.ts.snap +2 -1
  73. package/app/components/02.molecules/display-chip/CONSUMER-STYLING.md +52 -19
  74. package/app/components/02.molecules/display-chip/DisplayChip.vue +72 -54
  75. package/app/components/02.molecules/display-chip/stories/DisplayChip.stories.ts +103 -6
  76. package/app/components/02.molecules/display-chip/tests/DisplayChip.spec.ts +98 -29
  77. package/app/components/02.molecules/display-chip/tests/__snapshots__/DisplayChip.spec.ts.snap +8 -4
  78. package/app/components/02.molecules/prompt/CONSUMER-STYLING.md +9 -1
  79. package/app/components/02.molecules/prompt/DisplayPrompt.vue +2 -2
  80. package/app/components/02.molecules/prompt/stories/DisplayPrompt.stories.ts +208 -71
  81. package/app/components/02.molecules/prompt/tests/DisplayPrompt.spec.ts +19 -6
  82. package/app/components/02.molecules/select-menu/CONSUMER-STYLING.md +66 -8
  83. package/app/components/02.molecules/select-menu/SelectMenu.vue +141 -18
  84. package/app/components/02.molecules/select-menu/stories/SelectMenu.stories.ts +232 -2
  85. package/app/components/02.molecules/select-menu/tests/SelectMenu.spec.ts +151 -0
  86. package/app/components/02.molecules/select-menu/tests/__snapshots__/SelectMenu.spec.ts.snap +6 -2
  87. package/app/components/02.molecules/toast/CONSUMER-STYLING.md +91 -27
  88. package/app/components/02.molecules/toast/DisplayToast.vue +109 -50
  89. package/app/components/02.molecules/toast/DisplayToastProvider.vue +109 -45
  90. package/app/components/02.molecules/toast/stories/DisplayToast.stories.ts +23 -0
  91. package/app/components/02.molecules/toast/stories/DisplayToastProvider.stories.ts +73 -0
  92. package/app/components/02.molecules/toast/tests/DisplayToast.spec.ts +132 -18
  93. package/app/components/02.molecules/toast/tests/DisplayToastProvider.spec.ts +105 -39
  94. package/app/components/03.organisms/image-galleries/slider-gallery/CONSUMER-STYLING.md +19 -1
  95. package/app/components/03.organisms/image-galleries/slider-gallery/SliderGallery.vue +65 -7
  96. package/app/components/03.organisms/image-galleries/slider-gallery/stories/SliderGallery.stories.ts +201 -63
  97. package/app/components/03.organisms/image-galleries/slider-gallery/tests/SliderGallery.spec.ts +41 -2
  98. package/app/components/03.organisms/image-galleries/slider-gallery/tests/__snapshots__/SliderGallery.spec.ts.snap +6 -6
  99. package/app/components/03.organisms/responsive-header/CONSUMER-STYLING.md +42 -11
  100. package/app/components/03.organisms/responsive-header/NavigationItems.vue +60 -16
  101. package/app/components/03.organisms/responsive-header/ResponsiveHeader.vue +86 -24
  102. package/app/components/03.organisms/responsive-header/stories/ResponsiveHeader.stories.ts +52 -0
  103. package/app/components/03.organisms/responsive-header/tests/NavigationItems.spec.ts +20 -0
  104. package/app/components/03.organisms/responsive-header/tests/ResponsiveHeader.spec.ts +40 -0
  105. package/app/components/03.organisms/services/service-detail/CONSUMER-STYLING.md +92 -41
  106. package/app/components/03.organisms/services/service-detail/ServiceDetail.vue +198 -73
  107. package/app/components/03.organisms/services/service-detail/stories/ServiceDetail.stories.ts +157 -9
  108. package/app/components/03.organisms/services/service-detail/tests/ServiceDetail.spec.ts +58 -0
  109. package/app/components/03.organisms/services/service-detail/tests/__snapshots__/ServiceDetail.spec.ts.snap +5 -4
  110. package/app/components/03.organisms/services/service-summary/CONSUMER-STYLING.md +30 -0
  111. package/app/components/03.organisms/services/service-summary/ServiceSummary.vue +32 -8
  112. package/app/components/03.organisms/services/service-summary/stories/ServiceSummary.stories.ts +147 -0
  113. package/app/components/03.organisms/services/service-summary/tests/ServiceSummary.spec.ts +26 -0
  114. package/app/components/03.organisms/services/service-summary/tests/__snapshots__/ServiceSummary.spec.ts.snap +2 -2
  115. package/app/components/03.organisms/services/services-card/CONSUMER-STYLING.md +104 -63
  116. package/app/components/03.organisms/services/services-card/ServicesCard.vue +92 -66
  117. package/app/components/03.organisms/services/services-card/stories/ServicesCard.stories.ts +194 -27
  118. package/app/components/03.organisms/services/services-card/tests/ServicesCard.spec.ts +34 -8
  119. package/app/components/03.organisms/services/services-card/tests/__snapshots__/ServicesCard.spec.ts.snap +8 -8
  120. package/app/components/03.organisms/services/services-grids/CONSUMER-STYLING.md +9 -0
  121. package/app/components/03.organisms/services/services-grids/ServicesCardGrid.vue +2 -1
  122. package/app/components/03.organisms/services/services-grids/stories/ServiceSummaryGrid.stories.ts +46 -6
  123. package/app/components/03.organisms/services/services-grids/stories/ServicesCardGrid.stories.ts +89 -11
  124. package/app/components/03.organisms/services/services-grids/tests/ServicesCardGrid.spec.ts +8 -0
  125. package/app/components/03.organisms/services/services-grids/tests/__snapshots__/ServiceSummaryGrid.spec.ts.snap +6 -6
  126. package/app/components/03.organisms/services/services-grids/tests/__snapshots__/ServicesCardGrid.spec.ts.snap +24 -24
  127. package/app/components/05.forms/input-button/CONSUMER-STYLING.md +8 -0
  128. package/app/components/05.forms/input-button/InputButton.vue +3 -1
  129. package/app/composables/tests/useAnchorScroll.spec.ts +8 -0
  130. package/app/composables/useAnchorScroll.ts +1 -1
  131. package/app/composables/useTabs.ts +148 -206
  132. package/app/types/components/display-chip.d.ts +8 -4
  133. package/app/types/components/display-toast.d.ts +2 -0
  134. package/app/types/components/index.ts +0 -1
  135. package/app/types/components/select-menu.d.ts +2 -0
  136. package/app/utils/pausableTimeout.ts +41 -0
  137. package/nuxt.config.ts +1 -1
  138. package/package.json +1 -1
  139. package/.claude/skills/components/site-navigation.md +0 -145
  140. package/.claude/skills/components/tab-navigation.md +0 -272
  141. package/.claude/skills/components/tabs-core.md +0 -187
  142. package/.vscode/srcdev-component-site-navigation.code-snippets +0 -38
  143. package/.vscode/srcdev-component-tab-navigation.code-snippets +0 -30
  144. package/app/components/01.atoms/navigation/tabs/CONSUMER-STYLING.md +0 -120
  145. package/app/components/01.atoms/navigation/tabs/TabsCore.vue +0 -272
  146. package/app/components/01.atoms/navigation/tabs/stories/TabsCore.stories.ts +0 -199
  147. package/app/components/01.atoms/navigation/tabs/tests/TabsCore.spec.ts +0 -274
  148. package/app/components/02.molecules/navigation/site-navigation/CONSUMER-STYLING.md +0 -109
  149. package/app/components/02.molecules/navigation/site-navigation/SiteNavigation.vue +0 -751
  150. package/app/components/02.molecules/navigation/site-navigation/stories/SiteNavigation.stories.ts +0 -356
  151. package/app/components/02.molecules/navigation/site-navigation/tests/SiteNavigation.spec.ts +0 -367
  152. package/app/components/02.molecules/navigation/site-navigation/tests/__snapshots__/SiteNavigation.spec.ts.snap +0 -30
  153. package/app/components/02.molecules/navigation/tab-navigation/CONSUMER-STYLING.md +0 -150
  154. package/app/components/02.molecules/navigation/tab-navigation/TabNavigation.vue +0 -518
  155. package/app/components/02.molecules/navigation/tab-navigation/stories/TabNavigation.stories.ts +0 -340
  156. package/app/components/02.molecules/navigation/tab-navigation/tests/TabNavigation.spec.ts +0 -339
  157. package/app/components/02.molecules/navigation/tab-navigation/tests/__snapshots__/TabNavigation.spec.ts.snap +0 -33
  158. package/app/composables/useNavCollapse.ts +0 -86
  159. package/app/types/components/nav-item.d.ts +0 -11
@@ -1,145 +0,0 @@
1
- # SiteNavigation
2
-
3
- ## Overview
4
-
5
- `SiteNavigation` is a responsive site-wide navigation component. It renders a horizontal link list on wide viewports and automatically collapses to a burger-menu + slide-down panel on narrow viewports. Collapse is driven by a `ResizeObserver` that compares the list's natural `scrollWidth` against the nav container's `clientWidth` — no breakpoint prop needed.
6
-
7
- Both the horizontal list and the panel include animated active/hover indicator decorators (underline indicator + background highlight) that snap into position using CSS custom properties set via JavaScript.
8
-
9
- ## Component location
10
-
11
- `app/components/02.molecules/navigation/site-navigation/SiteNavigation.vue`
12
-
13
- ## Props
14
-
15
- | Prop | Type | Default | Description |
16
- |---|---|---|---|
17
- | `navItemData` | `NavItemData` | — (required) | Navigation items — see type below |
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 |
20
- | `openMenuLabel` | `string` | `"Open navigation menu"` | Burger button text (screen-reader) while closed — override for localisation |
21
- | `closeMenuLabel` | `string` | `"Close navigation menu"` | Burger button text (screen-reader) while open — override for localisation |
22
- | `styleClassPassthrough` | `string \| string[]` | `[]` | Extra classes applied to the root `<nav>` |
23
-
24
- ## NavItemData type
25
-
26
- **Within the layer:**
27
-
28
- ```ts
29
- import type { NavItemData } from "~/types/components/nav-item.d";
30
- ```
31
-
32
- **In a consuming app:**
33
-
34
- ```ts
35
- import type { NavItemData } from "srcdev-nuxt-components";
36
- ```
37
-
38
- ```ts
39
- const navItemData: NavItemData = {
40
- main: [
41
- { text: "Home", href: "/" },
42
- { text: "About", href: "/about" },
43
- { text: "Services", href: "/services", cssName: "is-featured" },
44
- { text: "Contact", href: "/contact", iconName: "heroicons:envelope", isExternal: false },
45
- ],
46
- };
47
- ```
48
-
49
- `NavItem` fields:
50
-
51
- | Field | Type | Description |
52
- |---|---|---|
53
- | `text` | `string` | Link label |
54
- | `href` | `string` | Link destination |
55
- | `isExternal` | `boolean?` | Passed to NuxtLink `:external` — opens in new tab |
56
- | `iconName` | `string?` | Iconify icon name rendered before the label |
57
- | `cssName` | `string?` | CSS class applied to the `<li>` element |
58
-
59
- ## Basic usage
60
-
61
- ```vue
62
- <SiteNavigation :nav-item-data="navItemData" nav-align="left" />
63
- ```
64
-
65
- Always use hyphenated prop names in templates (ESLint enforces this).
66
-
67
- ## CSS token API
68
-
69
- Set these tokens on a parent element (e.g. your `<header>`) to theme the navigation.
70
-
71
- ```css
72
- .your-header {
73
- /* ── Decorators ────────────────────────────────────── */
74
- --site-nav-decorator-indicator-color: var(--rose-05); /* underline bar */
75
- --site-nav-decorator-hovered-bg: transparent; /* hover pill behind a link */
76
- --site-nav-decorator-active-bg: transparent; /* active pill behind a link */
77
-
78
- /* ── Horizontal nav links ──────────────────────────── */
79
- --site-nav-link-color: var(--warm-01);
80
- --site-nav-link-hover-color: var(--rose-04);
81
- --site-nav-link-active-color: var(--rose-05);
82
- --site-nav-link-size: 1.6rem;
83
- --site-nav-link-weight: 400;
84
- --site-nav-link-tracking: 0.06em;
85
- --site-nav-gap: 2.2rem;
86
- --site-nav-transition: 250ms ease;
87
-
88
- /* ── Mobile panel ──────────────────────────────────── */
89
- --site-nav-panel-bg: var(--page-bg, #1a1614);
90
- --site-nav-panel-border-color: color-mix(in oklch, var(--rose-05) 35%, transparent);
91
- --site-nav-panel-link-color: var(--warm-01);
92
- --site-nav-panel-link-hover-color: var(--rose-04);
93
- --site-nav-panel-link-active-color: var(--rose-05);
94
- --site-nav-panel-padding-block: 1.4rem;
95
- --site-nav-panel-padding-inline: 1.5rem;
96
- --site-nav-panel-slide-duration: 350ms;
97
- --site-nav-panel-slide-easing: cubic-bezier(0.4, 0, 0.2, 1);
98
- --site-nav-panel-decorator-indicator-color: var(--rose-05);
99
- --site-nav-panel-indicator-left: 0; /* position the panel indicator bar */
100
- --site-nav-panel-indicator-right: auto;
101
-
102
- /* ── Burger button ─────────────────────────────────── */
103
- --site-nav-burger-color: var(--warm-01);
104
- --site-nav-burger-width: 22px;
105
- --site-nav-burger-height: 1.5px;
106
- --site-nav-burger-gap: 5px;
107
- --site-nav-burger-transition: 300ms ease;
108
-
109
- }
110
- ```
111
-
112
- The backdrop is teleported to `<body>`, so its tokens only land when set on `:root`/`html`/`body`,
113
- not on your header:
114
-
115
- ```css
116
- :where(html) {
117
- --site-nav-backdrop-bg: oklch(0% 0 0 / 55%);
118
- --site-nav-backdrop-blur: 3px;
119
- --site-nav-backdrop-duration: 350ms;
120
- --site-nav-backdrop-z-index: 10; /* keep your header above this */
121
- }
122
- ```
123
-
124
- Full reference: `app/components/02.molecules/navigation/site-navigation/CONSUMER-STYLING.md`.
125
-
126
- ## Behaviour notes
127
-
128
- - **Collapse detection**: `ResizeObserver` fires on every container resize. The list's `scrollWidth` is cached whenever the list is in the DOM; that cached value is compared against the container's `clientWidth` to set `isCollapsed`.
129
- - **isLoaded state**: The component uses `useState("site-nav-loaded")` — a Nuxt shared state — to gate visibility until the first measurement is complete, preventing a flash of the wrong nav state on load. The nav renders `opacity: 0` until `is-loaded` is applied.
130
- - **Teleport**: The backdrop overlay is teleported to `<body>` via `<Teleport>` and is only mounted when `isCollapsed && isLoaded`.
131
- - **Panel inert**: The panel div (id `site-nav-panel-{useId}`, unique per instance) receives `:inert="!isMenuOpen ? true : undefined"` — it is inert (keyboard/pointer-inaccessible) when closed.
132
- - **Decorator init**: `initNavDecorators()` and `initPanelDecorators()` inject `<li>` elements with CSS-driven indicator `<div>`s. They query `[data-nav-item]` / `[data-panel-nav-item]` to find links, and look for `router-link-active` to set the initial active position.
133
-
134
- ## Accessibility
135
-
136
- - Root `<nav>` has `aria-label` from the `ariaLabel` prop (default `"Site navigation"`).
137
- - Burger button text comes from `openMenuLabel`/`closeMenuLabel`, with `aria-expanded` (string `"true"/"false"`) and `aria-controls` pointing at the panel's per-instance id (was a fixed `site-nav-panel`, which collided when two navs were on one page; fixed 2026-09-27).
138
- - Backdrop and indicator `<li>` elements are `aria-hidden="true"`.
139
- - Panel div is `inert` when closed.
140
-
141
- ## Related files
142
-
143
- - Type: `app/types/components/nav-item.d.ts`
144
- - Tests: `app/components/02.molecules/navigation/site-navigation/tests/SiteNavigation.spec.ts`
145
- - Story: `app/components/02.molecules/navigation/site-navigation/stories/SiteNavigation.stories.ts`
@@ -1,272 +0,0 @@
1
- # TabNavigation
2
-
3
- ## Overview
4
-
5
- A responsive horizontal navigation bar with CSS anchor-positioning indicators, smooth anchor
6
- scroll, and a burger-menu collapse at narrow widths. Accepts both route links (`NuxtLink`) and
7
- hash anchors (`<a>`). Items are driven by a `NavItemData` object; the component only reads the
8
- `main` key.
9
-
10
- ---
11
-
12
- ## Types
13
-
14
- Import from the layer package root — do **not** use `~/types/...` in consuming apps:
15
-
16
- ```ts
17
- import type { NavItemData } from "srcdev-nuxt-components"
18
- ```
19
-
20
- ```ts
21
- interface NavItem {
22
- text: string;
23
- href?: string;
24
- isExternal?: boolean; // adds target="_blank" rel behaviour via NuxtLink
25
- iconName?: string; // icon set name, rendered via <Icon>
26
- cssName?: string; // extra CSS class on the <li>
27
- }
28
-
29
- interface NavItemData {
30
- [key: string]: NavItem[]; // component reads navItemData.main
31
- }
32
- ```
33
-
34
- ---
35
-
36
- ## Props
37
-
38
- | Prop (template form) | Type | Default | Notes |
39
- |---|---|---|---|
40
- | `nav-item-data` | `NavItemData` | required | Navigation link data — only `main` key is read |
41
- | `nav-align` | `"left" \| "center" \| "right"` | `"left"` | Positions the tab list within the nav container |
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 |
44
- | `:style-class-passthrough` | `string \| string[]` | `[]` | Extra classes on the root `<nav>` element |
45
-
46
- ---
47
-
48
- ## Basic usage — route links
49
-
50
- ```vue
51
- <TabNavigation :nav-item-data="navData" nav-align="center" />
52
- ```
53
-
54
- ```ts
55
- import type { NavItemData } from "srcdev-nuxt-components"
56
-
57
- const navData: NavItemData = {
58
- main: [
59
- { text: "Home", href: "/" },
60
- { text: "About", href: "/about" },
61
- { text: "Services", href: "/services" },
62
- { text: "Contact", href: "/contact" },
63
- ],
64
- }
65
- ```
66
-
67
- Items without a leading `#` render as `NuxtLink`. The active indicator tracks the current route
68
- via `router-link-exact-active`.
69
-
70
- ---
71
-
72
- ## Anchor scroll usage
73
-
74
- Hash-prefixed hrefs render as plain `<a>` elements to keep Vue Router out of the scroll path.
75
- Pass a getter for the offset so the sticky bar height is read at click time (not at mount).
76
-
77
- ```vue
78
- <div ref="stickyRef" class="sticky-nav">
79
- <TabNavigation
80
- :nav-item-data="anchorData"
81
- :anchor-scroll-offset="() => stickyRef?.offsetHeight ?? 0"
82
- />
83
- </div>
84
- ```
85
-
86
- ```ts
87
- const stickyRef = ref<HTMLElement | null>(null)
88
-
89
- const anchorData: NavItemData = {
90
- main: [
91
- { text: "Overview", href: "#overview" },
92
- { text: "Features", href: "#features" },
93
- { text: "Pricing", href: "#pricing" },
94
- ],
95
- }
96
- ```
97
-
98
- Clicks are handled by `useAnchorScroll`. Respects `prefers-reduced-motion` — motion-sensitive
99
- users get an instant jump rather than smooth scroll. See the `composable-anchor-scroll.md` skill
100
- for the full composable reference.
101
-
102
- ### Mixed navigation
103
-
104
- Route links and hash anchors can coexist in the same `navItemData`:
105
-
106
- ```ts
107
- const navData: NavItemData = {
108
- main: [
109
- { text: "Home", href: "/" }, // NuxtLink
110
- { text: "Overview", href: "#intro" }, // <a> + smooth scroll
111
- { text: "Blog", href: "/blog" }, // NuxtLink
112
- ],
113
- }
114
- ```
115
-
116
- ---
117
-
118
- ## Responsive collapse
119
-
120
- `useNavCollapse` measures the nav list on every resize. When the list overflows its container it
121
- is hidden and replaced with a burger button. The button opens a slide-down panel listing all items.
122
-
123
- The nav hides itself (opacity 0) until the first measurement completes to prevent a flash of the
124
- wrong state.
125
-
126
- ---
127
-
128
- ## Alignment variants
129
-
130
- ```vue
131
- <TabNavigation :nav-item-data="navData" nav-align="left" /> <!-- default -->
132
- <TabNavigation :nav-item-data="navData" nav-align="center" />
133
- <TabNavigation :nav-item-data="navData" nav-align="right" />
134
- ```
135
-
136
- `center` and `right` use `margin-inline: auto` / `margin-inline-start: auto` on the `ul`.
137
-
138
- ---
139
-
140
- ## CSS token API
141
-
142
- Set `--tab-nav-*` tokens on any ancestor element — no prop needed. See `CONSUMER-STYLING.md` in
143
- the component source folder for the full token reference.
144
-
145
- ### Horizontal nav
146
-
147
- | Token | Default | Controls |
148
- |---|---|---|
149
- | `--tab-nav-link-color` | `var(--slate-01, currentColor)` | Link text colour |
150
- | `--tab-nav-link-hover-color` | `var(--slate-04, currentColor)` | Link hover colour |
151
- | `--tab-nav-link-active-color` | `var(--slate-01, currentColor)` | Active link colour |
152
- | `--tab-nav-link-size` | `1.6rem` | Font size |
153
- | `--tab-nav-link-weight` | `400` | Font weight |
154
- | `--tab-nav-link-tracking` | `0.06em` | Letter spacing |
155
- | `--tab-nav-gap` | `2.2rem` | Gap between items |
156
- | `--tab-nav-transition` | `250ms ease` | Colour fade on hover/active |
157
-
158
- ### Indicator decorators
159
-
160
- | Token | Default | Controls |
161
- |---|---|---|
162
- | `--tab-nav-decorator-indicator-color` | `var(--slate-01, currentColor)` | Active underline bar colour |
163
- | `--tab-nav-decorator-hovered-bg` | `transparent` | Hover pill background (set non-transparent to enable) |
164
-
165
- ### Mobile panel
166
-
167
- | Token | Default | Controls |
168
- |---|---|---|
169
- | `--tab-nav-panel-bg` | `var(--page-bg, #1a1614)` | Panel background |
170
- | `--tab-nav-panel-border-color` | `color-mix(in oklch, var(--slate-01) 35%, transparent)` | Panel top border |
171
- | `--tab-nav-panel-item-border` | `color-mix(in oklch, var(--slate-01) 8%, transparent)` | Item dividers |
172
- | `--tab-nav-panel-link-color` | `var(--slate-01, currentColor)` | Panel link colour |
173
- | `--tab-nav-panel-padding-block` | `1.4rem` | Link vertical padding |
174
- | `--tab-nav-panel-padding-inline` | `1.5rem` | Link horizontal padding |
175
- | `--tab-nav-panel-slide-duration` | `350ms` | Panel open/close duration |
176
- | `--tab-nav-panel-slide-easing` | `cubic-bezier(0.4, 0, 0.2, 1)` | Panel easing |
177
-
178
- ### Burger button
179
-
180
- | Token | Default | Controls |
181
- |---|---|---|
182
- | `--tab-nav-burger-color` | `var(--slate-01, currentColor)` | Bar colour |
183
- | `--tab-nav-burger-width` | `22px` | Bar width |
184
- | `--tab-nav-burger-height` | `1.5px` | Bar height |
185
- | `--tab-nav-burger-gap` | `5px` | Gap between bars |
186
- | `--tab-nav-burger-transition` | `300ms ease` | Open/close animation |
187
-
188
- ### Backdrop
189
-
190
- | Token | Default | Controls |
191
- |---|---|---|
192
- | `--tab-nav-backdrop-bg` | `oklch(0% 0 0 / 55%)` | Overlay colour |
193
- | `--tab-nav-backdrop-blur` | `3px` | Overlay blur |
194
- | `--tab-nav-backdrop-duration` | `350ms` | Overlay fade duration |
195
-
196
- ### Page-scoped theme example
197
-
198
- ```css
199
- /* Unscoped <style> in the consuming page */
200
- .services-page {
201
- .tab-navigation {
202
- --tab-nav-decorator-indicator-color: var(--brand-accent);
203
- --tab-nav-decorator-hovered-bg: oklch(from var(--brand-accent) l c h / 0.1);
204
- --tab-nav-link-active-color: var(--brand-accent);
205
- --tab-nav-panel-bg: var(--page-bg);
206
- }
207
- }
208
- ```
209
-
210
- ---
211
-
212
- ## Notes
213
-
214
- - Auto-imported in Nuxt — no manual import needed.
215
- - The indicator and hover pill use CSS Anchor Positioning and animate via `left`/`right` transitions
216
- — set `--tab-nav-decorator-hovered-bg` to a semi-transparent colour to enable the pill; it is
217
- invisible by default.
218
- - `--tab-nav-transition` controls only the link colour fade; the indicator slide animation is
219
- internally managed and does not inherit this token.
220
- - The panel teleports its backdrop to `<body>` so it escapes any parent `overflow: hidden` or
221
- stacking-context constraint.
222
- - `--tab-nav-panel-bg` should match `--page-bg` for a seamless panel appearance in the collapsed
223
- state.
224
-
225
- ## Known issue: SSR/hydration layout shift when the nav needs to collapse (2026-07-07)
226
-
227
- **Symptom:** on first page load (hard reload/direct navigation), the header briefly renders taller
228
- than its settled state, then shrinks — visibly shifting all content below it up. Most noticeable on
229
- short pages (little content below the fold makes the jump occupy a large fraction of the viewport),
230
- but the same underlying reflow happens on every page where the nav actually needs to collapse at
231
- the viewport's width; it's just less visible on long pages or when the timing happens to resolve
232
- before first paint.
233
-
234
- **Root cause:** `useNavCollapse`'s `isLoaded` ref (and therefore `isCollapsed`) can only be
235
- determined client-side — the server has no way to measure real DOM/viewport widths. So:
236
-
237
- ```vue
238
- <ul v-if="!isCollapsed || !isLoaded" ...>
239
- ```
240
-
241
- SSR and the very first client paint always render the **full, uncollapsed** `<ul>` (since
242
- `!isLoaded` is true pre-mount), regardless of whether it will actually fit. Once mounted,
243
- `checkOverflow()` runs (in `onMounted`, after `nextTick()`), and if the nav overflows, sets
244
- `isCollapsed = true` — swapping the full list out for the shorter collapsed/burger variant. That
245
- swap is what shrinks the header and shifts everything below it.
246
-
247
- Confirmed via `PerformanceObserver({type: "layout-shift"})` plus DOM `MutationObserver` tracing in a
248
- consuming app: the class mutation on `.tab-navigation` (adding
249
- `is-loaded is-animated`) lines up exactly with `.main-content`'s bounding-rect top moving by the
250
- header's full height delta, at a variable point (roughly 100ms–800ms after first paint depending on
251
- hydration timing) after first paint.
252
-
253
- **Candidate fixes (not yet implemented — pick up later):**
254
-
255
- 1. **CSS-only collapse instead of JS-measured overflow.** Use a `@container` or `@media` breakpoint
256
- to switch between the full nav and the burger button, so the correct variant renders on the very
257
- first paint (both server and client agree, no measurement round-trip needed). Loses the
258
- "collapse exactly when it overflows, regardless of item count" precision of the current
259
- JS-measured approach, gains zero-reflow correctness. Probably the most robust fix if a
260
- reasonably conservative breakpoint can be chosen.
261
- 2. **Reserve height instead of eliminating the swap.** Give the header a `min-height` matching the
262
- taller (uncollapsed) state so the swap doesn't change the header's box size — trades a visible
263
- "jump" for a slightly awkward gap during the pre-collapse window, but never moves content below
264
- it. Cheap, but doesn't fix the actual root cause, just its visible symptom.
265
- 3. **Suppress the flash rather than the shift**, e.g. hide the header's nav content entirely
266
- (`visibility: hidden` or `opacity: 0`) until `isLoaded` is true, then fade in — avoids showing
267
- the "wrong" state at all, but delays when the nav becomes visible/interactive, and would need
268
- care to avoid its own CLS/accessibility issues (e.g. focus order, screen readers encountering
269
- hidden nav).
270
- 4. Investigate whether a plausible default guess for `isCollapsed` could be derived from something
271
- SSR does have access to (e.g. a `Sec-CH-Viewport-Width` client hint header, if the deployment
272
- target reliably sends one) — likely not worth the complexity/fragility versus option 1.
@@ -1,187 +0,0 @@
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".
@@ -1,38 +0,0 @@
1
- {
2
- "SRCDEV SiteNavigation": {
3
- "description": "SiteNavigation: horizontal links that collapse to a burger panel when they don't fit",
4
- "scope": "vue,html",
5
- "body": [
6
- "<SiteNavigation",
7
- " :nav-item-data=\"$1navItems\"",
8
- " nav-align=\"${2|left,center,right|}\"",
9
- " aria-label=\"${3:Site navigation}\"",
10
- "/>"
11
- ]
12
- },
13
- "SRCDEV SiteNavigation Items": {
14
- "description": "NavItemData object for SiteNavigation",
15
- "scope": "javascript,typescript",
16
- "body": [
17
- "const $1navItems: NavItemData = {",
18
- " main: [",
19
- " { text: \"$2Home\", href: \"/\" },",
20
- " { text: \"$3About\", href: \"/about\" },",
21
- " ],",
22
- "};"
23
- ]
24
- },
25
- "SRCDEV SiteNavigation CSS Override": {
26
- "description": "CSS override scaffold for SiteNavigation tokens (backdrop tokens go on :root)",
27
- "scope": "css",
28
- "body": [
29
- ".$1site-header {",
30
- " --site-nav-link-color: $2;",
31
- " --site-nav-link-hover-color: $3;",
32
- " --site-nav-decorator-indicator-color: $4;",
33
- " --site-nav-panel-bg: $5;",
34
- " --site-nav-burger-color: $6;",
35
- "}"
36
- ]
37
- }
38
- }
@@ -1,30 +0,0 @@
1
- {
2
- "SRCDEV TabNavigation Basic": {
3
- "description": "TabNavigation with route/hash links",
4
- "scope": "vue,html",
5
- "body": [
6
- "<TabNavigation :nav-item-data=\"$1navData\" nav-align=\"${2:center}\" />"
7
- ]
8
- },
9
- "SRCDEV TabNavigation Localised": {
10
- "description": "TabNavigation with a localised aria-label",
11
- "scope": "vue,html",
12
- "body": [
13
- "<TabNavigation :nav-item-data=\"$1navData\" aria-label=\"${2:Site navigation}\" />"
14
- ]
15
- },
16
- "SRCDEV TabNavigation Nav Data Script": {
17
- "description": "navItemData for TabNavigation",
18
- "scope": "typescript,vue",
19
- "body": [
20
- "import type { NavItemData } from \"srcdev-nuxt-components\";",
21
- "",
22
- "const $1navData: NavItemData = {",
23
- " main: [",
24
- " { text: \"$2Home\", href: \"$3/\" },",
25
- " { text: \"$4Overview\", href: \"$5#overview\" },",
26
- " ],",
27
- "};"
28
- ]
29
- }
30
- }