{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "back-to-top",
  "type": "registry:ui",
  "title": "Back to top",
  "description": "A back to top button that appears after scrolling, with a ring that fills as the reader progresses. Smooth scroll that respects reduced motion.",
  "dependencies": [
    "lucide-react"
  ],
  "registryDependencies": [
    "utils"
  ],
  "files": [
    {
      "path": "registry/ui/back-to-top.tsx",
      "type": "registry:ui",
      "target": "components/ui/back-to-top.tsx",
      "content": "\"use client\"\nimport * as React from \"react\"\nimport { ArrowUpIcon } from \"lucide-react\"\nimport { cn } from \"@/lib/utils\"\n\n/**\n * A back to top button that appears after the reader scrolls, with a ring\n * that fills as they progress through the page. Scrolls smoothly unless\n * reduced motion is on, then moves focus to the top of the document.\n */\ntype BackToTopProps = {\n  /** Show after this many pixels of scroll. */\n  threshold?: number\n  /** Element to scroll; defaults to the window. */\n  target?: React.RefObject<HTMLElement | null>\n  className?: string\n}\n\nfunction BackToTop({ threshold = 400, target, className }: BackToTopProps) {\n  const [progress, setProgress] = React.useState(0)\n  const [visible, setVisible] = React.useState(false)\n  React.useEffect(() => {\n    const el = target?.current\n    const read = () => {\n      const top = el ? el.scrollTop : window.scrollY\n      const max = el ? el.scrollHeight - el.clientHeight : document.documentElement.scrollHeight - window.innerHeight\n      setVisible(top > threshold)\n      setProgress(max > 0 ? Math.min(1, top / max) : 0)\n    }\n    read()\n    const src: HTMLElement | Window = el ?? window\n    src.addEventListener(\"scroll\", read, { passive: true })\n    return () => src.removeEventListener(\"scroll\", read)\n  }, [threshold, target])\n  const r = 19\n  const c = 2 * Math.PI * r\n  return (\n    <button\n      type=\"button\"\n      data-slot=\"back-to-top\"\n      aria-label=\"Back to top\"\n      onClick={() => {\n        const reduce = window.matchMedia(\"(prefers-reduced-motion: reduce)\").matches\n        const el = target?.current\n        ;(el ?? window).scrollTo({ top: 0, behavior: reduce ? \"auto\" : \"smooth\" })\n        ;(document.querySelector(\"main, h1\") as HTMLElement | null)?.focus?.({ preventScroll: true })\n      }}\n      className={cn(\n        \"fixed right-5 bottom-5 z-40 grid size-11 place-items-center rounded-full border border-border bg-raised text-fg shadow-overlay outline-none transition-[opacity,transform] duration-300 focus-visible:ring-2 focus-visible:ring-accent\",\n        visible ? \"translate-y-0 opacity-100\" : \"pointer-events-none translate-y-3 opacity-0\",\n        className,\n      )}\n    >\n      <svg aria-hidden viewBox=\"0 0 44 44\" className=\"absolute inset-0 size-full -rotate-90\">\n        <circle cx=\"22\" cy=\"22\" r={r} fill=\"none\" stroke=\"var(--border)\" strokeWidth=\"2\" />\n        <circle cx=\"22\" cy=\"22\" r={r} fill=\"none\" stroke=\"var(--accent)\" strokeWidth=\"2\" strokeLinecap=\"round\" strokeDasharray={c} strokeDashoffset={c * (1 - progress)} />\n      </svg>\n      <ArrowUpIcon className=\"relative size-4\" />\n    </button>\n  )\n}\n\nexport { BackToTop }\nexport type { BackToTopProps }\n"
    }
  ]
}
