{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "magnetic-button",
  "type": "registry:ui",
  "title": "Magnetic Button",
  "description": "A button gently pulled toward the pointer inside a bounded magnetic field.",
  "author": "WANSTUDIO <https://wanstudio.my>",
  "dependencies": [
    "motion"
  ],
  "files": [
    {
      "path": "registry/wancraft/magnetic-button.tsx",
      "content": "\"use client\";\n\nimport * as React from \"react\";\nimport { motion, useMotionValue, useReducedMotion, useSpring, useTransform } from \"motion/react\";\n\nexport interface MagneticButtonProps\n  extends Omit<\n    React.ComponentPropsWithoutRef<\"button\">,\n    \"onDrag\" | \"onDragStart\" | \"onDragEnd\" | \"onAnimationStart\" | \"onAnimationEnd\"\n  > {\n  /** How strongly the button follows the pointer, 0–100. */\n  strength?: number;\n  /** Extra distance (px) around the button where magnetism is active. */\n  radius?: number;\n  /** Spring used to settle the button. */\n  spring?: { stiffness: number; damping: number; mass: number };\n  /** Accent colour (any CSS colour). */\n  accent?: string;\n  /** Corner radius in px. */\n  rounded?: number;\n  variant?: \"solid\" | \"outline\";\n}\n\n/**\n * A button that is gently pulled toward the pointer within a bounded magnetic field.\n * Magnetism is disabled for touch input and when the user prefers reduced motion.\n */\nexport const MagneticButton = React.forwardRef<HTMLButtonElement, MagneticButtonProps>(function MagneticButton(\n  {\n    strength = 40,\n    radius = 48,\n    spring = { stiffness: 180, damping: 14, mass: 0.6 },\n    accent = \"#FF7448\",\n    rounded = 999,\n    variant = \"solid\",\n    className = \"\",\n    style,\n    disabled,\n    children,\n    ...props\n  },\n  ref,\n) {\n  const reduceMotion = useReducedMotion();\n  const innerRef = React.useRef<HTMLButtonElement>(null);\n  React.useImperativeHandle(ref, () => innerRef.current as HTMLButtonElement);\n\n  const x = useMotionValue(0);\n  const y = useMotionValue(0);\n  const sx = useSpring(x, spring);\n  const sy = useSpring(y, spring);\n  // The label travels slightly further than the shell, giving a sense of depth.\n  const lx = useTransform(sx, (v) => v * 0.35);\n  const ly = useTransform(sy, (v) => v * 0.35);\n\n  const active = !disabled && !reduceMotion && strength > 0;\n\n  const handlePointerMove = (event: React.PointerEvent<HTMLSpanElement>) => {\n    if (!active || event.pointerType !== \"mouse\" || !innerRef.current) return;\n    const rect = innerRef.current.getBoundingClientRect();\n    const dx = event.clientX - (rect.left + rect.width / 2);\n    const dy = event.clientY - (rect.top + rect.height / 2);\n    const factor = Math.min(Math.max(strength, 0), 100) / 100;\n    x.set(dx * factor * 0.6);\n    y.set(dy * factor * 0.6);\n  };\n\n  const reset = () => {\n    x.set(0);\n    y.set(0);\n  };\n\n  const variantClass =\n    variant === \"solid\"\n      ? \"bg-[var(--mb-accent)] text-zinc-950 hover:brightness-110\"\n      : \"border border-zinc-900/20 bg-transparent text-zinc-900 hover:border-[var(--mb-accent)] dark:border-white/20 dark:text-zinc-100\";\n\n  return (\n    <span\n      // The invisible field extends the hit area without shifting layout.\n      className=\"inline-block\"\n      style={{ padding: radius, margin: -radius }}\n      onPointerMove={handlePointerMove}\n      onPointerLeave={reset}\n    >\n      <motion.button\n        ref={innerRef}\n        type=\"button\"\n        disabled={disabled}\n        onBlur={reset}\n        className={`relative inline-flex h-12 select-none items-center justify-center gap-2 px-7 text-sm font-medium tracking-tight transition-[filter,border-color,opacity] duration-200 focus-visible:outline-2 focus-visible:outline-offset-4 focus-visible:outline-[var(--mb-accent)] disabled:cursor-not-allowed disabled:opacity-50 ${variantClass} ${className}`}\n        style={{ x: sx, y: sy, borderRadius: rounded, [\"--mb-accent\" as string]: accent, ...style }}\n        {...props}\n      >\n        <motion.span className=\"inline-flex items-center gap-2\" style={{ x: lx, y: ly }}>\n          {children}\n        </motion.span>\n      </motion.button>\n    </span>\n  );\n});\n",
      "type": "registry:ui"
    }
  ],
  "categories": [
    "buttons"
  ],
  "docs": "Documentation: https://wancraft.my/components/magnetic-button"
}
