srcdev-nuxt-components 9.0.15 → 9.0.17

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 (112) hide show
  1. package/.claude/settings.json +25 -0
  2. package/.claude/skills/component-aria-landmark.md +68 -0
  3. package/.claude/skills/component-dynamic-slots.md +150 -0
  4. package/.claude/skills/component-export-types.md +61 -0
  5. package/.claude/skills/component-local-style-override.md +126 -0
  6. package/.claude/skills/component-prop-driven-container-layout.md +42 -0
  7. package/.claude/skills/components/accordian-core.md +159 -0
  8. package/.claude/skills/components/contact-section.md +101 -0
  9. package/.claude/skills/components/expanding-panel.md +156 -0
  10. package/.claude/skills/components/eyebrow-text.md +25 -0
  11. package/.claude/skills/components/hero-text.md +25 -0
  12. package/.claude/skills/components/layout-grid-by-cols.md +147 -0
  13. package/.claude/skills/components/layout-row.md +35 -0
  14. package/.claude/skills/components/link-text.md +33 -0
  15. package/.claude/skills/components/page-hero-highlights.md +224 -0
  16. package/.claude/skills/components/services-card.md +28 -0
  17. package/.claude/skills/components/services-section.md +25 -0
  18. package/.claude/skills/components/stepper-list.md +227 -0
  19. package/.claude/skills/css-grid-max-width-gutters.md +67 -0
  20. package/.claude/skills/index.md +15 -3
  21. package/.claude/skills/storybook-add-story.md +60 -0
  22. package/.claude/skills/testing-add-unit-test.md +56 -0
  23. package/app/assets/styles/setup/01.config/index.css +0 -1
  24. package/app/assets/styles/setup/03.theming/default/_dark.css +2 -2
  25. package/app/assets/styles/setup/04.elements/forms/02.typography.css +1 -0
  26. package/app/assets/styles/setup/05.typography/02.utility-classes/_font-classes-page-link.css +14 -14
  27. package/app/assets/styles/setup/index.css +0 -1
  28. package/app/components/01.atoms/card/CardCore.vue +92 -0
  29. package/app/components/01.atoms/card/stories/CardCore.stories.ts +132 -0
  30. package/app/components/01.atoms/card/tests/CardCore.spec.ts +207 -0
  31. package/app/components/01.atoms/card/tests/__snapshots__/CardCore.spec.ts.snap +43 -0
  32. package/app/components/01.atoms/content-wrappers/content-columns-2/ContentColumns2.vue +51 -0
  33. package/app/components/01.atoms/content-wrappers/content-columns-2/stories/ContentColumns2.stories.ts +110 -0
  34. package/app/components/01.atoms/content-wrappers/content-columns-2/tests/ContentColumns2.spec.ts +105 -0
  35. package/app/components/01.atoms/content-wrappers/content-columns-2/tests/__snapshots__/ContentColumns2.spec.ts.snap +14 -0
  36. package/app/components/01.atoms/content-wrappers/content-width/ContentWidth.vue +88 -0
  37. package/app/components/01.atoms/content-wrappers/content-width/stories/ContentWidth.stories.ts +362 -0
  38. package/app/components/01.atoms/content-wrappers/content-width/tests/ContentWidth.spec.ts +132 -0
  39. package/app/components/01.atoms/content-wrappers/layout-grid/layout-grid-by-cols/LayoutGridByCols.vue +71 -0
  40. package/app/components/01.atoms/content-wrappers/layout-grid/layout-grid-by-cols/stories/LayoutGridByCols.stories.ts +219 -0
  41. package/app/components/01.atoms/content-wrappers/layout-grid/layout-grid-by-cols/tests/LayoutGridByCols.spec.ts +174 -0
  42. package/app/components/01.atoms/content-wrappers/layout-grid/layout-grid-by-cols/tests/__snapshots__/LayoutGrid.spec.ts.snap +36 -0
  43. package/app/components/01.atoms/content-wrappers/layout-grid/layout-grid-by-cols/tests/__snapshots__/LayoutGridByCols.spec.ts.snap +36 -0
  44. package/app/components/01.atoms/content-wrappers/layout-grid/layout-grid-by-width/LayoutGridByWidth.vue +70 -0
  45. package/app/components/01.atoms/content-wrappers/layout-grid/layout-grid-by-width/stories/LayoutGridByWidth.stories.ts +220 -0
  46. package/app/components/01.atoms/content-wrappers/layout-grid/layout-grid-by-width/tests/LayoutGridByWidth.spec.ts +174 -0
  47. package/app/components/01.atoms/content-wrappers/layout-grid/layout-grid-by-width/tests/__snapshots__/LayoutGrid.spec.ts.snap +36 -0
  48. package/app/components/01.atoms/content-wrappers/layout-grid/layout-grid-by-width/tests/__snapshots__/LayoutGridByCols.spec.ts.snap +36 -0
  49. package/app/components/01.atoms/content-wrappers/layout-grid/layout-grid-by-width/tests/__snapshots__/LayoutGridByWidth.spec.ts.snap +36 -0
  50. package/app/components/01.atoms/text-blocks/eyebrow-text/stories/EyebrowText.stories.ts +1 -1
  51. package/app/components/01.atoms/text-blocks/hero-text/stories/HeroText.stories.ts +1 -1
  52. package/app/components/01.atoms/text-blocks/link-text/stories/LinkText.stories.ts +1 -1
  53. package/app/components/02.molecules/contact-section/stories/ContactSection.stories.ts +5 -0
  54. package/app/components/02.molecules/contact-section/tests/ContactSection.spec.ts +15 -0
  55. package/app/components/02.molecules/contact-section/tests/ContactSection.vue +25 -17
  56. package/app/components/{accordian → 02.molecules/expandable/accordian}/stories/AccordianCore.stories.ts +1 -1
  57. package/app/components/02.molecules/expandable/expanding-panel/stories/ExpandingPanel.stories.ts +245 -0
  58. package/app/components/02.molecules/expandable/expanding-panel/tests/ExpandingPanel.spec.ts +351 -0
  59. package/app/components/02.molecules/expandable/expanding-panel/tests/__snapshots__/ExpandingPanel.spec.ts.snap +38 -0
  60. package/app/components/02.molecules/navigation/navigation-horizontal/NavigationHorizontal.vue +162 -0
  61. package/app/components/02.molecules/navigation/navigation-horizontal/stories/NavigationHorizontal.stories.ts +373 -0
  62. package/app/components/02.molecules/navigation/navigation-horizontal/tests/NavigationHorizontal.spec.ts +152 -0
  63. package/app/components/02.molecules/navigation/navigation-horizontal/tests/__snapshots__/NavigationHorizontal.spec.ts.snap +17 -0
  64. package/app/components/02.molecules/profile-section/ProfileSection.vue +2 -3
  65. package/app/components/02.molecules/profile-section/tests/ProfileSection.spec.ts +2 -2
  66. package/app/components/02.molecules/stepper-list/StepperList.vue +131 -92
  67. package/app/components/02.molecules/stepper-list/stories/StepperList.stories.ts +31 -0
  68. package/app/components/02.molecules/stepper-list/tests/StepperList.spec.ts +24 -0
  69. package/app/components/02.molecules/stepper-list/tests/__snapshots__/StepperList.spec.ts.snap +22 -9
  70. package/app/components/03.organisms/image-galleries/slider-gallery/SliderGallery.vue +782 -0
  71. package/app/components/03.organisms/image-galleries/slider-gallery/stories/SliderGallery.stories.ts +233 -0
  72. package/app/components/03.organisms/image-galleries/slider-gallery/tests/SliderGallery.spec.ts +226 -0
  73. package/app/components/03.organisms/image-galleries/slider-gallery/tests/__snapshots__/SliderGallery.spec.ts.snap +69 -0
  74. package/app/components/03.organisms/services/services-grids/ServicesCardGrid.vue +1 -1
  75. package/app/components/03.organisms/services/services-grids/ServicesSectionGrid.vue +1 -1
  76. package/app/components/03.organisms/services/services-section/ServicesSection.vue +2 -3
  77. package/app/components/04.templates/page-hero-highlights/PageHeroHighlights.vue +239 -0
  78. package/app/components/04.templates/page-hero-highlights/stories/PageHeroHighlights.stories.ts +404 -0
  79. package/app/components/04.templates/page-hero-highlights/tests/PageHeroHighlights.spec.ts +198 -0
  80. package/app/components/04.templates/page-hero-highlights/tests/__snapshots__/PageHeroHighlights.spec.ts.snap +19 -0
  81. package/app/components/container-glow/ContainerGlowCore.vue +20 -27
  82. package/app/components/forms/input-button/InputButtonCore.vue +105 -104
  83. package/app/components/glowing-border/stories/GlowingBorder.stories.ts +21 -21
  84. package/app/composables/useAriaLabelledById.ts +13 -0
  85. package/app/layouts/default.vue +8 -3
  86. package/app/pages/forms/examples/buttons/index.vue +6 -6
  87. package/app/pages/forms/examples/material/checkbox-radio-panels.vue +3 -3
  88. package/app/pages/forms/examples/material/text-fields.vue +607 -610
  89. package/app/pages/page-hero-highlights.vue +81 -0
  90. package/app/pages/ui/{display-card.vue → card-core.vue} +15 -15
  91. package/app/pages/ui/contact-section.vue +1 -1
  92. package/app/pages/ui/container-glow.vue +1 -1
  93. package/app/pages/ui/content-width.vue +126 -0
  94. package/app/pages/ui/glowing-border.vue +9 -9
  95. package/app/pages/ui/navigation/navigation-horizontal.vue +484 -0
  96. package/app/pages/ui/services/services-section/[slug].vue +3 -1
  97. package/app/types/components/index.ts +1 -0
  98. package/app/types/components/navigation-horizontal.d.ts +11 -0
  99. package/package.json +2 -2
  100. package/app/assets/styles/setup/01.config/_basic-resets.css +0 -9
  101. package/app/components/content-columns/TwoColumns.vue +0 -59
  102. package/app/components/content-columns/stories/TwoColumns.stories.ts +0 -561
  103. package/app/components/content-containers/ContentContainer.vue +0 -89
  104. package/app/components/content-containers/stories/ContentContainer.stories.ts +0 -465
  105. package/app/components/content-grid/ContentGrid.vue +0 -85
  106. package/app/components/display-card/DisplayCard.vue +0 -122
  107. package/app/components/image-galleries/SliderGallery.vue +0 -786
  108. package/app/pages/ui/content-container.vue +0 -112
  109. /package/app/components/{accordian → 02.molecules/expandable/accordian}/AccordianCore.vue +0 -0
  110. /package/app/components/{accordian → 02.molecules/expandable/accordian}/tests/AccordianCore.spec.ts +0 -0
  111. /package/app/components/{accordian → 02.molecules/expandable/accordian}/tests/__snapshots__/AccordianCore.spec.ts.snap +0 -0
  112. /package/app/components/{expanding-panel → 02.molecules/expandable/expanding-panel}/ExpandingPanel.vue +0 -0
