bandsbender's picture
Upload 348 files
21a686e verified
raw
history blame
4.57 kB
"use client";
import { cn } from "@/lib/utils";
import React, { useRef, useState, useEffect } from "react";
import {
motion,
useMotionValue,
useSpring,
useTransform,
animate,
useVelocity,
useAnimationControls,
} from "motion/react";
export const DraggableCardBody = ({
className,
children,
}: {
className?: string;
children?: React.ReactNode;
}) => {
const mouseX = useMotionValue(0);
const mouseY = useMotionValue(0);
const cardRef = useRef<HTMLDivElement>(null);
const controls = useAnimationControls();
const [constraints, setConstraints] = useState({
top: 0,
left: 0,
right: 0,
bottom: 0,
});
// physics biatch
const velocityX = useVelocity(mouseX);
const velocityY = useVelocity(mouseY);
const springConfig = {
stiffness: 100,
damping: 20,
mass: 0.5,
};
const rotateX = useSpring(
useTransform(mouseY, [-300, 300], [25, -25]),
springConfig,
);
const rotateY = useSpring(
useTransform(mouseX, [-300, 300], [-25, 25]),
springConfig,
);
const opacity = useSpring(
useTransform(mouseX, [-300, 0, 300], [0.8, 1, 0.8]),
springConfig,
);
const glareOpacity = useSpring(
useTransform(mouseX, [-300, 0, 300], [0.2, 0, 0.2]),
springConfig,
);
useEffect(() => {
// Update constraints when component mounts or window resizes
const updateConstraints = () => {
if (typeof window !== "undefined") {
setConstraints({
top: -window.innerHeight / 2,
left: -window.innerWidth / 2,
right: window.innerWidth / 2,
bottom: window.innerHeight / 2,
});
}
};
updateConstraints();
// Add resize listener
window.addEventListener("resize", updateConstraints);
// Clean up
return () => {
window.removeEventListener("resize", updateConstraints);
};
}, []);
const handleMouseMove = (e: React.MouseEvent<HTMLDivElement>) => {
const { clientX, clientY } = e;
const { width, height, left, top } =
cardRef.current?.getBoundingClientRect() ?? {
width: 0,
height: 0,
left: 0,
top: 0,
};
const centerX = left + width / 2;
const centerY = top + height / 2;
const deltaX = clientX - centerX;
const deltaY = clientY - centerY;
mouseX.set(deltaX);
mouseY.set(deltaY);
};
const handleMouseLeave = () => {
mouseX.set(0);
mouseY.set(0);
};
return (
<motion.div
ref={cardRef}
drag
dragConstraints={constraints}
onDragStart={() => {
document.body.style.cursor = "grabbing";
}}
onDragEnd={(event, info) => {
document.body.style.cursor = "default";
controls.start({
rotateX: 0,
rotateY: 0,
transition: {
type: "spring",
...springConfig,
},
});
const currentVelocityX = velocityX.get();
const currentVelocityY = velocityY.get();
const velocityMagnitude = Math.sqrt(
currentVelocityX * currentVelocityX +
currentVelocityY * currentVelocityY,
);
const bounce = Math.min(0.8, velocityMagnitude / 1000);
animate(info.point.x, info.point.x + currentVelocityX * 0.3, {
duration: 0.8,
ease: [0.2, 0, 0, 1],
bounce,
type: "spring",
stiffness: 50,
damping: 15,
mass: 0.8,
});
animate(info.point.y, info.point.y + currentVelocityY * 0.3, {
duration: 0.8,
ease: [0.2, 0, 0, 1],
bounce,
type: "spring",
stiffness: 50,
damping: 15,
mass: 0.8,
});
}}
style={{
rotateX,
rotateY,
opacity,
willChange: "transform",
}}
animate={controls}
whileHover={{ scale: 1.02 }}
onMouseMove={handleMouseMove}
onMouseLeave={handleMouseLeave}
className={cn(
"relative min-h-96 w-80 overflow-hidden rounded-md bg-neutral-100 p-6 shadow-2xl transform-3d dark:bg-neutral-900",
className,
)}
>
{children}
<motion.div
style={{
opacity: glareOpacity,
}}
className="pointer-events-none absolute inset-0 bg-white select-none"
/>
</motion.div>
);
};
export const DraggableCardContainer = ({
className,
children,
}: {
className?: string;
children?: React.ReactNode;
}) => {
return (
<div className={cn("[perspective:3000px]", className)}>{children}</div>
);
};