@stackshift-ui/navigation 6.0.3 → 6.0.4

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.
@@ -0,0 +1,476 @@
1
+ import { PortableText } from "@portabletext/react";
2
+ import { Button } from "@stackshift-ui/button";
3
+ import { Flex } from "@stackshift-ui/flex";
4
+ import { Image } from "@stackshift-ui/image";
5
+ import { Link } from "@stackshift-ui/link";
6
+ import { Section } from "@stackshift-ui/section";
7
+ import { Fragment, useEffect, useRef, useState } from "react";
8
+ import { NavigationProps, ResponsiveNavLinksProps } from ".";
9
+ import { logoLink } from "./helper";
10
+ import { blockStyle } from "./helper/blockStyle";
11
+ import { LabeledRouteWithKey, Logo } from "./types";
12
+
13
+ export default function Navigation_E({ banner, logo, links }: NavigationProps) {
14
+ const [menu, setMenu] = useState<boolean>(false);
15
+ const [showSearchBar, setShowSearchBar] = useState<boolean>(false);
16
+ const [productQuery, setProductQuery] = useState<string>("");
17
+ const prevQuery = useRef(""); // the useRef React hook allows to persist data between renders
18
+
19
+ useEffect(() => {
20
+ //assign the ref's current value to the productQuery hook
21
+ prevQuery.current = productQuery;
22
+ }, [productQuery]); //run this code when the value of productQuery changes
23
+
24
+ const showMenu = () => setMenu(!menu);
25
+
26
+ // Add query param to /search page based on search input
27
+ const handleSearchRouting = (e: React.FormEvent<HTMLFormElement>) => {
28
+ e.preventDefault();
29
+
30
+ const q = document.getElementById("query") as HTMLInputElement;
31
+ if (q) {
32
+ setProductQuery(q.value);
33
+ window.location.href = `/search?q=${productQuery}`;
34
+ }
35
+ };
36
+
37
+ return (
38
+ <Section className="relative bg-background">
39
+ <BannerSection banner={banner} />
40
+ <Flex as="nav" justify="between" className="relative">
41
+ <Flex align="center" className="w-full px-12 py-8">
42
+ <LogoSection logo={logo} />
43
+ <NavLinks links={links} />
44
+ </Flex>
45
+ <div className="items-center justify-end hidden mt-6 mr-12 lg:flex">
46
+ <SearchButton showSearchBar={showSearchBar} setShowSearchBar={setShowSearchBar} />
47
+ <SearchForm
48
+ showSearchBar={showSearchBar}
49
+ handleSearchRouting={handleSearchRouting}
50
+ productQuery={productQuery}
51
+ setProductQuery={setProductQuery}
52
+ />
53
+ <CartIcon />
54
+ <AccountIcon />
55
+ </div>
56
+
57
+ <MenuIcon showMenu={showMenu} />
58
+ </Flex>
59
+ <ResponsiveNavLinks
60
+ logo={logo}
61
+ links={links}
62
+ menu={menu}
63
+ showMenu={showMenu}
64
+ handleSearchRouting={handleSearchRouting}
65
+ productQuery={productQuery}
66
+ setProductQuery={setProductQuery}
67
+ />
68
+ </Section>
69
+ );
70
+ }
71
+
72
+ function BannerSection({ banner }: any) {
73
+ if (!banner) return null;
74
+
75
+ return (
76
+ <div className="py-2 bg-primary">
77
+ <Flex align="center" justify="center">
78
+ <svg
79
+ className="mr-2"
80
+ width={18}
81
+ height={11}
82
+ viewBox="0 0 18 11"
83
+ fill="none"
84
+ xmlns="http://www.w3.org/2000/svg">
85
+ <rect
86
+ y="3.07129"
87
+ width={4}
88
+ height={10}
89
+ rx={2}
90
+ transform="rotate(-45 0 3.07129)"
91
+ fill="white"
92
+ />
93
+ <rect
94
+ x={8}
95
+ y="2.82861"
96
+ width={4}
97
+ height={10}
98
+ rx={2}
99
+ transform="rotate(-45 8 2.82861)"
100
+ fill="white"
101
+ />
102
+ </svg>
103
+ <PortableText
104
+ value={banner}
105
+ components={blockStyle}
106
+ onMissingComponent={false} // Disabling warnings / handling unknown types
107
+ />
108
+ </Flex>
109
+ </div>
110
+ );
111
+ }
112
+
113
+ function LogoSection({ logo }: { logo?: Logo }) {
114
+ if (!logo) return null;
115
+
116
+ return (
117
+ <Link
118
+ aria-label={`Go to ${logoLink(logo) === "/" ? "home page" : logoLink(logo)}`}
119
+ className="text-3xl font-bold leading-none"
120
+ href={logoLink(logo)}
121
+ target={logo?.linkTarget}
122
+ rel={logo?.linkTarget === "_blank" ? "noopener noreferrer" : ""}>
123
+ <Image
124
+ src={logo?.image}
125
+ alt={logo?.alt ?? "navigation-logo"}
126
+ width={48}
127
+ height={48}
128
+ className="text-3xl font-bold leading-none"
129
+ />
130
+ </Link>
131
+ );
132
+ }
133
+
134
+ function NavLinks({ links }: { links?: LabeledRouteWithKey[] }) {
135
+ if (!links) return null;
136
+
137
+ return (
138
+ <ul className="hidden lg:flex lg:w-auto lg:items-center lg:space-x-5">
139
+ {links.map((link, index) => (
140
+ <Fragment key={index}>
141
+ <NavItem link={link} />
142
+ </Fragment>
143
+ ))}
144
+ </ul>
145
+ );
146
+ }
147
+
148
+ function NavItem({ link }: { link?: LabeledRouteWithKey }) {
149
+ if (!link) return null;
150
+
151
+ return (
152
+ <li>
153
+ <Button
154
+ as="link"
155
+ variant="link"
156
+ ariaLabel={link?.label}
157
+ link={link}
158
+ className={
159
+ link?.type === "linkInternal"
160
+ ? "xl:mr-12 lg:mr-8 font-bold font-heading hover:text-gray-600 no-underline text-black"
161
+ : "mr-12 font-bold font-heading hover:text-gray-600 no-underline text-black"
162
+ }>
163
+ {link?.label}
164
+ </Button>
165
+ </li>
166
+ );
167
+ }
168
+
169
+ function SearchButton({
170
+ showSearchBar,
171
+ setShowSearchBar,
172
+ }: {
173
+ showSearchBar: boolean;
174
+ setShowSearchBar: (showSearchBar: boolean) => void;
175
+ }) {
176
+ return (
177
+ <Button
178
+ as="button"
179
+ variant="unstyled"
180
+ ariaLabel="Search button"
181
+ type="button"
182
+ onClick={() => setShowSearchBar(!showSearchBar)}>
183
+ <svg
184
+ width={24}
185
+ height={24}
186
+ xmlns="http://www.w3.org/2000/svg"
187
+ fillRule="evenodd"
188
+ clipRule="evenodd">
189
+ <path d="M15.853 16.56c-1.683 1.517-3.911 2.44-6.353 2.44-5.243 0-9.5-4.257-9.5-9.5s4.257-9.5 9.5-9.5 9.5 4.257 9.5 9.5c0 2.442-.923 4.67-2.44 6.353l7.44 7.44-.707.707-7.44-7.44zm-6.353-15.56c4.691 0 8.5 3.809 8.5 8.5s-3.809 8.5-8.5 8.5-8.5-3.809-8.5-8.5 3.809-8.5 8.5-8.5z" />
190
+ </svg>
191
+ </Button>
192
+ );
193
+ }
194
+
195
+ interface SearchFormProps {
196
+ showSearchBar: boolean;
197
+ handleSearchRouting: (e: React.FormEvent<HTMLFormElement>) => void;
198
+ productQuery?: string;
199
+ setProductQuery: (q: string) => void;
200
+ }
201
+
202
+ function SearchForm({
203
+ showSearchBar,
204
+ handleSearchRouting,
205
+ productQuery,
206
+ setProductQuery,
207
+ }: SearchFormProps) {
208
+ if (!showSearchBar) return null;
209
+
210
+ return (
211
+ <form
212
+ id="form"
213
+ className="flex items-center pl-8 mb-10 mr-auto bg-white lg:mb-0"
214
+ method="get"
215
+ role="search"
216
+ onSubmit={handleSearchRouting}>
217
+ <input
218
+ id="query"
219
+ name="query"
220
+ aria-label="Search..."
221
+ className="inline-block w-40 h-full p-2 mt-1 text-sm bg-white border shadow-sm border-slate-300 placeholder-slate-400 focus:border-primary-foreground focus:outline-none focus:ring-1 focus:ring-primary-foreground"
222
+ placeholder="Search..."
223
+ onChange={e => setProductQuery(e.target.value)}
224
+ type="search"
225
+ />
226
+ <Button
227
+ as="button"
228
+ variant="unstyled"
229
+ ariaLabel="Submit product search"
230
+ className={`mt-1 inline-flex h-[35px] w-10 items-center justify-center bg-primary ${
231
+ productQuery === ""
232
+ ? "cursor-not-allowed opacity-50"
233
+ : "transition duration-200 hover:bg-primary-foreground"
234
+ }`}
235
+ disabled={productQuery === ""}
236
+ type="submit">
237
+ <svg
238
+ width={7}
239
+ height={12}
240
+ viewBox="0 0 7 12"
241
+ fill="none"
242
+ xmlns="http://www.w3.org/2000/svg">
243
+ <path
244
+ d="M4.125 6.00252L0 1.87752L1.17801 0.699219L6.48102 6.00252L1.17801 11.3058L0 10.1275L4.125 6.00252Z"
245
+ fill="white"
246
+ />
247
+ </svg>
248
+ </Button>
249
+ </form>
250
+ );
251
+ }
252
+
253
+ function MenuIcon({ showMenu }: { showMenu: () => void }) {
254
+ return (
255
+ <Button
256
+ variant="unstyled"
257
+ as="button"
258
+ ariaLabel="Nav Sidebar"
259
+ className="self-center mr-12 navbar-burger lg:hidden"
260
+ onClick={showMenu}>
261
+ <svg
262
+ width={20}
263
+ height={12}
264
+ viewBox="0 0 20 12"
265
+ fill="none"
266
+ xmlns="http://www.w3.org/2000/svg">
267
+ <path
268
+ d="M1 2H19C19.2652 2 19.5196 1.89464 19.7071 1.70711C19.8946 1.51957 20 1.26522 20 1C20 0.734784 19.8946 0.48043 19.7071 0.292893C19.5196 0.105357 19.2652 0 19 0H1C0.734784 0 0.48043 0.105357 0.292893 0.292893C0.105357 0.48043 0 0.734784 0 1C0 1.26522 0.105357 1.51957 0.292893 1.70711C0.48043 1.89464 0.734784 2 1 2ZM19 10H1C0.734784 10 0.48043 10.1054 0.292893 10.2929C0.105357 10.4804 0 10.7348 0 11C0 11.2652 0.105357 11.5196 0.292893 11.7071C0.48043 11.8946 0.734784 12 1 12H19C19.2652 12 19.5196 11.8946 19.7071 11.7071C19.8946 11.5196 20 11.2652 20 11C20 10.7348 19.8946 10.4804 19.7071 10.2929C19.5196 10.1054 19.2652 10 19 10ZM19 5H1C0.734784 5 0.48043 5.10536 0.292893 5.29289C0.105357 5.48043 0 5.73478 0 6C0 6.26522 0.105357 6.51957 0.292893 6.70711C0.48043 6.89464 0.734784 7 1 7H19C19.2652 7 19.5196 6.89464 19.7071 6.70711C19.8946 6.51957 20 6.26522 20 6C20 5.73478 19.8946 5.48043 19.7071 5.29289C19.5196 5.10536 19.2652 5 19 5Z"
269
+ fill="currentColor"
270
+ />
271
+ </svg>
272
+ </Button>
273
+ );
274
+ }
275
+
276
+ function CartIcon() {
277
+ return (
278
+ <div className="mx-10 cart-icon cart-link">
279
+ <div data-icon="BAG" className="ec-cart-widget" />
280
+ <a className="cart-link" href="/cart?store-page=cart" aria-label="Cart" />
281
+ </div>
282
+ );
283
+ }
284
+
285
+ function AccountIcon() {
286
+ return (
287
+ <a href="/cart?store-page=account">
288
+ <svg
289
+ width={32}
290
+ height={31}
291
+ viewBox="0 0 32 31"
292
+ fill="none"
293
+ xmlns="http://www.w3.org/2000/svg">
294
+ <path
295
+ d="M16.0006 16.3154C19.1303 16.3154 21.6673 13.799 21.6673 10.6948C21.6673 7.59064 19.1303 5.07422 16.0006 5.07422C12.871 5.07422 10.334 7.59064 10.334 10.6948C10.334 13.799 12.871 16.3154 16.0006 16.3154Z"
296
+ stroke="currentColor"
297
+ strokeWidth="1.5"
298
+ strokeLinecap="round"
299
+ strokeLinejoin="round"
300
+ />
301
+ <path
302
+ d="M24.4225 23.8963C23.6678 22.3507 22.4756 21.0445 20.9845 20.1298C19.4934 19.2151 17.7647 18.7295 15.9998 18.7295C14.2349 18.7295 12.5063 19.2151 11.0152 20.1298C9.52406 21.0445 8.33179 22.3507 7.57715 23.8963"
303
+ stroke="currentColor"
304
+ strokeWidth="1.5"
305
+ strokeLinecap="round"
306
+ strokeLinejoin="round"
307
+ />
308
+ </svg>
309
+ </a>
310
+ );
311
+ }
312
+
313
+ interface NavProps extends ResponsiveNavLinksProps {
314
+ logo?: Logo;
315
+ handleSearchRouting: (e: React.FormEvent<HTMLFormElement>) => void;
316
+ productQuery: string;
317
+ setProductQuery: (q: string) => void;
318
+ }
319
+
320
+ function ResponsiveNavLinks({
321
+ logo,
322
+ links,
323
+ menu,
324
+ showMenu,
325
+ handleSearchRouting,
326
+ productQuery,
327
+ setProductQuery,
328
+ }: NavProps) {
329
+ return (
330
+ <div
331
+ className={`${menu ? null : "hidden"} mobile-nav fixed bottom-0 right-0 top-0 w-5/6 max-w-sm`}
332
+ style={{ zIndex: 60 }}>
333
+ <div className="fixed inset-0 bg-gray-800 opacity-25" onClick={showMenu} />
334
+ <nav className="relative flex flex-col w-full h-full px-6 py-6 overflow-y-auto bg-white border-r">
335
+ <div className="flex items-center mb-8">
336
+ {logo && (
337
+ <Link
338
+ aria-label={`Go to ${logoLink(logo) === "/" ? "home page" : logoLink(logo)}`}
339
+ className="text-3xl font-bold leading-none"
340
+ href={logoLink(logo)}
341
+ target={logo?.linkTarget}
342
+ rel={logo?.linkTarget === "_blank" ? "noopener noreferrer" : ""}>
343
+ <Image
344
+ src={logo?.image}
345
+ alt={logo?.alt ?? "navigation-logo"}
346
+ width={48}
347
+ height={48}
348
+ className="text-3xl font-bold leading-none"
349
+ />
350
+ </Link>
351
+ )}
352
+
353
+ <Button
354
+ variant="unstyled"
355
+ as="button"
356
+ ariaLabel="Close navigation menu"
357
+ className="ml-auto"
358
+ onClick={showMenu}>
359
+ <svg
360
+ className="w-2 h-2 text-gray-500 cursor-pointer"
361
+ width={10}
362
+ height={10}
363
+ viewBox="0 0 10 10"
364
+ fill="none"
365
+ xmlns="http://www.w3.org/2000/svg">
366
+ <path
367
+ d="M9.00002 1L1 9.00002M1.00003 1L9.00005 9.00002"
368
+ stroke="black"
369
+ strokeWidth="1.5"
370
+ strokeLinecap="round"
371
+ strokeLinejoin="round"
372
+ />
373
+ </svg>
374
+ </Button>
375
+ </div>
376
+ {/* show search bar on mobile view */}
377
+ <form
378
+ id="form"
379
+ className="flex mt-3 bg-white"
380
+ method="get"
381
+ role="search"
382
+ onSubmit={handleSearchRouting}>
383
+ <input
384
+ id="query"
385
+ name="query"
386
+ aria-label="Search product"
387
+ className="inline-block w-full h-full p-2 text-sm bg-white border shadow-sm border-slate-300 placeholder-slate-400 focus:border-primary-foreground focus:outline-none focus:ring-1 focus:ring-primary-foreground sm:w-60"
388
+ placeholder="Search..."
389
+ onChange={e => setProductQuery(e.target.value)}
390
+ type="search"
391
+ />
392
+ <Button
393
+ variant="unstyled"
394
+ as="button"
395
+ ariaLabel="Submit product search"
396
+ className={`inline-flex h-full w-10 items-center justify-center bg-primary ${
397
+ productQuery === ""
398
+ ? "cursor-not-allowed opacity-50"
399
+ : "transition duration-200 hover:bg-primary-foreground"
400
+ }`}
401
+ disabled={productQuery === ""}
402
+ type="submit">
403
+ <svg
404
+ width={7}
405
+ height={12}
406
+ viewBox="0 0 7 12"
407
+ fill="none"
408
+ xmlns="http://www.w3.org/2000/svg">
409
+ <path
410
+ d="M4.125 6.00252L0 1.87752L1.17801 0.699219L6.48102 6.00252L1.17801 11.3058L0 10.1275L4.125 6.00252Z"
411
+ fill="white"
412
+ />
413
+ </svg>
414
+ </Button>
415
+ </form>
416
+ {/* mobile view navigation sidebar */}
417
+ <ul className="mt-10 mb-5">
418
+ {links &&
419
+ links.map((link, index) => (
420
+ <Fragment key={index}>
421
+ <li className="mb-8">
422
+ <Button
423
+ as="link"
424
+ variant="link"
425
+ ariaLabel={link?.label ?? `navigation link ${index + 1}`}
426
+ link={link}
427
+ className="font-bold text-black no-underline font-heading hover:text-gray-600">
428
+ {link?.label}
429
+ </Button>
430
+ </li>
431
+ </Fragment>
432
+ ))}
433
+ </ul>
434
+ <hr />
435
+ {/* mobile view cart and account buttons */}
436
+ <div className="flex items-center mx-auto mt-3">
437
+ {/* Cart */}
438
+ <a
439
+ className="flex mr-10 cart-icon cart-link"
440
+ aria-label="Cart"
441
+ href="/cart?store-page=cart">
442
+ <div data-icon="BAG" className="ec-cart-widget" />
443
+ <span className="my-auto text-sm">Cart</span>
444
+ </a>
445
+ {/* Account */}
446
+ <a className="flex" aria-label="Account" href="/cart?store-page=account">
447
+ <svg
448
+ width={32}
449
+ height={31}
450
+ viewBox="0 0 32 31"
451
+ fill="none"
452
+ xmlns="http://www.w3.org/2000/svg">
453
+ <path
454
+ d="M16.0006 16.3154C19.1303 16.3154 21.6673 13.799 21.6673 10.6948C21.6673 7.59064 19.1303 5.07422 16.0006 5.07422C12.871 5.07422 10.334 7.59064 10.334 10.6948C10.334 13.799 12.871 16.3154 16.0006 16.3154Z"
455
+ stroke="currentColor"
456
+ strokeWidth="1.5"
457
+ strokeLinecap="round"
458
+ strokeLinejoin="round"
459
+ />
460
+ <path
461
+ d="M24.4225 23.8963C23.6678 22.3507 22.4756 21.0445 20.9845 20.1298C19.4934 19.2151 17.7647 18.7295 15.9998 18.7295C14.2349 18.7295 12.5063 19.2151 11.0152 20.1298C9.52406 21.0445 8.33179 22.3507 7.57715 23.8963"
462
+ stroke="currentColor"
463
+ strokeWidth="1.5"
464
+ strokeLinecap="round"
465
+ strokeLinejoin="round"
466
+ />
467
+ </svg>
468
+ <span className="my-auto text-sm">Account</span>
469
+ </a>
470
+ </div>
471
+ </nav>
472
+ </div>
473
+ );
474
+ }
475
+
476
+ export { Navigation_E };