@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
package/bin/menus.js
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
const path = require("path");
|
|
4
|
+
const categoryConfig = require("../category.config.json");
|
|
5
|
+
|
|
6
|
+
function loadCore() {
|
|
7
|
+
try {
|
|
8
|
+
return require("@notionhive/core");
|
|
9
|
+
} catch {
|
|
10
|
+
return require(path.join(__dirname, "../../core/lib"));
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
const { createCategoryCli } = loadCore();
|
|
15
|
+
const run = createCategoryCli(categoryConfig);
|
|
16
|
+
run(path.resolve(__dirname, ".."));
|
package/package.json
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@notionhive/menus",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Add editable menus into your Next.js project.",
|
|
5
|
+
"license": "UNLICENSED",
|
|
6
|
+
"bin": {
|
|
7
|
+
"menus": "./bin/menus.js"
|
|
8
|
+
},
|
|
9
|
+
"files": [
|
|
10
|
+
"bin",
|
|
11
|
+
"registry",
|
|
12
|
+
"templates",
|
|
13
|
+
"category.config.json"
|
|
14
|
+
],
|
|
15
|
+
"scripts": {
|
|
16
|
+
"prepublishOnly": "node ../../scripts/sync-templates.js"
|
|
17
|
+
},
|
|
18
|
+
"dependencies": {
|
|
19
|
+
"@notionhive/core": "0.1.0"
|
|
20
|
+
},
|
|
21
|
+
"engines": {
|
|
22
|
+
"node": ">=18"
|
|
23
|
+
}
|
|
24
|
+
}
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
{
|
|
2
|
+
"category": "menus",
|
|
3
|
+
"packageName": "@notionhive/menus",
|
|
4
|
+
"items": [
|
|
5
|
+
{
|
|
6
|
+
"id": "menu-01",
|
|
7
|
+
"exportName": "Menu01",
|
|
8
|
+
"folderName": "Menu01",
|
|
9
|
+
"shared": [
|
|
10
|
+
"atoms/SafeImage",
|
|
11
|
+
"hooks/useCarousel",
|
|
12
|
+
"molecules/SiteHeader",
|
|
13
|
+
"molecules/EquipmentMegaMenu",
|
|
14
|
+
"molecules/SlideIndicators",
|
|
15
|
+
"molecules/MobileNavDrawer"
|
|
16
|
+
],
|
|
17
|
+
"assets": "public/menus/menu01"
|
|
18
|
+
},
|
|
19
|
+
{
|
|
20
|
+
"id": "menu-02",
|
|
21
|
+
"exportName": "Menu02",
|
|
22
|
+
"folderName": "Menu02",
|
|
23
|
+
"shared": [
|
|
24
|
+
"hooks/useCarousel",
|
|
25
|
+
"molecules/CorporateHeader",
|
|
26
|
+
"molecules/SlideIndicators",
|
|
27
|
+
"molecules/MobileNavDrawer"
|
|
28
|
+
],
|
|
29
|
+
"assets": "public/menus/menu02"
|
|
30
|
+
},
|
|
31
|
+
{
|
|
32
|
+
"id": "menu-03",
|
|
33
|
+
"exportName": "Menu03",
|
|
34
|
+
"folderName": "Menu03",
|
|
35
|
+
"shared": [
|
|
36
|
+
"atoms/SafeImage",
|
|
37
|
+
"hooks/useCarousel",
|
|
38
|
+
"molecules/SlideIndicators",
|
|
39
|
+
"molecules/MobileNavDrawer"
|
|
40
|
+
],
|
|
41
|
+
"assets": "public/menus/menu03"
|
|
42
|
+
},
|
|
43
|
+
{
|
|
44
|
+
"id": "menu-04",
|
|
45
|
+
"exportName": "Menu04",
|
|
46
|
+
"folderName": "Menu04",
|
|
47
|
+
"shared": [
|
|
48
|
+
"atoms/SafeImage",
|
|
49
|
+
"molecules/FullScreenNavOverlay"
|
|
50
|
+
],
|
|
51
|
+
"assets": "public/menus/menu04"
|
|
52
|
+
},
|
|
53
|
+
{
|
|
54
|
+
"id": "menu-05",
|
|
55
|
+
"exportName": "Menu05",
|
|
56
|
+
"folderName": "Menu05",
|
|
57
|
+
"shared": [
|
|
58
|
+
"atoms/SafeImage",
|
|
59
|
+
"molecules/MobileNavDrawer"
|
|
60
|
+
],
|
|
61
|
+
"assets": "public/menus/menu05"
|
|
62
|
+
},
|
|
63
|
+
{
|
|
64
|
+
"id": "menu-06",
|
|
65
|
+
"exportName": "Menu06",
|
|
66
|
+
"folderName": "Menu06",
|
|
67
|
+
"shared": [
|
|
68
|
+
"atoms/SafeImage",
|
|
69
|
+
"molecules/FullScreenNavOverlay"
|
|
70
|
+
],
|
|
71
|
+
"assets": "public/menus/menu06"
|
|
72
|
+
}
|
|
73
|
+
]
|
|
74
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
{
|
|
2
|
+
"id": "menu-01",
|
|
3
|
+
"exportName": "Menu01",
|
|
4
|
+
"folderName": "Menu01",
|
|
5
|
+
"shared": [
|
|
6
|
+
"atoms/SafeImage",
|
|
7
|
+
"hooks/useCarousel",
|
|
8
|
+
"molecules/SiteHeader",
|
|
9
|
+
"molecules/EquipmentMegaMenu",
|
|
10
|
+
"molecules/SlideIndicators",
|
|
11
|
+
"molecules/MobileNavDrawer"
|
|
12
|
+
],
|
|
13
|
+
"assets": "public/menus/menu01"
|
|
14
|
+
}
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
import Image from "next/image";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* SafeImage — renders local /heroes/ assets reliably.
|
|
5
|
+
*
|
|
6
|
+
* Figma exports are often SVG content saved with .png extensions.
|
|
7
|
+
* Native img avoids next/image SVG restrictions and broken renders in Storybook.
|
|
8
|
+
*
|
|
9
|
+
* @param {object} props - Same as next/image (src, alt, fill, className, sizes, priority, width, height).
|
|
10
|
+
*/
|
|
11
|
+
export function SafeImage({
|
|
12
|
+
src,
|
|
13
|
+
alt = "",
|
|
14
|
+
fill,
|
|
15
|
+
className = "",
|
|
16
|
+
sizes,
|
|
17
|
+
priority,
|
|
18
|
+
width,
|
|
19
|
+
height,
|
|
20
|
+
style,
|
|
21
|
+
...rest
|
|
22
|
+
}) {
|
|
23
|
+
if (!src) return null;
|
|
24
|
+
|
|
25
|
+
const isLocalAsset =
|
|
26
|
+
src.startsWith("/heroes/") ||
|
|
27
|
+
src.startsWith("/menus/") ||
|
|
28
|
+
src.startsWith("/about/") ||
|
|
29
|
+
src.startsWith("/services/") ||
|
|
30
|
+
src.startsWith("/testimonials/") ||
|
|
31
|
+
src.startsWith("/cta/") ||
|
|
32
|
+
src.startsWith("/contact/") ||
|
|
33
|
+
src.startsWith("/footer/");
|
|
34
|
+
const isSvg = src.endsWith(".svg");
|
|
35
|
+
|
|
36
|
+
if (isLocalAsset || isSvg) {
|
|
37
|
+
if (fill) {
|
|
38
|
+
return (
|
|
39
|
+
// eslint-disable-next-line @next/next/no-img-element
|
|
40
|
+
<img
|
|
41
|
+
src={src}
|
|
42
|
+
alt={alt}
|
|
43
|
+
className={className}
|
|
44
|
+
style={{
|
|
45
|
+
...style,
|
|
46
|
+
position: "absolute",
|
|
47
|
+
inset: 0,
|
|
48
|
+
width: "100%",
|
|
49
|
+
height: "100%",
|
|
50
|
+
objectFit: "cover",
|
|
51
|
+
}}
|
|
52
|
+
{...rest}
|
|
53
|
+
/>
|
|
54
|
+
);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
return (
|
|
58
|
+
// eslint-disable-next-line @next/next/no-img-element
|
|
59
|
+
<img
|
|
60
|
+
src={src}
|
|
61
|
+
alt={alt}
|
|
62
|
+
className={className}
|
|
63
|
+
width={width}
|
|
64
|
+
height={height}
|
|
65
|
+
style={style}
|
|
66
|
+
{...rest}
|
|
67
|
+
/>
|
|
68
|
+
);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
if (fill) {
|
|
72
|
+
return (
|
|
73
|
+
<Image
|
|
74
|
+
src={src}
|
|
75
|
+
alt={alt}
|
|
76
|
+
fill
|
|
77
|
+
className={className}
|
|
78
|
+
sizes={sizes}
|
|
79
|
+
priority={priority}
|
|
80
|
+
style={style}
|
|
81
|
+
{...rest}
|
|
82
|
+
/>
|
|
83
|
+
);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
return (
|
|
87
|
+
<Image
|
|
88
|
+
src={src}
|
|
89
|
+
alt={alt}
|
|
90
|
+
width={width}
|
|
91
|
+
height={height}
|
|
92
|
+
className={className}
|
|
93
|
+
sizes={sizes}
|
|
94
|
+
priority={priority}
|
|
95
|
+
style={style}
|
|
96
|
+
{...rest}
|
|
97
|
+
/>
|
|
98
|
+
);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
export default SafeImage;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { SafeImage, default } from "./SafeImage";
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { useCallback, useEffect, useState } from "react";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* useCarousel — shared slide state for hero carousels and sliders.
|
|
7
|
+
*
|
|
8
|
+
* @param {object} options
|
|
9
|
+
* @param {number} [options.count=1] - Total slide count.
|
|
10
|
+
* @param {number} [options.initialIndex=0] - Starting slide.
|
|
11
|
+
* @param {boolean} [options.loop=false] - Wrap from last → first.
|
|
12
|
+
* @param {number} [options.autoPlayMs=0] - Auto-advance interval; 0 = off.
|
|
13
|
+
* @param {(index: number) => void} [options.onChange] - Called when slide changes.
|
|
14
|
+
* @returns {{ activeSlide: number, goTo: Function, next: Function, prev: Function, pause: Function, resume: Function, isPaused: boolean }}
|
|
15
|
+
*/
|
|
16
|
+
export function useCarousel({
|
|
17
|
+
count = 1,
|
|
18
|
+
initialIndex = 0,
|
|
19
|
+
loop = false,
|
|
20
|
+
autoPlayMs = 0,
|
|
21
|
+
onChange,
|
|
22
|
+
} = {}) {
|
|
23
|
+
const maxIndex = Math.max(0, count - 1);
|
|
24
|
+
const [activeSlide, setActiveSlide] = useState(() =>
|
|
25
|
+
Math.min(Math.max(initialIndex, 0), maxIndex)
|
|
26
|
+
);
|
|
27
|
+
const [isPaused, setIsPaused] = useState(false);
|
|
28
|
+
|
|
29
|
+
const goTo = useCallback(
|
|
30
|
+
(index) => {
|
|
31
|
+
const next = loop
|
|
32
|
+
? ((index % count) + count) % count
|
|
33
|
+
: Math.min(Math.max(index, 0), maxIndex);
|
|
34
|
+
setActiveSlide(next);
|
|
35
|
+
onChange?.(next);
|
|
36
|
+
},
|
|
37
|
+
[count, loop, maxIndex, onChange]
|
|
38
|
+
);
|
|
39
|
+
|
|
40
|
+
const next = useCallback(() => {
|
|
41
|
+
if (loop || activeSlide < maxIndex) goTo(activeSlide + 1);
|
|
42
|
+
}, [activeSlide, goTo, loop, maxIndex]);
|
|
43
|
+
|
|
44
|
+
const prev = useCallback(() => {
|
|
45
|
+
if (loop || activeSlide > 0) goTo(activeSlide - 1);
|
|
46
|
+
}, [activeSlide, goTo, loop]);
|
|
47
|
+
|
|
48
|
+
const pause = useCallback(() => setIsPaused(true), []);
|
|
49
|
+
const resume = useCallback(() => setIsPaused(false), []);
|
|
50
|
+
|
|
51
|
+
useEffect(() => {
|
|
52
|
+
if (!autoPlayMs || isPaused || count <= 1) return undefined;
|
|
53
|
+
|
|
54
|
+
const id = window.setInterval(next, autoPlayMs);
|
|
55
|
+
return () => window.clearInterval(id);
|
|
56
|
+
}, [autoPlayMs, count, isPaused, next]);
|
|
57
|
+
|
|
58
|
+
useEffect(() => {
|
|
59
|
+
setActiveSlide((current) => Math.min(Math.max(current, 0), maxIndex));
|
|
60
|
+
}, [maxIndex]);
|
|
61
|
+
|
|
62
|
+
return {
|
|
63
|
+
activeSlide,
|
|
64
|
+
goTo,
|
|
65
|
+
next,
|
|
66
|
+
prev,
|
|
67
|
+
pause,
|
|
68
|
+
resume,
|
|
69
|
+
isPaused,
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
export default useCarousel;
|
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
import SafeImage from "../../atoms/SafeImage";
|
|
2
|
+
import { corporateHeaderPropTypes } from "./CorporateHeader.propTypes";
|
|
3
|
+
|
|
4
|
+
function ChevronDownIcon() {
|
|
5
|
+
return (
|
|
6
|
+
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" aria-hidden="true">
|
|
7
|
+
<path
|
|
8
|
+
d="M4 6L8 10L12 6"
|
|
9
|
+
stroke="#010623"
|
|
10
|
+
strokeWidth="1.2"
|
|
11
|
+
strokeLinecap="round"
|
|
12
|
+
strokeLinejoin="round"
|
|
13
|
+
/>
|
|
14
|
+
</svg>
|
|
15
|
+
);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function SearchIcon() {
|
|
19
|
+
return (
|
|
20
|
+
<svg width="32" height="32" viewBox="0 0 32 32" fill="none" aria-hidden="true">
|
|
21
|
+
<circle cx="14" cy="14" r="8" stroke="#010623" strokeWidth="1.5" />
|
|
22
|
+
<path d="M20 20L26 26" stroke="#010623" strokeWidth="1.5" strokeLinecap="round" />
|
|
23
|
+
</svg>
|
|
24
|
+
);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function CartIcon() {
|
|
28
|
+
return (
|
|
29
|
+
<svg width="32" height="32" viewBox="0 0 32 32" fill="none" aria-hidden="true">
|
|
30
|
+
<path
|
|
31
|
+
d="M8 8H26L24 20H12L10 8H8ZM12 24C12.5523 24 13 23.5523 13 23C13 22.4477 12.5523 22 12 22C11.4477 22 11 22.4477 11 23C11 23.5523 11.4477 24 12 24ZM22 24C22.5523 24 23 23.5523 23 23C23 22.4477 22.5523 22 22 22C21.4477 22 21 22.4477 21 23C21 23.5523 21.4477 24 22 24Z"
|
|
32
|
+
stroke="#010623"
|
|
33
|
+
strokeWidth="1.5"
|
|
34
|
+
strokeLinecap="round"
|
|
35
|
+
strokeLinejoin="round"
|
|
36
|
+
/>
|
|
37
|
+
</svg>
|
|
38
|
+
);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function MenuIcon() {
|
|
42
|
+
return (
|
|
43
|
+
<svg width="32" height="32" viewBox="0 0 32 32" fill="none" aria-hidden="true">
|
|
44
|
+
<path d="M6 10H26M6 16H26M6 22H18" stroke="#010623" strokeWidth="1.5" strokeLinecap="round" />
|
|
45
|
+
</svg>
|
|
46
|
+
);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function NavItem({ item }) {
|
|
50
|
+
const content = (
|
|
51
|
+
<>
|
|
52
|
+
{item.label}
|
|
53
|
+
{item.hasDropdown ? <ChevronDownIcon /> : null}
|
|
54
|
+
</>
|
|
55
|
+
);
|
|
56
|
+
|
|
57
|
+
const className =
|
|
58
|
+
"inline-flex items-center gap-0.5 rounded-lg p-2 text-sm leading-5 tracking-[0.09px] text-[#010623] transition-colors duration-200 ease-out hover:bg-neutral-50 lg:text-lg";
|
|
59
|
+
|
|
60
|
+
if (item.href) {
|
|
61
|
+
return (
|
|
62
|
+
<a href={item.href} className={className}>
|
|
63
|
+
{content}
|
|
64
|
+
</a>
|
|
65
|
+
);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
return (
|
|
69
|
+
<button type="button" onClick={item.onClick} className={className}>
|
|
70
|
+
{content}
|
|
71
|
+
</button>
|
|
72
|
+
);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* CorporateHeader — finance/corporate site header with dropdown nav and dual CTAs.
|
|
77
|
+
*/
|
|
78
|
+
export function CorporateHeader({
|
|
79
|
+
logoSrc,
|
|
80
|
+
logoAlt = "Brand logo",
|
|
81
|
+
navItems = [],
|
|
82
|
+
secondaryCtaText = "BRAC EPL Trade",
|
|
83
|
+
primaryCtaText = "Let's Get Started",
|
|
84
|
+
onSecondaryClick,
|
|
85
|
+
onPrimaryClick,
|
|
86
|
+
onSearchClick,
|
|
87
|
+
onCartClick,
|
|
88
|
+
onMenuClick,
|
|
89
|
+
className = "",
|
|
90
|
+
}) {
|
|
91
|
+
return (
|
|
92
|
+
<header
|
|
93
|
+
className={[
|
|
94
|
+
"flex min-h-[72px] w-full shrink-0 items-center justify-between border-b border-[#edeef0] bg-white px-4 py-4 sm:px-6 md:px-10 lg:h-[114px] lg:px-14 lg:py-6",
|
|
95
|
+
className,
|
|
96
|
+
]
|
|
97
|
+
.filter(Boolean)
|
|
98
|
+
.join(" ")}
|
|
99
|
+
>
|
|
100
|
+
<div className="relative h-9 w-32 shrink-0 sm:h-10 sm:w-40 lg:h-[52px] lg:w-[218px]">
|
|
101
|
+
<SafeImage
|
|
102
|
+
src={logoSrc}
|
|
103
|
+
alt={logoAlt}
|
|
104
|
+
fill
|
|
105
|
+
className="object-contain object-left"
|
|
106
|
+
sizes="218px"
|
|
107
|
+
priority
|
|
108
|
+
/>
|
|
109
|
+
</div>
|
|
110
|
+
|
|
111
|
+
<div className="flex items-center gap-3 lg:gap-14">
|
|
112
|
+
<nav className="hidden xl:block" aria-label="Primary">
|
|
113
|
+
<ul className="flex items-center gap-1 lg:gap-2">
|
|
114
|
+
{navItems.map((item) => (
|
|
115
|
+
<li key={item.label}>
|
|
116
|
+
<NavItem item={item} />
|
|
117
|
+
</li>
|
|
118
|
+
))}
|
|
119
|
+
</ul>
|
|
120
|
+
</nav>
|
|
121
|
+
|
|
122
|
+
<div className="flex items-center gap-2 sm:gap-4 lg:gap-6">
|
|
123
|
+
<div className="hidden items-center gap-2 md:flex lg:gap-4">
|
|
124
|
+
<button
|
|
125
|
+
type="button"
|
|
126
|
+
onClick={onSecondaryClick}
|
|
127
|
+
className="rounded-sm border border-[#b0d6ff] bg-white px-4 py-3 text-sm leading-[18px] tracking-[0.09px] text-[#0058b5] transition-colors duration-200 ease-out hover:bg-blue-50 lg:px-8 lg:py-6 lg:text-lg"
|
|
128
|
+
>
|
|
129
|
+
{secondaryCtaText}
|
|
130
|
+
</button>
|
|
131
|
+
<button
|
|
132
|
+
type="button"
|
|
133
|
+
onClick={onPrimaryClick}
|
|
134
|
+
className="rounded-sm bg-[#0058b5] px-4 py-3 text-sm leading-[18px] tracking-[0.09px] text-white transition-colors duration-200 ease-out hover:bg-[#004494] lg:px-8 lg:py-6 lg:text-lg"
|
|
135
|
+
>
|
|
136
|
+
{primaryCtaText}
|
|
137
|
+
</button>
|
|
138
|
+
</div>
|
|
139
|
+
|
|
140
|
+
<div className="flex items-center gap-3 sm:gap-6">
|
|
141
|
+
<button
|
|
142
|
+
type="button"
|
|
143
|
+
onClick={onSearchClick}
|
|
144
|
+
aria-label="Search"
|
|
145
|
+
className="transition-opacity duration-200 ease-out hover:opacity-70"
|
|
146
|
+
>
|
|
147
|
+
<SearchIcon />
|
|
148
|
+
</button>
|
|
149
|
+
<button
|
|
150
|
+
type="button"
|
|
151
|
+
onClick={onCartClick}
|
|
152
|
+
aria-label="Cart"
|
|
153
|
+
className="hidden transition-opacity duration-200 ease-out hover:opacity-70 sm:block"
|
|
154
|
+
>
|
|
155
|
+
<CartIcon />
|
|
156
|
+
</button>
|
|
157
|
+
<button
|
|
158
|
+
type="button"
|
|
159
|
+
onClick={onMenuClick}
|
|
160
|
+
aria-label="Open menu"
|
|
161
|
+
className="transition-opacity duration-200 ease-out hover:opacity-70 xl:hidden"
|
|
162
|
+
>
|
|
163
|
+
<MenuIcon />
|
|
164
|
+
</button>
|
|
165
|
+
</div>
|
|
166
|
+
</div>
|
|
167
|
+
</div>
|
|
168
|
+
</header>
|
|
169
|
+
);
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
CorporateHeader.propTypes = corporateHeaderPropTypes;
|
|
173
|
+
|
|
174
|
+
export default CorporateHeader;
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import PropTypes from "prop-types";
|
|
2
|
+
|
|
3
|
+
const navItemShape = PropTypes.shape({
|
|
4
|
+
label: PropTypes.string.isRequired,
|
|
5
|
+
href: PropTypes.string,
|
|
6
|
+
onClick: PropTypes.func,
|
|
7
|
+
hasDropdown: PropTypes.bool,
|
|
8
|
+
});
|
|
9
|
+
|
|
10
|
+
export const corporateHeaderPropTypes = {
|
|
11
|
+
logoSrc: PropTypes.string.isRequired,
|
|
12
|
+
logoAlt: PropTypes.string,
|
|
13
|
+
navItems: PropTypes.arrayOf(navItemShape),
|
|
14
|
+
secondaryCtaText: PropTypes.string,
|
|
15
|
+
primaryCtaText: PropTypes.string,
|
|
16
|
+
onSecondaryClick: PropTypes.func,
|
|
17
|
+
onPrimaryClick: PropTypes.func,
|
|
18
|
+
onSearchClick: PropTypes.func,
|
|
19
|
+
onCartClick: PropTypes.func,
|
|
20
|
+
onMenuClick: PropTypes.func,
|
|
21
|
+
className: PropTypes.string,
|
|
22
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { CorporateHeader, default } from "./CorporateHeader";
|