@@ -0,0 +1,101 @@
1
+ # ContactSection Component
2
+
3
+ ## Overview
4
+
5
+ `ContactSection` is a two-column molecule that pairs a 3-item info list (using `StepperList` internally) with a contact form slot. On narrow viewports the columns stack; at 768 px and above they sit side by side.
6
+
7
+ ---
8
+
9
+ ## Props reference
10
+
11
+ | Prop | Type | Default | Notes |
12
+ |------|------|---------|-------|
13
+ | `tag` | `"div" \| "section" \| "article" \| "main"` | `"div"` | Root HTML element. Use `"section"` when the landmark is meaningful. |
14
+ | `stepperIndicatorSize` | `string` | `"3rem"` | Passed through to the internal `StepperList` `indicatorSize` prop. Any valid CSS length. |
15
+ | `styleClassPassthrough` | `string \| string[]` | `[]` | Extra CSS classes applied to the root element. |
16
+
17
+ ---
18
+
19
+ ## Slot API
20
+
21
+ The component exposes 7 slots — 3 info-item slots, 3 indicator slots, and a form slot.
22
+
23
+ | Slot | Purpose |
24
+ |------|---------|
25
+ | `#item-0` | Content of the first info item |
26
+ | `#item-1` | Content of the second info item |
27
+ | `#item-2` | Content of the third info item |
28
+ | `#indicator-0` | Custom indicator icon for item 0 (optional — CSS counter bubble shown if omitted) |
29
+ | `#indicator-1` | Custom indicator icon for item 1 (optional) |
30
+ | `#indicator-2` | Custom indicator icon for item 2 (optional) |
31
+ | `#form` | Contact form or any right-column content |
32
+
33
+ Slots are **zero-indexed**. The internal `StepperList` is always rendered with `itemCount="3"` and `:connected="false"`.
34
+
35
+ ---
36
+
37
+ ## Usage examples
38
+
39
+ ### Default (no slots)
40
+
41
+ ```vue
42
+ <ContactSection />
43
+ ```
44
+
45
+ Renders three placeholder `<p>` tags and an empty form column.
46
+
47
+ ### With info content and a form
48
+
49
+ ```vue
50
+ <ContactSection tag="section" :stepper-indicator-size="'2.4rem'">
51
+ <template #item-0>
52
+ <div>
53
+ <strong>Get in touch</strong>
54
+ <p class="page-body-normal">We'd love to hear from you.</p>
55
+ </div>
56
+ </template>
57
+ <template #item-1>
58
+ <div>
59
+ <strong>Email us</strong>
60
+ <p class="page-body-normal"><a href="mailto:hello@example.com">hello@example.com</a></p>
61
+ </div>
62
+ </template>
63
+ <template #item-2>
64
+ <div>
65
+ <strong>Call us</strong>
66
+ <p class="page-body-normal"><a href="tel:+441234567890">+44 1234 567 890</a></p>
67
+ </div>
68
+ </template>
69
+ <template #form>
70
+ <form>...</form>
71
+ </template>
72
+ </ContactSection>
73
+ ```
74
+
75
+ ### With custom indicator icons
76
+
77
+ ```vue
78
+ <ContactSection>
79
+ <template #indicator-0>
80
+ <Icon name="lucide-map-pin" class="indicator-icon" />
81
+ </template>
82
+ <template #item-0>
83
+ <div>
84
+ <strong>Location</strong>
85
+ <p class="page-body-normal">123 High Street, Bath, BA1 1AA</p>
86
+ </div>
87
+ </template>
88
+ <!-- repeat for indicator-1/item-1, indicator-2/item-2 -->
89
+ </ContactSection>
90
+ ```
91
+
92
+ Custom indicator content should use the `indicator-icon` class so the icon inherits the correct size and colour from `StepperList`.
93
+
94
+ ---
95
+
96
+ ## Notes
97
+
98
+ - `stepperIndicatorSize` passes directly to the internal `StepperList` `indicatorSize` — use it to scale the indicator bubble/icon area. Default `"3rem"` is 30 px at the project's `62.5%` rem base.
99
+ - The internal `StepperList` always uses `tag="ul"`, `:connected="false"`, and `indicator-alignment="top"`. These are not configurable from `ContactSection`.
100
+ - Auto-imported in Nuxt — no manual import needed.
101
+ - See [stepper-list.md](stepper-list.md) for `StepperList` prop details and CSS custom property theming.
@@ -0,0 +1,156 @@
1
+ # ExpandingPanel Component
2
+
3
+ ## Overview
4
+
5
+ `ExpandingPanel` is a single expand/collapse panel built on the native `<details>`/`<summary>` element. It animates open/close via a CSS grid-template-rows trick, supports `v-model` for controlled state, and can be locked open with `forceOpened`. Multiple panels can be grouped into a native accordion by sharing the same `name` prop (see `AccordianCore`).
6
+
7
+ ---
8
+
9
+ ## Props reference
10
+
11
+ | Prop | Type | Default | Notes |
12
+ |------|------|---------|-------|
13
+ | `name` | `string` | `useId()` | Identifies the panel. Used in ARIA attributes (`id-{name}-trigger`, `id-{name}-content`). If omitted, a unique id is generated automatically. |
14
+ | `animationDuration` | `number` | `400` | Expand/collapse transition duration in milliseconds. Pass `0` to disable animation. |
15
+ | `forceOpened` | `boolean` | `false` | When `true`, the panel is always open. The toggle icon is hidden and clicks do not close the panel. |
16
+ | `styleClassPassthrough` | `string \| string[]` | `[]` | Extra CSS classes applied to the root `.expanding-panel` element. |
17
+
18
+ ## Model
19
+
20
+ | Model | Type | Default | Notes |
21
+ |-------|------|---------|-------|
22
+ | `v-model` | `boolean` | `false` | Controls open/closed state. Bind to a `ref<boolean>` to manage state externally. |
23
+
24
+ ---
25
+
26
+ ## Slots
27
+
28
+ | Slot | Purpose |
29
+ |------|---------|
30
+ | `#summary` | Content rendered inside the clickable `<summary>` row (label area). |
31
+ | `#icon` | Custom toggle icon. Defaults to a `bi:caret-down-fill` icon that flips on open. Hidden when `forceOpened` is `true`. |
32
+ | `#content` | Content revealed when the panel is open. Can contain any markup. |
33
+
34
+ ---
35
+
36
+ ## Usage examples
37
+
38
+ ### Basic uncontrolled panel
39
+
40
+ ```vue
41
+ <ExpandingPanel name="delivery">
42
+ <template #summary>
43
+ <span>Delivery &amp; Returns</span>
44
+ </template>
45
+ <template #content>
46
+ <p>Free standard delivery on orders over £50.</p>
47
+ </template>
48
+ </ExpandingPanel>
49
+ ```
50
+
51
+ ### Controlled via v-model
52
+
53
+ ```vue
54
+ <script setup lang="ts">
55
+ const isOpen = ref(false);
56
+ </script>
57
+
58
+ <template>
59
+ <ExpandingPanel name="faq-1" v-model="isOpen">
60
+ <template #summary><span>What is your returns policy?</span></template>
61
+ <template #content><p>You can return any item within 30 days.</p></template>
62
+ </ExpandingPanel>
63
+ <button @click="isOpen = !isOpen">Toggle externally</button>
64
+ </template>
65
+ ```
66
+
67
+ ### Force opened (always visible, no toggle)
68
+
69
+ ```vue
70
+ <ExpandingPanel name="notice" :force-opened="true">
71
+ <template #summary><strong>Important notice</strong></template>
72
+ <template #content>
73
+ <p>This panel cannot be collapsed.</p>
74
+ </template>
75
+ </ExpandingPanel>
76
+ ```
77
+
78
+ ### Custom icon
79
+
80
+ ```vue
81
+ <ExpandingPanel name="custom-icon">
82
+ <template #summary><span>Section title</span></template>
83
+ <template #icon>
84
+ <svg width="12" height="12" viewBox="0 0 12 12">
85
+ <path d="M6 9L1 3h10z" fill="currentColor" />
86
+ </svg>
87
+ </template>
88
+ <template #content><p>Content here.</p></template>
89
+ </ExpandingPanel>
90
+ ```
91
+
92
+ ### Slow animation
93
+
94
+ ```vue
95
+ <ExpandingPanel name="slow" :animation-duration="800">
96
+ <template #summary><span>Slow panel</span></template>
97
+ <template #content><p>Opens and closes over 800 ms.</p></template>
98
+ </ExpandingPanel>
99
+ ```
100
+
101
+ ---
102
+
103
+ ## ARIA / accessibility
104
+
105
+ The component wires ARIA automatically from the `name` prop:
106
+
107
+ | Element | Attribute | Value |
108
+ |---------|-----------|-------|
109
+ | `<summary>` | `id` | `id-{name}-trigger` |
110
+ | `<summary>` | `aria-controls` | `id-{name}-content` |
111
+ | `<summary>` | `aria-expanded` | `true` / `false` |
112
+ | content div | `id` | `id-{name}-content` |
113
+ | content div | `aria-labelledby` | `id-{name}-trigger` |
114
+ | content div | `role` | `region` |
115
+
116
+ Always supply a meaningful `name` prop when using multiple panels on the same page to avoid duplicate IDs.
117
+
118
+ ---
119
+
120
+ ## Local style override scaffold
121
+
122
+ When consuming this component, scaffold a style block using `styleClassPassthrough`. Delete the block if unused.
123
+
124
+ See [component-local-style-override.md](../component-local-style-override.md) for the full pattern.
125
+
126
+ ```vue
127
+ <ExpandingPanel name="my-item" :style-class-passthrough="['my-panel']">
128
+ ...
129
+ </ExpandingPanel>
130
+
131
+ <style>
132
+ /* ─── ExpandingPanel local overrides ───────────────────────────────
133
+ Colours, borders, geometry only — do not override behaviour.
134
+ Delete this block if no overrides are needed.
135
+ ─────────────────────────────────────────────────────────────────── */
136
+ .expanding-panel {
137
+ &.my-panel {
138
+ /* Border */
139
+ /* border-block-end: 1px solid currentColor; */
140
+
141
+ /* Summary row geometry */
142
+ /* .expanding-panel-details .expanding-panel-summary { padding-block: 1.2rem; } */
143
+ }
144
+ }
145
+ </style>
146
+ ```
147
+
148
+ ---
149
+
150
+ ## Notes
151
+
152
+ - The open/close animation uses `grid-template-rows: 0fr → 1fr` — no JS height measurement needed.
153
+ - When `forceOpened` is `true`, `open` stays `true` regardless of `v-model`, but `v-model` still updates internally on clicks (useful if you later set `forceOpened` back to `false`).
154
+ - Group panels into a native accordion (only one open at a time) by passing the same `name` to multiple panels or use `AccordianCore` which handles this automatically.
155
+ - Auto-imported in Nuxt — no manual import needed.
156
+ - File: `app/components/02.molecules/expandable/expanding-panel/ExpandingPanel.vue`
@@ -77,6 +77,31 @@ Text is always `text-transform: uppercase` — do not pass pre-uppercased string
77
77
 
