import { useState, useEffect, useRef, useCallback, useMemo } from 'react'; import { motion, useInView } from 'framer-motion'; import { LucideIcon, TrendingUp, // Generic icon for fallback Search, DeleteIcon, } from 'lucide-react'; // NOTE: Placeholder for your custom Input component const Input = (props: React.InputHTMLAttributes) => ( ); // --- Core Data Interface --- export interface ChainItem { id: string | number; // Unique ID name: string; icon: LucideIcon; /** A secondary string line for the item, e.g., a short description or a value. */ details?: string; logo?: string; // Optional image URL } // --- Internal Animated Type --- /** The specific type returned by getVisibleItems, extending the base ChainItem. */ type AnimatedChainItem = ChainItem & { distanceFromCenter: number; originalIndex: number; }; // --- Component Props Interfaces --- interface CarouselItemProps { chain: AnimatedChainItem; side: 'left' | 'right'; } interface ChainCarouselProps { /** The list of items to display in the carousel. (REQUIRED) */ items: ChainItem[]; /** The speed of the auto-scroll rotation in milliseconds. */ scrollSpeedMs?: number; /** The number of carousel items visible at once (must be an odd number). */ visibleItemCount?: number; /** Custom class for the main container div. */ className?: string; /** Function to call when a chain is selected from the search dropdown. */ onChainSelect?: (chainId: ChainItem['id'], chainName: string) => void; } // --- Helper Components --- /** A single item card for the carousel. */ const CarouselItemCard: React.FC = ({ 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 = (
{logo ? ( {`${name} ) : ( )}
); return ( {IconOrLogo}
{/* FIX: Added whitespace-nowrap to prevent the name from wrapping. */} {name} {/* Display generic details/description */} {details}
); }; // --- Main Component --- const ChainCarousel: React.FC = ({ 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: NodeJS.Timeout; 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( (): AnimatedChainItem[] => { // Explicitly define return type const visibleItems: AnimatedChainItem[] = []; 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: ChainItem['id'], name: string) => { 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 (
{/* Left Section - Chain Carousel (Hidden on smaller screens) */} !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 }} > {/* Fading overlay to mask items */}
{getVisibleItems().map((chain) => ( ))}
{/* Middle Section - Text and Search Input */}
{/* Currently Selected Item Display */} {currentItem && (
{currentItem.logo ? ( {`${currentItem.name} ) : ( )}

{currentItem.name}

{currentItem.details || 'View Details'}

)} {/* Search Bar */}
{ 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 !placeholder-gray-800 text-lg rounded-full border-gray-500 pr-10 pl-10 py-2 cursor-pointer border" /> {searchTerm && ( )}
{/* Dropdown for search results */} {showDropdown && filteredItems.length > 0 && (
{filteredItems.slice(0, 10).map((chain) => (
{ e.preventDefault(); handleSelectChain(chain.id, chain.name); }} className="flex items-center gap-3 px-4 py-3 cursor-pointer hover:bg-gray-100/10 transition-colors duration-150 rounded-lg m-2" > {chain.logo ? ( {`${chain.name} ) : ( )} {chain.name} {chain.details}
))}
)}
{/* Right Section - Chain Carousel */} !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 }} > {/* Fading overlay to mask items */}
{getVisibleItems().map((chain) => ( ))}
); }; export default ChainCarousel;