@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.
Files changed (61) hide show
  1. package/bin/menus.js +16 -0
  2. package/category.config.json +7 -0
  3. package/package.json +24 -0
  4. package/registry/index.json +74 -0
  5. package/registry/menu-01.json +14 -0
  6. package/registry/menu-02.json +12 -0
  7. package/registry/menu-03.json +12 -0
  8. package/registry/menu-04.json +10 -0
  9. package/registry/menu-05.json +10 -0
  10. package/registry/menu-06.json +10 -0
  11. package/templates/components/atoms/SafeImage/SafeImage.jsx +101 -0
  12. package/templates/components/atoms/SafeImage/index.js +1 -0
  13. package/templates/components/hooks/useCarousel.js +73 -0
  14. package/templates/components/molecules/CorporateHeader/CorporateHeader.jsx +174 -0
  15. package/templates/components/molecules/CorporateHeader/CorporateHeader.propTypes.js +22 -0
  16. package/templates/components/molecules/CorporateHeader/index.js +1 -0
  17. package/templates/components/molecules/EquipmentMegaMenu/EquipmentMegaMenu.jsx +202 -0
  18. package/templates/components/molecules/EquipmentMegaMenu/EquipmentMegaMenu.propTypes.js +104 -0
  19. package/templates/components/molecules/EquipmentMegaMenu/index.js +5 -0
  20. package/templates/components/molecules/FullScreenNavOverlay/FullScreenNavOverlay.jsx +117 -0
  21. package/templates/components/molecules/FullScreenNavOverlay/FullScreenNavOverlay.propTypes.js +34 -0
  22. package/templates/components/molecules/FullScreenNavOverlay/index.js +2 -0
  23. package/templates/components/molecules/MobileNavDrawer/MobileNavDrawer.jsx +139 -0
  24. package/templates/components/molecules/MobileNavDrawer/index.js +2 -0
  25. package/templates/components/molecules/SiteHeader/SiteHeader.jsx +177 -0
  26. package/templates/components/molecules/SiteHeader/SiteHeader.propTypes.js +20 -0
  27. package/templates/components/molecules/SiteHeader/index.js +1 -0
  28. package/templates/components/molecules/SlideIndicators/SlideIndicators.jsx +75 -0
  29. package/templates/components/molecules/SlideIndicators/SlideIndicators.propTypes.js +10 -0
  30. package/templates/components/molecules/SlideIndicators/index.js +1 -0
  31. package/templates/components/organisms/Menu01/Menu01.jsx +211 -0
  32. package/templates/components/organisms/Menu01/Menu01.propTypes.js +63 -0
  33. package/templates/components/organisms/Menu01/index.js +1 -0
  34. package/templates/components/organisms/Menu02/Menu02.jsx +133 -0
  35. package/templates/components/organisms/Menu02/Menu02.propTypes.js +71 -0
  36. package/templates/components/organisms/Menu02/index.js +1 -0
  37. package/templates/components/organisms/Menu03/Menu03.jsx +411 -0
  38. package/templates/components/organisms/Menu03/Menu03.propTypes.js +113 -0
  39. package/templates/components/organisms/Menu03/index.js +1 -0
  40. package/templates/components/organisms/Menu04/Menu04.jsx +232 -0
  41. package/templates/components/organisms/Menu04/Menu04.propTypes.js +53 -0
  42. package/templates/components/organisms/Menu04/index.js +2 -0
  43. package/templates/components/organisms/Menu05/Menu05.jsx +318 -0
  44. package/templates/components/organisms/Menu05/Menu05.propTypes.js +79 -0
  45. package/templates/components/organisms/Menu05/index.js +2 -0
  46. package/templates/components/organisms/Menu06/Menu06.jsx +241 -0
  47. package/templates/components/organisms/Menu06/Menu06.propTypes.js +55 -0
  48. package/templates/components/organisms/Menu06/index.js +2 -0
  49. package/templates/public/menus/menu01/agriculture.png +0 -0
  50. package/templates/public/menus/menu01/banner.jpg +0 -0
  51. package/templates/public/menus/menu01/build-your-own.png +0 -0
  52. package/templates/public/menus/menu01/construction.png +0 -0
  53. package/templates/public/menus/menu01/lawn-care.png +0 -0
  54. package/templates/public/menus/menu01/logo.png +0 -0
  55. package/templates/public/menus/menu01/promotions.png +0 -0
  56. package/templates/public/menus/menu03/logo.png +46 -0
  57. package/templates/public/menus/menu04/featured.jpg +0 -0
  58. package/templates/public/menus/menu04/logo.png +0 -0
  59. package/templates/public/menus/menu05/hero-bg.jpg +0 -0
  60. package/templates/public/menus/menu05/logo.png +0 -0
  61. package/templates/public/menus/menu06/pool.jpg +0 -0
