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 { 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 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-6 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"
>
<div className="flex items-center justify-between gap-2">
<Button
aria-label="Home"
variant="ghost"
size="icon-lg"
className="size-11 rounded-full bg-muted"
render={<Link href="#" role="link" />}
nativeButton={false}
>
<Icons.Logo aria-hidden="true" className="size-6 text-foreground" />
</Button>
<div className="flex items-center gap-1 rounded-full bg-muted p-1.5 max-sm:hidden">
{NAV_LINKS.map((link) => (
<Button
key={link.href}
variant="ghost"
className="h-8 rounded-full px-4 text-sm font-medium text-muted-foreground transition-colors duration-200 ease-out hover:bg-card hover:text-foreground"
render={<Link href={link.href} role="link" />}
nativeButton={false}
>
{link.label}
</Button>
))}
</div>
<Button
onClick={() => setIsOpen(!isOpen)}
variant="ghost"
className="size-11 rounded-full bg-transparent text-foreground hover:bg-transparent sm:hidden *:[&>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>
<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 flex-col gap-1 rounded-3xl bg-muted p-2 sm:hidden"
>
{NAV_LINKS.map((link) => (
<Button
key={link.href}
variant="ghost"
className="h-11 w-full justify-start rounded-2xl px-4 font-medium text-muted-foreground transition-colors duration-200 ease-out hover:bg-card hover:text-foreground"
onClick={() => setIsOpen(false)}
render={<Link href={link.href} role="link" />}
nativeButton={false}
>
{link.label}
</Button>
))}
</motion.div>
)}
</AnimatePresence>
</nav>
</header>
)
}




























