@stackshift-ui/navigation 6.0.13 → 6.1.0-alpha.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 (56) hide show
  1. package/dist/chunk-4N6ET2I6.mjs +1 -0
  2. package/dist/{chunk-YNDJK4RH.mjs → chunk-67XVPRGX.mjs} +1 -1
  3. package/dist/chunk-6QSBJPEW.mjs +1 -0
  4. package/dist/{chunk-6XDFQS4J.mjs → chunk-EASZP4S2.mjs} +1 -1
  5. package/dist/{chunk-DJV2UTMZ.mjs → chunk-LBUYM4NV.mjs} +1 -1
  6. package/dist/chunk-MSVUIX4E.mjs +1 -0
  7. package/dist/chunk-OXF5VEOU.mjs +1 -0
  8. package/dist/chunk-P3TZNIE6.mjs +1 -0
  9. package/dist/chunk-PCTXJCVP.mjs +1 -0
  10. package/dist/{chunk-KB2HS5UA.mjs → chunk-SVLHEWIY.mjs} +1 -1
  11. package/dist/chunk-V7KKZJMX.mjs +1 -0
  12. package/dist/context/megaNavContext.d.ts +14 -0
  13. package/dist/context/megaNavContext.mjs +1 -0
  14. package/dist/helper/index.d.ts +2 -1
  15. package/dist/helper/index.mjs +1 -1
  16. package/dist/helper.d.ts +6 -0
  17. package/dist/helper.mjs +1 -0
  18. package/dist/index.d.ts +3 -0
  19. package/dist/index.mjs +1 -1
  20. package/dist/navigation.d.ts +9 -1
  21. package/dist/navigation.mjs +1 -1
  22. package/dist/navigation_a.mjs +1 -1
  23. package/dist/navigation_b.mjs +1 -1
  24. package/dist/navigation_c.mjs +1 -1
  25. package/dist/navigation_d.mjs +1 -1
  26. package/dist/navigation_e.mjs +1 -1
  27. package/dist/navigation_f.d.ts +3 -0
  28. package/dist/navigation_f.mjs +1 -0
  29. package/dist/navigation_g.d.ts +3 -0
  30. package/dist/navigation_g.mjs +1 -0
  31. package/dist/navigation_h.d.ts +3 -0
  32. package/dist/navigation_h.mjs +1 -0
  33. package/dist/types.d.ts +104 -1
  34. package/package.json +14 -6
  35. package/src/context/megaNavContext.tsx +78 -0
  36. package/src/helper/index.tsx +130 -0
  37. package/src/helper.ts +14 -0
  38. package/src/index.ts +3 -0
  39. package/src/navigation.tsx +29 -1
  40. package/src/navigation_f.tsx +531 -0
  41. package/src/navigation_g.tsx +607 -0
  42. package/src/navigation_h.tsx +742 -0
  43. package/src/types.ts +113 -1
  44. package/dist/chunk-KQFO7OZP.mjs +0 -1
  45. package/dist/chunk-TTJMPYYC.mjs +0 -1
  46. package/dist/helper/blockStyle.js +0 -1
  47. package/dist/helper/index.js +0 -1
  48. package/dist/index.js +0 -2
  49. package/dist/navigation.js +0 -1
  50. package/dist/navigation_a.js +0 -1
  51. package/dist/navigation_b.js +0 -1
  52. package/dist/navigation_c.js +0 -1
  53. package/dist/navigation_d.js +0 -1
  54. package/dist/navigation_e.js +0 -1
  55. package/dist/types.js +0 -1
  56. package/src/helper/index.ts +0 -14
