103 lines
3.1 KiB
TypeScript
103 lines
3.1 KiB
TypeScript
'use client';
|
||
|
||
import { useEffect, useRef } from 'react';
|
||
import L from 'leaflet';
|
||
|
||
const DEFAULT_CENTER = { lat: 55.7558, lng: 37.6173 };
|
||
|
||
let iconsFixed = false;
|
||
|
||
function fixLeafletIcons() {
|
||
if (iconsFixed || typeof window === 'undefined') return;
|
||
iconsFixed = true;
|
||
L.Icon.Default.mergeOptions({
|
||
iconUrl: 'https://unpkg.com/leaflet@1.9.4/dist/images/marker-icon.png',
|
||
iconRetinaUrl: 'https://unpkg.com/leaflet@1.9.4/dist/images/marker-icon-2x.png',
|
||
shadowUrl: 'https://unpkg.com/leaflet@1.9.4/dist/images/marker-shadow.png',
|
||
iconSize: [25, 41],
|
||
iconAnchor: [12, 41],
|
||
popupAnchor: [1, -34],
|
||
shadowSize: [41, 41]
|
||
});
|
||
}
|
||
|
||
export function AddressMapPicker({
|
||
latitude,
|
||
longitude,
|
||
onPick
|
||
}: {
|
||
latitude?: number;
|
||
longitude?: number;
|
||
onPick: (lat: number, lng: number) => void;
|
||
}) {
|
||
const mapRef = useRef<HTMLDivElement>(null);
|
||
const mapInstance = useRef<L.Map | null>(null);
|
||
const markerRef = useRef<L.Marker | null>(null);
|
||
const onPickRef = useRef(onPick);
|
||
|
||
useEffect(() => {
|
||
onPickRef.current = onPick;
|
||
}, [onPick]);
|
||
|
||
useEffect(() => {
|
||
fixLeafletIcons();
|
||
if (!mapRef.current || mapInstance.current) return;
|
||
|
||
const lat = latitude ?? DEFAULT_CENTER.lat;
|
||
const lng = longitude ?? DEFAULT_CENTER.lng;
|
||
const map = L.map(mapRef.current, { zoomControl: true }).setView([lat, lng], latitude != null ? 16 : 11);
|
||
|
||
L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
|
||
attribution: '© <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a>'
|
||
}).addTo(map);
|
||
|
||
const marker = L.marker([lat, lng], { draggable: true }).addTo(map);
|
||
|
||
const emitPick = (nextLat: number, nextLng: number) => {
|
||
onPickRef.current(nextLat, nextLng);
|
||
};
|
||
|
||
marker.on('dragend', () => {
|
||
const position = marker.getLatLng();
|
||
emitPick(position.lat, position.lng);
|
||
});
|
||
|
||
map.on('click', (event) => {
|
||
marker.setLatLng(event.latlng);
|
||
emitPick(event.latlng.lat, event.latlng.lng);
|
||
});
|
||
|
||
mapInstance.current = map;
|
||
markerRef.current = marker;
|
||
|
||
const resizeObserver = new ResizeObserver(() => {
|
||
map.invalidateSize();
|
||
});
|
||
resizeObserver.observe(mapRef.current);
|
||
|
||
return () => {
|
||
resizeObserver.disconnect();
|
||
map.remove();
|
||
mapInstance.current = null;
|
||
markerRef.current = null;
|
||
};
|
||
}, []);
|
||
|
||
useEffect(() => {
|
||
if (!markerRef.current || !mapInstance.current) return;
|
||
if (latitude == null || longitude == null) return;
|
||
const latLng = L.latLng(latitude, longitude);
|
||
markerRef.current.setLatLng(latLng);
|
||
mapInstance.current.setView(latLng, Math.max(mapInstance.current.getZoom(), 15));
|
||
}, [latitude, longitude]);
|
||
|
||
return (
|
||
<div className="overflow-hidden rounded-2xl border border-[#eceef4]">
|
||
<div ref={mapRef} className="embedded-leaflet-map h-[280px] w-full" />
|
||
<p className="border-t border-[#eceef4] bg-[#fafbfd] px-4 py-2 text-xs text-[#667085]">
|
||
Нажмите на карту или перетащите маркер, чтобы указать адрес
|
||
</p>
|
||
</div>
|
||
);
|
||
}
|