78
78
  Override via `styleClassPassthrough` or a parent HOC `<style>` block targeting `.eyebrow-text`.
79
79
 
80
+ ## Local style override scaffold
81
+
82
+ When consuming this component, scaffold a style block using `styleClassPassthrough`. Delete the block if unused.
83
+
84
+ See [component-local-style-override.md](../component-local-style-override.md) for the full pattern.
85
+
86
+ ```vue
87
+ <EyebrowText :style-class-passthrough="['my-eyebrow']" text-content="Our Services" />
88
+
89
+ <style>
90
+ /* ─── EyebrowText local overrides ──────────────────────────────────
91
+ Colours, borders, geometry only — do not override behaviour.
92
+ Delete this block if no overrides are needed.
93
+ ─────────────────────────────────────────────────────────────────── */
94
+ .eyebrow-text {
95
+ &.my-eyebrow {
96
+ /* Colours */
97
+ /* --colour-text-eyebrow: var(--brand-accent); */
98
+ }
99
+ }
100
+ </style>
101
+ ```
102
+
103
+ > **Note:** Font size is controlled via the `fontSize` prop and theme tokens `--eyebrow-text-large/medium/small` — define these at theme level, not as local overrides.
104
+
80
105
  ## Notes
81
106
 
82
107
  - Component is auto-imported in Nuxt — no import needed.
