This commit is contained in:
Andras Schmelczer 2026-02-14 12:53:29 +00:00
parent 3a3f899ea2
commit 128b3191e7
68 changed files with 28060 additions and 1152 deletions

View file

@ -11,6 +11,7 @@ import { FeatureActions } from '../ui/FeatureIcons';
import { FeatureLabel } from '../ui/FeatureLabel';
import { RouteIcon, PlusIcon } from '../ui/icons';
import { IconButton } from '../ui/IconButton';
import { TRANSPORT_MODES, MODE_LABELS, type TransportMode } from '../../hooks/useTravelTime';
interface FeatureBrowserProps {
availableFeatures: FeatureMeta[];
@ -21,8 +22,8 @@ interface FeatureBrowserProps {
onNavigateToSource?: (slug: string, featureName: string) => void;
openInfoFeature?: string | null;
onClearOpenInfoFeature?: () => void;
travelTimeEnabled?: boolean;
onEnableTravelTime?: () => void;
activeTravelModes: TransportMode[];
onEnableTravelMode: (mode: TransportMode) => void;
}
export default function FeatureBrowser({
@ -34,8 +35,8 @@ export default function FeatureBrowser({
onNavigateToSource,
openInfoFeature,
onClearOpenInfoFeature,
travelTimeEnabled,
onEnableTravelTime,
activeTravelModes,
onEnableTravelMode,
}: FeatureBrowserProps) {
const [search, setSearch] = useState('');
const [infoFeature, setInfoFeature] = useState<FeatureMeta | null>(null);
@ -60,32 +61,42 @@ export default function FeatureBrowser({
// When searching, expand all groups so results are visible
const isSearching = search.length > 0;
// Inactive modes available to add
const inactiveModes = useMemo(
() => TRANSPORT_MODES.filter((m) => !activeTravelModes.includes(m)),
[activeTravelModes]
);
const showTravelModes =
inactiveModes.length > 0 &&
(!search || 'travel time journey commute car bicycle walking transit'.includes(search.toLowerCase()));
return (
<>
<div className="shrink-0 p-2 border-b border-warm-200 dark:border-navy-700">
<SearchInput value={search} onChange={setSearch} placeholder="Search features..." />
</div>
<div className="md:min-h-0 md:flex-1 md:overflow-y-auto flex flex-col">
{!travelTimeEnabled && onEnableTravelTime && (!search || 'travel time journey commute'.includes(search.toLowerCase())) && (
<div className="shrink-0 border-b border-warm-200 dark:border-warm-700">
{showTravelModes && inactiveModes.map((mode) => (
<div key={mode} className="shrink-0 border-b border-warm-200 dark:border-warm-700">
<div className="flex items-start justify-between px-3 py-2 hover:bg-teal-50 dark:hover:bg-teal-900/30 cursor-pointer">
<div className="flex items-center gap-2 min-w-0" onClick={onEnableTravelTime}>
<div className="flex items-center gap-2 min-w-0" onClick={() => onEnableTravelMode(mode)}>
<RouteIcon className="w-4 h-4 text-teal-600 dark:text-teal-400 shrink-0" />
<div className="min-w-0">
<span className="text-sm font-medium text-navy-950 dark:text-warm-100">
Travel Time
Travel Time ({MODE_LABELS[mode]})
</span>
<span className="text-xs text-warm-400 dark:text-warm-500 block">
Color by journey time to a destination
</span>
</div>
</div>
<IconButton onClick={() => onEnableTravelTime()} title="Add travel time">
<IconButton onClick={() => onEnableTravelMode(mode)} title={`Add ${MODE_LABELS[mode]} travel time`}>
<PlusIcon className="w-3.5 h-3.5" />
</IconButton>
</div>
</div>
)}
))}
{grouped.map((group) => {
const isExpanded = isSearching || expandedGroups.has(group.name);
return (
@ -128,7 +139,7 @@ export default function FeatureBrowser({
</div>
);
})}
{grouped.length === 0 ? (
{grouped.length === 0 && !showTravelModes ? (
<EmptyState
icon={<FilterIcon className="w-8 h-8 text-warm-300 dark:text-warm-600" />}
title={search ? 'No matching features' : 'All features are active'}