@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.
Files changed (68) hide show
  1. package/bin/contacts.js +16 -0
  2. package/category.config.json +7 -0
  3. package/package.json +24 -0
  4. package/registry/contact-01.json +9 -0
  5. package/registry/contact-02.json +9 -0
  6. package/registry/contact-03.json +9 -0
  7. package/registry/contact-04.json +6 -0
  8. package/registry/contact-05.json +9 -0
  9. package/registry/contact-06.json +9 -0
  10. package/registry/contact-07.json +9 -0
  11. package/registry/contact-08.json +6 -0
  12. package/registry/contact-09.json +10 -0
  13. package/registry/contact-10.json +6 -0
  14. package/registry/index.json +88 -0
  15. package/templates/components/atoms/SafeImage/SafeImage.jsx +101 -0
  16. package/templates/components/atoms/SafeImage/index.js +1 -0
  17. package/templates/components/hooks/useCarousel.js +73 -0
  18. package/templates/components/organisms/Contact01/Contact01.jsx +363 -0
  19. package/templates/components/organisms/Contact01/Contact01.propTypes.js +105 -0
  20. package/templates/components/organisms/Contact01/index.js +1 -0
  21. package/templates/components/organisms/Contact02/Contact02.jsx +288 -0
  22. package/templates/components/organisms/Contact02/Contact02.propTypes.js +78 -0
  23. package/templates/components/organisms/Contact02/index.js +1 -0
  24. package/templates/components/organisms/Contact03/Contact03.jsx +94 -0
  25. package/templates/components/organisms/Contact03/Contact03.propTypes.js +25 -0
  26. package/templates/components/organisms/Contact03/index.js +1 -0
  27. package/templates/components/organisms/Contact04/Contact04.jsx +239 -0
  28. package/templates/components/organisms/Contact04/Contact04.propTypes.js +50 -0
  29. package/templates/components/organisms/Contact04/index.js +1 -0
  30. package/templates/components/organisms/Contact05/Contact05.jsx +272 -0
  31. package/templates/components/organisms/Contact05/Contact05.propTypes.js +49 -0
  32. package/templates/components/organisms/Contact05/index.js +1 -0
  33. package/templates/components/organisms/Contact06/Contact06.jsx +223 -0
  34. package/templates/components/organisms/Contact06/Contact06.propTypes.js +40 -0
  35. package/templates/components/organisms/Contact06/index.js +1 -0
  36. package/templates/components/organisms/Contact07/Contact07.jsx +348 -0
  37. package/templates/components/organisms/Contact07/Contact07.propTypes.js +92 -0
  38. package/templates/components/organisms/Contact07/index.js +1 -0
  39. package/templates/components/organisms/Contact08/Contact08.jsx +178 -0
  40. package/templates/components/organisms/Contact08/Contact08.propTypes.js +36 -0
  41. package/templates/components/organisms/Contact08/index.js +1 -0
  42. package/templates/components/organisms/Contact09/Contact09.jsx +345 -0
  43. package/templates/components/organisms/Contact09/Contact09.propTypes.js +96 -0
  44. package/templates/components/organisms/Contact09/index.js +1 -0
  45. package/templates/components/organisms/Contact10/Contact10.jsx +175 -0
  46. package/templates/components/organisms/Contact10/Contact10.propTypes.js +55 -0
  47. package/templates/components/organisms/Contact10/index.js +1 -0
  48. package/templates/public/contact/contact01/ellipse.svg +12 -0
  49. package/templates/public/contact/contact01/logo-accenture.svg +6 -0
  50. package/templates/public/contact/contact01/logo-amart.svg +12 -0
  51. package/templates/public/contact/contact01/logo-brand12.png +0 -0
  52. package/templates/public/contact/contact01/logo-brand5.png +0 -0
  53. package/templates/public/contact/contact01/logo-brand7.png +0 -0
  54. package/templates/public/contact/contact01/logo-brand9.svg +12 -0
  55. package/templates/public/contact/contact01/logo-great-eastern.png +0 -0
  56. package/templates/public/contact/contact01/logo-kenwood.png +0 -0
  57. package/templates/public/contact/contact01/logo-nissan.svg +12 -0
  58. package/templates/public/contact/contact01/logo-suncorp.png +0 -0
  59. package/templates/public/contact/contact01/logo-ticketmaster.png +0 -0
  60. package/templates/public/contact/contact01/logo-toyota.svg +5 -0
  61. package/templates/public/contact/contact02/map.jpg +0 -0
  62. package/templates/public/contact/contact03/bg.jpg +0 -0
  63. package/templates/public/contact/contact03/card-photo.jpg +0 -0
  64. package/templates/public/contact/contact05/hero.jpg +0 -0
  65. package/templates/public/contact/contact05/pattern.png +0 -0
  66. package/templates/public/contact/contact06/hero.jpg +0 -0
  67. package/templates/public/contact/contact07/hero.jpg +0 -0
  68. package/templates/public/contact/contact09/avatar.jpg +0 -0
