srcdev-nuxt-components 9.3.11 → 9.3.13

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 (77) hide show
  1. package/.claude/skills/component-aria-landmark.md +3 -3
  2. package/.claude/skills/components/auto-grid.md +1 -1
  3. package/.claude/skills/components/page-hero-highlights.md +1 -1
  4. package/.claude/skills/components/service-detail.md +13 -14
  5. package/.claude/skills/components/service-summary-grid.md +141 -0
  6. package/.claude/skills/components/service-summary.md +165 -0
  7. package/.claude/skills/icon-sets.md +1 -1
  8. package/.claude/skills/index.md +3 -2
  9. package/.claude/skills/theming-component-token-pattern.md +126 -0
  10. package/.vscode/srcdev-component-service-summary.code-snippets +67 -0
  11. package/app/assets/styles/setup/04.elements/forms/06.button-geometry.css +10 -1
  12. package/app/components/02.molecules/input-copy/CONSUMER-STYLING.md +3 -3
  13. package/app/components/02.molecules/input-copy/InputCopy.vue +9 -3
  14. package/app/components/02.molecules/pricing-card/CONSUMER-STYLING.md +3 -3
  15. package/app/components/02.molecules/pricing-card/PricingCard.vue +6 -3
  16. package/app/components/03.organisms/services/service-detail/CONSUMER-STYLING.md +7 -8
  17. package/app/components/03.organisms/services/service-detail/ServiceDetail.vue +0 -1
  18. package/app/components/03.organisms/services/service-detail/stories/ServiceDetail.stories.ts +2 -2
  19. package/app/components/03.organisms/services/service-summary/CONSUMER-STYLING.md +116 -0
  20. package/app/components/03.organisms/services/service-summary/ServiceSummary.vue +183 -0
  21. package/app/components/03.organisms/services/{services-section/stories/ServicesSection.stories.ts → service-summary/stories/ServiceSummary.stories.ts} +78 -148
  22. package/app/components/03.organisms/services/service-summary/tests/ServiceSummary.spec.ts +210 -0
  23. package/app/components/03.organisms/services/service-summary/tests/__snapshots__/ServiceSummary.spec.ts.snap +17 -0
  24. package/app/components/03.organisms/services/services-grids/{ServicesSectionGrid.vue → ServiceSummaryGrid.vue} +8 -10
  25. package/app/components/03.organisms/services/services-grids/stories/{ServicesSectionGrid.stories.ts → ServiceSummaryGrid.stories.ts} +21 -21
  26. package/app/components/03.organisms/services/services-grids/tests/{ServicesSectionGrid.spec.ts → ServiceSummaryGrid.spec.ts} +28 -28
  27. package/app/components/03.organisms/services/services-grids/tests/__snapshots__/ServiceSummaryGrid.spec.ts.snap +45 -0
  28. package/app/components/05.forms/input-button/CONSUMER-STYLING.md +117 -0
  29. package/app/components/05.forms/input-button/InputButtonCore.vue +83 -37
  30. package/app/components/05.forms/input-button/playwright/input-button-core.playwright.ts-snapshots/combo-icons-secondary-chromium-darwin.png +0 -0
  31. package/app/components/05.forms/input-button/playwright/input-button-core.playwright.ts-snapshots/combo-icons-tertiary-chromium-darwin.png +0 -0
  32. package/app/components/05.forms/input-button/playwright/input-button-core.playwright.ts-snapshots/combo-primary-pending-chromium-darwin.png +0 -0
  33. package/app/components/05.forms/input-button/playwright/input-button-core.playwright.ts-snapshots/combo-secondary-pill-chromium-darwin.png +0 -0
  34. package/app/components/05.forms/input-button/playwright/input-button-core.playwright.ts-snapshots/combo-tertiary-readonly-chromium-darwin.png +0 -0
  35. package/app/components/05.forms/input-button/playwright/input-button-core.playwright.ts-snapshots/default-chromium-darwin.png +0 -0
  36. package/app/components/05.forms/input-button/playwright/input-button-core.playwright.ts-snapshots/default-theme-default-chromium-darwin.png +0 -0
  37. package/app/components/05.forms/input-button/playwright/input-button-core.playwright.ts-snapshots/default-theme-error-chromium-darwin.png +0 -0
  38. package/app/components/05.forms/input-button/playwright/input-button-core.playwright.ts-snapshots/default-theme-success-chromium-darwin.png +0 -0
  39. package/app/components/05.forms/input-button/playwright/input-button-core.playwright.ts-snapshots/default-theme-warning-chromium-darwin.png +0 -0
  40. package/app/components/05.forms/input-button/playwright/input-button-core.playwright.ts-snapshots/default-variant-primary-chromium-darwin.png +0 -0
  41. package/app/components/05.forms/input-button/playwright/input-button-core.playwright.ts-snapshots/default-variant-secondary-chromium-darwin.png +0 -0
  42. package/app/components/05.forms/input-button/playwright/input-button-core.playwright.ts-snapshots/default-variant-tertiary-chromium-darwin.png +0 -0
  43. package/app/components/05.forms/input-button/playwright/input-button-core.playwright.ts-snapshots/nuxt-icon-only-component-chromium-darwin.png +0 -0
  44. package/app/components/05.forms/input-button/playwright/input-button-core.playwright.ts-snapshots/state-pending-chromium-darwin.png +0 -0
  45. package/app/components/05.forms/input-button/playwright/input-button-core.playwright.ts-snapshots/state-pill-chromium-darwin.png +0 -0
  46. package/app/components/05.forms/input-button/playwright/input-button-core.playwright.ts-snapshots/state-readonly-chromium-darwin.png +0 -0
  47. package/app/components/05.forms/input-button/playwright/input-button-core.playwright.ts-snapshots/with-both-nuxt-icon-components-chromium-darwin.png +0 -0
  48. package/app/components/05.forms/input-button/stories/InputButtonCore.stories.ts +35 -0
  49. package/app/components/05.forms/input-checkbox-radio/CONSUMER-STYLING.md +68 -0
  50. package/app/components/05.forms/input-checkbox-radio/InputCheckboxRadioButton.vue +19 -9
  51. package/app/components/05.forms/input-checkbox-radio/InputCheckboxRadioCore.vue +8 -5
  52. package/app/components/05.forms/input-checkbox-radio/stories/InputCheckboxRadioButton.stories.ts +93 -0
  53. package/app/components/05.forms/input-checkbox-radio/stories/InputCheckboxRadioCore.stories.ts +75 -0
  54. package/app/components/05.forms/input-number/CONSUMER-STYLING.md +41 -0
  55. package/app/components/05.forms/input-number/InputNumberCore.vue +12 -6
  56. package/app/components/05.forms/input-select/CONSUMER-STYLING.md +35 -29
  57. package/app/components/05.forms/input-select/InputSelectCore.vue +32 -20
  58. package/app/components/05.forms/input-select/stories/InputSelectCore.stories.ts +69 -0
  59. package/app/components/05.forms/input-text/CONSUMER-STYLING.md +38 -48
  60. package/app/components/05.forms/input-text/InputTextCore.vue +35 -18
  61. package/app/components/05.forms/patterns/stories/ContactForm.stories.ts +184 -0
  62. package/app/components/05.forms/patterns/stories/LoginForm.stories.ts +125 -0
  63. package/app/components/05.forms/toggle-switch/CONSUMER-STYLING.md +49 -0
  64. package/app/components/05.forms/toggle-switch/ToggleSwitchCore.vue +23 -16
  65. package/app/components/05.forms/triple-toggle-switch/CONSUMER-STYLING.md +72 -0
  66. package/app/components/05.forms/triple-toggle-switch/TripleToggleSwitchCore.vue +42 -20
  67. package/app/components/05.forms/triple-toggle-switch/stories/TripleToggleSwitchCore.stories.ts +63 -0
  68. package/app/components/display-theme-switch/DisplayThemeSwitch.vue +13 -6
  69. package/package.json +1 -1
  70. package/.claude/skills/components/services-section-grid.md +0 -130
  71. package/.claude/skills/components/services-section.md +0 -211
  72. package/.vscode/srcdev-component-services-section.code-snippets +0 -84
  73. package/app/components/03.organisms/services/services-grids/tests/__snapshots__/ServicesSectionGrid.spec.ts.snap +0 -87
  74. package/app/components/03.organisms/services/services-section/CONSUMER-STYLING.md +0 -149
  75. package/app/components/03.organisms/services/services-section/ServicesSection.vue +0 -345
  76. package/app/components/03.organisms/services/services-section/tests/ServicesSection.spec.ts +0 -297
  77. package/app/components/03.organisms/services/services-section/tests/__snapshots__/ServicesSection.spec.ts.snap +0 -87
