header.tsx
"use client"
import { useEffect, useId, useRef, useState } from "react"
import Link from "next/link"
import { AnimatePresence, motion, useReducedMotion } from "motion/react"
import { Button } from "@/components/ui/button"
import { Separator } from "@/components/ui/separator"
import { Icons } from "@/components/ui/icons"
import { XIcon } from 'lucide-react'
const NAV_LINKS = [
{ label: "Features", href: "#features" },
{ label: "Pricing", href: "#pricing" },
{ label: "About", href: "#about" },
{ label: "Blog", href: "#blog" },
]
export function SiteHeader() {
const [isOpen, setIsOpen] = useState(false)
const mid = Math.ceil(NAV_LINKS.length / 2)
const menuId = useId()
const reducedMotion = useReducedMotion()
const menuRef = useRef<HTMLElement>(null)
useEffect(() => {
if (!isOpen) return
const dismiss = (event: PointerEvent | KeyboardEvent) => {
if (event instanceof KeyboardEvent) {
if (event.key !== "Escape") return
event.preventDefault()
setIsOpen(false)
menuRef.current
?.querySelector<HTMLButtonElement>('button[aria-expanded="true"]')
?.focus()
} else if (
event.target instanceof Node &&
!menuRef.current?.contains(event.target)
) {
setIsOpen(false)
}
}
const viewportWidth = window.innerWidth
const resize = () => {
if (window.innerWidth === viewportWidth) return
setIsOpen(false)
}
document.addEventListener("pointerdown", dismiss)
document.addEventListener("keydown", dismiss)
window.addEventListener("resize", resize)
return () => {
document.removeEventListener("pointerdown", dismiss)
document.removeEventListener("keydown", dismiss)
window.removeEventListener("resize", resize)
}
}, [isOpen])
return (
<header
ref={menuRef}
className="sticky top-0 z-50 flex w-full justify-center px-4"
data-slot="header"
>
<nav
aria-label="Main navigation"
className="flex w-full max-w-md flex-col gap-2 sm:max-w-fit"
>
<div className="rounded-2xl bg-card shadow-elevated-2xs">
<div className="flex h-14 items-center px-2 sm:hidden">
<Button
aria-label="Home"
variant="ghost"
size="icon-lg"
render={<Link href="#" role="link" />}
nativeButton={false}
>
<Icons.Logo
aria-hidden="true"
className="size-8 text-foreground"
/>
</Button>
<Separator
orientation="vertical"
className="mx-2 my-auto h-14 bg-border/50"
/>
<div className="flex-1" />
<Separator
orientation="vertical"
className="mx-2 my-auto h-14 bg-border/50"
/>
<Button
onClick={() => setIsOpen(!isOpen)}
variant="ghost"
className="w-8 p-0 text-muted-foreground transition-colors hover:bg-transparent hover:text-foreground hover:no-underline *:[&>svg]:size-5"
aria-label={isOpen ? "Close Menu" : "Open Menu"}
aria-expanded={isOpen}
aria-controls={isOpen ? menuId : undefined}
>
{isOpen ? (
<XIcon aria-hidden="true" data-icon="inline-end" />
) : (
<svg
aria-hidden="true"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth={2}
strokeLinecap="round"
className="size-5"
>
<path d="M5 9h14M5 15h14" />
</svg>
)}
</Button>
</div>
<div className="group flex h-14 items-center justify-center gap-1 px-2 max-sm:hidden">
{NAV_LINKS.map((link, i) => (
<span key={link.label} className="contents">
{i === mid && (
<>
<Separator
orientation="vertical"
className="mr-2 h-14 bg-border/50"
/>
<Button
aria-label="Home"
variant="ghost"
size="icon-lg"
render={<Link href="#" role="link" />}
nativeButton={false}
>
<Icons.Logo
aria-hidden="true"
className="size-8 text-foreground"
/>
</Button>
<Separator
orientation="vertical"
className="ml-2 h-14 bg-border/50"
/>
</>
)}
<Button
variant="ghost"
className="text-muted-foreground transition-colors duration-200 ease-out group-has-[[data-nav-link]:hover]:text-muted-foreground/50 hover:text-foreground hover:no-underline hover:group-has-[[data-nav-link]:hover]:text-foreground"
data-nav-link=""
render={<Link href={link.href} role="link" />}
nativeButton={false}
>
{link.label}
</Button>
</span>
))}
</div>
</div>
<AnimatePresence>
{isOpen && (
<motion.div
data-slot="navigation-menu-popup"
id={menuId}
initial={{ opacity: 0, y: reducedMotion ? 0 : -8 }}
animate={{ opacity: 1, y: 0 }}
exit={{ opacity: 0, y: reducedMotion ? 0 : -8 }}
transition={{
duration: reducedMotion ? 0 : 0.2,
ease: "easeOut",
}}
className="flex w-full flex-col gap-1 rounded-2xl bg-popover p-2 shadow-elevated-2xs sm:hidden"
>
{NAV_LINKS.map((link) => (
<Button
key={link.href}
variant="ghost"
className="w-full justify-start"
onClick={() => setIsOpen(false)}
render={<Link href={link.href} role="link" />}
nativeButton={false}
>
{link.label}
</Button>
))}
</motion.div>
)}
</AnimatePresence>
</nav>
</header>
)
}





