@@ -103,6 +103,31 @@ Key CSS custom properties:
103
103
  - `--colour-text-accent` — colour applied to `.accent` spans and the icon
104
104
  - `--hero-text-{scale}` — font size per scale value
105
105
 
106
+ ## Local style override scaffold
107
+
108
+ When consuming this component, scaffold a style block using `styleClassPassthrough`. Delete the block if unused.
109
+
110
+ See [component-local-style-override.md](../component-local-style-override.md) for the full pattern.
111
+
112
+ ```vue
113
+ <HeroText :style-class-passthrough="['my-hero']" tag="h1" :text-content="[...]" />
114
+
115
+ <style>
116
+ /* ─── HeroText local overrides ─────────────────────────────────────
117
+ Colours, borders, geometry only — do not override behaviour.
118
+ Delete this block if no overrides are needed.
119
+ ─────────────────────────────────────────────────────────────────── */
120
+ .hero-text {
121
+ &.my-hero {
122
+ /* Colours */
123
+ /* --colour-text-accent: var(--brand-primary); */
124
+ }
125
+ }
126
+ </style>
127
+ ```
128
+
129
+ > **Note:** Font size is controlled via the `fontSize` prop and theme tokens `--hero-text-display/title/heading/subheading/label` — define these at theme level, not as local overrides.
130
+
106
131
  ## Notes
