Maps & Location Features
The Ever Works template supports interactive maps with a provider abstraction layer for both Google Maps and Mapbox. The system includes geocoding, location picking, marker clustering, and geolocation.
See also: Map View — the listing-level map experience (sidebar + map, header link, dedicated
/maproute) built on top of these primitives. Spec:017-map-view. Provider abstraction spec:011-maps-providers.
Architecture
The lib/maps/ module re-exports all types and providers:
// lib/maps/index.ts
export * from './types';
export * from './providers';
Core Types
Coordinates
interface Coordinates {
latitude: number;
longitude: number;
}
interface MapBounds {
north: number;
south: number;
east: number;
west: number;
}
Markers
interface MapMarkerData {
id: string;
coordinates: Coordinates;
title: string;
icon?: string;
category?: string;
slug: string; // Item slug for linking
description?: string;
}
interface MapMarkerWithDistance extends MapMarkerData {
distanceKm?: number; // Distance from reference point
}
Clustering
interface MapClusterData {
id: string;
coordinates: Coordinates;
count: number;
markerIds: string[];
expansionZoom: number;
}
interface ClusterOptions {
radius?: number; // Cluster radius in pixels (default: 60)
maxZoom?: number; // Max zoom for clustering (default: 16)
minZoom?: number; // Min zoom for clustering (default: 0)
minPoints?: number; // Min points per cluster (default: 2)
}
Viewport
interface MapViewport {
center: Coordinates;
zoom: number;
bounds?: MapBounds;
}
MapComponent Props
The main map component accepts a comprehensive set of props:
interface MapComponentProps {
markers?: MapMarkerData[];
center?: Coordinates;
zoom?: number; // 1-20
style?: MapStyle; // 'streets' | 'satellite'
className?: string;
height?: string | number;
width?: string | number;
controls?: MapControlsConfig;
enableClustering?: boolean;
clusterOptions?: ClusterOptions;
isLoading?: boolean;
isDisabled?: boolean;
error?: string | null;
onMarkerClick?: (marker: MapMarkerData) => void;
onClusterClick?: (cluster: MapClusterData) => void;
onViewportChange?: (viewport: MapViewport) => void;
onReady?: () => void;
onError?: (error: Error) => void;
ariaLabel?: string;
}
Map Controls
interface MapControlsConfig {
showZoomControls?: boolean;
showFullscreenControl?: boolean;
showNavigationControl?: boolean;
showScaleControl?: boolean;
}
LocationPicker Component
A form component for selecting locations with address autocomplete, map preview, and service area selection:
interface LocationPickerProps {
value?: LocationPickerValue;
onChange?: (location: LocationPickerValue) => void;
errors?: { address?: string; coordinates?: string; serviceArea?: string };
showMap?: boolean;
showServiceArea?: boolean;
showRemoteOption?: boolean;
mapHeight?: string | number;
isDisabled?: boolean;
isLoading?: boolean;
}
interface LocationPickerValue {
address?: string;
city?: string;
state?: string;
country?: string;
postalCode?: string;
latitude?: number;
longitude?: number;
serviceArea?: 'local' | 'regional' | 'national' | 'global';
isRemote?: boolean;
}
Hooks
useMapProvider
Determines the active map provider and its configuration status:
import { useMapProvider } from '@/hooks/use-map-provider';
const {
provider, // 'google' | 'mapbox'
isConfigured, // boolean -- API keys present
isLoading,
error,
mapStyle, // 'streets' | 'satellite'
} = useMapProvider();
useMapCoordinates
Manages map center and zoom state:
import { useMapCoordinates } from '@/hooks/use-map-coordinates';
const {
center, zoom, bounds,
setCenter, setZoom, setBounds,
fitToBounds,
} = useMapCoordinates(initialCenter, initialZoom);
useGeolocation
Browser geolocation with permission handling:
import { useGeolocation } from '@/hooks/use-geolocation';
const {
coordinates, // Coordinates | null
error, // 'PERMISSION_DENIED' | 'POSITION_UNAVAILABLE' | 'TIMEOUT' | 'NOT_SUPPORTED'
isLoading,
permission, // PermissionState | null
} = useGeolocation();