Files
New-Optic/components/Navbar.tsx

102 lines
4.7 KiB
TypeScript

"use client";
import { AnimatePresence, motion } from "framer-motion";
import { Menu, X } from "lucide-react";
import Image from "next/image";
import { type MouseEvent, useState } from "react";
import { business, type Locale } from "@/config/business";
import type { Messages } from "@/messages";
import LanguageSwitcher from "./LanguageSwitcher";
import PhysicsButton from "./PhysicsButton";
export default function Navbar({ locale, onLocaleChange, t, whatsappUrl }: { locale: Locale; onLocaleChange: (locale: Locale) => void; t: Messages; whatsappUrl: string }) {
const [open, setOpen] = useState(false);
function scrollToSection(event: MouseEvent<HTMLAnchorElement>, href: string) {
if (!href.startsWith("#")) return;
event.preventDefault();
const target = document.querySelector(href);
if (!target) return;
setOpen(false);
target.scrollIntoView({ behavior: "smooth", block: "start" });
window.history.replaceState(null, "", href);
}
return (
<header className="fixed inset-x-0 top-0 z-50 px-4 pt-4 sm:px-6">
<nav className="glass premium-glass mx-auto flex max-w-7xl items-center justify-between gap-2 rounded-full px-3 py-3 sm:px-5" aria-label="Main navigation">
<a href="#home" onClick={(event) => scrollToSection(event, "#home")} className="relative z-[3] flex items-center gap-3" aria-label="New Optic home">
<span className="relative grid size-10 place-items-center overflow-hidden rounded-full bg-white shadow-sm">
<Image src={business.assets.logo} alt="New Optic logo" fill sizes="40px" className="object-contain p-1" priority />
</span>
<span className="hidden text-sm font-semibold tracking-[-0.02em] xs:inline sm:inline">{business.name}</span>
</a>
<div className="relative z-[3] hidden items-center gap-7 lg:flex">
{t.nav.links.map((link) => (
<a key={link.href} href={link.href} onClick={(event) => scrollToSection(event, link.href)} className="text-sm font-medium text-ink/58 transition hover:text-ink">
{link.label}
</a>
))}
</div>
<div className="relative z-[3] hidden items-center gap-3 md:flex">
<LanguageSwitcher locale={locale} onLocaleChange={onLocaleChange} />
<PhysicsButton href={whatsappUrl} external className="rounded-full bg-ink px-5 py-2.5 text-sm font-semibold text-white shadow-soft transition-colors hover:bg-optical">
{t.nav.cta}
</PhysicsButton>
</div>
<div className="relative z-[3] flex items-center gap-2 md:hidden">
<PhysicsButton href={whatsappUrl} external className="rounded-full bg-ink px-4 py-2.5 text-xs font-semibold text-white shadow-soft transition-colors hover:bg-optical sm:px-5 sm:text-sm">
{t.nav.cta}
</PhysicsButton>
<motion.button
type="button"
onClick={() => setOpen((value) => !value)}
whileTap={{ scale: 0.94 }}
transition={{ duration: 0.08 }}
className="grid size-11 place-items-center rounded-full bg-white/70 text-ink outline-none transition focus-visible:ring-2 focus-visible:ring-optical/45 focus-visible:ring-offset-2"
aria-label={t.nav.menu}
aria-expanded={open}
>
{open ? <X size={18} /> : <Menu size={18} />}
</motion.button>
</div>
</nav>
<AnimatePresence>
{open ? (
<motion.div
initial={{ opacity: 0, y: -8, scale: 0.985 }}
animate={{ opacity: 1, y: 0, scale: 1 }}
exit={{ opacity: 0, y: -6, scale: 0.99 }}
transition={{ duration: 0.13, ease: "easeOut" }}
className="relative mx-auto mt-3 max-w-7xl overflow-hidden rounded-[2rem] border border-ink/8 bg-white p-4 shadow-glass md:hidden"
>
<div className="grid gap-2">
{t.nav.links.map((link) => (
<motion.a
key={link.href}
href={link.href}
onClick={(event) => scrollToSection(event, link.href)}
initial={{ opacity: 0, x: locale === "ar" ? -8 : 8 }}
animate={{ opacity: 1, x: 0 }}
transition={{ duration: 0.1, ease: "easeOut" }}
whileTap={{ scale: 0.98 }}
className="rounded-2xl px-4 py-3 text-sm font-semibold text-ink/72 hover:bg-smoke"
>
{link.label}
</motion.a>
))}
</div>
<div className="mt-4 flex items-center justify-between gap-3">
<LanguageSwitcher locale={locale} onLocaleChange={onLocaleChange} />
</div>
</motion.div>
) : null}
</AnimatePresence>
</header>
);
}