@notionhive/menus 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/menus.js +16 -0
- package/category.config.json +7 -0
- package/package.json +24 -0
- package/registry/index.json +74 -0
- package/registry/menu-01.json +14 -0
- package/registry/menu-02.json +12 -0
- package/registry/menu-03.json +12 -0
- package/registry/menu-04.json +10 -0
- package/registry/menu-05.json +10 -0
- package/registry/menu-06.json +10 -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/molecules/CorporateHeader/CorporateHeader.jsx +174 -0
- package/templates/components/molecules/CorporateHeader/CorporateHeader.propTypes.js +22 -0
- package/templates/components/molecules/CorporateHeader/index.js +1 -0
- package/templates/components/molecules/EquipmentMegaMenu/EquipmentMegaMenu.jsx +202 -0
- package/templates/components/molecules/EquipmentMegaMenu/EquipmentMegaMenu.propTypes.js +104 -0
- package/templates/components/molecules/EquipmentMegaMenu/index.js +5 -0
- package/templates/components/molecules/FullScreenNavOverlay/FullScreenNavOverlay.jsx +117 -0
- package/templates/components/molecules/FullScreenNavOverlay/FullScreenNavOverlay.propTypes.js +34 -0
- package/templates/components/molecules/FullScreenNavOverlay/index.js +2 -0
- package/templates/components/molecules/MobileNavDrawer/MobileNavDrawer.jsx +139 -0
- package/templates/components/molecules/MobileNavDrawer/index.js +2 -0
- package/templates/components/molecules/SiteHeader/SiteHeader.jsx +177 -0
- package/templates/components/molecules/SiteHeader/SiteHeader.propTypes.js +20 -0
- package/templates/components/molecules/SiteHeader/index.js +1 -0
- package/templates/components/molecules/SlideIndicators/SlideIndicators.jsx +75 -0
- package/templates/components/molecules/SlideIndicators/SlideIndicators.propTypes.js +10 -0
- package/templates/components/molecules/SlideIndicators/index.js +1 -0
- package/templates/components/organisms/Menu01/Menu01.jsx +211 -0
- package/templates/components/organisms/Menu01/Menu01.propTypes.js +63 -0
- package/templates/components/organisms/Menu01/index.js +1 -0
- package/templates/components/organisms/Menu02/Menu02.jsx +133 -0
- package/templates/components/organisms/Menu02/Menu02.propTypes.js +71 -0
- package/templates/components/organisms/Menu02/index.js +1 -0
- package/templates/components/organisms/Menu03/Menu03.jsx +411 -0
- package/templates/components/organisms/Menu03/Menu03.propTypes.js +113 -0
- package/templates/components/organisms/Menu03/index.js +1 -0
- package/templates/components/organisms/Menu04/Menu04.jsx +232 -0
- package/templates/components/organisms/Menu04/Menu04.propTypes.js +53 -0
- package/templates/components/organisms/Menu04/index.js +2 -0
- package/templates/components/organisms/Menu05/Menu05.jsx +318 -0
- package/templates/components/organisms/Menu05/Menu05.propTypes.js +79 -0
- package/templates/components/organisms/Menu05/index.js +2 -0
- package/templates/components/organisms/Menu06/Menu06.jsx +241 -0
- package/templates/components/organisms/Menu06/Menu06.propTypes.js +55 -0
- package/templates/components/organisms/Menu06/index.js +2 -0
- package/templates/public/menus/menu01/agriculture.png +0 -0
- package/templates/public/menus/menu01/banner.jpg +0 -0
- package/templates/public/menus/menu01/build-your-own.png +0 -0
- package/templates/public/menus/menu01/construction.png +0 -0
- package/templates/public/menus/menu01/lawn-care.png +0 -0
- package/templates/public/menus/menu01/logo.png +0 -0
- package/templates/public/menus/menu01/promotions.png +0 -0
- package/templates/public/menus/menu03/logo.png +46 -0
- package/templates/public/menus/menu04/featured.jpg +0 -0
- package/templates/public/menus/menu04/logo.png +0 -0
- package/templates/public/menus/menu05/hero-bg.jpg +0 -0
- package/templates/public/menus/menu05/logo.png +0 -0
- package/templates/public/menus/menu06/pool.jpg +0 -0
|
@@ -0,0 +1,318 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { useState } from "react";
|
|
4
|
+
import SafeImage from "../../atoms/SafeImage";
|
|
5
|
+
import { MobileNavDrawer } from "../../molecules/MobileNavDrawer";
|
|
6
|
+
import { menu05DefaultProps, menu05PropTypes } from "./Menu05.propTypes";
|
|
7
|
+
|
|
8
|
+
function ChevronDownIcon({ className = "" }) {
|
|
9
|
+
return (
|
|
10
|
+
<svg
|
|
11
|
+
width="20"
|
|
12
|
+
height="20"
|
|
13
|
+
viewBox="0 0 20 20"
|
|
14
|
+
fill="none"
|
|
15
|
+
aria-hidden="true"
|
|
16
|
+
className={className}
|
|
17
|
+
>
|
|
18
|
+
<path
|
|
19
|
+
d="M5 7.5L10 12.5L15 7.5"
|
|
20
|
+
stroke="currentColor"
|
|
21
|
+
strokeWidth="1.5"
|
|
22
|
+
strokeLinecap="round"
|
|
23
|
+
strokeLinejoin="round"
|
|
24
|
+
/>
|
|
25
|
+
</svg>
|
|
26
|
+
);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function ChevronRightIcon({ className = "" }) {
|
|
30
|
+
return (
|
|
31
|
+
<svg
|
|
32
|
+
width="20"
|
|
33
|
+
height="20"
|
|
34
|
+
viewBox="0 0 20 20"
|
|
35
|
+
fill="none"
|
|
36
|
+
aria-hidden="true"
|
|
37
|
+
className={className}
|
|
38
|
+
>
|
|
39
|
+
<path
|
|
40
|
+
d="M7.5 5L12.5 10L7.5 15"
|
|
41
|
+
stroke="currentColor"
|
|
42
|
+
strokeWidth="1.5"
|
|
43
|
+
strokeLinecap="round"
|
|
44
|
+
strokeLinejoin="round"
|
|
45
|
+
/>
|
|
46
|
+
</svg>
|
|
47
|
+
);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function PhoneIcon() {
|
|
51
|
+
return (
|
|
52
|
+
<svg width="14" height="14" viewBox="0 0 14 14" fill="none" aria-hidden="true">
|
|
53
|
+
<path
|
|
54
|
+
d="M12.5 9.8V11.3C12.5 11.74 12.16 12.1 11.73 12.1C6.55 12.1 2.4 7.95 2.4 2.77C2.4 2.34 2.76 2 3.2 2H4.7C5.05 2 5.35 2.24 5.42 2.58L5.78 4.48C5.84 4.78 5.74 5.09 5.52 5.29L4.62 6.19C5.48 7.78 6.72 9.02 8.31 9.88L9.21 8.98C9.41 8.76 9.72 8.66 10.02 8.72L11.92 9.08C12.26 9.15 12.5 9.45 12.5 9.8Z"
|
|
55
|
+
stroke="currentColor"
|
|
56
|
+
strokeWidth="1.2"
|
|
57
|
+
strokeLinecap="round"
|
|
58
|
+
strokeLinejoin="round"
|
|
59
|
+
/>
|
|
60
|
+
</svg>
|
|
61
|
+
);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function HamburgerIcon() {
|
|
65
|
+
return (
|
|
66
|
+
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" aria-hidden="true">
|
|
67
|
+
<path d="M4 7H20M4 12H20M4 17H20" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" />
|
|
68
|
+
</svg>
|
|
69
|
+
);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Menu05 — Oxford College header with utility bar, hero section,
|
|
74
|
+
* and About Oxford dropdown panel.
|
|
75
|
+
*
|
|
76
|
+
* @param {object} props - See Menu05.propTypes.js
|
|
77
|
+
*/
|
|
78
|
+
export function Menu05({
|
|
79
|
+
utilityLinks = menu05DefaultProps.utilityLinks,
|
|
80
|
+
phone = menu05DefaultProps.phone,
|
|
81
|
+
logoSrc = menu05DefaultProps.logoSrc,
|
|
82
|
+
logoAlt = menu05DefaultProps.logoAlt,
|
|
83
|
+
navItems = menu05DefaultProps.navItems,
|
|
84
|
+
ctaText = menu05DefaultProps.ctaText,
|
|
85
|
+
onCtaClick,
|
|
86
|
+
defaultDropdownOpen = menu05DefaultProps.defaultDropdownOpen,
|
|
87
|
+
dropdownLinks = menu05DefaultProps.dropdownLinks,
|
|
88
|
+
heroImage = menu05DefaultProps.heroImage,
|
|
89
|
+
heroImageAlt = menu05DefaultProps.heroImageAlt,
|
|
90
|
+
headline = menu05DefaultProps.headline,
|
|
91
|
+
description = menu05DefaultProps.description,
|
|
92
|
+
primaryCtaText = menu05DefaultProps.primaryCtaText,
|
|
93
|
+
secondaryCtaText = menu05DefaultProps.secondaryCtaText,
|
|
94
|
+
onPrimaryCtaClick,
|
|
95
|
+
onSecondaryCtaClick,
|
|
96
|
+
onMenuClick,
|
|
97
|
+
className = "",
|
|
98
|
+
}) {
|
|
99
|
+
const [dropdownOpen, setDropdownOpen] = useState(defaultDropdownOpen);
|
|
100
|
+
const [menuOpen, setMenuOpen] = useState(false);
|
|
101
|
+
|
|
102
|
+
const drawerItems = [
|
|
103
|
+
...navItems.map((item) => ({ label: item.label, href: item.href, onClick: item.onClick })),
|
|
104
|
+
...dropdownLinks.map((item) => ({ label: item.label, href: item.href, onClick: item.onClick })),
|
|
105
|
+
];
|
|
106
|
+
|
|
107
|
+
return (
|
|
108
|
+
<section
|
|
109
|
+
className={["relative w-full overflow-hidden bg-[#f7f7f7]", className]
|
|
110
|
+
.filter(Boolean)
|
|
111
|
+
.join(" ")}
|
|
112
|
+
data-menu="menu05"
|
|
113
|
+
>
|
|
114
|
+
{/* Utility bar */}
|
|
115
|
+
<div className="relative z-30 bg-[#0f1e40] px-4 py-3 sm:px-6 md:px-10 lg:px-[120px]">
|
|
116
|
+
<div className="ml-auto flex max-w-full flex-wrap items-center justify-end gap-2 text-sm text-white sm:gap-2.5">
|
|
117
|
+
{utilityLinks.map((link, index) => (
|
|
118
|
+
<span key={link.label} className="flex items-center gap-2.5">
|
|
119
|
+
{index > 0 ? (
|
|
120
|
+
<span className="text-white/40" aria-hidden="true">
|
|
121
|
+
|
|
|
122
|
+
</span>
|
|
123
|
+
) : null}
|
|
124
|
+
{link.href ? (
|
|
125
|
+
<a
|
|
126
|
+
href={link.href}
|
|
127
|
+
className="whitespace-nowrap transition-opacity duration-200 ease-out hover:opacity-80"
|
|
128
|
+
>
|
|
129
|
+
{link.label}
|
|
130
|
+
</a>
|
|
131
|
+
) : (
|
|
132
|
+
<button
|
|
133
|
+
type="button"
|
|
134
|
+
onClick={link.onClick}
|
|
135
|
+
className="whitespace-nowrap transition-opacity duration-200 ease-out hover:opacity-80"
|
|
136
|
+
>
|
|
137
|
+
{link.label}
|
|
138
|
+
</button>
|
|
139
|
+
)}
|
|
140
|
+
</span>
|
|
141
|
+
))}
|
|
142
|
+
<span className="hidden text-white/40 sm:inline" aria-hidden="true">
|
|
143
|
+
|
|
|
144
|
+
</span>
|
|
145
|
+
<a
|
|
146
|
+
href={`tel:${phone.replace(/[^0-9+]/g, "")}`}
|
|
147
|
+
className="inline-flex items-center gap-1 transition-opacity duration-200 ease-out hover:opacity-80"
|
|
148
|
+
>
|
|
149
|
+
<PhoneIcon />
|
|
150
|
+
<span className="whitespace-nowrap">{phone}</span>
|
|
151
|
+
</a>
|
|
152
|
+
</div>
|
|
153
|
+
</div>
|
|
154
|
+
|
|
155
|
+
{/* Main header */}
|
|
156
|
+
<header className="relative z-30 bg-white px-4 py-3 sm:px-6 md:px-10 lg:px-[120px] lg:py-2.5">
|
|
157
|
+
<div className="mx-auto flex w-full max-w-[1680px] items-center justify-between gap-4">
|
|
158
|
+
<div className="relative h-10 w-[200px] shrink-0 sm:h-[53px] sm:w-[280px] lg:w-[337px]">
|
|
159
|
+
<SafeImage
|
|
160
|
+
src={logoSrc}
|
|
161
|
+
alt={logoAlt}
|
|
162
|
+
fill
|
|
163
|
+
className="object-contain object-left"
|
|
164
|
+
sizes="337px"
|
|
165
|
+
priority
|
|
166
|
+
/>
|
|
167
|
+
</div>
|
|
168
|
+
|
|
169
|
+
<div className="hidden items-center gap-6 lg:flex">
|
|
170
|
+
<nav className="flex items-center gap-[34px]">
|
|
171
|
+
{navItems.map((item) => {
|
|
172
|
+
const isAbout = item.label === "About Oxford";
|
|
173
|
+
|
|
174
|
+
return (
|
|
175
|
+
<div key={item.label} className="relative">
|
|
176
|
+
<button
|
|
177
|
+
type="button"
|
|
178
|
+
onClick={() => {
|
|
179
|
+
if (isAbout) setDropdownOpen((open) => !open);
|
|
180
|
+
item.onClick?.();
|
|
181
|
+
}}
|
|
182
|
+
className={[
|
|
183
|
+
"inline-flex items-center gap-1 text-lg font-medium text-[#0f1e40] transition-colors duration-200 ease-out hover:text-[#0f1e40]/70",
|
|
184
|
+
isAbout && dropdownOpen ? "text-[#0f1e40]" : "",
|
|
185
|
+
].join(" ")}
|
|
186
|
+
aria-expanded={isAbout ? dropdownOpen : undefined}
|
|
187
|
+
>
|
|
188
|
+
{item.label}
|
|
189
|
+
{item.hasDropdown ? <ChevronDownIcon /> : null}
|
|
190
|
+
</button>
|
|
191
|
+
|
|
192
|
+
{isAbout && dropdownOpen ? (
|
|
193
|
+
<div className="absolute left-0 top-full z-40 mt-3 w-[min(395px,calc(100vw-2rem))] border-t-[3px] border-[#eaaf51] bg-white p-5 shadow-lg animate-fade-in">
|
|
194
|
+
<ul className="flex flex-col gap-5">
|
|
195
|
+
{dropdownLinks.map((link, index) => (
|
|
196
|
+
<li key={link.label}>
|
|
197
|
+
{index > 0 ? (
|
|
198
|
+
<div className="mb-5 h-px bg-neutral-200" aria-hidden="true" />
|
|
199
|
+
) : null}
|
|
200
|
+
<div className="flex items-center justify-between gap-3">
|
|
201
|
+
{link.href ? (
|
|
202
|
+
<a
|
|
203
|
+
href={link.href}
|
|
204
|
+
className="text-sm text-[#0f1e40] transition-colors duration-200 ease-out hover:text-[#0f1e40]/70"
|
|
205
|
+
>
|
|
206
|
+
{link.label}
|
|
207
|
+
</a>
|
|
208
|
+
) : (
|
|
209
|
+
<button
|
|
210
|
+
type="button"
|
|
211
|
+
onClick={link.onClick}
|
|
212
|
+
className="text-left text-sm text-[#0f1e40] transition-colors duration-200 ease-out hover:text-[#0f1e40]/70"
|
|
213
|
+
>
|
|
214
|
+
{link.label}
|
|
215
|
+
</button>
|
|
216
|
+
)}
|
|
217
|
+
<ChevronRightIcon className="shrink-0 text-neutral-400" />
|
|
218
|
+
</div>
|
|
219
|
+
</li>
|
|
220
|
+
))}
|
|
221
|
+
</ul>
|
|
222
|
+
</div>
|
|
223
|
+
) : null}
|
|
224
|
+
</div>
|
|
225
|
+
);
|
|
226
|
+
})}
|
|
227
|
+
</nav>
|
|
228
|
+
|
|
229
|
+
<button
|
|
230
|
+
type="button"
|
|
231
|
+
onClick={onCtaClick}
|
|
232
|
+
className="shrink-0 rounded-[99px] bg-[#eaaf51] px-8 py-[18px] text-lg font-medium tracking-[-0.02em] text-[#0f1e40] transition-colors duration-200 ease-out hover:bg-[#e0a647] focus-visible:outline-2 focus-visible:outline-offset-2"
|
|
233
|
+
>
|
|
234
|
+
{ctaText}
|
|
235
|
+
</button>
|
|
236
|
+
</div>
|
|
237
|
+
|
|
238
|
+
<div className="flex items-center gap-3 lg:hidden">
|
|
239
|
+
<button
|
|
240
|
+
type="button"
|
|
241
|
+
onClick={onCtaClick}
|
|
242
|
+
className="rounded-[99px] bg-[#eaaf51] px-4 py-2.5 text-sm font-medium text-[#0f1e40] transition-colors duration-200 ease-out hover:bg-[#e0a647]"
|
|
243
|
+
>
|
|
244
|
+
{ctaText}
|
|
245
|
+
</button>
|
|
246
|
+
<button
|
|
247
|
+
type="button"
|
|
248
|
+
onClick={() => {
|
|
249
|
+
setMenuOpen(true);
|
|
250
|
+
onMenuClick?.();
|
|
251
|
+
}}
|
|
252
|
+
aria-label="Open menu"
|
|
253
|
+
className="rounded-lg p-2 text-[#0f1e40] transition-colors duration-200 ease-out hover:bg-neutral-100 focus-visible:outline-2 focus-visible:outline-offset-2"
|
|
254
|
+
>
|
|
255
|
+
<HamburgerIcon />
|
|
256
|
+
</button>
|
|
257
|
+
</div>
|
|
258
|
+
</div>
|
|
259
|
+
</header>
|
|
260
|
+
|
|
261
|
+
<MobileNavDrawer
|
|
262
|
+
isOpen={menuOpen}
|
|
263
|
+
onClose={() => setMenuOpen(false)}
|
|
264
|
+
navItems={drawerItems}
|
|
265
|
+
accentColor="#eaaf51"
|
|
266
|
+
/>
|
|
267
|
+
|
|
268
|
+
{/* Hero */}
|
|
269
|
+
<div className="relative min-h-[60vh] w-full overflow-hidden md:min-h-[70vh] lg:min-h-[870px]">
|
|
270
|
+
<SafeImage
|
|
271
|
+
src={heroImage}
|
|
272
|
+
alt={heroImageAlt}
|
|
273
|
+
fill
|
|
274
|
+
className="object-cover object-center"
|
|
275
|
+
sizes="100vw"
|
|
276
|
+
priority
|
|
277
|
+
/>
|
|
278
|
+
<div
|
|
279
|
+
className="absolute inset-0"
|
|
280
|
+
style={{
|
|
281
|
+
backgroundImage:
|
|
282
|
+
"linear-gradient(-74.6deg, rgb(15, 30, 64) 24.45%, rgba(15, 30, 64, 0) 98.7%)",
|
|
283
|
+
}}
|
|
284
|
+
aria-hidden="true"
|
|
285
|
+
/>
|
|
286
|
+
|
|
287
|
+
<div className="relative z-10 mx-auto flex max-w-[711px] flex-col gap-6 px-4 py-16 sm:px-6 sm:py-20 md:px-10 lg:px-[120px] lg:py-24">
|
|
288
|
+
<h1 className="text-3xl leading-tight tracking-[-0.04em] text-white sm:text-4xl md:text-5xl lg:text-[56px] lg:tracking-[-2.24px]">
|
|
289
|
+
{headline}
|
|
290
|
+
</h1>
|
|
291
|
+
<p className="max-w-xl text-base leading-relaxed tracking-[0.01em] text-white/80 sm:text-lg">
|
|
292
|
+
{description}
|
|
293
|
+
</p>
|
|
294
|
+
<div className="flex flex-col gap-3 sm:flex-row sm:gap-2">
|
|
295
|
+
<button
|
|
296
|
+
type="button"
|
|
297
|
+
onClick={onPrimaryCtaClick}
|
|
298
|
+
className="rounded-full bg-[#eaaf51] px-8 py-[18px] text-lg font-medium tracking-[-0.02em] text-[#0f1e40] transition-colors duration-200 ease-out hover:bg-[#e0a647] focus-visible:outline-2 focus-visible:outline-offset-2"
|
|
299
|
+
>
|
|
300
|
+
{primaryCtaText}
|
|
301
|
+
</button>
|
|
302
|
+
<button
|
|
303
|
+
type="button"
|
|
304
|
+
onClick={onSecondaryCtaClick}
|
|
305
|
+
className="rounded-full bg-white px-8 py-[18px] text-lg font-medium tracking-[-0.02em] text-[#0f1e40] transition-colors duration-200 ease-out hover:bg-neutral-100 focus-visible:outline-2 focus-visible:outline-offset-2"
|
|
306
|
+
>
|
|
307
|
+
{secondaryCtaText}
|
|
308
|
+
</button>
|
|
309
|
+
</div>
|
|
310
|
+
</div>
|
|
311
|
+
</div>
|
|
312
|
+
</section>
|
|
313
|
+
);
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
Menu05.propTypes = menu05PropTypes;
|
|
317
|
+
|
|
318
|
+
export default Menu05;
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import PropTypes from "prop-types";
|
|
2
|
+
|
|
3
|
+
export const menu05PropTypes = {
|
|
4
|
+
utilityLinks: PropTypes.arrayOf(
|
|
5
|
+
PropTypes.shape({
|
|
6
|
+
label: PropTypes.string.isRequired,
|
|
7
|
+
href: PropTypes.string,
|
|
8
|
+
onClick: PropTypes.func,
|
|
9
|
+
}),
|
|
10
|
+
),
|
|
11
|
+
phone: PropTypes.string,
|
|
12
|
+
logoSrc: PropTypes.string,
|
|
13
|
+
logoAlt: PropTypes.string,
|
|
14
|
+
navItems: PropTypes.arrayOf(
|
|
15
|
+
PropTypes.shape({
|
|
16
|
+
label: PropTypes.string.isRequired,
|
|
17
|
+
href: PropTypes.string,
|
|
18
|
+
onClick: PropTypes.func,
|
|
19
|
+
hasDropdown: PropTypes.bool,
|
|
20
|
+
}),
|
|
21
|
+
),
|
|
22
|
+
ctaText: PropTypes.string,
|
|
23
|
+
onCtaClick: PropTypes.func,
|
|
24
|
+
/** Whether the About Oxford dropdown starts open. */
|
|
25
|
+
defaultDropdownOpen: PropTypes.bool,
|
|
26
|
+
dropdownLinks: PropTypes.arrayOf(
|
|
27
|
+
PropTypes.shape({
|
|
28
|
+
label: PropTypes.string.isRequired,
|
|
29
|
+
href: PropTypes.string,
|
|
30
|
+
onClick: PropTypes.func,
|
|
31
|
+
}),
|
|
32
|
+
),
|
|
33
|
+
heroImage: PropTypes.string,
|
|
34
|
+
heroImageAlt: PropTypes.string,
|
|
35
|
+
headline: PropTypes.string,
|
|
36
|
+
description: PropTypes.string,
|
|
37
|
+
primaryCtaText: PropTypes.string,
|
|
38
|
+
secondaryCtaText: PropTypes.string,
|
|
39
|
+
onPrimaryCtaClick: PropTypes.func,
|
|
40
|
+
onSecondaryCtaClick: PropTypes.func,
|
|
41
|
+
onMenuClick: PropTypes.func,
|
|
42
|
+
className: PropTypes.string,
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
export const menu05DefaultProps = {
|
|
46
|
+
utilityLinks: [
|
|
47
|
+
{ label: "Take career quiz", href: "#" },
|
|
48
|
+
{ label: "Student login", href: "#" },
|
|
49
|
+
],
|
|
50
|
+
phone: "1-866-918-0657",
|
|
51
|
+
logoSrc: "/menus/menu05/logo.png",
|
|
52
|
+
logoAlt: "Oxford College of Arts, Business and Technology",
|
|
53
|
+
navItems: [
|
|
54
|
+
{ label: "Programs", href: "#", hasDropdown: true },
|
|
55
|
+
{ label: "Admissions", href: "#", hasDropdown: true },
|
|
56
|
+
{ label: "About Oxford", href: "#", hasDropdown: true },
|
|
57
|
+
{ label: "Student Experience", href: "#", hasDropdown: true },
|
|
58
|
+
{ label: "Financial Aid", href: "#", hasDropdown: true },
|
|
59
|
+
],
|
|
60
|
+
ctaText: "Request Info",
|
|
61
|
+
defaultDropdownOpen: false,
|
|
62
|
+
dropdownLinks: [
|
|
63
|
+
{ label: "About Oxford", href: "#" },
|
|
64
|
+
{ label: "Mission, Vision, Values", href: "#" },
|
|
65
|
+
{ label: "President's Message", href: "#" },
|
|
66
|
+
{ label: "Accreditations and Affiliations", href: "#" },
|
|
67
|
+
{ label: "Join Our Team", href: "#" },
|
|
68
|
+
{ label: "Agent", href: "#" },
|
|
69
|
+
{ label: "Land Acknowledgement", href: "#" },
|
|
70
|
+
{ label: "Policies", href: "#" },
|
|
71
|
+
],
|
|
72
|
+
heroImage: "/menus/menu05/hero-bg.jpg",
|
|
73
|
+
heroImageAlt: "Students sitting together on campus",
|
|
74
|
+
headline: "Graduate job-ready. Start your career sooner.",
|
|
75
|
+
description:
|
|
76
|
+
"Hands-on diploma programs, expert instructors, financial aid options, and career-focused training across 6 campuses.",
|
|
77
|
+
primaryCtaText: "Explore Programs",
|
|
78
|
+
secondaryCtaText: "Speak to Admissions",
|
|
79
|
+
};
|
|
@@ -0,0 +1,241 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { useState } from "react";
|
|
4
|
+
import SafeImage from "../../atoms/SafeImage";
|
|
5
|
+
import { FullScreenNavOverlay } from "../../molecules/FullScreenNavOverlay";
|
|
6
|
+
import { menu06DefaultProps, menu06PropTypes } from "./Menu06.propTypes";
|
|
7
|
+
|
|
8
|
+
function CloseIcon() {
|
|
9
|
+
return (
|
|
10
|
+
<svg width="36" height="36" viewBox="0 0 36 36" fill="none" aria-hidden="true">
|
|
11
|
+
<path
|
|
12
|
+
d="M11 11L25 25M25 11L11 25"
|
|
13
|
+
stroke="currentColor"
|
|
14
|
+
strokeWidth="1.5"
|
|
15
|
+
strokeLinecap="round"
|
|
16
|
+
/>
|
|
17
|
+
</svg>
|
|
18
|
+
);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function MenuDashIcon() {
|
|
22
|
+
return (
|
|
23
|
+
<span className="inline-block h-[9px] w-3.5 rounded-sm bg-white" aria-hidden="true" />
|
|
24
|
+
);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Menu06 — Sandbay resort split-panel menu overlay with pool image
|
|
29
|
+
* and burgundy navigation panel sliding in from the right.
|
|
30
|
+
*
|
|
31
|
+
* @param {object} props - See Menu06.propTypes.js
|
|
32
|
+
*/
|
|
33
|
+
export function Menu06({
|
|
34
|
+
defaultOpen = menu06DefaultProps.defaultOpen,
|
|
35
|
+
showHeroPeek = menu06DefaultProps.showHeroPeek,
|
|
36
|
+
brandName = menu06DefaultProps.brandName,
|
|
37
|
+
peekHeadline = menu06DefaultProps.peekHeadline,
|
|
38
|
+
panelImage = menu06DefaultProps.panelImage,
|
|
39
|
+
panelImageAlt = menu06DefaultProps.panelImageAlt,
|
|
40
|
+
navItems = menu06DefaultProps.navItems,
|
|
41
|
+
email = menu06DefaultProps.email,
|
|
42
|
+
phone = menu06DefaultProps.phone,
|
|
43
|
+
socialLinks = menu06DefaultProps.socialLinks,
|
|
44
|
+
menuLabel = menu06DefaultProps.menuLabel,
|
|
45
|
+
onClose,
|
|
46
|
+
className = "",
|
|
47
|
+
}) {
|
|
48
|
+
const [isOpen, setIsOpen] = useState(defaultOpen);
|
|
49
|
+
|
|
50
|
+
const handleClose = () => {
|
|
51
|
+
setIsOpen(false);
|
|
52
|
+
onClose?.();
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
return (
|
|
56
|
+
<section
|
|
57
|
+
className={["relative min-h-[600px] w-full overflow-hidden bg-[#fff8f3] md:min-h-[700px]", className]
|
|
58
|
+
.filter(Boolean)
|
|
59
|
+
.join(" ")}
|
|
60
|
+
data-menu="menu06"
|
|
61
|
+
>
|
|
62
|
+
{showHeroPeek ? (
|
|
63
|
+
<div
|
|
64
|
+
className="relative flex min-h-[600px] flex-col px-4 pb-10 pt-6 sm:px-6 md:min-h-[700px] md:px-10 lg:px-[60px] lg:pt-9"
|
|
65
|
+
aria-hidden={isOpen}
|
|
66
|
+
>
|
|
67
|
+
<div className="mx-auto flex w-full max-w-[1800px] items-center justify-between">
|
|
68
|
+
<p className="font-serif text-3xl leading-none tracking-[-0.04em] text-[#680015] sm:text-4xl md:text-[48px]">
|
|
69
|
+
{brandName}
|
|
70
|
+
</p>
|
|
71
|
+
{!isOpen ? (
|
|
72
|
+
<button
|
|
73
|
+
type="button"
|
|
74
|
+
onClick={() => setIsOpen(true)}
|
|
75
|
+
className="rounded-[120px] border border-[#680015]/20 px-6 py-3 text-base text-[#641002] transition-colors duration-200 ease-out hover:bg-white focus-visible:outline-2 focus-visible:outline-offset-2"
|
|
76
|
+
>
|
|
77
|
+
Open Menu
|
|
78
|
+
</button>
|
|
79
|
+
) : null}
|
|
80
|
+
</div>
|
|
81
|
+
|
|
82
|
+
<h1 className="mx-auto mt-10 max-w-[1282px] text-center font-serif text-[clamp(2rem,8vw,8rem)] leading-[0.75] tracking-[-0.04em] text-[#410000] sm:mt-16 lg:mt-[115px]">
|
|
83
|
+
{peekHeadline}
|
|
84
|
+
</h1>
|
|
85
|
+
</div>
|
|
86
|
+
) : null}
|
|
87
|
+
|
|
88
|
+
{!showHeroPeek && !isOpen ? (
|
|
89
|
+
<div className="flex min-h-[600px] items-center justify-center px-4 md:min-h-[700px]">
|
|
90
|
+
<button
|
|
91
|
+
type="button"
|
|
92
|
+
onClick={() => setIsOpen(true)}
|
|
93
|
+
className="rounded-[120px] bg-[#680015] px-8 py-4 text-white transition-colors duration-200 ease-out hover:bg-[#5a0012] focus-visible:outline-2 focus-visible:outline-offset-2"
|
|
94
|
+
>
|
|
95
|
+
Open Menu
|
|
96
|
+
</button>
|
|
97
|
+
</div>
|
|
98
|
+
) : null}
|
|
99
|
+
|
|
100
|
+
<FullScreenNavOverlay
|
|
101
|
+
isOpen={isOpen}
|
|
102
|
+
onClose={handleClose}
|
|
103
|
+
slideFrom="right"
|
|
104
|
+
showBackdrop={showHeroPeek}
|
|
105
|
+
backdropClassName="bg-black/30"
|
|
106
|
+
ariaLabel="Sandbay navigation"
|
|
107
|
+
panelClassName="shadow-2xl"
|
|
108
|
+
>
|
|
109
|
+
<div className="flex min-h-full w-full flex-col md:flex-row">
|
|
110
|
+
{/* Left image panel — 40% */}
|
|
111
|
+
<div className="relative h-56 w-full shrink-0 sm:h-72 md:h-auto md:min-h-full md:w-[40%]">
|
|
112
|
+
<SafeImage
|
|
113
|
+
src={panelImage}
|
|
114
|
+
alt={panelImageAlt}
|
|
115
|
+
fill
|
|
116
|
+
className="object-cover object-center"
|
|
117
|
+
sizes="(max-width: 768px) 100vw, 40vw"
|
|
118
|
+
priority
|
|
119
|
+
/>
|
|
120
|
+
</div>
|
|
121
|
+
|
|
122
|
+
{/* Right burgundy panel — 60% */}
|
|
123
|
+
<div className="flex min-h-0 flex-1 flex-col bg-[#680015] px-6 py-8 sm:px-10 sm:py-10 md:w-[60%] md:px-[60px] md:py-[60px] lg:px-[100px]">
|
|
124
|
+
<div className="flex flex-1 flex-col gap-12 md:gap-[60px] lg:gap-[140px]">
|
|
125
|
+
<div className="flex flex-col gap-10 md:gap-[60px]">
|
|
126
|
+
<div className="flex items-center justify-between gap-4">
|
|
127
|
+
<div className="flex items-center gap-1.5 uppercase tracking-wide text-white">
|
|
128
|
+
<MenuDashIcon />
|
|
129
|
+
<span className="text-base">{menuLabel}</span>
|
|
130
|
+
</div>
|
|
131
|
+
<button
|
|
132
|
+
type="button"
|
|
133
|
+
onClick={handleClose}
|
|
134
|
+
aria-label="Close menu"
|
|
135
|
+
className="rounded-[3px] border border-white/20 p-1 text-white transition-colors duration-200 ease-out hover:bg-white/10 focus-visible:outline-2 focus-visible:outline-offset-2"
|
|
136
|
+
>
|
|
137
|
+
<CloseIcon />
|
|
138
|
+
</button>
|
|
139
|
+
</div>
|
|
140
|
+
|
|
141
|
+
<nav className="flex flex-col gap-6 md:gap-8">
|
|
142
|
+
{navItems.map((item, index) => {
|
|
143
|
+
const linkClass = [
|
|
144
|
+
"text-left font-normal leading-[0.75] tracking-[-0.04em] transition-opacity duration-200 ease-out",
|
|
145
|
+
"text-3xl sm:text-4xl md:text-[48px]",
|
|
146
|
+
item.isActive
|
|
147
|
+
? "text-white"
|
|
148
|
+
: "text-white/80 hover:text-white",
|
|
149
|
+
].join(" ");
|
|
150
|
+
|
|
151
|
+
const content = item.href ? (
|
|
152
|
+
<a
|
|
153
|
+
href={item.href}
|
|
154
|
+
className={linkClass}
|
|
155
|
+
aria-current={item.isActive ? "page" : undefined}
|
|
156
|
+
>
|
|
157
|
+
{item.label}
|
|
158
|
+
</a>
|
|
159
|
+
) : (
|
|
160
|
+
<button
|
|
161
|
+
type="button"
|
|
162
|
+
onClick={item.onClick}
|
|
163
|
+
className={`${linkClass} focus-visible:outline-2 focus-visible:outline-offset-2`}
|
|
164
|
+
aria-current={item.isActive ? "page" : undefined}
|
|
165
|
+
>
|
|
166
|
+
{item.label}
|
|
167
|
+
</button>
|
|
168
|
+
);
|
|
169
|
+
|
|
170
|
+
return (
|
|
171
|
+
<div key={item.label} className="flex flex-col gap-6 md:gap-8">
|
|
172
|
+
{content}
|
|
173
|
+
{index < navItems.length - 1 ? (
|
|
174
|
+
<div className="h-px w-full bg-white/20" aria-hidden="true" />
|
|
175
|
+
) : null}
|
|
176
|
+
</div>
|
|
177
|
+
);
|
|
178
|
+
})}
|
|
179
|
+
</nav>
|
|
180
|
+
</div>
|
|
181
|
+
|
|
182
|
+
<div className="mt-auto flex flex-col gap-10 md:gap-[60px]">
|
|
183
|
+
<div className="flex flex-col gap-6">
|
|
184
|
+
<p className="text-base uppercase text-white">Let's Talk</p>
|
|
185
|
+
<div className="flex flex-col gap-4">
|
|
186
|
+
{email ? (
|
|
187
|
+
<a
|
|
188
|
+
href={`mailto:${email}`}
|
|
189
|
+
className="text-2xl leading-tight tracking-[-0.02em] text-white/80 transition-opacity duration-200 ease-out hover:text-white sm:text-3xl md:text-[48px]"
|
|
190
|
+
>
|
|
191
|
+
{email}
|
|
192
|
+
</a>
|
|
193
|
+
) : null}
|
|
194
|
+
{phone ? (
|
|
195
|
+
<a
|
|
196
|
+
href={`tel:${phone.replace(/\s/g, "")}`}
|
|
197
|
+
className="text-lg tracking-[-0.02em] text-white/70 transition-opacity duration-200 ease-out hover:text-white md:text-2xl"
|
|
198
|
+
>
|
|
199
|
+
{phone}
|
|
200
|
+
</a>
|
|
201
|
+
) : null}
|
|
202
|
+
</div>
|
|
203
|
+
</div>
|
|
204
|
+
|
|
205
|
+
<div className="flex flex-col gap-4">
|
|
206
|
+
<p className="text-base uppercase text-white">Social</p>
|
|
207
|
+
<div className="flex flex-wrap gap-6 text-lg tracking-[-0.02em] text-white/70 md:gap-8 md:text-2xl">
|
|
208
|
+
{socialLinks.map((link) =>
|
|
209
|
+
link.href ? (
|
|
210
|
+
<a
|
|
211
|
+
key={link.label}
|
|
212
|
+
href={link.href}
|
|
213
|
+
className="transition-opacity duration-200 ease-out hover:text-white"
|
|
214
|
+
>
|
|
215
|
+
{link.label}
|
|
216
|
+
</a>
|
|
217
|
+
) : (
|
|
218
|
+
<button
|
|
219
|
+
key={link.label}
|
|
220
|
+
type="button"
|
|
221
|
+
onClick={link.onClick}
|
|
222
|
+
className="transition-opacity duration-200 ease-out hover:text-white"
|
|
223
|
+
>
|
|
224
|
+
{link.label}
|
|
225
|
+
</button>
|
|
226
|
+
),
|
|
227
|
+
)}
|
|
228
|
+
</div>
|
|
229
|
+
</div>
|
|
230
|
+
</div>
|
|
231
|
+
</div>
|
|
232
|
+
</div>
|
|
233
|
+
</div>
|
|
234
|
+
</FullScreenNavOverlay>
|
|
235
|
+
</section>
|
|
236
|
+
);
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
Menu06.propTypes = menu06PropTypes;
|
|
240
|
+
|
|
241
|
+
export default Menu06;
|