82 lines
2.4 KiB
TypeScript
82 lines
2.4 KiB
TypeScript
import React, { useEffect, useRef } from 'react';
|
|
import L from 'leaflet';
|
|
import 'leaflet/dist/leaflet.css';
|
|
|
|
// Fix for default marker icon in Leaflet with Next.js
|
|
if (typeof window !== 'undefined') {
|
|
const DefaultIcon = L.icon({
|
|
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',
|
|
iconSize: [25, 41],
|
|
iconAnchor: [12, 41],
|
|
});
|
|
|
|
L.Marker.prototype.options.icon = DefaultIcon;
|
|
}
|
|
|
|
export interface MapLocation {
|
|
name: string;
|
|
address: string;
|
|
lat: number;
|
|
lng: number;
|
|
}
|
|
|
|
interface LeafletMapProps {
|
|
locations: MapLocation[];
|
|
}
|
|
|
|
export default function LeafletMap({ locations }: LeafletMapProps) {
|
|
const mapRef = useRef<HTMLDivElement>(null);
|
|
const mapInstanceRef = useRef<L.Map | null>(null);
|
|
|
|
useEffect(() => {
|
|
if (!mapRef.current || mapInstanceRef.current || !locations.length) return;
|
|
|
|
// Calculate center based on markers or first marker
|
|
const firstLoc = locations[0];
|
|
const map = L.map(mapRef.current, {
|
|
center: [firstLoc.lat, firstLoc.lng],
|
|
zoom: 6, // Zoom out to see both or more
|
|
scrollWheelZoom: false,
|
|
});
|
|
|
|
// Add tiles (Dark mode themed for industrial feel if possible, but standard OSM is safer for now)
|
|
L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
|
|
attribution:
|
|
'© <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors',
|
|
}).addTo(map);
|
|
|
|
const bounds = L.latLngBounds([]);
|
|
|
|
locations.forEach((loc) => {
|
|
const marker = L.marker([loc.lat, loc.lng]).addTo(map);
|
|
const popupContent = `
|
|
<div class="p-2">
|
|
<div class="text-primary font-bold border-b border-neutral-100 pb-1 mb-1">${loc.name}</div>
|
|
<div class="text-xs text-neutral-600">${loc.address.replace(/\n/g, '<br/>')}</div>
|
|
</div>
|
|
`;
|
|
marker.bindPopup(popupContent);
|
|
bounds.extend([loc.lat, loc.lng]);
|
|
});
|
|
|
|
// Fit bounds if more than 1 location
|
|
if (locations.length > 1) {
|
|
map.fitBounds(bounds, { padding: [50, 50] });
|
|
}
|
|
|
|
mapInstanceRef.current = map;
|
|
|
|
// Cleanup on unmount
|
|
return () => {
|
|
if (mapInstanceRef.current) {
|
|
mapInstanceRef.current.remove();
|
|
mapInstanceRef.current = null;
|
|
}
|
|
};
|
|
}, [locations]);
|
|
|
|
return <div ref={mapRef} className="h-full w-full z-0 rounded-2xl shadow-inner border border-neutral-200" />;
|
|
}
|
|
|