{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "scroll-progress-navbar",
  "type": "registry:ui",
  "title": "Scroll Progress Navbar",
  "description": "A header that tightens and gains a surface as you scroll, with a reading progress bar.",
  "author": "WANSTUDIO <https://wanstudio.my>",
  "dependencies": [
    "motion"
  ],
  "files": [
    {
      "path": "registry/wancraft/scroll-progress-navbar.tsx",
      "content": "\"use client\";\n\nimport * as React from \"react\";\nimport { motion, useMotionValueEvent, useReducedMotion, useScroll, useSpring } from \"motion/react\";\n\nexport interface NavbarLink {\n  label: string;\n  href: string;\n  /** Marks the current page (aria-current=\"page\"). */\n  active?: boolean;\n}\n\nexport interface ScrollProgressNavbarProps extends Omit<React.HTMLAttributes<HTMLElement>, \"children\"> {\n  /** Logo or wordmark on the left. */\n  brand?: React.ReactNode;\n  /** Primary navigation links. They collapse into a menu on narrow containers. */\n  links?: NavbarLink[];\n  /** Slot on the right, e.g. a call-to-action button. */\n  action?: React.ReactNode;\n  /** Scrollable element to track. Defaults to the window. */\n  container?: React.RefObject<HTMLElement | null>;\n  /** Scroll distance in px after which the bar turns compact. */\n  threshold?: number;\n  /** Stick to the top of the scrolling area. */\n  sticky?: boolean;\n  /** Show the reading progress bar. */\n  showProgress?: boolean;\n  /** Where the progress bar sits. */\n  progressPosition?: \"top\" | \"bottom\";\n  /** Spring-smooth the progress bar. */\n  smooth?: boolean;\n  /** Progress bar, active link and focus ring colour. */\n  accent?: string;\n  /** Accessible name of the navigation landmark. */\n  label?: string;\n}\n\n/**\n * A header that tightens and gains a surface once you scroll past a threshold, with a reading\n * progress bar. Works with the window or any scrollable container.\n */\nexport function ScrollProgressNavbar({\n  brand,\n  links = [],\n  action,\n  container,\n  threshold = 48,\n  sticky = true,\n  showProgress = true,\n  progressPosition = \"bottom\",\n  smooth = true,\n  accent = \"#FF7448\",\n  label = \"Main\",\n  className = \"\",\n  style,\n  ...props\n}: ScrollProgressNavbarProps) {\n  const reduceMotion = useReducedMotion();\n  const headerRef = React.useRef<HTMLElement>(null);\n  const progressRef = React.useRef<HTMLDivElement>(null);\n  const menuId = React.useId();\n  const [menuOpen, setMenuOpen] = React.useState(false);\n\n  const { scrollY, scrollYProgress } = useScroll({ container });\n  const smoothed = useSpring(scrollYProgress, { stiffness: 260, damping: 40, mass: 0.3, restDelta: 0.001 });\n  const progress = smooth && !reduceMotion ? smoothed : scrollYProgress;\n\n  // Compact state and progress value are written to the DOM directly — no re-render while scrolling.\n  const syncCompact = (y: number) => headerRef.current?.setAttribute(\"data-compact\", String(y > threshold));\n  const syncProgress = (p: number) => progressRef.current?.setAttribute(\"aria-valuenow\", String(Math.round(p * 100)));\n  useMotionValueEvent(scrollY, \"change\", syncCompact);\n  useMotionValueEvent(scrollYProgress, \"change\", syncProgress);\n\n  // Initial sync (e.g. the page was restored mid-scroll) and when the threshold changes.\n  React.useEffect(() => {\n    headerRef.current?.setAttribute(\"data-compact\", String(scrollY.get() > threshold));\n    progressRef.current?.setAttribute(\"aria-valuenow\", String(Math.round(scrollYProgress.get() * 100)));\n  }, [threshold, scrollY, scrollYProgress]);\n\n  const renderLinks = (orientation: \"row\" | \"column\") =>\n    links.map((link) => (\n      <li key={link.href + link.label}>\n        <a\n          href={link.href}\n          aria-current={link.active ? \"page\" : undefined}\n          onClick={() => setMenuOpen(false)}\n          className={`group/link relative flex items-center rounded-md text-sm tracking-tight text-zinc-600 transition-colors hover:text-zinc-950 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-[var(--spn-accent)] aria-[current=page]:text-zinc-950 dark:text-zinc-400 dark:hover:text-white dark:aria-[current=page]:text-white ${\n            orientation === \"row\" ? \"h-8 px-3\" : \"h-10 px-1\"\n          }`}\n        >\n          {link.label}\n          {orientation === \"row\" && (\n            <span\n              aria-hidden=\"true\"\n              className=\"absolute bottom-0.5 left-1/2 h-1 w-1 -translate-x-1/2 rounded-full bg-[var(--spn-accent)] opacity-0 transition-opacity group-aria-[current=page]/link:opacity-100\"\n            />\n          )}\n        </a>\n      </li>\n    ));\n\n  return (\n    <header\n      ref={headerRef}\n      data-compact=\"false\"\n      className={`group/nav @container z-40 w-full ${sticky ? \"sticky top-0\" : \"relative\"} ${className}`}\n      style={{ [\"--spn-accent\" as string]: accent, ...style }}\n      {...props}\n    >\n      <div className=\"relative border-b border-transparent transition-[background-color,border-color,box-shadow] duration-300 motion-reduce:transition-none group-data-[compact=true]/nav:border-zinc-900/10 group-data-[compact=true]/nav:bg-white/85 group-data-[compact=true]/nav:shadow-[0_8px_24px_-18px_rgba(0,0,0,0.35)] group-data-[compact=true]/nav:backdrop-blur-md dark:group-data-[compact=true]/nav:border-white/10 dark:group-data-[compact=true]/nav:bg-zinc-950/80\">\n        <div className=\"flex h-16 items-center gap-6 px-6 transition-[height,padding] duration-300 ease-[cubic-bezier(0.16,1,0.3,1)] motion-reduce:transition-none group-data-[compact=true]/nav:h-12 group-data-[compact=true]/nav:px-4\">\n          {brand && <div className=\"flex shrink-0 items-center\">{brand}</div>}\n\n          {links.length > 0 && (\n            <nav aria-label={label} className=\"hidden @[36rem]:block\">\n              <ul className=\"flex items-center gap-1\">{renderLinks(\"row\")}</ul>\n            </nav>\n          )}\n\n          <div className=\"ml-auto flex items-center gap-2\">\n            {action}\n            {links.length > 0 && (\n              <button\n                type=\"button\"\n                aria-expanded={menuOpen}\n                aria-controls={menuId}\n                aria-label={menuOpen ? \"Close menu\" : \"Open menu\"}\n                onClick={() => setMenuOpen((open) => !open)}\n                className=\"group/menu flex h-8 w-8 items-center justify-center rounded-md border border-zinc-900/10 text-zinc-700 transition-colors hover:text-zinc-950 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-[var(--spn-accent)] @[36rem]:hidden dark:border-white/10 dark:text-zinc-300 dark:hover:text-white\"\n              >\n                <span aria-hidden=\"true\" className=\"relative block h-2.5 w-3.5\">\n                  <span className=\"absolute left-0 top-0 h-px w-full bg-current transition-all duration-200 group-aria-expanded/menu:top-1/2 group-aria-expanded/menu:rotate-45\" />\n                  <span className=\"absolute bottom-0 left-0 h-px w-full bg-current transition-all duration-200 group-aria-expanded/menu:bottom-auto group-aria-expanded/menu:top-1/2 group-aria-expanded/menu:-rotate-45\" />\n                </span>\n              </button>\n            )}\n          </div>\n        </div>\n\n        {links.length > 0 && (\n          <nav\n            id={menuId}\n            aria-label={label}\n            hidden={!menuOpen}\n            className=\"border-t border-zinc-900/10 bg-white px-6 py-2 @[36rem]:hidden dark:border-white/10 dark:bg-zinc-950\"\n          >\n            <ul className=\"flex flex-col\">{renderLinks(\"column\")}</ul>\n          </nav>\n        )}\n\n        {showProgress && (\n          <div\n            ref={progressRef}\n            role=\"progressbar\"\n            aria-label=\"Reading progress\"\n            aria-valuemin={0}\n            aria-valuemax={100}\n            className={`pointer-events-none absolute inset-x-0 h-[2px] overflow-hidden ${\n              progressPosition === \"top\" ? \"top-0\" : \"-bottom-px\"\n            }`}\n          >\n            <motion.div className=\"h-full w-full origin-left bg-[var(--spn-accent)]\" style={{ scaleX: progress }} />\n          </div>\n        )}\n      </div>\n    </header>\n  );\n}\n",
      "type": "registry:ui"
    }
  ],
  "categories": [
    "navigation"
  ],
  "docs": "Documentation: https://wancraft.my/components/scroll-progress-navbar"
}
