338 lines
12 KiB
TypeScript
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>
|
|
);
|
|
}
|