{ "$schema": "https://ui.shadcn.com/schema/registry-item.json", "name": "pixel-image", "title": "Pixel Image", "description": "A hover-driven pixelated dissolve between two images.", "dependencies": [ "motion" ], "files": [ { "path": "registry/new-york/components/pixel-image/pixel-image.tsx", "type": "registry:component", "content": "\"use client\";\n\nimport { useCallback, useEffect, useMemo, useRef, useState } from \"react\";\nimport { motion } from \"motion/react\";\nimport { cn } from \"@/lib/utils\";\n\nconst OVERLAP = 1;\n\nfunction shuffledIndices(n: number, seed: number): number[] {\n let s = seed || 1;\n const arr = Array.from({ length: n }, (_, i) => i);\n for (let i = arr.length - 1; i > 0; i--) {\n s = (s * 16807) % 2147483647;\n const j = s % (i + 1);\n [arr[i], arr[j]] = [arr[j], arr[i]];\n }\n return arr;\n}\n\nexport interface PixelImageProps {\n defaultImage: string;\n hoverImage: string;\n cols?: number;\n rows?: number;\n waveDuration?: number;\n tileDuration?: number;\n className?: string;\n}\n\nexport default function PixelImage({\n defaultImage,\n hoverImage,\n cols = 10,\n rows = 10,\n waveDuration = 0.3,\n tileDuration = 0.15,\n className,\n}: PixelImageProps) {\n const tileCount = cols * rows;\n const [seed, setSeed] = useState(1);\n const [isHovered, setIsHovered] = useState(false);\n const [size, setSize] = useState({ width: 0, height: 0 });\n const [mounted, setMounted] = useState(false);\n const containerRef = useRef(null);\n\n const tiles = useMemo(\n () =>\n Array.from({ length: tileCount }, (_, i) => ({\n id: i,\n row: Math.floor(i / cols),\n col: i % cols,\n })),\n [tileCount, cols],\n );\n\n useEffect(() => {\n const el = containerRef.current;\n if (!el) return;\n\n const observer = new ResizeObserver((entries) => {\n const { width, height } = entries[0].contentRect;\n setSize({ width, height });\n setMounted(true);\n });\n observer.observe(el);\n return () => observer.disconnect();\n }, []);\n\n const order = shuffledIndices(tileCount, seed);\n const step = waveDuration / tileCount;\n\n const handleEnter = useCallback(() => {\n setSeed((s) => s + 1);\n setIsHovered(true);\n }, []);\n\n const handleLeave = useCallback(() => {\n setSeed((s) => s + 1);\n setIsHovered(false);\n }, []);\n\n const tileWidth = size.width / cols;\n const tileHeight = size.height / rows;\n\n return (\n
\n \n \n\n \n {tiles.map((tile, i) => {\n const x = tile.col * tileWidth;\n const y = tile.row * tileHeight;\n const fx = Math.floor(x);\n const fy = Math.floor(y);\n const w = Math.ceil(tileWidth) + OVERLAP;\n const h = Math.ceil(tileHeight) + OVERLAP;\n\n return (\n \n );\n })}\n
\n \n \n );\n}\n" } ], "type": "registry:component" }