{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "mini-map",
  "title": "Map Mini Map",
  "description": "Minimap overview navigation component.",
  "dependencies": ["mapbox-gl"],
  "devDependencies": ["@types/mapbox-gl"],
  "registryDependencies": ["https://www.terrae.dev/map.json"],
  "files": [
    {
      "path": "src/registry/map/mini-map.tsx",
      "content": "\"use client\"\n\nimport { useEffect, useRef, useState, useCallback } from \"react\"\nimport { useTheme } from \"next-themes\"\nimport type mapboxgl from \"mapbox-gl\"\nimport { mapgl } from \"./map-library\"\nimport { useMap } from \"./hooks\"\nimport { cn } from \"@/lib/utils\"\nimport { defaultMapStyles, defaultMapLibreStyles, type MapThemeStyles } from \"./types\"\n\ntype MapMiniMapProps = {\n  position?: \"top-left\" | \"top-right\" | \"bottom-left\" | \"bottom-right\"\n  width?: number\n  height?: number\n  zoomOffset?: number\n  style?: string\n  styles?: MapThemeStyles\n  boxColor?: string\n  boxBorderWidth?: number\n  rounded?: number | \"full\" | \"none\"\n  draggable?: boolean\n}\n\nconst DEFAULT_BORDER_RADIUS = 8\n\ntype Position = { x: number; y: number }\n\nexport function MapMiniMap({\n  position = \"bottom-right\",\n  width = 200,\n  height = 150,\n  zoomOffset = -4,\n  style,\n  styles,\n  boxColor = \"#3b82f6\",\n  boxBorderWidth = 2,\n  rounded = DEFAULT_BORDER_RADIUS,\n  draggable = false,\n}: MapMiniMapProps) {\n  const { map: mainMap, isLoaded, library } = useMap()\n  const { resolvedTheme } = useTheme()\n  const containerRef = useRef<HTMLDivElement>(null)\n  const wrapperRef = useRef<HTMLDivElement>(null)\n  const miniMapRef = useRef<mapboxgl.Map | null>(null)\n  const [miniMapLoaded, setMiniMapLoaded] = useState(false)\n  const [dragPosition, setDragPosition] = useState<Position | null>(null)\n  const isDraggingRef = useRef(false)\n  const dragStartRef = useRef<Position>({ x: 0, y: 0 })\n  const positionStartRef = useRef<Position>({ x: 0, y: 0 })\n\n  const positionClasses = {\n    \"top-left\": \"top-4 left-4\",\n    \"top-right\": \"top-4 right-4\",\n    \"bottom-left\": \"bottom-4 left-4\",\n    \"bottom-right\": \"bottom-4 right-4\",\n  }\n\n  const getMapStyle = () => {\n    if (style) {\n      return style\n    }\n    const defaults = library === \"maplibre\" ? defaultMapLibreStyles : defaultMapStyles\n    if (styles) {\n      const darkStyle = styles.dark ?? defaults.dark\n      const lightStyle = styles.light ?? defaults.light\n      return resolvedTheme === \"dark\" ? darkStyle : lightStyle\n    }\n    return resolvedTheme === \"dark\" ? defaults.dark : defaults.light\n  }\n\n  const addViewportBox = (miniMap: mapboxgl.Map) => {\n    miniMap.addSource(\"viewport-box\", {\n      type: \"geojson\",\n      data: {\n        type: \"Feature\",\n        properties: {},\n        geometry: { type: \"Polygon\", coordinates: [[]] },\n      },\n    })\n\n    miniMap.addLayer({\n      id: \"viewport-box-fill\",\n      type: \"fill\",\n      source: \"viewport-box\",\n      paint: { \"fill-color\": boxColor, \"fill-opacity\": 0.1 },\n    })\n\n    miniMap.addLayer({\n      id: \"viewport-box-outline\",\n      type: \"line\",\n      source: \"viewport-box\",\n      paint: { \"line-color\": boxColor, \"line-width\": boxBorderWidth },\n    })\n  }\n\n  const handleMouseDown = useCallback(\n    (e: React.MouseEvent) => {\n      if (!draggable || !wrapperRef.current) {\n        return\n      }\n      e.preventDefault()\n      isDraggingRef.current = true\n      dragStartRef.current = { x: e.clientX, y: e.clientY }\n\n      const rect = wrapperRef.current.getBoundingClientRect()\n      const parentRect = wrapperRef.current.parentElement?.getBoundingClientRect()\n      if (parentRect) {\n        positionStartRef.current = { x: rect.left - parentRect.left, y: rect.top - parentRect.top }\n      }\n    },\n    [draggable]\n  )\n\n  const handleMouseMove = useCallback(\n    (e: MouseEvent) => {\n      if (!isDraggingRef.current || !wrapperRef.current) {\n        return\n      }\n\n      const deltaX = e.clientX - dragStartRef.current.x\n      const deltaY = e.clientY - dragStartRef.current.y\n\n      const parentRect = wrapperRef.current.parentElement?.getBoundingClientRect()\n      if (!parentRect) {\n        return\n      }\n\n      const newX = Math.max(0, Math.min(positionStartRef.current.x + deltaX, parentRect.width - width))\n      const newY = Math.max(0, Math.min(positionStartRef.current.y + deltaY, parentRect.height - height))\n\n      setDragPosition({ x: newX, y: newY })\n    },\n    [width, height]\n  )\n\n  const handleMouseUp = useCallback(() => {\n    isDraggingRef.current = false\n  }, [])\n\n  useEffect(() => {\n    if (!draggable) {\n      return\n    }\n\n    document.addEventListener(\"mousemove\", handleMouseMove)\n    document.addEventListener(\"mouseup\", handleMouseUp)\n\n    return () => {\n      document.removeEventListener(\"mousemove\", handleMouseMove)\n      document.removeEventListener(\"mouseup\", handleMouseUp)\n    }\n  }, [draggable, handleMouseMove, handleMouseUp])\n\n  useEffect(() => {\n    if (!mainMap || !isLoaded || !containerRef.current) return\n\n    const miniMap = new mapgl.Map({\n      container: containerRef.current,\n      style: getMapStyle(),\n      center: mainMap.getCenter(),\n      zoom: Math.max(0, mainMap.getZoom() + zoomOffset),\n      interactive: false,\n      attributionControl: false,\n      logoPosition: \"bottom-left\",\n    })\n\n    miniMap.on(\"load\", () => {\n      addViewportBox(miniMap)\n      setMiniMapLoaded(true)\n    })\n\n    miniMapRef.current = miniMap\n\n    return () => {\n      miniMap.remove()\n      miniMapRef.current = null\n      setMiniMapLoaded(false)\n    }\n  }, [mainMap, isLoaded, style, zoomOffset, boxColor, boxBorderWidth])\n\n  useEffect(() => {\n    if (!miniMapRef.current || !miniMapLoaded || style) {\n      return\n    }\n\n    const miniMap = miniMapRef.current\n\n    const handleStyleLoad = () => {\n      addViewportBox(miniMap)\n    }\n\n    miniMap.once(\"style.load\", handleStyleLoad)\n    miniMap.setStyle(getMapStyle())\n\n    return () => {\n      miniMap.off(\"style.load\", handleStyleLoad)\n    }\n  }, [resolvedTheme, styles, miniMapLoaded, style])\n\n  useEffect(() => {\n    if (!mainMap || !miniMapRef.current || !miniMapLoaded) return\n\n    const updateMiniMap = () => {\n      const miniMap = miniMapRef.current\n      if (!miniMap) return\n\n      miniMap.setCenter(mainMap.getCenter())\n      miniMap.setZoom(Math.max(0, mainMap.getZoom() + zoomOffset))\n\n      const bounds = mainMap.getBounds()\n      if (!bounds) return\n\n      const nw = bounds.getNorthWest()\n      const ne = bounds.getNorthEast()\n      const se = bounds.getSouthEast()\n      const sw = bounds.getSouthWest()\n\n      const boxSource = miniMap.getSource(\"viewport-box\") as mapboxgl.GeoJSONSource\n      boxSource?.setData({\n        type: \"Feature\",\n        properties: {},\n        geometry: {\n          type: \"Polygon\",\n          coordinates: [\n            [\n              [nw.lng, nw.lat],\n              [ne.lng, ne.lat],\n              [se.lng, se.lat],\n              [sw.lng, sw.lat],\n              [nw.lng, nw.lat],\n            ],\n          ],\n        },\n      })\n    }\n\n    mainMap.on(\"move\", updateMiniMap)\n    mainMap.on(\"zoom\", updateMiniMap)\n    updateMiniMap()\n\n    return () => {\n      mainMap.off(\"move\", updateMiniMap)\n      mainMap.off(\"zoom\", updateMiniMap)\n    }\n  }, [mainMap, miniMapLoaded, zoomOffset])\n\n  const getBorderRadius = () => {\n    if (rounded === \"full\") {\n      return \"9999px\"\n    }\n    if (rounded === \"none\") {\n      return \"0px\"\n    }\n    return `${rounded}px`\n  }\n\n  const wrapperStyle = draggable && dragPosition ? { left: dragPosition.x, top: dragPosition.y } : undefined\n\n  return (\n    <div\n      ref={wrapperRef}\n      className={cn(\"absolute z-10\", !draggable || !dragPosition ? positionClasses[position] : \"\")}\n      style={wrapperStyle}\n      onMouseDown={handleMouseDown}\n    >\n      <div\n        ref={containerRef}\n        className=\"overflow-hidden border-2 border-border shadow-lg\"\n        style={{\n          width,\n          height,\n          borderRadius: getBorderRadius(),\n          cursor: draggable ? \"grab\" : \"default\",\n        }}\n      />\n    </div>\n  )\n}\n",
      "type": "registry:ui",
      "target": "components/ui/map/mini-map.tsx"
    }
  ],
  "type": "registry:ui"
}