@@ -0,0 +1,363 @@
1
+ "use client";
2
+
3
+ import { useId, useState } from "react";
4
+ import SafeImage from "../../atoms/SafeImage";
5
+ import {
6
+ contact01DefaultProps,
7
+ contact01PropTypes,
8
+ } from "./Contact01.propTypes";
9
+
10
+ const inputClassName =
11
+ "h-[52px] w-full rounded-full border border-[#ededee] bg-[#f9f9fa] px-5 text-base text-[#1b1e2e] outline-none transition-colors duration-200 ease-out focus:border-[#020617]";
12
+
13
+ function ChevronDownIcon({ className = "" }) {
14
+ return (
15
+ <svg viewBox="0 0 24 24" fill="none" aria-hidden="true" className={className}>
16
+ <path
17
+ d="M8 10l4 4 4-4"
18
+ stroke="currentColor"
19
+ strokeWidth="1.5"
20
+ strokeLinecap="round"
21
+ strokeLinejoin="round"
22
+ />
23
+ </svg>
24
+ );
25
+ }
26
+
27
+ function PlusIcon({ className = "" }) {
28
+ return (
29
+ <svg viewBox="0 0 24 24" fill="none" aria-hidden="true" className={className}>
30
+ <path d="M12 5v14M5 12h14" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" />
31
+ </svg>
32
+ );
33
+ }
34
+
35
+ function MinusIcon({ className = "" }) {
36
+ return (
37
+ <svg viewBox="0 0 24 24" fill="none" aria-hidden="true" className={className}>
38
+ <path d="M5 12h14" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" />
39
+ </svg>
40
+ );
41
+ }
42
+
43
+ function FieldLabel({ children, required = false }) {
44
+ return (
45
+ <label className="px-5 text-base font-medium tracking-[0.32px] text-[#7e8088]">
46
+ {children}
47
+ {required ? <span className="text-[#ff373c]"> *</span> : null}
48
+ </label>
49
+ );
50
+ }
51
+
52
+ /**
53
+ * Contact01 — Platform One contact hero with form, partner logos, and FAQ.
54
+ * Figma node 26:15508 at 1920px.
55
+ *
56
+ * @param {object} props - See Contact01.propTypes.js.
57
+ */
58
+ export function Contact01({
59
+ headline = contact01DefaultProps.headline,
60
+ description = contact01DefaultProps.description,
61
+ officeTitle = contact01DefaultProps.officeTitle,
62
+ office = contact01DefaultProps.office,
63
+ purposeOptions = contact01DefaultProps.purposeOptions,
64
+ privacyHref = contact01DefaultProps.privacyHref,
65
+ submitText = contact01DefaultProps.submitText,
66
+ partnersTitle = contact01DefaultProps.partnersTitle,
67
+ partnerLogos = contact01DefaultProps.partnerLogos,
68
+ faqTitle = contact01DefaultProps.faqTitle,
69
+ faqItems = contact01DefaultProps.faqItems,
70
+ defaultOpenFaq = contact01DefaultProps.defaultOpenFaq,
71
+ activeFaq: controlledFaq,
72
+ onFaqChange,
73
+ formData: controlledForm,
74
+ onFieldChange,
75
+ onSubmit,
76
+ showPartners = contact01DefaultProps.showPartners,
77
+ showFaq = contact01DefaultProps.showFaq,
78
+ className = "",
79
+ }) {
80
+ const [internalFaq, setInternalFaq] = useState(defaultOpenFaq);
81
+ const [internalForm, setInternalForm] = useState({
82
+ firstName: "",
83
+ lastName: "",
84
+ email: "",
85
+ company: "",
86
+ purpose: "",
87
+ bio: "",
88
+ agreed: false,
89
+ });
90
+
91
+ const marqueeName = `contact01-marquee-${useId().replace(/[^a-zA-Z0-9]/g, "")}`;
92
+
93
+ const activeFaq = controlledFaq ?? internalFaq;
94
+ const form = controlledForm ?? internalForm;
95
+
96
+ const setField = (field, value) => {
97
+ onFieldChange?.(field, value);
98
+ if (!controlledForm) {
99
+ setInternalForm((prev) => ({ ...prev, [field]: value }));
100
+ }
101
+ };
102
+
103
+ const toggleFaq = (index) => {
104
+ const next = activeFaq === index ? -1 : index;
105
+ onFaqChange?.(next);
106
+ if (controlledFaq === undefined) setInternalFaq(next);
107
+ };
108
+
109
+ const handleSubmit = (event) => {
110
+ event.preventDefault();
111
+ onSubmit?.(form);
112
+ };
113
+
114
+ return (
115
+ <section
116
+ className={["relative w-full overflow-hidden bg-white", className]
117
+ .filter(Boolean)
118
+ .join(" ")}
119
+ data-contact="contact01"
120
+ >
121
+ {/* Hero — form + copy */}
122
+ <div className="relative mx-auto w-full max-w-[1920px] overflow-hidden px-4 py-12 sm:px-6 md:px-10 lg:px-20 lg:py-20 xl:px-[160px] xl:py-[84px]">
123
+ <div
124
+ className="pointer-events-none absolute left-0 top-1/2 size-[min(90vw,493px)] -translate-y-1/2 lg:left-[55px] lg:top-[447px] lg:size-[493px] lg:translate-y-0 xl:left-[55px]"
125
+ aria-hidden="true"
126
+ >
127
+ <div className="absolute inset-[-171.2%]">
128
+ {/* eslint-disable-next-line @next/next/no-img-element */}
129
+ <img
130
+ src="/contact/contact01/ellipse.svg"
131
+ alt=""
132
+ className="block size-full max-w-none"
133
+ />
134
+ </div>
135
+ </div>
136
+
137
+ <div className="relative z-10 mx-auto flex w-full max-w-[1330px] flex-col gap-12 lg:flex-row lg:items-start lg:justify-between lg:gap-16 xl:gap-[83px]">
138
+ {/* Form card */}
139
+ <form
140
+ onSubmit={handleSubmit}
141
+ className="w-full max-w-[600px] shrink-0 rounded-2xl bg-white px-4 py-5 shadow-sm lg:px-4 lg:py-5"
142
+ >
143
+ <div className="flex flex-col gap-5">
144
+ <div className="grid grid-cols-1 gap-5 sm:grid-cols-2">
145
+ <div className="flex flex-col gap-2.5">
146
+ <FieldLabel required>First Name</FieldLabel>
147
+ <input
148
+ type="text"
149
+ value={form.firstName}
150
+ onChange={(e) => setField("firstName", e.target.value)}
151
+ className={inputClassName}
152
+ required
153
+ />
154
+ </div>
155
+ <div className="flex flex-col gap-2.5">
156
+ <FieldLabel required>Last Name</FieldLabel>
157
+ <input
158
+ type="text"
159
+ value={form.lastName}
160
+ onChange={(e) => setField("lastName", e.target.value)}
161
+ className={inputClassName}
162
+ required
163
+ />
164
+ </div>
165
+ </div>
166
+
167
+ <div className="flex flex-col gap-2.5">
168
+ <FieldLabel required>Work Email</FieldLabel>
169
+ <input
170
+ type="email"
171
+ value={form.email}
172
+ onChange={(e) => setField("email", e.target.value)}
173
+ className={inputClassName}
174
+ required
175
+ />
176
+ </div>
177
+
178
+ <div className="flex flex-col gap-2.5">
179
+ <FieldLabel required>Company Name</FieldLabel>
180
+ <input
181
+ type="text"
182
+ value={form.company}
183
+ onChange={(e) => setField("company", e.target.value)}
184
+ className={inputClassName}
185
+ required
186
+ />
187
+ </div>
188
+
189
+ <div className="flex flex-col gap-2.5">
190
+ <FieldLabel required>Contact Purpose</FieldLabel>
191
+ <div className="relative">
192
+ <select
193
+ value={form.purpose}
194
+ onChange={(e) => setField("purpose", e.target.value)}
195
+ className={`${inputClassName} appearance-none pr-12`}
196
+ required
197
+ >
198
+ <option value="" disabled>
199
+ Select purpose
200
+ </option>
201
+ {purposeOptions.map((option) => (
202
+ <option key={option} value={option}>
203
+ {option}
204
+ </option>
205
+ ))}
206
+ </select>
207
+ <ChevronDownIcon className="pointer-events-none absolute right-5 top-1/2 size-6 -translate-y-1/2 text-[#1b1e2e]" />
208
+ </div>
209
+ </div>
210
+
211
+ <div className="flex flex-col gap-2.5">
212
+ <FieldLabel>Short Bio (Optional)</FieldLabel>
213
+ <textarea
214
+ value={form.bio}
215
+ onChange={(e) => setField("bio", e.target.value)}
216
+ rows={5}
217
+ className="min-h-[140px] w-full resize-none rounded-2xl border border-[#ededee] bg-[#f9f9fa] px-5 py-3.5 text-base text-[#1b1e2e] outline-none transition-colors duration-200 ease-out focus:border-[#020617]"
218
+ />
219
+ </div>
220
+
221
+ <label className="flex cursor-pointer items-center gap-1.5 text-sm tracking-[0.28px] text-[#1b1e2e]">
222
+ <input
223
+ type="checkbox"
224
+ checked={form.agreed}
225
+ onChange={(e) => setField("agreed", e.target.checked)}
226
+ className="size-4 rounded border-[#ededee] accent-[#020617]"
227
+ />
228
+ <span>
229
+ You agree with our{" "}
230
+ <a
231
+ href={privacyHref}
232
+ className="underline transition-opacity duration-200 hover:opacity-80"
233
+ >
234
+ Privacy Policy.
235
+ </a>
236
+ </span>
237
+ </label>
238
+
239
+ <button
240
+ type="submit"
241
+ className="h-14 w-full rounded-full bg-[#020617] text-base font-medium tracking-[0.32px] text-white transition-colors duration-200 ease-out hover:bg-[#1b1e2e] focus-visible:outline-2 focus-visible:outline-offset-2"
242
+ >
243
+ {submitText}
244
+ </button>
245
+ </div>
246
+ </form>
247
+
248
+ {/* Copy + office info */}
249
+ <div className="flex w-full max-w-[657px] flex-col gap-10 lg:gap-[70px]">
250
+ <div className="flex flex-col gap-6 lg:gap-10">
251
+ <h2 className="text-4xl font-normal leading-[1.1] tracking-[-0.02em] text-[#020617] sm:text-5xl lg:text-[70px] lg:tracking-[-1.4px]">
252
+ {headline}
253
+ </h2>
254
+ <p className="text-base leading-[1.2] text-[#1b1e2e] sm:text-lg">
255
+ {description}
256
+ </p>
257
+ </div>
258
+
259
+ <div className="flex flex-col gap-5">
260
+ <h3 className="text-xl font-medium tracking-[-0.48px] text-[#020617] sm:text-2xl">
261
+ {officeTitle}
262
+ </h3>
263
+ <div className="flex flex-col gap-2 text-base leading-[1.2] text-[#1b1e2e] sm:text-lg">
264
+ {office?.label ? <p>{office.label}</p> : null}
265
+ {office?.address ? <p>{office.address}</p> : null}
266
+ {office?.phone ? <p>{office.phone}</p> : null}
267
+ </div>
268
+ </div>
269
+ </div>
270
+ </div>
271
+ </div>
272
+
273
+ {/* Partner logos */}
274
+ {showPartners && partnerLogos?.length ? (
275
+ <div className="border-t border-[#ededee] bg-white py-8 lg:py-9">
276
+ <p className="mb-2.5 text-center text-lg leading-[1.3] text-[#020617] sm:text-xl">
277
+ {partnersTitle}
278
+ </p>
279
+ <div className="relative overflow-hidden">
280
+ <style>{`
281
+ @keyframes ${marqueeName} {
282
+ from { transform: translateX(0); }
283
+ to { transform: translateX(-50%); }
284
+ }
285
+ `}</style>
286
+ <div
287
+ className="flex w-max gap-0 motion-reduce:transform-none"
288
+ style={{ animation: `${marqueeName} 30s linear infinite` }}
289
+ >
290
+ {[...partnerLogos, ...partnerLogos].map((logo, index) => (
291
+ <div
292
+ key={`${logo.src}-${index}`}
293
+ className="flex h-[110px] w-[200px] shrink-0 items-center justify-center px-6"
294
+ >
295
+ <SafeImage
296
+ src={logo.src}
297
+ alt={logo.alt ?? ""}
298
+ width={120}
299
+ height={60}
300
+ className="h-auto max-h-[60px] w-auto max-w-[120px] object-contain"
301
+ />
302
+ </div>
303
+ ))}
304
+ </div>
305
+ </div>
306
+ </div>
307
+ ) : null}
308
+
309
+ {/* FAQ */}
310
+ {showFaq && faqItems?.length ? (
311
+ <div className="bg-white px-4 py-16 sm:px-6 md:px-10 lg:px-20 lg:py-24 xl:px-[295px]">
312
+ <div className="mx-auto flex w-full max-w-[1330px] flex-col gap-10 lg:flex-row lg:gap-4">
313
+ <h2 className="w-full max-w-[522px] shrink-0 text-3xl leading-[1.1] text-[#020617] sm:text-4xl lg:text-[48px]">
314
+ {faqTitle}
315
+ </h2>
316
+
317
+ <div className="flex min-w-0 flex-1 flex-col gap-4">
318
+ {faqItems.map((item, index) => {
319
+ const isOpen = activeFaq === index;
320
+ return (
321
+ <div
322
+ key={item.question}
323
+ className={[
324
+ "rounded-2xl border border-[#ededee] p-6 transition-colors duration-200",
325
+ isOpen ? "bg-[#f9f9fa]" : "bg-white",
326
+ ].join(" ")}
327
+ >
328
+ <button
329
+ type="button"
330
+ onClick={() => toggleFaq(index)}
331
+ className="flex w-full items-center justify-between gap-4 text-left"
332
+ aria-expanded={isOpen}
333
+ >
334
+ <span className="text-lg font-medium leading-[1.2] tracking-[-0.48px] text-[#020617] sm:text-2xl">
335
+ {item.question}
336
+ </span>
337
+ <span className="shrink-0 text-[#020617]">
338
+ {isOpen ? (
339
+ <MinusIcon className="size-6" />
340
+ ) : (
341
+ <PlusIcon className="size-6" />
342
+ )}
343
+ </span>
344
+ </button>
345
+ {isOpen && item.answer ? (
346
+ <p className="mt-[30px] text-base leading-[1.2] text-[#343744] sm:text-lg">
347
+ {item.answer}
348
+ </p>
349
+ ) : null}
350
+ </div>
351
+ );
352
+ })}
353
+ </div>
354
+ </div>
355
+ </div>
356
+ ) : null}
357
+ </section>
358
+ );
359
+ }
360
+
361
+ Contact01.propTypes = contact01PropTypes;
362
+
363
+ export default Contact01;
@@ -0,0 +1,105 @@
1
+ import PropTypes from "prop-types";
2
+
3
+ const officeShape = PropTypes.shape({
4
+ label: PropTypes.string,
5
+ address: PropTypes.string,
6
+ phone: PropTypes.string,
7
+ });
8
+
9
+ const faqItemShape = PropTypes.shape({
10
+ question: PropTypes.string.isRequired,
11
+ answer: PropTypes.string,
12
+ });
13
+
14
+ const logoShape = PropTypes.shape({
15
+ src: PropTypes.string.isRequired,
16
+ alt: PropTypes.string,
17
+ });
18
+
19
+ export const contact01PropTypes = {
20
+ headline: PropTypes.string,
21
+ description: PropTypes.string,
22
+ officeTitle: PropTypes.string,
23
+ office: officeShape,
24
+ purposeOptions: PropTypes.arrayOf(PropTypes.string),
25
+ privacyHref: PropTypes.string,
26
+ submitText: PropTypes.string,
27
+ partnersTitle: PropTypes.string,
28
+ partnerLogos: PropTypes.arrayOf(logoShape),
29
+ faqTitle: PropTypes.string,
30
+ faqItems: PropTypes.arrayOf(faqItemShape),
31
+ defaultOpenFaq: PropTypes.number,
32
+ activeFaq: PropTypes.number,
33
+ onFaqChange: PropTypes.func,
34
+ formData: PropTypes.shape({
35
+ firstName: PropTypes.string,
36
+ lastName: PropTypes.string,
37
+ email: PropTypes.string,
38
+ company: PropTypes.string,
39
+ purpose: PropTypes.string,
40
+ bio: PropTypes.string,
41
+ agreed: PropTypes.bool,
42
+ }),
43
+ onFieldChange: PropTypes.func,
44
+ onSubmit: PropTypes.func,
45
+ showPartners: PropTypes.bool,
46
+ showFaq: PropTypes.bool,
47
+ className: PropTypes.string,
48
+ };
49
+
50
+ export const contact01DefaultProps = {
51
+ headline: "Discuss Your Business Goals with Us",
52
+ description:
53
+ "We’d love to hear from you. Whether you’re exploring your first CX program, scaling globally, or looking for strategic guidance, our team is here to help.",
54
+ officeTitle: "Office Info",
55
+ office: {
56
+ label: "Australia – HQ",
57
+ address: "Level 9, 100 Walker Street, North Sydney, NSW 2060",
58
+ phone: "+61 2 9900 9999",
59
+ },
60
+ purposeOptions: [
61
+ "General inquiry",
62
+ "Partnership",
63
+ "Product demo",
64
+ "Support",
65
+ ],
66
+ privacyHref: "#",
67
+ submitText: "Lets Connect Together",
68
+ partnersTitle: "Trusted By Global Partenrs",
69
+ partnerLogos: [
70
+ { src: "/contact/contact01/logo-nissan.svg", alt: "Nissan" },
71
+ { src: "/contact/contact01/logo-amart.svg", alt: "Amart" },
72
+ { src: "/contact/contact01/logo-toyota.svg", alt: "Toyota" },
73
+ { src: "/contact/contact01/logo-great-eastern.png", alt: "Great Eastern" },
74
+ { src: "/contact/contact01/logo-brand5.png", alt: "Partner" },
75
+ { src: "/contact/contact01/logo-kenwood.png", alt: "Kenwood" },
76
+ { src: "/contact/contact01/logo-brand7.png", alt: "Partner" },
77
+ { src: "/contact/contact01/logo-ticketmaster.png", alt: "Ticketmaster" },
78
+ { src: "/contact/contact01/logo-brand9.svg", alt: "Partner" },
79
+ { src: "/contact/contact01/logo-suncorp.png", alt: "Suncorp" },
80
+ { src: "/contact/contact01/logo-accenture.svg", alt: "Accenture" },
81
+ { src: "/contact/contact01/logo-brand12.png", alt: "Partner" },
82
+ ],
83
+ faqTitle: "Frequently Asked Questions",
84
+ faqItems: [
85
+ {
86
+ question: "1. What is Platform One?",
87
+ answer:
88
+ "Platform One is an experience intelligence platform that unifies customer, employee, product, and brand feedback into a single, actionable view. We combine AI-powered analytics with human expertise to help organizations make smarter, faster, and more human-centered decisions.",
89
+ },
90
+ { question: "2. Who is Platform One for?", answer: "" },
91
+ {
92
+ question: "3. How is Platform One different from other CX tools?",
93
+ answer: "",
94
+ },
95
+ {
96
+ question: "4. Can Platform One integrate with my existing systems?",
97
+ answer: "",
98
+ },
99
+ { question: "5. How secure is my data?", answer: "" },
100
+ { question: "6. Do you work internationally?", answer: "" },
101
+ ],
102
+ defaultOpenFaq: 0,
103
+ showPartners: true,
104
+ showFaq: true,
105
+ };
@@ -0,0 +1 @@
1
+ export { Contact01, default } from "./Contact01";