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,224 @@
1
+ # PageHeroHighlights Component
2
+
3
+ ## Overview
4
+
5
+ `PageHeroHighlights` is a page-level layout template that creates a "hero + highlights strip" grid. It has:
6
+
7
+ - A **header zone** — full edge-to-edge background, content drives the row height
8
+ - A **highlights strip** — straddles the header/content boundary (overlaps both), sits above via `z-index`
9
+ - A **content zone** — background fills behind the highlights strip, actual content sits below it
10
+
11
+ The layout uses a 4-row CSS Grid with `subgrid` — no `translate`, negative margins, or absolute positioning.
12
+
13
+ ## Props
14
+
15
+ | Prop | Type | Default | Description |
16
+ | ----------------------- | ------------------------------------------------------------------- | ----------- | ------------------------------------------------------------------------------------------------------------------------------- |
17
+ | `tag` | `"div" \| "section" \| "main"` | `"div"` | Root element tag |
18
+ | `highlightsEqualWidths` | `boolean` | `false` | Equal-width grid columns for highlight items |
19
+ | `highlightsJustify` | `"start" \| "center" \| "end" \| "space-between" \| "space-around"` | `"start"` | Alignment of highlight items along the main axis |
20
+ | `maxWidth` | `string` | `undefined` | Cap the central content column (e.g. `"1064px"`). Gutters grow to enforce the constraint; below this width they hold at `16px`. |
21
+ | `contentAlign` | `"start" \| "center"` | `"center"` | When `maxWidth` is set: `"center"` grows gutters equally; `"start"` pins content to the left with a fixed `16px` left gutter. |
22
+ | `styleClassPassthrough` | `string \| string[]` | `[]` | Extra classes on the root element |
23
+
24
+ ## Slots
25
+
26
+ | Slot | Slot props | Purpose |
27
+ | ------------ | ----------------------- | ----------------------------------- |
28
+ | `header` | `{ headingId: string }` | Header zone — text, title, subtitle |
29
+ | `highlights` | — | Highlight cards in the strip |
30
+ | `content` | — | Page body content below the strip |
31
+
32
+ ## Basic usage
33
+
34
+ ```vue
35
+ <PageHeroHighlights>
36
+ <template #header>
37
+ <p class="page-heading-1">Dashboard</p>
38
+ <p class="page-body-normal">Overview of your account activity.</p>
39
+ </template>
40
+
41
+ <template #highlights>
42
+ <div class="card">Total Revenue: £24,500</div>
43
+ <div class="card">Active Users: 1,284</div>
44
+ <div class="card">Open Tasks: 37</div>
45
+ </template>
46
+
47
+ <template #content>
48
+ <p class="page-heading-2">Recent Activity</p>
49
+ </template>
50
+ </PageHeroHighlights>
51
+ ```
52
+
53
+ ## Composition with other library components
54
+
55
+ Each slot accepts any content, but these library components are natural fits:
56
+
57
+ | Slot | Component | Notes |
58
+ | ------------- | --------------------------------- | ----------------------------------------------------------------------------------------------- |
59
+ | `#header` | `HeroText` | Heading with accent text, icon, and configurable size — wires `headingId` for `aria-labelledby` |
60
+ | `#highlights` | `ServicesCard` (×n) | Portrait cards with image, title, description, and CTA slot |
61
+ | `#highlights` | `LayoutGridByCols` wrapping cards | When you want a responsive column grid rather than a single row of cards |
62
+ | `#content` | Any content component | Below the straddle — safe to use `LayoutRow`, `ServicesSection`, etc. |
63
+
64
+ Example with `HeroText` in the header slot:
65
+
66
+ ```vue
67
+ <PageHeroHighlights tag="section" max-width="1064px">
68
+ <template #header="{ headingId }">
69
+ <HeroText :heading-id="headingId" text="Welcome back" accent-text="Simon" />
70
+ </template>
71
+
72
+ <template #highlights>
73
+ <ServicesCard v-for="item in highlights" :key="item.id" v-bind="item">
74
+ <template #actions>
75
+ <LinkText :href="item.href">View</LinkText>
76
+ </template>
77
+ </ServicesCard>
78
+ </template>
79
+
80
+ <template #content>
81
+ <!-- page body -->
82
+ </template>
83
+ </PageHeroHighlights>
84
+ ```
85
+
86
+ ## With aria-labelledby (section tag)
87
+
88
+ When `tag="section"`, `aria-labelledby` is set automatically. Wire the heading id via the scoped slot prop:
89
+
90
+ ```vue
91
+ <PageHeroHighlights tag="section">
92
+ <template #header="{ headingId }">
93
+ <h1 :id="headingId" class="page-heading-1">Dashboard</h1>
94
+ </template>
95
+ ...
96
+ </PageHeroHighlights>
97
+ ```
98
+
99
+ See [component-aria-landmark.md](../component-aria-landmark.md) for the full pattern.
100
+
101
+ ## Equal-width highlights
102
+
103
+ By default, highlight items size to their content (`flex-wrap`). Pass `:highlights-equal-widths="true"` to switch to a grid where all items share equal column widths:
104
+
105
+ ```vue
106
+ <PageHeroHighlights :highlights-equal-widths="true">
107
+ ...
108
+ </PageHeroHighlights>
109
+ ```
110
+
111
+ ## Constraining the central column width
112
+
113
+ Pass `max-width` to cap the content column. The gutters grow to enforce it — full-bleed backgrounds are unaffected and `subgrid` continues to work. Use `content-align` to pin to the left or centre:
114
+
115
+ ```vue
116
+ <!-- Centred, capped at 1064px -->
117
+ <PageHeroHighlights max-width="1064px" content-align="center">...</PageHeroHighlights>
118
+
119
+ <!-- Left-pinned, capped at 1064px (right side takes remaining space) -->
120
+ <PageHeroHighlights max-width="1064px" content-align="start">...</PageHeroHighlights>
121
+ ```
122
+
123
+ See [css-grid-max-width-gutters.md](../css-grid-max-width-gutters.md) for the full pattern explanation.
124
+
125
+ ## Local style override scaffold
126
+
127
+ When consuming this component, scaffold a style block using `styleClassPassthrough`. The block below lists every available CSS custom property — update the values you need and delete the rest.
128
+
129
+ See [component-local-style-override.md](../component-local-style-override.md) for the full pattern.
130
+
131
+ ```vue
132
+ <PageHeroHighlights :style-class-passthrough="['my-page-hero']">
133
+ ...
134
+ </PageHeroHighlights>
135
+
136
+ <style>
137
+ /* ─── PageHeroHighlights local overrides ────────────────────────────
138
+ Update values as needed. Delete tokens you are not overriding.
139
+ ─────────────────────────────────────────────────────────────────── */
140
+ .page-hero-highlights {
141
+ &.my-page-hero {
142
+ /* Header zone */
143
+ /* --header-row-background-colour: darkblue; */
144
+
145
+ /* Highlights strip */
146
+ /* --highlights-row-item-gap: 1rem; */
147
+ /* --highlights-row-initial-item-offset: 1.2rem; */
148
+
149
+ /* Highlight cards */
150
+ /* --highlight-rows-gap: 1.2rem; */
151
+ /* --highlight-title-height: 1fr; */ /* see: highlight-title-baseline prop */
152
+ /* --highlight-padding-block-start: 1.2rem; */
153
+ /* --highlight-padding: 1.2rem; */
154
+ /* --highlight-background-color: white; */
155
+ /* --highlight-border: 1px solid black; */
156
+ /* --highlight-border-radius: 8px; */
157
+ /* --highlight-color: black; */
158
+
159
+ /* Content zone */
160
+ /* --content-row-background-color: var(--slate-01); */ /* transparent */
161
+ /* --content-row-start-gap: 1.2rem; */
162
+ /* --content-row-end-gap: 1.2rem; */
163
+
164
+ /* Content slot decorative border */
165
+ /* --content-slot-margin-block-start: 2.4rem; */
166
+ /* --content-slot-margin: var(--highlights-row-initial-item-offset); */
167
+ /* --content-slot-background-color: var(--slate-00); */
168
+ /* --content-slot-border: 1px solid var(--slate-06); */
169
+ /* --content-slot-border-radius: 0.8rem; */
170
+ /* --content-slot-outline: 1px solid var(--slate-02); */
171
+
172
+ /* When using :highlight-title-baseline="true" */
173
+ /* &.highlight-title-baseline { */
174
+ /* --highlight-title-height: 4rem; */ /* proportional value preferred */
175
+ /* --highlight-padding-block-start: 0; */
176
+ /* } */
177
+ }
178
+ }
179
+ </style>
180
+ ```
181
+
182
+ > **Note:** The minimum gutter width (`16px`) and layout behaviour are not overridable via CSS custom properties. Use the `max-width` and `content-align` props to control column constraints.
183
+
184
+ ## Grid structure (reference)
185
+
186
+ ```text
187
+ col: [gutter] [centre] [gutter]
188
+ row1: header content (height driven by slot)
189
+ row2: highlights top half ← highlights spans rows 2–3, z-index: 1
190
+ row3: highlights bottom half
191
+ row4: page content (never underflows highlights)
192
+ ```
193
+
194
+ `.header-row` spans cols 1–3, rows 1–2 (edge-to-edge bg). `.header-slot` is placed in row 1 only.
195
+ `.content-row` spans cols 1–3, rows 3–4 (bg fills behind highlights; `.content-slot` is placed in row 4 only). The decorative border behind `.content-slot` is rendered via `.content-row:before` — there is no separate DOM element for it.
196
+
197
+ ## Layout pitfall: do not use `grid-template-rows: subgrid` inside `.highlights-row`
198
+
199
+ The `.highlights-row` element spans rows 2–3 of the parent grid (the "straddle"). If you add an inner grid to `.highlights-row` (e.g. to extend `equal-widths` behaviour) and include `grid-template-rows: subgrid`, auto-placed items will only occupy row 1 of the subgrid (= parent row 2). Parent row 3 collapses to 0-height, destroying the straddle effect — `.content-row` appears immediately below the highlights instead of overlapping it.
200
+
201
+ ```css
202
+ /* ❌ — breaks the straddle when items are auto-placed by column flow */
203
+ &.equal-widths {
204
+ display: grid;
205
+ grid-template-rows: subgrid; /* row 3 collapses */
206
+ grid-auto-columns: 1fr;
207
+ grid-auto-flow: column;
208
+ }
209
+
210
+ /* ✅ — single implicit row; items stretch to fill combined height of rows 2–3 */
211
+ &.equal-widths {
212
+ display: grid;
213
+ grid-auto-columns: 1fr;
214
+ grid-auto-flow: column;
215
+ }
216
+ ```
217
+
218
+ ## Notes
219
+
220
+ - Component is auto-imported in Nuxt — no import needed.
221
+ - Lives in `app/components/04.templates/page-hero-highlights/`.
222
+ - Storybook title: `"Templates/PageHeroHighlights"`.
223
+ - **Minimum gutter is fixed at `16px`** — it is baked into the `gridColumns` computed and cannot be overridden by a CSS custom property. If a consumer needs a different minimum (e.g. `24px`), it requires a prop or a fork of the component.
224
+ - **`contentAlign` has no effect without `maxWidth`** — both sides always hold `16px` when `maxWidth` is not set.
@@ -53,6 +53,34 @@ The `actions` slot receives `serviceData` as a scoped prop so the consumer can c
53
53
  </ServicesCard>
