{"$schema":"https://ui.shadcn.com/schema/registry-item.json","name":"animated-icon","type":"registry:component","title":"Animated Icon","description":"Hover-played icon micro-interactions in one component — draw, wiggle, spin, bounce, pop, pulse, nudge — driven by a single variant prop. Built on motion + lucide, reduced-motion safe.","author":"UI Lab","dependencies":["clsx","lucide-react","motion","tailwind-merge"],"registryDependencies":[],"files":[{"path":"components/motion/animated-icon.tsx","type":"registry:component","target":"@components/motion/animated-icon.tsx","content":"\"use client\";\n// ui-lab-ten.vercel.app/components/motion/animated-icon\n\nimport type { LucideIcon } from \"lucide-react\";\nimport { motion, useReducedMotion } from \"motion/react\";\nimport { cn } from \"@/lib/utils\";\n\nexport type AnimatedIconVariant =\n  | \"draw\"\n  | \"wiggle\"\n  | \"spin\"\n  | \"bounce\"\n  | \"pop\"\n  | \"pulse\"\n  | \"nudge\";\n\nexport interface AnimatedIconProps {\n  /** Which hover animation to play. */\n  variant: AnimatedIconVariant;\n  /** Icon size in px. */\n  size?: number;\n  className?: string;\n  /** Lucide icon component to animate. Ignored by the `draw` variant. */\n  icon?: LucideIcon;\n  /** SVG path `d` strings the `draw` variant renders. Defaults to a checkmark. */\n  paths?: string[];\n}\n\nconst DEFAULT_PATHS = [\"M5 13l4 4L19 7\"];\n\n/**\n * Seven hover-played icon micro-interactions behind a single `variant` prop:\n * `draw` self-draws its stroke (pathLength 0 → 1) — ignores `icon`, uses `paths`;\n * `wiggle` rocks side to side; `spin` rotates a full turn; `bounce` springs\n * vertically; `pop` springs its scale up and back; `pulse` loops a gentle\n * scale while hovered; `nudge` springs a small step sideways. Reduced-motion\n * renders a static icon (or a static checkmark for `draw`) with no animation.\n */\nexport function AnimatedIcon({\n  variant,\n  size = 28,\n  className,\n  icon: Icon,\n  paths = DEFAULT_PATHS,\n}: AnimatedIconProps) {\n  const reduce = useReducedMotion();\n\n  if (variant === \"draw\") {\n    if (reduce) {\n      return (\n        <svg\n          aria-hidden=\"true\"\n          viewBox=\"0 0 24 24\"\n          width={size}\n          height={size}\n          className={cn(\"text-current\", className)}\n          fill=\"none\"\n          stroke=\"currentColor\"\n          strokeWidth={2}\n          strokeLinecap=\"round\"\n          strokeLinejoin=\"round\"\n        >\n          {paths.map((d) => (\n            <path key={d} d={d} pathLength={1} />\n          ))}\n        </svg>\n      );\n    }\n\n    return (\n      <motion.svg\n        aria-hidden=\"true\"\n        viewBox=\"0 0 24 24\"\n        width={size}\n        height={size}\n        className={cn(\"text-current\", className)}\n        fill=\"none\"\n        stroke=\"currentColor\"\n        strokeWidth={2}\n        strokeLinecap=\"round\"\n        strokeLinejoin=\"round\"\n        initial=\"rest\"\n        whileHover=\"hover\"\n      >\n        {paths.map((d) => (\n          <motion.path\n            key={d}\n            d={d}\n            variants={{\n              rest: { pathLength: 1 },\n              hover: { pathLength: [0, 1] },\n            }}\n            transition={{ duration: 0.6 }}\n          />\n        ))}\n      </motion.svg>\n    );\n  }\n\n  if (!Icon) return null;\n\n  if (reduce) {\n    return <Icon size={size} className={cn(\"text-current\", className)} />;\n  }\n\n  switch (variant) {\n    case \"wiggle\":\n      return (\n        <motion.div\n          style={{ display: \"inline-flex\" }}\n          whileHover={{ rotate: [0, -14, 10, -6, 0] }}\n          transition={{ duration: 0.6 }}\n        >\n          <Icon size={size} className={cn(\"text-current\", className)} />\n        </motion.div>\n      );\n    case \"spin\":\n      return (\n        <motion.div\n          style={{ display: \"inline-flex\" }}\n          whileHover={{ rotate: 360 }}\n          transition={{ duration: 0.6, ease: \"easeInOut\" }}\n        >\n          <Icon size={size} className={cn(\"text-current\", className)} />\n        </motion.div>\n      );\n    case \"bounce\":\n      return (\n        <motion.div\n          style={{ display: \"inline-flex\" }}\n          whileHover={{ y: [0, 4, 0] }}\n          transition={{ type: \"spring\", stiffness: 400, damping: 12 }}\n        >\n          <Icon size={size} className={cn(\"text-current\", className)} />\n        </motion.div>\n      );\n    case \"pop\":\n      return (\n        <motion.div\n          style={{ display: \"inline-flex\" }}\n          whileHover={{ scale: [1, 1.25, 1] }}\n          transition={{ type: \"spring\", stiffness: 400, damping: 12 }}\n        >\n          <Icon size={size} className={cn(\"text-current\", className)} />\n        </motion.div>\n      );\n    case \"pulse\":\n      return (\n        <motion.div\n          style={{ display: \"inline-flex\" }}\n          whileHover={{ scale: [1, 1.15, 1] }}\n          transition={{ duration: 0.8, repeat: Number.POSITIVE_INFINITY }}\n        >\n          <Icon size={size} className={cn(\"text-current\", className)} />\n        </motion.div>\n      );\n    case \"nudge\":\n      return (\n        <motion.div\n          style={{ display: \"inline-flex\" }}\n          whileHover={{ x: [0, 4, 0] }}\n          transition={{ type: \"spring\", stiffness: 400, damping: 14 }}\n        >\n          <Icon size={size} className={cn(\"text-current\", className)} />\n        </motion.div>\n      );\n    default:\n      return null;\n  }\n}\n"},{"path":"lib/utils.ts","type":"registry:lib","target":"@lib/utils.ts","content":"import { clsx, type ClassValue } from \"clsx\"\nimport { twMerge } from \"tailwind-merge\"\n\nexport function cn(...inputs: ClassValue[]) {\n  return twMerge(clsx(inputs))\n}\n"}]}