/** * SourceBadgeList - Badge list container with responsive layout * Desktop: Expandable grid with show more/less * Mobile: Horizontal scroll with snap */ 'use client'; import { useState, useRef, useCallback, useEffect } from 'react'; import { Icons } from '@/components/ui/Icon'; import { SourceBadgeItem } from './SourceBadgeItem'; import { useKeyboardNavigation } from '@/lib/hooks/useKeyboardNavigation'; interface Source { id: string; name: string; count: number; } interface SourceBadgeListProps { sources: Source[]; selectedSources: Set; onToggleSource: (sourceId: string) => void; } export function SourceBadgeList({ sources, selectedSources, onToggleSource }: SourceBadgeListProps) { const [isExpanded, setIsExpanded] = useState(false); const [focusedIndex, setFocusedIndex] = useState(-1); const [hasOverflow, setHasOverflow] = useState(false); const containerRef = useRef(null); const badgeContainerRef = useRef(null); const badgeRefs = useRef<(HTMLButtonElement | null)[]>([]); // Keyboard navigation useKeyboardNavigation({ enabled: true, containerRef: containerRef, currentIndex: focusedIndex, itemCount: sources.length, orientation: 'horizontal', onNavigate: useCallback((index: number) => { setFocusedIndex(index); badgeRefs.current[index]?.focus(); // Scroll into view for mobile badgeRefs.current[index]?.scrollIntoView({ behavior: 'smooth', block: 'nearest', inline: 'center', }); }, []), onSelect: useCallback((index: number) => { onToggleSource(sources[index].id); }, [sources, onToggleSource]), }); // Check if content has overflow on mount and when sources change useEffect(() => { const checkOverflow = () => { if (badgeContainerRef.current) { const maxHeight = 50; // 50px to fit one row (44px) + padding but hide second row (starts at 52px) setHasOverflow(badgeContainerRef.current.scrollHeight > maxHeight); } }; checkOverflow(); // Recheck after a short delay to account for animations const timeout = setTimeout(checkOverflow, 100); return () => clearTimeout(timeout); }, [sources]); return ( <> {/* Desktop: Expandable Grid */}
{sources.map((source, index) => ( onToggleSource(source.id)} isFocused={focusedIndex === index} onFocus={() => setFocusedIndex(index)} innerRef={(el: HTMLButtonElement | null) => { badgeRefs.current[index] = el; }} /> ))}
{hasOverflow && ( )}
{/* Mobile & Tablet: Horizontal Scroll */}
{sources.map((source, index) => ( onToggleSource(source.id)} isFocused={focusedIndex === index} onFocus={() => setFocusedIndex(index)} innerRef={(el: HTMLButtonElement | null) => { badgeRefs.current[index] = el; }} /> ))}
); }