54
54
  ```
55
55
 
56
+ ## Local style override scaffold
57
+
58
+ When consuming this component, scaffold a style block using `styleClassPassthrough`. Delete the block if unused.
59
+
60
+ See [component-local-style-override.md](../component-local-style-override.md) for the full pattern.
61
+
62
+ ```vue
63
+ <ServicesCard :style-class-passthrough="['my-card']" :service-data="service">
64
+ ...
65
+ </ServicesCard>
66
+
67
+ <style>
68
+ /* ─── ServicesCard local overrides ─────────────────────────────────
69
+ Colours, borders, geometry only — do not override behaviour.
70
+ Delete this block if no overrides are needed.
71
+ ─────────────────────────────────────────────────────────────────── */
72
+ .services-card {
73
+ &.my-card {
74
+ /* Geometry — image */
75
+ /* .image-wrapper { border-radius: 1.2rem; } */
76
+
77
+ /* Colours */
78
+ /* .description { color: var(--brand-text-secondary); } */
79
+ }
80
+ }
81
+ </style>
82
+ ```
83
+
56
84
  ## Notes
57
85
 
58
86
  - Component is auto-imported in Nuxt — no import needed.
@@ -104,6 +104,31 @@ Both slots receive `serviceData` as a scoped prop.
104
104
  </ServicesSection>
105
105
  ```
