forked from UKSOURCE/ipv6
236 lines
9.4 KiB
TypeScript
236 lines
9.4 KiB
TypeScript
"use client";
|
|
|
|
import Link from "next/link";
|
|
import { usePathname } from "next/navigation";
|
|
import { useEffect, useMemo, useState } from "react";
|
|
import { useLanguage } from "@/app/context/LanguageContext";
|
|
import { useTranslation } from "@/app/hooks/useTranslation";
|
|
|
|
type NavItem = { label: string; href: string };
|
|
|
|
export default function HeaderClient({
|
|
brand,
|
|
nav,
|
|
}: {
|
|
brand: { name: string };
|
|
nav: NavItem[];
|
|
}) {
|
|
const pathname = usePathname();
|
|
const [mobileOpen, setMobileOpen] = useState(false);
|
|
const { lang, setLang } = useLanguage();
|
|
const { site } = useTranslation();
|
|
|
|
const isHome = pathname === "/" || pathname === "/ipv6" || pathname === "/ipv6/";
|
|
const navItems = useMemo(() => site.nav.filter((x) => x.href !== "/"), [site.nav]);
|
|
|
|
const isActive = (href: string) => {
|
|
if (href === "/") return isHome;
|
|
return pathname === href || pathname === `/ipv6${href}` || pathname?.startsWith(`${href}/`) || pathname?.startsWith(`/ipv6${href}/`);
|
|
};
|
|
|
|
const registerLabel = lang === "vi" ? "Đăng Ký" : "Register";
|
|
const loginLabel = lang === "vi" ? "Đăng Nhập" : "Log In";
|
|
const registerNowLabel = lang === "vi" ? "Đăng Ký Ngay" : "Register Now";
|
|
const loginNowLabel = lang === "vi" ? "Đăng Nhập" : "Log In";
|
|
const backLabel = lang === "vi" ? "Trang Chủ" : "Home";
|
|
|
|
useEffect(() => {
|
|
if (!mobileOpen) return;
|
|
document.body.style.overflow = "hidden";
|
|
return () => {
|
|
document.body.style.overflow = "";
|
|
};
|
|
}, [mobileOpen]);
|
|
|
|
return (
|
|
<>
|
|
<header className="fixed top-0 w-full z-50 bg-surface/90 backdrop-blur-xl border-b border-white/10 shadow-2xl">
|
|
<div className="flex justify-between items-center px-[var(--spacing-gutter)] py-4 lg:py-6 w-full max-w-hd mx-auto">
|
|
<Link
|
|
href="/"
|
|
className="font-[var(--font-display-lg)] text-[16px] sm:text-[18px] lg:text-[22px] text-primary tracking-tighter uppercase font-bold"
|
|
>
|
|
{brand.name}
|
|
</Link>
|
|
|
|
<nav className="hidden lg:flex items-center gap-8 2xl:gap-12">
|
|
<Link
|
|
className={`font-[var(--font-label-caps)] text-sm ${
|
|
isHome ? "text-primary border-b-2 border-primary pb-1" : "text-on-surface-variant hover:text-primary"
|
|
} transition-colors duration-300`}
|
|
href="/"
|
|
>
|
|
{backLabel}
|
|
</Link>
|
|
{navItems.map((item) => {
|
|
const active = isActive(item.href);
|
|
return (
|
|
<Link
|
|
key={item.href}
|
|
className={`font-[var(--font-label-caps)] text-sm transition-colors duration-300 ${
|
|
active ? "text-primary border-b-2 border-primary pb-1" : "text-on-surface-variant hover:text-primary"
|
|
}`}
|
|
href={item.href}
|
|
>
|
|
{item.label}
|
|
</Link>
|
|
);
|
|
})}
|
|
</nav>
|
|
|
|
<div className="flex items-center gap-4 lg:gap-6">
|
|
{/* Language Switcher — Desktop */}
|
|
<div className="hidden sm:flex items-center gap-1 font-[var(--font-label-caps)] text-xs">
|
|
<button
|
|
type="button"
|
|
onClick={() => setLang("en")}
|
|
className={`flex items-center gap-1 px-2 py-1 rounded transition-all duration-200 ${
|
|
lang === "en"
|
|
? "text-primary border-b-2 border-primary"
|
|
: "text-on-surface-variant/60 hover:text-primary"
|
|
}`}
|
|
title="English"
|
|
>
|
|
<span>EN</span>
|
|
</button>
|
|
<span className="text-on-surface-variant/30">|</span>
|
|
<button
|
|
type="button"
|
|
onClick={() => setLang("vi")}
|
|
className={`flex items-center gap-1 px-2 py-1 rounded transition-all duration-200 ${
|
|
lang === "vi"
|
|
? "text-primary border-b-2 border-primary"
|
|
: "text-on-surface-variant/60 hover:text-primary"
|
|
}`}
|
|
title="Tiếng Việt"
|
|
>
|
|
<span>VIE</span>
|
|
</button>
|
|
</div>
|
|
|
|
<Link
|
|
href="/login"
|
|
className="hidden sm:inline-flex gold-gradient-bg text-on-primary px-4 lg:px-6 py-2 lg:py-2.5 font-[var(--font-body-base)] text-xs lg:text-sm font-bold uppercase tracking-wider rounded-lg active:scale-95 transition-transform shadow-lg shadow-primary/10"
|
|
>
|
|
{loginLabel}
|
|
</Link>
|
|
<Link
|
|
href="/registration"
|
|
className="hidden sm:inline-flex gold-gradient-bg text-on-primary px-4 lg:px-6 py-2 lg:py-2.5 font-[var(--font-body-base)] text-xs lg:text-sm font-bold uppercase tracking-wider rounded-lg active:scale-95 transition-transform shadow-lg shadow-primary/10"
|
|
>
|
|
{registerLabel}
|
|
</Link>
|
|
<button
|
|
type="button"
|
|
className="lg:hidden text-primary p-2"
|
|
aria-label="Toggle menu"
|
|
aria-expanded={mobileOpen}
|
|
onClick={() => setMobileOpen((v) => !v)}
|
|
>
|
|
<span className="material-symbols-outlined text-3xl">{mobileOpen ? "close" : "menu"}</span>
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</header>
|
|
|
|
{/* Mobile Menu Overlay */}
|
|
<div
|
|
className={`fixed inset-0 bg-black/80 backdrop-blur-md z-[60] transition-opacity duration-500 lg:hidden ${
|
|
mobileOpen ? "opacity-100 pointer-events-auto" : "opacity-0 pointer-events-none"
|
|
}`}
|
|
onClick={() => setMobileOpen(false)}
|
|
/>
|
|
|
|
{/* Mobile Menu Side Drawer */}
|
|
<div
|
|
className={`fixed inset-y-0 right-0 w-[80%] sm:w-[380px] z-[70] flex flex-col bg-[#1a1a1a] shadow-[-20px_0_60px_rgba(0,0,0,1)] transition-transform duration-500 ease-out lg:hidden ${
|
|
mobileOpen ? "translate-x-0" : "translate-x-full"
|
|
}`}
|
|
>
|
|
<div className="p-8 flex justify-between items-center border-b border-white/5 bg-[#222222]">
|
|
<span className="font-[var(--font-display-lg)] text-primary text-sm font-bold uppercase tracking-widest">
|
|
Menu
|
|
</span>
|
|
<button
|
|
type="button"
|
|
className="text-primary/70 hover:text-primary transition-colors"
|
|
onClick={() => setMobileOpen(false)}
|
|
>
|
|
<span className="material-symbols-outlined text-4xl">close</span>
|
|
</button>
|
|
</div>
|
|
|
|
<div className="flex-1 flex flex-col p-8 gap-8 overflow-y-auto">
|
|
<nav className="flex flex-col gap-6">
|
|
<Link
|
|
className={`text-xl font-[var(--font-display-lg)] ${isHome ? "text-primary" : "text-on-surface"} hover:text-primary transition-colors`}
|
|
href="/"
|
|
onClick={() => setMobileOpen(false)}
|
|
>
|
|
{backLabel}
|
|
</Link>
|
|
{navItems.map((item) => {
|
|
const active = isActive(item.href);
|
|
return (
|
|
<Link
|
|
key={item.href}
|
|
className={`text-xl font-[var(--font-display-lg)] transition-colors ${
|
|
active ? "text-primary" : "text-on-surface hover:text-primary"
|
|
}`}
|
|
href={item.href}
|
|
onClick={() => setMobileOpen(false)}
|
|
>
|
|
{item.label}
|
|
</Link>
|
|
);
|
|
})}
|
|
</nav>
|
|
|
|
<div className="mt-auto pt-10 border-t border-white/5 flex flex-col gap-6">
|
|
{/* Language Switcher — Mobile */}
|
|
<div className="flex items-center gap-2 font-[var(--font-label-caps)] text-sm">
|
|
<button
|
|
type="button"
|
|
onClick={() => setLang("en")}
|
|
className={`flex items-center gap-1.5 px-3 py-2 rounded-lg border transition-all duration-200 ${
|
|
lang === "en"
|
|
? "border-primary text-primary bg-primary/10"
|
|
: "border-white/10 text-on-surface-variant/60 hover:text-primary hover:border-primary/40"
|
|
}`}
|
|
>
|
|
<span>EN</span>
|
|
</button>
|
|
<button
|
|
type="button"
|
|
onClick={() => setLang("vi")}
|
|
className={`flex items-center gap-1.5 px-3 py-2 rounded-lg border transition-all duration-200 ${
|
|
lang === "vi"
|
|
? "border-primary text-primary bg-primary/10"
|
|
: "border-white/10 text-on-surface-variant/60 hover:text-primary hover:border-primary/40"
|
|
}`}
|
|
>
|
|
<span>VIE</span>
|
|
</button>
|
|
</div>
|
|
|
|
<Link
|
|
href="/login"
|
|
className="gold-gradient-bg text-on-primary w-full py-5 font-[var(--font-body-base)] text-lg font-bold uppercase tracking-widest rounded-xl text-center shadow-2xl"
|
|
onClick={() => setMobileOpen(false)}
|
|
>
|
|
{loginNowLabel}
|
|
</Link>
|
|
<Link
|
|
href="/registration"
|
|
className="gold-gradient-bg text-on-primary w-full py-5 font-[var(--font-body-base)] text-lg font-bold uppercase tracking-widest rounded-xl text-center shadow-2xl"
|
|
onClick={() => setMobileOpen(false)}
|
|
>
|
|
{registerNowLabel}
|
|
</Link>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</>
|
|
);
|
|
}
|