@@ -0,0 +1,531 @@
1
+ import { Container } from "@stackshift-ui/container";
2
+ import { Flex } from "@stackshift-ui/flex";
3
+ import { Image } from "@stackshift-ui/image";
4
+ import { Link } from "@stackshift-ui/link";
5
+ import { Section } from "@stackshift-ui/section";
6
+ import React, { useEffect, useRef, useState } from "react";
7
+ import { BiArrowBack, BiChevronDown, BiMenu } from "react-icons/bi";
8
+ import { BsFillTelephoneFill, BsTelephone } from "react-icons/bs";
9
+ import { MdClose } from "react-icons/md";
10
+ import { NavigationProps } from ".";
11
+ import { ConditionalLink, logoLink } from "./helper/index";
12
+ import { LabeledRouteWithKey } from "./types";
13
+
14
+ // Types
15
+ type DropdownRef = React.RefObject<HTMLDivElement>;
16
+
17
+ interface DropdownState {
18
+ previous: number | null;
19
+ index: number | null;
20
+ }
21
+
22
+ // Components
23
+ const NavigationLogo = ({ logo, hasScrolled }: { logo: any; hasScrolled: boolean }) => {
24
+ if (!logo?.image) return null;
25
+
26
+ return (
27
+ <Link
28
+ aria-label={`Go to ${logoLink(logo) === "/" ? "home page" : logoLink(logo)}`}
29
+ className="text-3xl"
30
+ href={logoLink(logo)}
31
+ target={logo?.linkTarget}
32
+ rel={logo?.linkTarget === "_blank" ? "noopener noreferrer" : ""}>
33
+ <Image
34
+ src={
35
+ hasScrolled
36
+ ? logo?.image
37
+ : "https://cdn.sanity.io/images/9itgab5x/staging/7f9353c628ae4dd0bdd479d3b1407a3c242755e8-1963x833.png?fm=webp&w=96&q=75"
38
+ }
39
+ width={500}
40
+ height={500}
41
+ alt={logo?.alt ?? "navigation-logo"}
42
+ className={`transition-all duration-500 w-28 ${hasScrolled ? "md:w-40" : "md:w-44"}`}
43
+ />
44
+ </Link>
45
+ );
46
+ };
47
+
48
+ const NavigationButton = ({ button, hasScrolled }: { button: any; hasScrolled: boolean }) => {
49
+ if (!button?.label) return null;
50
+
51
+ return (
52
+ <ConditionalLink
53
+ ariaLabel={button?.label}
54
+ link={button}
55
+ target={button?.linkTarget}
56
+ className={`font-body ml-auto p-2.5 md:p-3 transition duration-200 font-medium flex items-center space-x-2 text-left ${
57
+ hasScrolled
58
+ ? "text-white border border-white bg-transparent hover:bg-white hover:text-black"
59
+ : "text-white bg-secondary hover:text-secondary hover:border hover:border-black/40 hover:bg-white"
60
+ }`}>
61
+ <span className="flex-none text-3xl sm:text-xl">
62
+ {hasScrolled ? (
63
+ <BsTelephone className="text-lg md:text-xl" />
64
+ ) : (
65
+ <BsFillTelephoneFill className="text-lg md:text-xl" />
66
+ )}
67
+ </span>
68
+ <span className="text-sm md:text-base sm:block hidden whitespace-nowrap">
69
+ {button?.label}
70
+ </span>
71
+ </ConditionalLink>
72
+ );
73
+ };
74
+
75
+ const MobileMenuButton = ({
76
+ hasScrolled,
77
+ menu,
78
+ showMenu,
79
+ }: {
80
+ hasScrolled: boolean;
81
+ menu: boolean;
82
+ showMenu: (e: React.MouseEvent) => void;
83
+ }) => (
84
+ <div>
85
+ <BiMenu
86
+ color={hasScrolled ? "white" : "black"}
87
+ size={40}
88
+ onClick={showMenu}
89
+ className={`cursor-pointer hover:scale-105 transition lg:hidden block ${
90
+ menu ? "hidden" : "block"
91
+ }`}
92
+ />
93
+ <MdClose
94
+ color={hasScrolled ? "white" : "black"}
95
+ size={40}
96
+ onClick={showMenu}
97
+ className={`cursor-pointer hover:scale-105 transition lg:hidden block ${
98
+ menu ? "block" : "hidden"
99
+ }`}
100
+ />
101
+ </div>
102
+ );
103
+
104
+ export default function Navigation_F({
105
+ primaryButton,
106
+ secondaryButton,
107
+ logo,
108
+ multipleLinks,
109
+ title,
110
+ }: NavigationProps) {
111
+ const [dropdown, setDropdown] = useState<number | null>(null);
112
+ const dropdownRef = useRef<HTMLDivElement>(null);
113
+ const [menu, setMenu] = useState(false);
114
+ const [hasScrolled, setHasScrolled] = useState(false);
115
+
116
+ const handleClickOutside = (event: MouseEvent) => {
117
+ if (dropdownRef.current && !dropdownRef.current.contains(event.target as Node)) {
118
+ setDropdown(null);
119
+ }
120
+ };
121
+
122
+ useEffect(() => {
123
+ const handleDocumentClick = (event: MouseEvent) => {
124
+ handleClickOutside(event);
125
+ };
126
+
127
+ document.addEventListener("click", handleDocumentClick);
128
+ return () => {
129
+ document.removeEventListener("click", handleDocumentClick);
130
+ };
131
+ }, []);
132
+
133
+ const showMenu = (e: React.MouseEvent) => {
134
+ if (e) {
135
+ e.stopPropagation();
136
+ }
137
+ setMenu(prevState => !prevState);
138
+ };
139
+
140
+ useEffect(() => {
141
+ const handleScroll = () => {
142
+ setHasScrolled(window.scrollY > 0);
143
+ };
144
+
145
+ window.addEventListener("scroll", handleScroll);
146
+ return () => window.removeEventListener("scroll", handleScroll);
147
+ }, []);
148
+
149
+ return (
150
+ <>
151
+ <Section
152
+ className={`fixed !z-[999999] w-full left-0 transition-all top-0 ${
153
+ hasScrolled ? "bg-black/80" : "bg-background"
154
+ }`}>
155
+ <Container>
156
+ <div className="flex justify-between space-x-5 py-2 items-center">
157
+ <NavigationLogo logo={logo} hasScrolled={hasScrolled} />
158
+
159
+ <Flex direction="row" align="center" className="hidden lg:flex gap-x-8">
160
+ {multipleLinks?.map((link: LabeledRouteWithKey, index: number) => (
161
+ <NavigationLink
162
+ key={index}
163
+ link={link}
164
+ index={index}
165
+ dropdown={dropdown}
166
+ setDropdown={setDropdown}
167
+ hasScrolled={hasScrolled}
168
+ dropdownRef={dropdownRef}
169
+ />
170
+ ))}
171
+ </Flex>
172
+
173
+ <Flex direction="row" align="center" gap={2}>
174
+ {(primaryButton?.label || secondaryButton?.label) && (
175
+ <div className="flex space-x-4 sm:space-x-6 items-center">
176
+ <NavigationButton button={primaryButton} hasScrolled={hasScrolled} />
177
+ </div>
178
+ )}
179
+ <MobileMenuButton hasScrolled={hasScrolled} menu={menu} showMenu={showMenu} />
180
+ </Flex>
181
+ </div>
182
+ </Container>
183
+ <Drawer links={multipleLinks} open={menu} showMenu={showMenu} />
184
+ </Section>
185
+ </>
186
+ );
187
+ }
188
+
189
+ const NavigationLink = ({
190
+ link,
191
+ index,
192
+ dropdown,
193
+ setDropdown,
194
+ hasScrolled,
195
+ dropdownRef,
196
+ }: {
197
+ link: LabeledRouteWithKey;
198
+ index: number;
199
+ dropdown: number | null;
200
+ setDropdown: (value: number | null) => void;
201
+ hasScrolled: boolean;
202
+ dropdownRef: DropdownRef;
203
+ }) => {
204
+ if (!link?.multipleRoutes) {
205
+ return (
206
+ <ConditionalLink
207
+ ariaLabel={link?.label || ""}
208
+ link={link}
209
+ target={link?.linkTarget}
210
+ className={`hover:text-secondary font-body text-lg whitespace-nowrap ${
211
+ hasScrolled ? "text-white" : "text-black"
212
+ }`}>
213
+ {link?.label}
214
+ </ConditionalLink>
215
+ );
216
+ }
217
+
218
+ const handleDropdownChange = (newIndex: number | null) => {
219
+ setDropdown(newIndex);
220
+ };
221
+
222
+ return (
223
+ <div className="relative">
224
+ {link?.internalLink || link?.externalLink ? (
225
+ <Flex
226
+ onMouseEnter={(e: React.MouseEvent) => {
227
+ e.stopPropagation();
228
+ handleDropdownChange(dropdown === index ? null : index);
229
+ }}>
230
+ <ConditionalLink
231
+ ariaLabel={link?.label || ""}
232
+ link={link}
233
+ target={link?.linkTarget}
234
+ className={`hover:text-secondary font-body text-lg cursor-pointer ${
235
+ hasScrolled ? "text-white" : "text-black"
236
+ }`}>
237
+ {link?.label}
238
+ </ConditionalLink>
239
+
240
+ <BiChevronDown
241
+ size={24}
242
+ className={`font-bold mt-0.5 font-body cursor-pointer hover:text-secondary hover:translate-y-0.5 transition ${
243
+ hasScrolled ? "text-white" : "text-black"
244
+ } ${dropdown === index && "translate-y-0.5 !text-secondary"}`}
245
+ onClick={(e: React.MouseEvent) => {
246
+ e.stopPropagation();
247
+ handleDropdownChange(index);
248
+ }}
249
+ />
250
+ </Flex>
251
+ ) : (
252
+ <p
253
+ className={`hover:text-secondary font-body text-lg cursor-pointer flex items-center group ${
254
+ hasScrolled ? "text-white" : "text-black"
255
+ }`}
256
+ onClick={(e: React.MouseEvent) => {
257
+ e.stopPropagation();
258
+ handleDropdownChange(dropdown === index ? null : index);
259
+ }}
260
+ onMouseEnter={(e: React.MouseEvent) => {
261
+ e.stopPropagation();
262
+ handleDropdownChange(dropdown === index ? null : index);
263
+ }}>
264
+ {link?.label}
265
+ <BiChevronDown
266
+ size={24}
267
+ className={`font-bold mt-0.5 group-hover:translate-y-0.5 transition ${
268
+ dropdown === index && "translate-y-0.5 text-secondary"
269
+ }`}
270
+ />
271
+ </p>
272
+ )}
273
+
274
+ {dropdown === index && (
275
+ <DropdownMenu
276
+ link={link}
277
+ dropdownRef={dropdownRef}
278
+ setDropdown={setDropdown}
279
+ hasScrolled={hasScrolled}
280
+ />
281
+ )}
282
+ </div>
283
+ );
284
+ };
285
+
286
+ const DropdownMenu = ({
287
+ link,
288
+ dropdownRef,
289
+ setDropdown,
290
+ hasScrolled,
291
+ }: {
292
+ link: LabeledRouteWithKey;
293
+ dropdownRef: DropdownRef;
294
+ setDropdown: (value: number | null) => void;
295
+ hasScrolled: boolean;
296
+ }) => (
297
+ <div
298
+ ref={dropdownRef}
299
+ className="absolute flex flex-col top-8 bg-primary rounded rounded-tl-none"
300
+ onMouseLeave={() => setDropdown(null)}>
301
+ <span className="absolute top-0 w-0 h-0 border-x-transparent border-t-transparent border-b-primary border-[6px] transform -translate-y-3" />
302
+ {link?.multipleRoutes?.map((mLink, idx) => (
303
+ <DropdownMenuItem key={idx} mLink={mLink} hasScrolled={hasScrolled} />
304
+ ))}
305
+ </div>
306
+ );
307
+
308
+ const DropdownMenuItem = ({
309
+ mLink,
310
+ hasScrolled,
311
+ }: {
312
+ mLink: LabeledRouteWithKey;
313
+ hasScrolled: boolean;
314
+ }) => {
315
+ if (mLink?.multipleInnerRoutes) {
316
+ return (
317
+ <div className="relative group">
318
+ {mLink?.internalLink || mLink?.externalLink ? (
319
+ <Flex align="center">
320
+ <ConditionalLink
321
+ ariaLabel={mLink?.label || ""}
322
+ link={mLink}
323
+ target={mLink?.linkTarget}
324
+ className="font-body hover:text-secondary text-lg cursor-pointer py-2 pl-4 text-white">
325
+ {mLink?.label}
326
+ </ConditionalLink>
327
+ <BiChevronDown
328
+ size={24}
329
+ className="font-bold mt-0.5 cursor-pointer group-hover:translate-y-0.5 transition group-hover:text-secondary text-white"
330
+ />
331
+ </Flex>
332
+ ) : (
333
+ <p
334
+ className={`hover:text-secondary text-lg cursor-pointer flex items-center group py-2 px-4 ${
335
+ hasScrolled ? "text-white" : "text-black"
336
+ }`}>
337
+ {mLink?.label}
338
+ <BiChevronDown
339
+ size={24}
340
+ className="font-bold mt-0.5 group-hover:translate-y-0.5 transition"
341
+ />
342
+ </p>
343
+ )}
344
+ <div className="absolute hidden flex-col top-0 left-full bg-primary rounded-r rounded-r-b group-hover:flex hover:flex">
345
+ {mLink?.multipleInnerRoutes?.map((innerLink: LabeledRouteWithKey, idx: number) => (
346
+ <ConditionalLink
347
+ key={idx}
348
+ ariaLabel={innerLink?.label || ""}
349
+ link={innerLink}
350
+ target={innerLink?.linkTarget}
351
+ className="hover:text-secondary font-body text-lg py-2 px-4 whitespace-nowrap text-white">
352
+ {innerLink?.label}
353
+ </ConditionalLink>
354
+ ))}
355
+ </div>
356
+ </div>
357
+ );
358
+ }
359
+
360
+ return (
361
+ <ConditionalLink
362
+ ariaLabel={mLink?.label || ""}
363
+ link={mLink}
364
+ target={mLink?.linkTarget}
365
+ className="hover:text-secondary font-body text-lg py-2 px-4 whitespace-nowrap text-white">
366
+ {mLink?.label}
367
+ </ConditionalLink>
368
+ );
369
+ };
370
+
371
+ const Drawer = ({
372
+ links,
373
+ open,
374
+ showMenu,
375
+ }: {
376
+ links: LabeledRouteWithKey[];
377
+ open: boolean;
378
+ showMenu: (e: React.MouseEvent) => void;
379
+ }) => {
380
+ const [hasScrolled, setHasScrolled] = useState(false);
381
+ const drawerRef = useRef<HTMLDivElement>(null);
382
+
383
+ useEffect(() => {
384
+ const handleScroll = () => setHasScrolled(window.scrollY > 0);
385
+ window.addEventListener("scroll", handleScroll);
386
+ return () => window.removeEventListener("scroll", handleScroll);
387
+ }, []);
388
+
389
+ useEffect(() => {
390
+ const handleClickOutside = (event: MouseEvent) => {
391
+ if (
392
+ drawerRef.current &&
393
+ !drawerRef.current.contains(event.target as Node) &&
394
+ !(event.target as Element).closest(".BiMenu")
395
+ ) {
396
+ if (open) {
397
+ showMenu(event as unknown as React.MouseEvent);
398
+ }
399
+ }
400
+ };
401
+
402
+ document.addEventListener("click", handleClickOutside);
403
+ return () => document.removeEventListener("click", handleClickOutside);
404
+ }, [open, showMenu]);
405
+
406
+ const [showInnerRoutes, setShowInnerRoutes] = useState<DropdownState>({
407
+ previous: null,
408
+ index: null,
409
+ });
410
+
411
+ const handleRouteClick = (previous: number | null, index: number | null) => {
412
+ setShowInnerRoutes({ previous, index });
413
+ };
414
+
415
+ const renderLink = (
416
+ link: LabeledRouteWithKey,
417
+ idx: number,
418
+ hasChevron = false,
419
+ onClick?: () => void,
420
+ ) => {
421
+ const isNewConstruction = link?.label === "New Construction";
422
+ const isRenovation = link?.label === "Renovations" || link?.label === "Renovation";
423
+
424
+ return (
425
+ <div className="flex items-center py-2 px-4" key={idx}>
426
+ {link?.internalLink || link?.externalLink ? (
427
+ isNewConstruction || isRenovation ? (
428
+ <p
429
+ className={`text-xl font-body ${
430
+ hasScrolled ? "text-white" : "text-black"
431
+ } hover:text-secondary cursor-pointer`}
432
+ onClick={() =>
433
+ (window.location.href = `/projects/residential?filter=${
434
+ isNewConstruction ? "new_construction" : "renovations"
435
+ }`)
436
+ }>
437
+ {link?.label}
438
+ </p>
439
+ ) : (
440
+ <ConditionalLink
441
+ ariaLabel={link?.label || ""}
442
+ link={link}
443
+ target={link?.linkTarget}
444
+ className={`text-xl font-body ${
445
+ hasScrolled ? "text-white" : "text-black"
446
+ } hover:text-secondary`}>
447
+ <p className="w-full h-full" onClick={showMenu}>
448
+ {link?.label}
449
+ </p>
450
+ </ConditionalLink>
451
+ )
452
+ ) : (
453
+ <p
454
+ className={`text-xl font-body peer cursor-pointer ${
455
+ hasScrolled ? "text-white" : "text-black"
456
+ } hover:text-secondary`}
457
+ onClick={onClick}>
458
+ {link?.label}
459
+ </p>
460
+ )}
461
+ {hasChevron && (
462
+ <BiChevronDown
463
+ fontSize={24}
464
+ className={`hover:-rotate-90 hover:text-secondary peer-hover:-rotate-90 peer-hover:text-secondary transition duration-200 cursor-pointer ${
465
+ hasScrolled ? "text-white" : "text-black"
466
+ }`}
467
+ onClick={onClick}
468
+ />
469
+ )}
470
+ </div>
471
+ );
472
+ };
473
+
474
+ const renderLinks = (linksArray: LabeledRouteWithKey[], isInnerRoute = false) =>
475
+ linksArray.map((link, idx) => {
476
+ const isDropdown = Boolean(link?.multipleRoutes || link?.multipleInnerRoutes);
477
+ const onClick = isDropdown
478
+ ? () => handleRouteClick(isInnerRoute ? showInnerRoutes.index : null, idx)
479
+ : undefined;
480
+
481
+ return renderLink(link, idx, isDropdown, onClick);
482
+ });
483
+
484
+ const renderBackButton = (onClick: () => void) => (
485
+ <p
486
+ className={`hover:text-secondary text-xl font-body py-2 px-4 flex items-center gap-2 cursor-pointer group ${
487
+ hasScrolled ? "text-white" : "text-black"
488
+ }`}
489
+ onClick={onClick}>
490
+ <BiArrowBack />
491
+ Back
492
+ </p>
493
+ );
494
+
495
+ return (
496
+ <Flex
497
+ className={`flex sm:block text-white overflow-hidden transition lg:hidden columns-2 ${
498
+ open ? "animate-curtain-down mt-5" : "animate-curtain-up h-0"
499
+ }`}>
500
+ <div ref={drawerRef} onClick={(e: React.MouseEvent) => e.stopPropagation()}>
501
+ {open && (
502
+ <>
503
+ {links && showInnerRoutes.index === null && renderLinks(links)}
504
+ {showInnerRoutes.previous === null && showInnerRoutes.index !== null && (
505
+ <div className="flex flex-col">
506
+ {renderBackButton(() => handleRouteClick(null, null))}
507
+ {renderLinks(links[showInnerRoutes.index]?.multipleRoutes || [], true)}
508
+ </div>
509
+ )}
510
+ {showInnerRoutes.previous !== null && showInnerRoutes.index !== null && (
511
+ <div className="flex flex-col">
512
+ {renderBackButton(() =>
513
+ setShowInnerRoutes({
514
+ previous: null,
515
+ index: showInnerRoutes.previous,
516
+ }),
517
+ )}
518
+ {renderLinks(
519
+ links[showInnerRoutes.previous]?.multipleRoutes?.[showInnerRoutes.index]
520
+ ?.multipleInnerRoutes || [],
521
+ )}
522
+ </div>
523
+ )}
524
+ </>
525
+ )}
526
+ </div>
527
+ </Flex>
528
+ );
529
+ };
530
+
531
+ export { Navigation_F };