reactbits-mcp-server
Version:
MCP Server for React Bits - Access 99+ React components with animations, backgrounds, and UI elements
165 lines (149 loc) • 6 kB
JSX
import { useEffect, useState } from "react";
import {
motion,
useMotionValue,
useAnimation,
useTransform,
} from "framer-motion";
const IMGS = [
"https://images.unsplash.com/photo-1528181304800-259b08848526?q=80&w=3870&auto=format&fit=crop&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D",
"https://images.unsplash.com/photo-1506665531195-3566af2b4dfa?q=80&w=3870&auto=format&fit=crop&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D",
"https://images.unsplash.com/photo-1520250497591-112f2f40a3f4?q=80&w=3456&auto=format&fit=crop&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D",
"https://images.unsplash.com/photo-1495103033382-fe343886b671?q=80&w=3870&auto=format&fit=crop&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D",
"https://images.unsplash.com/photo-1506781961370-37a89d6b3095?q=80&w=3264&auto=format&fit=crop&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D",
"https://images.unsplash.com/photo-1599576838688-8a6c11263108?q=80&w=3870&auto=format&fit=crop&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D",
"https://images.unsplash.com/photo-1494094892896-7f14a4433b7a?q=80&w=3870&auto=format&fit=crop&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D",
"https://plus.unsplash.com/premium_photo-1664910706524-e783eed89e71?q=80&w=3869&auto=format&fit=crop&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D",
"https://images.unsplash.com/photo-1503788311183-fa3bf9c4bc32?q=80&w=3870&auto=format&fit=crop&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D",
"https://images.unsplash.com/photo-1585970480901-90d6bb2a48b5?q=80&w=3774&auto=format&fit=crop&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D",
];
const RollingGallery = ({
autoplay = false,
pauseOnHover = false,
images = [],
}) => {
images = images.length > 0 ? images : IMGS;
const [isScreenSizeSm, setIsScreenSizeSm] = useState(
window.innerWidth <= 640
);
useEffect(() => {
const handleResize = () => setIsScreenSizeSm(window.innerWidth <= 640);
window.addEventListener("resize", handleResize);
return () => window.removeEventListener("resize", handleResize);
}, []);
const cylinderWidth = isScreenSizeSm ? 1100 : 1800;
const faceCount = images.length;
const faceWidth = (cylinderWidth / faceCount) * 1.5;
const radius = cylinderWidth / (2 * Math.PI);
const dragFactor = 0.05;
const rotation = useMotionValue(0);
const controls = useAnimation();
const transform = useTransform(
rotation,
(val) => `rotate3d(0,1,0,${val}deg)`
);
const startInfiniteSpin = (startAngle) => {
controls.start({
rotateY: [startAngle, startAngle - 360],
transition: {
duration: 20,
ease: "linear",
repeat: Infinity,
},
});
};
useEffect(() => {
if (autoplay) {
const currentAngle = rotation.get();
startInfiniteSpin(currentAngle);
} else {
controls.stop();
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [autoplay]);
const handleUpdate = (latest) => {
if (typeof latest.rotateY === "number") {
rotation.set(latest.rotateY);
}
};
const handleDrag = (_, info) => {
controls.stop();
rotation.set(rotation.get() + info.offset.x * dragFactor);
};
const handleDragEnd = (_, info) => {
const finalAngle = rotation.get() + info.velocity.x * dragFactor;
rotation.set(finalAngle);
if (autoplay) {
startInfiniteSpin(finalAngle);
}
};
const handleMouseEnter = () => {
if (autoplay && pauseOnHover) {
controls.stop();
}
};
const handleMouseLeave = () => {
if (autoplay && pauseOnHover) {
const currentAngle = rotation.get();
startInfiniteSpin(currentAngle);
}
};
return (
<div className="relative h-[500px] w-full overflow-hidden">
<div
className="absolute top-0 left-0 h-full w-[48px] z-10"
style={{
background:
"linear-gradient(to left, rgba(0,0,0,0) 0%, #060010 100%)",
}}
/>
<div
className="absolute top-0 right-0 h-full w-[48px] z-10"
style={{
background:
"linear-gradient(to right, rgba(0,0,0,0) 0%, #060010 100%)",
}}
/>
<div className="flex h-full items-center justify-center [perspective:1000px] [transform-style:preserve-3d]">
<motion.div
drag="x"
dragElastic={0}
onDrag={handleDrag}
onDragEnd={handleDragEnd}
onMouseEnter={handleMouseEnter}
onMouseLeave={handleMouseLeave}
animate={controls}
onUpdate={handleUpdate}
style={{
transform: transform,
rotateY: rotation,
width: cylinderWidth,
transformStyle: "preserve-3d",
}}
className="flex min-h-[200px] cursor-grab items-center justify-center [transform-style:preserve-3d]"
>
{images.map((url, i) => (
<div
key={i}
className="group absolute flex h-fit items-center justify-center p-[8%] [backface-visibility:hidden] md:p-[6%]"
style={{
width: `${faceWidth}px`,
transform: `rotateY(${(360 / faceCount) * i
}deg) translateZ(${radius}px)`,
}}
>
<img
src={url}
alt="gallery"
className="pointer-events-none h-[120px] w-[300px] rounded-[15px] border-[3px] border-white object-cover
transition-transform duration-300 ease-out group-hover:scale-105
sm:h-[100px] sm:w-[220px]"
/>
</div>
))}
</motion.div>
</div>
</div>
);
};
export default RollingGallery;