106
106
 
107
+ ## Local style override scaffold
108
+
109
+ When consuming this component, scaffold a style block using `styleClassPassthrough`. Delete the block if unused.
110
+
111
+ See [component-local-style-override.md](../component-local-style-override.md) for the full pattern.
112
+
113
+ ```vue
114
+ <ServicesSection :style-class-passthrough="['my-section']" :service-data="service">
115
+ ...
116
+ </ServicesSection>
117
+
118
+ <style>
119
+ /* ─── ServicesSection local overrides ──────────────────────────────
120
+ Colours, borders, geometry only — do not override behaviour.
121
+ Delete this block if no overrides are needed.
122
+ ─────────────────────────────────────────────────────────────────── */
123
+ .services-section {
124
+ &.my-section {
125
+ /* Geometry — image wrapper */
126
+ /* .image-wrapper { border-radius: 1.2rem; } */
127
+ }
128
+ }
129
+ </style>
130
+ ```
131
+
107
132
  ## Notes
108
133
 
109
134
  - Component is auto-imported in Nuxt — no import needed.
@@ -0,0 +1,227 @@
1
+ # StepperList Component
2
+
3
+ ## Overview
4
+
5
+ `StepperList` is a numbered/stepped list component where each item has a visual indicator (a counter bubble or custom icon) and a content area. Indicators can optionally be connected by a vertical line between them. The number of items is controlled by the `itemCount` prop — content is filled via **dynamic named slots**.
6
+
7
+ ---
8
+
9
+ ## Dynamic slot pattern
10
+
11
+ `StepperList` generates slots based on `itemCount`. For `itemCount="3"` the following slots exist:
12
+
13
+ | Slot | Purpose |
14
+ |------|---------|
15
+ | `#item-0` | Content of the first list item |
16
+ | `#item-1` | Content of the second list item |
17
+ | `#item-2` | Content of the third list item |
18
+ | `#indicator-0` | Custom indicator replacing the default counter bubble for item 0 |
19
+ | `#indicator-1` | Custom indicator replacing the default counter bubble for item 1 |
20
+ | … | … |
21
+
22
+ **Rules:**
23
+ - Slots are always **zero-indexed**: first item = `item-0`, last item = `item-{itemCount - 1}`.
24
+ - `item-{n}` slots are required if you want content in each row.
25
+ - `indicator-{n}` slots are **optional**. When omitted, a CSS counter bubble is rendered automatically. When provided, the custom content replaces the counter entirely — the CSS counter is hidden for that item only.
26
+ - You can mix custom indicators and counter bubbles across items in the same list.
27
+ - Always set `itemCount` to match the number of `#item-*` slots you provide.
28
+
29
+ ---
30
+
31
+ ## Props reference
32
+
33
+ | Prop | Type | Default | Notes |
34
+ |------|------|---------|-------|
35
+ | `itemCount` | `number` | — | **Required.** Controls how many `<li>` elements are rendered and which slots exist. |
36
+ | `tag` | `"ul" \| "ol"` | `"ul"` | Use `"ol"` for sequentially meaningful content (recipes, instructions). |
37
+ | `indicatorAlignment` | `"top" \| "center"` | `"top"` | Vertical alignment of the indicator relative to each item's content. `"center"` suits single-line items; `"top"` suits multi-line content. |
38
+ | `indicatorVariant` | `"disc" \| "circle" \| "square"` | `"disc"` | Visual style of the auto-generated counter bubble. |
39
+ | `indicatorSize` | `string` | `"3rem"` | Any valid CSS length — controls the width/height of the indicator bubble. |
40
+ | `connected` | `boolean` | `true` | Draws a vertical connector line between indicators. JS measures indicator positions to calculate the line height precisely. |
41
+ | `styleClassPassthrough` | `string \| string[]` | `[]` | Extra CSS classes applied to the root element. |
42
+
43
+ ---
44
+
45
+ ## Usage examples
46
+
47
+ ### Basic counter list (no indicator slots)
48
+
49
+ ```vue
50
+ <StepperList :itemCount="3" indicatorVariant="disc">
51
+ <template #item-0><p>Plan your project goals</p></template>
52
+ <template #item-1><p>Set up your development environment</p></template>
53
+ <template #item-2><p>Ship and monitor</p></template>
54
+ </StepperList>
55
+ ```
56
+
57
+ Counter bubbles are rendered automatically via CSS — no `#indicator-*` slots needed.
58
+
59
+ ### Ordered list with connectors
60
+
61
+ ```vue
62
+ <StepperList tag="ol" :itemCount="4" :connected="true" indicatorVariant="circle">
63
+ <template #item-0><p>Preheat the oven to 180°C</p></template>
64
+ <template #item-1><p>Sift together the flour and baking powder</p></template>
65
+ <template #item-2><p>Cream the butter and sugar until fluffy</p></template>
66
+ <template #item-3><p>Bake for 25–30 minutes</p></template>
67
+ </StepperList>
68
+ ```
69
+
70
+ ### Custom indicator icons (SVG)
71
+
72
+ ```vue
73
+ <StepperList :itemCount="3">
74
+ <template #indicator-0>
75
+ <svg class="indicator-icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
76
+ <polyline points="20 6 9 17 4 12" />
77
+ </svg>
78
+ </template>
79
+ <template #item-0><p>Identity verified</p></template>
80
+
81
+ <template #indicator-1>
82
+ <svg class="indicator-icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
83
+ <polyline points="20 6 9 17 4 12" />
84
+ </svg>
85
+ </template>
86
+ <template #item-1><p>Payment confirmed</p></template>
87
+
88
+ <template #indicator-2>
89
+ <svg class="indicator-icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
90
+ <circle cx="12" cy="12" r="10" /><line x1="12" y1="8" x2="12" y2="12" /><line x1="12" y1="16" x2="12.01" y2="16" />
91
+ </svg>
92
+ </template>
93
+ <template #item-2><p>Awaiting email confirmation</p></template>
94
+ </StepperList>
95
+ ```
96
+
97
+ Custom indicator SVGs should use the `indicator-icon` class — it applies `color: var(--stepper-list-icon)` and sizes the icon to match `indicatorSize`.
98
+
99
+ ### Mixed — custom icons for completed steps, counters for pending
100
+
101
+ ```vue
102
+ <StepperList :itemCount="4" :connected="true">
103
+ <!-- Completed steps: custom checkmark icon -->
104
+ <template #indicator-0>
105
+ <svg class="indicator-icon" ...>...</svg>
106
+ </template>
107
+ <template #item-0><p>Account created</p></template>
108
+
109
+ <template #indicator-1>
110
+ <svg class="indicator-icon" ...>...</svg>
111
+ </template>
112
+ <template #item-1><p>Email verified</p></template>
113
+
114
+ <!-- Pending steps: no indicator slot → CSS counter bubble shown -->
115
+ <template #item-2><p>Choose a plan</p></template>
116
+ <template #item-3><p>Start building</p></template>
117
+ </StepperList>
118
+ ```
119
+
120
+ ### Rich item content (heading + body)
121
+
122
+ ```vue
123
+ <StepperList tag="ol" :itemCount="3">
124
+ <template #item-0>
125
+ <div>
126
+ <strong>Create your account</strong>
127
+ <p style="margin: 0.25rem 0 0">Enter your email and choose a password.</p>
128
+ </div>
129
+ </template>
130
+ <template #item-1>
131
+ <div>
132
+ <strong>Verify your email</strong>
133
+ <p style="margin: 0.25rem 0 0">Click the link we sent to your inbox.</p>
134
+ </div>
135
+ </template>
136
+ <template #item-2>
137
+ <div>
138
+ <strong>Start building</strong>
139
+ <p style="margin: 0.25rem 0 0">You're all set — open your dashboard.</p>
140
+ </div>
141
+ </template>
142
+ </StepperList>
143
+ ```
144
+
145
+ ---
146
+
147
+ ## CSS custom properties
148
+
149
+ Override these in your consuming component or theme to restyle the indicators and connectors:
150
+
151
+ | Property | Used by |
152
+ |----------|---------|
153
+ | `--stepper-list-counter-disc-background` | Disc variant counter bubble background |
154
+ | `--stepper-list-counter-disc-text` | Disc variant counter number colour |
155
+ | `--stepper-list-counter-disc-border` | Disc variant counter border colour |
156
+ | `--stepper-list-counter-circle-background` | Circle variant counter bubble background |
157
+ | `--stepper-list-counter-circle-text` | Circle variant counter number colour |
158
+ | `--stepper-list-counter-circle-border` | Circle variant counter border colour |
159
+ | `--stepper-list-counter-square-background` | Square variant counter bubble background |
160
+ | `--stepper-list-counter-square-text` | Square variant counter number colour |
161
+ | `--stepper-list-counter-square-border` | Square variant counter border colour |
162
+ | `--stepper-list-connector-color` | Connector line colour (defaults to `currentColor`) |
163
+ | `--stepper-list-icon` | Icon colour for custom `indicator-icon` SVGs |
164
+
165
+ ---
166
+
167
+ ## Connector behaviour
168
+
169
+ - Connectors are drawn as `::after` pseudo-elements on each `<li>` except the last.
170
+ - JS (`ResizeObserver`) measures the bottom of each indicator and the top of the next to set `--_connector-top` and `--_connector-height` precisely, keeping the line flush between bubbles regardless of content height.
171
+ - Before JS runs (SSR / initial paint), CSS fallback values approximate the correct position for `indicatorAlignment="top"`.
172
+ - If `connected="false"`, no connectors are rendered and JS measurement is skipped.
173
+ - Connectors recalculate automatically when `itemCount`, `connected`, or `indicatorAlignment` props change.
174
+
175
+ ---
176
+
177
+ ## Local style override scaffold
178
+
179
+ When consuming this component, scaffold a style block using `styleClassPassthrough`. Delete the block if unused.
180
+
181
+ See [component-local-style-override.md](../component-local-style-override.md) for the full pattern.
182
+
183
+ ```vue
184
+ <StepperList :style-class-passthrough="['my-stepper']" :item-count="3">
185
+ ...
186
+ </StepperList>
187
+
188
+ <style>
189
+ /* ─── StepperList local overrides ──────────────────────────────────
190
+ Colours, borders, geometry only — do not override behaviour.
191
+ Delete this block if no overrides are needed.
192
+ ─────────────────────────────────────────────────────────────────── */
193
+ .stepper-list {
194
+ &.my-stepper {
195
+ /* Counter bubble — disc variant */
196
+ /* --stepper-list-counter-disc-background: var(--brand-primary); */
197
+ /* --stepper-list-counter-disc-text: white; */
198
+ /* --stepper-list-counter-disc-border: transparent; */
199
+
200
+ /* Counter bubble — circle variant */
201
+ /* --stepper-list-counter-circle-background: transparent; */
202
+ /* --stepper-list-counter-circle-text: var(--brand-primary); */
203
+ /* --stepper-list-counter-circle-border: var(--brand-primary); */
204
+
205
+ /* Counter bubble — square variant */
206
+ /* --stepper-list-counter-square-background: var(--brand-primary); */
207
+ /* --stepper-list-counter-square-text: white; */
208
+ /* --stepper-list-counter-square-border: transparent; */
209
+
210
+ /* Custom indicator icon colour */
211
+ /* --stepper-list-icon: var(--brand-primary); */
212
+
213
+ /* Connector line */
214
+ /* --stepper-list-connector-color: var(--brand-primary); */
215
+ }
216
+ }
217
+ </style>
218
+ ```
219
+
220
+ ---
221
+
222
+ ## Notes
223
+
224
+ - Always keep `itemCount` in sync with the number of `#item-*` slots you provide — mismatches will render empty `<li>` rows.
225
+ - Slot names are **zero-indexed** — `#item-0` not `#item-1`.
226
+ - The `indicator-icon` class is defined inside the component CSS and sizes the SVG to `var(--_counter-size)` (driven by `indicatorSize`). Always add it to custom SVGs.
227
+ - Auto-imported in Nuxt — no manual import needed.
@@ -0,0 +1,67 @@
1
+ # CSS Grid — Max Width via Growing Gutters
2
+
3
+ ## Overview
4
+
5
+ A pattern for capping the width of a central grid column without breaking full-bleed backgrounds. Instead of capping the column with `max-width`, the gutters grow to enforce the constraint. The centre column stays `1fr` and the layout never breaks.
6
+
7
+ ## The problem with capping the centre column
8
+
9
+ Using `minmax(0, 1064px)` on the centre column caps its width but doesn't distribute the leftover space — it simply goes unused. Full-bleed backgrounds on adjacent rows break, and you lose the ability to use `subgrid`.
10
+
11
+ ## The pattern
12
+
13
+ Grow the gutters instead:
14
+
15
+ ```css
16
+ grid-template-columns: max(MIN_GUTTER, (100% - MAX_WIDTH) / 2) 1fr max(MIN_GUTTER, (100% - MAX_WIDTH) / 2);
17
+ ```
18
+
19
+ - When the container is **narrower** than `MAX_WIDTH`: `(100% - MAX_WIDTH) / 2` is negative, `max()` clamps back to `MIN_GUTTER`. Gutters hold their minimum.
20
+ - When the container is **wider** than `MAX_WIDTH`: gutters grow equally, enforcing the cap. The centre column never exceeds `MAX_WIDTH`.
21
+
22
+ ## Start-aligned variant
23
+
24
+ When you want the content pinned to one side (e.g. left-aligned editorial layout):
25
+
26
+ ```css
27
+ grid-template-columns: MIN_GUTTER minmax(0, MAX_WIDTH) 1fr;
28
+ ```
29
+
30
+ Left gutter stays fixed, content column is capped at `MAX_WIDTH`, remaining space goes to the right.
31
+
32
+ ## In Vue with a prop
33
+
34
+ Because `v-bind()` in `<style>` can't be nested inside CSS functions like `max()`, build the column string as a computed and bind the whole value:
35
+
36
+ ```ts
37
+ // Props
38
+ interface Props {
39
+ maxWidth?: string; // e.g. "1064px"
40
+ contentAlign?: "start" | "center";
41
+ }
42
+
43
+ const props = withDefaults(defineProps<Props>(), {
44
+ maxWidth: undefined,
45
+ contentAlign: "center",
46
+ });
47
+
48
+ // Computed column string
49
+ const gridColumns = computed(() => {
50
+ if (!props.maxWidth) return "16px 1fr 16px";
51
+ if (props.contentAlign === "start") return `16px minmax(0, ${props.maxWidth}) 1fr`;
52
+ return `max(16px, (100% - ${props.maxWidth}) / 2) 1fr max(16px, (100% - ${props.maxWidth}) / 2)`;
53
+ });
54
+ ```
55
+
56
+ ```css
57
+ .component {
58
+ display: grid;
59
+ grid-template-columns: v-bind(gridColumns);
60
+ }
61
+ ```
62
+
63
+ ## Notes
64
+
65
+ - `subgrid` on child elements still works — the column count doesn't change, only the gutter widths.
66
+ - The minimum gutter (e.g. `16px`) is always enforced, so narrow viewports are safe without media queries.
67
+ - `contentAlign` has no effect when `maxWidth` is not set — fall through to the fixed-gutter default.
@@ -23,15 +23,27 @@ Each skill is a single markdown file named `<area>-<task>.md`.
23
23
  ├── storybook-add-font.md — add a new font to Storybook
