66 lines
1.7 KiB
TypeScript
66 lines
1.7 KiB
TypeScript
"use client";
|
|
|
|
import { useEffect } from "react";
|
|
import { MapContainer, Marker, TileLayer, useMapEvents } from "react-leaflet";
|
|
import L from "leaflet";
|
|
|
|
type LatLng = { lat: number; lng: number };
|
|
|
|
let iconConfigured = false;
|
|
|
|
const ensureLeafletIcons = () => {
|
|
if (iconConfigured) return;
|
|
delete (L.Icon.Default.prototype as { _getIconUrl?: () => string })._getIconUrl;
|
|
L.Icon.Default.mergeOptions({
|
|
iconRetinaUrl:
|
|
"https://unpkg.com/leaflet@1.9.4/dist/images/marker-icon-2x.png",
|
|
iconUrl: "https://unpkg.com/leaflet@1.9.4/dist/images/marker-icon.png",
|
|
shadowUrl: "https://unpkg.com/leaflet@1.9.4/dist/images/marker-shadow.png"
|
|
});
|
|
iconConfigured = true;
|
|
};
|
|
|
|
function MapClickHandler({ onPick }: { onPick: (coords: LatLng) => void }) {
|
|
useMapEvents({
|
|
click(event) {
|
|
onPick({ lat: event.latlng.lat, lng: event.latlng.lng });
|
|
}
|
|
});
|
|
return null;
|
|
}
|
|
|
|
export default function MapPicker({
|
|
value,
|
|
onChange
|
|
}: {
|
|
value: LatLng | null;
|
|
onChange: (coords: LatLng) => void;
|
|
}) {
|
|
useEffect(() => {
|
|
ensureLeafletIcons();
|
|
}, []);
|
|
|
|
const center = value || { lat: 52.52, lng: 13.405 };
|
|
|
|
return (
|
|
<div
|
|
className="h-64 w-full overflow-hidden rounded-xl overscroll-contain"
|
|
onWheel={(event) => event.stopPropagation()}
|
|
>
|
|
<MapContainer
|
|
center={center}
|
|
zoom={value ? 14 : 5}
|
|
scrollWheelZoom
|
|
className="h-full w-full"
|
|
>
|
|
<TileLayer
|
|
attribution='© <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a>'
|
|
url="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png"
|
|
/>
|
|
<MapClickHandler onPick={onChange} />
|
|
{value && <Marker position={value} />}
|
|
</MapContainer>
|
|
</div>
|
|
);
|
|
}
|