{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "fill-button",
  "title": "Fill Button",
  "description": "Hover/focus button with a GSAP clip-path fill sweep. Configurable direction, duration and ease.",
  "dependencies": [
    "gsap"
  ],
  "registryDependencies": [
    "https://trytweenly.vercel.app/r/use-reduced-motion.json"
  ],
  "files": [
    {
      "path": "registry/new-york/fill-button/fill-button.tsx",
      "content": "\"use client\"\n\nimport { useEffect, useRef } from \"react\"\nimport gsap from \"gsap\"\nimport { useReducedMotion } from \"@/registry/new-york/hooks/use-reduced-motion\"\n\nexport type FillDirection = \"up\" | \"down\" | \"left\" | \"right\"\n\nexport interface FillButtonProps\n  extends React.ButtonHTMLAttributes<HTMLButtonElement> {\n  /** Direction the fill sweeps toward. Default: \"up\" */\n  direction?: FillDirection\n  /** Sweep length in seconds. Default: 0.45 */\n  duration?: number\n  /** Any GSAP ease string, e.g. \"power3.out\", \"elastic.out(1, 0.6)\". Default: \"power3.out\" */\n  ease?: string\n}\n\n// clip-path insets are: top right bottom left\nconst HIDDEN: Record<FillDirection, string> = {\n  up: \"inset(100% 0% 0% 0%)\", // rises from the bottom edge\n  down: \"inset(0% 0% 100% 0%)\", // drops from the top edge\n  left: \"inset(0% 0% 0% 100%)\", // sweeps from the right edge\n  right: \"inset(0% 100% 0% 0%)\", // sweeps from the left edge\n}\nconst SHOWN = \"inset(0% 0% 0% 0%)\"\n\nexport function FillButton({\n  children,\n  direction = \"up\",\n  duration = 0.45,\n  ease = \"power3.out\",\n  className = \"\",\n  type = \"button\",\n  onMouseEnter,\n  onMouseLeave,\n  onFocus,\n  onBlur,\n  ...props\n}: FillButtonProps) {\n  const fillRef = useRef<HTMLSpanElement>(null)\n  const reduced = useReducedMotion()\n\n  // Kill any running sweep when the button unmounts\n  useEffect(() => {\n    const fill = fillRef.current\n    return () => {\n      gsap.killTweensOf(fill)\n    }\n  }, [])\n\n  const sweep = (clipPath: string) => {\n    gsap.to(fillRef.current, {\n      clipPath,\n      duration: reduced ? 0 : duration,\n      ease,\n      overwrite: true,\n    })\n  }\n\n  return (\n    <button\n      {...props}\n      type={type}\n      className={`relative inline-flex items-center justify-center overflow-hidden rounded-full border border-foreground px-6 py-3 text-sm font-medium text-foreground focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-foreground disabled:pointer-events-none disabled:opacity-50 ${className}`}\n      onMouseEnter={(e) => {\n        sweep(SHOWN)\n        onMouseEnter?.(e)\n      }}\n      onMouseLeave={(e) => {\n        sweep(HIDDEN[direction])\n        onMouseLeave?.(e)\n      }}\n      onFocus={(e) => {\n        sweep(SHOWN)\n        onFocus?.(e)\n      }}\n      onBlur={(e) => {\n        sweep(HIDDEN[direction])\n        onBlur?.(e)\n      }}\n    >\n      <span>{children}</span>\n\n      {/* Inverted copy, revealed by the clip-path animation */}\n      <span\n        ref={fillRef}\n        aria-hidden=\"true\"\n        className=\"absolute inset-0 flex items-center justify-center bg-foreground text-background\"\n        style={{ clipPath: HIDDEN[direction] }}\n      >\n        {children}\n      </span>\n    </button>\n  )\n}",
      "type": "registry:component"
    }
  ],
  "docs": "Docs, live playground and every prop: https://trytweenly.vercel.app/docs/components/fill-button",
  "type": "registry:component"
}