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