107
132
 
108
133
  - Text segments are trimmed and a trailing space is automatically appended between segments in horizontal axis — do not manually pad `text` values.
@@ -0,0 +1,147 @@
1
+ # LayoutGridByCols Component
2
+
3
+ ## Overview
4
+
5
+ `LayoutGridByCols` is a CSS grid layout wrapper that arranges content into a fixed number of equal-width columns. It uses **named dynamic slots** — the component renders whatever slots the consumer passes, in the order they appear. It collapses to a single column below a configurable breakpoint.
6
+
7
+ ---
8
+
9
+ ## Slot pattern
10
+
11
+ Pass any number of slots with any names. The component renders each one in document order inside the grid.
12
+
13
+ ```vue
14
+ <LayoutGridByCols :column-count="3">
15
+ <template #item-0><ServicesCard /></template>
16
+ <template #item-1><ServicesCard /></template>
17
+ <template #item-2><ServicesCard /></template>
18
+ </LayoutGridByCols>
19
+ ```
20
+
21
+ When filling from a data array, use a dynamic slot name in a `v-for`:
22
+
23
+ ```vue
24
+ <LayoutGridByCols :column-count="3">
25
+ <template v-for="(item, i) in items" #[`item-${i}`] :key="i">
26
+ <ServicesCard :data="item" />
27
+ </template>
28
+ </LayoutGridByCols>
29
+ ```
30
+
31
+ ---
32
+
33
+ ## Props reference
34
+
35
+ > **Hyphenation rule**: Vue's ESLint config enforces `vue/attribute-hyphenation`. Always write camelCase prop names hyphenated in templates: `:column-count`, `:single-col-below`, `:style-class-passthrough`.
36
+
37
+ | Prop (template form) | Type | Default | Notes |
38
+ |------|------|---------|-------|
39
+ | `:column-count` | `2 \| 3 \| 4 \| 5 \| 6` | `2` | Number of equal-width columns above `single-col-below`. Minimum is 2. |
40
+ | `:gap` | `string` | `"1rem"` | Any valid CSS length or shorthand. |
41
+ | `:single-col-below` | `string` | `"768px"` | Container width below which the grid collapses to a single column. |
42
+ | `tag` | `"div" \| "section"` | `"div"` | Use `"section"` for semantic page regions. |
43
+ | `label` | `string` | `""` | Required when `tag="section"` — rendered as a visually-hidden `<p>` linked via `aria-labelledby`. |
44
+ | `:style-class-passthrough` | `string \| string[]` | `[]` | Extra CSS classes on the root element. |
45
+
46
+ ---
47
+
48
+ ## Usage examples
49
+
50
+ ### Two-column grid (default)
51
+
52
+ ```vue
53
+ <LayoutGridByCols>
54
+ <template #left>
55
+ <p>Left cell</p>
56
+ </template>
57
+ <template #right>
58
+ <p>Right cell</p>
59
+ </template>
60
+ </LayoutGridByCols>
61
+ ```
62
+
63
+ ### Three-column card grid
64
+
65
+ ```vue
66
+ <LayoutGridByCols :column-count="3" gap="2rem">
67
+ <template #card-a><ServicesCard title="Card A" /></template>
68
+ <template #card-b><ServicesCard title="Card B" /></template>
69
+ <template #card-c><ServicesCard title="Card C" /></template>
70
+ </LayoutGridByCols>
71
+ ```
72
+
73
+ ### Section with accessible label
74
+
75
+ ```vue
76
+ <LayoutGridByCols tag="section" label="Our team" :column-count="4" gap="1.6rem">
77
+ <template #alice><TeamCard name="Alice" /></template>
78
+ <template #bob><TeamCard name="Bob" /></template>
79
+ <template #carol><TeamCard name="Carol" /></template>
80
+ <template #dan><TeamCard name="Dan" /></template>
81
+ </LayoutGridByCols>
82
+ ```
83
+
84
+ ### Data-driven with v-for
85
+
86
+ ```vue
87
+ <LayoutGridByCols :column-count="3">
88
+ <template v-for="(item, i) in items" #[`item-${i}`] :key="i">
89
+ <Card :data="item" />
90
+ </template>
91
+ </LayoutGridByCols>
92
+ ```
93
+
94
+ ---
95
+
96
+ ## Accessibility
97
+
98
+ - When `tag="section"`, the root element automatically receives `aria-labelledby` pointing to a visually-hidden `<p>` with the `label` value.
99
+ - Always provide a meaningful `label` when using `tag="section"`.
100
+ - When `tag="div"`, no label or ARIA attributes are added.
101
+
102
+ ---
103
+
104
+ ## Responsive behaviour
105
+
106
+ Uses **CSS container queries** (`container-type: inline-size`) — responds to its own container width, not the viewport.
107
+
108
+ - **Below `singleColBelow`**: single-column stacked layout.
109
+ - **At or above `singleColBelow`**: `columnCount`-column grid.
110
+
111
+ ---
112
+
113
+ ## Local style override scaffold
114
+
115
+ When consuming this component, scaffold a style block using `styleClassPassthrough`. Delete the block if unused.
116
+
117
+ See [component-local-style-override.md](../component-local-style-override.md) for the full pattern.
118
+
119
+ ```vue
120
+ <LayoutGridByCols :style-class-passthrough="['my-grid']" :column-count="3">
121
+ ...
122
+ </LayoutGridByCols>
123
+
124
+ <style>
125
+ /* ─── LayoutGridByCols local overrides ─────────────────────────────
126
+ Colours, borders, geometry only — do not override behaviour.
127
+ Delete this block if no overrides are needed.
128
+ ─────────────────────────────────────────────────────────────────── */
129
+ .layout-grid-by-cols {
130
+ &.my-grid {
131
+ /* Colours */
132
+ /* background: var(--brand-surface); */
133
+ }
134
+ }
135
+ </style>
136
+ ```
137
+
138
+ > **Note:** `gap` and `column-count` are prop-driven — use the props rather than CSS overrides for layout changes.
139
+
140
+ ---
141
+
142
+ ## Notes
143
+
144
+ - Auto-imported in Nuxt — no manual import needed.
145
+ - `column-count` is clamped to a minimum of 2 internally.
146
+ - `gap` accepts any CSS value including compound values like `"1rem 2rem"`.
147
+ - Slot names can be anything — semantic or indexed. Document order determines render order.
@@ -196,6 +196,41 @@ Override in consuming app to adjust all track sizes globally:
196
196
 
