{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "primitives-effects-image-zoom",
  "title": "Image Zoom",
  "description": "An effect that allows you to zoom in on an image on hover.",
  "dependencies": [
    "motion"
  ],
  "files": [
    {
      "path": "registry/primitives/effects/image-zoom/index.tsx",
      "content": "'use client';\n\nimport * as React from 'react';\nimport { motion, type Transition } from 'motion/react';\n\ntype ImageZoomProps = {\n  zoomScale?: number;\n  transition?: Transition;\n  style?: React.CSSProperties;\n  zoomOnClick?: boolean;\n  zoomOnHover?: boolean;\n  disabled?: boolean;\n  width?: React.CSSProperties['width'];\n  height?: React.CSSProperties['height'];\n  children: React.ReactElement;\n} & React.ComponentProps<'div'>;\n\nexport function ImageZoom({\n  children,\n  zoomScale = 3,\n  transition = { type: 'spring', stiffness: 200, damping: 28 },\n  style,\n  zoomOnClick = true,\n  zoomOnHover = true,\n  disabled = false,\n  width = '100%',\n  height = '100%',\n  ...props\n}: ImageZoomProps) {\n  const [isZoomed, setIsZoomed] = React.useState(false);\n  const containerRef = React.useRef<HTMLDivElement | null>(null);\n  const isTouch =\n    typeof window !== 'undefined' && matchMedia('(pointer: coarse)').matches;\n\n  const setOriginFromEvent = React.useCallback(\n    (e: React.MouseEvent | React.TouchEvent) => {\n      if (!containerRef.current) return;\n      const rect = containerRef.current.getBoundingClientRect();\n      let clientX = 0;\n      let clientY = 0;\n\n      if ('touches' in e && e.touches[0]) {\n        clientX = e.touches[0].clientX;\n        clientY = e.touches[0].clientY;\n      } else if ('clientX' in e) {\n        clientX = (e as React.MouseEvent).clientX;\n        clientY = (e as React.MouseEvent).clientY;\n      }\n\n      const x = Math.max(0, Math.min(rect.width, clientX - rect.left));\n      const y = Math.max(0, Math.min(rect.height, clientY - rect.top));\n      const child = containerRef.current\n        .firstElementChild as HTMLElement | null;\n      if (!child) return;\n      child.style.transformOrigin = `${x}px ${y}px`;\n    },\n    [],\n  );\n\n  const handleMouseEnter = React.useCallback(() => {\n    if (disabled || isTouch || !zoomOnHover) return;\n    setIsZoomed(true);\n  }, [disabled, isTouch, zoomOnHover]);\n\n  const handleMouseLeave = React.useCallback(() => {\n    if (disabled || isTouch || !zoomOnHover) return;\n    setIsZoomed(false);\n  }, [disabled, isTouch, zoomOnHover]);\n\n  const handleMouseMove = React.useCallback(\n    (e: React.MouseEvent) => {\n      if (disabled || isTouch) return;\n      setOriginFromEvent(e);\n    },\n    [disabled, isTouch, setOriginFromEvent],\n  );\n\n  const handleClick = React.useCallback(\n    (e: React.MouseEvent) => {\n      if (disabled || !zoomOnClick) return;\n      setOriginFromEvent(e);\n      setIsZoomed((v) => !v);\n    },\n    [disabled, zoomOnClick, setOriginFromEvent],\n  );\n\n  const handleTouchStart = React.useCallback(\n    (e: React.TouchEvent) => {\n      if (disabled) return;\n      setOriginFromEvent(e);\n      if (zoomOnClick) setIsZoomed((v) => !v);\n      else setIsZoomed(true);\n    },\n    [disabled, zoomOnClick, setOriginFromEvent],\n  );\n\n  const handleTouchMove = React.useCallback(\n    (e: React.TouchEvent) => {\n      if (disabled) return;\n      setOriginFromEvent(e);\n    },\n    [disabled, setOriginFromEvent],\n  );\n\n  const handleTouchEnd = React.useCallback(() => {\n    if (disabled) return;\n    if (!zoomOnClick) setIsZoomed(false);\n  }, [disabled, zoomOnClick]);\n\n  return (\n    <div\n      ref={containerRef}\n      style={{\n        overflow: 'hidden',\n        position: 'relative',\n        width,\n        height,\n        touchAction: 'manipulation',\n        cursor: disabled ? 'default' : isZoomed ? 'zoom-out' : 'zoom-in',\n        ...style,\n      }}\n      onMouseEnter={handleMouseEnter}\n      onMouseLeave={handleMouseLeave}\n      onMouseMove={handleMouseMove}\n      onClick={handleClick}\n      onTouchStart={handleTouchStart}\n      onTouchMove={handleTouchMove}\n      onTouchEnd={handleTouchEnd}\n      role=\"img\"\n      {...props}\n    >\n      <motion.div\n        animate={{ scale: disabled ? 1 : isZoomed ? zoomScale : 1 }}\n        transition={transition}\n        style={{\n          width: '100%',\n          height: '100%',\n          display: 'flex',\n          alignItems: 'center',\n          justifyContent: 'center',\n          willChange: 'transform',\n        }}\n      >\n        {children}\n      </motion.div>\n    </div>\n  );\n}\n\ntype ImageProps<T extends React.ElementType = 'img'> = {\n  objectFit?: React.CSSProperties['objectFit'];\n  as?: T;\n} & React.ComponentProps<T>;\n\nexport function Image<T extends React.ElementType = 'img'>({\n  objectFit = 'cover',\n  as: Component = 'img',\n  ...props\n}: ImageProps<T>) {\n  return (\n    <Component\n      draggable={false}\n      style={{\n        width: '100%',\n        height: '100%',\n        objectFit,\n        userSelect: 'none',\n        pointerEvents: 'none',\n      }}\n      {...props}\n    />\n  );\n}\n",
      "type": "registry:ui",
      "target": "components/animate-ui/primitives/effects/image-zoom.tsx"
    }
  ],
  "type": "registry:ui"
}