{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "inputs-magnetic",
  "title": "Magnetic inputs",
  "description": "Styled inputs: the Magnetic set. 5 decorative inputs variations (TiltInput, GlowTrailInput, ExpandInput, ShakeInput, SpotlightInput) 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/inputs-magnetic.tsx",
      "content": "\"use client\"\n\nimport * as React from \"react\"\nimport {\n  motion,\n  useMotionTemplate,\n  useMotionValue,\n  useReducedMotion,\n  useSpring,\n} from \"motion/react\"\n\nimport { cn } from \"@/lib/utils\"\n\n/* Magnetic input family: 5 text inputs reacting to pointer and focus physics. Position values are held with useMotionValue plus useSpring (no re-renders). Colour comes ONLY from tokens, and every pointer and motion effect is off under reduced-motion. Each wraps a real <input> and passes every native prop through. */\n\nexport type StyledSize = \"sm\" | \"md\" | \"lg\" | \"xl\"\n\nconst height: Record<StyledSize, string> = {\n  sm: \"h-8 text-sm\",\n  md: \"h-9 text-sm\",\n  lg: \"h-10 text-base\",\n  xl: \"h-12 text-base\",\n}\n\nconst fieldBase =\n  \"w-full bg-transparent text-foreground outline-none placeholder:text-muted-foreground disabled:cursor-not-allowed disabled:opacity-50\"\n\nconst wrapBase =\n  \"relative inline-flex w-56 max-w-full items-center rounded-lg border border-field-border px-3 transition-colors focus-within:border-ring focus-within:ring-[3px] focus-within:ring-ring/50\"\n\ntype Props = Omit<React.ComponentProps<\"input\">, \"size\"> & { size?: StyledSize }\n\n/* Tilt: alan, imlece dogru hafifce 3B egilir. */\nexport function TiltInput({ className, size = \"md\", ...props }: Props) {\n  const ref = React.useRef<HTMLSpanElement>(null)\n  const reduce = useReducedMotion()\n  const rx = useSpring(useMotionValue(0), { stiffness: 300, damping: 20 })\n  const ry = useSpring(useMotionValue(0), { stiffness: 300, damping: 20 })\n\n  const onMove = (e: React.PointerEvent<HTMLSpanElement>) => {\n    if (reduce || !ref.current) return\n    const r = ref.current.getBoundingClientRect()\n    ry.set(((e.clientX - (r.left + r.width / 2)) / r.width) * 12)\n    rx.set(((r.top + r.height / 2 - e.clientY) / r.height) * 12)\n  }\n  const reset = () => {\n    rx.set(0)\n    ry.set(0)\n  }\n  return (\n    <motion.span\n      ref={ref}\n      data-slot=\"styled-input\"\n      onPointerMove={onMove}\n      onPointerLeave={reset}\n      style={reduce ? undefined : { rotateX: rx, rotateY: ry, transformPerspective: 600 }}\n      className={cn(wrapBase, height[size], className)}\n    >\n      <input {...props} className={cn(fieldBase, \"h-full\")} />\n    </motion.span>\n  )\n}\n\n/* GlowTrail: a token glow blob follows the cursor horizontally on focus and hover. */\nexport function GlowTrailInput({ className, size = \"md\", ...props }: Props) {\n  const ref = React.useRef<HTMLSpanElement>(null)\n  const reduce = useReducedMotion()\n  const x = useSpring(useMotionValue(0), { stiffness: 250, damping: 25 })\n  const left = useMotionTemplate`${x}px`\n\n  const onMove = (e: React.PointerEvent<HTMLSpanElement>) => {\n    if (reduce || !ref.current) return\n    const r = ref.current.getBoundingClientRect()\n    x.set(e.clientX - r.left)\n  }\n  return (\n    <span\n      ref={ref}\n      data-slot=\"styled-input\"\n      onPointerMove={onMove}\n      className={cn(wrapBase, \"group overflow-hidden\", height[size], className)}\n    >\n      {reduce ? null : (\n        <motion.span\n          aria-hidden=\"true\"\n          style={{ left, x: \"-50%\" }}\n          className=\"pointer-events-none absolute top-1/2 h-24 w-24 -translate-y-1/2 rounded-full bg-info/25 opacity-0 blur-2xl transition-opacity duration-(--motion-base) group-focus-within:opacity-100\"\n        />\n      )}\n      <input {...props} className={cn(fieldBase, \"relative h-full\")} />\n    </span>\n  )\n}\n\n/* Expand: odakta yayli olarak biraz genisler. */\nexport function ExpandInput({ className, size = \"md\", ...props }: Props) {\n  const reduce = useReducedMotion()\n  const [focused, setFocused] = React.useState(false)\n  return (\n    <motion.span\n      data-slot=\"styled-input\"\n      animate={reduce ? undefined : { width: focused ? 256 : 224 }}\n      transition={{ type: \"spring\", stiffness: 300, damping: 26 }}\n      className={cn(wrapBase, \"w-56\", height[size], className)}\n    >\n      <input\n        {...props}\n        onFocus={(e) => {\n          setFocused(true)\n          props.onFocus?.(e)\n        }}\n        onBlur={(e) => {\n          setFocused(false)\n          props.onBlur?.(e)\n        }}\n        className={cn(fieldBase, \"h-full\")}\n      />\n    </motion.span>\n  )\n}\n\n/* Shake: invalid prop true iken kisa bir titreme oynatir (danger kenarlik). */\nexport function ShakeInput({\n  className,\n  size = \"md\",\n  invalid = false,\n  ...props\n}: Props & { invalid?: boolean }) {\n  const reduce = useReducedMotion()\n  return (\n    <motion.span\n      data-slot=\"styled-input\"\n      data-tone={invalid ? \"danger\" : \"neutral\"}\n      animate={reduce ? undefined : invalid ? { x: [0, -5, 5, -4, 4, 0] } : { x: 0 }}\n      transition={{ duration: 0.4, ease: \"easeInOut\" }}\n      className={cn(\n        wrapBase,\n        invalid && \"border-danger focus-within:border-danger focus-within:ring-danger/25\",\n        height[size],\n        className\n      )}\n    >\n      <input\n        {...props}\n        aria-invalid={invalid ? \"true\" : undefined}\n        className={cn(fieldBase, \"h-full\")}\n      />\n    </motion.span>\n  )\n}\n\n/* Spotlight: alan boyunca imleci takip eden radyal token isik. */\nexport function SpotlightInput({ className, size = \"md\", ...props }: Props) {\n  const ref = React.useRef<HTMLSpanElement>(null)\n  const reduce = useReducedMotion()\n  const x = useMotionValue(0)\n  const y = useMotionValue(0)\n  const bg = useMotionTemplate`radial-gradient(120px circle at ${x}px ${y}px, color-mix(in oklab, var(--color-info) 22%, transparent), transparent 70%)`\n\n  const onMove = (e: React.PointerEvent<HTMLSpanElement>) => {\n    if (reduce || !ref.current) return\n    const r = ref.current.getBoundingClientRect()\n    x.set(e.clientX - r.left)\n    y.set(e.clientY - r.top)\n  }\n  return (\n    <span\n      ref={ref}\n      data-slot=\"styled-input\"\n      onPointerMove={onMove}\n      className={cn(wrapBase, \"group overflow-hidden\", height[size], className)}\n    >\n      {reduce ? null : (\n        <motion.span\n          aria-hidden=\"true\"\n          style={{ background: bg }}\n          className=\"pointer-events-none absolute inset-0 opacity-0 transition-opacity duration-(--motion-base) group-hover:opacity-100 group-focus-within:opacity-100\"\n        />\n      )}\n      <input {...props} className={cn(fieldBase, \"relative h-full\")} />\n    </span>\n  )\n}\n",
      "type": "registry:component",
      "target": "components/ui/inputs-magnetic.tsx"
    }
  ],
  "categories": [
    "styled",
    "inputs"
  ],
  "type": "registry:component"
}