{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "floating-navbar",
  "title": "Floating Navbar",
  "description": "A header that hides on scroll-down and reappears on scroll-up, giving long reading surfaces the vertical space back without losing the nav.",
  "registryDependencies": [
    "utils"
  ],
  "files": [
    {
      "path": "src/registry/ui/floating-navbar.tsx",
      "content": "\"use client\"\n\nimport * as React from \"react\"\nimport { cn } from \"@/lib/utils\"\n\nexport interface FloatingNavbarProps extends React.HTMLAttributes<HTMLElement> {\n  /** The consumer's own nav content — logo, links, actions. This component is a shell only. */\n  children: React.ReactNode\n  /** Scrollable container to track; omit to track the window. */\n  target?: React.RefObject<HTMLElement | null>\n  /** Top-of-page exemption band in px — while scroll position is within it, the bar always stays visible. */\n  threshold?: number\n}\n\nfunction getScrollTop(target?: React.RefObject<HTMLElement | null>) {\n  const el = target?.current\n  return el ? el.scrollTop : window.scrollY\n}\n\n/**\n * A sticky header that hides on scroll-down and reappears on scroll-up, so long\n * reading surfaces (articles, docs, feeds) get the vertical space back without\n * losing the nav entirely. Direction is derived from a ref-held previous\n * scrollTop, not React state, so the high-frequency scroll math never re-renders.\n */\nexport const FloatingNavbar = React.forwardRef<HTMLElement, FloatingNavbarProps>(\n  ({ children, target, threshold = 80, className, ...props }, ref) => {\n    const [hidden, setHidden] = React.useState(false)\n    const lastY = React.useRef(0)\n\n    // Passive scroll listener, throttled to one direction check per frame via rAF.\n    // setHidden only ever runs inside the rAF callback, never synchronously in the effect body.\n    React.useEffect(() => {\n      lastY.current = getScrollTop(target)\n      let raf = 0\n\n      const tick = () => {\n        raf = 0\n        const y = getScrollTop(target)\n\n        if (y <= threshold) {\n          // Top-of-page exemption: never hide inside the band, no direction math needed.\n          setHidden(false)\n          lastY.current = y\n          return\n        }\n\n        const delta = y - lastY.current\n        // Direction jitter guard — sub-8px wobble (trackpads, momentum scroll) is\n        // ignored and keeps accumulating against the same reference point.\n        if (Math.abs(delta) < 8) return\n        setHidden(delta > 0)\n        lastY.current = y\n      }\n\n      const onScroll = () => {\n        if (raf) return\n        raf = requestAnimationFrame(tick)\n      }\n\n      // Container + window, same lesson as scroll-to-top: a target container may\n      // not be scrollable yet at mount time, so both sources stay wired.\n      const node = target?.current ?? null\n      const sources: EventTarget[] = node ? [node, window] : [window]\n      for (const s of sources) s.addEventListener(\"scroll\", onScroll, { passive: true })\n      return () => {\n        for (const s of sources) s.removeEventListener(\"scroll\", onScroll)\n        if (raf) cancelAnimationFrame(raf)\n      }\n    }, [target, threshold])\n\n    return (\n      <header\n        className={cn(\n          \"fixed inset-x-0 top-0 z-40 border-b bg-background/80 backdrop-blur transition-transform duration-300 motion-reduce:transition-none\",\n          // Focus safety: a keyboard user tabbing into the nav must never have it\n          // slide away underneath their focus — focus-within always wins over hidden.\n          \"focus-within:translate-y-0\",\n          hidden && \"-translate-y-full\",\n          className,\n        )}\n        ref={ref}\n        {...props}\n      >\n        {children}\n      </header>\n    )\n  },\n)\n\nFloatingNavbar.displayName = \"FloatingNavbar\"\n\nexport default FloatingNavbar\n",
      "type": "registry:ui"
    }
  ],
  "type": "registry:ui"
}