197
197
  ---
198
198
 
199
+ ## Local style override scaffold
200
+
201
+ When consuming this component, scaffold a style block using `styleClassPassthrough`. Delete the block if unused.
202
+
203
+ See [component-local-style-override.md](../component-local-style-override.md) for the full pattern.
204
+
205
+ ```vue
206
+ <LayoutRow variant="content" :style-class-passthrough="['my-row']">
207
+ ...
208
+ </LayoutRow>
209
+
210
+ <style>
211
+ /* ─── LayoutRow local overrides ────────────────────────────────────
212
+ Colours, borders, geometry only — do not override behaviour.
213
+ Delete this block if no overrides are needed.
214
+ ─────────────────────────────────────────────────────────────────── */
215
+ .layout-row {
216
+ &.my-row {
217
+ /* Track widths — for this instance only */
218
+ /* --content-max-width: 1200px; */
219
+ /* --popout-max-width: 1600px; */
220
+ /* --inset-content-max-width: 900px; */
221
+ /* --minimum-content-padding: 2rem; */
222
+
223
+ /* Colours */
224
+ /* background: var(--brand-surface); */
225
+ }
226
+ }
227
+ </style>
228
+ ```
229
+
230
+ > **Note:** Track width custom properties set here affect only this instance. For site-wide changes, set them at `:root` or theme level.
231
+
232
+ ---
233
+
199
234
  ## Notes
