Nye-TeeOff/frontend/src/components/Header.tsx

338 lines
12 KiB
TypeScript

"use client";
import Image from "next/image";
import Link from "next/link";
import { useRef, useState } from "react";
import HeaderSearch from "@/components/HeaderSearch";
type NavItem = {
href: string;
label: string;
external?: boolean;
};
const placeGroups = [
{
label: "Hele Norge",
items: [{ href: "/sted/norge", label: "Hele Norge" }],
},
{
label: "Nord-Norge",
items: [
{ href: "/sted/nord-norge", label: "Nord-Norge" },
{ href: "/sted/finnmark", label: "Finnmark" },
{ href: "/sted/troms", label: "Troms" },
{ href: "/sted/nordland", label: "Nordland" },
],
},
{
label: "Midt-Norge",
items: [
{ href: "/sted/nord-trondelag", label: "Nord-Trøndelag" },
{ href: "/sted/sor-trondelag", label: "Sør-Trøndelag" },
{ href: "/sted/trondelag", label: "Trøndelag" },
],
},
{
label: "Vestlandet",
items: [
{ href: "/sted/vestlandet", label: "Vestlandet" },
{ href: "/sted/more-og-romsdal", label: "Møre og Romsdal" },
{ href: "/sted/sogn-og-fjordane", label: "Sogn og Fjordane" },
{ href: "/sted/hordaland", label: "Hordaland" },
{ href: "/sted/rogaland", label: "Rogaland" },
],
},
{
label: "Sørlandet",
items: [
{ href: "/sted/sorlandet", label: "Sørlandet" },
{ href: "/sted/vest-agder", label: "Vest-Agder" },
{ href: "/sted/aust-agder", label: "Aust-Agder" },
],
},
{
label: "Østlandet",
items: [
{ href: "/sted/ostlandet", label: "Østlandet" },
{ href: "/sted/telemark", label: "Telemark" },
{ href: "/sted/vestfold", label: "Vestfold" },
{ href: "/sted/ostfold", label: "Østfold" },
{ href: "/sted/buskerud", label: "Buskerud" },
{ href: "/sted/hedmark", label: "Hedmark" },
{ href: "/sted/oppland", label: "Oppland" },
{ href: "/sted/oslo-og-akershus", label: "Oslo og Akershus" },
{ href: "/sted/akershus", label: "Akershus" },
{ href: "/sted/oslo", label: "Oslo" },
],
},
];
const primaryNavItems: NavItem[] = [
{ href: "/medlemskap", label: "Medlemskap" },
{ href: "/vtg", label: "VTG" },
{ href: "/banebesok", label: "Banebesøk" },
{ href: "/meninger", label: "Meninger" },
];
const resourceNavItems: NavItem[] = [
{ href: "/turneringer", label: "Turneringer" },
{ href: "/klubbnummer", label: "Klubbnummer" },
{ href: "https://golfquiz.no", label: "Golfquiz.no", external: true },
{ href: "https://golfbox.golf", label: "Golfbox", external: true },
{ href: "/om", label: "FAQ / Om" },
{ href: "/kontakt", label: "Kontakt" },
{ href: "/personvern-og-cookies", label: "Personvern / Cookies" },
];
export default function Header() {
const [isOpen, setIsOpen] = useState(false);
const [isPlacesOpen, setIsPlacesOpen] = useState(false);
const [isResourcesOpen, setIsResourcesOpen] = useState(false);
const placesCloseTimeoutRef = useRef<number | null>(null);
const clearPlacesCloseTimeout = () => {
if (placesCloseTimeoutRef.current !== null) {
window.clearTimeout(placesCloseTimeoutRef.current);
placesCloseTimeoutRef.current = null;
}
};
const openPlacesMenu = () => {
clearPlacesCloseTimeout();
setIsPlacesOpen(true);
};
const schedulePlacesClose = () => {
clearPlacesCloseTimeout();
placesCloseTimeoutRef.current = window.setTimeout(() => {
setIsPlacesOpen(false);
placesCloseTimeoutRef.current = null;
}, 120);
};
const closeAllMenus = () => {
setIsOpen(false);
setIsPlacesOpen(false);
setIsResourcesOpen(false);
};
const mobilePlacesItems = placeGroups.flatMap((group) => group.items);
const mobileMenuLinkClass = "text-lg font-extrabold uppercase tracking-[0.08em] text-white";
const mobileMenuSectionHeadingClass = "text-lg font-extrabold uppercase tracking-[0.08em] text-[#8BC34A]";
return (
<header className="sticky top-0 z-[2000] border-b border-white/10 bg-[#25312A]/95 text-white shadow-sm backdrop-blur-md">
<div className="mx-auto flex h-20 max-w-[1400px] items-center justify-between px-4 sm:px-6 lg:px-8">
<Link href="/" className="h-10 transition-transform hover:scale-[1.02] active:scale-95 md:h-12">
<Image
src="/TeeOff-logo-Retina-1.png"
alt="TeeOff.no"
width={336}
height={102}
priority
className="h-full w-auto object-contain"
/>
</Link>
<nav className="hidden items-center gap-6 text-[12px] font-extrabold uppercase tracking-[0.14em] text-white/90 lg:flex xl:gap-8">
<div
className="group relative"
onMouseEnter={openPlacesMenu}
onMouseLeave={schedulePlacesClose}
>
<button
type="button"
className="flex items-center gap-2 text-[12px] font-extrabold uppercase tracking-[0.14em] transition hover:text-[#8BC34A]"
onClick={() => {
clearPlacesCloseTimeout();
setIsPlacesOpen((current) => !current);
setIsResourcesOpen(false);
}}
aria-expanded={isPlacesOpen}
>
<span>Steder</span>
<span className={`text-[10px] transition ${isPlacesOpen ? "rotate-180" : ""}`}></span>
</button>
{isPlacesOpen && (
<div
className="fixed left-1/2 top-20 z-[2100] w-[min(100vw-2rem,68rem)] -translate-x-1/2 px-4"
onMouseEnter={openPlacesMenu}
onMouseLeave={schedulePlacesClose}
>
<div className="max-h-[min(80vh,42rem)] overflow-y-auto rounded-[1.75rem] border border-white/10 bg-[#25312A] p-5 shadow-2xl">
<div className="grid grid-cols-2 gap-x-6 gap-y-5 md:grid-cols-3">
{placeGroups.map((group) => (
<div key={group.label}>
<p className="mb-3 text-[10px] font-extrabold uppercase tracking-[0.2em] text-[#8BC34A]">
{group.label}
</p>
<div className="space-y-2">
{group.items.map((item) => (
<Link
key={item.href}
href={item.href}
className="block text-[13px] font-bold normal-case tracking-normal text-white/88 transition hover:text-[#FF5722]"
onClick={() => setIsPlacesOpen(false)}
>
{item.label}
</Link>
))}
</div>
</div>
))}
</div>
</div>
</div>
)}
</div>
{primaryNavItems.map((item) => (
<Link key={item.href} href={item.href} className="transition hover:text-[#8BC34A]">
{item.label}
</Link>
))}
<div
className="group relative"
onMouseEnter={() => setIsResourcesOpen(true)}
onMouseLeave={() => setIsResourcesOpen(false)}
>
<button
type="button"
className="flex items-center gap-2 text-[12px] font-extrabold uppercase tracking-[0.14em] transition hover:text-[#8BC34A]"
onClick={() => {
setIsResourcesOpen((current) => !current);
setIsPlacesOpen(false);
}}
aria-expanded={isResourcesOpen}
>
<span>Ressurser</span>
<span className={`text-[10px] transition ${isResourcesOpen ? "rotate-180" : ""}`}></span>
</button>
{isResourcesOpen && (
<div className="absolute right-0 top-full z-[2100] w-[20rem]">
<div className="rounded-[1.75rem] border border-white/10 bg-[#25312A] p-5 shadow-2xl">
<p className="mb-3 text-[10px] font-extrabold uppercase tracking-[0.2em] text-[#8BC34A]">
Ressurser
</p>
<div className="space-y-2">
{resourceNavItems.map((item) =>
item.external ? (
<a
key={item.href}
href={item.href}
target="_blank"
rel="noreferrer"
className="flex items-center justify-between text-[13px] font-bold normal-case tracking-normal text-white/88 transition hover:text-[#FF5722]"
onClick={() => setIsResourcesOpen(false)}
>
<span>{item.label}</span>
<span aria-hidden="true"></span>
</a>
) : (
<Link
key={item.href}
href={item.href}
className="block text-[13px] font-bold normal-case tracking-normal text-white/88 transition hover:text-[#FF5722]"
onClick={() => setIsResourcesOpen(false)}
>
{item.label}
</Link>
),
)}
</div>
</div>
</div>
)}
</div>
<HeaderSearch
onOpen={() => {
setIsPlacesOpen(false);
setIsResourcesOpen(false);
}}
/>
</nav>
<button onClick={() => setIsOpen(!isOpen)} className="p-2 text-white lg:hidden" aria-label="Meny">
<div className="mb-1.5 h-0.5 w-6 bg-current transition-all"></div>
<div className="mb-1.5 h-0.5 w-6 bg-current"></div>
<div className="h-0.5 w-6 bg-current"></div>
</button>
</div>
{isOpen && (
<div className="absolute left-0 top-20 max-h-[calc(100dvh-5rem)] w-full overflow-y-auto overscroll-y-contain border-b border-white/10 bg-[#25312A] px-6 py-6 shadow-2xl md:hidden">
<div className="flex flex-col gap-5 pb-[calc(env(safe-area-inset-bottom)+3rem)]">
{primaryNavItems.map((item) => (
<Link
key={item.href}
onClick={closeAllMenus}
href={item.href}
className={mobileMenuLinkClass}
>
{item.label}
</Link>
))}
<div className="border-t border-white/10 pt-5">
<p className={mobileMenuSectionHeadingClass}>Ressurser</p>
<div className="mt-4 flex flex-col gap-4">
{resourceNavItems.map((item) =>
item.external ? (
<a
key={item.href}
href={item.href}
target="_blank"
rel="noreferrer"
className={`${mobileMenuLinkClass} flex items-center justify-between gap-4`}
onClick={closeAllMenus}
>
<span>{item.label}</span>
<span aria-hidden="true"></span>
</a>
) : (
<Link
key={item.href}
onClick={closeAllMenus}
href={item.href}
className={mobileMenuLinkClass}
>
{item.label}
</Link>
),
)}
</div>
</div>
<div className="border-t border-white/10 pt-5">
<p className={mobileMenuSectionHeadingClass}>Steder</p>
<div className="mt-4 grid gap-4">
{mobilePlacesItems.map((item) => (
<Link
key={item.href}
onClick={closeAllMenus}
href={item.href}
className={`block ${mobileMenuLinkClass}`}
>
{item.label}
</Link>
))}
</div>
</div>
<div className="border-t border-white/10 pt-5">
<p className={mobileMenuSectionHeadingClass}>Søk</p>
<div className="mt-4">
<HeaderSearch mobile onNavigate={closeAllMenus} />
</div>
</div>
</div>
</div>
)}
</header>
);
}