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 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:h-14 sm:max-w-fit sm:flex-row sm:items-center sm:gap-2 sm:px-2"
>
<div className="flex h-14 w-full items-center justify-between sm:w-auto sm:shrink-0">
<Button
aria-label="Home"
variant="ghost"
size="icon-lg"
render={<Link href="#" role="link" />}
nativeButton={false}
>
<Icons.Logo aria-hidden="true" className="size-6 text-foreground" />
</Button>
<Button
onClick={() => setIsOpen(!isOpen)}
variant="ghost"
className="size-10 rounded-xl text-foreground 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>
<div className="flex h-5 items-center justify-center max-sm:hidden">
<Separator orientation="vertical" className="bg-border/50" />
</div>
<div className="group flex items-center gap-1 max-sm:hidden">
{NAV_LINKS.map((link) => (
<Button
key={link.href}
variant="ghost"
className="text-muted-foreground group-has-[a:hover]:text-muted-foreground/50 hover:text-foreground hover:group-has-[a:hover]:text-foreground"
render={<Link href={link.href} role="link" />}
nativeButton={false}
>
{link.label}
</Button>
))}
</div>
<div className="flex h-5 items-center justify-center max-sm:hidden">
<Separator orientation="vertical" className="bg-border/50" />
</div>
<div className="flex shrink-0 items-center gap-1 max-sm:hidden">
<Button
variant="secondary"
size="lg"
className="h-10 rounded-l-full rounded-r-lg px-4"
render={<Link href="#sign-up" role="link" />}
nativeButton={false}
>
Sign up
</Button>
<Button
size="lg"
className="h-10 rounded-l-lg rounded-r-full px-4"
render={<Link href="#sign-in" role="link" />}
nativeButton={false}
>
Sign in
</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 w-full flex-col gap-2 rounded-3xl bg-popover p-2 shadow-elevated-sm sm:hidden"
>
<div className="flex flex-col gap-1">
{NAV_LINKS.map((link) => (
<Button
key={link.href}
variant="ghost"
className="h-11 w-full justify-start rounded-2xl px-4 text-muted-foreground hover:bg-transparent hover:text-foreground"
onClick={() => setIsOpen(false)}
render={<Link href={link.href} role="link" />}
nativeButton={false}
>
{link.label}
</Button>
))}
</div>
<Separator className="bg-border/50" />
<div className="grid grid-cols-2 gap-1">
<Button
variant="secondary"
size="lg"
className="h-11 w-full min-w-0 rounded-l-2xl rounded-r-lg"
onClick={() => setIsOpen(false)}
render={<Link href="#sign-up" role="link" />}
nativeButton={false}
>
Sign up
</Button>
<Button
size="lg"
className="h-11 w-full min-w-0 rounded-l-lg rounded-r-2xl"
onClick={() => setIsOpen(false)}
render={<Link href="#sign-in" role="link" />}
nativeButton={false}
>
Sign in
</Button>
</div>
</motion.div>
)}
</AnimatePresence>
</nav>
</header>
)
}

