24
24
  ├── testing-add-unit-test.md — create a Vitest unit test with snapshots
25
25
  ├── testing-add-playwright.md — create a Playwright visual regression test
26
- ├── theming-override-default.md — override the default theme with a custom colour scale
27
- ├── colour-scheme-disable.md — disable light/dark scheme support in a consumer app
26
+ ├── theming-override-default.md — override the default theme with a custom colour scale
27
+ ├── colour-scheme-disable.md — disable light/dark scheme support in a consumer app
28
+ ├── component-dynamic-slots.md — named dynamic slots ($slots iteration) vs indexed dynamic slots (itemCount pattern)
29
+ ├── component-local-style-override.md — styleClassPassthrough + scoped style block for per-usage visual customisation
30
+ ├── component-prop-driven-container-layout.md — vary CSS grid layout inside @container queries using data-* attribute selectors
31
+ ├── css-grid-max-width-gutters.md — cap a centre grid column width by growing gutters, with start/center alignment variants
32
+ ├── component-aria-landmark.md — useAriaLabelledById composable: aria-labelledby for section/main/article/aside tags
33
+ ├── component-export-types.md — move inline component types to app/types/components/ barrel for consumer imports
28
34
  └── components/
35
+ ├── accordian-core.md — AccordianCore indexed dynamic slots (accordian-{n}-summary/icon/content), exclusive-open grouping
29
36
  ├── eyebrow-text.md — EyebrowText props, usage patterns, styling
30
37
  ├── hero-text.md — HeroText props, usage patterns, styling
38
+ ├── layout-grid-by-cols.md — LayoutGridByCols dynamic slots (item-{n}), props, column/gap/breakpoint decisions
31
39
  ├── layout-row.md — LayoutRow variant guide, width/margin decisions, usage patterns
32
40
  ├── link-text.md — LinkText props, slots, usage patterns, styling
41
+ ├── page-hero-highlights.md — PageHeroHighlights template: hero + highlights strip grid, CSS custom property theming
33
42
  ├── services-card.md — ServicesCard props, actions slot, usage patterns
34
- └── services-section.md — ServicesSection props, summary-link/cta slots, summary vs full mode
43
+ ├── services-section.md — ServicesSection props, summary-link/cta slots, summary vs full mode
44
+ ├── contact-section.md — ContactSection props (stepperIndicatorSize pass-through), 3-item info+form layout, slot API
45
+ ├── stepper-list.md — StepperList dynamic slots (item-{n}/indicator-{n}), props, connector behaviour
46
+ └── expanding-panel.md — ExpandingPanel v-model, forceOpened, slots (summary/icon/content), ARIA wiring
35
47
  ```
36
48
 
37
49
  ## Skill file template