"use client" import * as React from "react" import MapGL, { Marker, NavigationControl, Popup, type MapMouseEvent, type MapProps as ReactMapProps, type MarkerDragEvent, } from "react-map-gl/maplibre" import { MapPinIcon } from "lucide-react" import "maplibre-gl/dist/maplibre-gl.css" import { cn } from "@/lib/utils" export type MapCoordinate = { longitude: number latitude: number } export type MapMarkerItem = MapCoordinate & { id: string label?: string content?: React.ReactNode popup?: React.ReactNode draggable?: boolean disabled?: boolean } export type MapViewProps = Omit & { markers?: MapMarkerItem[] selectedMarkerId?: string | null defaultSelectedMarkerId?: string | null onSelectedMarkerChange?: (markerId: string | null) => void onMarkerChange?: (markerId: string, coordinate: MapCoordinate) => void onMapClick?: (coordinate: MapCoordinate, event: MapMouseEvent) => void showNavigationControl?: boolean navigationControlPosition?: React.ComponentProps["position"] markerIcon?: React.ReactNode children?: React.ReactNode className?: string mapClassName?: string style?: React.CSSProperties } const DEFAULT_MAP_STYLE = "https://demotiles.maplibre.org/style.json" const DEFAULT_VIEW_STATE = { longitude: 0, latitude: 20, zoom: 2, } function MapView({ markers = [], selectedMarkerId, defaultSelectedMarkerId = null, onSelectedMarkerChange, onMarkerChange, onMapClick, showNavigationControl = true, navigationControlPosition = "top-right", markerIcon, children, className, mapClassName, style, initialViewState = DEFAULT_VIEW_STATE, mapStyle = DEFAULT_MAP_STYLE, onClick, ...mapProps }: MapViewProps) { const [internalSelectedMarkerId, setInternalSelectedMarkerId] = React.useState( defaultSelectedMarkerId ) const activeMarkerId = selectedMarkerId === undefined ? internalSelectedMarkerId : selectedMarkerId const activeMarker = markers.find((marker) => marker.id === activeMarkerId) const selectMarker = React.useCallback( (markerId: string | null) => { if (selectedMarkerId === undefined) setInternalSelectedMarkerId(markerId) onSelectedMarkerChange?.(markerId) }, [onSelectedMarkerChange, selectedMarkerId] ) return (
{ onClick?.(event) onMapClick?.( { longitude: event.lngLat.lng, latitude: event.lngLat.lat }, event ) }} > {showNavigationControl ? : null} {markers.map((marker) => ( { onMarkerChange?.(marker.id, { longitude: event.lngLat.lng, latitude: event.lngLat.lat, }) }} > ))} {activeMarker?.popup ? ( selectMarker(null)} >
{activeMarker.popup}
) : null} {children}
) } export type LocationPickerProps = Omit< MapViewProps, "markers" | "selectedMarkerId" | "defaultSelectedMarkerId" | "onSelectedMarkerChange" | "onMarkerChange" | "onMapClick" > & { value?: MapCoordinate | null defaultValue?: MapCoordinate | null onValueChange?: (coordinate: MapCoordinate | null) => void markerLabel?: string draggable?: boolean selectOnMapClick?: boolean clearable?: boolean } function LocationPicker({ value, defaultValue = null, onValueChange, markerLabel = "Selected location", draggable = true, selectOnMapClick = true, clearable = false, initialViewState, ...props }: LocationPickerProps) { const [internalValue, setInternalValue] = React.useState(defaultValue) const currentValue = value === undefined ? internalValue : value const updateValue = React.useCallback( (coordinate: MapCoordinate | null) => { if (value === undefined) setInternalValue(coordinate) onValueChange?.(coordinate) }, [onValueChange, value] ) const resolvedInitialViewState = initialViewState ?? (currentValue ? { longitude: currentValue.longitude, latitude: currentValue.latitude, zoom: 13, } : DEFAULT_VIEW_STATE) return (
updateValue(coordinate)} onMapClick={(coordinate) => { if (selectOnMapClick) updateValue(coordinate) }} /> {currentValue ? (
{currentValue.latitude.toFixed(6)}, {currentValue.longitude.toFixed(6)} {clearable ? ( ) : null}
) : null}
) } export { LocationPicker, MapView }