{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "primitive-ripple",
  "type": "registry:ui",
  "title": "Ripple",
  "description": "Expand a restrained ripple from the input coordinate for precise touch feedback.",
  "dependencies": [
    "motion"
  ],
  "categories": [
    "primitive",
    "feedback"
  ],
  "docs": "https://motion-lexicon.pages.dev/en/primitives/ripple/",
  "files": [
    {
      "path": "src/registry/primitives/ripple.tsx",
      "type": "registry:ui",
      "target": "components/motion-lexicon/primitives/ripple.tsx",
      "content": "\"use client\";\n\nimport { AnimatePresence, motion, useReducedMotion } from \"motion/react\";\nimport { useState, type ReactNode } from \"react\";\n\ntype Ripple = { id: number; x: number; y: number };\n\nexport type RipplePrimitiveProps = {\n  children: ReactNode;\n  onPress?: () => void;\n  duration?: number;\n  size?: number;\n  opacity?: number;\n  className?: string;\n};\n\nexport function RipplePrimitive({\n  children,\n  onPress,\n  duration = 0.16,\n  size = 2.2,\n  opacity = 0.24,\n  className,\n}: RipplePrimitiveProps) {\n  const reduceMotion = useReducedMotion();\n  const [ripples, setRipples] = useState<Ripple[]>([]);\n\n  return (\n    <motion.button\n      type=\"button\"\n      className={`relative isolate overflow-hidden ${className ?? \"\"}`}\n      whileTap={reduceMotion ? { opacity: 0.78 } : { transform: \"scale(0.97)\" }}\n      transition={{ duration: 0.12, ease: [0.16, 1, 0.3, 1] }}\n      onPointerDown={(event) => {\n        if (reduceMotion) return;\n        const rect = event.currentTarget.getBoundingClientRect();\n        setRipples((current) => [...current, {\n          id: event.timeStamp,\n          x: event.clientX - rect.left,\n          y: event.clientY - rect.top,\n        }]);\n      }}\n      onClick={onPress}\n    >\n      <AnimatePresence>\n        {ripples.map((ripple) => (\n          <motion.span\n            aria-hidden\n            className=\"pointer-events-none absolute rounded-full bg-current\"\n            key={ripple.id}\n            style={{ left: ripple.x, top: ripple.y, width: \"100%\", aspectRatio: 1 }}\n            initial={{ opacity, transform: \"translate(-50%, -50%) scale(0.08)\" }}\n            animate={{ opacity: 0, transform: `translate(-50%, -50%) scale(${size})` }}\n            exit={{ opacity: 0 }}\n            transition={{ duration, ease: [0.23, 1, 0.32, 1] }}\n            onAnimationComplete={() => setRipples((current) => current.filter((item) => item.id !== ripple.id))}\n          />\n        ))}\n      </AnimatePresence>\n      <span className=\"relative\">{children}</span>\n    </motion.button>\n  );\n}\n"
    }
  ]
}
