221 lines
6.7 KiB
TypeScript
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>
|
|
</>
|
|
);
|
|
}
|