srcdev-nuxt-components 9.1.49 → 9.1.51

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 (220) hide show
  1. package/.claude/settings.json +2 -4
  2. package/.claude/settings.local.json +22 -2
  3. package/.claude/skills/components/card-core.md +205 -0
  4. package/.claude/skills/components/display-dialog.md +128 -0
  5. package/.claude/skills/components/page-hero-highlights.md +1 -1
  6. package/.claude/skills/components/page-row.md +157 -0
  7. package/.claude/skills/components/services-card-grid.md +4 -4
  8. package/.claude/skills/components/services-section-grid.md +4 -4
  9. package/.claude/skills/components/tab-navigation.md +222 -0
  10. package/.claude/skills/composable-dialog-controls.md +55 -43
  11. package/.claude/skills/index.md +5 -2
  12. package/app/components/01.atoms/card/CONSUMER-STYLING.md +175 -0
  13. package/app/components/01.atoms/card/CardCore.vue +48 -21
  14. package/app/components/01.atoms/display-dialog/CONSUMER-STYLING.md +163 -0
  15. package/app/components/01.atoms/display-dialog/DisplayDialog.vue +277 -0
  16. package/app/components/01.atoms/display-dialog/stories/DisplayDialog.stories.ts +177 -0
  17. package/app/components/01.atoms/display-dialog/tests/DisplayDialog.spec.ts +264 -0
  18. package/app/components/01.atoms/display-dialog/tests/__snapshots__/DisplayDialog.spec.ts.snap +18 -0
  19. package/app/components/01.atoms/page-row/CONSUMER-STYLING.md +90 -0
  20. package/app/components/01.atoms/page-row/PageRow.vue +115 -0
  21. package/app/components/01.atoms/page-row/stories/PageRow.stories.ts +311 -0
  22. package/app/components/01.atoms/page-row/tests/PageRow.spec.ts +191 -0
  23. package/app/components/02.molecules/navigation/tab-navigation/CONSUMER-STYLING.md +145 -0
  24. package/app/components/02.molecules/navigation/tab-navigation/TabNavigation.vue +20 -9
  25. package/app/components/02.molecules/navigation/tab-navigation/tests/__snapshots__/TabNavigation.spec.ts.snap +12 -6
  26. package/app/components/{forms/ui → 05.forms/form-field}/FormField.vue +12 -19
  27. package/app/components/{forms/ui → 05.forms/form-wrapper}/FormWrapper.vue +6 -6
  28. package/app/components/{forms → 05.forms}/input-button/InputButtonCore.vue +2 -0
  29. package/app/components/{forms → 05.forms}/input-button/stories/InputButtonCore.stories.ts +1 -1
  30. package/app/components/{forms → 05.forms}/input-text/InputTextCore.vue +73 -73
  31. package/app/components/{forms → 05.forms}/input-text/variants/InputTextAsNumberWithLabel.vue +29 -29
  32. package/app/components/carousels/CarouselBasic.vue +99 -99
  33. package/app/components/carousels/CarouselInfinite.vue +150 -150
  34. package/app/components/deep-expanding-menu/DeepExpandingMenu.vue +111 -111
  35. package/app/components/responsive-header/ResponsiveHeader.vue +250 -282
  36. package/app/components/test-storybook/TestStorybook.vue +16 -16
  37. package/app/composables/tests/useDialogControls.spec.ts +34 -64
  38. package/app/composables/useDialogControls.ts +19 -33
  39. package/app/composables/useFormControl.ts +0 -2
  40. package/app/layouts/default.vue +9 -7
  41. package/app/pages/auto-grid.vue +37 -20
  42. package/app/pages/banner-video.vue +2 -2
  43. package/app/pages/forms/examples/buttons/index.vue +189 -215
  44. package/app/pages/forms/examples/material/text-fields.vue +2 -2
  45. package/app/pages/grid-stack.vue +10 -10
  46. package/app/pages/index.vue +20 -20
  47. package/app/pages/samaritan.vue +4 -4
  48. package/app/pages/typography/hero-text.vue +6 -6
  49. package/app/pages/typography/index.vue +4 -4
  50. package/app/pages/typography/page-body.vue +4 -4
  51. package/app/pages/typography/page-heading.vue +4 -4
  52. package/app/pages/typography/page-link.vue +4 -4
  53. package/app/pages/ui/accordian.vue +4 -4
  54. package/app/pages/ui/anchor-scroll-tab-navigation.vue +77 -66
  55. package/app/pages/ui/anchor-scroll.vue +94 -70
  56. package/app/pages/ui/animated-svg-text.vue +4 -4
  57. package/app/pages/ui/block-decorators.vue +11 -11
  58. package/app/pages/ui/card-core.vue +9 -9
  59. package/app/pages/ui/carousel-basic.vue +4 -4
  60. package/app/pages/ui/carousel-flip.vue +2 -2
  61. package/app/pages/ui/carousel-infinite.vue +4 -4
  62. package/app/pages/ui/clipped-panels.vue +10 -10
  63. package/app/pages/ui/contact-section.vue +4 -4
  64. package/app/pages/ui/container-glow.vue +2 -3
  65. package/app/pages/ui/deep-expanding-menu.vue +138 -0
  66. package/app/pages/ui/display-avatar.vue +2 -2
  67. package/app/pages/ui/display-banner.vue +2 -2
  68. package/app/pages/ui/display-chip.vue +32 -25
  69. package/app/pages/ui/display-details.vue +6 -10
  70. package/app/pages/ui/display-dialog.vue +96 -131
  71. package/app/pages/ui/display-pill.vue +27 -33
  72. package/app/pages/ui/display-prompt.vue +4 -4
  73. package/app/pages/ui/display-toast.vue +2 -2
  74. package/app/pages/ui/expanding-panel.vue +12 -16
  75. package/app/pages/ui/glowing-border.vue +12 -12
  76. package/app/pages/ui/layout-grid-a.vue +6 -6
  77. package/app/pages/ui/layout-grid-b.vue +4 -4
  78. package/app/pages/ui/magnetic-navigation.vue +6 -6
  79. package/app/pages/ui/marquee-scroller.vue +2 -2
  80. package/app/pages/ui/mask-element.vue +2 -2
  81. package/app/pages/ui/masonry-columns.vue +4 -4
  82. package/app/pages/ui/masonry-grid-ordered-experiment.vue +4 -4
  83. package/app/pages/ui/masonry-grid-ordered.vue +4 -4
  84. package/app/pages/ui/masonry-grid-sorted.vue +4 -4
  85. package/app/pages/ui/masonry-grid.vue +4 -4
  86. package/app/pages/ui/navigation/navigation-horizontal.vue +12 -12
  87. package/app/pages/ui/navigation/tab-navigation.vue +493 -0
  88. package/app/pages/ui/page-row.vue +127 -0
  89. package/app/pages/ui/price-list.vue +4 -4
  90. package/app/pages/ui/profile-section.vue +4 -4
  91. package/app/pages/ui/qr-code/[componentName].vue +6 -6
  92. package/app/pages/ui/rotating-carousel.vue +16 -16
  93. package/app/pages/ui/scroll-reveal-image.vue +20 -20
  94. package/app/pages/ui/section-parallax.vue +7 -7
  95. package/app/pages/ui/services/colour-finder.vue +4 -4
  96. package/app/pages/ui/services/services-cards.vue +4 -4
  97. package/app/pages/ui/services/services-section/[slug].vue +4 -4
  98. package/app/pages/ui/services/services-sections-compact.vue +6 -6
  99. package/app/pages/ui/settings.vue +10 -10
  100. package/app/pages/ui/simple-grid.vue +2 -2
  101. package/app/pages/ui/slider-gallery.vue +5 -5
  102. package/app/pages/ui/tabs-y.vue +2 -3
  103. package/app/pages/ui/tabs.vue +6 -9
  104. package/app/pages/ui/tooltips.vue +9 -9
  105. package/app/pages/ui/wipe-away-vertical.vue +14 -14
  106. package/package.json +1 -1
  107. package/.claude/skills/components/layout-row.md +0 -239
  108. package/app/components/display-dialog/DisplayDialogCore.vue +0 -276
  109. package/app/components/display-dialog/variants/DisplayDialogConfirm.vue +0 -47
  110. package/app/components/display-dialog/variants/DisplayDialogScrollableContent.vue +0 -51
  111. package/app/components/forms/c12/utils.ts +0 -14
  112. package/app/components/layout-row/LayoutRow.vue +0 -171
  113. package/app/components/layout-row/stories/LayoutRow.stories.ts +0 -528
  114. package/app/pages/ui/layout-row.vue +0 -139
  115. /package/app/components/{forms → 05.forms}/form-errors/InputError.vue +0 -0
  116. /package/app/components/{forms → 05.forms}/form-errors/tests/InputError.spec.ts +0 -0
  117. /package/app/components/{forms → 05.forms}/form-fieldset/FormFieldset.vue +0 -0
  118. /package/app/components/{forms → 05.forms}/input-button/playwright/input-button-core.playwright.ts +0 -0
  119. /package/app/components/{forms → 05.forms}/input-button/playwright/input-button-core.playwright.ts-snapshots/combo-icons-secondary-chromium-darwin.png +0 -0
  120. /package/app/components/{forms → 05.forms}/input-button/playwright/input-button-core.playwright.ts-snapshots/combo-icons-secondary-firefox-darwin.png +0 -0
  121. /package/app/components/{forms → 05.forms}/input-button/playwright/input-button-core.playwright.ts-snapshots/combo-icons-secondary-webkit-darwin.png +0 -0
  122. /package/app/components/{forms → 05.forms}/input-button/playwright/input-button-core.playwright.ts-snapshots/combo-icons-tertiary-chromium-darwin.png +0 -0
  123. /package/app/components/{forms → 05.forms}/input-button/playwright/input-button-core.playwright.ts-snapshots/combo-icons-tertiary-firefox-darwin.png +0 -0
  124. /package/app/components/{forms → 05.forms}/input-button/playwright/input-button-core.playwright.ts-snapshots/combo-icons-tertiary-webkit-darwin.png +0 -0
  125. /package/app/components/{forms → 05.forms}/input-button/playwright/input-button-core.playwright.ts-snapshots/combo-primary-pending-chromium-darwin.png +0 -0
  126. /package/app/components/{forms → 05.forms}/input-button/playwright/input-button-core.playwright.ts-snapshots/combo-primary-pending-firefox-darwin.png +0 -0
  127. /package/app/components/{forms → 05.forms}/input-button/playwright/input-button-core.playwright.ts-snapshots/combo-primary-pending-webkit-darwin.png +0 -0
  128. /package/app/components/{forms → 05.forms}/input-button/playwright/input-button-core.playwright.ts-snapshots/combo-secondary-pill-chromium-darwin.png +0 -0
  129. /package/app/components/{forms → 05.forms}/input-button/playwright/input-button-core.playwright.ts-snapshots/combo-secondary-pill-firefox-darwin.png +0 -0
  130. /package/app/components/{forms → 05.forms}/input-button/playwright/input-button-core.playwright.ts-snapshots/combo-secondary-pill-webkit-darwin.png +0 -0
  131. /package/app/components/{forms → 05.forms}/input-button/playwright/input-button-core.playwright.ts-snapshots/combo-tertiary-readonly-chromium-darwin.png +0 -0
  132. /package/app/components/{forms → 05.forms}/input-button/playwright/input-button-core.playwright.ts-snapshots/combo-tertiary-readonly-firefox-darwin.png +0 -0
  133. /package/app/components/{forms → 05.forms}/input-button/playwright/input-button-core.playwright.ts-snapshots/combo-tertiary-readonly-webkit-darwin.png +0 -0
  134. /package/app/components/{forms → 05.forms}/input-button/playwright/input-button-core.playwright.ts-snapshots/default-chromium-darwin.png +0 -0
  135. /package/app/components/{forms → 05.forms}/input-button/playwright/input-button-core.playwright.ts-snapshots/default-firefox-darwin.png +0 -0
  136. /package/app/components/{forms → 05.forms}/input-button/playwright/input-button-core.playwright.ts-snapshots/default-theme-default-chromium-darwin.png +0 -0
  137. /package/app/components/{forms → 05.forms}/input-button/playwright/input-button-core.playwright.ts-snapshots/default-theme-default-firefox-darwin.png +0 -0
  138. /package/app/components/{forms → 05.forms}/input-button/playwright/input-button-core.playwright.ts-snapshots/default-theme-default-webkit-darwin.png +0 -0
  139. /package/app/components/{forms → 05.forms}/input-button/playwright/input-button-core.playwright.ts-snapshots/default-theme-error-chromium-darwin.png +0 -0
  140. /package/app/components/{forms → 05.forms}/input-button/playwright/input-button-core.playwright.ts-snapshots/default-theme-error-firefox-darwin.png +0 -0
  141. /package/app/components/{forms → 05.forms}/input-button/playwright/input-button-core.playwright.ts-snapshots/default-theme-error-webkit-darwin.png +0 -0
  142. /package/app/components/{forms → 05.forms}/input-button/playwright/input-button-core.playwright.ts-snapshots/default-theme-success-chromium-darwin.png +0 -0
  143. /package/app/components/{forms → 05.forms}/input-button/playwright/input-button-core.playwright.ts-snapshots/default-theme-success-firefox-darwin.png +0 -0
  144. /package/app/components/{forms → 05.forms}/input-button/playwright/input-button-core.playwright.ts-snapshots/default-theme-success-webkit-darwin.png +0 -0
  145. /package/app/components/{forms → 05.forms}/input-button/playwright/input-button-core.playwright.ts-snapshots/default-theme-warning-chromium-darwin.png +0 -0
  146. /package/app/components/{forms → 05.forms}/input-button/playwright/input-button-core.playwright.ts-snapshots/default-theme-warning-firefox-darwin.png +0 -0
  147. /package/app/components/{forms → 05.forms}/input-button/playwright/input-button-core.playwright.ts-snapshots/default-theme-warning-webkit-darwin.png +0 -0
  148. /package/app/components/{forms → 05.forms}/input-button/playwright/input-button-core.playwright.ts-snapshots/default-variant-primary-chromium-darwin.png +0 -0
  149. /package/app/components/{forms → 05.forms}/input-button/playwright/input-button-core.playwright.ts-snapshots/default-variant-primary-firefox-darwin.png +0 -0
  150. /package/app/components/{forms → 05.forms}/input-button/playwright/input-button-core.playwright.ts-snapshots/default-variant-primary-webkit-darwin.png +0 -0
  151. /package/app/components/{forms → 05.forms}/input-button/playwright/input-button-core.playwright.ts-snapshots/default-variant-secondary-chromium-darwin.png +0 -0
  152. /package/app/components/{forms → 05.forms}/input-button/playwright/input-button-core.playwright.ts-snapshots/default-variant-secondary-firefox-darwin.png +0 -0
  153. /package/app/components/{forms → 05.forms}/input-button/playwright/input-button-core.playwright.ts-snapshots/default-variant-secondary-webkit-darwin.png +0 -0
  154. /package/app/components/{forms → 05.forms}/input-button/playwright/input-button-core.playwright.ts-snapshots/default-variant-tertiary-chromium-darwin.png +0 -0
  155. /package/app/components/{forms → 05.forms}/input-button/playwright/input-button-core.playwright.ts-snapshots/default-variant-tertiary-firefox-darwin.png +0 -0
  156. /package/app/components/{forms → 05.forms}/input-button/playwright/input-button-core.playwright.ts-snapshots/default-variant-tertiary-webkit-darwin.png +0 -0
  157. /package/app/components/{forms → 05.forms}/input-button/playwright/input-button-core.playwright.ts-snapshots/default-webkit-darwin.png +0 -0
  158. /package/app/components/{forms → 05.forms}/input-button/playwright/input-button-core.playwright.ts-snapshots/nuxt-icon-only-component-chromium-darwin.png +0 -0
  159. /package/app/components/{forms → 05.forms}/input-button/playwright/input-button-core.playwright.ts-snapshots/nuxt-icon-only-component-firefox-darwin.png +0 -0
  160. /package/app/components/{forms → 05.forms}/input-button/playwright/input-button-core.playwright.ts-snapshots/nuxt-icon-only-component-webkit-darwin.png +0 -0
  161. /package/app/components/{forms → 05.forms}/input-button/playwright/input-button-core.playwright.ts-snapshots/state-pending-chromium-darwin.png +0 -0
  162. /package/app/components/{forms → 05.forms}/input-button/playwright/input-button-core.playwright.ts-snapshots/state-pending-firefox-darwin.png +0 -0
  163. /package/app/components/{forms → 05.forms}/input-button/playwright/input-button-core.playwright.ts-snapshots/state-pending-webkit-darwin.png +0 -0
  164. /package/app/components/{forms → 05.forms}/input-button/playwright/input-button-core.playwright.ts-snapshots/state-pill-chromium-darwin.png +0 -0
  165. /package/app/components/{forms → 05.forms}/input-button/playwright/input-button-core.playwright.ts-snapshots/state-pill-firefox-darwin.png +0 -0
  166. /package/app/components/{forms → 05.forms}/input-button/playwright/input-button-core.playwright.ts-snapshots/state-pill-webkit-darwin.png +0 -0
  167. /package/app/components/{forms → 05.forms}/input-button/playwright/input-button-core.playwright.ts-snapshots/state-readonly-chromium-darwin.png +0 -0
  168. /package/app/components/{forms → 05.forms}/input-button/playwright/input-button-core.playwright.ts-snapshots/state-readonly-firefox-darwin.png +0 -0
  169. /package/app/components/{forms → 05.forms}/input-button/playwright/input-button-core.playwright.ts-snapshots/state-readonly-webkit-darwin.png +0 -0
  170. /package/app/components/{forms → 05.forms}/input-button/playwright/input-button-core.playwright.ts-snapshots/with-both-nuxt-icon-components-chromium-darwin.png +0 -0
  171. /package/app/components/{forms → 05.forms}/input-button/playwright/input-button-core.playwright.ts-snapshots/with-both-nuxt-icon-components-firefox-darwin.png +0 -0
  172. /package/app/components/{forms → 05.forms}/input-button/playwright/input-button-core.playwright.ts-snapshots/with-both-nuxt-icon-components-webkit-darwin.png +0 -0
  173. /package/app/components/{forms → 05.forms}/input-button/tests/InputButtonCore.spec.ts +0 -0
  174. /package/app/components/{forms → 05.forms}/input-button/tests/__snapshots__/InputButtonCore.spec.ts.snap +0 -0
  175. /package/app/components/{forms → 05.forms}/input-checkbox/MultipleCheckboxes.vue +0 -0
  176. /package/app/components/{forms → 05.forms}/input-checkbox/SingleCheckbox.vue +0 -0
  177. /package/app/components/{forms → 05.forms}/input-checkbox/tests/MultipleCheckboxes.spec.ts +0 -0
  178. /package/app/components/{forms → 05.forms}/input-checkbox/tests/data/tags.json +0 -0
  179. /package/app/components/{forms → 05.forms}/input-checkbox-radio/InputCheckboxRadioButton.vue +0 -0
  180. /package/app/components/{forms → 05.forms}/input-checkbox-radio/InputCheckboxRadioCore.vue +0 -0
  181. /package/app/components/{forms → 05.forms}/input-checkbox-radio/InputCheckboxRadioWithLabel.vue +0 -0
  182. /package/app/components/{forms → 05.forms}/input-checkbox-radio/tests/InputCheckboxRadioCore.spec.ts +0 -0
  183. /package/app/components/{forms → 05.forms}/input-copy/InputCopyCore.vue +0 -0
  184. /package/app/components/{forms → 05.forms}/input-copy/stories/InputCopyCore.stories.ts +0 -0
  185. /package/app/components/{forms → 05.forms}/input-copy/tests/InputCopyCore.spec.ts +0 -0
  186. /package/app/components/{forms → 05.forms}/input-copy/tests/__snapshots__/InputCopyCore.spec.ts.snap +0 -0
  187. /package/app/components/{forms → 05.forms}/input-description/InputDescription.vue +0 -0
  188. /package/app/components/{forms → 05.forms}/input-description/tests/InputDescription.spec.ts +0 -0
  189. /package/app/components/{forms → 05.forms}/input-label/InputLabel.vue +0 -0
  190. /package/app/components/{forms → 05.forms}/input-label/tests/InputLabel.spec.ts +0 -0
  191. /package/app/components/{forms → 05.forms}/input-number/InputNumberCore.vue +0 -0
  192. /package/app/components/{forms → 05.forms}/input-number/variants/InputNumberDefault.vue +0 -0
  193. /package/app/components/{forms → 05.forms}/input-radio/MultipleRadiobuttons.vue +0 -0
  194. /package/app/components/{forms → 05.forms}/input-radio/tests/MultipleRadioButtons.spec.ts +0 -0
  195. /package/app/components/{forms → 05.forms}/input-radio/tests/data/tags.json +0 -0
  196. /package/app/components/{forms → 05.forms}/input-range/InputRangeCore.vue +0 -0
  197. /package/app/components/{forms → 05.forms}/input-range/variants/InputRangeDefault.vue +0 -0
  198. /package/app/components/{forms → 05.forms}/input-range-fancy/InputRangeFancyCore.vue +0 -0
  199. /package/app/components/{forms → 05.forms}/input-range-fancy/InputRangeFancyWithLabel.vue +0 -0
  200. /package/app/components/{forms → 05.forms}/input-select/InputSelectCore.vue +0 -0
  201. /package/app/components/{forms → 05.forms}/input-select/variants/InputSelectWithLabel.vue +0 -0
  202. /package/app/components/{forms → 05.forms}/input-text/stories/InputPasswordWithLabel.stories.ts +0 -0
  203. /package/app/components/{forms → 05.forms}/input-text/stories/InputTextAsNumberWithLabel.stories.ts +0 -0
  204. /package/app/components/{forms → 05.forms}/input-text/stories/InputTextCore.stories.ts +0 -0
  205. /package/app/components/{forms → 05.forms}/input-text/stories/InputTextWithLabel.stories.ts +0 -0
  206. /package/app/components/{forms → 05.forms}/input-text/variants/InputPasswordWithLabel.vue +0 -0
  207. /package/app/components/{forms → 05.forms}/input-text/variants/InputTextWithLabel.vue +0 -0
  208. /package/app/components/{forms → 05.forms}/input-textarea/InputTextareaCore.vue +0 -0
  209. /package/app/components/{forms → 05.forms}/input-textarea/stories/InputTextareaCore.stories.ts +0 -0
  210. /package/app/components/{forms → 05.forms}/input-textarea/stories/InputTextareaWithLabel.stories.ts +0 -0
  211. /package/app/components/{forms → 05.forms}/input-textarea/variants/InputTextareaWithLabel.vue +0 -0
  212. /package/app/components/{forms → 05.forms}/pending-effect/PendingEffect.vue +0 -0
  213. /package/app/components/{forms → 05.forms}/toggle-switch/ToggleSwitchCore.vue +0 -0
  214. /package/app/components/{forms → 05.forms}/toggle-switch/stories/ToggleSwitchCore.stories.ts +0 -0
  215. /package/app/components/{forms → 05.forms}/toggle-switch/stories/ToggleSwitchWithLabel.stories.ts +0 -0
  216. /package/app/components/{forms → 05.forms}/toggle-switch/stories/ToggleSwitchWithLabelInline.stories.ts +0 -0
  217. /package/app/components/{forms → 05.forms}/toggle-switch/variants/ToggleSwitchWithLabel.vue +0 -0
  218. /package/app/components/{forms → 05.forms}/toggle-switch/variants/ToggleSwitchWithLabelInline.vue +0 -0
  219. /package/app/components/{forms → 05.forms}/triple-toggle-switch/TripleToggleSwitchCore.vue +0 -0
  220. /package/app/{components/forms/c12/prop-validators/index.ts → utils/prop-validators.ts} +0 -0
