shadimage
diarmuradi
blockheader-5source: ok · refreshed

Header 5

npx shadcn add "https://diarmuradi.com/r/header-5.json"
free

Where it goes

navbar

Tags auto-generated by rules on 9/24/2026 — empty means no evidence, not lack of support.

Dependencies

Bundle weight · medium
iconslucide-react
animationmotion

Registry dependencies

https://www.diarmuradi.com/r/styles/base-nova/button.jsondesktop-navhttps://www.diarmuradi.com/r/styles/base-nova/fancy-button.jsonhttps://www.diarmuradi.com/r/styles/base-nova/icons.jsonmobile-navhttps://www.diarmuradi.com/r/styles/base-nova/navigation-menu.jsonhttps://www.diarmuradi.com/r/styles/base-nova/separator.json

Files1 · source cached 10/8/2026, 4:44:22 AM

header.tsx
"use client"

import { useEffect, useId, useRef, useState } from "react"
import Link from "next/link"
import { cn } from "cn"
import {
  animate,
  AnimatePresence,
  motion,
  useMotionValue,
  useReducedMotion,
  useTransform,
} from "motion/react"
import useMeasure from "react-use-measure"

import { Button } from "@/components/ui/button"
import { Icons } from "@/components/ui/icons"

const links = [
  { label: "Home", href: "#" },
  { label: "About", href: "#about" },
  { label: "Blog", href: "#blog" },
  { label: "Contact", href: "#contact" },
]

export function SiteHeader() {
  const [isOpen, setIsOpen] = useState(false)
  const [measureRef, bounds] = useMeasure({ offsetSize: true })
  const navRef = useRef<HTMLElement>(null)
  const toggleRef = useRef<HTMLButtonElement>(null)
  const menuId = useId()
  const reducedMotion = useReducedMotion()
  const width = useMotionValue(192)
  const viewBox = useTransform(width, (value) => `0 0 ${value} 96`)
  const fill = useTransform(
    width,
    (value) =>
      `M0 0 H${value} V40 H${value - 8} C${value - 36} 40 ${value - 36} 72 ${value - 64} 72 H64 C36 72 36 40 8 40 H0 Z`
  )
  const stroke = useTransform(
    width,
    (value) =>
      `M0 39.5 H8 C36 39.5 36 71.5 64 71.5 H${value - 64} C${value - 36} 71.5 ${value - 36} 39.5 ${value - 8} 39.5 H${value}`
  )

  useEffect(() => {
    const controls = animate(
      width,
      isOpen ? Math.max(192, bounds.width) : 192,
      reducedMotion
        ? { duration: 0 }
        : {
            type: "spring",
            stiffness: 340,
            damping: 30,
            delay: isOpen ? 0 : 0.2,
          }
    )
    return () => controls.stop()
  }, [bounds.width, isOpen, reducedMotion, width])

  useEffect(() => {
    if (!isOpen) return
    const dismiss = (event: PointerEvent | KeyboardEvent) => {
      if (event instanceof KeyboardEvent) {
        if (event.key !== "Escape") return
        event.preventDefault()
        setIsOpen(false)
        toggleRef.current?.focus()
      } else if (
        event.target instanceof Node &&
        !navRef.current?.contains(event.target)
      )
        setIsOpen(false)
    }
    document.addEventListener("pointerdown", dismiss)
    document.addEventListener("keydown", dismiss)
    return () => {
      document.removeEventListener("pointerdown", dismiss)
      document.removeEventListener("keydown", dismiss)
    }
  }, [isOpen])

  return (
    <header
      data-slot="header"
      className="pointer-events-none fixed inset-x-0 top-0 z-50 flex h-24 justify-center"
    >
      <div
        aria-hidden="true"
        className="absolute inset-x-0 top-0 h-10 border-b border-border/50 bg-card"
      />
      <div
        ref={measureRef}
        className="relative flex w-[calc(100%-2rem)] max-w-xl justify-center"
      >
        <motion.nav
          ref={navRef}
          style={{ width }}
          aria-label="Main navigation"
          className="pointer-events-auto relative h-24 overflow-hidden"
          onPointerEnter={(event) => {
            if (event.pointerType === "mouse") setIsOpen(true)
          }}
          onPointerLeave={() => {
            if (!navRef.current?.contains(document.activeElement))
              setIsOpen(false)
          }}
          onBlur={(event) => {
            if (!event.currentTarget.contains(event.relatedTarget))
              setIsOpen(false)
          }}
        >
          <motion.svg
            aria-hidden="true"
            style={{ width }}
            height="96"
            viewBox={viewBox}
            className="pointer-events-none absolute inset-0 overflow-visible"
          >
            <motion.path d={fill} className="fill-card" />
            <motion.path
              d={stroke}
              fill="none"
              className="stroke-border/50"
              vectorEffect="non-scaling-stroke"
            />
          </motion.svg>
          <Button
            ref={toggleRef}
            type="button"
            aria-label={isOpen ? "Close navigation" : "Open navigation"}
            aria-expanded={isOpen}
            aria-controls={isOpen ? menuId : undefined}
            onClick={() => setIsOpen(!isOpen)}
            variant="ghost"
            size="icon-lg"
            className="absolute top-3.5 left-1/2 z-20 -translate-x-1/2"
          >
            <Icons.Logo aria-hidden="true" className="size-7" />
          </Button>
          <AnimatePresence initial={false}>
            {isOpen && (
              <motion.div
                data-slot="navigation-menu-popup"
                id={menuId}
                initial="closed"
                animate="open"
                exit="closed"
                variants={{
                  open: {
                    transition: {
                      delayChildren: reducedMotion ? 0 : 0.12,
                      staggerChildren: reducedMotion ? 0 : 0.025,
                    },
                  },
                  closed: {
                    transition: {
                      staggerChildren: reducedMotion ? 0 : 0.015,
                      staggerDirection: -1,
                    },
                  },
                }}
                style={{ width: Math.max(192, bounds.width) - 32 }}
                className="absolute top-3.5 left-1/2 grid h-10 -translate-x-1/2 grid-cols-[1fr_2.5rem_1fr] gap-2 sm:gap-6"
              >
                {[links.slice(0, 2), links.slice(2)].map((group, index) => (
                  <div
                    key={group[0].href}
                    className={cn(
                      "flex items-center gap-1 sm:gap-6",
                      index === 0 ? "col-start-1 justify-end" : "col-start-3"
                    )}
                  >
                    {group.map((link) => (
                      <motion.div
                        key={link.href}
                        variants={{
                          open: { opacity: 1, y: 0 },
                          closed: { opacity: 0, y: reducedMotion ? 0 : -10 },
                        }}
                        transition={{
                          duration: reducedMotion ? 0 : 0.16,
                          ease: "easeOut",
                        }}
                      >
                        <Button
                          onClick={() => setIsOpen(false)}
                          variant="ghost"
                          className="sm:text-sm"
                          render={<Link href={link.href} role="link" />}
                          nativeButton={false}
                        >
                          {link.label}
                        </Button>
                      </motion.div>
                    ))}
                  </div>
                ))}
              </motion.div>
            )}
          </AnimatePresence>
        </motion.nav>
      </div>
    </header>
  )
}

More from diarmuradi

Browse all components in this library →