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.
- package/.claude/skills/component-aria-landmark.md +3 -3
- package/.claude/skills/components/auto-grid.md +1 -1
- package/.claude/skills/components/page-hero-highlights.md +1 -1
- package/.claude/skills/components/service-detail.md +13 -14
- package/.claude/skills/components/service-summary-grid.md +141 -0
- package/.claude/skills/components/service-summary.md +165 -0
- package/.claude/skills/icon-sets.md +1 -1
- package/.claude/skills/index.md +3 -2
- package/.claude/skills/theming-component-token-pattern.md +126 -0
- package/.vscode/srcdev-component-service-summary.code-snippets +67 -0
- package/app/assets/styles/setup/04.elements/forms/06.button-geometry.css +10 -1
- package/app/components/02.molecules/input-copy/CONSUMER-STYLING.md +3 -3
- package/app/components/02.molecules/input-copy/InputCopy.vue +9 -3
- package/app/components/02.molecules/pricing-card/CONSUMER-STYLING.md +3 -3
- package/app/components/02.molecules/pricing-card/PricingCard.vue +6 -3
- package/app/components/03.organisms/services/service-detail/CONSUMER-STYLING.md +7 -8
- package/app/components/03.organisms/services/service-detail/ServiceDetail.vue +0 -1
- package/app/components/03.organisms/services/service-detail/stories/ServiceDetail.stories.ts +2 -2
- package/app/components/03.organisms/services/service-summary/CONSUMER-STYLING.md +116 -0
- package/app/components/03.organisms/services/service-summary/ServiceSummary.vue +183 -0
- package/app/components/03.organisms/services/{services-section/stories/ServicesSection.stories.ts → service-summary/stories/ServiceSummary.stories.ts} +78 -148
- package/app/components/03.organisms/services/service-summary/tests/ServiceSummary.spec.ts +210 -0
- package/app/components/03.organisms/services/service-summary/tests/__snapshots__/ServiceSummary.spec.ts.snap +17 -0
- package/app/components/03.organisms/services/services-grids/{ServicesSectionGrid.vue → ServiceSummaryGrid.vue} +8 -10
- package/app/components/03.organisms/services/services-grids/stories/{ServicesSectionGrid.stories.ts → ServiceSummaryGrid.stories.ts} +21 -21
- package/app/components/03.organisms/services/services-grids/tests/{ServicesSectionGrid.spec.ts → ServiceSummaryGrid.spec.ts} +28 -28
- package/app/components/03.organisms/services/services-grids/tests/__snapshots__/ServiceSummaryGrid.spec.ts.snap +45 -0
- package/app/components/05.forms/input-button/CONSUMER-STYLING.md +117 -0
- package/app/components/05.forms/input-button/InputButtonCore.vue +83 -37
- package/app/components/05.forms/input-button/playwright/input-button-core.playwright.ts-snapshots/combo-icons-secondary-chromium-darwin.png +0 -0
- package/app/components/05.forms/input-button/playwright/input-button-core.playwright.ts-snapshots/combo-icons-tertiary-chromium-darwin.png +0 -0
- package/app/components/05.forms/input-button/playwright/input-button-core.playwright.ts-snapshots/combo-primary-pending-chromium-darwin.png +0 -0
- package/app/components/05.forms/input-button/playwright/input-button-core.playwright.ts-snapshots/combo-secondary-pill-chromium-darwin.png +0 -0
- package/app/components/05.forms/input-button/playwright/input-button-core.playwright.ts-snapshots/combo-tertiary-readonly-chromium-darwin.png +0 -0
- package/app/components/05.forms/input-button/playwright/input-button-core.playwright.ts-snapshots/default-chromium-darwin.png +0 -0
- package/app/components/05.forms/input-button/playwright/input-button-core.playwright.ts-snapshots/default-theme-default-chromium-darwin.png +0 -0
- package/app/components/05.forms/input-button/playwright/input-button-core.playwright.ts-snapshots/default-theme-error-chromium-darwin.png +0 -0
- package/app/components/05.forms/input-button/playwright/input-button-core.playwright.ts-snapshots/default-theme-success-chromium-darwin.png +0 -0
- package/app/components/05.forms/input-button/playwright/input-button-core.playwright.ts-snapshots/default-theme-warning-chromium-darwin.png +0 -0
- package/app/components/05.forms/input-button/playwright/input-button-core.playwright.ts-snapshots/default-variant-primary-chromium-darwin.png +0 -0
- package/app/components/05.forms/input-button/playwright/input-button-core.playwright.ts-snapshots/default-variant-secondary-chromium-darwin.png +0 -0
- package/app/components/05.forms/input-button/playwright/input-button-core.playwright.ts-snapshots/default-variant-tertiary-chromium-darwin.png +0 -0
- package/app/components/05.forms/input-button/playwright/input-button-core.playwright.ts-snapshots/nuxt-icon-only-component-chromium-darwin.png +0 -0
- package/app/components/05.forms/input-button/playwright/input-button-core.playwright.ts-snapshots/state-pending-chromium-darwin.png +0 -0
- package/app/components/05.forms/input-button/playwright/input-button-core.playwright.ts-snapshots/state-pill-chromium-darwin.png +0 -0
- package/app/components/05.forms/input-button/playwright/input-button-core.playwright.ts-snapshots/state-readonly-chromium-darwin.png +0 -0
- package/app/components/05.forms/input-button/playwright/input-button-core.playwright.ts-snapshots/with-both-nuxt-icon-components-chromium-darwin.png +0 -0
- package/app/components/05.forms/input-button/stories/InputButtonCore.stories.ts +35 -0
- package/app/components/05.forms/input-checkbox-radio/CONSUMER-STYLING.md +68 -0
- package/app/components/05.forms/input-checkbox-radio/InputCheckboxRadioButton.vue +19 -9
- package/app/components/05.forms/input-checkbox-radio/InputCheckboxRadioCore.vue +8 -5
- package/app/components/05.forms/input-checkbox-radio/stories/InputCheckboxRadioButton.stories.ts +93 -0
- package/app/components/05.forms/input-checkbox-radio/stories/InputCheckboxRadioCore.stories.ts +75 -0
- package/app/components/05.forms/input-number/CONSUMER-STYLING.md +41 -0
- package/app/components/05.forms/input-number/InputNumberCore.vue +12 -6
- package/app/components/05.forms/input-select/CONSUMER-STYLING.md +35 -29
- package/app/components/05.forms/input-select/InputSelectCore.vue +32 -20
- package/app/components/05.forms/input-select/stories/InputSelectCore.stories.ts +69 -0
- package/app/components/05.forms/input-text/CONSUMER-STYLING.md +38 -48
- package/app/components/05.forms/input-text/InputTextCore.vue +35 -18
- package/app/components/05.forms/patterns/stories/ContactForm.stories.ts +184 -0
- package/app/components/05.forms/patterns/stories/LoginForm.stories.ts +125 -0
- package/app/components/05.forms/toggle-switch/CONSUMER-STYLING.md +49 -0
- package/app/components/05.forms/toggle-switch/ToggleSwitchCore.vue +23 -16
- package/app/components/05.forms/triple-toggle-switch/CONSUMER-STYLING.md +72 -0
- package/app/components/05.forms/triple-toggle-switch/TripleToggleSwitchCore.vue +42 -20
- package/app/components/05.forms/triple-toggle-switch/stories/TripleToggleSwitchCore.stories.ts +63 -0
- package/app/components/display-theme-switch/DisplayThemeSwitch.vue +13 -6
- package/package.json +1 -1
- package/.claude/skills/components/services-section-grid.md +0 -130
- package/.claude/skills/components/services-section.md +0 -211
- package/.vscode/srcdev-component-services-section.code-snippets +0 -84
- package/app/components/03.organisms/services/services-grids/tests/__snapshots__/ServicesSectionGrid.spec.ts.snap +0 -87
- package/app/components/03.organisms/services/services-section/CONSUMER-STYLING.md +0 -149
- package/app/components/03.organisms/services/services-section/ServicesSection.vue +0 -345
- package/app/components/03.organisms/services/services-section/tests/ServicesSection.spec.ts +0 -297
- 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.
|