{ "$schema": "https://ui.shadcn.com/schema/registry-item.json", "name": "BounceCards-TS-TW", "title": "BounceCards", "description": "Cards bounce that bounce in on mount.", "type": "registry:component", "files": [ { "type": "registry:component", "path": "BounceCards/BounceCards.tsx", "content": "import { useEffect, useRef } from 'react';\nimport { gsap } from 'gsap';\n\ninterface BounceCardsProps {\n className?: string;\n images?: string[];\n containerWidth?: number;\n containerHeight?: number;\n animationDelay?: number;\n animationStagger?: number;\n easeType?: string;\n transformStyles?: string[];\n enableHover?: boolean;\n}\n\nexport default function BounceCards({\n className = '',\n images = [],\n containerWidth = 400,\n containerHeight = 400,\n animationDelay = 0.5,\n animationStagger = 0.06,\n easeType = 'elastic.out(1, 0.8)',\n transformStyles = [\n 'rotate(10deg) translate(-170px)',\n 'rotate(5deg) translate(-85px)',\n 'rotate(-3deg)',\n 'rotate(-10deg) translate(85px)',\n 'rotate(2deg) translate(170px)'\n ],\n enableHover = false\n}: BounceCardsProps) {\n const containerRef = useRef(null);\n useEffect(() => {\n const ctx = gsap.context(() => {\n gsap.fromTo(\n '.card',\n { scale: 0 },\n {\n scale: 1,\n stagger: animationStagger,\n ease: easeType,\n delay: animationDelay\n }\n );\n }, containerRef);\n return () => ctx.revert();\n }, [animationDelay, animationStagger, easeType]);\n\n const getNoRotationTransform = (transformStr: string): string => {\n const hasRotate = /rotate\\([\\s\\S]*?\\)/.test(transformStr);\n if (hasRotate) {\n return transformStr.replace(/rotate\\([\\s\\S]*?\\)/, 'rotate(0deg)');\n } else if (transformStr === 'none') {\n return 'rotate(0deg)';\n } else {\n return `${transformStr} rotate(0deg)`;\n }\n };\n\n const getPushedTransform = (baseTransform: string, offsetX: number): string => {\n const translateRegex = /translate\\(([-0-9.]+)px\\)/;\n const match = baseTransform.match(translateRegex);\n if (match) {\n const currentX = parseFloat(match[1]);\n const newX = currentX + offsetX;\n return baseTransform.replace(translateRegex, `translate(${newX}px)`);\n } else {\n return baseTransform === 'none' ? `translate(${offsetX}px)` : `${baseTransform} translate(${offsetX}px)`;\n }\n };\n\n const pushSiblings = (hoveredIdx: number) => {\n const q = gsap.utils.selector(containerRef);\n if (!enableHover || !containerRef.current) return;\n\n images.forEach((_, i) => {\n const selector = q(`.card-${i}`);\n gsap.killTweensOf(selector);\n\n const baseTransform = transformStyles[i] || 'none';\n\n if (i === hoveredIdx) {\n const noRotation = getNoRotationTransform(baseTransform);\n gsap.to(selector, {\n transform: noRotation,\n duration: 0.4,\n ease: 'back.out(1.4)',\n overwrite: 'auto'\n });\n } else {\n const offsetX = i < hoveredIdx ? -160 : 160;\n const pushedTransform = getPushedTransform(baseTransform, offsetX);\n\n const distance = Math.abs(hoveredIdx - i);\n const delay = distance * 0.05;\n\n gsap.to(selector, {\n transform: pushedTransform,\n duration: 0.4,\n ease: 'back.out(1.4)',\n delay,\n overwrite: 'auto'\n });\n }\n });\n };\n\n const resetSiblings = () => {\n if (!enableHover || !containerRef.current) return;\n const q = gsap.utils.selector(containerRef);\n\n images.forEach((_, i) => {\n const selector = q(`.card-${i}`);\n gsap.killTweensOf(selector);\n\n const baseTransform = transformStyles[i] || 'none';\n gsap.to(selector, {\n transform: baseTransform,\n duration: 0.4,\n ease: 'back.out(1.4)',\n overwrite: 'auto'\n });\n });\n };\n\n return (\n \n {images.map((src, idx) => (\n pushSiblings(idx)}\n onMouseLeave={resetSiblings}\n >\n {`card-${idx}`}\n \n ))}\n \n );\n}\n" } ], "registryDependencies": [], "dependencies": [ "gsap@^3.13.0" ] }