{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "map",
  "title": "Map",
  "description": "Core map component with Mapbox GL and MapLibre GL support.",
  "dependencies": ["mapbox-gl", "next-themes", "lucide-react"],
  "devDependencies": ["@types/mapbox-gl"],
  "registryDependencies": [],
  "files": [
    {
      "path": "src/registry/map/map.tsx",
      "content": "\"use client\"\n\nimport { useTheme } from \"next-themes\"\nimport { createContext, useEffect, useRef, useState, type ReactNode } from \"react\"\nimport type mapboxgl from \"mapbox-gl\"\nimport { mapgl, detectedLibrary } from \"./map-library\"\nimport { Globe } from \"lucide-react\"\nimport {\n  defaultMapStyles,\n  defaultMapLibreStyles,\n  type MapContextValue,\n  type MapThemeStyles,\n  type MapProjection,\n  type MapCoordinates,\n  type MapBounds,\n} from \"./types\"\n\nexport const MapContext = createContext<MapContextValue | null>(null)\n\nconst DEFAULT_CENTER: MapCoordinates = [0, 0]\nconst DEFAULT_ZOOM = 2\nconst DEFAULT_BEARING = 0\nconst DEFAULT_PITCH = 0\nconst DEFAULT_ROTATE_SPEED = 3\n\ntype MapProps = {\n  accessToken?: string\n  children?: ReactNode\n  loader?: ReactNode\n  // Forces loader to show when true, hides when false, auto when undefined\n  showLoader?: boolean\n  // Overrides theme-based styles when set\n  style?: string\n  styles?: MapThemeStyles\n  center?: MapCoordinates\n  zoom?: number\n  bearing?: number\n  pitch?: number\n  projection?: MapProjection\n  minZoom?: number\n  maxZoom?: number\n  maxBounds?: MapBounds\n  // Auto-rotate the globe (only works with projection=\"globe\")\n  autoRotate?: boolean\n  // Rotation speed in degrees per second\n  rotateSpeed?: number\n}\n\nexport const Map = ({\n  accessToken,\n  children,\n  loader,\n  showLoader,\n  style,\n  styles,\n  center = DEFAULT_CENTER,\n  zoom = DEFAULT_ZOOM,\n  bearing = DEFAULT_BEARING,\n  pitch = DEFAULT_PITCH,\n  projection,\n  minZoom,\n  maxZoom,\n  maxBounds,\n  autoRotate,\n  rotateSpeed = DEFAULT_ROTATE_SPEED,\n}: MapProps) => {\n  const containerRef = useRef<HTMLDivElement>(null)\n  const mapRef = useRef<mapboxgl.Map | null>(null)\n  const [isLoaded, setIsLoaded] = useState(false)\n  const [error, setError] = useState<string | null>(null)\n  const { resolvedTheme } = useTheme()\n  const initializedRef = useRef(false)\n\n  const shouldShowLoader = showLoader ?? !isLoaded\n\n  const getMapStyle = () => {\n    if (style) {\n      return style\n    }\n    const defaults = detectedLibrary === \"maplibre\" ? defaultMapLibreStyles : defaultMapStyles\n    const darkStyle = styles?.dark ?? defaults.dark\n    const lightStyle = styles?.light ?? defaults.light\n\n    return resolvedTheme === \"dark\" ? darkStyle : lightStyle\n  }\n\n  const createMapInstance = (container: HTMLDivElement) => {\n    return new mapgl.Map({\n      container,\n      style: getMapStyle(),\n      center,\n      zoom,\n      bearing,\n      pitch,\n      projection,\n      minZoom,\n      maxZoom,\n      maxBounds,\n      attributionControl: false,\n    })\n  }\n\n  const isStandardStyle = (styleUrl: string) => {\n    return styleUrl.includes(\"mapbox://styles/mapbox/standard\")\n  }\n\n  const updateStandardLightPreset = (mapInstance: mapboxgl.Map) => {\n    if (detectedLibrary !== \"mapbox\") {\n      return\n    }\n    const currentStyle = getMapStyle()\n    if (isStandardStyle(currentStyle)) {\n      const lightPreset = resolvedTheme === \"dark\" ? \"night\" : \"day\"\n      mapInstance.setConfigProperty(\"basemap\", \"lightPreset\", lightPreset)\n    }\n  }\n\n  const handleMapLoad = () => {\n    setIsLoaded(true)\n    if (mapRef.current) {\n      updateStandardLightPreset(mapRef.current)\n    }\n  }\n\n  const handleMapError = (e: mapboxgl.ErrorEvent) => {\n    console.error(\"Map error:\", e.error)\n    setError(\"Failed to load map\")\n  }\n\n  const cleanupMap = (mapInstance: mapboxgl.Map) => {\n    mapInstance.remove()\n    mapRef.current = null\n    setIsLoaded(false)\n    initializedRef.current = false\n  }\n\n  useEffect(() => {\n    if (initializedRef.current) {\n      return\n    }\n    if (!containerRef.current) {\n      return\n    }\n    if (detectedLibrary === \"mapbox\" && !accessToken) {\n      setError(\n        \"Mapbox access token is required. Add NEXT_PUBLIC_MAPBOX_ACCESS_TOKEN to your .env.local file and restart the dev server.\"\n      )\n      return\n    }\n\n    initializedRef.current = true\n\n    if (accessToken) {\n      mapgl.accessToken = accessToken\n    }\n\n    try {\n      const container = containerRef.current\n      const originalGetBoundingClientRect = container.getBoundingClientRect.bind(container)\n      container.getBoundingClientRect = () => {\n        const rect = originalGetBoundingClientRect()\n        const width = container.offsetWidth\n        const height = container.offsetHeight\n        return { ...rect, width, height, right: rect.left + width, bottom: rect.top + height }\n      }\n\n      const mapInstance = createMapInstance(container)\n      mapInstance.on(\"load\", handleMapLoad)\n      mapInstance.on(\"error\", handleMapError)\n      mapRef.current = mapInstance\n\n      return () => {\n        delete (container as unknown as Record<string, unknown>).getBoundingClientRect\n        cleanupMap(mapInstance)\n      }\n    } catch (err) {\n      console.error(\"Error creating map:\", err)\n      setError(\"Failed to create map\")\n      initializedRef.current = false\n    }\n  }, [])\n\n  useEffect(() => {\n    if (!mapRef.current || !isLoaded) {\n      return\n    }\n\n    const currentStyle = getMapStyle()\n    if (isStandardStyle(currentStyle)) {\n      updateStandardLightPreset(mapRef.current)\n    } else {\n      mapRef.current.setStyle(currentStyle)\n    }\n  }, [style, styles, resolvedTheme])\n\n  useEffect(() => {\n    if (!mapRef.current || !isLoaded) {\n      return\n    }\n\n    mapRef.current.setCenter(center)\n  }, [center])\n\n  useEffect(() => {\n    if (!mapRef.current || !isLoaded) {\n      return\n    }\n\n    mapRef.current.setZoom(zoom)\n  }, [zoom])\n\n  useEffect(() => {\n    if (!mapRef.current || !isLoaded) {\n      return\n    }\n\n    mapRef.current.setBearing(bearing)\n  }, [bearing])\n\n  useEffect(() => {\n    if (!mapRef.current || !isLoaded) {\n      return\n    }\n\n    mapRef.current.setPitch(pitch)\n  }, [pitch])\n\n  useEffect(() => {\n    if (!mapRef.current || !isLoaded || !projection) {\n      return\n    }\n\n    mapRef.current.setProjection(projection)\n  }, [projection])\n\n  useEffect(() => {\n    if (!containerRef.current || !mapRef.current || !isLoaded) {\n      return\n    }\n\n    const container = containerRef.current\n    const observer = new ResizeObserver(() => {\n      mapRef.current?.resize()\n    })\n    observer.observe(container)\n\n    return () => {\n      observer.disconnect()\n    }\n  }, [isLoaded])\n\n  useEffect(() => {\n    if (!mapRef.current || !isLoaded || !autoRotate || projection !== \"globe\") {\n      return\n    }\n\n    let animationId: number\n    let lastTime = performance.now()\n\n    const rotate = (currentTime: number) => {\n      if (!mapRef.current) {\n        return\n      }\n\n      const delta = (currentTime - lastTime) / 1000\n      lastTime = currentTime\n\n      const currentCenter = mapRef.current.getCenter()\n      const newLng = currentCenter.lng + rotateSpeed * delta\n\n      mapRef.current.setCenter([newLng, currentCenter.lat])\n\n      animationId = requestAnimationFrame(rotate)\n    }\n\n    animationId = requestAnimationFrame(rotate)\n\n    return () => {\n      cancelAnimationFrame(animationId)\n    }\n  }, [isLoaded, autoRotate, projection, rotateSpeed])\n\n  const contextValue: MapContextValue = {\n    map: mapRef.current,\n    isLoaded,\n    library: detectedLibrary,\n  }\n\n  if (error) {\n    return (\n      <div className=\"relative w-full h-full\">\n        <div className=\"absolute inset-0 flex items-center justify-center\">\n          <div className=\"text-destructive text-sm\">{error}</div>\n        </div>\n      </div>\n    )\n  }\n\n  return (\n    <MapContext.Provider value={contextValue}>\n      <div ref={containerRef} className=\"relative w-full h-full\">\n        {shouldShowLoader && (loader || <DefaultLoader />)}\n        {mapRef.current && children}\n      </div>\n    </MapContext.Provider>\n  )\n}\n\nconst DefaultLoader = () => {\n  return (\n    <div className=\"absolute inset-0 z-10 flex items-center justify-center bg-muted\">\n      <Globe className=\"size-8 text-muted-foreground/60 animate-spin\" />\n    </div>\n  )\n}\n",
      "type": "registry:ui",
      "target": "components/ui/map/map.tsx"
    },
    {
      "path": "src/registry/map/types.ts",
      "content": "import { type RefObject } from \"react\"\nimport type { Map, Marker, ProjectionSpecification } from \"mapbox-gl\"\nimport type { MapLibraryName } from \"./map-library\"\n\nexport type { MapLibraryName }\n\nexport type MapCoordinates = [longitude: number, latitude: number]\nexport type MapBounds = [southwest: MapCoordinates, northeast: MapCoordinates]\nexport type MapPath = MapCoordinates[]\nexport type MapImageCorners = [\n  topLeft: MapCoordinates,\n  topRight: MapCoordinates,\n  bottomRight: MapCoordinates,\n  bottomLeft: MapCoordinates,\n]\nexport type MapProjection = ProjectionSpecification[\"name\"]\n\nexport type LngLatCoordinates = {\n  lng: number\n  lat: number\n}\n\nexport type MapThemeStyles = {\n  light?: string\n  dark?: string\n}\n\nexport const defaultMapStyles: Required<MapThemeStyles> = {\n  light: \"mapbox://styles/mapbox/light-v11\",\n  dark: \"mapbox://styles/mapbox/dark-v11\",\n}\n\nexport const standardMapStyles: Required<MapThemeStyles> = {\n  light: \"mapbox://styles/mapbox/standard\",\n  dark: \"mapbox://styles/mapbox/standard\",\n}\n\nexport const streetsMapStyles: Required<MapThemeStyles> = {\n  light: \"mapbox://styles/mapbox/streets-v12\",\n  dark: \"mapbox://styles/mapbox/dark-v11\",\n}\n\nexport const outdoorsMapStyles: Required<MapThemeStyles> = {\n  light: \"mapbox://styles/mapbox/outdoors-v12\",\n  dark: \"mapbox://styles/mapbox/dark-v11\",\n}\n\nexport const satelliteMapStyles: Required<MapThemeStyles> = {\n  light: \"mapbox://styles/mapbox/satellite-streets-v12\",\n  dark: \"mapbox://styles/mapbox/satellite-streets-v12\",\n}\n\nexport const navigationMapStyles: Required<MapThemeStyles> = {\n  light: \"mapbox://styles/mapbox/navigation-day-v1\",\n  dark: \"mapbox://styles/mapbox/navigation-night-v1\",\n}\n\nexport const defaultMapLibreStyles: Required<MapThemeStyles> = {\n  light: \"https://basemaps.cartocdn.com/gl/positron-gl-style/style.json\",\n  dark: \"https://basemaps.cartocdn.com/gl/dark-matter-gl-style/style.json\",\n}\n\nexport type MapCompareOrientation = \"horizontal\" | \"vertical\"\n\nexport type MapSyncLayout = \"horizontal\" | \"vertical\" | \"grid\"\n\nexport type MapContextValue = {\n  map: Map | null\n  isLoaded: boolean\n  library: MapLibraryName\n}\n\nexport type MarkerContextValue = {\n  markerRef: RefObject<Marker | null>\n  markerElementRef: RefObject<HTMLDivElement | null>\n  map: Map | null\n  isReady: boolean\n}\n",
      "type": "registry:ui",
      "target": "components/ui/map/types.ts"
    },
    {
      "path": "src/registry/map/hooks.ts",
      "content": "import { useContext } from \"react\"\nimport { MapContext } from \"./map\"\n\nexport const useMap = () => {\n  const context = useContext(MapContext)\n  if (!context) {\n    return { map: null, isLoaded: false, library: \"mapbox\" as const }\n  }\n  return context\n}\n",
      "type": "registry:ui",
      "target": "components/ui/map/hooks.ts"
    },
    {
      "path": "src/registry/map/map-library.ts",
      "content": "import mapboxgl from \"mapbox-gl\"\nimport \"mapbox-gl/dist/mapbox-gl.css\"\n\nexport type MapLibraryName = \"mapbox\" | \"maplibre\"\n\n// Change these two lines to switch between Mapbox GL and MapLibre GL:\n//   import mapboxgl from \"maplibre-gl\"\n//   import \"maplibre-gl/dist/maplibre-gl.css\"\n//   const detectedLibrary: MapLibraryName = \"maplibre\"\nconst detectedLibrary: MapLibraryName = \"mapbox\"\n\nconst mapgl = mapboxgl\n\nexport { mapgl, detectedLibrary }\n",
      "type": "registry:ui",
      "target": "components/ui/map/map-library.ts"
    }
  ],
  "css": {
    "@layer base": {
      ".mapboxgl-popup-content, .maplibregl-popup-content": {
        "@apply bg-transparent! shadow-none! p-0! rounded-none!": {}
      },
      ".mapboxgl-popup-tip, .maplibregl-popup-tip": {
        "@apply hidden!": {}
      }
    }
  },
  "type": "registry:ui"
}