@@ -0,0 +1,177 @@
1
+ "use client";
2
+
3
+ import SafeImage from "../../atoms/SafeImage";
4
+ import { siteHeaderPropTypes } from "./SiteHeader.propTypes";
5
+
6
+ function SearchIcon() {
7
+ return (
8
+ <svg width="20" height="20" viewBox="0 0 20 20" fill="none" aria-hidden="true">
9
+ <circle cx="9" cy="9" r="6" stroke="#212121" strokeWidth="1.5" />
10
+ <path d="M14 14L17.5 17.5" stroke="#212121" strokeWidth="1.5" strokeLinecap="round" />
11
+ </svg>
12
+ );
13
+ }
14
+
15
+ function LocationIcon() {
16
+ return (
17
+ <svg width="16" height="16" viewBox="0 0 16 16" fill="none" aria-hidden="true">
18
+ <path
19
+ d="M8 1.5C5.51472 1.5 3.5 3.51472 3.5 6C3.5 9.5 8 14.5 8 14.5C8 14.5 12.5 9.5 12.5 6C12.5 3.51472 10.4853 1.5 8 1.5Z"
20
+ stroke="#212121"
21
+ strokeWidth="1.2"
22
+ />
23
+ <circle cx="8" cy="6" r="1.5" fill="#212121" />
24
+ </svg>
25
+ );
26
+ }
27
+
28
+ function ChevronDownIcon() {
29
+ return (
30
+ <svg width="16" height="16" viewBox="0 0 16 16" fill="none" aria-hidden="true">
31
+ <path
32
+ d="M4 6L8 10L12 6"
33
+ stroke="#212121"
34
+ strokeWidth="1.2"
35
+ strokeLinecap="round"
36
+ strokeLinejoin="round"
37
+ />
38
+ </svg>
39
+ );
40
+ }
41
+
42
+ function MenuIcon() {
43
+ return (
44
+ <svg width="24" height="24" viewBox="0 0 24 24" fill="none" aria-hidden="true">
45
+ <path d="M4 7H20M4 12H20M4 17H14" stroke="#212121" strokeWidth="1.5" strokeLinecap="round" />
46
+ </svg>
47
+ );
48
+ }
49
+
50
+ function Divider() {
51
+ return <span className="mx-1 hidden h-3 w-px bg-neutral-100 md:block" aria-hidden="true" />;
52
+ }
53
+
54
+ /**
55
+ * SiteHeader — logo, primary navigation, search, and store selector.
56
+ *
57
+ * @param {object} props
58
+ * @param {string} props.logoSrc - Logo image URL.
59
+ * @param {string} [props.logoAlt="Site logo"] - Logo alt text.
60
+ * @param {{ label: string, href?: string, active?: boolean, onClick?: () => void }[]} [props.navItems=[]] - Nav links.
61
+ * @param {string} [props.myDealerLabel="MyDealer"] - MyDealer link label.
62
+ * @param {string} [props.storeLabel="Choose a Store"] - Store selector label.
63
+ * @param {() => void} [props.onStoreClick] - Store selector click handler.
64
+ * @param {() => void} [props.onSearchClick] - Search icon click handler.
65
+ * @param {() => void} [props.onMenuClick] - Mobile menu handler.
66
+ * @param {string} [props.className=""] - Extra classes on the root element.
67
+ */
68
+ export function SiteHeader({
69
+ logoSrc,
70
+ logoAlt = "Site logo",
71
+ navItems = [],
72
+ myDealerLabel = "MyDealer",
73
+ storeLabel = "Choose a Store",
74
+ onStoreClick,
75
+ onSearchClick,
76
+ onMenuClick,
77
+ className = "",
78
+ }) {
79
+ return (
80
+ <header
81
+ className={[
82
+ "flex h-14 w-full shrink-0 items-center justify-between border-b border-neutral-100 bg-white px-4 sm:h-[61px] sm:px-6 md:px-8",
83
+ className,
84
+ ]
85
+ .filter(Boolean)
86
+ .join(" ")}
87
+ >
88
+ <div className="relative h-8 w-40 shrink-0 sm:h-10 sm:w-52 md:h-[50px] md:w-[317px]">
89
+ <SafeImage
90
+ src={logoSrc}
91
+ alt={logoAlt}
92
+ fill
93
+ className="object-contain object-left"
94
+ sizes="(max-width: 768px) 160px, 317px"
95
+ priority
96
+ />
97
+ </div>
98
+
99
+ <div className="flex items-center justify-end gap-2 sm:gap-4 md:gap-[18px]">
100
+ <nav className="hidden items-center gap-2 lg:flex lg:gap-4" aria-label="Primary">
101
+ <ul className="flex items-start gap-2 xl:gap-3">
102
+ {navItems.map((item) => (
103
+ <li key={item.label}>
104
+ {item.href ? (
105
+ <a
106
+ href={item.href}
107
+ className={[
108
+ "inline-flex items-center justify-center px-1.5 py-3 text-sm font-medium leading-6 transition-colors duration-200 ease-out xl:px-2 xl:py-4 xl:text-base",
109
+ item.active
110
+ ? "border-b-2 border-brand-500 text-brand-500"
111
+ : "text-neutral-800 hover:text-brand-500",
112
+ ].join(" ")}
113
+ >
114
+ {item.label}
115
+ </a>
116
+ ) : (
117
+ <button
118
+ type="button"
119
+ onClick={item.onClick}
120
+ className={[
121
+ "inline-flex items-center justify-center px-1.5 py-3 text-sm font-medium leading-6 transition-colors duration-200 ease-out xl:px-2 xl:py-4 xl:text-base",
122
+ item.active
123
+ ? "border-b-2 border-brand-500 text-brand-500"
124
+ : "text-neutral-800 hover:text-brand-500",
125
+ ].join(" ")}
126
+ >
127
+ {item.label}
128
+ </button>
129
+ )}
130
+ </li>
131
+ ))}
132
+ </ul>
133
+
134
+ <Divider />
135
+
136
+ <button
137
+ type="button"
138
+ onClick={onSearchClick}
139
+ aria-label="Search"
140
+ className="flex size-10 items-center justify-center transition-opacity duration-200 ease-out hover:opacity-70"
141
+ >
142
+ <SearchIcon />
143
+ </button>
144
+
145
+ <Divider />
146
+
147
+ <span className="hidden text-sm font-medium leading-6 text-neutral-800 xl:inline xl:text-base">
148
+ {myDealerLabel}
149
+ </span>
150
+ </nav>
151
+
152
+ <button
153
+ type="button"
154
+ onClick={onStoreClick}
155
+ className="hidden items-center gap-1 rounded-pill border border-neutral-100 px-3 py-1.5 text-xs leading-5 text-neutral-800 transition-colors duration-200 ease-out hover:bg-neutral-50 sm:inline-flex sm:px-4 sm:py-2 sm:text-sm"
156
+ >
157
+ <LocationIcon />
158
+ <span className="max-w-[100px] truncate sm:max-w-none">{storeLabel}</span>
159
+ <ChevronDownIcon />
160
+ </button>
161
+
162
+ <button
163
+ type="button"
164
+ onClick={onMenuClick}
165
+ aria-label="Open menu"
166
+ className="flex size-10 items-center justify-center transition-opacity duration-200 ease-out hover:opacity-70 lg:hidden"
167
+ >
168
+ <MenuIcon />
169
+ </button>
170
+ </div>
171
+ </header>
172
+ );
173
+ }
174
+
175
+ SiteHeader.propTypes = siteHeaderPropTypes;
176
+
177
+ export default SiteHeader;
@@ -0,0 +1,20 @@
1
+ import PropTypes from "prop-types";
2
+
3
+ const navItemShape = PropTypes.shape({
4
+ label: PropTypes.string.isRequired,
5
+ href: PropTypes.string,
6
+ active: PropTypes.bool,
7
+ onClick: PropTypes.func,
8
+ });
9
+
10
+ export const siteHeaderPropTypes = {
11
+ logoSrc: PropTypes.string.isRequired,
12
+ logoAlt: PropTypes.string,
13
+ navItems: PropTypes.arrayOf(navItemShape),
14
+ myDealerLabel: PropTypes.string,
15
+ storeLabel: PropTypes.string,
16
+ onStoreClick: PropTypes.func,
17
+ onSearchClick: PropTypes.func,
18
+ onMenuClick: PropTypes.func,
19
+ className: PropTypes.string,
20
+ };
@@ -0,0 +1 @@
1
+ export { SiteHeader, default } from "./SiteHeader";
@@ -0,0 +1,75 @@
1
+ import { slideIndicatorsPropTypes } from "./SlideIndicators.propTypes";
2
+
3
+ /**
4
+ * SlideIndicators — animated dot/tab indicators for carousels.
5
+ *
6
+ * @param {object} props
7
+ * @param {number} props.count - Total slides.
8
+ * @param {number} props.activeIndex - Current active slide (0-based).
9
+ * @param {(index: number) => void} [props.onSelect] - Click handler per dot.
10
+ * @param {string} [props.className=""] - Extra classes on the root.
11
+ */
12
+ export function SlideIndicators({
13
+ count,
14
+ activeIndex,
15
+ onSelect,
16
+ orientation = "horizontal",
17
+ variant = "dot",
18
+ className = "",
19
+ }) {
20
+ const isVertical = orientation === "vertical";
21
+
22
+ return (
23
+ <div
24
+ className={[
25
+ "flex items-center",
26
+ isVertical ? "flex-col gap-2" : "gap-1.5",
27
+ className,
28
+ ]
29
+ .filter(Boolean)
30
+ .join(" ")}
31
+ role="tablist"
32
+ aria-label="Slides"
33
+ aria-orientation={isVertical ? "vertical" : "horizontal"}
34
+ >
35
+ {Array.from({ length: count }).map((_, index) => {
36
+ const isActive = index === activeIndex;
37
+ const Tag = onSelect ? "button" : "span";
38
+
39
+ let sizeClass = "size-1 bg-white/30 hover:bg-white/50";
40
+ if (variant === "dot") {
41
+ sizeClass = isActive ? "h-1.5 w-4 bg-white" : "size-1 bg-white/30 hover:bg-white/50";
42
+ } else if (variant === "bar") {
43
+ sizeClass = isActive
44
+ ? isVertical
45
+ ? "h-[30px] w-1 bg-white/80"
46
+ : "h-1 w-8 bg-white/80"
47
+ : isVertical
48
+ ? "h-[30px] w-1 bg-white/40"
49
+ : "h-1 w-4 bg-white/40";
50
+ } else if (variant === "pill") {
51
+ sizeClass = isActive ? "size-2 bg-white" : "size-2 bg-white/40";
52
+ }
53
+
54
+ return (
55
+ <Tag
56
+ key={index}
57
+ type={onSelect ? "button" : undefined}
58
+ role="tab"
59
+ aria-selected={isActive}
60
+ aria-label={onSelect ? `Go to slide ${index + 1}` : undefined}
61
+ onClick={onSelect ? () => onSelect(index) : undefined}
62
+ className={[
63
+ "rounded-full transition-all duration-300 ease-out motion-reduce:transition-none",
64
+ sizeClass,
65
+ ].join(" ")}
66
+ />
67
+ );
68
+ })}
69
+ </div>
70
+ );
71
+ }
72
+
73
+ SlideIndicators.propTypes = slideIndicatorsPropTypes;
74
+
75
+ export default SlideIndicators;
@@ -0,0 +1,10 @@
1
+ import PropTypes from "prop-types";
2
+
3
+ export const slideIndicatorsPropTypes = {
4
+ count: PropTypes.number.isRequired,
5
+ activeIndex: PropTypes.number.isRequired,
6
+ onSelect: PropTypes.func,
7
+ orientation: PropTypes.oneOf(["horizontal", "vertical"]),
8
+ variant: PropTypes.oneOf(["dot", "bar", "pill"]),
9
+ className: PropTypes.string,
10
+ };
@@ -0,0 +1 @@
1
+ export { SlideIndicators } from "./SlideIndicators";
@@ -0,0 +1,211 @@
1
+ "use client";
2
+
3
+ import { useCallback, useEffect, useMemo, useState } from "react";
4
+ import SafeImage from "../../atoms/SafeImage";
5
+ import { useCarousel } from "../../hooks/useCarousel";
6
+ import { SiteHeader } from "../../molecules/SiteHeader";
7
+ import { EquipmentMegaMenu } from "../../molecules/EquipmentMegaMenu";
8
+ import { SlideIndicators } from "../../molecules/SlideIndicators";
9
+ import { MobileNavDrawer } from "../../molecules/MobileNavDrawer";
10
+ import { menu01DefaultProps, menu01PropTypes } from "./Menu01.propTypes";
11
+
12
+ /**
13
+ * Menu01 — Koenig Equipment mega menu open state with hero banner behind.
14
+ *
15
+ * @param {object} props - See Menu01.propTypes.js for full prop list.
16
+ */
17
+ export function Menu01({
18
+ logoSrc = menu01DefaultProps.logoSrc,
19
+ logoAlt = menu01DefaultProps.logoAlt,
20
+ navItems = menu01DefaultProps.navItems,
21
+ myDealerLabel = menu01DefaultProps.myDealerLabel,
22
+ storeLabel = menu01DefaultProps.storeLabel,
23
+ onMenuClick,
24
+ megaMenuOpen: megaMenuOpenProp,
25
+ defaultMegaMenuOpen = menu01DefaultProps.defaultMegaMenuOpen,
26
+ onMegaMenuChange,
27
+ bannerImage = menu01DefaultProps.bannerImage,
28
+ bannerAlt = menu01DefaultProps.bannerAlt,
29
+ headline = menu01DefaultProps.headline,
30
+ subheadline = menu01DefaultProps.subheadline,
31
+ description = menu01DefaultProps.description,
32
+ ctaText = menu01DefaultProps.ctaText,
33
+ onCtaClick,
34
+ categories = menu01DefaultProps.categories,
35
+ slideCount = menu01DefaultProps.slideCount,
36
+ activeSlide: activeSlideProp = menu01DefaultProps.activeSlide,
37
+ onSlideChange,
38
+ className = "",
39
+ }) {
40
+ const [mobileMenuOpen, setMobileMenuOpen] = useState(false);
41
+ const [internalMegaOpen, setInternalMegaOpen] = useState(defaultMegaMenuOpen);
42
+
43
+ const isMegaControlled = megaMenuOpenProp !== undefined;
44
+ const megaMenuOpen = isMegaControlled ? megaMenuOpenProp : internalMegaOpen;
45
+
46
+ const setMegaMenuOpen = useCallback(
47
+ (next) => {
48
+ const value = typeof next === "function" ? next(megaMenuOpen) : next;
49
+ if (!isMegaControlled) setInternalMegaOpen(value);
50
+ onMegaMenuChange?.(value);
51
+ },
52
+ [isMegaControlled, megaMenuOpen, onMegaMenuChange]
53
+ );
54
+
55
+ const toggleMegaMenu = useCallback(() => {
56
+ setMegaMenuOpen((open) => !open);
57
+ }, [setMegaMenuOpen]);
58
+
59
+ const headerNavItems = useMemo(
60
+ () =>
61
+ navItems.map((item) => {
62
+ if (item.label === "New Equipment") {
63
+ return {
64
+ ...item,
65
+ active: megaMenuOpen,
66
+ onClick: () => {
67
+ item.onClick?.();
68
+ toggleMegaMenu();
69
+ },
70
+ };
71
+ }
72
+ return item;
73
+ }),
74
+ [navItems, megaMenuOpen, toggleMegaMenu]
75
+ );
76
+
77
+ const drawerNavItems = useMemo(
78
+ () =>
79
+ navItems.map((item) =>
80
+ item.label === "New Equipment"
81
+ ? { ...item, onClick: toggleMegaMenu }
82
+ : item
83
+ ),
84
+ [navItems, toggleMegaMenu]
85
+ );
86
+
87
+ const { activeSlide, goTo, pause, resume } = useCarousel({
88
+ count: slideCount,
89
+ initialIndex: activeSlideProp,
90
+ autoPlayMs: 6000,
91
+ loop: true,
92
+ onChange: onSlideChange,
93
+ });
94
+
95
+ useEffect(() => {
96
+ goTo(activeSlideProp);
97
+ }, [activeSlideProp, goTo]);
98
+
99
+ const subheadlineLines = subheadline.split("\n");
100
+
101
+ return (
102
+ <section
103
+ className={["relative flex w-full flex-col", className].filter(Boolean).join(" ")}
104
+ data-menu="menu01"
105
+ onMouseEnter={pause}
106
+ onMouseLeave={resume}
107
+ >
108
+ <div className="relative z-20 w-full shrink-0 bg-white">
109
+ <SiteHeader
110
+ logoSrc={logoSrc}
111
+ logoAlt={logoAlt}
112
+ navItems={headerNavItems}
113
+ myDealerLabel={myDealerLabel}
114
+ storeLabel={storeLabel}
115
+ onMenuClick={() => {
116
+ setMobileMenuOpen(true);
117
+ onMenuClick?.();
118
+ }}
119
+ />
120
+ <EquipmentMegaMenu
121
+ isOpen={megaMenuOpen}
122
+ onClose={() => setMegaMenuOpen(false)}
123
+ />
124
+ </div>
125
+
126
+ <MobileNavDrawer
127
+ isOpen={mobileMenuOpen}
128
+ onClose={() => setMobileMenuOpen(false)}
129
+ navItems={drawerNavItems}
130
+ accentColor="#367c2b"
131
+ />
132
+
133
+ <div className="relative min-h-[60vh] w-full overflow-hidden md:min-h-[700px] lg:min-h-[896px]">
134
+ <SafeImage
135
+ src={bannerImage}
136
+ alt={bannerAlt}
137
+ fill
138
+ className="object-cover object-center transition-opacity duration-700 ease-out"
139
+ sizes="100vw"
140
+ priority
141
+ />
142
+
143
+ <SlideIndicators
144
+ count={slideCount}
145
+ activeIndex={activeSlide}
146
+ onSelect={goTo}
147
+ orientation="vertical"
148
+ variant="bar"
149
+ className="absolute right-4 top-1/2 hidden -translate-y-1/2 sm:flex md:right-8 lg:right-12"
150
+ />
151
+
152
+ <div className="absolute left-1/2 top-[calc(50%+57px)] flex w-max max-w-[calc(100%-2rem)] -translate-x-1/2 -translate-y-1/2 animate-fade-in flex-col items-start justify-center gap-4 px-4 sm:gap-5 sm:px-0 md:gap-[22px]">
153
+ <p className="text-left text-4xl font-bold leading-none text-white/80 sm:text-6xl md:text-8xl lg:text-[120px] xl:text-[150px] xl:leading-[74px]">
154
+ {headline}
155
+ </p>
156
+ <div className="flex flex-col items-start gap-2 text-left sm:gap-3">
157
+ <p className="text-xl font-bold leading-snug text-white/80 sm:text-2xl md:text-3xl lg:text-[42px] lg:leading-9">
158
+ {subheadlineLines.map((line, i) => (
159
+ <span key={line}>
160
+ {line}
161
+ {i < subheadlineLines.length - 1 ? <br /> : null}
162
+ </span>
163
+ ))}
164
+ </p>
165
+ <p className="text-base font-medium leading-relaxed text-white sm:text-lg md:text-xl lg:text-2xl lg:leading-[54px]">
166
+ {description}
167
+ </p>
168
+ </div>
169
+ </div>
170
+
171
+ <button
172
+ type="button"
173
+ onClick={onCtaClick}
174
+ className="absolute bottom-32 left-1/2 -translate-x-1/2 rounded-pill bg-accent-yellow px-5 py-2.5 text-base font-semibold leading-6 text-neutral-800 transition-colors duration-200 ease-out hover:bg-accent-yellow-hover sm:bottom-36 sm:px-6 sm:py-3 sm:text-lg md:bottom-[190px]"
175
+ >
176
+ {ctaText}
177
+ </button>
178
+
179
+ <div className="absolute bottom-0 left-0 flex min-h-[120px] w-full flex-col items-center justify-end bg-gradient-to-b from-transparent via-black/80 via-[77.604%] to-black/80 sm:min-h-[160px] lg:h-[195px]">
180
+ <div className="grid w-full max-w-[1410px] grid-cols-2 gap-0 sm:flex sm:flex-1 sm:items-end lg:grid-cols-none lg:flex">
181
+ {categories.map((category, index) => (
182
+ <div key={category} className="flex flex-1 items-end">
183
+ {index > 0 ? (
184
+ <span
185
+ className="hidden h-full w-px shrink-0 self-stretch bg-white/20 sm:block"
186
+ aria-hidden="true"
187
+ />
188
+ ) : null}
189
+ <button
190
+ type="button"
191
+ className="flex min-h-[48px] flex-1 flex-col items-center justify-end px-2 pb-6 text-center text-sm font-bold leading-snug text-white/80 transition-colors duration-200 ease-out hover:text-white sm:min-h-[60px] sm:px-4 sm:pb-10 sm:text-base md:text-lg lg:pb-[50px] lg:text-xl lg:leading-7"
192
+ >
193
+ {category.split("\n").map((line, lineIndex) => (
194
+ <span key={lineIndex}>
195
+ {line}
196
+ {lineIndex < category.split("\n").length - 1 ? <br /> : null}
197
+ </span>
198
+ ))}
199
+ </button>
200
+ </div>
201
+ ))}
202
+ </div>
203
+ </div>
204
+ </div>
205
+ </section>
206
+ );
207
+ }
208
+
209
+ Menu01.propTypes = menu01PropTypes;
210
+
211
+ export default Menu01;
@@ -0,0 +1,63 @@
1
+ import PropTypes from "prop-types";
2
+
3
+ const navItemShape = PropTypes.shape({
4
+ label: PropTypes.string.isRequired,
5
+ href: PropTypes.string,
6
+ active: PropTypes.bool,
7
+ onClick: PropTypes.func,
8
+ });
9
+
10
+ export const menu01PropTypes = {
11
+ logoSrc: PropTypes.string,
12
+ logoAlt: PropTypes.string,
13
+ navItems: PropTypes.arrayOf(navItemShape),
14
+ myDealerLabel: PropTypes.string,
15
+ storeLabel: PropTypes.string,
16
+ onMenuClick: PropTypes.func,
17
+ megaMenuOpen: PropTypes.bool,
18
+ defaultMegaMenuOpen: PropTypes.bool,
19
+ onMegaMenuChange: PropTypes.func,
20
+ bannerImage: PropTypes.string,
21
+ bannerAlt: PropTypes.string,
22
+ headline: PropTypes.string,
23
+ subheadline: PropTypes.string,
24
+ description: PropTypes.string,
25
+ ctaText: PropTypes.string,
26
+ onCtaClick: PropTypes.func,
27
+ categories: PropTypes.arrayOf(PropTypes.string),
28
+ slideCount: PropTypes.number,
29
+ activeSlide: PropTypes.number,
30
+ onSlideChange: PropTypes.func,
31
+ className: PropTypes.string,
32
+ };
33
+
34
+ export const menu01DefaultProps = {
35
+ logoSrc: "/menus/menu01/logo.png",
36
+ logoAlt: "John Deere and Koenig Equipment",
37
+ navItems: [
38
+ { label: "New Equipment", active: true },
39
+ { label: "Used Equipment", href: "#" },
40
+ { label: "Parts & Services", href: "#" },
41
+ { label: "Contact Us", href: "#" },
42
+ { label: "Resources", href: "#" },
43
+ ],
44
+ myDealerLabel: "MyDealer",
45
+ storeLabel: "Choose a Store",
46
+ defaultMegaMenuOpen: false,
47
+ bannerImage: "/menus/menu01/banner.jpg",
48
+ bannerAlt: "John Deere compact tractor in a field",
49
+ headline: "0%",
50
+ subheadline: "FOR 84 MONTHS\nOR save up to $3,400",
51
+ description: "ON SELECT COMPACT TRACTORS",
52
+ ctaText: "Learn More",
53
+ categories: [
54
+ "Large Property\nOwner",
55
+ "Hay & Forage",
56
+ "Production Ag",
57
+ "Homeowner",
58
+ "Professional\nLandscaper",
59
+ "Construction\n& Contractor",
60
+ ],
61
+ slideCount: 6,
62
+ activeSlide: 0,
63
+ };
@@ -0,0 +1 @@
1
+ export { Menu01, default } from "./Menu01";