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
JavaScript
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;