@stackshift-ui/navigation 6.0.2 → 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,37 @@
1
+ import { MyPortableTextComponents } from "../types";
2
+ import { Text } from "@stackshift-ui/text";
3
+
4
+ // block styling as props to `serializers` of the PortableText component
5
+ export const blockStyle: MyPortableTextComponents = {
6
+ block: {
7
+ normal: ({ children }) => {
8
+ return (
9
+ <Text fontSize="xs" weight="bold" className="text-white ">
10
+ {children}
11
+ </Text>
12
+ );
13
+ },
14
+ },
15
+ code: ({ value }) => {
16
+ return (
17
+ <pre data-language={value.language}>
18
+ <code>{value.code}</code>
19
+ </pre>
20
+ );
21
+ },
22
+ marks: {
23
+ strong: ({ children }) => <strong>{children}</strong>,
24
+ em: ({ children }) => <em>{children}</em>,
25
+ code: ({ children }) => <code>{children}</code>,
26
+ link: ({ children, value }) => (
27
+ <a
28
+ aria-label={value?.href ?? "external link"}
29
+ className="text-primary-foreground hover:text-secondary-foreground"
30
+ href={value?.href}
31
+ target="_blank"
32
+ rel="noopener noreferrer">
33
+ {children}
34
+ </a>
35
+ ),
36
+ },
37
+ };
@@ -0,0 +1,14 @@
1
+ import { Logo } from "../types";
2
+
3
+ export const logoLink = (logo: Logo) => {
4
+ if (logo?.internalLink && logo?.type === "linkInternal") {
5
+ if (logo?.internalLink?.toLowerCase()?.includes("home")) {
6
+ return "/";
7
+ }
8
+ return `/${logo.internalLink}`;
9
+ } else if (logo?.externalLink && logo?.type === "linkExternal") {
10
+ return logo?.externalLink ?? "/";
11
+ } else {
12
+ return "/";
13
+ }
14
+ };
package/src/index.ts ADDED
@@ -0,0 +1,9 @@
1
+ "use client";
2
+
3
+ // component exports
4
+ export * from "./navigation";
5
+ export * from "./navigation_a";
6
+ export * from "./navigation_b";
7
+ export * from "./navigation_c";
8
+ export * from "./navigation_d";
9
+ // export * from "./navigation_e";
@@ -0,0 +1,13 @@
1
+ import { cleanup, render, screen } from "@testing-library/react";
2
+ import { afterEach, describe, test } from "vitest";
3
+ import { Navigation } from "./navigation";
4
+
5
+ describe.concurrent("navigation", () => {
6
+ afterEach(cleanup);
7
+
8
+ test.skip("Dummy test - test if renders without errors", ({ expect }) => {
9
+ const clx = "my-class";
10
+ render(<Navigation />);
11
+ expect(screen.getByTestId("{ kebabCase name }}").classList).toContain(clx);
12
+ });
13
+ });
@@ -0,0 +1,45 @@
1
+ import { lazy } from "react";
2
+ import { LabeledRoute, LabeledRouteWithKey, Logo, SectionsProps } from "./types";
3
+
4
+ const Variants = {
5
+ variant_a: lazy(() => import("./navigation_a")),
6
+ variant_b: lazy(() => import("./navigation_b")),
7
+ variant_c: lazy(() => import("./navigation_c")),
8
+ variant_d: lazy(() => import("./navigation_d")),
9
+ };
10
+
11
+ export interface ResponsiveNavLinksProps {
12
+ menu: boolean;
13
+ showMenu: () => void;
14
+ links?: LabeledRouteWithKey[];
15
+ primaryButton?: LabeledRoute;
16
+ secondaryButton?: LabeledRoute;
17
+ }
18
+
19
+ export interface NavigationProps {
20
+ template?: any;
21
+ logo?: Logo;
22
+ links?: LabeledRouteWithKey[];
23
+ primaryButton?: LabeledRoute;
24
+ secondaryButton?: LabeledRoute;
25
+ banner?: any;
26
+ }
27
+
28
+ const displayName = "Navigation";
29
+
30
+ export const Navigation: React.FC<SectionsProps> = ({ data }) => {
31
+ const variant = data?.variant;
32
+ const Variant = variant && Variants?.[variant as keyof typeof Variants];
33
+
34
+ const props = {
35
+ logo: data?.variants?.logo ?? undefined,
36
+ links: data?.variants?.routes ?? undefined,
37
+ primaryButton: data?.variants?.primaryButton ?? undefined,
38
+ secondaryButton: data?.variants?.secondaryButton ?? undefined,
39
+ banner: data?.variants?.banner ?? undefined,
40
+ };
41
+
42
+ return Variant ? <Variant {...props} /> : null;
43
+ };
44
+
45
+ Navigation.displayName = displayName;
@@ -0,0 +1,259 @@
1
+ import { Button } from "@stackshift-ui/button";
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 { Text } from "@stackshift-ui/text";
7
+ import React from "react";
8
+ import { NavigationProps, ResponsiveNavLinksProps } from ".";
9
+ import { logoLink } from "./helper";
10
+ import { LabeledRoute, LabeledRouteWithKey, Logo } from "./types";
11
+
12
+ export default function Navigation_A({
13
+ links,
14
+ primaryButton,
15
+ secondaryButton,
16
+ logo,
17
+ }: NavigationProps) {
18
+ const [menu, setMenu] = React.useState(false);
19
+ const showMenu = () => {
20
+ setMenu(prevState => !prevState);
21
+ };
22
+
23
+ return (
24
+ <Section className="bg-background">
25
+ <Flex align="center" justify="between" className="px-6 py-6">
26
+ <LogoSection logo={logo} />
27
+ <NavLinks links={links} />
28
+ <Buttons primaryButton={primaryButton} secondaryButton={secondaryButton} />
29
+ <MobileMenu showMenu={showMenu} />
30
+ </Flex>
31
+ <ResponsiveNavLinks
32
+ menu={menu}
33
+ showMenu={showMenu}
34
+ links={links}
35
+ primaryButton={primaryButton}
36
+ secondaryButton={secondaryButton}
37
+ />
38
+ </Section>
39
+ );
40
+ }
41
+
42
+ function LogoSection({ logo }: { logo?: Logo }) {
43
+ if (!logo) return null;
44
+
45
+ return (
46
+ <Link
47
+ aria-label={`Go to ${logoLink(logo) === "/" ? "home page" : logoLink(logo)}`}
48
+ className="text-3xl font-bold leading-none"
49
+ href={logoLink(logo)}
50
+ target={logo?.linkTarget}
51
+ rel={logo?.linkTarget === "_blank" ? "noopener noreferrer" : ""}>
52
+ <Image
53
+ src={logo?.image}
54
+ alt={logo?.alt ?? "navigation-logo"}
55
+ width={50}
56
+ height={50}
57
+ className="text-3xl font-bold leading-none"
58
+ />
59
+ </Link>
60
+ );
61
+ }
62
+
63
+ function NavIcon() {
64
+ return (
65
+ <li className="text-gray-500">
66
+ <svg
67
+ className="w-4 h-4 current-fill"
68
+ xmlns="http://www.w3.org/2000/svg"
69
+ fill="none"
70
+ viewBox="0 0 24 24"
71
+ stroke="currentColor">
72
+ <path
73
+ strokeLinecap="round"
74
+ strokeLinejoin="round"
75
+ strokeWidth="2"
76
+ d="M12 5v.01M12 12v.01M12 19v.01M12 6a1 1 0 110-2 1 1 0 010 2zm0 7a1 1 0 110-2 1 1 0 010 2zm0 7a1 1 0 110-2 1 1 0 010 2z"></path>
77
+ </svg>
78
+ </li>
79
+ );
80
+ }
81
+
82
+ function NavLinks({ links }: { links?: LabeledRouteWithKey[] }) {
83
+ if (!links) return null;
84
+
85
+ return (
86
+ <Flex>
87
+ <ul className="hidden lg:flex lg:items-center lg:space-x-6">
88
+ {links?.map((link: any, index: number) => (
89
+ <React.Fragment key={index}>
90
+ <NavItem link={link} key={link._key} />
91
+ {links.length !== index + 1 ? <NavIcon /> : null}
92
+ </React.Fragment>
93
+ ))}
94
+ </ul>
95
+ </Flex>
96
+ );
97
+ }
98
+
99
+ function NavItem({ link }: { link?: LabeledRoute }) {
100
+ if (!link?.label) return null;
101
+
102
+ return (
103
+ <li>
104
+ <Button
105
+ as="link"
106
+ link={link}
107
+ ariaLabel={link?.label}
108
+ className="text-sm text-gray-500 no-underline hover:text-gray-900">
109
+ {link?.label}
110
+ </Button>
111
+ </li>
112
+ );
113
+ }
114
+
115
+ function ResponsiveNavLinks({
116
+ menu,
117
+ showMenu,
118
+ links,
119
+ primaryButton,
120
+ secondaryButton,
121
+ }: ResponsiveNavLinksProps) {
122
+ return (
123
+ <div className={`${menu ? null : "hidden"} mobile-nav relative z-50`}>
124
+ <div className="fixed inset-0 bg-gray-800 opacity-25 navbar-backdrop" onClick={showMenu} />
125
+ <Flex
126
+ as="nav"
127
+ direction="col"
128
+ className="fixed top-0 bottom-0 left-0 w-5/6 max-w-sm px-6 py-6 overflow-y-auto bg-white border-r">
129
+ <BurgerMenuIcon showMenu={showMenu} />
130
+ <div className="w-full">
131
+ {links ? (
132
+ <ul>
133
+ {links?.map((link: any, index: number) => (
134
+ <li className="mb-1" key={index}>
135
+ <Button
136
+ as="link"
137
+ ariaLabel={link?.label}
138
+ className="block w-full cursor-pointer p-4 text-sm font-semibold text-gray-900 no-underline rounded hover:bg-secondary-foreground hover:text-primary"
139
+ link={link}>
140
+ {link?.label}
141
+ </Button>
142
+ </li>
143
+ ))}
144
+ </ul>
145
+ ) : null}
146
+ </div>
147
+ <div className="w-full mt-auto">
148
+ <Flex direction="col" className="pt-6 space-x-2">
149
+ {primaryButton?.label ? (
150
+ <Button
151
+ as="link"
152
+ link={primaryButton}
153
+ ariaLabel={primaryButton?.label}
154
+ variant="outline"
155
+ className="block w-full px-4 py-3 mb-3 text-xs cursor-pointer font-semibold leading-loose text-center text-gray-900 rounded-global bg-secondary hover:bg-secondary/50">
156
+ {primaryButton?.label}
157
+ </Button>
158
+ ) : null}
159
+ {secondaryButton?.label ? (
160
+ <Button
161
+ as="link"
162
+ link={secondaryButton}
163
+ ariaLabel={secondaryButton?.label}
164
+ variant="solid"
165
+ className={`block w-full px-4 py-3 mb-2 cursor-pointer leading-loose text-xs text-center font-semibold bg-primary hover:bg-primary-foreground rounded-global`}>
166
+ {secondaryButton?.label}
167
+ </Button>
168
+ ) : null}
169
+ </Flex>
170
+ <Text fontSize="xs" className="my-4 text-center text-gray-900">
171
+ <span>{`© ${new Date().getFullYear()} All rights reserved.`}</span>
172
+ </Text>
173
+ </div>
174
+ </Flex>
175
+ </div>
176
+ );
177
+ }
178
+
179
+ function MobileMenu({ showMenu }: { showMenu: () => void }) {
180
+ return (
181
+ <div className="lg:hidden">
182
+ <Button
183
+ variant="unstyled"
184
+ as="button"
185
+ ariaLabel="Navigation Menu"
186
+ className="flex items-center p-3 navbar-burger text-primary"
187
+ onClick={showMenu}>
188
+ <svg
189
+ className="block w-4 h-4 fill-current"
190
+ viewBox="0 0 20 20"
191
+ xmlns="http://www.w3.org/2000/svg">
192
+ <title>Mobile menu</title>
193
+ <path d="M0 3h20v2H0V3zm0 6h20v2H0V9zm0 6h20v2H0v-2z"></path>
194
+ </svg>
195
+ </Button>
196
+ </div>
197
+ );
198
+ }
199
+
200
+ function Buttons({
201
+ primaryButton,
202
+ secondaryButton,
203
+ }: {
204
+ primaryButton?: LabeledRoute;
205
+ secondaryButton?: LabeledRoute;
206
+ }) {
207
+ return (
208
+ <Flex gap={4}>
209
+ {primaryButton?.label ? (
210
+ <Button
211
+ as="link"
212
+ link={primaryButton}
213
+ ariaLabel={primaryButton?.label}
214
+ variant="outline"
215
+ className="hidden lg:flex px-4 py-3 mb-2 leading-loose text-center font-semibold text-gray-900 rounded-global bg-secondary hover:bg-secondary/50">
216
+ {primaryButton?.label}
217
+ </Button>
218
+ ) : null}
219
+ {secondaryButton?.label ? (
220
+ <Button
221
+ as="link"
222
+ link={secondaryButton}
223
+ ariaLabel={secondaryButton?.label}
224
+ variant="solid"
225
+ className="hidden lg:flex px-4 py-3 mb-2 leading-loose text-center font-semibold text-white bg-primary hover:bg-primary-foreground rounded-global">
226
+ {secondaryButton?.label}
227
+ </Button>
228
+ ) : null}
229
+ </Flex>
230
+ );
231
+ }
232
+
233
+ function BurgerMenuIcon({ showMenu }: { showMenu: () => void }) {
234
+ return (
235
+ <div className="flex items-center mb-8">
236
+ <Button
237
+ variant="unstyled"
238
+ as="button"
239
+ ariaLabel="Navigation Menu"
240
+ className="navbar-close"
241
+ onClick={showMenu}>
242
+ <svg
243
+ className="w-6 h-6 text-gray-500 cursor-pointer hover:text-gray-500"
244
+ xmlns="http://www.w3.org/2000/svg"
245
+ fill="none"
246
+ viewBox="0 0 24 24"
247
+ stroke="currentColor">
248
+ <path
249
+ strokeLinecap="round"
250
+ strokeLinejoin="round"
251
+ strokeWidth="2"
252
+ d="M6 18L18 6M6 6l12 12"></path>
253
+ </svg>
254
+ </Button>
255
+ </div>
256
+ );
257
+ }
258
+
259
+ export { Navigation_A };
@@ -0,0 +1,250 @@
1
+ import { Button } from "@stackshift-ui/button";
2
+ import { Container } from "@stackshift-ui/container";
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 { Text } from "@stackshift-ui/text";
8
+ import React from "react";
9
+ import { NavigationProps, ResponsiveNavLinksProps } from ".";
10
+ import { logoLink } from "./helper";
11
+ import { LabeledRoute, LabeledRouteWithKey, Logo } from "./types";
12
+
13
+ export default function Navigation_B({
14
+ links,
15
+ primaryButton,
16
+ secondaryButton,
17
+ logo,
18
+ }: NavigationProps) {
19
+ const [menu, setMenu] = React.useState(false);
20
+ const showMenu = () => {
21
+ setMenu(prevState => !prevState);
22
+ };
23
+
24
+ return (
25
+ <Section className="bg-background">
26
+ <nav className="relative py-6">
27
+ <Container maxWidth={1000}>
28
+ <Flex align="center" justify="between">
29
+ <LogoSection logo={logo} />
30
+ <MobileMenu showMenu={showMenu} />
31
+ <NavLinks links={links} />
32
+ <Buttons primaryButton={primaryButton} secondaryButton={secondaryButton} />
33
+ </Flex>
34
+ </Container>
35
+ </nav>
36
+ <ResponsiveNavLinks
37
+ menu={menu}
38
+ showMenu={showMenu}
39
+ links={links}
40
+ primaryButton={primaryButton}
41
+ secondaryButton={secondaryButton}
42
+ />
43
+ </Section>
44
+ );
45
+ }
46
+
47
+ function LogoSection({ logo }: { logo?: Logo }) {
48
+ if (!logo) return null;
49
+
50
+ return (
51
+ <Link
52
+ aria-label={`Go to ${logoLink(logo) === "/" ? "home page" : logoLink(logo)}`}
53
+ className="text-3xl font-bold leading-none"
54
+ href={logoLink(logo)}
55
+ target={logo?.linkTarget}
56
+ rel={logo?.linkTarget === "_blank" ? "noopener noreferrer" : ""}>
57
+ <Image
58
+ src={logo?.image}
59
+ alt={logo?.alt ?? "navigation-logo"}
60
+ width={48}
61
+ height={48}
62
+ className="text-3xl font-bold leading-none"
63
+ />
64
+ </Link>
65
+ );
66
+ }
67
+
68
+ function NavLinks({ links }: { links?: LabeledRouteWithKey[] }) {
69
+ if (!links) return null;
70
+
71
+ return (
72
+ <ul className="absolute hidden transform -translate-x-1/2 -translate-y-1/2 left-1/2 top-1/2 lg:mx-auto lg:flex lg:w-auto lg:items-center lg:space-x-6">
73
+ {links?.map((link, index) => (
74
+ <React.Fragment key={index}>
75
+ {link?.label && <NavItem link={link} key={index} />}
76
+ {links.length !== index + 1 ? <NavIcon /> : null}
77
+ </React.Fragment>
78
+ ))}
79
+ </ul>
80
+ );
81
+ }
82
+
83
+ function NavItem({ link }: { link?: LabeledRouteWithKey }) {
84
+ if (!link) return null;
85
+
86
+ return (
87
+ <li>
88
+ <Button
89
+ as="link"
90
+ ariaLabel={link?.label}
91
+ link={link}
92
+ className="text-sm text-gray-500 no-underline hover:text-gray-900">
93
+ {link?.label}
94
+ </Button>
95
+ </li>
96
+ );
97
+ }
98
+
99
+ function NavIcon() {
100
+ return (
101
+ <li className="text-gray-500">
102
+ <svg
103
+ className="w-4 h-4 current-fill"
104
+ xmlns="http://www.w3.org/2000/svg"
105
+ fill="none"
106
+ viewBox="0 0 24 24"
107
+ stroke="currentColor">
108
+ <path
109
+ strokeLinecap="round"
110
+ strokeLinejoin="round"
111
+ strokeWidth="2"
112
+ d="M12 5v.01M12 12v.01M12 19v.01M12 6a1 1 0 110-2 1 1 0 010 2zm0 7a1 1 0 110-2 1 1 0 010 2zm0 7a1 1 0 110-2 1 1 0 010 2z"></path>
113
+ </svg>
114
+ </li>
115
+ );
116
+ }
117
+
118
+ function Buttons({
119
+ primaryButton,
120
+ secondaryButton,
121
+ }: {
122
+ primaryButton?: LabeledRoute;
123
+ secondaryButton?: LabeledRoute;
124
+ }) {
125
+ return (
126
+ <React.Fragment>
127
+ {primaryButton?.label && (
128
+ <Button
129
+ as="link"
130
+ ariaLabel={primaryButton?.label}
131
+ link={primaryButton}
132
+ className="hidden lg:inline-block px-4 py-3 mb-2 text-gray-900 lg:ml-auto lg:mr-3 font-semibold rounded-global bg-secondary hover:bg-secondary/50">
133
+ {primaryButton?.label}
134
+ </Button>
135
+ )}
136
+ {secondaryButton?.label && (
137
+ <Button
138
+ as="link"
139
+ ariaLabel={secondaryButton?.label}
140
+ link={secondaryButton}
141
+ className="hidden lg:inline-block px-4 py-3 mb-2 leading-loose text-center text-white font-semibold bg-primary hover:bg-primary-foreground rounded-global">
142
+ {secondaryButton?.label}
143
+ </Button>
144
+ )}
145
+ </React.Fragment>
146
+ );
147
+ }
148
+
149
+ function MobileMenu({ showMenu }: { showMenu: () => void }) {
150
+ return (
151
+ <div className="lg:hidden">
152
+ <Button
153
+ variant="unstyled"
154
+ as="button"
155
+ ariaLabel="Navigation menu"
156
+ className="flex items-center p-3 navbar-burger text-primary"
157
+ onClick={showMenu}>
158
+ <svg
159
+ className="block w-4 h-4 fill-current"
160
+ viewBox="0 0 20 20"
161
+ xmlns="http://www.w3.org/2000/svg">
162
+ <title>Mobile menu</title>
163
+ <path d="M0 3h20v2H0V3zm0 6h20v2H0V9zm0 6h20v2H0v-2z"></path>
164
+ </svg>
165
+ </Button>
166
+ </div>
167
+ );
168
+ }
169
+
170
+ function ResponsiveNavLinks({
171
+ menu,
172
+ showMenu,
173
+ links,
174
+ primaryButton,
175
+ secondaryButton,
176
+ }: ResponsiveNavLinksProps) {
177
+ return (
178
+ <React.Fragment>
179
+ <div className={`${menu ? null : "hidden"} mobile-nav relative z-50`}>
180
+ <div className="fixed inset-0 bg-gray-800 opacity-25 navbar-backdrop" onClick={showMenu} />
181
+ <nav className="fixed top-0 bottom-0 left-0 flex flex-col w-5/6 max-w-sm px-6 py-6 overflow-y-auto bg-white border-r">
182
+ <div className="flex items-center mb-8">
183
+ <Button
184
+ variant="unstyled"
185
+ as="button"
186
+ ariaLabel="Navigation menu"
187
+ className="navbar-close"
188
+ onClick={showMenu}>
189
+ <svg
190
+ className="w-6 h-6 text-gray-500 cursor-pointer hover:text-gray-500"
191
+ xmlns="http://www.w3.org/2000/svg"
192
+ fill="none"
193
+ viewBox="0 0 24 24"
194
+ stroke="currentColor">
195
+ <path
196
+ strokeLinecap="round"
197
+ strokeLinejoin="round"
198
+ strokeWidth="2"
199
+ d="M6 18L18 6M6 6l12 12"
200
+ />
201
+ </svg>
202
+ </Button>
203
+ </div>
204
+ {links && (
205
+ <ul>
206
+ {links?.map((link, index) => (
207
+ <li className="mb-1" key={index}>
208
+ <Button
209
+ as="link"
210
+ ariaLabel={link?.label}
211
+ link={link}
212
+ className="block p-4 text-sm font-semibold text-gray-700 no-underline rounded hover:bg-secondary-foreground hover:text-primary">
213
+ {link?.label}
214
+ </Button>
215
+ </li>
216
+ ))}
217
+ </ul>
218
+ )}
219
+ <div className="mt-auto">
220
+ <div className="pt-6">
221
+ {primaryButton?.label && (
222
+ <Button
223
+ as="link"
224
+ ariaLabel={primaryButton?.label}
225
+ link={primaryButton}
226
+ className="block px-4 py-3 mb-2 text-gray-900 text-center lg:ml-auto lg:mr-3 font-semibold rounded-global bg-secondary hover:bg-secondary/50">
227
+ {primaryButton?.label}
228
+ </Button>
229
+ )}
230
+ {secondaryButton?.label && (
231
+ <Button
232
+ as="link"
233
+ ariaLabel={secondaryButton?.label}
234
+ link={secondaryButton}
235
+ className="block px-4 py-3 mb-2 leading-loose text-center text-white font-semibold bg-primary hover:bg-primary-foreground rounded-global">
236
+ {secondaryButton?.label}
237
+ </Button>
238
+ )}
239
+ </div>
240
+ <Text fontSize="xs" className="my-4 text-center text-gray-900">
241
+ <span>{`© ${new Date().getFullYear()} All rights reserved.`}</span>
242
+ </Text>
243
+ </div>
244
+ </nav>
245
+ </div>
246
+ </React.Fragment>
247
+ );
248
+ }
249
+
250
+ export { Navigation_B };