trptk/components/header/Header.tsx

221 lines
6.7 KiB
TypeScript

"use client";
import { useEffect, useState } from "react";
import Link from "next/link";
import { motion, AnimatePresence, type Variants } from "framer-motion";
import { TrptkLogo } from "../icons/TrptkLogo";
import { MenuToggleButton } from "./MenuToggleButton";
import { ThemeToggleButton } from "./ThemeToggleButton";
import { SocialButtons } from "./SocialButtons";
import { CartButton } from "@/components/cart/CartButton";
import { useCart } from "@/components/cart/CartContext";
import { AccountButton } from "@/components/auth/AccountButton";
import { GlobalSearch } from "@/components/search/GlobalSearch";
const nav = [
{ href: "/", label: "Home" },
{ href: "/blog", label: "Blog" },
{ href: "/about", label: "About" },
{ href: "/artists", label: "Artists" },
{ href: "/composers", label: "Composers" },
{ href: "/releases", label: "Releases" },
];
const overlayVariants = {
open: {
opacity: 1,
pointerEvents: "auto" as const,
transition: {
type: "tween",
ease: "easeInOut",
duration: 0.1,
},
},
closed: {
opacity: 0,
transition: {
type: "tween",
ease: "easeInOut",
duration: 0.1,
delay: 0.5,
},
transitionEnd: {
pointerEvents: "none" as const,
},
},
} satisfies Variants;
const panelVariants = {
open: {
opacity: 1,
transition: {
type: "tween",
ease: "easeInOut",
duration: 0.4,
},
},
closed: {
opacity: 0,
},
exit: {
opacity: 0,
transition: {
type: "tween",
ease: "easeInOut",
duration: 0.5,
delay: 0.5,
},
},
} satisfies Variants;
const listVariants = {
closed: {
transition: {
staggerChildren: 0.1,
staggerDirection: -1,
},
},
open: {
transition: {
delayChildren: 0.1,
staggerChildren: 0.1,
},
},
} satisfies Variants;
const itemVariants = {
closed: { opacity: 0, y: 8 },
open: {
opacity: 1,
y: 0,
transition: { type: "tween", ease: "easeInOut", duration: 0.35 },
},
} satisfies Variants;
export function Header() {
const [open, setOpen] = useState(false);
const [searchOpen, setSearchOpen] = useState(false);
const { itemCount } = useCart();
useEffect(() => {
if (open) {
// Prevent scrolling on both html and body
document.documentElement.style.overflow = "hidden";
document.body.style.overflow = "hidden";
// Prevent iOS Safari overscroll
document.body.style.position = "fixed";
document.body.style.width = "100%";
} else {
document.documentElement.style.overflow = "";
document.body.style.overflow = "";
document.body.style.position = "";
document.body.style.width = "";
}
return () => {
document.documentElement.style.overflow = "";
document.body.style.overflow = "";
document.body.style.position = "";
document.body.style.width = "";
};
}, [open]);
useEffect(() => {
const onKeyDown = (e: KeyboardEvent) => {
if (e.key === "Escape") setOpen(false);
};
window.addEventListener("keydown", onKeyDown);
return () => window.removeEventListener("keydown", onKeyDown);
}, []);
return (
<>
{/* Main Menu */}
<div id="masthead" className="flex w-full justify-between px-6 pt-6 md:px-8 md:pt-8">
<Link
href="/"
className={`content-center overflow-hidden transition-all duration-300 ease-in-out ${searchOpen ? "w-0 pointer-events-none opacity-0 md:w-auto md:pointer-events-auto md:opacity-100" : "opacity-100"}`}
>
<TrptkLogo width={90} />
</Link>
<motion.div
className={`flex gap-3 text-lg ${searchOpen ? "flex-1 md:flex-none" : ""}`}
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
transition={{ type: "tween", ease: "easeInOut", duration: 0.3 }}
>
<GlobalSearch onOpenChange={setSearchOpen} />
<div className={itemCount > 0 ? (searchOpen ? "hidden md:block" : "") : "hidden"}>
<CartButton />
</div>
<MenuToggleButton open={open} onClick={() => setOpen(true)} className={searchOpen ? "hidden md:flex" : ""} />
</motion.div>
</div>
<AnimatePresence>
{open && (
<motion.div
className="fixed inset-0 z-[60]"
variants={overlayVariants}
initial="closed"
animate="open"
exit="closed"
onClick={() => setOpen(false)}
>
{/* Full-screen Menu */}
<motion.aside
className="relative flex h-full w-full flex-col bg-lightbg/85 backdrop-blur-lg transition-[background-color] duration-1000 dark:bg-darkbg/85"
variants={panelVariants}
initial="closed"
animate="open"
exit="exit"
onClick={() => setOpen(false)}
>
{/* Top bar */}
<div className="flex w-full justify-between p-6 md:p-8" onClick={(e) => e.stopPropagation()}>
<Link href="/" className="content-center">
<TrptkLogo width={90} />
</Link>
<div className="flex gap-3 text-lg">
<ThemeToggleButton />
<AccountButton />
<MenuToggleButton open={open} onClick={() => setOpen(false)} />
</div>
</div>
{/* Center nav */}
<motion.nav
className="flex flex-1 items-center justify-center px-6"
initial="closed"
animate="open"
exit="closed"
>
<motion.ul className="space-y-6 text-center font-argesta" variants={listVariants}>
{nav.map((item) => (
<motion.li key={item.href} variants={itemVariants}>
<Link
href={item.href}
onClick={() => setOpen(false)}
className="text-3xl transition-colors duration-200 ease-in-out hover:text-trptkblue dark:hover:text-white"
>
{item.label}
</Link>
</motion.li>
))}
</motion.ul>
</motion.nav>
{/* Bottom bar */}
<div className="flex w-full justify-between p-6 md:p-8" onClick={(e) => e.stopPropagation()}>
<div className="content-center text-sm text-lightsec dark:text-darksec">
<span className="mr-2 font-silkasb">TRPTK</span> Polycast Media Group B.V.
</div>
<SocialButtons />
</div>
</motion.aside>
</motion.div>
)}
</AnimatePresence>
</>
);
}