{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "magnetic-button",
  "type": "registry:ui",
  "title": "Magnetic",
  "description": "A pointer-aware magnetic wrapper with bounded pull strength, self or parent interaction areas, and reduced-motion support.",
  "files": [
    {
      "path": "registry/sonaui/magnetic-button/magnetic-button.tsx",
      "type": "registry:ui",
      "content": "\"use client\";\n\nimport {\n  motion,\n  type SpringOptions,\n  useMotionValue,\n  useReducedMotion,\n  useSpring,\n} from \"motion/react\";\nimport { type ReactNode, useEffect, useRef, useState } from \"react\";\nimport { cn } from \"@/lib/sona-utils\";\n\nconst SPRING_CONFIG = { stiffness: 30, damping: 6, mass: 0.6 };\n\nexport interface MagneticProps {\n  /** The content to be rendered inside the magnetic button. */\n  children: ReactNode;\n  /**\n   * The intensity of the magnetic pull effect.\n   * @default 0.6\n   */\n  magneticIntensity?: number;\n  /**\n   * The range within which the magnetic effect is active.\n   * @default 100\n   */\n  magneticRange?: number;\n  /**\n   * Defines the area of interaction for the magnetic effect.\n   * @default \"self\"\n   */\n  interactionArea?: \"self\" | \"parent\";\n  /** Configuration for the spring animation. */\n  springConfig?: SpringOptions;\n  /** Additional class names for custom styling. */\n  customClassName?: string;\n  /** Additional classes for the wrapper. Prefer this for new usage. */\n  className?: string;\n}\n\nexport default function Magnetic({\n  children,\n  magneticIntensity = 0.6,\n  magneticRange = 100,\n  interactionArea = \"self\",\n  springConfig = SPRING_CONFIG,\n  customClassName,\n  className,\n}: MagneticProps) {\n  const [isMouseHovered, setMouseHovered] = useState(false);\n  const magneticRef = useRef<HTMLDivElement>(null);\n  const hoveredRef = useRef(false);\n  const shouldReduceMotion = useReducedMotion();\n\n  const motionX = useMotionValue(0);\n  const motionY = useMotionValue(0);\n\n  const springMotionX = useSpring(motionX, springConfig);\n  const springMotionY = useSpring(motionY, springConfig);\n  const resolvedRange = Math.max(1, magneticRange);\n\n  // Only listen while hovered — no idle document-wide mousemove work.\n  useEffect(() => {\n    if (!isMouseHovered || shouldReduceMotion) return;\n\n    const calculateMouseDistance = (event: MouseEvent) => {\n      if (!hoveredRef.current) {\n        motionX.set(0);\n        motionY.set(0);\n        return;\n      }\n      if (magneticRef.current) {\n        const rect = magneticRef.current.getBoundingClientRect();\n        const centerX = rect.left + rect.width / 2;\n        const centerY = rect.top + rect.height / 2;\n        const distanceX = event.clientX - centerX;\n        const distanceY = event.clientY - centerY;\n\n        const absoluteDistance = Math.sqrt(distanceX ** 2 + distanceY ** 2);\n\n        if (absoluteDistance <= resolvedRange) {\n          const scale = 1 - absoluteDistance / resolvedRange;\n\n          motionX.set(distanceX * magneticIntensity * scale);\n          motionY.set(distanceY * magneticIntensity * scale);\n        } else {\n          motionX.set(0);\n          motionY.set(0);\n        }\n      }\n    };\n\n    document.addEventListener(\"mousemove\", calculateMouseDistance);\n\n    return () => {\n      document.removeEventListener(\"mousemove\", calculateMouseDistance);\n      motionX.set(0);\n      motionY.set(0);\n    };\n  }, [\n    isMouseHovered,\n    shouldReduceMotion,\n    magneticIntensity,\n    resolvedRange,\n    motionX,\n    motionY,\n  ]);\n\n  useEffect(() => {\n    if (shouldReduceMotion) {\n      setMouseHovered(false);\n      motionX.set(0);\n      motionY.set(0);\n    }\n  }, [motionX, motionY, shouldReduceMotion]);\n\n  useEffect(() => {\n    if (interactionArea === \"parent\" && magneticRef.current?.parentElement) {\n      const parentElement = magneticRef.current.parentElement;\n\n      const handleParentMouseEnter = () => {\n        hoveredRef.current = true;\n        setMouseHovered(true);\n      };\n      const handleParentMouseLeave = () => {\n        hoveredRef.current = false;\n        setMouseHovered(false);\n        motionX.set(0);\n        motionY.set(0);\n      };\n\n      parentElement.addEventListener(\"mouseenter\", handleParentMouseEnter);\n      parentElement.addEventListener(\"mouseleave\", handleParentMouseLeave);\n\n      return () => {\n        parentElement.removeEventListener(\"mouseenter\", handleParentMouseEnter);\n        parentElement.removeEventListener(\"mouseleave\", handleParentMouseLeave);\n      };\n    }\n  }, [interactionArea, motionX, motionY]);\n\n  const handleMouseEnter = () => {\n    if (interactionArea === \"self\") {\n      hoveredRef.current = true;\n      setMouseHovered(true);\n    }\n  };\n\n  const handleMouseLeave = () => {\n    if (interactionArea === \"self\") {\n      hoveredRef.current = false;\n      setMouseHovered(false);\n      motionX.set(0);\n      motionY.set(0);\n    }\n  };\n\n  return (\n    <motion.div\n      ref={magneticRef}\n      onMouseEnter={interactionArea === \"self\" ? handleMouseEnter : undefined}\n      onMouseLeave={interactionArea === \"self\" ? handleMouseLeave : undefined}\n      style={{\n        x: springMotionX,\n        y: springMotionY,\n      }}\n      role=\"presentation\"\n      className={cn(customClassName, className)}\n    >\n      {children}\n    </motion.div>\n  );\n}\n",
      "target": "components/ui/magnetic-button/magnetic-button.tsx"
    }
  ],
  "dependencies": [
    "motion"
  ],
  "registryDependencies": [
    "@sona-ui/sona-utils"
  ]
}