{
  "title": "Fillable Button",
  "description": "A button that fills with color on hover.",
  "name": "motion-fillable-button",
  "dependencies": [
    "motion",
    "class-variance-authority"
  ],
  "registryDependencies": [],
  "type": "registry:ui",
  "files": [
    {
      "path": "components/paceui/motion/fillable-button.tsx",
      "type": "registry:ui",
      "content": "import * as React from \"react\";\n\nimport { type VariantProps, cva } from \"class-variance-authority\";\nimport { motion, useAnimation } from \"motion/react\";\n\nimport { cn } from \"@/lib/utils\";\n\nconst overlayVariants = cva(\n    \"pointer-events-none absolute left-0 top-0 block aspect-square w-[170%] -translate-x-1/2 -translate-y-1/2 rounded-full\",\n    {\n        variants: {\n            variant: {\n                default: \"bg-white/40 dark:bg-black/20\",\n                outline: \"bg-zinc-300 dark:bg-zinc-600\",\n                destructive: \"bg-destructive\",\n                secondary: \"bg-secondary-foreground\",\n                ghost: \"bg-muted\",\n                link: \"\",\n            },\n        },\n        defaultVariants: {\n            variant: \"default\",\n        },\n    },\n);\n\nconst buttonVariants = cva(\n    \"cursor-pointer inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-all disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4 shrink-0 [&_svg]:shrink-0 outline-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive transition-colors\",\n    {\n        variants: {\n            variant: {\n                default:\n                    \"relative bg-primary text-primary-foreground dark:hover:text-black overflow-hidden rounded-full! px-6 py-3.5 tracking-tight after:absolute after:inset-0 after:rounded-full after:pointer-events-none after:content-[''] \",\n                outline:\n                    \"relative overflow-hidden rounded-full bg-transparent text-foreground px-6 py-3.5 tracking-tight after:absolute after:inset-0 after:rounded-full after:border-2 after:border-zinc-300 dark:after:border-zinc-600 after:pointer-events-none after:content-[''] \",\n                destructive:\n                    \"relative bg-destructive/10 dark:bg-destructive/20 text-destructive hover:text-background overflow-hidden rounded-full! px-6 py-3.5 tracking-tight\",\n                secondary:\n                    \"relative bg-secondary text-secondary-foreground hover:text-background overflow-hidden rounded-full! px-6 py-3.5 tracking-tight\",\n                ghost: \"relative bg-transparent text-foreground hover:text-accent-foreground overflow-hidden rounded-full! px-6 py-3.5 tracking-tight\",\n                link: \"relative text-primary\",\n            },\n            size: {\n                default: \"h-9 px-4 py-2 has-[>svg]:px-3\",\n                xs: \"h-6 gap-1 rounded-md px-2 text-xs has-[>svg]:px-1.5 [&_svg:not([class*='size-'])]:size-3\",\n                sm: \"h-8 rounded-md gap-1.5 px-3 has-[>svg]:px-2.5\",\n                lg: \"h-10 rounded-md px-6 has-[>svg]:px-4\",\n                icon: \"size-9 p-0\",\n                \"icon-xs\": \"size-6 rounded-md p-0 [&_svg:not([class*='size-'])]:size-3\",\n                \"icon-sm\": \"size-8 p-0\",\n                \"icon-lg\": \"size-10 p-0\",\n            },\n        },\n        defaultVariants: {\n            variant: \"default\",\n            size: \"default\",\n        },\n    },\n);\n\nfunction FillableButton({\n    className,\n    variant = \"default\",\n    size = \"default\",\n    children,\n    overlayClassname,\n    ...props\n}: React.ComponentProps<\"button\"> &\n    VariantProps<typeof buttonVariants> & {\n        overlayClassname?: string;\n    }) {\n    const controls = useAnimation();\n    const underlineControls = useAnimation();\n\n    const handleMouseEnter = (e: React.MouseEvent<HTMLButtonElement>) => {\n        const rect = e.currentTarget.getBoundingClientRect();\n        const x = Math.max(0, Math.min(100, ((e.clientX - rect.left) / rect.width) * 100));\n        const y = Math.max(0, Math.min(100, ((e.clientY - rect.top) / rect.height) * 100));\n\n        if (variant === \"link\") {\n            underlineControls.set({ transformOrigin: `${x}% center`, scaleX: 0 });\n            underlineControls.start({ scaleX: 1, transition: { duration: 0.3, ease: \"easeOut\" } });\n        } else {\n            controls.set({ x: `${x}%`, y: `${y}%` });\n            controls.start({ scale: 1, transition: { duration: 0.4, ease: \"easeOut\" } });\n        }\n    };\n\n    const handleMouseLeave = (e: React.MouseEvent<HTMLButtonElement>) => {\n        const rect = e.currentTarget.getBoundingClientRect();\n        const x = Math.max(0, Math.min(100, ((e.clientX - rect.left) / rect.width) * 100));\n        const y = Math.max(0, Math.min(100, ((e.clientY - rect.top) / rect.height) * 100));\n\n        if (variant === \"link\") {\n            underlineControls.set({ transformOrigin: `${x}% center` });\n            underlineControls.start({ scaleX: 0, transition: { duration: 0.25, ease: \"easeIn\" } });\n        } else {\n            const targetX = x > 90 ? x + 20 : x < 10 ? x - 20 : x;\n            const targetY = y > 90 ? y + 20 : y < 10 ? y - 20 : y;\n\n            controls.start({\n                x: `${targetX}%`,\n                y: `${targetY}%`,\n                scale: 0,\n                transition: { duration: 0.3, ease: \"easeOut\" },\n            });\n        }\n    };\n\n    const handleMouseMove = (e: React.MouseEvent<HTMLButtonElement>) => {\n        if (variant === \"link\") return;\n        const rect = e.currentTarget.getBoundingClientRect();\n        const x = Math.max(0, Math.min(100, ((e.clientX - rect.left) / rect.width) * 100));\n        const y = Math.max(0, Math.min(100, ((e.clientY - rect.top) / rect.height) * 100));\n\n        controls.start({\n            x: `${x}%`,\n            y: `${y}%`,\n            transition: { duration: 0.4, ease: \"easeOut\" },\n        });\n    };\n\n    return (\n        <button\n            data-slot=\"button\"\n            data-variant={variant}\n            data-size={size}\n            className={cn(buttonVariants({ variant, size, className }))}\n            onMouseEnter={handleMouseEnter}\n            onMouseLeave={handleMouseLeave}\n            onMouseMove={handleMouseMove}\n            {...props}>\n            {variant !== \"link\" && (\n                <motion.span\n                    animate={controls}\n                    initial={{ scale: 0 }}\n                    className=\"pointer-events-none absolute inset-0 origin-top-left will-change-transform\">\n                    <span className={cn(overlayVariants({ variant }), overlayClassname)} />\n                </motion.span>\n            )}\n            <span className=\"relative z-10 flex items-center gap-2\">\n                {children}\n                {variant === \"link\" && (\n                    <motion.span\n                        animate={underlineControls}\n                        initial={{ scaleX: 0 }}\n                        className=\"absolute bottom-0 left-0 h-[1.5px] w-full bg-current\"\n                    />\n                )}\n            </span>\n        </button>\n    );\n}\n\nexport { FillableButton, buttonVariants };\n"
    }
  ],
  "mcp": {
    "description": "Provides refined UI interactions and accessible controls. A button that fills with color on hover. Relies on motion. Optimal for building reusable interface elements and user input flows.",
    "tags": [
      "component",
      "motion",
      "animation"
    ]
  }
}