srcdev-nuxt-components 9.3.7 → 9.3.8
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/hooks/check-component-update.sh +30 -1
- package/.claude/skills/components/breadcrumb.md +49 -0
- package/.claude/skills/components/content-docs.md +1 -1
- package/.claude/skills/components/service-detail.md +156 -0
- package/.claude/skills/components/services-card-grid.md +9 -9
- package/.claude/skills/components/services-card.md +82 -30
- package/.claude/skills/index.md +3 -1
- package/.claude/skills/qa-panel.md +2 -2
- package/.vscode/srcdev-component-breadcrumb.code-snippets +14 -0
- package/.vscode/srcdev-component-service-detail.code-snippets +49 -0
- package/.vscode/srcdev-component-services-card.code-snippets +108 -0
- package/app/components/01.atoms/navigation/breadcrumb/Breadcrumb.vue +94 -0
- package/app/components/01.atoms/navigation/breadcrumb/CONSUMER-STYLING.md +32 -0
- package/app/components/01.atoms/navigation/breadcrumb/stories/Breadcrumb.stories.ts +87 -0
- package/app/components/01.atoms/navigation/breadcrumb/tests/Breadcrumb.spec.ts +87 -0
- package/app/components/01.atoms/navigation/breadcrumb/tests/__snapshots__/Breadcrumb.spec.ts.snap +13 -0
- package/app/components/03.organisms/services/service-detail/CONSUMER-STYLING.md +181 -0
- package/app/components/03.organisms/services/service-detail/ServiceDetail.vue +480 -0
- package/app/components/03.organisms/services/service-detail/stories/ServiceDetail.stories.ts +282 -0
- package/app/components/03.organisms/services/service-detail/tests/ServiceDetail.spec.ts +270 -0
- package/app/components/03.organisms/services/service-detail/tests/__snapshots__/ServiceDetail.spec.ts.snap +99 -0
- package/app/components/03.organisms/services/services-card/CONSUMER-STYLING.md +129 -0
- package/app/components/03.organisms/services/services-card/ServicesCard.vue +153 -39
- package/app/components/03.organisms/services/services-card/stories/ServicesCard.stories.ts +65 -0
- package/app/components/03.organisms/services/services-card/tests/ServicesCard.spec.ts +82 -0
- package/app/components/03.organisms/services/services-card/tests/__snapshots__/ServicesCard.spec.ts.snap +13 -5
- package/app/components/03.organisms/services/services-grids/ServicesCardGrid.vue +3 -7
- package/app/components/03.organisms/services/services-grids/stories/ServicesCardGrid.stories.ts +126 -2
- package/app/components/03.organisms/services/services-grids/stories/ServicesSectionGrid.stories.ts +6 -0
- package/app/components/03.organisms/services/services-grids/tests/__snapshots__/ServicesCardGrid.spec.ts.snap +51 -27
- package/app/types/components/breadcrumb.ts +4 -0
- package/app/types/components/index.ts +1 -0
- package/package.json +2 -2
- package/app/pages/auto-grid.vue +0 -328
- package/app/pages/banner-video.vue +0 -238
- package/app/pages/forms/examples/buttons/index.vue +0 -259
- package/app/pages/forms/examples/material/checkbox-radio-panels.vue +0 -244
- package/app/pages/forms/examples/material/cssbattle.vue +0 -60
- package/app/pages/forms/examples/material/text-fields.vue +0 -910
- package/app/pages/grid-stack.vue +0 -252
- package/app/pages/index.vue +0 -127
- package/app/pages/page-hero-highlights.vue +0 -204
- package/app/pages/samaritan.vue +0 -327
- package/app/pages/typography/hero-text.vue +0 -97
- package/app/pages/typography/index.vue +0 -39
- package/app/pages/typography/page-body.vue +0 -171
- package/app/pages/typography/page-heading.vue +0 -76
- package/app/pages/typography/page-link.vue +0 -103
- package/app/pages/ui/accordian.vue +0 -211
- package/app/pages/ui/anchor-scroll-tab-navigation.vue +0 -191
- package/app/pages/ui/anchor-scroll.vue +0 -367
- package/app/pages/ui/animated-svg-text.vue +0 -85
- package/app/pages/ui/block-decorators.vue +0 -142
- package/app/pages/ui/card-core.vue +0 -82
- package/app/pages/ui/carousel-basic.vue +0 -292
- package/app/pages/ui/carousel-flip.vue +0 -449
- package/app/pages/ui/carousel-infinite.vue +0 -260
- package/app/pages/ui/clipped-panels.vue +0 -101
- package/app/pages/ui/contact-section.vue +0 -323
- package/app/pages/ui/container-glow.vue +0 -106
- package/app/pages/ui/content-width.vue +0 -126
- package/app/pages/ui/deep-expanding-menu.vue +0 -138
- package/app/pages/ui/display-avatar.vue +0 -253
- package/app/pages/ui/display-banner.vue +0 -78
- package/app/pages/ui/display-chip.vue +0 -367
- package/app/pages/ui/display-details.vue +0 -171
- package/app/pages/ui/display-dialog.vue +0 -344
- package/app/pages/ui/display-pill.vue +0 -396
- package/app/pages/ui/display-prompt.vue +0 -284
- package/app/pages/ui/display-toast.vue +0 -311
- package/app/pages/ui/expanding-panel.vue +0 -326
- package/app/pages/ui/glowing-border.vue +0 -111
- package/app/pages/ui/layout-content-docs.vue +0 -72
- package/app/pages/ui/layout-grid-a.vue +0 -96
- package/app/pages/ui/layout-grid-b.vue +0 -132
- package/app/pages/ui/magnetic-navigation.vue +0 -50
- package/app/pages/ui/marquee-scroller.vue +0 -99
- package/app/pages/ui/mask-element.vue +0 -104
- package/app/pages/ui/masonry-columns.vue +0 -55
- package/app/pages/ui/masonry-grid-ordered-experiment.vue +0 -79
- package/app/pages/ui/masonry-grid-ordered.vue +0 -56
- package/app/pages/ui/masonry-grid-sorted.vue +0 -55
- package/app/pages/ui/masonry-grid.vue +0 -55
- package/app/pages/ui/navigation/navigation-horizontal.vue +0 -484
- package/app/pages/ui/navigation/site-navigation/about.vue +0 -36
- package/app/pages/ui/navigation/site-navigation/contact.vue +0 -39
- package/app/pages/ui/navigation/site-navigation/index.vue +0 -38
- package/app/pages/ui/navigation/site-navigation/portfolio.vue +0 -43
- package/app/pages/ui/navigation/site-navigation/services.vue +0 -42
- package/app/pages/ui/navigation/tab-navigation.vue +0 -493
- package/app/pages/ui/page-row.vue +0 -127
- package/app/pages/ui/price-list.vue +0 -65
- package/app/pages/ui/profile-section.vue +0 -142
- package/app/pages/ui/qr-code/[componentName].vue +0 -218
- package/app/pages/ui/rotating-carousel.vue +0 -154
- package/app/pages/ui/scroll-reveal-image.vue +0 -483
- package/app/pages/ui/section-parallax.vue +0 -54
- package/app/pages/ui/services/colour-finder.vue +0 -43
- package/app/pages/ui/services/services-cards.vue +0 -57
- package/app/pages/ui/services/services-section/[slug].vue +0 -61
- package/app/pages/ui/services/services-sections-compact.vue +0 -64
- package/app/pages/ui/settings.vue +0 -54
- package/app/pages/ui/simple-grid.vue +0 -55
- package/app/pages/ui/slider-gallery.vue +0 -261
- package/app/pages/ui/tabs-y.vue +0 -107
- package/app/pages/ui/tabs.vue +0 -189
- package/app/pages/ui/tooltips.vue +0 -104
- package/app/pages/ui/wipe-away-vertical.vue +0 -129
|
@@ -1,101 +0,0 @@
|
|
|
1
|
-
<template>
|
|
2
|
-
<div>
|
|
3
|
-
<NuxtLayout name="default">
|
|
4
|
-
<template #layout-content>
|
|
5
|
-
<PageRow tag="div" variant="full" :style-class-passthrough="['mbe-20']">
|
|
6
|
-
<h1 class="page-heading-2">Clipped Panels</h1>
|
|
7
|
-
</PageRow>
|
|
8
|
-
<PageRow tag="div" variant="full" :style-class-passthrough="['mbe-20']">
|
|
9
|
-
<h2 class="page-heading-2">Rectangle</h2>
|
|
10
|
-
<ClippedPanel variant="rectangle">
|
|
11
|
-
<template #default>
|
|
12
|
-
<p>Clipped Panels Rectangle</p>
|
|
13
|
-
</template>
|
|
14
|
-
</ClippedPanel>
|
|
15
|
-
</PageRow>
|
|
16
|
-
<PageRow tag="div" variant="full" :style-class-passthrough="['mbe-20']">
|
|
17
|
-
<h2 class="page-heading-2">Square</h2>
|
|
18
|
-
<ClippedPanel variant="square">
|
|
19
|
-
<template #default>
|
|
20
|
-
<p>Clipped Panels Square</p>
|
|
21
|
-
</template>
|
|
22
|
-
</ClippedPanel>
|
|
23
|
-
</PageRow>
|
|
24
|
-
<PageRow tag="div" variant="full" :style-class-passthrough="['mbe-20']">
|
|
25
|
-
<h2 class="page-heading-2">Circle</h2>
|
|
26
|
-
<div class="counter-panel">
|
|
27
|
-
<div class="counter-panel__counter">1</div>
|
|
28
|
-
<div class="counter-panel__title">Title</div>
|
|
29
|
-
<div class="counter-panel__content">This will be the content</div>
|
|
30
|
-
</div>
|
|
31
|
-
</PageRow>
|
|
32
|
-
</template>
|
|
33
|
-
</NuxtLayout>
|
|
34
|
-
</div>
|
|
35
|
-
</template>
|
|
36
|
-
|
|
37
|
-
<script setup lang="ts">
|
|
38
|
-
definePageMeta({
|
|
39
|
-
layout: false,
|
|
40
|
-
});
|
|
41
|
-
|
|
42
|
-
useHead({
|
|
43
|
-
title: "Browser title tag",
|
|
44
|
-
meta: [
|
|
45
|
-
{
|
|
46
|
-
name: "description",
|
|
47
|
-
content: "Meta description content",
|
|
48
|
-
},
|
|
49
|
-
],
|
|
50
|
-
});
|
|
51
|
-
</script>
|
|
52
|
-
|
|
53
|
-
<style scoped lang="css">
|
|
54
|
-
.counter-panel {
|
|
55
|
-
display: inline-grid;
|
|
56
|
-
grid-template-areas: "select-stack";
|
|
57
|
-
|
|
58
|
-
aspect-ratio: 1;
|
|
59
|
-
width: 200px;
|
|
60
|
-
|
|
61
|
-
&:before {
|
|
62
|
-
content: "";
|
|
63
|
-
grid-area: select-stack;
|
|
64
|
-
background-color: red;
|
|
65
|
-
|
|
66
|
-
aspect-ratio: 1;
|
|
67
|
-
width: 200px;
|
|
68
|
-
/* clip-path: path(
|
|
69
|
-
'M 10, 40 L 30, 40 A 10, 10, 0, 0, 0 40, 30 L 40, 10 A 10, 10, 0, 0, 1 50, 0 L 190, 0 A 10, 10, 0, 0, 1 200, 10 L 200, 190 A 10, 10, 0, 0, 1 190, 200 L 10, 200 A 10, 10, 0, 0, 1 0, 190 L 0, 50 A 10, 10, 0, 0, 1 10, 40 Z'
|
|
70
|
-
); */
|
|
71
|
-
clip-path: path(
|
|
72
|
-
"M 10, 40 L 20, 40 A 20, 20, 0, 0, 0 40, 20 L 40, 10 A 10, 10, 0, 0, 1 50, 0 L 190, 0 A 10, 10, 0, 0, 1 200, 10 L 200, 190 A 10, 10, 0, 0, 1 190, 200 L 10, 200 A 10, 10, 0, 0, 1 0, 190 L 0, 50 A 10, 10, 0, 0, 1 10, 40 Z"
|
|
73
|
-
);
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
.counter-panel__counter {
|
|
77
|
-
grid-area: select-stack;
|
|
78
|
-
display: flex;
|
|
79
|
-
justify-self: start;
|
|
80
|
-
align-items: center;
|
|
81
|
-
justify-content: center;
|
|
82
|
-
background-color: white;
|
|
83
|
-
color: black;
|
|
84
|
-
border-radius: 50%;
|
|
85
|
-
border: 5px solid red;
|
|
86
|
-
aspect-ratio: 1;
|
|
87
|
-
width: 30px;
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
.counter-panel__title {
|
|
91
|
-
grid-area: select-stack;
|
|
92
|
-
display: flex;
|
|
93
|
-
justify-self: center;
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
.counter-panel__content {
|
|
97
|
-
grid-area: select-stack;
|
|
98
|
-
align-content: center;
|
|
99
|
-
}
|
|
100
|
-
}
|
|
101
|
-
</style>
|
|
@@ -1,323 +0,0 @@
|
|
|
1
|
-
<template>
|
|
2
|
-
<div>
|
|
3
|
-
<NuxtLayout name="default">
|
|
4
|
-
<template #layout-content>
|
|
5
|
-
<PageRow tag="div" variant="content" :style-class-passthrough="['mbe-20']">
|
|
6
|
-
<h2 class="page-heading-2">Profile Section</h2>
|
|
7
|
-
</PageRow>
|
|
8
|
-
|
|
9
|
-
<PageRow tag="div" variant="content" :style-class-passthrough="['mbe-20']">
|
|
10
|
-
<ContactSection tag="section" stepper-indicator-size="2.4rem" :style-class-passthrough="['mbe-20']">
|
|
11
|
-
<template #indicator-0>
|
|
12
|
-
<Icon name="lucide-map-pin" class="indicator-icon" />
|
|
13
|
-
</template>
|
|
14
|
-
<template #item-0>
|
|
15
|
-
<div>
|
|
16
|
-
<HeroText
|
|
17
|
-
tag="h2"
|
|
18
|
-
axis="horizontal"
|
|
19
|
-
font-size="label"
|
|
20
|
-
:text-content="[{ text: 'Location', styleClass: 'normal' }]"
|
|
21
|
-
:style-class-passthrough="['mbe-20']"
|
|
22
|
-
/>
|
|
23
|
-
|
|
24
|
-
<p class="page-body-normal">
|
|
25
|
-
Mobile service across Bath — Weston, Lansdown, Larkhall, Oldfield Park & surrounding areas.
|
|
26
|
-
</p>
|
|
27
|
-
</div>
|
|
28
|
-
</template>
|
|
29
|
-
|
|
30
|
-
<template #indicator-1>
|
|
31
|
-
<Icon name="lucide-phone" class="indicator-icon" />
|
|
32
|
-
</template>
|
|
33
|
-
<template #item-1>
|
|
34
|
-
<div>
|
|
35
|
-
<HeroText
|
|
36
|
-
tag="h2"
|
|
37
|
-
axis="horizontal"
|
|
38
|
-
font-size="label"
|
|
39
|
-
:text-content="[{ text: 'Contact', styleClass: 'normal' }]"
|
|
40
|
-
:style-class-passthrough="['mbe-20']"
|
|
41
|
-
/>
|
|
42
|
-
<p class="page-body-normal">
|
|
43
|
-
Call or text to arrange your home appointment. Evening and weekend slots available.
|
|
44
|
-
</p>
|
|
45
|
-
</div>
|
|
46
|
-
</template>
|
|
47
|
-
|
|
48
|
-
<template #indicator-2>
|
|
49
|
-
<Icon name="lucide-clock" class="indicator-icon" />
|
|
50
|
-
</template>
|
|
51
|
-
<template #item-2>
|
|
52
|
-
<div>
|
|
53
|
-
<HeroText
|
|
54
|
-
tag="h2"
|
|
55
|
-
axis="horizontal"
|
|
56
|
-
font-size="label"
|
|
57
|
-
:text-content="[{ text: 'Hours', styleClass: 'normal' }]"
|
|
58
|
-
:style-class-passthrough="['mbe-20']"
|
|
59
|
-
/>
|
|
60
|
-
<p class="page-body-normal">
|
|
61
|
-
Monday
|
|
62
|
-
<em>to</em>
|
|
63
|
-
Sunday
|
|
64
|
-
<br />
|
|
65
|
-
<em>between</em>
|
|
66
|
-
9:00 AM
|
|
67
|
-
<em>and</em>
|
|
68
|
-
7:00 PM
|
|
69
|
-
<br />
|
|
70
|
-
Flexible by arrangement
|
|
71
|
-
<br />
|
|
72
|
-
Sunday appointments available on request and a premium.
|
|
73
|
-
</p>
|
|
74
|
-
</div>
|
|
75
|
-
</template>
|
|
76
|
-
|
|
77
|
-
<template #form>
|
|
78
|
-
<ClientOnly>
|
|
79
|
-
<form ref="formRef" @submit.stop.prevent="submitForm()">
|
|
80
|
-
<div id="contact-aria-live" aria-live="assertive"></div>
|
|
81
|
-
|
|
82
|
-
<FormField width="wide" :has-gutter="false">
|
|
83
|
-
<template #default>
|
|
84
|
-
<InputTextWithLabel
|
|
85
|
-
id="fullName"
|
|
86
|
-
v-model="state.fullName"
|
|
87
|
-
type="text"
|
|
88
|
-
:maxlength="fieldMaxLength('fullName')"
|
|
89
|
-
name="fullName"
|
|
90
|
-
placeholder="eg. Jane Smith"
|
|
91
|
-
label="Full name"
|
|
92
|
-
:error-message="formErrors?.fullName?._errors[0] ?? ''"
|
|
93
|
-
:field-has-error="Boolean(zodFormControl.submitAttempted && formErrors?.fullName)"
|
|
94
|
-
:required="true"
|
|
95
|
-
input-variant="underlined"
|
|
96
|
-
>
|
|
97
|
-
<template #left>
|
|
98
|
-
<Icon name="radix-icons:person" class="icon" />
|
|
99
|
-
</template>
|
|
100
|
-
</InputTextWithLabel>
|
|
101
|
-
</template>
|
|
102
|
-
</FormField>
|
|
103
|
-
|
|
104
|
-
<FormField width="wide" :has-gutter="false">
|
|
105
|
-
<template #default>
|
|
106
|
-
<InputTextWithLabel
|
|
107
|
-
id="telNumber"
|
|
108
|
-
v-model="state.telNumber"
|
|
109
|
-
type="tel"
|
|
110
|
-
inputmode="tel"
|
|
111
|
-
:maxlength="fieldMaxLength('telNumber')"
|
|
112
|
-
name="telNumber"
|
|
113
|
-
placeholder="eg. 07700 900000"
|
|
114
|
-
label="Telephone number"
|
|
115
|
-
:error-message="formErrors?.telNumber?._errors[0] ?? ''"
|
|
116
|
-
:field-has-error="Boolean(zodFormControl.submitAttempted && formErrors?.telNumber)"
|
|
117
|
-
:required="true"
|
|
118
|
-
input-variant="underlined"
|
|
119
|
-
>
|
|
120
|
-
<template #left>
|
|
121
|
-
<Icon name="lucide-phone" class="icon" />
|
|
122
|
-
</template>
|
|
123
|
-
</InputTextWithLabel>
|
|
124
|
-
</template>
|
|
125
|
-
</FormField>
|
|
126
|
-
|
|
127
|
-
<FormField width="wide" :has-gutter="false">
|
|
128
|
-
<template #default>
|
|
129
|
-
<InputTextWithLabel
|
|
130
|
-
id="emailAddress"
|
|
131
|
-
v-model="state.emailAddress"
|
|
132
|
-
type="email"
|
|
133
|
-
inputmode="email"
|
|
134
|
-
:maxlength="fieldMaxLength('emailAddress')"
|
|
135
|
-
name="emailAddress"
|
|
136
|
-
placeholder="eg. name@domain.com"
|
|
137
|
-
label="Email address"
|
|
138
|
-
:error-message="formErrors?.emailAddress?._errors[0] ?? ''"
|
|
139
|
-
:field-has-error="Boolean(zodFormControl.submitAttempted && formErrors?.emailAddress)"
|
|
140
|
-
:required="true"
|
|
141
|
-
input-variant="underlined"
|
|
142
|
-
>
|
|
143
|
-
<template #left>
|
|
144
|
-
<Icon name="radix-icons:envelope-closed" class="icon" />
|
|
145
|
-
</template>
|
|
146
|
-
</InputTextWithLabel>
|
|
147
|
-
</template>
|
|
148
|
-
</FormField>
|
|
149
|
-
|
|
150
|
-
<FormField v-if="servicesData !== null" width="wide" :has-gutter="false">
|
|
151
|
-
<template #default>
|
|
152
|
-
<MultipleCheckboxes
|
|
153
|
-
v-model="state.services"
|
|
154
|
-
v-model:field-data="servicesData"
|
|
155
|
-
name="services"
|
|
156
|
-
legend="Services of interest"
|
|
157
|
-
label="Select all that apply"
|
|
158
|
-
:error-message="formErrors?.services?._errors[0] ?? ''"
|
|
159
|
-
:field-has-error="Boolean(zodFormControl.submitAttempted && formErrors?.services)"
|
|
160
|
-
options-layout="inline"
|
|
161
|
-
:is-button="true"
|
|
162
|
-
:is-pill="false"
|
|
163
|
-
input-variant="underlined"
|
|
164
|
-
>
|
|
165
|
-
<template #descriptionText>Choose one or more services you're interested in.</template>
|
|
166
|
-
</MultipleCheckboxes>
|
|
167
|
-
</template>
|
|
168
|
-
</FormField>
|
|
169
|
-
|
|
170
|
-
<FormField width="wide" :has-gutter="false">
|
|
171
|
-
<template #default>
|
|
172
|
-
<InputTextareaWithLabel
|
|
173
|
-
v-model="state.comments"
|
|
174
|
-
:maxlength="fieldMaxLength('comments')"
|
|
175
|
-
name="comments"
|
|
176
|
-
placeholder="Tell us a bit more about what you're looking for…"
|
|
177
|
-
label="Comments"
|
|
178
|
-
:error-message="formErrors?.comments?._errors[0] ?? ''"
|
|
179
|
-
:field-has-error="Boolean(zodFormControl.submitAttempted && formErrors?.comments)"
|
|
180
|
-
input-variant="underlined"
|
|
181
|
-
>
|
|
182
|
-
<template #descriptionText>Optional — any extra information that might help us.</template>
|
|
183
|
-
</InputTextareaWithLabel>
|
|
184
|
-
</template>
|
|
185
|
-
</FormField>
|
|
186
|
-
|
|
187
|
-
<FormField width="wide" :has-gutter="false">
|
|
188
|
-
<template #default>
|
|
189
|
-
<InputButtonCore
|
|
190
|
-
type="submit"
|
|
191
|
-
:is-pending="zodFormControl.displayLoader"
|
|
192
|
-
:readonly="zodFormControl.submitDisabled"
|
|
193
|
-
button-text="Send enquiry"
|
|
194
|
-
@click.stop.prevent="submitForm()"
|
|
195
|
-
/>
|
|
196
|
-
</template>
|
|
197
|
-
</FormField>
|
|
198
|
-
</form>
|
|
199
|
-
</ClientOnly>
|
|
200
|
-
</template>
|
|
201
|
-
</ContactSection>
|
|
202
|
-
</PageRow>
|
|
203
|
-
</template>
|
|
204
|
-
</NuxtLayout>
|
|
205
|
-
</div>
|
|
206
|
-
</template>
|
|
207
|
-
|
|
208
|
-
<script setup lang="ts">
|
|
209
|
-
import { z } from "zod";
|
|
210
|
-
import type { IFormMultipleOptions } from "~/types/forms/types.forms";
|
|
211
|
-
|
|
212
|
-
type ReturnTypeUseZodValidation = ReturnType<typeof useZodValidation>;
|
|
213
|
-
|
|
214
|
-
definePageMeta({
|
|
215
|
-
layout: false,
|
|
216
|
-
});
|
|
217
|
-
|
|
218
|
-
useHead({
|
|
219
|
-
title: "Contact Section",
|
|
220
|
-
meta: [
|
|
221
|
-
{
|
|
222
|
-
name: "description",
|
|
223
|
-
content: "Contact Section Meta description content",
|
|
224
|
-
},
|
|
225
|
-
],
|
|
226
|
-
bodyAttrs: {
|
|
227
|
-
class: "contact-section-page",
|
|
228
|
-
},
|
|
229
|
-
});
|
|
230
|
-
|
|
231
|
-
// Fetch the real services list from the existing API endpoint.
|
|
232
|
-
// Transform the raw service objects into the IFormMultipleOptions shape
|
|
233
|
-
// that MultipleCheckboxes expects: { id, name, value, label }[].
|
|
234
|
-
// Fetch the real services list from the existing API endpoint.
|
|
235
|
-
// Transform the raw service objects into the IFormMultipleOptions shape
|
|
236
|
-
// that MultipleCheckboxes expects: { id, name, value, label }[].
|
|
237
|
-
type ServiceItem = { slug: string; title: string };
|
|
238
|
-
const { data: servicesData } = await useFetch<IFormMultipleOptions>("/api/services", {
|
|
239
|
-
transform: (raw) => {
|
|
240
|
-
const services = raw as unknown as ServiceItem[];
|
|
241
|
-
return {
|
|
242
|
-
data: services.map((s) => ({ id: s.slug, name: "services", value: s.slug, label: s.title })),
|
|
243
|
-
total: services.length,
|
|
244
|
-
skip: 0,
|
|
245
|
-
limit: services.length,
|
|
246
|
-
};
|
|
247
|
-
},
|
|
248
|
-
});
|
|
249
|
-
|
|
250
|
-
const formSchema = z.object({
|
|
251
|
-
fullName: z
|
|
252
|
-
.string({ error: (i) => (i.input === undefined ? "Full name is required" : "Full name must be a string") })
|
|
253
|
-
.trim()
|
|
254
|
-
.min(2, "Name is too short")
|
|
255
|
-
.max(80, "Name is too long"),
|
|
256
|
-
telNumber: z
|
|
257
|
-
.string({
|
|
258
|
-
error: (i) => (i.input === undefined ? "Telephone number is required" : "Telephone number must be a string"),
|
|
259
|
-
})
|
|
260
|
-
.trim()
|
|
261
|
-
.min(7, "Telephone number is too short")
|
|
262
|
-
.max(20, "Telephone number is too long"),
|
|
263
|
-
emailAddress: z
|
|
264
|
-
.string({ error: (i) => (i.input === undefined ? "Email address is required" : "Email address must be a string") })
|
|
265
|
-
.email({ error: "Invalid email address" }),
|
|
266
|
-
services: z.array(z.string()).min(1, "Please select at least one service"),
|
|
267
|
-
comments: z.string().trim().max(500, "Comments must be 500 characters or fewer").optional(),
|
|
268
|
-
});
|
|
269
|
-
|
|
270
|
-
type FormSchema = z.infer<typeof formSchema>;
|
|
271
|
-
const formErrors = computed<z.ZodFormattedError<FormSchema> | null>(() => zodErrorObj.value);
|
|
272
|
-
|
|
273
|
-
const state = reactive({
|
|
274
|
-
fullName: "",
|
|
275
|
-
telNumber: "",
|
|
276
|
-
emailAddress: "",
|
|
277
|
-
services: [] as string[],
|
|
278
|
-
comments: "",
|
|
279
|
-
});
|
|
280
|
-
|
|
281
|
-
const formRef = ref<HTMLFormElement | null>(null);
|
|
282
|
-
|
|
283
|
-
const { initZodForm, zodFormControl, zodErrorObj, doZodValidate, fieldMaxLength, scrollToFirstError } =
|
|
284
|
-
useZodValidation<typeof formSchema>(formSchema, formRef) as ReturnTypeUseZodValidation;
|
|
285
|
-
|
|
286
|
-
initZodForm();
|
|
287
|
-
|
|
288
|
-
const submitForm = async () => {
|
|
289
|
-
zodFormControl.submitAttempted = true;
|
|
290
|
-
if (!(await doZodValidate(state))) {
|
|
291
|
-
scrollToFirstError();
|
|
292
|
-
return;
|
|
293
|
-
}
|
|
294
|
-
zodFormControl.displayLoader = true;
|
|
295
|
-
try {
|
|
296
|
-
// POST validated form data to the server route — Resend is called server-side
|
|
297
|
-
await $fetch("/api/contact", { method: "POST", body: state });
|
|
298
|
-
zodFormControl.submitSuccessful = true;
|
|
299
|
-
} catch (error) {
|
|
300
|
-
// Server-side failures (e.g. Resend API error) surface here
|
|
301
|
-
console.warn("Contact form submission failed", error);
|
|
302
|
-
} finally {
|
|
303
|
-
zodFormControl.displayLoader = false;
|
|
304
|
-
}
|
|
305
|
-
};
|
|
306
|
-
|
|
307
|
-
watch(
|
|
308
|
-
() => state,
|
|
309
|
-
() => {
|
|
310
|
-
doZodValidate(state);
|
|
311
|
-
},
|
|
312
|
-
{ deep: true }
|
|
313
|
-
);
|
|
314
|
-
</script>
|
|
315
|
-
|
|
316
|
-
<style lang="css">
|
|
317
|
-
.contact-section-page {
|
|
318
|
-
.contact-section {
|
|
319
|
-
/* max-inline-size: 800px; */
|
|
320
|
-
margin-inline: auto;
|
|
321
|
-
}
|
|
322
|
-
}
|
|
323
|
-
</style>
|
|
@@ -1,106 +0,0 @@
|
|
|
1
|
-
<template>
|
|
2
|
-
<div>
|
|
3
|
-
<NuxtLayout name="default">
|
|
4
|
-
<template #layout-content>
|
|
5
|
-
<PageRow tag="div" variant="popout" :style-class-passthrough="['mbe-20']">
|
|
6
|
-
<h1 class="page-heading-2">Container Glow Effect</h1>
|
|
7
|
-
<p>Glow will track mouse position</p>
|
|
8
|
-
|
|
9
|
-
<ContainerGlowCore
|
|
10
|
-
tag="div"
|
|
11
|
-
:style-class-passthrough="['class-scope', 'pbe-20']"
|
|
12
|
-
:config="{
|
|
13
|
-
proximity: 40,
|
|
14
|
-
spread: 80,
|
|
15
|
-
blur: 20,
|
|
16
|
-
gap: 32,
|
|
17
|
-
vertical: false,
|
|
18
|
-
inactiveOpacity: 0,
|
|
19
|
-
}"
|
|
20
|
-
>
|
|
21
|
-
<template v-for="(item, key) in data" :key="key" #[`container-glow-${key}`]>
|
|
22
|
-
<div class="example-card p-12">
|
|
23
|
-
<h3 class="page-heading-3">{{ item.title }}</h3>
|
|
24
|
-
<p class="pbs-24">{{ item.content }}</p>
|
|
25
|
-
</div>
|
|
26
|
-
</template>
|
|
27
|
-
</ContainerGlowCore>
|
|
28
|
-
</PageRow>
|
|
29
|
-
</template>
|
|
30
|
-
</NuxtLayout>
|
|
31
|
-
</div>
|
|
32
|
-
</template>
|
|
33
|
-
|
|
34
|
-
<script setup lang="ts">
|
|
35
|
-
definePageMeta({
|
|
36
|
-
layout: false,
|
|
37
|
-
});
|
|
38
|
-
|
|
39
|
-
useHead({
|
|
40
|
-
title: "Container Glow Effect",
|
|
41
|
-
meta: [{ name: "description", content: "Container Glow Effect" }],
|
|
42
|
-
bodyAttrs: {
|
|
43
|
-
class: "container-glow-core-page",
|
|
44
|
-
},
|
|
45
|
-
});
|
|
46
|
-
|
|
47
|
-
const data = [
|
|
48
|
-
{
|
|
49
|
-
title: "Trigger Item 1",
|
|
50
|
-
content:
|
|
51
|
-
"Lorem ipsum dolor sit amet consectetur, adipisicing elit. Iusto, amet!. Lorem ipsum dolor sit, adipisicing elit. Iusto, amet!",
|
|
52
|
-
},
|
|
53
|
-
{
|
|
54
|
-
title: "Trigger Item 2",
|
|
55
|
-
content: "Lorem ipsum dolor sit amet consectetur, adipisicing elit. Iusto, amet!",
|
|
56
|
-
},
|
|
57
|
-
{
|
|
58
|
-
title: "Trigger Item 3",
|
|
59
|
-
content:
|
|
60
|
-
"Lorem ipsum dolor sit amet consectetur, adipisicing elit. Iusto, amet!, Lorem ipsum dolor sit amet consectetur, adipisicing elit. Iusto, amet! Lorem ipsum dolor sit amet consectetur, adipisicing elit. Iusto, amet!",
|
|
61
|
-
},
|
|
62
|
-
];
|
|
63
|
-
</script>
|
|
64
|
-
|
|
65
|
-
<style lang="css">
|
|
66
|
-
.class-scope {
|
|
67
|
-
&.container-glow-wrapper {
|
|
68
|
-
.container-glow-core {
|
|
69
|
-
--gradient: conic-gradient(
|
|
70
|
-
from 180deg at 50% 70%,
|
|
71
|
-
hsla(0, 0%, 98%, 1) 0deg,
|
|
72
|
-
#54ff0b 144.0000021457672deg,
|
|
73
|
-
hsla(0, 0%, 98%, 1) 1turn
|
|
74
|
-
);
|
|
75
|
-
|
|
76
|
-
height: 100%;
|
|
77
|
-
background: light-dark(white, hsl(246 44% 7%));
|
|
78
|
-
padding: 2rem;
|
|
79
|
-
aspect-ratio: 330 / 400;
|
|
80
|
-
border-radius: 12px;
|
|
81
|
-
min-width: 280px;
|
|
82
|
-
max-width: 280px;
|
|
83
|
-
display: flex;
|
|
84
|
-
flex-direction: column;
|
|
85
|
-
gap: 0.25rem;
|
|
86
|
-
|
|
87
|
-
&::before {
|
|
88
|
-
/* Custom styles */
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
&::after {
|
|
92
|
-
/* Custom styles */
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
.glows {
|
|
96
|
-
/* Custom styles */
|
|
97
|
-
|
|
98
|
-
&::after,
|
|
99
|
-
&::before {
|
|
100
|
-
/* Custom styles */
|
|
101
|
-
}
|
|
102
|
-
}
|
|
103
|
-
}
|
|
104
|
-
}
|
|
105
|
-
}
|
|
106
|
-
</style>
|
|
@@ -1,126 +0,0 @@
|
|
|
1
|
-
<template>
|
|
2
|
-
<div>
|
|
3
|
-
<NuxtLayout name="default">
|
|
4
|
-
<template #layout-content>
|
|
5
|
-
<ContentWidth tag="div" :style-class-passthrough="['mbe-20']">
|
|
6
|
-
<h1 class="page-heading-2 mbe-18">Content Width</h1>
|
|
7
|
-
<p class="page-body-normal-semibold">
|
|
8
|
-
Small screens:
|
|
9
|
-
<code class="page-body-normal">--content-max-width: auto;</code>
|
|
10
|
-
</p>
|
|
11
|
-
<p class="page-body-normal-semibold">
|
|
12
|
-
>= Laptop screens:
|
|
13
|
-
<code class="page-body-normal">--content-max-width: 1064px;</code>
|
|
14
|
-
</p>
|
|
15
|
-
</ContentWidth>
|
|
16
|
-
|
|
17
|
-
<ContentWidth tag="div" :justify-content="'start'" :style-class-passthrough="['mbe-20']">
|
|
18
|
-
<h2 class="page-heading-2 mbe-18">Sample content (start)</h2>
|
|
19
|
-
<p class="page-body-normal">
|
|
20
|
-
Lorem ipsum odor amet, consectetuer adipiscing elit. Nec elementum maecenas placerat laoreet curae elit
|
|
21
|
-
convallis himenaeos. Tellus varius cursus convallis commodo suspendisse litora. Platea accumsan interdum
|
|
22
|
-
ultrices adipiscing molestie cras dui. Vehicula egestas nisi sagittis fames metus velit. Sodales blandit
|
|
23
|
-
nisi eu dis sit, ridiculus aliquam. Morbi tellus eu in penatibus torquent tortor. Platea gravida nam;
|
|
24
|
-
egestas enim nostra ultricies.
|
|
25
|
-
</p>
|
|
26
|
-
<p class="page-body-normal">
|
|
27
|
-
Mi nibh quisque taciti porta curabitur nostra volutpat. Habitant sodales arcu habitasse mi duis conubia leo
|
|
28
|
-
lacinia. Montes torquent sodales adipiscing; proin semper feugiat morbi ullamcorper praesent. Arcu luctus
|
|
29
|
-
tempor quam ligula vestibulum sapien faucibus ridiculus. Cursus consequat ultricies consectetur class
|
|
30
|
-
suscipit quisque convallis eget? Dignissim mattis luctus enim habitant porta pretium litora. Parturient
|
|
31
|
-
montes imperdiet massa; sollicitudin varius hac aptent. Eleifend parturient mattis tellus nisi a montes.
|
|
32
|
-
</p>
|
|
33
|
-
</ContentWidth>
|
|
34
|
-
|
|
35
|
-
<ContentWidth tag="div" :style-class-passthrough="['mbe-20']">
|
|
36
|
-
<h2 class="page-heading-2 mbe-18">Sample Column content (center/default)</h2>
|
|
37
|
-
|
|
38
|
-
<LayoutGridByCols :column-count="2">
|
|
39
|
-
<template #item-0>
|
|
40
|
-
<div>
|
|
41
|
-
<h3 class="page-heading-3 mbe-12">Column 1</h3>
|
|
42
|
-
<p class="page-body-normal">
|
|
43
|
-
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Donec a diam lectus. Sed sit amet ipsum
|
|
44
|
-
mauris. Maecenas congue ligula ac quam viverra nec consectetur ante hendrerit. Donec et mollis dolor.
|
|
45
|
-
Praesent et diam eget libero egestas mattis sit amet vitae augue. Nam tincidunt congue enim, ut porta
|
|
46
|
-
lorem lacinia consectetur. Donec ut libero sed arcu vehicula ultricies a non tortor. Lorem ipsum dolor
|
|
47
|
-
sit amet, consectetur adipiscing elit.
|
|
48
|
-
</p>
|
|
49
|
-
</div>
|
|
50
|
-
</template>
|
|
51
|
-
<template #item-1>
|
|
52
|
-
<div>
|
|
53
|
-
<h3 class="page-heading-3 mbe-12">Column 2</h3>
|
|
54
|
-
<p class="page-body-normal">
|
|
55
|
-
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Donec a diam lectus. Sed sit amet ipsum
|
|
56
|
-
mauris. Maecenas congue ligula ac quam viverra nec consectetur ante hendrerit. Donec et mollis dolor.
|
|
57
|
-
Praesent et diam eget libero egestas mattis sit amet vitae augue. Nam tincidunt congue enim, ut porta
|
|
58
|
-
lorem lacinia consectetur. Donec ut libero sed arcu vehicula ultricies a non tortor. Lorem ipsum dolor
|
|
59
|
-
sit amet, consectetur adipiscing elit.
|
|
60
|
-
</p>
|
|
61
|
-
</div>
|
|
62
|
-
</template>
|
|
63
|
-
</LayoutGridByCols>
|
|
64
|
-
</ContentWidth>
|
|
65
|
-
|
|
66
|
-
<ContentWidth tag="div" :justify-content="'end'" :style-class-passthrough="['mbe-20']">
|
|
67
|
-
<h2 class="page-heading-2 mbe-18">Sample content (end)</h2>
|
|
68
|
-
<p class="page-body-normal">
|
|
69
|
-
Lorem ipsum odor amet, consectetuer adipiscing elit. Nec elementum maecenas placerat laoreet curae elit
|
|
70
|
-
convallis himenaeos. Tellus varius cursus convallis commodo suspendisse litora. Platea accumsan interdum
|
|
71
|
-
ultrices adipiscing molestie cras dui. Vehicula egestas nisi sagittis fames metus velit. Sodales blandit
|
|
72
|
-
nisi eu dis sit, ridiculus aliquam. Morbi tellus eu in penatibus torquent tortor. Platea gravida nam;
|
|
73
|
-
egestas enim nostra ultricies.
|
|
74
|
-
</p>
|
|
75
|
-
<p class="page-body-normal">
|
|
76
|
-
Mi nibh quisque taciti porta curabitur nostra volutpat. Habitant sodales arcu habitasse mi duis conubia leo
|
|
77
|
-
lacinia. Montes torquent sodales adipiscing; proin semper feugiat morbi ullamcorper praesent. Arcu luctus
|
|
78
|
-
tempor quam ligula vestibulum sapien faucibus ridiculus. Cursus consequat ultricies consectetur class
|
|
79
|
-
suscipit quisque convallis eget? Dignissim mattis luctus enim habitant porta pretium litora. Parturient
|
|
80
|
-
montes imperdiet massa; sollicitudin varius hac aptent. Eleifend parturient mattis tellus nisi a montes.
|
|
81
|
-
</p>
|
|
82
|
-
</ContentWidth>
|
|
83
|
-
</template>
|
|
84
|
-
</NuxtLayout>
|
|
85
|
-
</div>
|
|
86
|
-
</template>
|
|
87
|
-
|
|
88
|
-
<script setup lang="ts">
|
|
89
|
-
definePageMeta({
|
|
90
|
-
layout: false,
|
|
91
|
-
});
|
|
92
|
-
|
|
93
|
-
useHead({
|
|
94
|
-
title: "UI Content Width",
|
|
95
|
-
meta: [{ name: "description", content: "Examples of UI Component Content Width" }],
|
|
96
|
-
bodyAttrs: {
|
|
97
|
-
class: "content-width-page",
|
|
98
|
-
},
|
|
99
|
-
});
|
|
100
|
-
</script>
|
|
101
|
-
|
|
102
|
-
<style lang="css">
|
|
103
|
-
.content-width-page {
|
|
104
|
-
.layout-grid-by-cols {
|
|
105
|
-
.layout-grid-inner {
|
|
106
|
-
> * {
|
|
107
|
-
outline: 1px solid green;
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
@container layoutGrid (width >= 768px) {
|
|
111
|
-
> * {
|
|
112
|
-
outline: 1px solid purple;
|
|
113
|
-
}
|
|
114
|
-
}
|
|
115
|
-
}
|
|
116
|
-
}
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
.content-width-wrapper {
|
|
120
|
-
.content-width {
|
|
121
|
-
.content-width-inner {
|
|
122
|
-
outline: 1px solid orange;
|
|
123
|
-
}
|
|
124
|
-
}
|
|
125
|
-
}
|
|
126
|
-
</style>
|