lightswind
Version:
A collection of beautifully crafted React Components, Blocks & Templates for Modern Developers. Create stunning web applications effortlessly by using our 160+ professional and animated react components.
103 lines (102 loc) • 5.16 kB
JavaScript
// @ts-nocheck
"use client";
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
import { useRef, useEffect, useState } from "react";
import { motion, useMotionValue, animate } from "framer-motion";
import { cn } from "../../lib/utils";
import Image from "next/image";
const cardVariants = {
offHover: (angle) => ({
rotateY: angle,
z: 60, // Ensure card is in front of container plane (which blocks -Z events)
opacity: 0.9,
scale: 1,
zIndex: 30, // Higher than potential overlays
transition: {
type: "spring",
mass: 3,
stiffness: 400,
damping: 50
}
}),
onHover: (hoverScale) => ({
rotateY: 0,
z: 120, // Pop out further
opacity: 1,
scale: hoverScale,
zIndex: 50,
transition: {
type: "spring",
mass: 3,
stiffness: 400,
damping: 50
}
})
};
const AngledCard = ({ item, angle, hoverScale, cardWidth }) => {
const [isHovered, setIsHovered] = useState(false);
return (_jsx(motion.div, { className: "relative flex-shrink-0 group overflow-visible cursor-pointer", style: {
width: cardWidth,
height: "100%",
transformStyle: "preserve-3d",
}, custom: isHovered ? hoverScale : angle, variants: cardVariants, initial: "offHover", animate: isHovered ? "onHover" : "offHover", onMouseEnter: () => setIsHovered(true), onMouseLeave: () => setIsHovered(false), children: _jsxs("div", { className: "relative h-full w-full overflow-hidden border border-white/10 bg-muted \r\n min-h-[300px] shadow-2xl", children: [_jsx(Image, { src: item.url, alt: item.alt || "Slider Image", fill: true, className: "object-cover transition-transform duration-500 group-hover:scale-110" }), item.title && (_jsx("div", { className: "absolute bottom-0 left-0 right-0 bg-gradient-to-t from-black/80 to-transparent p-4 text-white opacity-0 transition-opacity duration-300 group-hover:opacity-100", children: _jsx("h3", { className: "text-lg font-bold", children: item.title }) }))] }) }));
};
export const AngledSlider = ({ items, speed = 40, direction = "left", containerHeight = "400px", cardWidth = "300px", gap = "40px", angle = 20, hoverScale = 1.05, className, }) => {
const [width, setWidth] = useState(0);
const containerRef = useRef(null);
const x = useMotionValue(0);
const [isHovered, setIsHovered] = useState(false);
// Duplicate items for infinite loop effect
// We need enough duplicates to fill the screen + buffer
const duplicatedItems = [...items, ...items, ...items];
useEffect(() => {
const calculateWidth = () => {
// Fallback to prop-based calculation if ref is not quite ready or layout is shifting
// This is generally safer for known fixed-width items
const numWidth = parseInt(cardWidth?.toString().replace("px", "") || "300");
const numGap = parseInt(gap?.toString().replace("px", "") || "40");
if (!isNaN(numWidth) && !isNaN(numGap)) {
const calculatedWidth = (numWidth + numGap) * items.length;
setWidth(calculatedWidth);
}
else if (containerRef.current) {
const scrollWidth = containerRef.current.scrollWidth;
setWidth(scrollWidth / 3);
}
};
calculateWidth();
window.addEventListener('resize', calculateWidth);
return () => window.removeEventListener('resize', calculateWidth);
}, [items, cardWidth, gap]);
useEffect(() => {
if (width <= 0)
return;
const startX = direction === "left" ? 0 : -width;
const endX = direction === "left" ? -width : 0;
if (isHovered)
return;
const runAnimation = () => {
const currentX = x.get();
const totalDist = width;
const dist = Math.abs(endX - currentX);
const duration = speed * (dist / totalDist);
const controls = animate(x, endX, {
duration: duration,
ease: "linear",
onComplete: () => {
x.set(startX);
runAnimation();
}
});
return controls;
};
const animation = runAnimation();
return () => {
animation.stop();
};
}, [width, speed, direction, isHovered, x]);
return (_jsx("div", { className: cn("relative w-full overflow-hidden bg-background py-10 perspective-1000", className), style: {
height: containerHeight,
perspective: "1000px", // Essential for 3D effect
}, onMouseEnter: () => setIsHovered(true), onMouseLeave: () => setIsHovered(false), children: _jsx(motion.div, { ref: containerRef, className: "flex items-center", style: { x, gap, transformStyle: "preserve-3d" }, children: duplicatedItems.map((item, index) => (_jsx(AngledCard, { item: item, angle: angle, hoverScale: hoverScale, cardWidth: cardWidth }, `${item.id}-${index}`))) }) }));
};