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.

136 lines (135 loc) 11.3 kB
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime"; // @ts-nocheck import { useState, useEffect, useRef, useCallback, useMemo } from 'react'; import { motion, useInView } from 'framer-motion'; import { // Generic icon for fallback Search, DeleteIcon, } from 'lucide-react'; // NOTE: Placeholder for your custom Input component const Input = (props) => (_jsx("input", { ...props })); // --- Helper Components --- /** A single item card for the carousel. */ const CarouselItemCard = ({ chain, side }) => { const { distanceFromCenter, id, name, details, logo, icon: FallbackIcon } = chain; const distance = Math.abs(distanceFromCenter); // Visual effects based on distance from the center (0) const opacity = 1 - distance / 4; const scale = 1 - distance * 0.1; const yOffset = distanceFromCenter * 90; const xOffset = side === 'left' ? -distance * 50 : distance * 50; const IconOrLogo = (_jsx("div", { className: "rounded-full border border-muted-foreground/60 \r\n dark:border-muted-foreground/40 p-2 bg-foreground", children: logo ? (_jsx("img", { src: logo, alt: `${name} logo`, className: "size-8 rounded-full object-cover" })) : (_jsx(FallbackIcon, { className: "size-8 text-background" })) })); return (_jsxs(motion.div, { className: `absolute flex items-center gap-4 text-background px-6 py-3 ${side === 'left' ? 'flex-row-reverse' : 'flex-row'}`, animate: { opacity, scale, y: yOffset, x: xOffset, }, transition: { duration: 0.4, ease: 'easeInOut' }, children: [IconOrLogo, _jsxs("div", { className: `flex flex-col mx-4 ${side === 'left' ? 'text-right' : 'text-left'}`, children: [_jsx("span", { className: "text-md lg:text-lg font-semibold text-foreground whitespace-nowrap", children: name }), _jsx("span", { className: "text-xs lg:text-sm text-gray-500", children: details })] })] }, id)); }; // --- Main Component --- export const ChainCarousel = ({ items, scrollSpeedMs = 1500, visibleItemCount = 9, className = '', onChainSelect, }) => { const [currentIndex, setCurrentIndex] = useState(0); const [isPaused, setIsPaused] = useState(false); const [searchTerm, setSearchTerm] = useState(''); const [showDropdown, setShowDropdown] = useState(false); // References for Framer Motion scroll-based animation const rightSectionRef = useRef(null); const isInView = useInView(rightSectionRef, { margin: '-100px 0px -100px 0px' }); const totalItems = items.length; // 1. Auto-scroll effect useEffect(() => { if (isPaused || totalItems === 0) return; const interval = setInterval(() => { setCurrentIndex((prev) => (prev + 1) % totalItems); }, scrollSpeedMs); return () => clearInterval(interval); }, [isPaused, totalItems, scrollSpeedMs]); // 2. Scroll listener to pause carousel on page scroll useEffect(() => { let timeoutId; const handleScroll = () => { setIsPaused(true); clearTimeout(timeoutId); timeoutId = setTimeout(() => { setIsPaused(false); }, 500); // Resume auto-scroll after 500ms of no scrolling }; window.addEventListener('scroll', handleScroll, { passive: true }); return () => { window.removeEventListener('scroll', handleScroll); clearTimeout(timeoutId); }; }, []); // Memoized function for carousel items const getVisibleItems = useCallback(() => { const visibleItems = []; if (totalItems === 0) return []; // Ensure visibleItemCount is an odd number for a clear center item const itemsToShow = visibleItemCount % 2 === 0 ? visibleItemCount + 1 : visibleItemCount; const half = Math.floor(itemsToShow / 2); for (let i = -half; i <= half; i++) { let index = currentIndex + i; if (index < 0) index += totalItems; if (index >= totalItems) index -= totalItems; visibleItems.push({ ...items[index], originalIndex: index, distanceFromCenter: i, }); } return visibleItems; }, [currentIndex, items, totalItems, visibleItemCount]); // Filtered list for search dropdown const filteredItems = useMemo(() => { return items.filter((item) => item.name.toLowerCase().includes(searchTerm.toLowerCase())); }, [items, searchTerm]); // Handler for selecting an item from the dropdown const handleSelectChain = (id, name) => { const index = items.findIndex((c) => c.id === id); if (index !== -1) { setCurrentIndex(index); // Jump to the selected item setIsPaused(true); // Pause to highlight the selection if (onChainSelect) { } } setSearchTerm(name); // Set search term to the selected item's name setShowDropdown(false); }; // The current item displayed in the center const currentItem = items[currentIndex]; // --- JSX Render --- return (_jsx("div", { id: 'explore-section', className: ` space-y-20 ${className}`, children: _jsxs("div", { className: 'flex flex-col xl:flex-row \r\n max-w-7xl mx-auto px-4 md:px-8 gap-12 justify-center items-center', children: [_jsxs(motion.div, { className: "relative w-full max-w-md xl:max-w-2xl h-[450px] \r\n flex items-center justify-center hidden xl:flex -left-14", onMouseEnter: () => !searchTerm && setIsPaused(true), onMouseLeave: () => !searchTerm && setIsPaused(false), initial: { x: '-100%', opacity: 0 }, animate: isInView ? { x: 0, opacity: 1 } : {}, transition: { type: 'spring', stiffness: 80, damping: 20, duration: 0.8 }, children: [_jsxs("div", { className: "absolute inset-0 z-10 pointer-events-none", children: [_jsx("div", { className: "absolute top-0 h-1/4 w-full bg-transparent" }), _jsx("div", { className: "absolute bottom-0 h-1/4 w-full bg-transparent " })] }), getVisibleItems().map((chain) => (_jsx(CarouselItemCard, { chain: chain, side: "left" }, chain.id)))] }), _jsxs("div", { className: "flex flex-col text-center 2xl:text-center xl:text-center\r\n gap-4 max-w-md", children: [currentItem && (_jsxs("div", { className: "flex flex-col items-center justify-center gap-0 mt-4", children: [_jsx("div", { className: 'p-2 bg-foreground rounded-full', children: currentItem.logo ? (_jsx("img", { src: currentItem.logo, alt: `${currentItem.name} logo`, className: "size-12 rounded-full object-cover" })) : ((() => { const Icon = currentItem.icon; return _jsx(Icon, { className: "size-8 text-background" }); })()) }), _jsx("h3", { className: "text-xl xl:text-2xl font-bold text-foreground mt-2", children: currentItem.name }), _jsx("p", { className: "text-sm xl:text-lg text-gray-400", children: currentItem.details || 'View Details' })] })), _jsxs("div", { className: "mt-6 relative max-w-lg mx-auto xl:mx-0", children: [_jsxs("div", { className: "px-3 flex items-center relative", children: [_jsx(Input, { type: "text", value: searchTerm, placeholder: "Search Items...", onChange: (e) => { const val = e.target.value; setSearchTerm(val); setShowDropdown(val.length > 0); if (val === '') setIsPaused(false); }, onFocus: () => { if (searchTerm.length > 0) setShowDropdown(true); setIsPaused(true); }, onBlur: () => { // Wait briefly before hiding to allow click on dropdown item setTimeout(() => setShowDropdown(false), 200); }, className: "flex-grow outline-none text-foreground bg-background px-4 \r\n !placeholder-gray-800 text-lg rounded-full border-gray-500 pr-10 \r\n pl-10 py-2 cursor-pointer border" }), _jsx(Search, { className: "absolute text-foreground w-5 h-5 left-6 pointer-events-none" }), searchTerm && (_jsx("button", { onClick: () => { setSearchTerm(''); setShowDropdown(false); setIsPaused(false); }, className: "absolute right-6 text-foreground hover:text-gray-300", children: _jsx(DeleteIcon, {}) }))] }), showDropdown && filteredItems.length > 0 && (_jsx("div", { className: "absolute left-0 right-0 mt-2 bg-background \r\n rounded-lg border z-20 max-h-60 overflow-y-auto shadow-xl", children: filteredItems.slice(0, 10).map((chain) => (_jsxs("div", { // FIX: Use e.preventDefault() to stop browser's form validation/alert onMouseDown: (e) => { e.preventDefault(); handleSelectChain(chain.id, chain.name); }, className: "flex items-center gap-3 px-4 py-3 cursor-pointer \r\n hover:bg-gray-100/10 transition-colors duration-150 rounded-lg m-2", children: [chain.logo ? (_jsx("img", { src: chain.logo, alt: `${chain.name} logo`, className: "size-6 rounded-full object-cover" })) : ((() => { const Icon = chain.icon; return _jsx(Icon, { size: 24, className: "text-primary" }); })()), _jsx("span", { className: "text-foreground font-medium", children: chain.name }), _jsx("span", { className: "ml-auto text-sm text-gray-500", children: chain.details })] }, chain.id))) }))] })] }), _jsxs(motion.div, { ref: rightSectionRef, className: "relative w-full max-w-md xl:max-w-2xl h-[450px] \r\n flex items-center justify-center -right-14", onMouseEnter: () => !searchTerm && setIsPaused(true), onMouseLeave: () => !searchTerm && setIsPaused(false), initial: { x: '100%', opacity: 0 }, animate: isInView ? { x: 0, opacity: 1 } : {}, transition: { type: 'spring', stiffness: 80, damping: 20, duration: 0.8 }, children: [_jsxs("div", { className: "absolute inset-0 z-10 pointer-events-none", children: [_jsx("div", { className: "absolute top-0 h-1/4 w-full bg-transparent " }), _jsx("div", { className: "absolute bottom-0 h-1/4 w-full bg-transparent " })] }), getVisibleItems().map((chain) => (_jsx(CarouselItemCard, { chain: chain, side: "right" }, chain.id)))] })] }) })); }; export default ChainCarousel;