@notionhive/contacts 0.1.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/bin/contacts.js +16 -0
- package/category.config.json +7 -0
- package/package.json +24 -0
- package/registry/contact-01.json +9 -0
- package/registry/contact-02.json +9 -0
- package/registry/contact-03.json +9 -0
- package/registry/contact-04.json +6 -0
- package/registry/contact-05.json +9 -0
- package/registry/contact-06.json +9 -0
- package/registry/contact-07.json +9 -0
- package/registry/contact-08.json +6 -0
- package/registry/contact-09.json +10 -0
- package/registry/contact-10.json +6 -0
- package/registry/index.json +88 -0
- package/templates/components/atoms/SafeImage/SafeImage.jsx +101 -0
- package/templates/components/atoms/SafeImage/index.js +1 -0
- package/templates/components/hooks/useCarousel.js +73 -0
- package/templates/components/organisms/Contact01/Contact01.jsx +363 -0
- package/templates/components/organisms/Contact01/Contact01.propTypes.js +105 -0
- package/templates/components/organisms/Contact01/index.js +1 -0
- package/templates/components/organisms/Contact02/Contact02.jsx +288 -0
- package/templates/components/organisms/Contact02/Contact02.propTypes.js +78 -0
- package/templates/components/organisms/Contact02/index.js +1 -0
- package/templates/components/organisms/Contact03/Contact03.jsx +94 -0
- package/templates/components/organisms/Contact03/Contact03.propTypes.js +25 -0
- package/templates/components/organisms/Contact03/index.js +1 -0
- package/templates/components/organisms/Contact04/Contact04.jsx +239 -0
- package/templates/components/organisms/Contact04/Contact04.propTypes.js +50 -0
- package/templates/components/organisms/Contact04/index.js +1 -0
- package/templates/components/organisms/Contact05/Contact05.jsx +272 -0
- package/templates/components/organisms/Contact05/Contact05.propTypes.js +49 -0
- package/templates/components/organisms/Contact05/index.js +1 -0
- package/templates/components/organisms/Contact06/Contact06.jsx +223 -0
- package/templates/components/organisms/Contact06/Contact06.propTypes.js +40 -0
- package/templates/components/organisms/Contact06/index.js +1 -0
- package/templates/components/organisms/Contact07/Contact07.jsx +348 -0
- package/templates/components/organisms/Contact07/Contact07.propTypes.js +92 -0
- package/templates/components/organisms/Contact07/index.js +1 -0
- package/templates/components/organisms/Contact08/Contact08.jsx +178 -0
- package/templates/components/organisms/Contact08/Contact08.propTypes.js +36 -0
- package/templates/components/organisms/Contact08/index.js +1 -0
- package/templates/components/organisms/Contact09/Contact09.jsx +345 -0
- package/templates/components/organisms/Contact09/Contact09.propTypes.js +96 -0
- package/templates/components/organisms/Contact09/index.js +1 -0
- package/templates/components/organisms/Contact10/Contact10.jsx +175 -0
- package/templates/components/organisms/Contact10/Contact10.propTypes.js +55 -0
- package/templates/components/organisms/Contact10/index.js +1 -0
- package/templates/public/contact/contact01/ellipse.svg +12 -0
- package/templates/public/contact/contact01/logo-accenture.svg +6 -0
- package/templates/public/contact/contact01/logo-amart.svg +12 -0
- package/templates/public/contact/contact01/logo-brand12.png +0 -0
- package/templates/public/contact/contact01/logo-brand5.png +0 -0
- package/templates/public/contact/contact01/logo-brand7.png +0 -0
- package/templates/public/contact/contact01/logo-brand9.svg +12 -0
- package/templates/public/contact/contact01/logo-great-eastern.png +0 -0
- package/templates/public/contact/contact01/logo-kenwood.png +0 -0
- package/templates/public/contact/contact01/logo-nissan.svg +12 -0
- package/templates/public/contact/contact01/logo-suncorp.png +0 -0
- package/templates/public/contact/contact01/logo-ticketmaster.png +0 -0
- package/templates/public/contact/contact01/logo-toyota.svg +5 -0
- package/templates/public/contact/contact02/map.jpg +0 -0
- package/templates/public/contact/contact03/bg.jpg +0 -0
- package/templates/public/contact/contact03/card-photo.jpg +0 -0
- package/templates/public/contact/contact05/hero.jpg +0 -0
- package/templates/public/contact/contact05/pattern.png +0 -0
- package/templates/public/contact/contact06/hero.jpg +0 -0
- package/templates/public/contact/contact07/hero.jpg +0 -0
- package/templates/public/contact/contact09/avatar.jpg +0 -0
|
@@ -0,0 +1,239 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { useState } from "react";
|
|
4
|
+
import {
|
|
5
|
+
contact04DefaultProps,
|
|
6
|
+
contact04PropTypes,
|
|
7
|
+
} from "./Contact04.propTypes";
|
|
8
|
+
|
|
9
|
+
const inputClassName =
|
|
10
|
+
"w-full bg-transparent py-2 text-lg text-[rgba(0,0,0,0.9)] outline-none transition-colors duration-200 ease-out placeholder:text-[rgba(0,0,0,0.4)] focus:border-[#007cff]";
|
|
11
|
+
|
|
12
|
+
function EastArrowIcon({ className = "" }) {
|
|
13
|
+
return (
|
|
14
|
+
<svg viewBox="0 0 24 24" fill="none" aria-hidden="true" className={className}>
|
|
15
|
+
<path
|
|
16
|
+
d="M5 12h14M13 6l6 6-6 6"
|
|
17
|
+
stroke="currentColor"
|
|
18
|
+
strokeWidth="1.5"
|
|
19
|
+
strokeLinecap="round"
|
|
20
|
+
strokeLinejoin="round"
|
|
21
|
+
/>
|
|
22
|
+
</svg>
|
|
23
|
+
);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function ChevronDownIcon({ className = "" }) {
|
|
27
|
+
return (
|
|
28
|
+
<svg viewBox="0 0 20 20" fill="none" aria-hidden="true" className={className}>
|
|
29
|
+
<path
|
|
30
|
+
d="M5 8l5 5 5-5"
|
|
31
|
+
stroke="currentColor"
|
|
32
|
+
strokeWidth="1.5"
|
|
33
|
+
strokeLinecap="round"
|
|
34
|
+
strokeLinejoin="round"
|
|
35
|
+
/>
|
|
36
|
+
</svg>
|
|
37
|
+
);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function UnderlineField({ label, required = false, children }) {
|
|
41
|
+
return (
|
|
42
|
+
<div className="flex w-full flex-col">
|
|
43
|
+
<label className="text-base leading-6 text-[#010623]">
|
|
44
|
+
{label}
|
|
45
|
+
{required ? "*" : ""}
|
|
46
|
+
</label>
|
|
47
|
+
<div className="border-b border-[rgba(0,0,0,0.15)]">{children}</div>
|
|
48
|
+
</div>
|
|
49
|
+
);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Contact04 — Brace PL split contact info + form.
|
|
54
|
+
* Figma node 26:15849 at 1920px.
|
|
55
|
+
*
|
|
56
|
+
* @param {object} props - See Contact04.propTypes.js.
|
|
57
|
+
*/
|
|
58
|
+
export function Contact04({
|
|
59
|
+
label = contact04DefaultProps.label,
|
|
60
|
+
headline = contact04DefaultProps.headline,
|
|
61
|
+
phoneLabel = contact04DefaultProps.phoneLabel,
|
|
62
|
+
phone = contact04DefaultProps.phone,
|
|
63
|
+
emailLabel = contact04DefaultProps.emailLabel,
|
|
64
|
+
email = contact04DefaultProps.email,
|
|
65
|
+
socialLinks = contact04DefaultProps.socialLinks,
|
|
66
|
+
formTitle = contact04DefaultProps.formTitle,
|
|
67
|
+
accountTypeOptions = contact04DefaultProps.accountTypeOptions,
|
|
68
|
+
submitText = contact04DefaultProps.submitText,
|
|
69
|
+
formData: controlledForm,
|
|
70
|
+
onFieldChange,
|
|
71
|
+
onSubmit,
|
|
72
|
+
className = "",
|
|
73
|
+
}) {
|
|
74
|
+
const [internalForm, setInternalForm] = useState({
|
|
75
|
+
fullName: "",
|
|
76
|
+
email: "",
|
|
77
|
+
phone: "",
|
|
78
|
+
accountType: "",
|
|
79
|
+
message: "",
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
const form = controlledForm ?? internalForm;
|
|
83
|
+
|
|
84
|
+
const setField = (field, value) => {
|
|
85
|
+
onFieldChange?.(field, value);
|
|
86
|
+
if (!controlledForm) {
|
|
87
|
+
setInternalForm((prev) => ({ ...prev, [field]: value }));
|
|
88
|
+
}
|
|
89
|
+
};
|
|
90
|
+
|
|
91
|
+
const handleSubmit = (event) => {
|
|
92
|
+
event.preventDefault();
|
|
93
|
+
onSubmit?.(form);
|
|
94
|
+
};
|
|
95
|
+
|
|
96
|
+
return (
|
|
97
|
+
<section
|
|
98
|
+
className={["relative w-full overflow-hidden bg-[#f7f8fa]", className]
|
|
99
|
+
.filter(Boolean)
|
|
100
|
+
.join(" ")}
|
|
101
|
+
data-contact="contact04"
|
|
102
|
+
>
|
|
103
|
+
<div className="relative mx-auto w-full max-w-[1920px] px-4 py-16 sm:px-6 md:px-10 lg:px-20 lg:py-24 xl:px-[160px] xl:py-28">
|
|
104
|
+
<div className="mx-auto flex w-full max-w-[1260px] flex-col gap-12 lg:flex-row lg:items-center lg:justify-between lg:gap-16 xl:gap-20">
|
|
105
|
+
{/* Left — contact info */}
|
|
106
|
+
<div className="flex w-full max-w-[490px] flex-col gap-12 lg:gap-[86px]">
|
|
107
|
+
<div className="flex flex-col gap-4">
|
|
108
|
+
<p className="text-lg font-bold leading-[22px] text-[#007cff]">{label}</p>
|
|
109
|
+
<h2 className="whitespace-pre-wrap font-serif text-4xl font-extralight leading-tight tracking-[-0.02em] text-[#010623] sm:text-5xl md:text-6xl lg:text-7xl xl:text-[104px] xl:leading-[108px] xl:tracking-[-2.08px]">
|
|
110
|
+
{headline}
|
|
111
|
+
</h2>
|
|
112
|
+
</div>
|
|
113
|
+
|
|
114
|
+
<div className="flex flex-col gap-12 lg:gap-[88px]">
|
|
115
|
+
<div className="flex flex-col gap-8 lg:gap-10">
|
|
116
|
+
<div className="flex flex-col gap-2">
|
|
117
|
+
<p className="text-sm font-bold leading-[18px] text-[#414c5e]">{phoneLabel}</p>
|
|
118
|
+
<a
|
|
119
|
+
href={`tel:${phone.replace(/\s/g, "")}`}
|
|
120
|
+
className="font-serif text-2xl leading-10 text-[#010623] transition-opacity duration-200 hover:opacity-80 sm:text-3xl lg:text-[32px]"
|
|
121
|
+
>
|
|
122
|
+
{phone}
|
|
123
|
+
</a>
|
|
124
|
+
</div>
|
|
125
|
+
<div className="flex flex-col gap-2">
|
|
126
|
+
<p className="text-sm font-bold leading-[18px] text-[#414c5e]">{emailLabel}</p>
|
|
127
|
+
<a
|
|
128
|
+
href={`mailto:${email}`}
|
|
129
|
+
className="font-serif text-2xl leading-10 text-[#010623] transition-opacity duration-200 hover:opacity-80 sm:text-3xl lg:text-[32px]"
|
|
130
|
+
>
|
|
131
|
+
{email}
|
|
132
|
+
</a>
|
|
133
|
+
</div>
|
|
134
|
+
</div>
|
|
135
|
+
|
|
136
|
+
{socialLinks?.length ? (
|
|
137
|
+
<div className="flex max-w-[220px] flex-col gap-6 lg:gap-8">
|
|
138
|
+
{socialLinks.map((link) => (
|
|
139
|
+
<a
|
|
140
|
+
key={link.label}
|
|
141
|
+
href={link.href ?? "#"}
|
|
142
|
+
className="flex items-center justify-between gap-4 font-serif text-2xl leading-10 text-[#010623] transition-opacity duration-200 hover:opacity-80 sm:text-3xl lg:text-[32px]"
|
|
143
|
+
>
|
|
144
|
+
<span>{link.label}</span>
|
|
145
|
+
<EastArrowIcon className="size-6 shrink-0" />
|
|
146
|
+
</a>
|
|
147
|
+
))}
|
|
148
|
+
</div>
|
|
149
|
+
) : null}
|
|
150
|
+
</div>
|
|
151
|
+
</div>
|
|
152
|
+
|
|
153
|
+
{/* Right — form card */}
|
|
154
|
+
<form
|
|
155
|
+
onSubmit={handleSubmit}
|
|
156
|
+
className="flex w-full max-w-[666px] flex-col gap-8 bg-white/90 p-6 backdrop-blur-[22px] sm:p-8 lg:gap-10 lg:p-10"
|
|
157
|
+
>
|
|
158
|
+
<h3 className="font-serif text-2xl font-light leading-tight tracking-[-0.01em] text-[#007cff] sm:text-3xl lg:text-[48px] lg:leading-[54px] lg:tracking-[-0.48px]">
|
|
159
|
+
{formTitle}
|
|
160
|
+
</h3>
|
|
161
|
+
|
|
162
|
+
<div className="flex flex-col gap-6 lg:gap-8">
|
|
163
|
+
<UnderlineField label="Full Name " required>
|
|
164
|
+
<input
|
|
165
|
+
type="text"
|
|
166
|
+
value={form.fullName}
|
|
167
|
+
onChange={(e) => setField("fullName", e.target.value)}
|
|
168
|
+
className={inputClassName}
|
|
169
|
+
required
|
|
170
|
+
/>
|
|
171
|
+
</UnderlineField>
|
|
172
|
+
|
|
173
|
+
<UnderlineField label="Email " required>
|
|
174
|
+
<input
|
|
175
|
+
type="email"
|
|
176
|
+
value={form.email}
|
|
177
|
+
onChange={(e) => setField("email", e.target.value)}
|
|
178
|
+
className={inputClassName}
|
|
179
|
+
required
|
|
180
|
+
/>
|
|
181
|
+
</UnderlineField>
|
|
182
|
+
|
|
183
|
+
<UnderlineField label="Phone" required>
|
|
184
|
+
<input
|
|
185
|
+
type="tel"
|
|
186
|
+
value={form.phone}
|
|
187
|
+
onChange={(e) => setField("phone", e.target.value)}
|
|
188
|
+
className={inputClassName}
|
|
189
|
+
required
|
|
190
|
+
/>
|
|
191
|
+
</UnderlineField>
|
|
192
|
+
|
|
193
|
+
<UnderlineField label="Account type ">
|
|
194
|
+
<div className="relative">
|
|
195
|
+
<select
|
|
196
|
+
value={form.accountType}
|
|
197
|
+
onChange={(e) => setField("accountType", e.target.value)}
|
|
198
|
+
className={`${inputClassName} appearance-none pr-8`}
|
|
199
|
+
>
|
|
200
|
+
<option value="" disabled>
|
|
201
|
+
Select account type
|
|
202
|
+
</option>
|
|
203
|
+
{accountTypeOptions.map((option) => (
|
|
204
|
+
<option key={option} value={option}>
|
|
205
|
+
{option}
|
|
206
|
+
</option>
|
|
207
|
+
))}
|
|
208
|
+
</select>
|
|
209
|
+
<ChevronDownIcon className="pointer-events-none absolute right-0 top-1/2 size-5 -translate-y-1/2 text-[#010623]" />
|
|
210
|
+
</div>
|
|
211
|
+
</UnderlineField>
|
|
212
|
+
|
|
213
|
+
<UnderlineField label="Message " required>
|
|
214
|
+
<textarea
|
|
215
|
+
value={form.message}
|
|
216
|
+
onChange={(e) => setField("message", e.target.value)}
|
|
217
|
+
rows={4}
|
|
218
|
+
className={`${inputClassName} min-h-[80px] resize-none pt-2 lg:min-h-[100px]`}
|
|
219
|
+
required
|
|
220
|
+
/>
|
|
221
|
+
</UnderlineField>
|
|
222
|
+
|
|
223
|
+
<button
|
|
224
|
+
type="submit"
|
|
225
|
+
className="w-max max-w-full rounded-sm bg-[#0058b5] px-8 py-6 text-lg text-white transition-colors duration-200 ease-out hover:opacity-90 focus-visible:outline-2 focus-visible:outline-offset-2"
|
|
226
|
+
>
|
|
227
|
+
{submitText}
|
|
228
|
+
</button>
|
|
229
|
+
</div>
|
|
230
|
+
</form>
|
|
231
|
+
</div>
|
|
232
|
+
</div>
|
|
233
|
+
</section>
|
|
234
|
+
);
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
Contact04.propTypes = contact04PropTypes;
|
|
238
|
+
|
|
239
|
+
export default Contact04;
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import PropTypes from "prop-types";
|
|
2
|
+
|
|
3
|
+
const socialLinkShape = PropTypes.shape({
|
|
4
|
+
label: PropTypes.string.isRequired,
|
|
5
|
+
href: PropTypes.string,
|
|
6
|
+
});
|
|
7
|
+
|
|
8
|
+
export const contact04PropTypes = {
|
|
9
|
+
label: PropTypes.string,
|
|
10
|
+
headline: PropTypes.string,
|
|
11
|
+
phoneLabel: PropTypes.string,
|
|
12
|
+
phone: PropTypes.string,
|
|
13
|
+
emailLabel: PropTypes.string,
|
|
14
|
+
email: PropTypes.string,
|
|
15
|
+
socialLinks: PropTypes.arrayOf(socialLinkShape),
|
|
16
|
+
formTitle: PropTypes.string,
|
|
17
|
+
accountTypeOptions: PropTypes.arrayOf(PropTypes.string),
|
|
18
|
+
submitText: PropTypes.string,
|
|
19
|
+
formData: PropTypes.shape({
|
|
20
|
+
fullName: PropTypes.string,
|
|
21
|
+
email: PropTypes.string,
|
|
22
|
+
phone: PropTypes.string,
|
|
23
|
+
accountType: PropTypes.string,
|
|
24
|
+
message: PropTypes.string,
|
|
25
|
+
}),
|
|
26
|
+
onFieldChange: PropTypes.func,
|
|
27
|
+
onSubmit: PropTypes.func,
|
|
28
|
+
className: PropTypes.string,
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
export const contact04DefaultProps = {
|
|
32
|
+
label: "Contact Us",
|
|
33
|
+
headline: "Get in touch with us",
|
|
34
|
+
phoneLabel: "Phone Number",
|
|
35
|
+
phone: "+88 02-9514-729-30",
|
|
36
|
+
emailLabel: "Email Address",
|
|
37
|
+
email: "info@bracepl.com",
|
|
38
|
+
socialLinks: [
|
|
39
|
+
{ label: "Facebook", href: "#" },
|
|
40
|
+
{ label: "Youtube", href: "#" },
|
|
41
|
+
],
|
|
42
|
+
formTitle: "Fill out this brief from, and we'll get in touch with you shortly.",
|
|
43
|
+
accountTypeOptions: [
|
|
44
|
+
"Individual",
|
|
45
|
+
"Business",
|
|
46
|
+
"Enterprise",
|
|
47
|
+
"Partner",
|
|
48
|
+
],
|
|
49
|
+
submitText: "Submit",
|
|
50
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { Contact04, default } from "./Contact04";
|
|
@@ -0,0 +1,272 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { useState } from "react";
|
|
4
|
+
import SafeImage from "../../atoms/SafeImage";
|
|
5
|
+
import {
|
|
6
|
+
contact05DefaultProps,
|
|
7
|
+
contact05PropTypes,
|
|
8
|
+
} from "./Contact05.propTypes";
|
|
9
|
+
|
|
10
|
+
const fieldClassName =
|
|
11
|
+
"h-[52px] w-full rounded-lg border border-white/30 bg-white/10 px-4 text-base text-white outline-none transition-colors duration-200 ease-out placeholder:text-white/60 focus:border-white focus:bg-white/15";
|
|
12
|
+
|
|
13
|
+
function ChevronDownIcon({ className = "" }) {
|
|
14
|
+
return (
|
|
15
|
+
<svg viewBox="0 0 20 20" fill="none" aria-hidden="true" className={className}>
|
|
16
|
+
<path
|
|
17
|
+
d="M5 8l5 5 5-5"
|
|
18
|
+
stroke="currentColor"
|
|
19
|
+
strokeWidth="1.5"
|
|
20
|
+
strokeLinecap="round"
|
|
21
|
+
strokeLinejoin="round"
|
|
22
|
+
/>
|
|
23
|
+
</svg>
|
|
24
|
+
);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function ArrowRightIcon({ className = "" }) {
|
|
28
|
+
return (
|
|
29
|
+
<svg viewBox="0 0 24 24" fill="none" aria-hidden="true" className={className}>
|
|
30
|
+
<path
|
|
31
|
+
d="M5 12h14M13 6l6 6-6 6"
|
|
32
|
+
stroke="currentColor"
|
|
33
|
+
strokeWidth="1.5"
|
|
34
|
+
strokeLinecap="round"
|
|
35
|
+
strokeLinejoin="round"
|
|
36
|
+
/>
|
|
37
|
+
</svg>
|
|
38
|
+
);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function PhoneIcon({ className = "" }) {
|
|
42
|
+
return (
|
|
43
|
+
<svg viewBox="0 0 24 24" fill="none" aria-hidden="true" className={className}>
|
|
44
|
+
<path
|
|
45
|
+
d="M6.5 3.5h3l1.5 4-2 1.5a11 11 0 005 5l1.5-2 4 1.5v3a1.5 1.5 0 01-1.5 1.5A14 14 0 013.5 5a1.5 1.5 0 011-1.5z"
|
|
46
|
+
stroke="currentColor"
|
|
47
|
+
strokeWidth="1.5"
|
|
48
|
+
strokeLinecap="round"
|
|
49
|
+
strokeLinejoin="round"
|
|
50
|
+
/>
|
|
51
|
+
</svg>
|
|
52
|
+
);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function FieldLabel({ children, required = false }) {
|
|
56
|
+
return (
|
|
57
|
+
<label className="text-sm font-medium tracking-[0.28px] text-white/90">
|
|
58
|
+
{children}
|
|
59
|
+
{required ? <span className="text-white"> *</span> : null}
|
|
60
|
+
</label>
|
|
61
|
+
);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Contact05 — Red freight quote split with hero image and patterned form panel.
|
|
66
|
+
* Figma node 26:17380 at 1920px.
|
|
67
|
+
*
|
|
68
|
+
* @param {object} props - See Contact05.propTypes.js.
|
|
69
|
+
*/
|
|
70
|
+
export function Contact05({
|
|
71
|
+
headline = contact05DefaultProps.headline,
|
|
72
|
+
description = contact05DefaultProps.description,
|
|
73
|
+
heroImage = contact05DefaultProps.heroImage,
|
|
74
|
+
heroAlt = contact05DefaultProps.heroAlt,
|
|
75
|
+
patternImage = contact05DefaultProps.patternImage,
|
|
76
|
+
serviceOptions = contact05DefaultProps.serviceOptions,
|
|
77
|
+
submitText = contact05DefaultProps.submitText,
|
|
78
|
+
callLabel = contact05DefaultProps.callLabel,
|
|
79
|
+
phoneNumber = contact05DefaultProps.phoneNumber,
|
|
80
|
+
phoneHref = contact05DefaultProps.phoneHref,
|
|
81
|
+
formData: controlledForm,
|
|
82
|
+
onFieldChange,
|
|
83
|
+
onSubmit,
|
|
84
|
+
className = "",
|
|
85
|
+
}) {
|
|
86
|
+
const [internalForm, setInternalForm] = useState({
|
|
87
|
+
serviceType: "",
|
|
88
|
+
organizationName: "",
|
|
89
|
+
fullName: "",
|
|
90
|
+
phone: "",
|
|
91
|
+
email: "",
|
|
92
|
+
message: "",
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
const form = controlledForm ?? internalForm;
|
|
96
|
+
|
|
97
|
+
const setField = (field, value) => {
|
|
98
|
+
onFieldChange?.(field, value);
|
|
99
|
+
if (!controlledForm) {
|
|
100
|
+
setInternalForm((prev) => ({ ...prev, [field]: value }));
|
|
101
|
+
}
|
|
102
|
+
};
|
|
103
|
+
|
|
104
|
+
const handleSubmit = (event) => {
|
|
105
|
+
event.preventDefault();
|
|
106
|
+
onSubmit?.(form);
|
|
107
|
+
};
|
|
108
|
+
|
|
109
|
+
return (
|
|
110
|
+
<section
|
|
111
|
+
className={["relative w-full overflow-hidden bg-white", className]
|
|
112
|
+
.filter(Boolean)
|
|
113
|
+
.join(" ")}
|
|
114
|
+
data-contact="contact05"
|
|
115
|
+
>
|
|
116
|
+
<div className="relative mx-auto flex w-full max-w-[1920px] flex-col lg:min-h-[700px] lg:flex-row xl:min-h-[900px]">
|
|
117
|
+
{/* Left — hero image */}
|
|
118
|
+
<div className="relative h-[50vh] w-full shrink-0 lg:h-auto lg:min-h-[700px] lg:w-1/2 xl:min-h-[900px]">
|
|
119
|
+
<SafeImage
|
|
120
|
+
src={heroImage}
|
|
121
|
+
alt={heroAlt}
|
|
122
|
+
fill
|
|
123
|
+
className="object-cover object-center"
|
|
124
|
+
sizes="(max-width: 1024px) 100vw, 50vw"
|
|
125
|
+
priority
|
|
126
|
+
/>
|
|
127
|
+
</div>
|
|
128
|
+
|
|
129
|
+
{/* Right — red form panel */}
|
|
130
|
+
<div className="relative flex w-full items-center bg-[#e6232d] lg:w-1/2 lg:min-h-[700px] xl:min-h-[900px]">
|
|
131
|
+
{patternImage ? (
|
|
132
|
+
<div className="pointer-events-none absolute inset-0 opacity-20" aria-hidden="true">
|
|
133
|
+
<SafeImage
|
|
134
|
+
src={patternImage}
|
|
135
|
+
alt=""
|
|
136
|
+
fill
|
|
137
|
+
className="object-cover object-center"
|
|
138
|
+
sizes="50vw"
|
|
139
|
+
/>
|
|
140
|
+
</div>
|
|
141
|
+
) : null}
|
|
142
|
+
|
|
143
|
+
<form
|
|
144
|
+
onSubmit={handleSubmit}
|
|
145
|
+
className="relative z-10 flex w-full flex-col gap-8 px-4 py-12 sm:px-6 md:px-10 lg:px-16 lg:py-16 xl:px-[80px] xl:py-20"
|
|
146
|
+
>
|
|
147
|
+
<div className="flex flex-col gap-4 text-white">
|
|
148
|
+
<h2 className="text-3xl font-bold leading-[1.1] tracking-[-0.02em] sm:text-4xl xl:text-[48px]">
|
|
149
|
+
{headline}
|
|
150
|
+
</h2>
|
|
151
|
+
<p className="text-base leading-[1.4] text-white/85 sm:text-lg">
|
|
152
|
+
{description}
|
|
153
|
+
</p>
|
|
154
|
+
</div>
|
|
155
|
+
|
|
156
|
+
<div className="flex flex-col gap-5">
|
|
157
|
+
<div className="flex flex-col gap-2">
|
|
158
|
+
<FieldLabel required>Service Type</FieldLabel>
|
|
159
|
+
<div className="relative">
|
|
160
|
+
<select
|
|
161
|
+
value={form.serviceType}
|
|
162
|
+
onChange={(e) => setField("serviceType", e.target.value)}
|
|
163
|
+
className={`${fieldClassName} appearance-none pr-12`}
|
|
164
|
+
required
|
|
165
|
+
>
|
|
166
|
+
<option value="" disabled className="text-[#1b1e2e]">
|
|
167
|
+
Select service
|
|
168
|
+
</option>
|
|
169
|
+
{serviceOptions.map((option) => (
|
|
170
|
+
<option key={option} value={option} className="text-[#1b1e2e]">
|
|
171
|
+
{option}
|
|
172
|
+
</option>
|
|
173
|
+
))}
|
|
174
|
+
</select>
|
|
175
|
+
<ChevronDownIcon className="pointer-events-none absolute right-4 top-1/2 size-5 -translate-y-1/2 text-white" />
|
|
176
|
+
</div>
|
|
177
|
+
</div>
|
|
178
|
+
|
|
179
|
+
<div className="flex flex-col gap-2">
|
|
180
|
+
<FieldLabel required>Organization Name</FieldLabel>
|
|
181
|
+
<input
|
|
182
|
+
type="text"
|
|
183
|
+
value={form.organizationName}
|
|
184
|
+
onChange={(e) => setField("organizationName", e.target.value)}
|
|
185
|
+
className={fieldClassName}
|
|
186
|
+
required
|
|
187
|
+
/>
|
|
188
|
+
</div>
|
|
189
|
+
|
|
190
|
+
<div className="grid grid-cols-1 gap-5 sm:grid-cols-2">
|
|
191
|
+
<div className="flex flex-col gap-2">
|
|
192
|
+
<FieldLabel required>Full Name</FieldLabel>
|
|
193
|
+
<input
|
|
194
|
+
type="text"
|
|
195
|
+
value={form.fullName}
|
|
196
|
+
onChange={(e) => setField("fullName", e.target.value)}
|
|
197
|
+
className={fieldClassName}
|
|
198
|
+
required
|
|
199
|
+
/>
|
|
200
|
+
</div>
|
|
201
|
+
<div className="flex flex-col gap-2">
|
|
202
|
+
<FieldLabel required>Phone</FieldLabel>
|
|
203
|
+
<input
|
|
204
|
+
type="tel"
|
|
205
|
+
value={form.phone}
|
|
206
|
+
onChange={(e) => setField("phone", e.target.value)}
|
|
207
|
+
className={fieldClassName}
|
|
208
|
+
required
|
|
209
|
+
/>
|
|
210
|
+
</div>
|
|
211
|
+
</div>
|
|
212
|
+
|
|
213
|
+
<div className="flex flex-col gap-2">
|
|
214
|
+
<FieldLabel required>Email</FieldLabel>
|
|
215
|
+
<input
|
|
216
|
+
type="email"
|
|
217
|
+
value={form.email}
|
|
218
|
+
onChange={(e) => setField("email", e.target.value)}
|
|
219
|
+
className={fieldClassName}
|
|
220
|
+
required
|
|
221
|
+
/>
|
|
222
|
+
</div>
|
|
223
|
+
|
|
224
|
+
<div className="flex flex-col gap-2">
|
|
225
|
+
<FieldLabel>Message</FieldLabel>
|
|
226
|
+
<textarea
|
|
227
|
+
value={form.message}
|
|
228
|
+
onChange={(e) => setField("message", e.target.value)}
|
|
229
|
+
rows={4}
|
|
230
|
+
className="min-h-[120px] w-full resize-none rounded-lg border border-white/30 bg-white/10 px-4 py-3 text-base text-white outline-none transition-colors duration-200 ease-out placeholder:text-white/60 focus:border-white focus:bg-white/15"
|
|
231
|
+
/>
|
|
232
|
+
</div>
|
|
233
|
+
</div>
|
|
234
|
+
|
|
235
|
+
<button
|
|
236
|
+
type="submit"
|
|
237
|
+
className="flex h-14 w-full items-center justify-center gap-2 rounded-lg bg-white text-base font-semibold tracking-[0.32px] text-[#e6232d] transition-colors duration-200 ease-out hover:bg-white/90 focus-visible:outline-2 focus-visible:outline-offset-2"
|
|
238
|
+
>
|
|
239
|
+
{submitText}
|
|
240
|
+
<ArrowRightIcon className="size-5" />
|
|
241
|
+
</button>
|
|
242
|
+
|
|
243
|
+
<div className="flex items-center gap-4">
|
|
244
|
+
<div className="h-px flex-1 bg-white/40" />
|
|
245
|
+
<span className="text-sm font-medium uppercase tracking-[0.96px] text-white/80">
|
|
246
|
+
OR
|
|
247
|
+
</span>
|
|
248
|
+
<div className="h-px flex-1 bg-white/40" />
|
|
249
|
+
</div>
|
|
250
|
+
|
|
251
|
+
<a
|
|
252
|
+
href={phoneHref}
|
|
253
|
+
className="flex items-center justify-center gap-3 text-white transition-opacity duration-200 ease-out hover:opacity-90 focus-visible:outline-2 focus-visible:outline-offset-2"
|
|
254
|
+
>
|
|
255
|
+
<PhoneIcon className="size-6 shrink-0" />
|
|
256
|
+
<span className="flex flex-col items-start sm:flex-row sm:items-center sm:gap-2">
|
|
257
|
+
<span className="text-sm font-semibold uppercase tracking-[0.96px]">
|
|
258
|
+
{callLabel}
|
|
259
|
+
</span>
|
|
260
|
+
<span className="text-lg font-bold sm:text-xl">{phoneNumber}</span>
|
|
261
|
+
</span>
|
|
262
|
+
</a>
|
|
263
|
+
</form>
|
|
264
|
+
</div>
|
|
265
|
+
</div>
|
|
266
|
+
</section>
|
|
267
|
+
);
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
Contact05.propTypes = contact05PropTypes;
|
|
271
|
+
|
|
272
|
+
export default Contact05;
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import PropTypes from "prop-types";
|
|
2
|
+
|
|
3
|
+
export const contact05PropTypes = {
|
|
4
|
+
headline: PropTypes.string,
|
|
5
|
+
description: PropTypes.string,
|
|
6
|
+
heroImage: PropTypes.string,
|
|
7
|
+
heroAlt: PropTypes.string,
|
|
8
|
+
patternImage: PropTypes.string,
|
|
9
|
+
serviceOptions: PropTypes.arrayOf(PropTypes.string),
|
|
10
|
+
submitText: PropTypes.string,
|
|
11
|
+
callLabel: PropTypes.string,
|
|
12
|
+
phoneNumber: PropTypes.string,
|
|
13
|
+
phoneHref: PropTypes.string,
|
|
14
|
+
formData: PropTypes.shape({
|
|
15
|
+
serviceType: PropTypes.string,
|
|
16
|
+
organizationName: PropTypes.string,
|
|
17
|
+
fullName: PropTypes.string,
|
|
18
|
+
phone: PropTypes.string,
|
|
19
|
+
email: PropTypes.string,
|
|
20
|
+
message: PropTypes.string,
|
|
21
|
+
}),
|
|
22
|
+
onFieldChange: PropTypes.func,
|
|
23
|
+
onSubmit: PropTypes.func,
|
|
24
|
+
className: PropTypes.string,
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
export const contact05DefaultProps = {
|
|
28
|
+
headline: "Request an Expedited Freight Quote Now",
|
|
29
|
+
description:
|
|
30
|
+
"Tell us about your shipment and our logistics team will respond with a tailored quote within one business day.",
|
|
31
|
+
heroImage: "/contact/contact05/hero.jpg",
|
|
32
|
+
heroAlt: "Freight truck on highway at sunset",
|
|
33
|
+
patternImage: "/contact/contact05/pattern.png",
|
|
34
|
+
serviceOptions: [
|
|
35
|
+
"Full truckload (FTL)",
|
|
36
|
+
"Less than truckload (LTL)",
|
|
37
|
+
"Intermodal",
|
|
38
|
+
"Expedited freight",
|
|
39
|
+
"International shipping",
|
|
40
|
+
],
|
|
41
|
+
submitText: "Send Message",
|
|
42
|
+
callLabel: "CALL US",
|
|
43
|
+
phoneNumber: "+ (880) 2-2222-94745",
|
|
44
|
+
phoneHref: "tel:+8802222294745",
|
|
45
|
+
formData: undefined,
|
|
46
|
+
onFieldChange: undefined,
|
|
47
|
+
onSubmit: undefined,
|
|
48
|
+
className: "",
|
|
49
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { Contact05, default } from "./Contact05";
|