diff --git a/web/src/components/LeafletMap.tsx b/web/src/components/LeafletMap.tsx
index 6cffccee6..083c387af 100644
--- a/web/src/components/LeafletMap.tsx
+++ b/web/src/components/LeafletMap.tsx
@@ -1,8 +1,10 @@
-import { DivIcon, LatLng } from "leaflet";
-import { MapPinIcon } from "lucide-react";
-import { useEffect, useRef, useState } from "react";
+import L, { DivIcon, LatLng } from "leaflet";
+import { ExternalLinkIcon, MapPinIcon, MinusIcon, PlusIcon } from "lucide-react";
+import { type ReactNode, useEffect, useRef, useState } from "react";
+import { createRoot } from "react-dom/client";
import ReactDOMServer from "react-dom/server";
import { MapContainer, Marker, TileLayer, useMap, useMapEvents } from "react-leaflet";
+import { cn } from "@/lib/utils";
const markerIcon = new DivIcon({
className: "relative border-none",
@@ -54,6 +56,146 @@ const LocationMarker = (props: MarkerProps) => {
return position === undefined ? null : ;
};
+// Reusable glass-style button component
+interface GlassButtonProps {
+ icon: ReactNode;
+ onClick: () => void;
+ ariaLabel: string;
+ title: string;
+}
+
+const GlassButton = ({ icon, onClick, ariaLabel, title }: GlassButtonProps) => {
+ return (
+
+ );
+};
+
+// Container for all map control buttons
+interface ControlButtonsProps {
+ position: LatLng | undefined;
+ onZoomIn: () => void;
+ onZoomOut: () => void;
+ onOpenGoogleMaps: () => void;
+}
+
+const ControlButtons = ({ position, onZoomIn, onZoomOut, onOpenGoogleMaps }: ControlButtonsProps) => {
+ return (
+
+ {position && (
+ }
+ onClick={onOpenGoogleMaps}
+ ariaLabel="Open location in Google Maps"
+ title="Open in Google Maps"
+ />
+ )}
+ } onClick={onZoomIn} ariaLabel="Zoom in" title="Zoom in" />
+ } onClick={onZoomOut} ariaLabel="Zoom out" title="Zoom out" />
+
+ );
+};
+
+// Custom Leaflet Control class
+class MapControlsContainer extends L.Control {
+ private container: HTMLDivElement | null = null;
+
+ onAdd() {
+ this.container = L.DomUtil.create("div", "");
+ this.container.style.pointerEvents = "auto";
+
+ // Prevent map interactions when clicking controls
+ L.DomEvent.disableClickPropagation(this.container);
+ L.DomEvent.disableScrollPropagation(this.container);
+
+ return this.container;
+ }
+
+ onRemove() {
+ this.container = null;
+ }
+
+ getContainer() {
+ return this.container;
+ }
+}
+
+interface MapControlsProps {
+ position: LatLng | undefined;
+}
+
+const MapControls = ({ position }: MapControlsProps) => {
+ const map = useMap();
+ const controlRef = useRef(null);
+ const rootRef = useRef | null>(null);
+
+ const handleOpenInGoogleMaps = () => {
+ if (!position) return;
+ const url = `https://www.google.com/maps?q=${position.lat},${position.lng}`;
+ window.open(url, "_blank", "noopener,noreferrer");
+ };
+
+ const handleZoomIn = () => {
+ map.zoomIn();
+ };
+
+ const handleZoomOut = () => {
+ map.zoomOut();
+ };
+
+ useEffect(() => {
+ // Create custom Leaflet control
+ const control = new MapControlsContainer({ position: "topright" });
+ controlRef.current = control;
+ control.addTo(map);
+
+ // Get container and render React component into it
+ const container = control.getContainer();
+ if (container) {
+ rootRef.current = createRoot(container);
+ rootRef.current.render(
+ ,
+ );
+ }
+
+ return () => {
+ // Cleanup: unmount React component and remove control
+ if (rootRef.current) {
+ rootRef.current.unmount();
+ rootRef.current = null;
+ }
+ if (controlRef.current) {
+ controlRef.current.remove();
+ controlRef.current = null;
+ }
+ };
+ }, [map]);
+
+ // Update rendered content when position changes
+ useEffect(() => {
+ if (rootRef.current) {
+ rootRef.current.render(
+ ,
+ );
+ }
+ }, [position]);
+
+ return null;
+};
+
const MapCleanup = () => {
const map = useMap();
@@ -86,9 +228,10 @@ const DEFAULT_CENTER_LAT_LNG = new LatLng(48.8584, 2.2945);
const LeafletMap = (props: MapProps) => {
const position = props.latlng || DEFAULT_CENTER_LAT_LNG;
return (
-
+
{}} />
+
);
diff --git a/web/src/components/memo-metadata/LocationDisplay.tsx b/web/src/components/memo-metadata/LocationDisplay.tsx
index 50bc7f98f..480d5f271 100644
--- a/web/src/components/memo-metadata/LocationDisplay.tsx
+++ b/web/src/components/memo-metadata/LocationDisplay.tsx
@@ -26,7 +26,7 @@ const LocationDisplay = ({ location, mode, onRemove, className }: LocationDispla
+
+ [{location.latitude.toFixed(2)}°, {location.longitude.toFixed(2)}°]
+
{displayText}
{onRemove && (