{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "back-top",
  "title": "Back Top",
  "type": "registry:ui",
  "files": [
    {
      "path": "packages/registry-react/components/back-top/BackTop.tsx",
      "content": "import * as React from 'react'\nimport { ArrowUp } from 'lucide-react'\nimport { cn } from '@/lib/utils'\nimport { backTopVariants, type BackTopVariants } from './back-top.variants'\n\nexport interface BackTopProps extends Omit<React.ButtonHTMLAttributes<HTMLButtonElement>, 'content'> {\n  /** Visibility threshold in pixels. Button appears once scroll passes it. */\n  threshold?: number\n  /** Target container. Defaults to the window. Pass a CSS selector or an HTMLElement. */\n  target?: string | HTMLElement | Window\n  /** Scroll behavior: 'smooth' or 'auto' (instant). */\n  behavior?: ScrollBehavior\n  /** Size variant. */\n  size?: BackTopVariants['size']\n  /** Edge anchor position. */\n  position?: BackTopVariants['position']\n  /** Distance from the viewport edge (px). */\n  offset?: number\n  /** Use absolute positioning (for section-level containers) instead of fixed (viewport). */\n  absolute?: boolean\n  /** Accessible label. */\n  ariaLabel?: string\n  /** Override the default arrow icon. */\n  icon?: React.ReactNode\n  /** Fired whenever visibility toggles. */\n  onVisible?: (visible: boolean) => void\n}\n\ntype ScrollTarget = HTMLElement | Window | null\n\nconst BackTop = React.forwardRef<HTMLButtonElement, BackTopProps>(\n  (\n    {\n      className,\n      threshold = 200,\n      target,\n      behavior = 'smooth',\n      size = 'default',\n      position = 'bottom-right',\n      offset = 24,\n      absolute = false,\n      ariaLabel = 'Scroll to top',\n      icon,\n      onVisible,\n      onClick,\n      style,\n      ...props\n    },\n    ref,\n  ) => {\n    const [visible, setVisible] = React.useState(false)\n    // Keep the button mounted during the exit animation, then unmount.\n    const [mounted, setMounted] = React.useState(false)\n    const [dataState, setDataState] = React.useState<'open' | 'closed'>('closed')\n    const currentTargetRef = React.useRef<ScrollTarget>(null)\n    const onVisibleRef = React.useRef(onVisible)\n    onVisibleRef.current = onVisible\n\n    function resolveTarget(): ScrollTarget {\n      if (typeof window === 'undefined') return null\n      if (target === undefined || target === null) return window\n      if (typeof target === 'string') {\n        const el = document.querySelector<HTMLElement>(target)\n        return el ?? window\n      }\n      return target\n    }\n\n    function getScrollTop(el: HTMLElement | Window): number {\n      if (el === window) {\n        return window.scrollY ?? document.documentElement.scrollTop ?? document.body.scrollTop ?? 0\n      }\n      return (el as HTMLElement).scrollTop\n    }\n\n    function scrollToTop(el: HTMLElement | Window) {\n      if (el === window) {\n        window.scrollTo({ top: 0, behavior })\n      } else {\n        ;(el as HTMLElement).scrollTo({ top: 0, behavior })\n      }\n    }\n\n    const handleScroll = React.useCallback(() => {\n      const current = currentTargetRef.current\n      if (!current) return\n      const scrollTop = getScrollTop(current)\n      const next = scrollTop > threshold\n      setVisible((prev) => {\n        if (next !== prev) {\n          onVisibleRef.current?.(next)\n          return next\n        }\n        return prev\n      })\n    }, [threshold])\n\n    const handleClick = React.useCallback(\n      (e: React.MouseEvent<HTMLButtonElement>) => {\n        onClick?.(e)\n        const current = currentTargetRef.current\n        if (current) scrollToTop(current)\n      },\n      [onClick, behavior],\n    )\n\n    // Mount/unmount with exit animation, mirroring the Vue <Transition name=\"back-top\">.\n    React.useEffect(() => {\n      if (visible) {\n        setMounted(true)\n        setDataState('open')\n        return\n      }\n      if (mounted) {\n        setDataState('closed')\n        const t = window.setTimeout(() => setMounted(false), 200)\n        return () => window.clearTimeout(t)\n      }\n      // eslint-disable-next-line react-hooks/exhaustive-deps\n    }, [visible])\n\n    // Attach scroll listeners; re-resolve when the target prop changes.\n    React.useEffect(() => {\n      currentTargetRef.current = resolveTarget()\n      const current = currentTargetRef.current\n      if (!current) return\n      current.addEventListener('scroll', handleScroll, { passive: true })\n      // If target is a container, also listen to window scroll for safety on resize.\n      if (current !== window) {\n        window.addEventListener('scroll', handleScroll, { passive: true })\n      }\n      handleScroll()\n      return () => {\n        current.removeEventListener('scroll', handleScroll)\n        if (current !== window) {\n          window.removeEventListener('scroll', handleScroll)\n        }\n      }\n      // eslint-disable-next-line react-hooks/exhaustive-deps\n    }, [target, handleScroll])\n\n    const positionStyle = React.useMemo<React.CSSProperties>(() => {\n      const o = `${offset}px`\n      switch (position) {\n        case 'bottom-left':\n          return { left: o, bottom: o }\n        case 'top-right':\n          return { right: o, top: o }\n        case 'top-left':\n          return { left: o, top: o }\n        default:\n          return { right: o, bottom: o }\n      }\n    }, [position, offset])\n\n    if (!mounted) return null\n\n    return (\n      <button\n        ref={ref}\n        data-uipkge=\"\"\n        data-slot=\"back-top\"\n        data-state={dataState}\n        data-size={size ?? undefined}\n        data-position={position ?? undefined}\n        type=\"button\"\n        aria-label={ariaLabel}\n        className={cn(backTopVariants({ size, position }), absolute ? 'absolute' : 'fixed', className)}\n        style={{ ...positionStyle, ...style }}\n        onClick={handleClick}\n        {...props}\n      >\n        {icon ?? <ArrowUp aria-hidden=\"true\" />}\n      </button>\n    )\n  },\n)\nBackTop.displayName = 'BackTop'\n\nexport { BackTop }\n",
      "type": "registry:ui",
      "target": "~/components/ui/back-top/BackTop.tsx"
    },
    {
      "path": "packages/registry-react/components/back-top/back-top.variants.ts",
      "content": "import type { VariantProps } from 'class-variance-authority'\nimport { cva } from 'class-variance-authority'\n\n/**\n * Variant definitions live in their own file (rather than the package\n * `index.ts`) so `BackTop.tsx` can `import { backTopVariants } from\n * './back-top.variants'` without creating a circular dependency through the\n * index. See card.variants.ts for the same pattern.\n */\nexport const backTopVariants = cva(\n  'inline-flex items-center justify-center rounded-full border bg-background text-foreground shadow-lg transition-all duration-200 hover:bg-accent hover:text-accent-foreground focus-visible:ring-ring/50 focus-visible:ring-[3px] outline-none cursor-pointer motion-safe:data-[state=open]:animate-in motion-safe:data-[state=open]:fade-in-0 motion-safe:data-[state=open]:zoom-in-95 motion-safe:data-[state=closed]:animate-out motion-safe:data-[state=closed]:fade-out-0 motion-safe:data-[state=closed]:zoom-out-95 [&_svg:not([class*=size-])]:size-5 [&_svg]:pointer-events-none [&_svg]:shrink-0',\n  {\n    variants: {\n      size: {\n        sm: 'size-8 [&_svg:not([class*=size-])]:size-4',\n        default: 'size-10',\n        lg: 'size-12 [&_svg:not([class*=size-])]:size-6',\n      },\n      position: {\n        'bottom-right': '',\n        'bottom-left': '',\n        'top-right': '',\n        'top-left': '',\n      },\n    },\n    defaultVariants: {\n      size: 'default',\n      position: 'bottom-right',\n    },\n  },\n)\n\nexport type BackTopVariants = VariantProps<typeof backTopVariants>\n",
      "type": "registry:ui",
      "target": "~/components/ui/back-top/back-top.variants.ts"
    },
    {
      "path": "packages/registry-react/components/back-top/index.ts",
      "content": "export { BackTop, type BackTopProps } from './BackTop'\nexport { backTopVariants, type BackTopVariants } from './back-top.variants'\n",
      "type": "registry:ui",
      "target": "~/components/ui/back-top/index.ts"
    }
  ],
  "dependencies": [
    "class-variance-authority",
    "lucide-react"
  ],
  "devDependencies": [],
  "registryDependencies": [],
  "description": "Scroll-to-top floating button. Appears after the target container scrolls past a threshold and smooth-scrolls back on click. Supports custom target container, visibility threshold, scroll behavior, custom icon slot, four edge anchors, size variants, and edge offset.",
  "categories": [
    "navigation",
    "utility"
  ]
}