UNPKG

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
// @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}`))) }) })); };