HabitatMap/AirCasting

View on GitHub
app/javascript/react/components/Map/Markers/MapOverlay.tsx

Summary

Maintainability
A
0 mins
Test Coverage
import { useMap } from "@vis.gl/react-google-maps";
import React, { useEffect, useRef } from "react";
import ReactDOM from "react-dom";

interface MapOverlayProps {
  position: google.maps.LatLngLiteral;
  children: React.ReactNode;
}

const MapOverlay: React.FC<MapOverlayProps> = ({ position, children }) => {
  const map = useMap();
  const overlayRef = useRef<google.maps.OverlayView | null>(null);
  const containerElementRef = useRef<HTMLDivElement | null>(null);

  useEffect(() => {
    if (!map) return;

    class CustomOverlay extends google.maps.OverlayView {
      div: HTMLDivElement | null = null;

      onAdd() {
        this.div = document.createElement("div");
        this.div.style.position = "absolute";
        containerElementRef.current = this.div;
        const panes = this.getPanes();
        panes?.overlayMouseTarget.appendChild(this.div);
      }

      draw() {
        if (!this.div) return;

        const overlayProjection = this.getProjection();
        const point = overlayProjection.fromLatLngToDivPixel(
          new google.maps.LatLng(position)
        );

        if (point) {
          this.div.style.left = `${point.x}px`;
          this.div.style.top = `${point.y}px`;
          this.div.style.transform = "translate(-50%, -100%)";
        }
      }

      onRemove() {
        if (this.div) {
          this.div.parentNode?.removeChild(this.div);
          this.div = null;
          containerElementRef.current = null;
        }
      }
    }

    overlayRef.current = new CustomOverlay();
    overlayRef.current.setMap(map);

    return () => {
      overlayRef.current?.setMap(null);
      overlayRef.current = null;
    };
  }, [map, position]);

  return containerElementRef.current
    ? ReactDOM.createPortal(children, containerElementRef.current)
    : null;
};

export default MapOverlay;