@@ -1,130 +0,0 @@
1
- # ServicesSectionGrid Component
2
-
3
- ## Overview
4
-
5
- `ServicesSectionGrid` renders a vertical stack of `ServicesSection` components (in summary mode) from a `Service[]` array. Each section includes a hardcoded `summary-link` (navigates to the service detail page) and a `cta` button. Column order can be alternated on every other section via `useAlternateReverse`.
6
-
7
- All routing and CTA decisions are delegated to the consumer via scoped slots — the grid forwards `serviceData` from each `ServicesSection` iteration so the consumer can build hrefs, labels, and choose components freely.
8
-
9
- ## Props
10
-
11
- | Prop | Type | Default | Required |
12
- | ----------------------- | ------------------------------ | ----------- | -------- |
13
- | `servicesData` | `Service[]` | — | **yes** |
14
- | `tag` | `"div" \| "section" \| "main"` | `"div"` | no |
15
- | `useAlternateReverse` | `boolean` | `false` | no |
16
- | `summaryAlignment` | `"start" \| "center" \| "end"` | `"center"` | no |
17
- | `styleClassPassthrough` | `string \| string[]` | `[]` | no |
18
-
19
- ### `useAlternateReverse`
20
-
21
- When `true`, odd-indexed sections flip their image/content column order (`reverse` prop on each `ServicesSection`), creating a visual zigzag layout. Commonly used on services landing pages with three or more services.
22
-
23
- ### `summaryAlignment`
24
-
25
- Passed to every `ServicesSection` as `summaryAlignment` — controls vertical alignment of the info column content within the grid cell.
26
-
27
- ## Slots
28
-
29
- | Slot | Slot props | Purpose |
30
- | -------------- | -------------------------- | --------------------------------------------------------- |
31
- | `summary-link` | `{ serviceData: Service }` | Navigation link below the `whatIsIt` text in each section |
32
-
33
- The slot is forwarded from the inner `ServicesSection` and receives the current iteration's `serviceData` as a scoped prop. It is optional — omitting it leaves that area empty.
34
-
35
- > **Note**: `ServicesSectionGrid` always renders sections in summary mode (`isSummary: true`), so only `summary-link` is available. The `cta` slot (which lives inside a full-mode `GlassPanel`) is not reachable from this component.
36
-
37
- ## Basic usage
38
-
39
- ```vue
40
- <ServicesSectionGrid
41
- :services-data="servicesData ?? []"
42
- :use-alternate-reverse="true"
43
- tag="section"
44
- >
45
- <template #summary-link="{ serviceData }">
46
- <LinkText
47
- :to="`/services/${serviceData.slug}`"
48
- :link-text="`More about ${serviceData.title}`"
49
- :style-class-passthrough="['mb-20']"
50
- />
51
- </template>
52
- </ServicesSectionGrid>
53
- ```
54
-
55
- ## Alternate reverse (zigzag) layout
56
-
57
- ```vue
58
- <ServicesSectionGrid
59
- :services-data="servicesData ?? []"
60
- :use-alternate-reverse="true"
61
- tag="section"
62
- >
63
- <!-- slots as above -->
64
- </ServicesSectionGrid>
65
- ```
66
-
67
- ## Consumer page boilerplate
68
-
69
- ```vue
70
- <template>
71
- <div>
72
- <NuxtLayout name="default">
73
- <template #layout-content>
74
- <PageRow tag="div" variant="content" :style-class-passthrough="['mbe-20']">
75
- <h1 class="page-heading-1">Our Services</h1>
76
- </PageRow>
77
-
78
- <PageRow tag="div" variant="content" :style-class-passthrough="['mbe-20']">
79
- <ServicesSectionGrid
80
- :services-data="servicesData ?? []"
81
- :use-alternate-reverse="true"
82
- tag="section"
83
- >
84
- <template #summary-link="{ serviceData }">
85
- <LinkText
86
- :to="`/services/${serviceData.slug}`"
87
- :link-text="`More about ${serviceData.title}`"
88
- :style-class-passthrough="['mb-20']"
89
- />
90
- </template>
91
- </ServicesSectionGrid>
92
- </PageRow>
93
- </template>
94
- </NuxtLayout>
95
- </div>
96
- </template>
97
-
98
- <script setup lang="ts">
99
- definePageMeta({ layout: false });
100
-
101
- useHead({
102
- title: "Services",
103
- meta: [{ name: "description", content: "Browse our services" }],
104
- bodyAttrs: { class: "page-services" },
105
- });
106
-
107
- const store = useServicesStore();
108
- const { servicesData } = storeToRefs(store);
109
-
110
- if (servicesData.value.length === 0) {
111
- await store.fetchServicesData();
112
- }
113
- </script>
114
-
115
- <style lang="css">
116
- .page-services {
117
- .services-grid {
118
- gap: 6rem;
119
- }
120
- }
121
- </style>
122
- ```
123
-
124
- ## Notes
125
-
126
- - Component is auto-imported in Nuxt — no import needed.
127
- - The `Service` type is imported from `~/types/types.services`.
128
- - Each `ServicesSection` receives its array `index` for eager/lazy image loading decisions (first two sections load eagerly).
129
- - Pass `servicesData ?? []` as a safe fallback while data loads asynchronously.
130
- - See [services-section.md](./services-section.md) for the full `ServicesSection` prop and slot API.
@@ -1,211 +0,0 @@
1
- # ServicesSection Component
2
-
3
- ## Overview
4
-
5
- `ServicesSection` renders a single service as a full two-column section: image on one side, detailed content on the other. It has two modes controlled by the `isSummary` prop:
6
-
7
- - **Summary mode** (`isSummary: true`) — compact view: eyebrow, title, price/duration, `whatIsIt` text, and a `summary-link` slot for a navigational link.
8
- - **Full mode** (`isSummary: false`, default) — complete view: all summary content plus long description, hero heading, process stepper, ideal-for stepper, aftercare, FAQs, and a `GlassPanel` with a `cta` slot for a booking/contact button.
9
-
10
- All routing and CTA decisions are delegated to the consumer via slots.
11
-
12
- ## Props
13
-
14
- | Prop | Type | Default | Required |
15
- |------|------|---------|----------|
16
- | `serviceData` | `Service` | — | **yes** |
17
- | `tag` | `"div" \| "section" \| "article" \| "main"` | `"div"` | no |
18
- | `headerTag` | `"h1" \| "h2" \| "h3"` | `"h2"` | no |
19
- | `index` | `number` | `0` | no |
20
- | `isSummary` | `boolean` | `false` | no |
21
- | `summaryAlignment` | `"start" \| "center" \| "end"` | `"center"` | no |
22
- | `reverse` | `boolean` | `false` | no |
23
- | `durationIcon` | `string` | `"mdi:clock-time-four-outline"` | no |
24
- | `priceIcon` | `string` | `"mdi:currency-gbp"` | no |
25
- | `processHeading` | `string` | `"The Process"` | no |
26
- | `idealForHeading` | `string` | `"Ideal For"` | no |
27
- | `maintenanceHeading` | `string` | `"Aftercare & Maintenance"` | no |
28
- | `faqsHeading` | `string` | `"Frequently Asked Questions"` | no |
29
- | `ctaHeading` | `string` | `"Ready to book your appointment?"` | no |
30
- | `ctaBody` | `string` | `"Get in touch to book your appointment."` | no |
31
- | `styleClassPassthrough` | `string \| string[]` | `[]` | no |
32
-
33
- ### Heading and CTA copy props
34
-
35
- All section subheadings and the default CTA panel's heading/body are props, not hardcoded
36
- text — override them with wording specific to the consuming business:
37
-
38
- ```vue
39
- <ServicesSection
40
- :service-data="service"
41
- process-heading="How It Works"
42
- cta-heading="Ready to book your colour appointment?"
43
- cta-body="Mobile service across Bath — I come to you."
44
- />
45
- ```
46
-
47
- Name the actual service in `ctaHeading` (rather than a generic phrase) if every service page
48
- shares one CTA wrapper — a single hardcoded heading applied to every service previously caused
49
- a real bug in production, where a "Colour" service page's CTA read "Ready to book your
50
- **highlights** appointment?" regardless of which service was being viewed.
51
-
52
- ### Icon customisation
53
-
54
- The two icons in the price/duration row default to `mdi:clock-time-four-outline` and `mdi:currency-gbp`. Override them for a different currency or icon style:
55
-
56
- ```vue
57
- <ServicesSection
58
- :service-data="service"
59
- duration-icon="mdi:timer-outline"
60
- price-icon="mdi:currency-eur"
61
- />
62
- ```
63
-
64
- Any [Iconify](https://icon-sets.iconify.design/) icon name is accepted. See `.claude/skills/icon-sets.md` for the icon packages available in this layer.
65
-
66
- ### `index` and image loading
67
-
68
- The `index` prop controls both eager/lazy loading and fetch priority. The first two sections (`index` 0 and 1) load eagerly; all others load lazily. Only `index 0` gets `fetchpriority="high"` (LCP candidate). Pass the loop index when rendering a list of sections.
69
-
70
- ### `reverse`
71
-
72
- Flips the image to the right column and content to the left (CSS `order: 2` on the image wrapper).
73
-
74
- ## Slots
75
-
76
- | Slot | Slot props | Rendered when | Purpose |
77
- |------|-----------|---------------|---------|
78
- | `summary-link` | `{ serviceData: Service }` | `isSummary` is `true` | Navigation link below the `whatIsIt` text in summary mode |
79
- | `cta` | `{ serviceData: Service }` | `isSummary` is `false` | CTA button/link inside the closing `GlassPanel` in full mode |
80
- | `cta-panel` | `{ serviceData: Service, ctaHeading: string, ctaBody: string }` | `isSummary` is `false` | Replaces the entire default `GlassPanel` CTA block — use when a consumer needs a different component or layout, not just different copy |
81
-
82
- All three slots receive `serviceData` as a scoped prop. `cta-panel`'s default content *is*
83
- the `GlassPanel` + `ctaHeading`/`ctaBody` + `cta` slot combination described above — providing
84
- `cta-panel` replaces that whole block, so the `ctaHeading`/`ctaBody` props and `cta` slot are
85
- only useful if the `cta-panel` slot fallback (or the consumer's own override) still renders them.
86
-
87
- ## Summary mode usage
88
-
89
- ```vue
90
- <ServicesSection :service-data="service" :is-summary="true" :index="i">
91
- <template #summary-link="{ serviceData }">
92
- <LinkText
93
- :to="`/services/${serviceData.slug}`"
94
- :link-text="`More about ${serviceData.title}`"
95
- :style-class-passthrough="['mb-20']"
96
- >
97
- <template #right>
98
- <Icon name="mdi:arrow-right" />
99
- </template>
100
- </LinkText>
101
- </template>
102
- </ServicesSection>
103
- ```
104
-
105
- ## Full mode usage
106
-
107
- ```vue
108
- <ServicesSection :service-data="service">
109
- <template #cta>
110
- <InputButtonCore
111
- variant="secondary"
112
- button-text="Get in touch"
113
- href="/contact"
114
- :style-class-passthrough="['mbs-24']"
115
- >
116
- <template #right>
117
- <Icon name="mdi:arrow-right" class="icon" />
118
- </template>
119
- </InputButtonCore>
120
- </template>
121
- </ServicesSection>
122
- ```
123
-
124
- ## Multiple CTAs in full mode
125
-
126
- ```vue
127
- <ServicesSection :service-data="service">
128
- <template #cta="{ serviceData }">
129
- <InputButtonCore variant="primary" button-text="Book now" href="/book" />
130
- <InputButtonCore variant="secondary" button-text="Get in touch" href="/contact" />
131
- </template>
132
- </ServicesSection>
133
- ```
134
-
135
- ## Replacing the whole CTA panel (slot override)
136
-
137
- Use `cta-panel` instead of `ctaHeading`/`ctaBody` when the CTA needs a different component
138
- entirely — not just different text inside `GlassPanel`:
139
-
140
- ```vue
141
- <ServicesSection :service-data="service">
142
- <template #cta-panel="{ serviceData, ctaHeading, ctaBody }">
143
- <MyPromoBanner :heading="ctaHeading" :body="ctaBody">
144
- <template #cta><InputButtonCore button-text="Enquire Now" href="/contact" /></template>
145
- </MyPromoBanner>
146
- </template>
147
- </ServicesSection>
148
- ```
149
-
150
- ## Rendering a list (summary mode)
151
-
152
- ```vue
153
- <ServicesSection
154
- v-for="(service, i) in services"
155
- :key="service.slug"
156
- :service-data="service"
157
- :index="i"
158
- :is-summary="true"
159
- :reverse="i % 2 !== 0"
160
- tag="section"
161
- >
162
- <template #summary-link="{ serviceData }">
163
- <LinkText :to="`/services/${serviceData.slug}`" :link-text="`More about ${serviceData.title}`" />
164
- </template>
165
- </ServicesSection>
166
- ```
167
-
168
- ## Local style override scaffold
169
-
170
- When consuming this component, prefer the `--services-section-*` CSS custom properties
171
- documented in `CONSUMER-STYLING.md` (in the component's own folder) over raw class overrides.
172
- For anything the tokens don't cover, scaffold a style block using `styleClassPassthrough`.
173
- Delete the block if unused.
174
-
175
- See [component-local-style-override.md](../component-local-style-override.md) for the general pattern.
176
-
177
- ```vue
178
- <ServicesSection :style-class-passthrough="['my-section']" :service-data="service">
179
- ...
180
- </ServicesSection>
181
-
182
- <style>
183
- /* ─── ServicesSection local overrides ──────────────────────────────
184
- Colours, borders, geometry only — do not override behaviour.
185
- Delete this block if no overrides are needed.
186
- ─────────────────────────────────────────────────────────────────── */
187
- .services-section {
188
- &.my-section {
189
- --services-section-image-border-radius: 1.2rem;
190
-
191
- /* Deeper overrides target the BEM element classes directly, e.g.: */
192
- /* .services-section__faq { } */
193
- }
194
- }
195
- </style>
196
- ```
197
-
198
- ## Notes
199
-
200
- - Component is auto-imported in Nuxt — no import needed.
201
- - The `Service` type is imported from `~/types/types.services`.
202
- - `summary-link` slot is guarded by `v-if="isSummary"` — it will not render in full mode even if provided.
203
- - `cta` and `cta-panel` slots live inside `v-if="!isSummary"` — neither renders in summary mode.
204
- - Internal element classes are BEM-namespaced: `services-section__image-wrapper`,
205
- `services-section__info-wrapper`, `services-section__price-duration`,
206
- `services-section__decorator`, `services-section__faq`, `services-section__faq-answer`,
207
- `services-section__glass-panel`. If a consuming app has CSS overrides referencing older
208
- unprefixed names (`.image-wrapper`, `.price-duration`, `.services-faq`, etc. — from before
209
- this component's classnames were namespaced), those selectors need updating to match.
210
- - The section gets `aria-labelledby` automatically when `tag` is `"section"`, `"article"`, or `"aside"`, pointing at the id ServicesSection binds to its own title `HeroText` internally — no consumer action needed. (`tag="main"` renders a `<main>` element but is never auto-labelled; see [component-aria-landmark.md](../component-aria-landmark.md).)
211
- - `summaryAlignment` only has effect when `isSummary` is `true` — it aligns the info-wrapper content vertically within the grid cell.
@@ -1,84 +0,0 @@
1
- {
2
- "SRCDEV ServicesSection Full Mode": {
3
- "description": "ServicesSection full mode — complete content with CTA",
4
- "scope": "vue,html",
5
- "body": [
6
- "<ServicesSection",
7
- " :service-data=\"$1service\"",
8
- " :index=\"$2index\"",
9
- ">",
10
- " <template #cta>",
11
- " <button type=\"button\">$3Book Now</button>",
12
- " </template>",
13
- "</ServicesSection>"
14
- ]
15
- },
16
- "SRCDEV ServicesSection Summary Mode": {
17
- "description": "ServicesSection summary mode — compact card with summary-link slot",
18
- "scope": "vue,html",
19
- "body": [
20
- "<ServicesSection",
21
- " :service-data=\"$1service\"",
22
- " is-summary",
23
- " summary-alignment=\"$2center\"",
24
- ">",
25
- " <template #summary-link=\"{ serviceData }\">",
26
- " <NuxtLink :to=\"`/services/${serviceData.slug}`\">$3More about {{ serviceData.title }} →</NuxtLink>",
27
- " </template>",
28
- "</ServicesSection>"
29
- ]
30
- },
31
- "SRCDEV ServicesSection Custom Headings And Cta": {
32
- "description": "ServicesSection with custom section headings and CTA copy props",
33
- "scope": "vue,html",
34
- "body": [
35
- "<ServicesSection",
36
- " :service-data=\"$1service\"",
37
- " process-heading=\"$2How It Works\"",
38
- " ideal-for-heading=\"$3Who Is This For\"",
39
- " maintenance-heading=\"$4Aftercare\"",
40
- " faqs-heading=\"$5FAQs\"",
41
- " cta-heading=\"$6Ready to book?\"",
42
- " cta-body=\"$7Get in touch to book your appointment.\"",
43
- ">",
44
- " <template #cta>",
45
- " <button type=\"button\">$8Book Now</button>",
46
- " </template>",
47
- "</ServicesSection>"
48
- ]
49
- },
50
- "SRCDEV ServicesSection Custom Cta Panel Slot": {
51
- "description": "ServicesSection with the cta-panel slot fully replacing the default GlassPanel",
52
- "scope": "vue,html",
53
- "body": [
54
- "<ServicesSection :service-data=\"$1service\">",
55
- " <template #cta-panel=\"{ serviceData, ctaHeading, ctaBody }\">",
56
- " $2",
57
- " </template>",
58
- "</ServicesSection>"
59
- ]
60
- },
61
- "SRCDEV ServicesSection CSS Override": {
62
- "description": "CSS override scaffold for ServicesSection — scope to a page or section class",
63
- "scope": "css",
64
- "body": [
65
- ".$1my-page {",
66
- " .services-section {",
67
- " --services-section-grid-gap: ;",
68
- " --services-section-grid-gap-desktop: ;",
69
- " --services-section-image-border-radius: ;",
70
- " --services-section-price-duration-gap: ;",
71
- " --services-section-decorator-size: ;",
72
- " --services-section-faq-divider-color: ;",
73
- " --services-section-faq-answer-line-height: ;",
74
- "",
75
- " .services-section__image-wrapper {}",
76
- " .services-section__info-wrapper {}",
77
- " .services-section__price-duration {}",
78
- " .services-section__faq {}",
79
- " .services-section__glass-panel {}",
80
- " }",
81
- "}"
82
- ]
83
- }
84
- }
@@ -1,87 +0,0 @@
1
- // Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
2
-
3
- exports[`ServicesSectionGrid > renders correct HTML structure 1`] = `
4
- "<div class="services-grid">
5
- <div class="services-section">
6
- <div class="services-section__grid">
7
- <div class="services-section__image-wrapper"><img data-nuxt-img="" srcset="/_ipx/_/images/locs-installation.jpg 1x, /_ipx/_/images/locs-installation.jpg 2x" alt="Locs Installation" loading="eager" fetchpriority="high" class="services-section__image" src="/_ipx/_/images/locs-installation.jpg"></div>
8
- <div class="services-section__info-wrapper services-section__info-wrapper--summary services-section__info-wrapper--align-center">
9
- <div class="eyebrow-text large">Subtitle for Locs Installation</div>
10
- <h2 id="v-0-0-0" class="hero-text mb-20 title axis-horizontal">
11
- <!--v-if--><span class="text-block-0 normal">Locs Installation</span>
12
- </h2>
13
- <div class="services-section__price-duration">
14
- <div class="flex-row"><span class="iconify i-mdi:clock-time-four-outline services-section__decorator" aria-hidden="true"></span><span>60 mins</span></div>
15
- <div class="flex-row"><span class="iconify i-mdi:currency-gbp services-section__decorator" aria-hidden="true"></span><span>£50</span></div>
16
- </div>
17
- <!--v-if-->
18
- <!--v-if-->
19
- <p class="page-body-normal">What this service is.</p>
20
- <!--v-if-->
21
- <!--v-if-->
22
- <!--v-if-->
23
- <!--v-if-->
24
- <!--v-if-->
25
- <!--v-if-->
26
- <!--v-if-->
27
- <!--v-if-->
28
- <!--v-if-->
29
- </div>
30
- </div>
31
- </div>
32
- <div class="services-section">
33
- <div class="services-section__grid">
34
- <div class="services-section__image-wrapper"><img data-nuxt-img="" srcset="/_ipx/_/images/locs-retwist.jpg 1x, /_ipx/_/images/locs-retwist.jpg 2x" alt="Locs Retwist" loading="eager" fetchpriority="auto" class="services-section__image" src="/_ipx/_/images/locs-retwist.jpg"></div>
35
- <div class="services-section__info-wrapper services-section__info-wrapper--summary services-section__info-wrapper--align-center">
36
- <div class="eyebrow-text large">Subtitle for Locs Retwist</div>
37
- <h2 id="v-0-0-1" class="hero-text mb-20 title axis-horizontal">
38
- <!--v-if--><span class="text-block-0 normal">Locs Retwist</span>
39
- </h2>
40
- <div class="services-section__price-duration">
41
- <div class="flex-row"><span class="iconify i-mdi:clock-time-four-outline services-section__decorator" aria-hidden="true"></span><span>60 mins</span></div>
42
- <div class="flex-row"><span class="iconify i-mdi:currency-gbp services-section__decorator" aria-hidden="true"></span><span>£50</span></div>
43
- </div>
44
- <!--v-if-->
45
- <!--v-if-->
46
- <p class="page-body-normal">What this service is.</p>
47
- <!--v-if-->
48
- <!--v-if-->
49
- <!--v-if-->
50
- <!--v-if-->
51
- <!--v-if-->
52
- <!--v-if-->
53
- <!--v-if-->
54
- <!--v-if-->
55
- <!--v-if-->
56
- </div>
57
- </div>
58
- </div>
59
- <div class="services-section">
60
- <div class="services-section__grid">
61
- <div class="services-section__image-wrapper"><img data-nuxt-img="" srcset="/_ipx/_/images/colour-treatment.jpg 1x, /_ipx/_/images/colour-treatment.jpg 2x" alt="Colour Treatment" loading="lazy" fetchpriority="auto" class="services-section__image" src="/_ipx/_/images/colour-treatment.jpg"></div>
62
- <div class="services-section__info-wrapper services-section__info-wrapper--summary services-section__info-wrapper--align-center">
63
- <div class="eyebrow-text large">Subtitle for Colour Treatment</div>
64
- <h2 id="v-0-0-2" class="hero-text mb-20 title axis-horizontal">
65
- <!--v-if--><span class="text-block-0 normal">Colour Treatment</span>
66
- </h2>
67
- <div class="services-section__price-duration">
68
- <div class="flex-row"><span class="iconify i-mdi:clock-time-four-outline services-section__decorator" aria-hidden="true"></span><span>60 mins</span></div>
69
- <div class="flex-row"><span class="iconify i-mdi:currency-gbp services-section__decorator" aria-hidden="true"></span><span>£50</span></div>
70
- </div>
71
- <!--v-if-->
72
- <!--v-if-->
73
- <p class="page-body-normal">What this service is.</p>
74
- <!--v-if-->
75
- <!--v-if-->
76
- <!--v-if-->
77
- <!--v-if-->
78
- <!--v-if-->
79
- <!--v-if-->
80
- <!--v-if-->
81
- <!--v-if-->
82
- <!--v-if-->
83
- </div>
84
- </div>
85
- </div>
86
- </div>"
87
- `;
@@ -1,149 +0,0 @@
1
- # ServicesSection — Consumer Styling Guide
2
-
3
- ## Public token API
4
-
5
- All `--services-section-*` tokens are the stable override surface. Set them globally in a
6
- theme file, scoped to a page wrapper, or per-instance via `styleClassPassthrough`.
7
-
8
- ### Layout & sizing
9
-
10
- | Token | Default | Controls |
11
- |---|---|---|
12
- | `--services-section-grid-gap` | `2rem` | Gap between image and content columns below the 768px breakpoint |
13
- | `--services-section-grid-gap-desktop` | `3rem` | Gap between columns at 768px and above |
14
- | `--services-section-image-border-radius` | `0.8rem` | Corner rounding on the service image |
15
- | `--services-section-price-duration-gap` | `3rem` | Gap between the duration and price rows |
16
- | `--services-section-price-duration-item-gap` | `0.8rem` | Gap between icon and text within a single row |
17
- | `--services-section-price-duration-margin-block-end` | `2rem` | Space below the price/duration row |
18
- | `--services-section-decorator-size` | `2rem` | Width/height of the duration and price icons |
19
- | `--services-section-faq-margin-block-end` | `2.4rem` | Space below the FAQ accordion |
20
- | `--services-section-faq-answer-line-height` | `1.6` | Line height of FAQ answer text |
21
-
22
- ### Colours
23
-
24
- | Token | Default | Controls |
25
- |---|---|---|
26
- | `--services-section-faq-divider-color` | `currentColor` | Border colour between FAQ accordion items |
27
- | `--services-section-faq-divider-opacity` | `0.7` | Opacity of each FAQ accordion item's border |
28
-
29
- ---
30
-
31
- ## Text content — props, not CSS
32
-
33
- Section headings and the default CTA panel copy are **props**, not hardcoded strings, so
34
- there is nothing to override in CSS for these:
35
-
36
- | Prop | Default |
37
- |---|---|
38
- | `processHeading` | `"The Process"` |
39
- | `idealForHeading` | `"Ideal For"` |
40
- | `maintenanceHeading` | `"Aftercare & Maintenance"` |
41
- | `faqsHeading` | `"Frequently Asked Questions"` |
42
- | `ctaHeading` | `"Ready to book your appointment?"` |
43
- | `ctaBody` | `"Get in touch to book your appointment."` |
44
-
45
- ```vue
46
- <ServicesSection
47
- :service-data="service"
48
- cta-heading="Ready to book your locs consultation?"
49
- cta-body="Mobile service across Bath — I come to you."
50
- />
51
- ```
52
-
53
- If the CTA needs a different component entirely (not just different text), replace it with
54
- the `cta-panel` scoped slot instead of styling around the default `GlassPanel`:
55
-
56
- ```vue
57
- <ServicesSection :service-data="service">
58
- <template #cta-panel="{ serviceData, ctaHeading, ctaBody }">
59
- <MyCustomPanel :heading="ctaHeading" :body="ctaBody">
60
- <template #cta><button>Enquire Now</button></template>
61
- </MyCustomPanel>
62
- </template>
63
- </ServicesSection>
64
- ```
65
-
66
- ---
67
-
68
- ## Global theming — recommended approach
69
-
70
- Create `assets/styles/setup/07.components/services-section.css` in the consuming app and
71
- set tokens on `:root`. This applies to every `ServicesSection` across the site.
72
-
73
- ```css
74
- /* assets/styles/setup/07.components/services-section.css */
75
- :root {
76
- --services-section-grid-gap: 2.4rem;
77
- --services-section-grid-gap-desktop: 4rem;
78
- --services-section-image-border-radius: 1.2rem;
79
- --services-section-faq-divider-color: var(--brand-border);
80
- }
81
- ```
82
-
83
- ---
84
-
85
- ## Page-scoped overrides
86
-
87
- Override tokens for a specific section by scoping them under the page or layout wrapper.
88
- No `:deep()` is required (component styles are unscoped).
89
-
90
- ```css
91
- /* In the consuming page's unscoped <style> block */
92
- .our-services-page {
93
- .services-section {
94
- --services-section-decorator-size: 2.4rem;
95
- --services-section-faq-answer-line-height: 1.8;
96
- }
97
- }
98
- ```
99
-
100
- ---
101
-
102
- ## Per-instance overrides via styleClassPassthrough
103
-
104
- Use sparingly — prefer global or page-scoped CSS. When a single instance needs a distinct
105
- visual style, pass a modifier class:
106
-
107
- ```vue
108
- <ServicesSection :style-class-passthrough="['highlight-service']" :service-data="service" />
109
- ```
110
-
111
- ```css
112
- .services-section.highlight-service {
113
- --services-section-image-border-radius: 2rem;
114
- --services-section-faq-divider-color: var(--color-accent);
115
- }
116
- ```
117
-
118
- ---
119
-
120
- ## FAQ accordion styling
121
-
122
- The `AccordianCore` inside this component receives `services-section__faq` via
123
- `styleClassPassthrough`. To target the accordion's own internals (summary, icon rotation,
124
- content padding), style through that class rather than reaching into `AccordianCore`
125
- directly — see `.services-section__faq` in the component's own `<style>` block for the
126
- selectors already scoped this way (`.accordian-item.expanding-panel`,
127
- `.expanding-panel-summary`, `.services-section__faq-answer`).
128
-
129
- ```css
130
- .services-section {
131
- .services-section__faq {
132
- .expanding-panel-details .expanding-panel-summary {
133
- padding-block: 1.6rem;
134
- }
135
- }
136
- }
137
- ```
138
-
139
- ---
140
-
141
- ## Notes
142
-
143
- - `--services-section-grid-gap-desktop` only applies at `768px` and above — the breakpoint
144
- itself is not a token, since changing it would affect the grid's `minmax()` column sizing
145
- too, not just spacing.
146
- - The CTA panel is rendered inside a default `<slot name="cta-panel">` — passing that slot
147
- bypasses `GlassPanel` and all of its tokens (`--glass-panel-*`) entirely, along with the
148
- `ctaHeading`/`ctaBody` props, which are handed to the slot for reuse rather than applied
149
- automatically.