200
235
 
201
236
  - Auto-imported in Nuxt — no import needed.
@@ -93,6 +93,39 @@ Key CSS custom properties — define these in your consuming app to control appe
93
93
 
94
94
  Override via `styleClassPassthrough` or a parent HOC `<style>` block targeting `.link-text`.
95
95
 
96
+ ## Local style override scaffold
97
+
98
+ When consuming this component, scaffold a style block using `styleClassPassthrough`. Delete the block if unused.
99
+
100
+ See [component-local-style-override.md](../component-local-style-override.md) for the full pattern.
101
+
102
+ ```vue
103
+ <LinkText :style-class-passthrough="['my-link']" to="/path" link-text="Learn More" />
104
+
105
+ <style>
106
+ /* ─── LinkText local overrides ─────────────────────────────────────
107
+ Colours, borders, geometry only — do not override behaviour.
108
+ Delete this block if no overrides are needed.
109
+ ─────────────────────────────────────────────────────────────────── */
110
+ .link-text {
111
+ &.my-link {
112
+ /* Colours */
113
+ /* --link-text-colour: var(--brand-primary); */
114
+ /* --link-text-colour-hover: var(--brand-primary-dark); */
115
+
116
+ /* Typography */
117
+ /* --link-text-font-size: 1.6rem; */
118
+ /* --link-text-decoration: underline; */
119
+ /* --link-text-decoration-hover: none; */
120
+ /* --link-text-underline-offset: 0.3em; */
121
+
122
+ /* Geometry */
123
+ /* --link-text-gap: 0.6em; */
124
+ }
125
+ }
126
+ </style>
127
+ ```
128
+
96
129
  ## Notes
97
130
 
98
131
  - Component is auto-imported in Nuxt — no import needed.