{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "buttons-magnetic",
  "title": "Magnetic buttons",
  "description": "Styled buttons: the Magnetic set. 5 decorative buttons variations (MagneticButton, RepelButton, TiltPointerButton, ElasticButton, SquishButton) on the ai2 token system, powered by framer-motion, reduced-motion aware and sized sm to xl. Part of the free styled layer.",
  "dependencies": [
    "motion@^12.42.2"
  ],
  "registryDependencies": [
    "@ai2/tokens"
  ],
  "files": [
    {
      "path": "registry/ai2/styled/buttons-magnetic.tsx",
      "content": "\"use client\"\n\nimport * as React from \"react\"\nimport {\n  motion,\n  useMotionValue,\n  useReducedMotion,\n  useSpring,\n  type HTMLMotionProps,\n} from \"motion/react\"\n\nimport { cn } from \"@/lib/utils\"\n\n/* Magnetic button family: 5 variations on cursor physics. Position values are held with useMotionValue plus useSpring (no re-renders). All are sized, take colour ONLY from tokens, and every pointer effect is disabled under reduced-motion. */\n\nexport type StyledSize = \"sm\" | \"md\" | \"lg\" | \"xl\"\n\nconst sizes: Record<StyledSize, string> = {\n  sm: \"h-8 gap-1.5 rounded-md px-3 text-sm\",\n  md: \"h-9 gap-2 rounded-lg px-4 text-sm\",\n  lg: \"h-10 gap-2 rounded-lg px-5 text-sm\",\n  xl: \"h-12 gap-2.5 rounded-xl px-6 text-base\",\n}\n\nconst base =\n  \"relative inline-flex shrink-0 select-none items-center justify-center font-medium whitespace-nowrap outline-none focus-visible:ring-[3px] focus-visible:ring-ring/50 disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0 [&_i]:text-base [&_i]:leading-none\"\n\ntype Props = HTMLMotionProps<\"button\"> & { size?: StyledSize }\n\n/* Magnetic: buton imlece dogru yayli kayar. */\nexport function MagneticButton({ className, size = \"md\", children, ...props }: Props) {\n  const ref = React.useRef<HTMLButtonElement>(null)\n  const reduce = useReducedMotion()\n  const x = useMotionValue(0)\n  const y = useMotionValue(0)\n  const springX = useSpring(x, { stiffness: 300, damping: 20 })\n  const springY = useSpring(y, { stiffness: 300, damping: 20 })\n\n  const onMove = (e: React.PointerEvent<HTMLButtonElement>) => {\n    if (reduce || !ref.current) return\n    const r = ref.current.getBoundingClientRect()\n    x.set((e.clientX - (r.left + r.width / 2)) * 0.3)\n    y.set((e.clientY - (r.top + r.height / 2)) * 0.5)\n  }\n  const reset = () => {\n    x.set(0)\n    y.set(0)\n  }\n\n  return (\n    <motion.button\n      ref={ref}\n      data-slot=\"styled-button\"\n      onPointerMove={onMove}\n      onPointerLeave={reset}\n      style={reduce ? undefined : { x: springX, y: springY }}\n      whileTap={reduce ? undefined : { scale: 0.95 }}\n      className={cn(base, sizes[size], \"bg-primary text-primary-foreground\", className)}\n      {...props}\n    >\n      {children}\n    </motion.button>\n  )\n}\n\n/* Repel: buton imlecten yayli sekilde KACAR. */\nexport function RepelButton({ className, size = \"md\", children, ...props }: Props) {\n  const ref = React.useRef<HTMLButtonElement>(null)\n  const reduce = useReducedMotion()\n  const x = useMotionValue(0)\n  const y = useMotionValue(0)\n  const springX = useSpring(x, { stiffness: 250, damping: 18 })\n  const springY = useSpring(y, { stiffness: 250, damping: 18 })\n\n  const onMove = (e: React.PointerEvent<HTMLButtonElement>) => {\n    if (reduce || !ref.current) return\n    const r = ref.current.getBoundingClientRect()\n    x.set((e.clientX - (r.left + r.width / 2)) * -0.4)\n    y.set((e.clientY - (r.top + r.height / 2)) * -0.6)\n  }\n  const reset = () => {\n    x.set(0)\n    y.set(0)\n  }\n\n  return (\n    <motion.button\n      ref={ref}\n      data-slot=\"styled-button\"\n      onPointerMove={onMove}\n      onPointerLeave={reset}\n      style={reduce ? undefined : { x: springX, y: springY }}\n      whileTap={reduce ? undefined : { scale: 0.95 }}\n      className={cn(base, sizes[size], \"bg-brand text-brand-foreground\", className)}\n      {...props}\n    >\n      {children}\n    </motion.button>\n  )\n}\n\n/* Tilt: the button tilts in 3D towards the cursor (rotateX/Y from the pointer). */\nexport function TiltPointerButton({ className, size = \"md\", children, ...props }: Props) {\n  const ref = React.useRef<HTMLButtonElement>(null)\n  const reduce = useReducedMotion()\n  const rx = useMotionValue(0)\n  const ry = useMotionValue(0)\n  const springRx = useSpring(rx, { stiffness: 300, damping: 20 })\n  const springRy = useSpring(ry, { stiffness: 300, damping: 20 })\n\n  const onMove = (e: React.PointerEvent<HTMLButtonElement>) => {\n    if (reduce || !ref.current) return\n    const r = ref.current.getBoundingClientRect()\n    const px = (e.clientX - r.left) / r.width - 0.5\n    const py = (e.clientY - r.top) / r.height - 0.5\n    rx.set(py * -22)\n    ry.set(px * 22)\n  }\n  const reset = () => {\n    rx.set(0)\n    ry.set(0)\n  }\n\n  return (\n    <motion.button\n      ref={ref}\n      data-slot=\"styled-button\"\n      onPointerMove={onMove}\n      onPointerLeave={reset}\n      style={reduce ? undefined : { rotateX: springRx, rotateY: springRy, transformPerspective: 500 }}\n      whileTap={reduce ? undefined : { scale: 0.96 }}\n      className={cn(base, sizes[size], \"bg-primary text-primary-foreground\", className)}\n      {...props}\n    >\n      {children}\n    </motion.button>\n  )\n}\n\n/* Elastic: overshoots and settles with a low-damping spring on hover. */\nexport function ElasticButton({ className, size = \"md\", children, ...props }: Props) {\n  const reduce = useReducedMotion()\n  return (\n    <motion.button\n      data-slot=\"styled-button\"\n      whileHover={reduce ? undefined : { scale: 1.12 }}\n      whileTap={reduce ? undefined : { scale: 0.9 }}\n      transition={{ type: \"spring\", stiffness: 500, damping: 10 }}\n      className={cn(base, sizes[size], \"bg-info text-info-foreground\", className)}\n      {...props}\n    >\n      {children}\n    </motion.button>\n  )\n}\n\n/* Squish: basinca yassilir (scaleX artar, scaleY azalir). */\nexport function SquishButton({ className, size = \"md\", children, ...props }: Props) {\n  const reduce = useReducedMotion()\n  return (\n    <motion.button\n      data-slot=\"styled-button\"\n      whileTap={reduce ? undefined : { scaleX: 1.12, scaleY: 0.82 }}\n      transition={{ type: \"spring\", stiffness: 500, damping: 15 }}\n      className={cn(base, sizes[size], \"bg-primary text-primary-foreground\", className)}\n      {...props}\n    >\n      {children}\n    </motion.button>\n  )\n}\n",
      "type": "registry:component",
      "target": "components/ui/buttons-magnetic.tsx"
    }
  ],
  "categories": [
    "styled",
    "buttons"
  ],
  "type": "registry:component"
}