Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: header nav component #4

Merged
merged 6 commits into from
Feb 9, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
"format": "prettier --write './**/*.{js,jsx,ts,tsx,css,md,json}'"
},
"dependencies": {
"@headlessui/react": "^1.7.18",
"next": "14.1.0",
"react": "^18",
"react-dom": "^18",
Expand Down
8 changes: 5 additions & 3 deletions src/app/(routes)/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import type { Metadata } from "next";
import { twMerge } from "tailwind-merge";
import { Barlow } from "next/font/google";

import HeaderNav from "@/app/_components/header-nav";
import { HeaderNav } from "@/app/_components/header-nav";

import "./globals.css";

Expand All @@ -23,8 +23,10 @@ export default function RootLayout({
return (
<html lang="en">
<body className={twMerge(inter.className, "bg-grey-dark text-light-300")}>
<HeaderNav />
{children}
<div>
<HeaderNav />
{children}
</div>
</body>
</html>
);
Expand Down
18 changes: 18 additions & 0 deletions src/app/_components/button.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { ComponentProps } from "react";
import { twMerge } from "tailwind-merge";

type ButtonProps = ComponentProps<"button">;

export function Button({ className, children, ...props }: ButtonProps) {
return (
<button
className={twMerge(
"h-10 rounded-full border border-aqua-100/[.05] bg-aqua-100/[.05] px-6 py-2 text-xs uppercase tracking-wide-4 text-aqua-100 shadow-sm hover:opacity-80",
className,
)}
{...props}
>
{children}
</button>
);
}
32 changes: 0 additions & 32 deletions src/app/_components/header-nav.tsx

This file was deleted.

76 changes: 76 additions & 0 deletions src/app/_components/header-nav/flyout-menu.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
import { Fragment } from "react";
import { Popover, Transition } from "@headlessui/react";
import Link from "next/link";
import { twMerge } from "tailwind-merge";

import { ChevronDownIcon } from "../icons";
import { IconBox } from "../icon-box";

import { NavItem } from "./types";

type Props = {
buttonLabel: string;
menuItems: NavItem[];
className?: string;
useExternalLinks?: boolean;
};

export function FlyoutMenu({
useExternalLinks,
buttonLabel,
menuItems,
className,
}: Props) {
return (
<Popover className={twMerge("relative", className)}>
<Popover.Button className="flex cursor-pointer items-center justify-center gap-1 text-md text-light-300 focus-visible:outline-none">
{buttonLabel}
<ChevronDownIcon />
</Popover.Button>
<Transition
as={Fragment}
enter="transition ease-out duration-200"
enterFrom="opacity-0 translate-y-1"
enterTo="opacity-100 translate-y-0"
leave="transition ease-in duration-150"
leaveFrom="opacity-100 translate-y-0"
leaveTo="opacity-0 translate-y-1"
>
<Popover.Panel
className={twMerge(
"absolute left-40 z-10 mt-5 flex w-screen max-w-max -translate-x-1/2 px-4",
)}
>
<div className="w-screen max-w-md flex-auto overflow-hidden rounded-3xl bg-grey-light text-sm shadow-sm">
<div className="p-4">
{menuItems.map((item) => (
<span key={item.href} className="group">
<Popover.Button
as={useExternalLinks ? "a" : Link}
href={item.href}
className={twMerge(
"flex flex-row gap-4 rounded-3xl p-4 transition group-hover:shadow-sm",
item.containerClassName,
)}
target={useExternalLinks ? "_blank" : undefined}
rel={useExternalLinks ? "noopener noreferrer" : undefined}
>
<IconBox className={item.iconContainerClassName}>
<item.Icon className={item.iconClassName} />
</IconBox>
<div className="flex flex-col">
<div className="text-light-100">{item.label}</div>
<div className="text-sm tabular-nums text-light-300">
{item.description}
</div>
</div>
</Popover.Button>
</span>
))}
</div>
</div>
</Popover.Panel>
</Transition>
</Popover>
);
}
158 changes: 158 additions & 0 deletions src/app/_components/header-nav/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,158 @@
"use client";

import Link from "next/link";
import { usePathname } from "next/navigation";
import { useState } from "react";
import { twMerge } from "tailwind-merge";

import {
AcrossIcon,
MenuIcon,
MinusIcon,
BridgeIcon,
PlusIcon,
CheckmarkSimpleIcon,
DiscordIcon,
TwitterIcon,
MediumIcon,
} from "../icons";
import { Button } from "../button";

import { FlyoutMenu } from "./flyout-menu";
import { MobileMenu } from "./mobile-menu";
import { ProductsSubNav } from "./products-sub-nav";

const productsNavigationItems = [
{
label: "Across Bridge",
description: "Bridge Without Compromise",
href: "/across-bridge",
Icon: BridgeIcon,
iconClassName: "group-hover:drop-shadow-aqua h-4 w-4",
iconContainerClassName: "bg-aqua-100/[.05]",
containerClassName: "group-hover:bg-aqua-100/[.05]",
},
{
label: "Across+",
description: "Cross-chain Bridge hooks to Fullfill User intents",
href: "/across-plus",
Icon: PlusIcon,
iconClassName: "group-hover:drop-shadow-teal h-5 w-5",
iconContainerClassName: "bg-teal-100/[.05]",
containerClassName: "group-hover:bg-teal-100/[.05]",
},
{
label: "Across Settlement",
description: "Cross-chain Intents Settlement Layer",
href: "/across-settlement",
Icon: CheckmarkSimpleIcon,
iconClassName: "group-hover:drop-shadow-purple h-4 w-5",
iconContainerClassName: "bg-purple-100/[.05]",
containerClassName: "group-hover:bg-purple-100/[.05]",
},
];

const communityNavigationItems = [
{
label: "Discord",
description: "Access support and chat with community members",
href: "https://discord.com/invite/sKSkhTtu8s",
Icon: DiscordIcon,
iconClassName: "h-4 w-5",
iconContainerClassName: "bg-light-100/[.05]",
containerClassName: "group-hover:bg-light-100/[.05]",
},
{
label: "Twitter",
description: "Follow for the latest updates on Across",
href: "https://twitter.com/AcrossProtocol",
Icon: TwitterIcon,
iconClassName: "h-4 w-4",
iconContainerClassName: "bg-light-100/[.05]",
containerClassName: "group-hover:bg-light-100/[.05]",
},
{
label: "Medium",
description: "Read deep dives on Across infra and campaigns",
href: "https://medium.com/across-protocol",
Icon: MediumIcon,
iconClassName: "h-3 w-5",
iconContainerClassName: "bg-light-100/[.05]",
containerClassName: "group-hover:bg-light-100/[.05]",
},
];

export function HeaderNav() {
const [isMenuOpen, setIsMenuOpen] = useState(false);

const pathname = usePathname();
const isProductsPath = productsNavigationItems.some((item) =>
pathname.startsWith(item.href),
);

return (
<header className="sticky top-0">
<nav className="mx-auto max-w-7xl p-4">
<div className="flex flex-row items-center justify-between">
<div className="flex flex-row items-center">
<Link href="/" className="mr-12">
<AcrossIcon
className={twMerge(
"h-8 w-8 transition",
pathname === "/across-plus"
? "fill-teal-100"
: pathname === "/across-settlement"
? "fill-purple-100"
: "fill-aqua-100",
)}
/>
</Link>
<div className="hidden flex-row items-center gap-6 sm:flex">
<Link href="/">Home</Link>
<FlyoutMenu buttonLabel="Products" menuItems={productsNavigationItems} />
<FlyoutMenu
buttonLabel="Community"
menuItems={communityNavigationItems}
useExternalLinks
/>
</div>
</div>
<div className="flex flex-row gap-3">
<Button
className={twMerge(
"hidden transition sm:block",
pathname === "/across-plus"
? "border-teal-100/[.05] bg-teal-100/[.05] text-teal-100"
: pathname === "/across-settlement"
? "border-purple-100/[.05] bg-purple-100/[.05] text-purple-100"
: "",
)}
>
Bridge now
</Button>
{/* Only show menu button on mobile */}
<button
className="flex h-10 w-10 items-center justify-center rounded-full border border-grey-600 sm:hidden"
onClick={() => setIsMenuOpen((isMenuOpen) => !isMenuOpen)}
>
{isMenuOpen ? <MinusIcon /> : <MenuIcon />}
</button>
</div>
</div>
</nav>
{/* Only show sub-menu when in products-related path */}
{isProductsPath && !isMenuOpen && (
<ProductsSubNav navItems={productsNavigationItems} />
)}
<div className={isMenuOpen ? "relative" : "hidden"}>
<MobileMenu
isMenuOpen={isMenuOpen}
onClickItem={() => setIsMenuOpen(false)}
productsNavItems={productsNavigationItems}
communityNavItems={communityNavigationItems}
pathname={pathname}
/>
</div>
</header>
);
}
Loading
Loading