Skip to content

Commit f4c4050

Browse files
authored
feat: implemented generic NavBar in common package (#3654)
* feat: added root tsconfig and common package extends it * feat: implemented NavbarGeneric in common package; * fix: common - lint errors * chore: restore common tsconfig * Update tsconfig.json
1 parent b872983 commit f4c4050

File tree

12 files changed

+243
-0
lines changed

12 files changed

+243
-0
lines changed
Lines changed: 1 addition & 0 deletions
Loading
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
import { PropsWithChildren } from "react";
2+
3+
export function NavbarActions({ children }: PropsWithChildren) {
4+
return (
5+
<div className="flex items-center gap-6" data-testid="navbar-actions">
6+
{children}
7+
</div>
8+
);
9+
}
10+
11+
NavbarActions.displayName = "NavbarActions";
12+
NavbarActions.navbarSection = "main";
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
import { ReactElement } from "react";
2+
import { useNavbarContext } from "./NavbarContext";
3+
4+
export function NavbarBanner({ children }: { children: ReactElement }) {
5+
const { showBanner } = useNavbarContext();
6+
if (!showBanner) return null;
7+
return (
8+
<div
9+
className="bg-white/40 backdrop-blur-sm text-center w-full font-medium flex flex-col items-center justify-center text-black"
10+
data-testid="navbar-banner"
11+
>
12+
{children}
13+
</div>
14+
);
15+
}
16+
17+
NavbarBanner.displayName = "NavbarBanner";
18+
NavbarBanner.navbarSection = "banner";
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
import { PropsWithChildren } from "react";
2+
import { useNavbarContext } from "./NavbarContext";
3+
4+
export function NavbarConnectButton({ children }: PropsWithChildren) {
5+
const { showWalletInteraction } = useNavbarContext();
6+
7+
if (!showWalletInteraction) return null;
8+
9+
return (
10+
<div data-testid="connect-wallet-button" id="connect-wallet-button">
11+
{children}
12+
</div>
13+
);
14+
}
15+
16+
NavbarConnectButton.displayName = "NavbarConnectButton";
17+
NavbarConnectButton.navbarSection = "main";
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
import { createContext, PropsWithChildren, useContext } from "react";
2+
3+
// Define the type for the context value
4+
type NavbarContextType = {
5+
showWalletInteraction?: boolean;
6+
showBanner?: boolean;
7+
};
8+
9+
const NavbarContext = createContext<NavbarContextType | undefined>(undefined);
10+
11+
type NavbarProviderProps = PropsWithChildren<{
12+
value: NavbarContextType;
13+
}>;
14+
15+
const NavbarProvider: React.FC<NavbarProviderProps> = ({ children, value }) => {
16+
return (
17+
<NavbarContext.Provider value={value}>{children}</NavbarContext.Provider>
18+
);
19+
};
20+
21+
const useNavbarContext = () => {
22+
const context = useContext(NavbarContext);
23+
if (!context) {
24+
throw new Error("useNavbarContext must be used within a NavbarContext");
25+
}
26+
return context;
27+
};
28+
29+
export { NavbarProvider, useNavbarContext };
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
import { PropsWithChildren } from "react";
2+
3+
export function NavbarCustomAction({
4+
children,
5+
testId,
6+
}: PropsWithChildren<{
7+
testId?: string;
8+
}>) {
9+
return (
10+
<div
11+
className="flex items-center"
12+
data-testid={testId || "navbar-custom-action"}
13+
>
14+
{children}
15+
</div>
16+
);
17+
}
18+
19+
NavbarCustomAction.displayName = "NavbarCustomAction";
20+
NavbarCustomAction.navbarSection = "main";
Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
import React, { PropsWithChildren, ReactElement } from "react";
2+
import { NavbarBanner } from "./NavbarBanner";
3+
import { NavbarCustomAction } from "./NavbarCustomAction";
4+
import { NavbarConnectButton } from "./NavbarConnectButton";
5+
import { NavbarActions } from "./NavbarActions";
6+
import { NavbarLogo } from "./NavbarLogo";
7+
import { NavbarProvider } from "./NavbarContext";
8+
9+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
10+
type NavbarSubComponent = React.ComponentType<any> & {
11+
displayName?: string;
12+
navbarSection?: "main" | "banner";
13+
};
14+
15+
function isNavbarSubComponent(
16+
child: React.ReactNode
17+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
18+
): child is ReactElement<any, NavbarSubComponent> {
19+
return (
20+
React.isValidElement(child) &&
21+
typeof child.type === "function" &&
22+
"displayName" in child.type
23+
);
24+
}
25+
26+
function filterNavbarSubComponentsBySection(
27+
children: React.ReactNode,
28+
section: "main" | "banner"
29+
): React.ReactNode {
30+
return React.Children.toArray(children).filter(
31+
(child) =>
32+
isNavbarSubComponent(child) && child.type.navbarSection === section
33+
);
34+
}
35+
36+
type NavbarGenericProps = PropsWithChildren<{
37+
className?: string;
38+
showWalletInteraction?: boolean;
39+
showBanner?: boolean;
40+
fixed?: boolean;
41+
blurred?: boolean;
42+
}>;
43+
44+
export function NavbarGeneric({
45+
children,
46+
fixed = false,
47+
blurred = false,
48+
className = "",
49+
showWalletInteraction = true,
50+
showBanner = false,
51+
}: NavbarGenericProps) {
52+
const mainComponents = filterNavbarSubComponentsBySection(children, "main");
53+
const bannerComponent = filterNavbarSubComponentsBySection(
54+
children,
55+
"banner"
56+
);
57+
58+
return (
59+
<NavbarProvider value={{ showWalletInteraction, showBanner }}>
60+
<header className="w-full mb-3" data-testid="navbar-container">
61+
<nav
62+
className={`w-full z-20 shadow-md ${className} ${
63+
fixed ? "fixed" : ""
64+
} ${blurred ? "blurred" : ""}`}
65+
data-testid="navbar"
66+
>
67+
<div className="h-16 mx-auto py-[10px] px-4 sm:px-6 lg:px-20 flex justify-between items-center">
68+
{mainComponents}
69+
</div>
70+
{bannerComponent}
71+
</nav>
72+
</header>
73+
</NavbarProvider>
74+
);
75+
}
76+
77+
NavbarGeneric.Logo = NavbarLogo;
78+
79+
// Actions component for right-side items
80+
NavbarGeneric.Actions = NavbarActions;
81+
82+
NavbarGeneric.ConnectButton = NavbarConnectButton;
83+
84+
NavbarGeneric.CustomAction = NavbarCustomAction;
85+
86+
NavbarGeneric.Banner = NavbarBanner;
87+
88+
NavbarGeneric.displayName = "NavbarGeneric";
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
import { PropsWithChildren } from "react";
2+
import { Link } from "react-router-dom";
3+
4+
import { ReactComponent as GitcoinLogo } from "../../assets/gitcoinlogo-black.svg";
5+
import { NavbarSeparator } from "./NavbarSeparator";
6+
export function NavbarLogo({
7+
to = "",
8+
children,
9+
}: PropsWithChildren<{
10+
to?: string;
11+
}>) {
12+
return (
13+
<Link //NavbarLink
14+
to={to}
15+
className="flex-shrink-0 flex items-center gap-4"
16+
data-testid="navbar-logo"
17+
>
18+
<GitcoinLogo className="h-8 w-auto" />
19+
{children ? (
20+
<>
21+
<NavbarSeparator />
22+
{children}
23+
</>
24+
) : null}
25+
</Link>
26+
);
27+
}
28+
29+
NavbarLogo.displayName = "NavbarLogo";
30+
NavbarLogo.navbarSection = "main";
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
import React from "react";
2+
3+
export function NavbarSeparator() {
4+
return <div className="border-r border-[1.5px] border-grey-400 h-4" />;
5+
}
6+
7+
NavbarSeparator.displayName = "NavbarSeparator";
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export { NavbarGeneric } from "./NavbarGeneric";

0 commit comments

Comments
 (0)