{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "polygon",
  "title": "Map Polygon",
  "description": "Geographic polygon with customizable fill and stroke styling.",
  "dependencies": ["mapbox-gl"],
  "devDependencies": ["@types/mapbox-gl"],
  "registryDependencies": ["https://www.terrae.dev/map.json"],
  "files": [
    {
      "path": "src/registry/map/polygon.tsx",
      "content": "\"use client\"\n\nimport type mapboxgl from \"mapbox-gl\"\n\nimport { useEffect, useId, useRef } from \"react\"\nimport { useMap } from \"./hooks\"\nimport type { MapPath } from \"./types\"\n\ntype MapPolygonProps = {\n  coordinates: MapPath\n  fillColor?: string\n  fillOpacity?: number\n  strokeColor?: string\n  strokeWidth?: number\n  strokeOpacity?: number\n  dashArray?: [number, number]\n}\n\nconst DEFAULT_FILL_COLOR = \"#3b82f6\"\nconst DEFAULT_FILL_OPACITY = 0.4\nconst DEFAULT_STROKE_COLOR = \"#3b82f6\"\nconst DEFAULT_STROKE_WIDTH = 2\nconst DEFAULT_STROKE_OPACITY = 1\n\nconst getClosedCoordinates = (coords: MapPath): MapPath => {\n  if (coords.length < 3) {\n    return coords\n  }\n\n  const first = coords[0]\n  const last = coords[coords.length - 1]\n\n  if (first[0] === last[0] && first[1] === last[1]) {\n    return coords\n  }\n\n  return [...coords, first]\n}\n\nexport const MapPolygon = ({\n  coordinates,\n  fillColor = DEFAULT_FILL_COLOR,\n  fillOpacity = DEFAULT_FILL_OPACITY,\n  strokeColor = DEFAULT_STROKE_COLOR,\n  strokeWidth = DEFAULT_STROKE_WIDTH,\n  strokeOpacity = DEFAULT_STROKE_OPACITY,\n  dashArray,\n}: MapPolygonProps) => {\n  const { map, isLoaded } = useMap()\n  const id = useId()\n  const sourceId = `polygon-source-${id}`\n  const fillLayerId = `polygon-fill-layer-${id}`\n  const strokeLayerId = `polygon-stroke-layer-${id}`\n  const initializedRef = useRef(false)\n\n  const coordinatesRef = useRef(coordinates)\n  const fillColorRef = useRef(fillColor)\n  const fillOpacityRef = useRef(fillOpacity)\n  const strokeColorRef = useRef(strokeColor)\n  const strokeWidthRef = useRef(strokeWidth)\n  const strokeOpacityRef = useRef(strokeOpacity)\n  const dashArrayRef = useRef(dashArray)\n\n  coordinatesRef.current = coordinates\n  fillColorRef.current = fillColor\n  fillOpacityRef.current = fillOpacity\n  strokeColorRef.current = strokeColor\n  strokeWidthRef.current = strokeWidth\n  strokeOpacityRef.current = strokeOpacity\n  dashArrayRef.current = dashArray\n\n  useEffect(() => {\n    if (!map) {\n      return\n    }\n\n    const addLayers = (mapInstance: mapboxgl.Map) => {\n      if (mapInstance.getSource(sourceId)) {\n        return\n      }\n\n      const closedCoordinates = getClosedCoordinates(coordinatesRef.current)\n\n      mapInstance.addSource(sourceId, {\n        type: \"geojson\",\n        data: {\n          type: \"Feature\",\n          properties: {},\n          geometry: {\n            type: \"Polygon\",\n            coordinates: [closedCoordinates],\n          },\n        },\n      })\n\n      const layers = mapInstance.getStyle().layers\n      let firstSymbolId: string | undefined\n      if (layers) {\n        for (const layer of layers) {\n          if (layer.type === \"symbol\") {\n            firstSymbolId = layer.id\n            break\n          }\n        }\n      }\n\n      mapInstance.addLayer(\n        {\n          id: fillLayerId,\n          type: \"fill\",\n          source: sourceId,\n          paint: {\n            \"fill-color\": fillColorRef.current,\n            \"fill-opacity\": fillOpacityRef.current,\n          },\n        },\n        firstSymbolId\n      )\n\n      mapInstance.addLayer(\n        {\n          id: strokeLayerId,\n          type: \"line\",\n          source: sourceId,\n          layout: {\n            \"line-join\": \"round\",\n            \"line-cap\": \"round\",\n          },\n          paint: {\n            \"line-color\": strokeColorRef.current,\n            \"line-width\": strokeWidthRef.current,\n            \"line-opacity\": strokeOpacityRef.current,\n            ...(dashArrayRef.current && { \"line-dasharray\": dashArrayRef.current }),\n          },\n        },\n        firstSymbolId\n      )\n\n      initializedRef.current = true\n    }\n\n    const cleanupLayers = (mapInstance: mapboxgl.Map) => {\n      try {\n        if (mapInstance.getLayer(strokeLayerId)) {\n          mapInstance.removeLayer(strokeLayerId)\n        }\n        if (mapInstance.getLayer(fillLayerId)) {\n          mapInstance.removeLayer(fillLayerId)\n        }\n        if (mapInstance.getSource(sourceId)) {\n          mapInstance.removeSource(sourceId)\n        }\n      } catch {\n        // Layers may already be removed\n      }\n      initializedRef.current = false\n    }\n\n    const handleStyleLoad = () => {\n      initializedRef.current = false\n      addLayers(map)\n    }\n\n    if (isLoaded && !initializedRef.current) {\n      addLayers(map)\n    }\n\n    map.on(\"style.load\", handleStyleLoad)\n\n    return () => {\n      map.off(\"style.load\", handleStyleLoad)\n      cleanupLayers(map)\n    }\n  }, [map, isLoaded, sourceId, fillLayerId, strokeLayerId])\n\n  useEffect(() => {\n    if (!map || !initializedRef.current) {\n      return\n    }\n\n    try {\n      const source = map.getSource(sourceId) as mapboxgl.GeoJSONSource\n      if (source) {\n        const closedCoordinates = getClosedCoordinates(coordinates)\n        source.setData({\n          type: \"Feature\",\n          properties: {},\n          geometry: {\n            type: \"Polygon\",\n            coordinates: [closedCoordinates],\n          },\n        })\n      }\n    } catch {\n      // Source may not exist during style transition\n    }\n  }, [map, sourceId, coordinates])\n\n  useEffect(() => {\n    if (!map || !initializedRef.current) {\n      return\n    }\n\n    try {\n      if (map.getLayer(fillLayerId)) {\n        map.setPaintProperty(fillLayerId, \"fill-color\", fillColor)\n        map.setPaintProperty(fillLayerId, \"fill-opacity\", fillOpacity)\n      }\n    } catch {\n      // Layer may not exist during style transition\n    }\n  }, [map, fillLayerId, fillColor, fillOpacity])\n\n  useEffect(() => {\n    if (!map || !initializedRef.current) {\n      return\n    }\n\n    try {\n      if (map.getLayer(strokeLayerId)) {\n        map.setPaintProperty(strokeLayerId, \"line-color\", strokeColor)\n        map.setPaintProperty(strokeLayerId, \"line-width\", strokeWidth)\n        map.setPaintProperty(strokeLayerId, \"line-opacity\", strokeOpacity)\n        if (dashArray) {\n          map.setPaintProperty(strokeLayerId, \"line-dasharray\", dashArray)\n        }\n      }\n    } catch {\n      // Layer may not exist during style transition\n    }\n  }, [map, strokeLayerId, strokeColor, strokeWidth, strokeOpacity, dashArray])\n\n  return null\n}\n",
      "type": "registry:ui",
      "target": "components/ui/map/polygon.tsx"
    }
  ],
  "type": "registry:ui"
}
