import { useState, useMemo, useEffect } from 'react'; import { useCollapsibleGroups } from '../../hooks/useCollapsibleGroups'; import { SearchInput } from '../ui/SearchInput'; import { FilterIcon } from '../ui/icons'; import { CollapsibleGroupHeader } from '../ui/CollapsibleGroupHeader'; import { EmptyState } from '../ui/EmptyState'; import type { FeatureMeta } from '../../types'; import { groupFeaturesByCategory } from '../../lib/features'; import { FeatureInfoPopup } from '../ui/FeatureInfoPopup'; import { FeatureActions } from '../ui/FeatureIcons'; import { FeatureLabel } from '../ui/FeatureLabel'; import { CarIcon, BicycleIcon, WalkingIcon, TransitIcon, PlusIcon } from '../ui/icons'; import type { ComponentType } from 'react'; import { IconButton } from '../ui/IconButton'; import { TRANSPORT_MODES, MODE_LABELS, MODE_DESCRIPTIONS, type TransportMode, type TravelTimeEntry } from '../../hooks/useTravelTime'; const MODE_ICONS: Record> = { car: CarIcon, bicycle: BicycleIcon, walking: WalkingIcon, transit: TransitIcon, }; interface FeatureBrowserProps { availableFeatures: FeatureMeta[]; allFeatures: FeatureMeta[]; pinnedFeature: string | null; onAddFilter: (name: string) => void; onTogglePin: (name: string) => void; onNavigateToSource?: (slug: string, featureName: string) => void; openInfoFeature?: string | null; onClearOpenInfoFeature?: () => void; travelTimeEntries: TravelTimeEntry[]; onAddTravelTimeEntry: (mode: TransportMode) => void; isLicensed: boolean; onUpgradeClick?: () => void; } export default function FeatureBrowser({ availableFeatures, allFeatures, pinnedFeature, onAddFilter, onTogglePin, onNavigateToSource, openInfoFeature, onClearOpenInfoFeature, travelTimeEntries, onAddTravelTimeEntry, isLicensed, onUpgradeClick, }: FeatureBrowserProps) { const [search, setSearch] = useState(''); const [infoFeature, setInfoFeature] = useState(null); const [expandedGroups, toggleGroup] = useCollapsibleGroups(); useEffect(() => { if (openInfoFeature) { const feat = allFeatures.find((f) => f.name === openInfoFeature); if (feat) setInfoFeature(feat); onClearOpenInfoFeature?.(); } }, [openInfoFeature, allFeatures, onClearOpenInfoFeature]); const filtered = useMemo(() => { if (!search) return availableFeatures; const lower = search.toLowerCase(); return availableFeatures.filter((f) => f.name.toLowerCase().includes(lower)); }, [availableFeatures, search]); const grouped = useMemo(() => groupFeaturesByCategory(filtered), [filtered]); // When searching, expand all groups so results are visible const isSearching = search.length > 0; // All modes are always available (can add multiple entries per mode) const showTravelModes = !search || 'travel time journey commute car bicycle walking transit'.includes(search.toLowerCase()); return ( <>
{showTravelModes && (
toggleGroup('Travel Time')} className="px-3 py-2.5 text-sm font-bold text-navy-950 bg-warm-200 dark:bg-navy-900 dark:text-warm-100 sticky top-0 z-10 hover:bg-warm-200 dark:hover:bg-warm-800" > {TRANSPORT_MODES.length} {(isSearching || expandedGroups.has('Travel Time')) && TRANSPORT_MODES.map((mode) => { const ModeIcon = MODE_ICONS[mode]; return (
onAddTravelTimeEntry(mode)}>
{MODE_LABELS[mode]} {MODE_DESCRIPTIONS[mode]}
onAddTravelTimeEntry(mode)} title={`Add ${MODE_LABELS[mode]} travel time`} size="md">
); })}
)} {grouped.map((group) => { const isExpanded = isSearching || expandedGroups.has(group.name); return (
toggleGroup(group.name)} className="px-3 py-2.5 text-sm font-bold text-navy-950 bg-warm-200 dark:bg-navy-900 dark:text-warm-100 sticky top-0 z-10 hover:bg-warm-200 dark:hover:bg-warm-800" > {group.features.length} {isExpanded && group.features.map((f) => { const isPinned = pinnedFeature === f.name; return (
{f.description && ( {f.description} )}
); })}
); })} {grouped.length === 0 ? ( } title={search ? 'No matching features' : 'All features are active'} description={ search ? 'Try a different search term' : 'Remove a filter to see available features' } className="px-3 py-4" /> ) : isLicensed ? (

Everyone cares about different things. Pick the filters that matter most to you.

) : (

The biggest financial decision of your life deserves proper tools behind it.

Don't leave it to chance.

)}
{infoFeature && ( setInfoFeature(null)} onNavigateToSource={onNavigateToSource} /> )} ); }