srcdev-nuxt-components 9.5.6 → 9.6.0
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-compliance-checklist.md +6 -2
- package/.claude/skills/components/breadcrumb.md +5 -0
- package/.claude/skills/components/display-pill.md +2 -0
- package/.claude/skills/components/input-button.md +3 -0
- package/.claude/skills/components/navigation-items.md +1 -0
- package/.claude/skills/components/opening-hours.md +12 -0
- package/.claude/skills/components/price-list.md +10 -1
- package/.claude/skills/components/responsive-header.md +74 -0
- package/.claude/skills/components/service-detail.md +32 -0
- package/.claude/skills/components/service-summary.md +8 -0
- package/.claude/skills/components/services-card-grid.md +5 -3
- package/.claude/skills/components/services-card.md +22 -12
- package/.claude/skills/components/site-header.md +2 -0
- package/.claude/skills/components/slider-gallery.md +10 -4
- package/.claude/skills/components/tab-navigation.md +25 -9
- package/.claude/skills/index.md +1 -2
- package/.claude/skills/storybook-add-story.md +14 -0
- package/.vscode/srcdev-component-responsive-header.code-snippets +13 -1
- package/.vscode/srcdev-component-service-detail.code-snippets +1 -0
- package/.vscode/srcdev-component-service-summary.code-snippets +3 -1
- package/.vscode/srcdev-component-services-card.code-snippets +38 -36
- package/.vscode/srcdev-component-services-grids.code-snippets +2 -1
- package/.vscode/srcdev-component-site-header.code-snippets +3 -1
- package/.vscode/srcdev-component-tab-navigation.code-snippets +1 -1
- package/app/components/01.atoms/display-pill/CONSUMER-STYLING.md +3 -0
- package/app/components/01.atoms/display-pill/DisplayPill.vue +5 -2
- package/app/components/01.atoms/navigation/breadcrumb/Breadcrumb.vue +16 -4
- package/app/components/01.atoms/navigation/breadcrumb/CONSUMER-STYLING.md +28 -5
- package/app/components/01.atoms/navigation/breadcrumb/stories/Breadcrumb.stories.ts +94 -0
- package/app/components/01.atoms/navigation/breadcrumb/tests/Breadcrumb.spec.ts +27 -0
- package/app/components/02.molecules/navigation/tab-navigation/CONSUMER-STYLING.md +52 -11
- package/app/components/02.molecules/navigation/tab-navigation/TabNavigation.vue +55 -23
- package/app/components/02.molecules/navigation/tab-navigation/stories/TabNavigation.stories.ts +176 -257
- package/app/components/02.molecules/navigation/tab-navigation/tests/TabNavigation.spec.ts +42 -2
- package/app/components/02.molecules/opening-hours/CONSUMER-STYLING.md +11 -1
- package/app/components/02.molecules/opening-hours/OpeningHours.vue +71 -43
- package/app/components/02.molecules/opening-hours/stories/OpeningHours.stories.ts +138 -6
- package/app/components/02.molecules/opening-hours/tests/OpeningHours.spec.ts +53 -0
- package/app/components/02.molecules/opening-hours/tests/__snapshots__/OpeningHours.spec.ts.snap +1 -1
- package/app/components/02.molecules/opening-hours/tests/buildOpeningHoursSpecification.spec.ts +37 -1
- package/app/components/02.molecules/price-list/CONSUMER-STYLING.md +17 -5
- package/app/components/02.molecules/price-list/PriceList.vue +32 -9
- package/app/components/02.molecules/price-list/stories/PriceList.stories.ts +125 -29
- package/app/components/02.molecules/price-list/tests/PriceList.spec.ts +20 -0
- package/app/components/03.organisms/google-reviews/stories/GoogleReviews.stories.ts +9 -4
- package/app/components/03.organisms/image-galleries/slider-gallery/CONSUMER-STYLING.md +19 -1
- package/app/components/03.organisms/image-galleries/slider-gallery/SliderGallery.vue +65 -7
- package/app/components/03.organisms/image-galleries/slider-gallery/stories/SliderGallery.stories.ts +201 -63
- package/app/components/03.organisms/image-galleries/slider-gallery/tests/SliderGallery.spec.ts +41 -2
- package/app/components/03.organisms/image-galleries/slider-gallery/tests/__snapshots__/SliderGallery.spec.ts.snap +6 -6
- package/app/components/03.organisms/responsive-header/CONSUMER-STYLING.md +18 -0
- package/app/components/03.organisms/responsive-header/NavigationItems.vue +56 -18
- package/app/components/03.organisms/responsive-header/ResponsiveHeader.vue +106 -31
- package/app/components/03.organisms/responsive-header/stories/ResponsiveHeader.stories.ts +201 -18
- package/app/components/03.organisms/responsive-header/tests/.nuxt/app.config.mjs +21 -0
- package/app/components/03.organisms/responsive-header/tests/.nuxt/components.d.ts +66 -0
- package/app/components/03.organisms/responsive-header/tests/.nuxt/imports.d.ts +30 -0
- package/app/components/03.organisms/responsive-header/tests/.nuxt/manifest/meta/test.json +1 -0
- package/app/components/03.organisms/responsive-header/tests/.nuxt/types/app.config.d.ts +35 -0
- package/app/components/03.organisms/responsive-header/tests/.nuxt/types/build.d.ts +23 -0
- package/app/components/03.organisms/responsive-header/tests/.nuxt/types/builder-env.d.ts +1 -0
- package/app/components/03.organisms/responsive-header/tests/.nuxt/types/components.d.ts +71 -0
- package/app/components/03.organisms/responsive-header/tests/.nuxt/types/imports.d.ts +386 -0
- package/app/components/03.organisms/responsive-header/tests/.nuxt/types/middleware.d.ts +11 -0
- package/app/components/03.organisms/responsive-header/tests/.nuxt/types/modules.d.ts +79 -0
- package/app/components/03.organisms/responsive-header/tests/.nuxt/types/nitro-layouts.d.ts +17 -0
- package/app/components/03.organisms/responsive-header/tests/.nuxt/types/nitro-nuxt.d.ts +64 -0
- package/app/components/03.organisms/responsive-header/tests/.nuxt/types/plugins.d.ts +29 -0
- package/app/components/03.organisms/responsive-header/tests/.nuxt/types/runtime-config.d.ts +32 -0
- package/app/components/03.organisms/responsive-header/tests/.nuxt/types/shared-imports.d.ts +10 -0
- package/app/components/03.organisms/responsive-header/tests/.nuxt/types/vue-shim.d.ts +0 -0
- package/app/components/03.organisms/responsive-header/tests/NavigationItems.spec.ts +36 -0
- package/app/components/03.organisms/responsive-header/tests/ResponsiveHeader.spec.ts +131 -6
- package/app/components/03.organisms/responsive-header/tests/__snapshots__/ResponsiveHeader.spec.ts.snap +9 -9
- package/app/components/03.organisms/responsive-header/tests/node_modules/.cache/nuxt/chrome-workspace.json +1 -0
- package/app/components/03.organisms/services/service-detail/CONSUMER-STYLING.md +92 -41
- package/app/components/03.organisms/services/service-detail/ServiceDetail.vue +198 -73
- package/app/components/03.organisms/services/service-detail/stories/ServiceDetail.stories.ts +157 -9
- package/app/components/03.organisms/services/service-detail/tests/ServiceDetail.spec.ts +58 -0
- package/app/components/03.organisms/services/service-detail/tests/__snapshots__/ServiceDetail.spec.ts.snap +4 -3
- package/app/components/03.organisms/services/service-summary/CONSUMER-STYLING.md +30 -0
- package/app/components/03.organisms/services/service-summary/ServiceSummary.vue +32 -8
- package/app/components/03.organisms/services/service-summary/stories/ServiceSummary.stories.ts +147 -0
- package/app/components/03.organisms/services/service-summary/tests/ServiceSummary.spec.ts +26 -0
- package/app/components/03.organisms/services/service-summary/tests/__snapshots__/ServiceSummary.spec.ts.snap +1 -1
- package/app/components/03.organisms/services/services-card/CONSUMER-STYLING.md +104 -63
- package/app/components/03.organisms/services/services-card/ServicesCard.vue +92 -66
- package/app/components/03.organisms/services/services-card/stories/ServicesCard.stories.ts +194 -27
- package/app/components/03.organisms/services/services-card/tests/ServicesCard.spec.ts +34 -8
- package/app/components/03.organisms/services/services-card/tests/__snapshots__/ServicesCard.spec.ts.snap +8 -8
- package/app/components/03.organisms/services/services-grids/CONSUMER-STYLING.md +9 -0
- package/app/components/03.organisms/services/services-grids/ServicesCardGrid.vue +2 -1
- package/app/components/03.organisms/services/services-grids/stories/ServiceSummaryGrid.stories.ts +46 -6
- package/app/components/03.organisms/services/services-grids/stories/ServicesCardGrid.stories.ts +89 -11
- package/app/components/03.organisms/services/services-grids/tests/ServicesCardGrid.spec.ts +8 -0
- package/app/components/03.organisms/services/services-grids/tests/__snapshots__/ServiceSummaryGrid.spec.ts.snap +3 -3
- package/app/components/03.organisms/services/services-grids/tests/__snapshots__/ServicesCardGrid.spec.ts.snap +24 -24
- package/app/components/03.organisms/site-header/SiteHeader.vue +8 -0
- package/app/components/03.organisms/site-header/tests/__snapshots__/SiteHeader.spec.ts.snap +1 -1
- package/app/components/05.forms/input-button/CONSUMER-STYLING.md +8 -0
- package/app/components/05.forms/input-button/InputButton.vue +3 -1
- package/app/composables/tests/useAnchorScroll.spec.ts +8 -0
- package/app/composables/useAnchorScroll.ts +1 -1
- package/app/utils/opening-hours.ts +23 -2
- package/nuxt.config.ts +1 -1
- package/package.json +1 -1
- package/.claude/skills/components/site-navigation.md +0 -145
- package/.vscode/srcdev-component-site-navigation.code-snippets +0 -38
- package/app/components/02.molecules/navigation/site-navigation/CONSUMER-STYLING.md +0 -109
- package/app/components/02.molecules/navigation/site-navigation/SiteNavigation.vue +0 -751
- package/app/components/02.molecules/navigation/site-navigation/stories/SiteNavigation.stories.ts +0 -356
- package/app/components/02.molecules/navigation/site-navigation/tests/SiteNavigation.spec.ts +0 -367
- package/app/components/02.molecules/navigation/site-navigation/tests/__snapshots__/SiteNavigation.spec.ts.snap +0 -30
|
@@ -6,6 +6,8 @@
|
|
|
6
6
|
<NuxtImg
|
|
7
7
|
:src="serviceData.image"
|
|
8
8
|
:alt="serviceData.title"
|
|
9
|
+
width="1920"
|
|
10
|
+
height="1080"
|
|
9
11
|
:loading="imageLoading"
|
|
10
12
|
:fetchpriority="imageFetchPriority"
|
|
11
13
|
class="service-detail__hero-image"
|
|
@@ -15,18 +17,23 @@
|
|
|
15
17
|
<template #content>
|
|
16
18
|
<PageRow tag="div" :variant="heroContentVariant" class="service-detail__hero-overlay">
|
|
17
19
|
<div class="service-detail__hero-content">
|
|
18
|
-
<Breadcrumb
|
|
19
|
-
|
|
20
|
+
<Breadcrumb
|
|
21
|
+
v-if="resolvedBreadcrumbItems.length"
|
|
22
|
+
:items="resolvedBreadcrumbItems"
|
|
23
|
+
:aria-label="breadcrumbAriaLabel"
|
|
24
|
+
class="service-detail__breadcrumb"
|
|
25
|
+
/>
|
|
26
|
+
<EyebrowText v-if="serviceData.subtitle" font-size="large" :text-content="serviceData.subtitle" />
|
|
20
27
|
<HeroText
|
|
28
|
+
v-if="serviceData.title"
|
|
21
29
|
:id="headingId"
|
|
22
30
|
:tag="headerTag"
|
|
23
31
|
font-size="display"
|
|
24
32
|
:text-content="[{ text: serviceData.title, styleClass: 'normal' }]"
|
|
25
|
-
:style-class-passthrough="['mb-20']"
|
|
26
33
|
/>
|
|
27
|
-
<div class="service-detail__hero-pills">
|
|
28
|
-
<DisplayPill :label="serviceData.duration" size="md" variant="neutral" />
|
|
29
|
-
<DisplayPill
|
|
34
|
+
<div v-if="serviceData.duration || serviceData.price" class="service-detail__hero-pills">
|
|
35
|
+
<DisplayPill v-if="serviceData.duration" :label="serviceData.duration" size="md" variant="neutral" />
|
|
36
|
+
<DisplayPill v-if="serviceData.price" :label="heroPriceLabel" size="md" variant="neutral" />
|
|
30
37
|
</div>
|
|
31
38
|
</div>
|
|
32
39
|
</PageRow>
|
|
@@ -36,84 +43,99 @@
|
|
|
36
43
|
<PageRow tag="div" :variant="bodyVariant" class="service-detail__body-row">
|
|
37
44
|
<div class="service-detail__body">
|
|
38
45
|
<div class="service-detail__main">
|
|
39
|
-
<p class="page-body-normal">{{ serviceData.longDescription }}</p>
|
|
46
|
+
<p v-if="serviceData.longDescription" class="page-body-normal">{{ serviceData.longDescription }}</p>
|
|
40
47
|
|
|
41
48
|
<HeroText
|
|
49
|
+
v-if="hasHeroHeading"
|
|
42
50
|
:tag="subheadingTag"
|
|
43
51
|
axis="horizontal"
|
|
44
52
|
font-size="subheading"
|
|
45
53
|
:text-content="serviceData.heroHeading"
|
|
46
54
|
:style-class-passthrough="['mb-20']"
|
|
47
55
|
/>
|
|
48
|
-
<p class="page-body-normal">{{ serviceData.whatIsIt }}</p>
|
|
56
|
+
<p v-if="serviceData.whatIsIt" class="page-body-normal">{{ serviceData.whatIsIt }}</p>
|
|
49
57
|
|
|
50
|
-
<
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
58
|
+
<template v-if="serviceData.process?.length">
|
|
59
|
+
<HeroText
|
|
60
|
+
v-if="processHeading"
|
|
61
|
+
:tag="subheadingTag"
|
|
62
|
+
axis="horizontal"
|
|
63
|
+
font-size="subheading"
|
|
64
|
+
:text-content="[{ text: processHeading, styleClass: 'normal' }]"
|
|
65
|
+
:style-class-passthrough="['mb-20']"
|
|
66
|
+
/>
|
|
67
|
+
<StepperList
|
|
68
|
+
tag="ol"
|
|
59
69
|
indicator-alignment="top"
|
|
60
70
|
indicator-variant="circle"
|
|
61
71
|
:connected="true"
|
|
62
72
|
:item-count="serviceData.process.length"
|
|
63
73
|
class="service-detail__process"
|
|
64
74
|
>
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
75
|
+
<template v-for="(step, i) in serviceData.process" :key="i" #[`item-${i}`]>
|
|
76
|
+
<p class="page-body-normal">{{ step }}</p>
|
|
77
|
+
</template>
|
|
78
|
+
</StepperList>
|
|
79
|
+
</template>
|
|
69
80
|
|
|
70
|
-
<
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
<
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
81
|
+
<template v-if="serviceData.idealFor?.length">
|
|
82
|
+
<HeroText
|
|
83
|
+
v-if="idealForHeading"
|
|
84
|
+
:tag="subheadingTag"
|
|
85
|
+
axis="horizontal"
|
|
86
|
+
font-size="subheading"
|
|
87
|
+
:text-content="[{ text: idealForHeading, styleClass: 'normal' }]"
|
|
88
|
+
:style-class-passthrough="['mb-20']"
|
|
89
|
+
/>
|
|
90
|
+
<ul class="service-detail__ideal-for">
|
|
91
|
+
<li v-for="(item, i) in serviceData.idealFor" :key="i" class="service-detail__ideal-for-item">
|
|
92
|
+
<Icon :name="idealForIcon" class="service-detail__ideal-for-icon" aria-hidden="true" />
|
|
93
|
+
<p class="page-body-normal">{{ item }}</p>
|
|
94
|
+
</li>
|
|
95
|
+
</ul>
|
|
96
|
+
</template>
|
|
97
|
+
|
|
98
|
+
<template v-if="serviceData.maintenance">
|
|
99
|
+
<HeroText
|
|
100
|
+
v-if="maintenanceHeading"
|
|
101
|
+
:tag="subheadingTag"
|
|
102
|
+
axis="horizontal"
|
|
103
|
+
font-size="subheading"
|
|
104
|
+
:text-content="[{ text: maintenanceHeading, styleClass: 'normal' }]"
|
|
105
|
+
:style-class-passthrough="['mb-20']"
|
|
106
|
+
/>
|
|
107
|
+
<p class="page-body-normal">{{ serviceData.maintenance }}</p>
|
|
108
|
+
</template>
|
|
92
109
|
|
|
93
|
-
<
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
<
|
|
110
|
+
<template v-if="serviceData.faqs?.length">
|
|
111
|
+
<HeroText
|
|
112
|
+
v-if="faqsHeading"
|
|
113
|
+
:tag="subheadingTag"
|
|
114
|
+
axis="horizontal"
|
|
115
|
+
font-size="subheading"
|
|
116
|
+
:text-content="[{ text: faqsHeading, styleClass: 'normal' }]"
|
|
117
|
+
:style-class-passthrough="['mb-20']"
|
|
118
|
+
/>
|
|
119
|
+
<div class="service-detail__faqs">
|
|
120
|
+
<div v-for="(faq, i) in serviceData.faqs" :key="i" class="service-detail__faq">
|
|
121
|
+
<component :is="faqQuestionTag" v-if="faq.question" class="service-detail__faq-question">
|
|
122
|
+
{{ faq.question }}
|
|
123
|
+
</component>
|
|
124
|
+
<p v-if="faq.answer" class="page-body-normal">{{ faq.answer }}</p>
|
|
125
|
+
</div>
|
|
104
126
|
</div>
|
|
105
|
-
</
|
|
127
|
+
</template>
|
|
106
128
|
</div>
|
|
107
129
|
|
|
108
130
|
<aside class="service-detail__sidebar">
|
|
109
131
|
<GlassPanel :style-class-passthrough="['service-detail__booking-card']">
|
|
110
|
-
<p class="service-detail__sidebar-label">{{ bookingHeading }}</p>
|
|
132
|
+
<p v-if="bookingHeading" class="service-detail__sidebar-label">{{ bookingHeading }}</p>
|
|
111
133
|
|
|
112
|
-
<div class="service-detail__sidebar-row">
|
|
134
|
+
<div v-if="serviceData.price" class="service-detail__sidebar-row">
|
|
113
135
|
<span>{{ priceLabel }}</span>
|
|
114
136
|
<span>{{ serviceData.price }}</span>
|
|
115
137
|
</div>
|
|
116
|
-
<div class="service-detail__sidebar-row">
|
|
138
|
+
<div v-if="serviceData.duration" class="service-detail__sidebar-row">
|
|
117
139
|
<span>{{ durationLabel }}</span>
|
|
118
140
|
<span>{{ serviceData.duration }}</span>
|
|
119
141
|
</div>
|
|
@@ -132,12 +154,12 @@
|
|
|
132
154
|
</GlassPanel>
|
|
133
155
|
|
|
134
156
|
<div v-if="relatedServices.length" class="service-detail__related">
|
|
135
|
-
<p class="service-detail__sidebar-label">{{ relatedServicesHeading }}</p>
|
|
157
|
+
<p v-if="relatedServicesHeading" class="service-detail__sidebar-label">{{ relatedServicesHeading }}</p>
|
|
136
158
|
|
|
137
159
|
<div class="service-detail__related-items">
|
|
138
160
|
<div
|
|
139
161
|
v-for="(related, i) in relatedServices"
|
|
140
|
-
:key="related.slug"
|
|
162
|
+
:key="`${i}-${related.slug}`"
|
|
141
163
|
class="service-detail__related-item-slot"
|
|
142
164
|
>
|
|
143
165
|
<slot name="related-service" :service="related" :index="i">
|
|
@@ -145,6 +167,8 @@
|
|
|
145
167
|
<NuxtImg
|
|
146
168
|
:src="related.image"
|
|
147
169
|
:alt="related.title"
|
|
170
|
+
width="640"
|
|
171
|
+
height="640"
|
|
148
172
|
loading="lazy"
|
|
149
173
|
class="service-detail__related-image"
|
|
150
174
|
/>
|
|
@@ -165,15 +189,17 @@
|
|
|
165
189
|
<div class="service-detail__final-cta">
|
|
166
190
|
<div class="service-detail__final-cta-copy">
|
|
167
191
|
<HeroText
|
|
192
|
+
v-if="finalCtaHeading"
|
|
168
193
|
:tag="subheadingTag"
|
|
169
194
|
axis="horizontal"
|
|
170
195
|
font-size="subheading"
|
|
171
196
|
:text-content="[{ text: finalCtaHeading, styleClass: 'normal' }]"
|
|
172
|
-
:style-class-passthrough="['mbs-0', 'mbe-8']"
|
|
173
197
|
/>
|
|
174
|
-
<p class="page-body-normal">{{ finalCtaBody }}</p>
|
|
198
|
+
<p v-if="finalCtaBody" class="page-body-normal">{{ finalCtaBody }}</p>
|
|
199
|
+
</div>
|
|
200
|
+
<div v-if="$slots['final-cta']" class="service-detail__final-cta-action">
|
|
201
|
+
<slot name="final-cta" :service-data="serviceData"></slot>
|
|
175
202
|
</div>
|
|
176
|
-
<slot name="final-cta" :service-data="serviceData"></slot>
|
|
177
203
|
</div>
|
|
178
204
|
</PageRow>
|
|
179
205
|
</component>
|
|
@@ -189,6 +215,11 @@ interface Props {
|
|
|
189
215
|
subheadingTag?: "h2" | "h3";
|
|
190
216
|
serviceData: Service;
|
|
191
217
|
breadcrumbItems?: BreadcrumbItem[];
|
|
218
|
+
/** aria-label on the breadcrumb nav. Defaults to Breadcrumb's own label; override for localisation. */
|
|
219
|
+
breadcrumbAriaLabel?: string;
|
|
220
|
+
/** Text before the price in the hero pill, e.g. "From £95". Override for localisation; "" shows the price alone. */
|
|
221
|
+
pricePrefix?: string;
|
|
222
|
+
idealForIcon?: string;
|
|
192
223
|
heroImageVariant?: "full" | "popout" | "content" | "inset-content";
|
|
193
224
|
heroContentVariant?: "full" | "popout" | "content" | "inset-content";
|
|
194
225
|
bodyVariant?: "full" | "popout" | "content" | "inset-content";
|
|
@@ -216,6 +247,9 @@ const props = withDefaults(defineProps<Props>(), {
|
|
|
216
247
|
headerTag: "h1",
|
|
217
248
|
subheadingTag: "h2",
|
|
218
249
|
breadcrumbItems: undefined,
|
|
250
|
+
breadcrumbAriaLabel: undefined,
|
|
251
|
+
pricePrefix: "From",
|
|
252
|
+
idealForIcon: "mdi:diamond-stone",
|
|
219
253
|
heroImageVariant: "full",
|
|
220
254
|
heroContentVariant: "content",
|
|
221
255
|
bodyVariant: "content",
|
|
@@ -238,12 +272,20 @@ const props = withDefaults(defineProps<Props>(), {
|
|
|
238
272
|
styleClassPassthrough: () => [],
|
|
239
273
|
});
|
|
240
274
|
|
|
241
|
-
|
|
275
|
+
// No title means no heading to point at, so don't label the landmark from one.
|
|
276
|
+
const { headingId, ariaLabelledby } = useAriaLabelledById(() => (props.serviceData.title ? props.tag : "div"));
|
|
277
|
+
|
|
278
|
+
const heroPriceLabel = computed(() => `${props.pricePrefix} ${props.serviceData.price}`.trim());
|
|
279
|
+
const hasHeroHeading = computed(() => Boolean(props.serviceData.heroHeading?.some((item) => item.text)));
|
|
280
|
+
const faqQuestionTag = computed(() => (props.subheadingTag === "h2" ? "h3" : "h4"));
|
|
242
281
|
|
|
243
282
|
// Falls back to a plain, non-linked breadcrumb built from the service's own category/title
|
|
244
283
|
// when the consumer doesn't pass real routes — routing is always the consumer's decision.
|
|
245
284
|
const resolvedBreadcrumbItems = computed<BreadcrumbItem[]>(
|
|
246
|
-
() =>
|
|
285
|
+
() =>
|
|
286
|
+
(props.breadcrumbItems ?? [{ label: props.serviceData.category }, { label: props.serviceData.title }]).filter(
|
|
287
|
+
(item) => item.label
|
|
288
|
+
)
|
|
247
289
|
);
|
|
248
290
|
|
|
249
291
|
const { elementClasses, resetElementClasses } = useStyleClassPassthrough(props.styleClassPassthrough);
|
|
@@ -259,20 +301,22 @@ watch(
|
|
|
259
301
|
<style lang="css">
|
|
260
302
|
@layer components {
|
|
261
303
|
.service-detail {
|
|
304
|
+
--_clip-allowance: 0.2em;
|
|
305
|
+
|
|
262
306
|
container-type: inline-size;
|
|
263
307
|
container-name: service-detail;
|
|
264
308
|
|
|
265
309
|
.service-detail__hero {
|
|
266
310
|
border-radius: var(--service-detail-hero-border-radius, 0.8rem);
|
|
267
311
|
overflow: hidden;
|
|
268
|
-
|
|
312
|
+
min-block-size: var(--service-detail-hero-min-height-mobile, 32rem);
|
|
269
313
|
|
|
270
314
|
@container (width >= 768px) {
|
|
271
|
-
|
|
315
|
+
min-block-size: var(--service-detail-hero-min-height-tablet, 36rem);
|
|
272
316
|
}
|
|
273
317
|
|
|
274
318
|
@container (width >= 1024px) {
|
|
275
|
-
|
|
319
|
+
min-block-size: var(--service-detail-hero-min-height-desktop, 42rem);
|
|
276
320
|
}
|
|
277
321
|
|
|
278
322
|
.grid-stack__layer {
|
|
@@ -309,6 +353,8 @@ watch(
|
|
|
309
353
|
|
|
310
354
|
.service-detail__hero-content {
|
|
311
355
|
display: grid;
|
|
356
|
+
min-inline-size: 0;
|
|
357
|
+
overflow-wrap: anywhere;
|
|
312
358
|
align-content: var(--service-detail-hero-content-align, end);
|
|
313
359
|
|
|
314
360
|
.service-detail__breadcrumb {
|
|
@@ -319,6 +365,25 @@ watch(
|
|
|
319
365
|
.eyebrow-text,
|
|
320
366
|
.hero-text {
|
|
321
367
|
color: var(--service-detail-hero-text-colour, white);
|
|
368
|
+
display: -webkit-box;
|
|
369
|
+
-webkit-box-orient: vertical;
|
|
370
|
+
overflow: hidden;
|
|
371
|
+
/* Room for descenders and italic overhang inside the clip, cancelled by the margins. */
|
|
372
|
+
padding-block-end: var(--_clip-allowance);
|
|
373
|
+
padding-inline-end: var(--_clip-allowance);
|
|
374
|
+
margin-inline-end: calc(-1 * var(--_clip-allowance));
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
.eyebrow-text {
|
|
378
|
+
margin-block-end: calc(-1 * var(--_clip-allowance));
|
|
379
|
+
-webkit-line-clamp: var(--service-detail-hero-eyebrow-line-clamp, none);
|
|
380
|
+
line-clamp: var(--service-detail-hero-eyebrow-line-clamp, none);
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
.hero-text {
|
|
384
|
+
margin-block: 2rem calc(2rem - var(--_clip-allowance));
|
|
385
|
+
-webkit-line-clamp: var(--service-detail-hero-title-line-clamp, none);
|
|
386
|
+
line-clamp: var(--service-detail-hero-title-line-clamp, none);
|
|
322
387
|
}
|
|
323
388
|
|
|
324
389
|
.service-detail__hero-pills {
|
|
@@ -347,6 +412,7 @@ watch(
|
|
|
347
412
|
|
|
348
413
|
.service-detail__main {
|
|
349
414
|
min-inline-size: 0;
|
|
415
|
+
overflow-wrap: anywhere;
|
|
350
416
|
display: flex;
|
|
351
417
|
flex-direction: column;
|
|
352
418
|
gap: var(--service-detail-main-section-gap, 2.4rem);
|
|
@@ -378,8 +444,8 @@ watch(
|
|
|
378
444
|
align-items: flex-start;
|
|
379
445
|
gap: var(--service-detail-ideal-for-item-gap, 1rem);
|
|
380
446
|
padding: var(--service-detail-ideal-for-item-padding, 1.6rem);
|
|
381
|
-
background-color: var(--service-detail-ideal-for-item-background, var(--
|
|
382
|
-
border: 1px solid var(--service-detail-ideal-for-item-border-colour,
|
|
447
|
+
background-color: var(--service-detail-ideal-for-item-background, var(--theme-surface-subtle));
|
|
448
|
+
border: 1px solid var(--service-detail-ideal-for-item-border-colour, transparent);
|
|
383
449
|
border-radius: var(--service-detail-ideal-for-item-border-radius, 0.4rem);
|
|
384
450
|
|
|
385
451
|
.service-detail__ideal-for-icon {
|
|
@@ -392,6 +458,7 @@ watch(
|
|
|
392
458
|
|
|
393
459
|
p {
|
|
394
460
|
margin: 0;
|
|
461
|
+
min-inline-size: 0;
|
|
395
462
|
}
|
|
396
463
|
}
|
|
397
464
|
}
|
|
@@ -420,6 +487,8 @@ watch(
|
|
|
420
487
|
}
|
|
421
488
|
|
|
422
489
|
.service-detail__sidebar {
|
|
490
|
+
min-inline-size: 0;
|
|
491
|
+
overflow-wrap: anywhere;
|
|
423
492
|
display: flex;
|
|
424
493
|
flex-direction: column;
|
|
425
494
|
gap: var(--service-detail-sidebar-gap, 2rem);
|
|
@@ -428,6 +497,11 @@ watch(
|
|
|
428
497
|
|
|
429
498
|
.service-detail__sidebar-label {
|
|
430
499
|
margin: 0 0 var(--service-detail-sidebar-label-margin-block-end, 1.6rem);
|
|
500
|
+
display: -webkit-box;
|
|
501
|
+
-webkit-box-orient: vertical;
|
|
502
|
+
overflow: hidden;
|
|
503
|
+
-webkit-line-clamp: var(--service-detail-sidebar-label-line-clamp, none);
|
|
504
|
+
line-clamp: var(--service-detail-sidebar-label-line-clamp, none);
|
|
431
505
|
font-size: var(--service-detail-sidebar-label-font-size, 1.2rem);
|
|
432
506
|
text-transform: uppercase;
|
|
433
507
|
letter-spacing: var(--service-detail-sidebar-label-letter-spacing, 0.05em);
|
|
@@ -451,7 +525,21 @@ watch(
|
|
|
451
525
|
justify-content: var(--service-detail-sidebar-row-book-cta-justify-content, end);
|
|
452
526
|
}
|
|
453
527
|
|
|
528
|
+
span:last-child {
|
|
529
|
+
flex: 1 1 0;
|
|
530
|
+
min-inline-size: 0;
|
|
531
|
+
text-align: end;
|
|
532
|
+
display: -webkit-box;
|
|
533
|
+
-webkit-box-orient: vertical;
|
|
534
|
+
overflow: hidden;
|
|
535
|
+
-webkit-line-clamp: var(--service-detail-sidebar-value-line-clamp, none);
|
|
536
|
+
line-clamp: var(--service-detail-sidebar-value-line-clamp, none);
|
|
537
|
+
}
|
|
538
|
+
|
|
454
539
|
span:first-child {
|
|
540
|
+
flex: 0 0 auto;
|
|
541
|
+
max-inline-size: 50%;
|
|
542
|
+
overflow-wrap: break-word;
|
|
455
543
|
text-transform: uppercase;
|
|
456
544
|
font-size: var(--service-detail-sidebar-row-label-font-size, 1.2rem);
|
|
457
545
|
opacity: var(--service-detail-muted-opacity, 0.7);
|
|
@@ -474,14 +562,14 @@ watch(
|
|
|
474
562
|
gap: var(--service-detail-related-items-gap, 1rem);
|
|
475
563
|
|
|
476
564
|
.service-detail__related-item-slot {
|
|
477
|
-
background-color: var(--service-detail-related-item-background, var(--
|
|
565
|
+
background-color: var(--service-detail-related-item-background, var(--theme-surface-subtle));
|
|
478
566
|
padding: var(--service-detail-related-item-padding, 1rem);
|
|
479
|
-
border: 1px solid var(--service-detail-related-item-border-colour,
|
|
567
|
+
border: 1px solid var(--service-detail-related-item-border-colour, transparent);
|
|
480
568
|
border-radius: var(--service-detail-related-item-border-radius, 0.4rem);
|
|
481
569
|
|
|
482
570
|
.service-detail__related-item {
|
|
483
571
|
display: grid;
|
|
484
|
-
grid-template-columns: auto 1fr;
|
|
572
|
+
grid-template-columns: auto minmax(0, 1fr);
|
|
485
573
|
gap: var(--service-detail-related-item-gap, 1.2rem);
|
|
486
574
|
align-items: center;
|
|
487
575
|
|
|
@@ -498,6 +586,14 @@ watch(
|
|
|
498
586
|
margin: 0;
|
|
499
587
|
}
|
|
500
588
|
|
|
589
|
+
.service-detail__related-title {
|
|
590
|
+
display: -webkit-box;
|
|
591
|
+
-webkit-box-orient: vertical;
|
|
592
|
+
overflow: hidden;
|
|
593
|
+
-webkit-line-clamp: var(--service-detail-related-title-line-clamp, none);
|
|
594
|
+
line-clamp: var(--service-detail-related-title-line-clamp, none);
|
|
595
|
+
}
|
|
596
|
+
|
|
501
597
|
.service-detail__related-price {
|
|
502
598
|
margin-block-start: var(--service-detail-related-price-margin-block-start, 0.6rem);
|
|
503
599
|
font-size: var(--service-detail-related-price-font-size, 1.2rem);
|
|
@@ -523,12 +619,41 @@ watch(
|
|
|
523
619
|
@container (width >= 700px) {
|
|
524
620
|
flex-direction: row;
|
|
525
621
|
align-items: center;
|
|
622
|
+
|
|
623
|
+
.service-detail__final-cta-copy {
|
|
624
|
+
flex: 1 1 0;
|
|
625
|
+
}
|
|
626
|
+
}
|
|
627
|
+
|
|
628
|
+
.service-detail__final-cta-action {
|
|
629
|
+
flex-shrink: 0;
|
|
630
|
+
max-inline-size: 100%;
|
|
526
631
|
}
|
|
527
632
|
|
|
528
633
|
.service-detail__final-cta-copy {
|
|
634
|
+
min-inline-size: 0;
|
|
635
|
+
overflow-wrap: anywhere;
|
|
636
|
+
|
|
637
|
+
.hero-text {
|
|
638
|
+
display: -webkit-box;
|
|
639
|
+
-webkit-box-orient: vertical;
|
|
640
|
+
overflow: hidden;
|
|
641
|
+
padding-block-end: var(--_clip-allowance);
|
|
642
|
+
padding-inline-end: var(--_clip-allowance);
|
|
643
|
+
margin-block: 0 calc(0.8rem - var(--_clip-allowance));
|
|
644
|
+
margin-inline-end: calc(-1 * var(--_clip-allowance));
|
|
645
|
+
-webkit-line-clamp: var(--service-detail-final-cta-heading-line-clamp, none);
|
|
646
|
+
line-clamp: var(--service-detail-final-cta-heading-line-clamp, none);
|
|
647
|
+
}
|
|
648
|
+
|
|
529
649
|
p {
|
|
530
650
|
margin: 0;
|
|
531
651
|
opacity: var(--service-detail-muted-opacity, 0.7);
|
|
652
|
+
display: -webkit-box;
|
|
653
|
+
-webkit-box-orient: vertical;
|
|
654
|
+
overflow: hidden;
|
|
655
|
+
-webkit-line-clamp: var(--service-detail-final-cta-body-line-clamp, none);
|
|
656
|
+
line-clamp: var(--service-detail-final-cta-body-line-clamp, none);
|
|
532
657
|
}
|
|
533
658
|
}
|
|
534
659
|
}
|