@@ -0,0 +1,311 @@
1
+ import type { Meta, StoryFn } from "@nuxtjs/storybook";
2
+ import PageRowComponent from "../PageRow.vue";
3
+
4
+ interface PageRowArgs {
5
+ tag: string;
6
+ variant: string;
7
+ align?: "start" | "end";
8
+ id?: string;
9
+ styleClassPassthrough: string[];
10
+ }
11
+
12
+ export default {
13
+ title: "Atoms/Layouts/PageRow",
14
+ component: PageRowComponent,
15
+ argTypes: {
16
+ variant: {
17
+ control: { type: "select" },
18
+ options: ["full", "popout", "content", "inset-content"],
19
+ description: "Grid column track the element occupies when nested inside another PageRow",
20
+ table: { category: "Layout" },
21
+ },
22
+ align: {
23
+ control: { type: "select" },
24
+ options: [undefined, "start", "end"],
25
+ description: "Bleeds the element to the viewport edge on one side while keeping the opposite boundary at the variant track. Has no effect on variant=\"full\".",
26
+ table: { category: "Layout" },
27
+ },
28
+ tag: {
29
+ control: { type: "select" },
30
+ options: ["div", "section", "article", "header", "footer", "main", "nav"],
31
+ description: "Semantic HTML element to render as",
32
+ table: { category: "Semantic" },
33
+ },
34
+ id: {
35
+ control: { type: "text" },
36
+ description: "id attribute for the element",
37
+ table: { category: "HTML" },
38
+ },
39
+ styleClassPassthrough: {
40
+ table: { disable: true },
41
+ },
42
+ },
43
+ args: {
44
+ variant: "content",
45
+ align: undefined,
46
+ tag: "div",
47
+ id: "",
48
+ styleClassPassthrough: [],
49
+ },
50
+ parameters: {
51
+ docs: {
52
+ description: {
53
+ component:
54
+ "A page layout primitive built on CSS Grid named lines. Each PageRow is both a grid container and a grid item — nest them to compose full-width backgrounds with constrained inner content.",
55
+ },
56
+ },
57
+ },
58
+ } as Meta<PageRowArgs>;
59
+
60
+ const sampleText =
61
+ "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.";
62
+
63
+ // ===== SINGLE ROW STORIES =====
64
+
65
+ const SingleTemplate: StoryFn<PageRowArgs> = (args) => ({
66
+ components: { PageRowComponent },
67
+ setup() {
68
+ return { args, sampleText };
69
+ },
70
+ template: `
71
+ <div style="min-height: 100vh; background: linear-gradient(45deg, #f0f9ff 0%, #e0f2fe 100%);">
72
+ <PageRowComponent
73
+ :tag="args.tag"
74
+ :variant="args.variant"
75
+ :align="args.align || undefined"
76
+ :id="args.id || undefined"
77
+ :style-class-passthrough="args.styleClassPassthrough"
78
+ :is-landmark="args.isLandmark"
79
+ >
80
+ <div style="padding: 24px; background: white; border-radius: 8px; box-shadow: 0 2px 8px rgba(0,0,0,0.1); margin-block: 20px;">
81
+ <h2 style="margin: 0 0 12px; color: #374151; font-size: 18px;">variant="{{ args.variant }}"<span v-if="args.align"> align="{{ args.align }}"</span></h2>
82
+ <p style="margin: 0; color: #6b7280; line-height: 1.6;">{{ sampleText }}</p>
83
+ </div>
84
+ </PageRowComponent>
85
+ </div>
86
+ `,
87
+ });
88
+
89
+ export const Default = SingleTemplate.bind({});
90
+ Default.args = {};
91
+
92
+ export const Full = SingleTemplate.bind({});
93
+ Full.args = { variant: "full" };
94
+ Full.parameters = {
95
+ docs: { description: { story: "Full track (1fr) — extends edge to edge when nested in another PageRow." } },
96
+ };
97
+
98
+ export const Popout = SingleTemplate.bind({});
99
+ Popout.args = { variant: "popout" };
100
+ Popout.parameters = {
101
+ docs: { description: { story: "Popout track (max 1400px) — wider than content, useful for imagery or callouts." } },
102
+ };
103
+
104
+ export const Content = SingleTemplate.bind({});
105
+ Content.args = { variant: "content" };
106
+ Content.parameters = {
107
+ docs: { description: { story: "Content track (max 1064px) — optimal reading width. The default variant." } },
108
+ };
109
+
110
+ export const InsetContent = SingleTemplate.bind({});
111
+ InsetContent.args = { variant: "inset-content" };
112
+ InsetContent.parameters = {
113
+ docs: { description: { story: "Inset content track (max 840px) — narrower width for focused reading." } },
114
+ };
115
+
116
+ // ===== NESTING PATTERN =====
117
+
118
+ const NestingTemplate: StoryFn = () => ({
119
+ components: { PageRowComponent },
120
+ setup() {
121
+ return { sampleText };
122
+ },
123
+ template: `
124
+ <div style="min-height: 100vh;">
125
+
126
+ <PageRowComponent tag="section" variant="full" style="background: #1e3a5f; color: white; padding-block: 60px;">
127
+ <h1 style="margin: 0 0 16px; font-size: 2.4rem;">Full-width background, constrained content</h1>
128
+ <p style="margin: 0; opacity: 0.85; line-height: 1.7;">{{ sampleText }}</p>
129
+ </PageRowComponent>
130
+
131
+ <PageRowComponent tag="section" style="background: #f0fdf4; padding-block: 48px;">
132
+ <PageRowComponent variant="content">
133
+ <h2 style="margin: 0 0 12px; font-size: 1.8rem; color: #166534;">Nested: content inside a content row</h2>
134
+ <p style="margin: 0; color: #15803d; line-height: 1.7;">{{ sampleText }}</p>
135
+ </PageRowComponent>
136
+ <PageRowComponent variant="popout" style="margin-block: 24px; background: #dcfce7; border-radius: 6px; padding: 24px;">
137
+ <p style="margin: 0; color: #166534;">Popout child breaking out to 1400px</p>
138
+ </PageRowComponent>
139
+ <PageRowComponent variant="inset-content">
140
+ <p style="margin: 0; color: #15803d; font-style: italic; line-height: 1.7;">Inset content (840px) for a focused quote or aside.</p>
141
+ </PageRowComponent>
142
+ </PageRowComponent>
143
+
144
+ </div>
145
+ `,
146
+ });
147
+
148
+ export const NestingPattern = NestingTemplate.bind({});
149
+ NestingPattern.parameters = {
150
+ docs: {
151
+ description: {
152
+ story:
153
+ "The key composition pattern: nest a full-width outer PageRow (for backgrounds/borders) with inner PageRows at narrower variants. No extra wrapper divs needed.",
154
+ },
155
+ },
156
+ };
157
+
158
+ // ===== WIDTH COMPARISON =====
159
+
160
+ const ComparisonTemplate: StoryFn = () => ({
161
+ components: { PageRowComponent },
162
+ setup() {
163
+ const tracks = [
164
+ { variant: "full", label: "full", color: "#ef4444", desc: "1fr — edge to edge" },
165
+ { variant: "popout", label: "popout", color: "#f97316", desc: "max 1400px" },
166
+ { variant: "content", label: "content", color: "#eab308", desc: "max 1064px" },
167
+ { variant: "inset-content", label: "inset-content", color: "#22c55e", desc: "max 840px" },
168
+ ];
169
+ return { tracks };
170
+ },
171
+ template: `
172
+ <div style="background: #f8fafc; padding-block: 32px;">
173
+ <PageRowComponent>
174
+ <h2 style="margin: 0 0 8px; color: #374151;">Track width comparison</h2>
175
+ <p style="margin: 0 0 32px; color: #6b7280;">Resize the viewport to see how each track adapts.</p>
176
+ </PageRowComponent>
177
+
178
+ <PageRowComponent v-for="track in tracks" :key="track.variant">
179
+ <PageRowComponent :variant="track.variant" style="margin-block: 8px;">
180
+ <div
181
+ :style="{
182
+ background: track.color,
183
+ color: 'white',
184
+ padding: '16px 20px',
185
+ borderRadius: '6px',
186
+ display: 'flex',
187
+ justifyContent: 'space-between',
188
+ alignItems: 'center',
189
+ }"
190
+ >
191
+ <strong>{{ track.label }}</strong>
192
+ <span style="opacity: 0.85; font-size: 0.875rem;">{{ track.desc }}</span>
193
+ </div>
194
+ </PageRowComponent>
195
+ </PageRowComponent>
196
+ </div>
197
+ `,
198
+ });
199
+
200
+ export const WidthComparison = ComparisonTemplate.bind({});
201
+ WidthComparison.parameters = {
202
+ docs: {
203
+ description: {
204
+ story: "Side-by-side comparison of all four main track widths.",
205
+ },
206
+ },
207
+ };
208
+
209
+ // ===== ALIGNMENT =====
210
+
211
+ const AlignTemplate: StoryFn = () => ({
212
+ components: { PageRowComponent },
213
+ setup() {
214
+ return { sampleText };
215
+ },
216
+ template: `
217
+ <div style="background: #f8fafc; padding-block: 32px; overflow-x: hidden;">
218
+ <PageRowComponent>
219
+ <h2 style="margin: 0 0 8px; color: #374151;">align prop — edge bleed</h2>
220
+ <p style="margin: 0 0 32px; color: #6b7280;">
221
+ combine <code>variant</code> with <code>align</code> to bleed one side to the viewport edge
222
+ while keeping the opposite boundary at the track line.
223
+ </p>
224
+ </PageRowComponent>
225
+
226
+ <PageRowComponent style="margin-block: 8px;">
227
+ <PageRowComponent variant="content" align="start"
228
+ style="background: #fde68a; padding: 20px; border-radius: 0 6px 6px 0;">
229
+ <strong>variant="content" align="start"</strong>
230
+ <p style="margin: 8px 0 0;">full-start → content-end</p>
231
+ </PageRowComponent>
232
+ </PageRowComponent>
233
+
234
+ <PageRowComponent style="margin-block: 8px;">
235
+ <PageRowComponent variant="content" align="end"
236
+ style="background: #a5f3fc; padding: 20px; border-radius: 6px 0 0 6px;">
237
+ <strong>variant="content" align="end"</strong>
238
+ <p style="margin: 8px 0 0;">content-start → full-end</p>
239
+ </PageRowComponent>
240
+ </PageRowComponent>
241
+
242
+ <PageRowComponent style="margin-block: 8px;">
243
+ <PageRowComponent variant="popout" align="start"
244
+ style="background: #bbf7d0; padding: 20px; border-radius: 0 6px 6px 0;">
245
+ <strong>variant="popout" align="start"</strong>
246
+ <p style="margin: 8px 0 0;">full-start → popout-end</p>
247
+ </PageRowComponent>
248
+ </PageRowComponent>
249
+
250
+ <PageRowComponent style="margin-block: 8px;">
251
+ <PageRowComponent variant="inset-content" align="end"
252
+ style="background: #e9d5ff; padding: 20px; border-radius: 6px 0 0 6px;">
253
+ <strong>variant="inset-content" align="end"</strong>
254
+ <p style="margin: 8px 0 0;">inset-content-start → full-end</p>
255
+ </PageRowComponent>
256
+ </PageRowComponent>
257
+ </div>
258
+ `,
259
+ });
260
+
261
+ export const Alignment = AlignTemplate.bind({});
262
+ Alignment.parameters = {
263
+ docs: {
264
+ description: {
265
+ story:
266
+ "Use align=\"start\" or align=\"end\" to bleed a PageRow to the left or right viewport edge while respecting the variant track boundary on the opposite side. Typical use: asymmetric imagery, pull-quotes, or decorative panels.",
267
+ },
268
+ },
269
+ };
270
+
271
+ // ===== ACCESSIBILITY =====
272
+
273
+ const A11yTemplate: StoryFn = () => ({
274
+ components: { PageRowComponent },
275
+ template: `
276
+ <div style="display: flex; flex-direction: column; gap: 24px; padding-block: 32px;">
277
+
278
+ <PageRowComponent tag="section" style="background: #dbeafe; padding-block: 24px;">
279
+ <template #default="{ headingId }">
280
+ <h2 :id="headingId" style="margin: 0 0 8px; color: #1e40af;">Section with aria-labelledby</h2>
281
+ <p style="margin: 0; color: #3b82f6;">
282
+ The <code>&lt;section&gt;</code> element receives <code>aria-labelledby</code> automatically,
283
+ pointing to the heading via the <code>headingId</code> slot prop.
284
+ </p>
285
+ </template>
286
+ </PageRowComponent>
287
+
288
+ <PageRowComponent tag="div" style="background: #f3f4f6; padding-block: 24px;">
289
+ <template #default="{ headingId }">
290
+ <h2 :id="headingId" style="margin: 0 0 8px; color: #374151;">div — no aria-labelledby</h2>
291
+ <p style="margin: 0; color: #6b7280;">
292
+ Non-landmark tags (<code>div</code>, <code>header</code>, <code>footer</code>, <code>nav</code>)
293
+ do not receive <code>aria-labelledby</code>. The <code>headingId</code> slot prop is still
294
+ provided but unused.
295
+ </p>
296
+ </template>
297
+ </PageRowComponent>
298
+
299
+ </div>
300
+ `,
301
+ });
302
+
303
+ export const AriaLabelledBy = A11yTemplate.bind({});
304
+ AriaLabelledBy.parameters = {
305
+ docs: {
306
+ description: {
307
+ story:
308
+ "section, main, article, and aside receive aria-labelledby automatically. Use the headingId slot prop on the first heading inside the PageRow to complete the association.",
309
+ },
310
+ },
311
+ };
@@ -0,0 +1,191 @@
1
+ import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";
2
+ import { mountSuspended } from "@nuxt/test-utils/runtime";
3
+ import PageRow from "../PageRow.vue";
4
+
5
+ const mockElementClasses = { value: "" };
6
+ const mockUseStyleClassPassthrough = vi.fn(() => ({
7
+ elementClasses: mockElementClasses,
8
+ }));
9
+
10
+ vi.mock("#imports", () => ({
11
+ useStyleClassPassthrough: mockUseStyleClassPassthrough,
12
+ }));
13
+
14
+ describe("PageRow", () => {
15
+ let wrapper: Awaited<ReturnType<typeof mountSuspended>>;
16
+
17
+ const createWrapper = async (props = {}, slots = {}) => {
18
+ wrapper = await mountSuspended(PageRow, {
19
+ props: { ...props },
20
+ slots,
21
+ });
22
+ return wrapper;
23
+ };
24
+
25
+ beforeEach(() => {
26
+ vi.clearAllMocks();
27
+ mockElementClasses.value = "";
28
+ });
29
+
30
+ afterEach(() => {
31
+ wrapper?.unmount();
32
+ });
33
+
34
+ describe("Rendering", () => {
35
+ it("mounts without error", async () => {
36
+ await createWrapper();
37
+ expect(wrapper.find(".page-row").exists()).toBe(true);
38
+ });
39
+
40
+ it("renders as a div by default", async () => {
41
+ await createWrapper();
42
+ expect(wrapper.element.tagName.toLowerCase()).toBe("div");
43
+ });
44
+
45
+ it("renders slot content directly inside the root element", async () => {
46
+ await createWrapper({}, { default: "<p>Hello</p>" });
47
+ expect(wrapper.find(".page-row p").exists()).toBe(true);
48
+ expect(wrapper.find(".page-row p").text()).toBe("Hello");
49
+ });
50
+
51
+ it("has no intermediate wrapper elements", async () => {
52
+ await createWrapper({}, { default: "<span>Content</span>" });
53
+ const inner = wrapper.find(".page-row span");
54
+ expect(inner.element.parentElement?.classList.contains("page-row")).toBe(true);
55
+ });
56
+ });
57
+
58
+ describe("Props — tag", () => {
59
+ it.each([["section"], ["article"], ["header"], ["footer"], ["main"], ["nav"]] as const)(
60
+ "renders as <%s>",
61
+ async (tag) => {
62
+ await createWrapper({ tag });
63
+ expect(wrapper.element.tagName.toLowerCase()).toBe(tag);
64
+ }
65
+ );
66
+ });
67
+
68
+ describe("Props — variant", () => {
69
+ it("applies content class by default", async () => {
70
+ await createWrapper();
71
+ expect(wrapper.classes()).toContain("content");
72
+ });
73
+
74
+ it.each(["full", "popout", "content", "inset-content"])("applies variant class %s", async (variant) => {
75
+ await createWrapper({ variant });
76
+ expect(wrapper.classes()).toContain(variant);
77
+ });
78
+ });
79
+
80
+ describe("Props — id", () => {
81
+ it("does not render id attribute by default", async () => {
82
+ await createWrapper();
83
+ expect(wrapper.attributes("id")).toBeUndefined();
84
+ });
85
+
86
+ it("renders the id attribute when provided", async () => {
87
+ await createWrapper({ id: "hero-section" });
88
+ expect(wrapper.attributes("id")).toBe("hero-section");
89
+ });
90
+ });
91
+
92
+ describe("Props — styleClassPassthrough", () => {
93
+ it("forwards string class via elementClasses", async () => {
94
+ mockElementClasses.value = "custom-class";
95
+ await createWrapper({ styleClassPassthrough: "custom-class" });
96
+ expect(wrapper.classes()).toContain("custom-class");
97
+ });
98
+
99
+ it("forwards array classes via elementClasses", async () => {
100
+ await createWrapper({ styleClassPassthrough: ["class-a", "class-b"] });
101
+ expect(wrapper.classes()).toContain("class-a");
102
+ expect(wrapper.classes()).toContain("class-b");
103
+ });
104
+ });
105
+
106
+ describe("Props — align", () => {
107
+ it("does not set data-align attribute by default", async () => {
108
+ await createWrapper();
109
+ expect(wrapper.attributes("data-align")).toBeUndefined();
110
+ });
111
+
112
+ it("sets data-align=start when align is start", async () => {
113
+ await createWrapper({ align: "start" });
114
+ expect(wrapper.attributes("data-align")).toBe("start");
115
+ });
116
+
117
+ it("sets data-align=end when align is end", async () => {
118
+ await createWrapper({ align: "end" });
119
+ expect(wrapper.attributes("data-align")).toBe("end");
120
+ });
121
+
122
+ it("combines variant and align on the same element", async () => {
123
+ await createWrapper({ variant: "content", align: "start" });
124
+ expect(wrapper.classes()).toContain("content");
125
+ expect(wrapper.attributes("data-align")).toBe("start");
126
+ });
127
+ });
128
+
129
+ describe("Accessibility — aria-labelledby", () => {
130
+ it("does not set aria-labelledby on non-landmark tags", async () => {
131
+ await createWrapper({ tag: "div" });
132
+ expect(wrapper.attributes("aria-labelledby")).toBeUndefined();
133
+ });
134
+
135
+ it.each(["section", "main", "article", "aside"] as const)(
136
+ "sets aria-labelledby on <%s>",
137
+ async (tag) => {
138
+ await createWrapper({ tag });
139
+ expect(wrapper.attributes("aria-labelledby")).toBeTruthy();
140
+ }
141
+ );
142
+
143
+ it("does not set aria-labelledby on header, footer, nav", async () => {
144
+ for (const tag of ["header", "footer", "nav"] as const) {
145
+ await createWrapper({ tag });
146
+ expect(wrapper.attributes("aria-labelledby")).toBeUndefined();
147
+ wrapper.unmount();
148
+ }
149
+ });
150
+
151
+ it("exposes headingId as a slot prop", async () => {
152
+ let slotHeadingId: string | undefined;
153
+ await createWrapper(
154
+ { tag: "section" },
155
+ {
156
+ default: (slotProps: { headingId: string }) => {
157
+ slotHeadingId = slotProps.headingId;
158
+ return "<h2>Title</h2>";
159
+ },
160
+ }
161
+ );
162
+ expect(slotHeadingId).toBeTruthy();
163
+ expect(wrapper.attributes("aria-labelledby")).toBe(slotHeadingId);
164
+ });
165
+
166
+ it("preserves ARIA attributes on slot content", async () => {
167
+ await createWrapper({}, { default: '<nav aria-label="Main navigation">Nav</nav>' });
168
+ expect(wrapper.find("nav").attributes("aria-label")).toBe("Main navigation");
169
+ });
170
+ });
171
+
172
+ describe("CSS classes", () => {
173
+ it("always includes the page-row base class", async () => {
174
+ await createWrapper();
175
+ expect(wrapper.classes()).toContain("page-row");
176
+ });
177
+
178
+ it("includes both base class and variant class", async () => {
179
+ await createWrapper({ variant: "full" });
180
+ expect(wrapper.classes()).toContain("page-row");
181
+ expect(wrapper.classes()).toContain("full");
182
+ });
183
+
184
+ it("includes elementClasses from styleClassPassthrough alongside other classes", async () => {
185
+ await createWrapper({ variant: "popout", styleClassPassthrough: ["extra-class"] });
186
+ expect(wrapper.classes()).toContain("page-row");
187
+ expect(wrapper.classes()).toContain("popout");
188
+ expect(wrapper.classes()).toContain("extra-class");
189
+ });
190
+ });
191
+ });
@@ -0,0 +1,145 @@
1
+ # TabNavigation — Consumer Styling Guide
2
+
3
+ ## Public token API
4
+
5
+ All `--tab-nav-*` tokens are the stable override surface. Set them at any scope (global, page, or
6
+ instance) without touching the component itself.
7
+
8
+ ### Horizontal nav
9
+
10
+ | Token | Default | Controls |
11
+ |---|---|---|
12
+ | `--tab-nav-link-color` | `var(--slate-01, currentColor)` | Link text colour (rest state) |
13
+ | `--tab-nav-link-hover-color` | `var(--slate-04, currentColor)` | Link text colour on hover |
14
+ | `--tab-nav-link-active-color` | `var(--slate-01, currentColor)` | Link text colour when route is active |
15
+ | `--tab-nav-link-size` | `1.6rem` | Link font size |
16
+ | `--tab-nav-link-weight` | `400` | Link font weight |
17
+ | `--tab-nav-link-tracking` | `0.06em` | Link letter spacing |
18
+ | `--tab-nav-gap` | `2.2rem` | Gap between nav items |
19
+ | `--tab-nav-transition` | `250ms ease` | Colour transition on hover/active |
20
+
21
+ ### Indicator decorators
22
+
23
+ | Token | Default | Controls |
24
+ |---|---|---|
25
+ | `--tab-nav-decorator-indicator-color` | `var(--slate-01, currentColor)` | Active-item underline bar colour |
26
+ | `--tab-nav-decorator-hovered-bg` | `transparent` | Background fill pill that follows the pointer |
27
+
28
+ ### Mobile panel
29
+
30
+ | Token | Default | Controls |
31
+ |---|---|---|
32
+ | `--tab-nav-panel-bg` | `var(--page-bg, #1a1614)` | Panel background colour |
33
+ | `--tab-nav-panel-border-color` | `color-mix(in oklch, var(--slate-01, #c0847a) 35%, transparent)` | Border between nav bar and open panel |
34
+ | `--tab-nav-panel-item-border` | `color-mix(in oklch, var(--slate-01, white) 8%, transparent)` | Divider between panel items |
35
+ | `--tab-nav-panel-link-color` | `var(--slate-01, currentColor)` | Panel link text colour |
36
+ | `--tab-nav-panel-link-hover-color` | `var(--slate-04, currentColor)` | Panel link text colour on hover |
37
+ | `--tab-nav-panel-link-active-color` | `var(--slate-01, currentColor)` | Panel active link colour |
38
+ | `--tab-nav-panel-padding-block` | `1.4rem` | Panel link vertical padding |
39
+ | `--tab-nav-panel-padding-inline` | `1.5rem` | Panel link horizontal padding |
40
+ | `--tab-nav-panel-slide-duration` | `350ms` | Panel open/close animation duration |
41
+ | `--tab-nav-panel-slide-easing` | `cubic-bezier(0.4, 0, 0.2, 1)` | Panel open/close easing |
42
+
43
+ ### Burger button
44
+
45
+ | Token | Default | Controls |
46
+ |---|---|---|
47
+ | `--tab-nav-burger-color` | `var(--slate-01, currentColor)` | Burger bar colour |
48
+ | `--tab-nav-burger-width` | `22px` | Width of each burger bar |
49
+ | `--tab-nav-burger-height` | `1.5px` | Height of each burger bar |
50
+ | `--tab-nav-burger-gap` | `5px` | Gap between burger bars |
51
+ | `--tab-nav-burger-transition` | `300ms ease` | Burger open/close animation |
52
+
53
+ ### Backdrop
54
+
55
+ | Token | Default | Controls |
56
+ |---|---|---|
57
+ | `--tab-nav-backdrop-bg` | `oklch(0% 0 0 / 55%)` | Backdrop overlay background colour |
58
+ | `--tab-nav-backdrop-blur` | `3px` | Backdrop blur amount |
59
+ | `--tab-nav-backdrop-duration` | `350ms` | Backdrop fade duration |
60
+
61
+ ---
62
+
63
+ ## Global theming — app-level CSS file
64
+
65
+ Create `assets/styles/setup/07.components/tab-navigation.css` in the consuming app and set tokens
66
+ on `:root`. These values apply to every `TabNavigation` instance across the site.
67
+
68
+ ```css
69
+ /* assets/styles/setup/07.components/tab-navigation.css */
70
+ :root {
71
+ --tab-nav-link-color: var(--brand-text);
72
+ --tab-nav-link-hover-color: var(--brand-text-muted);
73
+ --tab-nav-link-active-color: var(--brand-accent);
74
+ --tab-nav-decorator-indicator-color: var(--brand-accent);
75
+ --tab-nav-decorator-hovered-bg: oklch(from var(--brand-accent) l c h / 0.1);
76
+ --tab-nav-gap: 3rem;
77
+ }
78
+ ```
79
+
80
+ ---
81
+
82
+ ## Page-scoped overrides
83
+
84
+ Override tokens for a single page by scoping them under a page wrapper. No `:deep()` is required
85
+ (component styles are unscoped).
86
+
87
+ ```css
88
+ /* In the consuming page's unscoped <style> block */
89
+ .about-page {
90
+ .tab-navigation {
91
+ --tab-nav-link-color: var(--brand-warm-text);
92
+ --tab-nav-decorator-indicator-color: var(--brand-warm-accent);
93
+ --tab-nav-gap: 4rem;
94
+ }
95
+ }
96
+ ```
97
+
98
+ ---
99
+
100
+ ## Per-instance overrides via inline style
101
+
102
+ ```vue
103
+ <TabNavigation
104
+ :nav-item-data="navData"
105
+ style="
106
+ --tab-nav-decorator-indicator-color: oklch(65% 0.2 230);
107
+ --tab-nav-link-active-color: oklch(65% 0.2 230);
108
+ --tab-nav-gap: 3rem;
109
+ "
110
+ />
111
+ ```
112
+
113
+ ---
114
+
115
+ ## Per-instance overrides via styleClassPassthrough
116
+
117
+ ```vue
118
+ <TabNavigation :nav-item-data="navData" :style-class-passthrough="['brand-nav']" />
119
+ ```
120
+
121
+ ```css
122
+ .tab-navigation.brand-nav {
123
+ --tab-nav-decorator-indicator-color: var(--brand-accent);
124
+ --tab-nav-decorator-hovered-bg: oklch(from var(--brand-accent) l c h / 0.1);
125
+ --tab-nav-link-active-color: var(--brand-accent);
126
+ --tab-nav-link-size: 1.4rem;
127
+ --tab-nav-link-tracking: 0.08em;
128
+ }
129
+ ```
130
+
131
+ ---
132
+
133
+ ## Notes
134
+
135
+ - The active underline indicator and hover pill use CSS Anchor Positioning. They animate via
136
+ `left`/`right` transitions, so `transition` on `.nav__active-indicator` and `.nav__hovered` is
137
+ handled internally — only set `--tab-nav-transition` to control the link colour fade.
138
+ - `--tab-nav-decorator-hovered-bg: transparent` (the default) hides the hover pill entirely; set
139
+ a semi-transparent colour to enable it.
140
+ - Panel and burger tokens only take visual effect when the nav has collapsed to the burger state
141
+ (i.e. when the nav list overflows its container).
142
+ - `--tab-nav-panel-bg` should match `--page-bg` so the panel blends with the page background in
143
+ the collapsed state.
144
+ - The backdrop (`--tab-nav-backdrop-*`) appears behind the open panel and above page content at
145
+ `z-index: 10`. It teleports to `<body>` so it is unaffected by parent stacking contexts.