{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "data-maps",
  "title": "Data Maps",
  "author": "AlgorizeTech",
  "description": "Ask the user to pick one of 2 data maps variants: https://avaraui.com/view/data-maps. This item holds 2 of them as looks of one component (fill, dots). Skip the question if the user already named one. A choropleth world map colouring countries by metric, with fill and dot variants. Use it for geographic analytics and regional breakdowns.",
  "dependencies": [
    "geojson",
    "leaflet",
    "react-leaflet"
  ],
  "devDependencies": [
    "@types/leaflet"
  ],
  "registryDependencies": [
    "utils"
  ],
  "files": [
    {
      "path": "src/components/custom/blocks/DataMapsBlock.tsx",
      "content": "\"use client\";\n\nimport React, {\n  useEffect,\n  useMemo,\n  useRef,\n  useState,\n  useSyncExternalStore,\n} from \"react\";\nimport { cn } from \"@/lib/utils\";\nimport \"leaflet/dist/leaflet.css\";\nimport { useMap } from \"react-leaflet\";\n\n/**\n * Leaflet reads `window` at import time, so it must never load on a server.\n *\n * The map components are pulled in at runtime and held in state, NOT as React.lazy\n * components. Lazy children (GeoJSON, ZoomControl) suspend *after* MapContainer has\n * mounted, and the resulting unmount/remount tears down the leaflet instance mid-flight\n * -- \"Map container is being reused by another instance\". One import, one gate, one mount.\n *\n * Held in state rather than next/dynamic because this component ships to Vite apps too,\n * where next/dynamic does not resolve.\n */\ntype ReactLeaflet = typeof import(\"react-leaflet\");\ntype Leaflet = typeof import(\"leaflet\");\n\n/** A country polygon, carrying the GeoJSON feature it was built from. */\ntype FeatureLayer = import(\"leaflet\").Polygon & {\n  feature?: { id?: string | number };\n};\n\nfunction ResizeHandler({ wrapperRef, initialZoom, zoomable }: { wrapperRef: React.RefObject<HTMLDivElement | null>; initialZoom: number; zoomable?: boolean }) {\n  const map = useMap();\n\n  useEffect(() => {\n    if (zoomable) return;\n    if (!wrapperRef.current) return;\n\n    const handleResize = () => {\n      const el = wrapperRef.current;\n      if (!el) return;\n      const width = el.clientWidth;\n      let targetZoom: number;\n      if (width < 450) {\n        targetZoom = 0;\n      } else if (width < 640) {\n        targetZoom = 1;\n      } else {\n        targetZoom = initialZoom;\n      }\n      map.invalidateSize();\n      map.setZoom(targetZoom);\n    };\n\n    handleResize();\n    const observer = new ResizeObserver(handleResize);\n    observer.observe(wrapperRef.current);\n    return () => observer.disconnect();\n  }, [map, wrapperRef, initialZoom, zoomable]);\n  return null;\n}\n\nexport interface DataPoint {\n  id: string; // ISO 3166-1 alpha-3 country code\n  name: string; // Name to display on hover\n  active?: number; // Active count\n  activeTrend?: \"up\" | \"down\" | (string & {});\n  new?: number; // New count\n  newTrend?: \"up\" | \"down\" | (string & {});\n  color?: string; // Optional custom fill color\n  flag?: string; // Optional flag emoji\n}\n\nexport interface DataMapsProps extends React.HTMLAttributes<HTMLDivElement> {\n  dataPoints: DataPoint[];\n  variant?: \"fill\" | \"dots\";\n  center?: [number, number];\n  zoom?: number;\n  minZoom?: number;\n  maxZoom?: number;\n  zoomable?: boolean;\n  scrollWheelZoom?: boolean;\n  highlightColor?: string;\n  defaultColor?: string;\n  mapGeoJsonUrl?: string;\n  mapId?: string;\n}\n\nconst alpha3ToAlpha2: Record<string, string> = {\"AFG\":\"AF\",\"ALA\":\"AX\",\"ALB\":\"AL\",\"DZA\":\"DZ\",\"ASM\":\"AS\",\"AND\":\"AD\",\"AGO\":\"AO\",\"AIA\":\"AI\",\"ATA\":\"AQ\",\"ATG\":\"AG\",\"ARG\":\"AR\",\"ARM\":\"AM\",\"ABW\":\"AW\",\"AUS\":\"AU\",\"AUT\":\"AT\",\"AZE\":\"AZ\",\"BHS\":\"BS\",\"BHR\":\"BH\",\"BGD\":\"BD\",\"BRB\":\"BB\",\"BLR\":\"BY\",\"BEL\":\"BE\",\"BLZ\":\"BZ\",\"BEN\":\"BJ\",\"BMU\":\"BM\",\"BTN\":\"BT\",\"BOL\":\"BO\",\"BES\":\"BQ\",\"BIH\":\"BA\",\"BWA\":\"BW\",\"BVT\":\"BV\",\"BRA\":\"BR\",\"IOT\":\"IO\",\"BRN\":\"BN\",\"BGR\":\"BG\",\"BFA\":\"BF\",\"BDI\":\"BI\",\"CPV\":\"CV\",\"KHM\":\"KH\",\"CMR\":\"CM\",\"CAN\":\"CA\",\"CYM\":\"KY\",\"CAF\":\"CF\",\"TCD\":\"TD\",\"CHL\":\"CL\",\"CHN\":\"CN\",\"CXR\":\"CX\",\"CCK\":\"CC\",\"COL\":\"CO\",\"COM\":\"KM\",\"COG\":\"CG\",\"COD\":\"CD\",\"COK\":\"CK\",\"CRI\":\"CR\",\"CIV\":\"CI\",\"HRV\":\"HR\",\"CUB\":\"CU\",\"CUW\":\"CW\",\"CYP\":\"CY\",\"CZE\":\"CZ\",\"DNK\":\"DK\",\"DJI\":\"DJ\",\"DMA\":\"DM\",\"DOM\":\"DO\",\"ECU\":\"EC\",\"EGY\":\"EG\",\"SLV\":\"SV\",\"GNQ\":\"GQ\",\"ERI\":\"ER\",\"EST\":\"EE\",\"SWZ\":\"SZ\",\"ETH\":\"ET\",\"FLK\":\"FK\",\"FRO\":\"FO\",\"FJI\":\"FJ\",\"FIN\":\"FI\",\"FRA\":\"FR\",\"GUF\":\"GF\",\"PYF\":\"PF\",\"ATF\":\"TF\",\"GAB\":\"GA\",\"GMB\":\"GM\",\"GEO\":\"GE\",\"DEU\":\"DE\",\"GHA\":\"GH\",\"GIB\":\"GI\",\"GRC\":\"GR\",\"GRL\":\"GL\",\"GRD\":\"GD\",\"GLP\":\"GP\",\"GUM\":\"GU\",\"GTM\":\"GT\",\"GGY\":\"GG\",\"GIN\":\"GN\",\"GNB\":\"GW\",\"GUY\":\"GY\",\"HTI\":\"HT\",\"HMD\":\"HM\",\"VAT\":\"VA\",\"HND\":\"HN\",\"HKG\":\"HK\",\"HUN\":\"HU\",\"ISL\":\"IS\",\"IND\":\"IN\",\"IDN\":\"ID\",\"IRN\":\"IR\",\"IRQ\":\"IQ\",\"IRL\":\"IE\",\"IMN\":\"IM\",\"ISR\":\"IL\",\"ITA\":\"IT\",\"JAM\":\"JM\",\"JPN\":\"JP\",\"JEY\":\"JE\",\"JOR\":\"JO\",\"KAZ\":\"KZ\",\"KEN\":\"KE\",\"KIR\":\"KI\",\"PRK\":\"KP\",\"KOR\":\"KR\",\"KWT\":\"KW\",\"KGZ\":\"KG\",\"LAO\":\"LA\",\"LVA\":\"LV\",\"LBN\":\"LB\",\"LSO\":\"LS\",\"LBR\":\"LR\",\"LBY\":\"LY\",\"LIE\":\"LI\",\"LTU\":\"LT\",\"LUX\":\"LU\",\"MAC\":\"MO\",\"MDG\":\"MG\",\"MWI\":\"MW\",\"MYS\":\"MY\",\"MDV\":\"MV\",\"MLI\":\"ML\",\"MLT\":\"MT\",\"MHL\":\"MH\",\"MTQ\":\"MQ\",\"MRT\":\"MR\",\"MUS\":\"MU\",\"MYT\":\"YT\",\"MEX\":\"MX\",\"FSM\":\"FM\",\"MDA\":\"MD\",\"MCO\":\"MC\",\"MNG\":\"MN\",\"MNE\":\"ME\",\"MSR\":\"MS\",\"MAR\":\"MA\",\"MOZ\":\"MZ\",\"MMR\":\"MM\",\"NAM\":\"NA\",\"NRU\":\"NR\",\"NPL\":\"NP\",\"NLD\":\"NL\",\"NCL\":\"NC\",\"NZL\":\"NZ\",\"NIC\":\"NI\",\"NER\":\"NE\",\"NGA\":\"NG\",\"NIU\":\"NU\",\"NFK\":\"NF\",\"MKD\":\"MK\",\"MNP\":\"MP\",\"NOR\":\"NO\",\"OMN\":\"OM\",\"PAK\":\"PK\",\"PLW\":\"PW\",\"PSE\":\"PS\",\"PAN\":\"PA\",\"PNG\":\"PG\",\"PRY\":\"PY\",\"PER\":\"PE\",\"PHL\":\"PH\",\"PCN\":\"PN\",\"POL\":\"PL\",\"PRT\":\"PT\",\"PRI\":\"PR\",\"QAT\":\"QA\",\"REU\":\"RE\",\"ROU\":\"RO\",\"RUS\":\"RU\",\"RWA\":\"RW\",\"BLM\":\"BL\",\"SHN\":\"SH\",\"KNA\":\"KN\",\"LCA\":\"LC\",\"MAF\":\"MF\",\"SPM\":\"PM\",\"VCT\":\"VC\",\"WSM\":\"WS\",\"SMR\":\"SM\",\"STP\":\"ST\",\"SAU\":\"SA\",\"SEN\":\"SN\",\"SRB\":\"RS\",\"SYC\":\"SC\",\"SLE\":\"SL\",\"SGP\":\"SG\",\"SXM\":\"SX\",\"SVK\":\"SK\",\"SVN\":\"SI\",\"SLB\":\"SB\",\"SOM\":\"SO\",\"ZAF\":\"ZA\",\"SGS\":\"GS\",\"SSD\":\"SS\",\"ESP\":\"ES\",\"LKA\":\"LK\",\"SDN\":\"SD\",\"SUR\":\"SR\",\"SJM\":\"SJ\",\"SWE\":\"SE\",\"CHE\":\"CH\",\"SYR\":\"SY\",\"TWN\":\"TW\",\"TJK\":\"TJ\",\"TZA\":\"TZ\",\"THA\":\"TH\",\"TLS\":\"TL\",\"TGO\":\"TG\",\"TKL\":\"TK\",\"TON\":\"TO\",\"TTO\":\"TT\",\"TUN\":\"TN\",\"TUR\":\"TR\",\"TKM\":\"TM\",\"TCA\":\"TC\",\"TUV\":\"TV\",\"UGA\":\"UG\",\"UKR\":\"UA\",\"ARE\":\"AE\",\"GBR\":\"GB\",\"USA\":\"US\",\"UMI\":\"UM\",\"URY\":\"UY\",\"UZB\":\"UZ\",\"VUT\":\"VU\",\"VEN\":\"VE\",\"VNM\":\"VN\",\"VGB\":\"VG\",\"VIR\":\"VI\",\"WLF\":\"WF\",\"ESH\":\"EH\",\"YEM\":\"YE\",\"ZMB\":\"ZM\",\"ZWE\":\"ZW\"};\n\nfunction getFlagHtml(alpha3: string) {\n  const alpha2 = alpha3ToAlpha2[alpha3];\n  if (!alpha2) return \"\";\n  return `<img src=\"https://flagcdn.com/w40/${alpha2.toLowerCase()}.png\" width=\"20\" alt=\"${alpha2}\" class=\"inline-block object-cover rounded-[2px]\" />`;\n}\n\ntype DotMarker = { id: string; lat: number; lng: number; color: string };\n\n/** Static centroid coordinates for key ISO-3 countries, used as a fallback. */\nconst COUNTRY_CENTROIDS: Record<string, [number, number]> = {\n  CHN: [35.8617, 104.1954],\n  IND: [20.5937, 78.9629],\n  USA: [37.0902, -95.7129],\n  BRA: [-14.2350, -51.9253],\n  GBR: [55.3781, -3.4360],\n  DEU: [51.1657, 10.4515],\n  CAN: [56.1304, -106.3468],\n  AUS: [-25.2744, 133.7751],\n  FRA: [46.2276, 2.2137],\n  JPN: [36.2048, 138.2529],\n  RUS: [61.5240, 105.3188],\n};\n\n/**\n * True only after hydration. useSyncExternalStore reports a different snapshot\n * on the server than on the client, so this needs no state update in an effect.\n */\nconst subscribeToNothing = () => () => {};\nconst useIsMounted = () =>\n  useSyncExternalStore(\n    subscribeToNothing,\n    () => true,\n    () => false,\n  );\n\nexport const DataMaps = React.forwardRef<HTMLDivElement, DataMapsProps>(\n  (\n    {\n      dataPoints,\n      variant = \"fill\",\n      center = [20, 0],\n      zoom = 2,\n      minZoom = 1,\n      maxZoom = 8,\n      zoomable = false,\n      scrollWheelZoom = false,\n      highlightColor = \"var(--primary, #2563eb)\",\n      defaultColor = \"var(--map-default-fill)\",\n      mapGeoJsonUrl = \"https://raw.githubusercontent.com/johan/world.geo.json/master/countries.geo.json\",\n      mapId,\n      className,\n      ...props\n    },\n    ref\n  ) => {\n    const isMounted = useIsMounted();\n    const instanceId = React.useId();\n    const [geoData, setGeoData] =\n      useState<import(\"geojson\").FeatureCollection | null>(null);\n    const mapRef = useRef<import(\"leaflet\").Map | null>(null);\n    const mapWrapperRef = useRef<HTMLDivElement>(null);\n    const geoJsonRef = useRef<import(\"leaflet\").GeoJSON | null>(null);\n    const markersGroupRef = useRef<import(\"leaflet\").LayerGroup | null>(null);\n    const highlightColorRef = useRef(highlightColor);\n    const variantRef = useRef(variant);\n    const [leafletLib, setLeafletLib] = useState<Leaflet | null>(null);\n    const [rl, setRl] = useState<ReactLeaflet | null>(null);\n    // Dots placed on the drawn country layers. Null until those layers exist\n    // (or whenever they are detached), and then the dots sit on the static\n    // centroids instead.\n    const [layerDots, setLayerDots] = useState<{ markers: DotMarker[]; count: number } | null>(null);\n    const centroidDots = useMemo(\n      () =>\n        (dataPoints ?? []).map((dp) => {\n          const dpId = String(dp.id).toUpperCase();\n          const coords = COUNTRY_CENTROIDS[dpId] || [20, 0];\n          return {\n            id: dpId,\n            lat: coords[0],\n            lng: coords[1],\n            color: dp.color || highlightColor,\n          };\n        }),\n      [dataPoints, highlightColor],\n    );\n    const dotMarkers = layerDots?.markers ?? centroidDots;\n    const dotCount = layerDots?.count ?? (dataPoints?.length || 0);\n    const wrapperRef = useRef<HTMLDivElement>(null);\n\n    useEffect(() => {\n      let cancelled = false;\n      Promise.all([import(\"leaflet\"), import(\"react-leaflet\")]).then(([L, RL]) => {\n        if (cancelled) return;\n        setLeafletLib(L);\n        setRl(RL);\n      });\n      return () => {\n        cancelled = true;\n      };\n    }, []);\n\n    useEffect(() => {\n      variantRef.current = variant;\n    }, [variant]);\n\n    useEffect(() => {\n      const map = mapRef.current;\n      if (!map) return;\n      \n      // Always allow dragging/panning across screen on all variants\n      map.dragging?.enable();\n\n      if (zoomable) {\n        map.touchZoom?.enable();\n        map.doubleClickZoom?.enable();\n        if (scrollWheelZoom) map.scrollWheelZoom?.enable();\n        else map.scrollWheelZoom?.disable();\n        map.boxZoom?.enable();\n        map.keyboard?.enable();\n      } else {\n        map.touchZoom?.disable();\n        map.doubleClickZoom?.disable();\n        map.scrollWheelZoom?.disable();\n        map.boxZoom?.disable();\n        map.keyboard?.disable();\n      }\n\n      // Reset view position to default center & initial zoom level whenever variant changes or tab switches\n      map.setView(center, zoom, { animate: false });\n    }, [variant, zoomable, scrollWheelZoom, center, zoom, isMounted, geoData]);\n\n    useEffect(() => {\n      highlightColorRef.current = highlightColor;\n      \n      // Update existing layers dynamically to prevent unmounting React-Leaflet\n      if (geoJsonRef.current && leafletLib) {\n        const isDots = variant === \"dots\";\n        const map = (geoJsonRef.current as unknown as { _map?: import(\"leaflet\").Map })\n          ._map;\n        \n        if (map && !markersGroupRef.current) {\n          markersGroupRef.current = leafletLib.layerGroup().addTo(map);\n        }\n        \n        if (markersGroupRef.current) {\n          markersGroupRef.current.clearLayers();\n        }\n\n        let currentDotCount = 0;\n        const newDotMarkers: DotMarker[] = [];\n\n        geoJsonRef.current.eachLayer((baseLayer) => {\n          const layer = baseLayer as FeatureLayer;\n          const countryId = layer.feature?.id || layer.feature?.properties?.id || layer.feature?.properties?.ISO_A3;\n          const dataPoint = dataPoints.find((p) => String(p.id).toUpperCase() === String(countryId).toUpperCase());\n          \n          layer.setStyle({\n            fillColor: (dataPoint && !isDots) ? (dataPoint.color || highlightColor) : defaultColor,\n          });\n\n          if (isDots && dataPoint && layer.getBounds) {\n             currentDotCount++;\n             const c = layer.getBounds().getCenter();\n             const color = dataPoint.color || highlightColor;\n             newDotMarkers.push({\n               id: String(dataPoint.id),\n               lat: c.lat,\n               lng: c.lng,\n               color,\n             });\n          }\n        });\n\n        // Fallback for any data points not matched by layer bounds\n        if (isDots) {\n          dataPoints.forEach((dp) => {\n            const dpId = String(dp.id).toUpperCase();\n            if (!newDotMarkers.some((m) => m.id.toUpperCase() === dpId)) {\n              const coords = COUNTRY_CENTROIDS[dpId];\n              if (coords) {\n                newDotMarkers.push({\n                  id: dpId,\n                  lat: coords[0],\n                  lng: coords[1],\n                  color: dp.color || highlightColor,\n                });\n                currentDotCount++;\n              }\n            }\n          });\n        }\n        \n        setLayerDots({\n          markers: newDotMarkers,\n          count: currentDotCount > 0 ? currentDotCount : dataPoints.length,\n        });\n      } else {\n        // The country layers are not attached yet, so the dots fall back to the\n        // static centroids, which are derived from the props during render.\n        setLayerDots(null);\n      }\n      \n      return () => {\n         if (markersGroupRef.current) {\n             markersGroupRef.current.clearLayers();\n         }\n      }\n    }, [highlightColor, defaultColor, dataPoints, variant, geoData, leafletLib]);\n\n    useEffect(() => {\n      fetch(mapGeoJsonUrl)\n        .then((res) => res.json())\n        .then((data) => setGeoData(data))\n        .catch((err) => console.error(\"Error loading map GeoJSON:\", err));\n    }, [mapGeoJsonUrl]);\n\n    useEffect(() => {\n      return () => {\n        if (mapRef.current) {\n          try {\n            mapRef.current.remove();\n          } catch {\n            // Ignore\n          }\n          mapRef.current = null;\n        }\n        if (mapWrapperRef.current) {\n          mapWrapperRef.current.innerHTML = \"\";\n        }\n      };\n    }, []);\n\n    // Leaflet sizes its canvas once at creation — refit whenever the wrapper\n    // resizes (responsive preview, layout changes) so the full map stays visible.\n    useEffect(() => {\n      const wrapper = mapWrapperRef.current;\n      if (!wrapper) return;\n      const win = wrapper.ownerDocument.defaultView ?? window;\n      const Observer = (win as Window & typeof globalThis).ResizeObserver ?? ResizeObserver;\n      const observer = new Observer(() => {\n        mapRef.current?.invalidateSize();\n      });\n      observer.observe(wrapper);\n      return () => observer.disconnect();\n    }, [isMounted, geoData]);\n\n    if (!isMounted || !geoData) {\n      return (\n        <div ref={ref} className={cn(\"w-full h-full min-h-[250px] sm:min-h-[400px] bg-muted rounded-lg animate-pulse flex items-center justify-center\", className)} {...props}>\n          <p className=\"text-muted-foreground text-sm\">Loading Maps...</p>\n        </div>\n      );\n    }\n\n      return (\n        <div ref={(node) => { wrapperRef.current = node; if (typeof ref === 'function') ref(node); else if (ref) ref.current = node; }} className={cn(\"relative w-full h-full min-h-[250px] sm:min-h-[400px] overflow-hidden rounded-lg bg-muted map-wrapper\", className)} {...props}>\n        <style dangerouslySetInnerHTML={{__html: `\n          .map-wrapper {\n            --map-default-fill: color-mix(in oklab, var(--foreground) 12%, var(--muted));\n          }\n          path.leaflet-interactive:focus {\n            outline: none;\n          }\n          .map-stroke {\n            stroke: var(--background);\n            stroke-width: 0.5;\n            transition: fill 0.2s ease, fill-opacity 0.2s ease;\n          }\n          .custom-leaflet-tooltip {\n            background: transparent !important;\n            border: none !important;\n            box-shadow: none !important;\n            padding: 0 !important;\n            opacity: 1 !important;\n          }\n          .custom-leaflet-tooltip::before {\n            display: none !important;\n          }\n          .leaflet-marker-icon {\n            background: transparent !important;\n            border: none !important;\n            overflow: visible !important;\n          }\n          .map-pulse-dot {\n            width: 8px;\n            height: 8px;\n            border-radius: 50%;\n            position: relative;\n            background-color: var(--pulse-color, #2563eb);\n            box-shadow: 0 0 6px var(--pulse-color, #2563eb);\n            display: block;\n            pointer-events: none;\n          }\n          .map-pulse-dot::after {\n            content: \"\";\n            position: absolute;\n            top: 50%;\n            left: 50%;\n            width: 100%;\n            height: 100%;\n            transform: translate(-50%, -50%);\n            border-radius: 50%;\n            box-shadow: 0 0 0 0 var(--pulse-color, #2563eb);\n            animation: map-pulse 2s infinite cubic-bezier(0.4, 0, 0.2, 1);\n          }\n          @keyframes map-pulse {\n            0% { transform: translate(-50%, -50%) scale(0.8); box-shadow: 0 0 0 0 var(--pulse-color, #2563eb); opacity: 0.95; }\n            70% { transform: translate(-50%, -50%) scale(1.4); box-shadow: 0 0 0 28px transparent; opacity: 0; }\n            100% { transform: translate(-50%, -50%) scale(0.8); box-shadow: 0 0 0 0 transparent; opacity: 0; }\n          }\n        `}} />\n        <div ref={mapWrapperRef} className=\"absolute inset-0 z-0\">\n          {rl && (\n            <rl.MapContainer\n              key={mapId ?? `${instanceId}-${variant}-${center.join(\",\")}-${zoom}`}\n              ref={mapRef}\n            center={center}\n            zoom={zoom}\n            minZoom={minZoom}\n            maxZoom={maxZoom}\n            style={{ height: \"100%\", width: \"100%\", background: \"transparent\" }}\n            scrollWheelZoom={zoomable ? scrollWheelZoom : false}\n            doubleClickZoom={zoomable}\n            dragging={zoomable}\n            touchZoom={zoomable}\n            zoomControl={false}\n            attributionControl={false}\n          >\n            <ResizeHandler wrapperRef={wrapperRef} initialZoom={zoom} zoomable={zoomable} />\n            {zoomable && <rl.ZoomControl position=\"bottomright\" />}\n            <rl.GeoJSON\n              ref={geoJsonRef}\n              key={`geojson-${variant}`}\n              data={geoData}\n              style={(feature) => {\n                const countryId = feature?.id || feature?.properties?.id || feature?.properties?.ISO_A3;\n                const dataPoint = dataPoints.find((p) => String(p.id).toUpperCase() === String(countryId).toUpperCase());\n                const isDots = variant === \"dots\";\n                return {\n                  fillColor: (dataPoint && !isDots) ? (dataPoint.color || highlightColor) : defaultColor,\n                  className: \"map-stroke\",\n                  weight: 0.5,\n                  fillOpacity: 1,\n                };\n              }}\n              onEachFeature={(feature, layer) => {\n                const countryId = feature?.id || feature?.properties?.id || feature?.properties?.ISO_A3;\n                const dataPoint = dataPoints.find((p) => String(p.id).toUpperCase() === String(countryId).toUpperCase());\n                const flagHtml = countryId ? getFlagHtml(String(countryId)) : \"\";\n                \n                if (dataPoint) {\n                  const upArrow = `<svg width=\"14\" height=\"14\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"#10b981\" stroke-width=\"2.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"M7 17L17 7\"/><path d=\"M7 7h10v10\"/></svg>`;\n                  const downArrow = `<svg width=\"14\" height=\"14\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"#ef4444\" stroke-width=\"2.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"M7 7l10 10\"/><path d=\"M17 7v10H7\"/></svg>`;\n\n                  const activeTrendIcon = dataPoint.activeTrend === \"up\" ? upArrow : (dataPoint.activeTrend === \"down\" ? downArrow : \"\");\n                  const newTrendIcon = dataPoint.newTrend === \"up\" ? upArrow : (dataPoint.newTrend === \"down\" ? downArrow : \"\");\n\n                  layer.bindTooltip(`\n                    <div class=\"font-sans flex flex-col gap-1.5 min-w-[130px] w-max p-3 bg-card border border-border rounded-xl shadow-xl\">\n                      <div class=\"flex items-center gap-2 mb-1 whitespace-nowrap\">\n                        ${flagHtml}\n                        <strong class=\"text-foreground text-base font-medium pr-1\">${dataPoint.name}</strong>\n                      </div>\n                      ${dataPoint.active !== undefined ? `\n                        <div class=\"flex items-center justify-between text-sm pr-1\">\n                          <span class=\"text-muted-foreground\">Active:</span>\n                          <div class=\"flex items-center gap-1\">\n                            <span class=\"font-medium text-foreground\">${dataPoint.active}</span>\n                            ${activeTrendIcon}\n                          </div>\n                        </div>\n                      ` : \"\"}\n                      ${dataPoint.new !== undefined ? `\n                        <div class=\"flex items-center justify-between text-sm pr-1\">\n                          <span class=\"text-muted-foreground\">New:</span>\n                          <div class=\"flex items-center gap-1\">\n                            <span class=\"font-medium text-foreground\">${dataPoint.new}</span>\n                            ${newTrendIcon}\n                          </div>\n                        </div>\n                      ` : \"\"}\n                    </div>\n                  `, { \n                    sticky: true,\n                    className: \"custom-leaflet-tooltip\" \n                  });\n                } else if (feature?.properties?.name) {\n                  layer.bindTooltip(`\n                    <div class=\"font-sans flex items-center gap-2 text-sm py-2 px-3 bg-card border border-border rounded-lg shadow-md whitespace-nowrap w-max\">\n                      ${flagHtml}\n                      <span class=\"text-foreground font-medium pr-1\">${feature.properties.name}</span>\n                    </div>\n                  `, { \n                    sticky: true,\n                    className: \"custom-leaflet-tooltip\" \n                  });\n                }\n\n                // Add hover effects using the ref color to prevent closure traps\n                layer.on({\n                  mouseover: (e) => {\n                    const target = e.target;\n                    target.setStyle({\n                      fillOpacity: 0.7,\n                      fillColor: dataPoint ? (dataPoint.color || highlightColorRef.current) : highlightColorRef.current,\n                    });\n                  },\n                  mouseout: (e) => {\n                    const target = e.target;\n                    const isDots = variantRef.current === \"dots\";\n                    target.setStyle({\n                      fillOpacity: 1,\n                      fillColor: (dataPoint && !isDots) ? (dataPoint.color || highlightColorRef.current) : \"var(--map-default-fill)\",\n                    });\n                  }\n                });\n              }}\n            />\n            {variant === \"dots\" && leafletLib && dotMarkers.map((dm) => {\n              const icon = leafletLib.divIcon({\n                className: 'bg-transparent border-none overflow-visible',\n                html: `<div class=\"map-pulse-dot\" style=\"--pulse-color: ${dm.color};\"></div>`,\n                iconSize: [8, 8],\n                iconAnchor: [4, 4]\n              });\n              return (\n                <rl.Marker\n                  key={dm.id}\n                  position={[dm.lat, dm.lng]}\n                  icon={icon}\n                  interactive={false}\n                />\n              );\n            })}\n          </rl.MapContainer>\n          )}\n        </div>\n        \n        {variant === \"dots\" && (\n          <div className=\"absolute bottom-4 right-4 bg-card/90 backdrop-blur-sm border border-border px-3 py-2 rounded-full shadow-sm z-[400] flex items-center gap-1.5\">\n            <svg \n              xmlns=\"http://www.w3.org/2000/svg\" \n              width=\"14\" \n              height=\"14\" \n              viewBox=\"0 0 24 24\" \n              fill=\"none\" \n              stroke={highlightColor} \n              strokeWidth=\"2.5\" \n              strokeLinecap=\"round\" \n              strokeLinejoin=\"round\"\n            >\n              <path d=\"M20 10c0 6-8 12-8 12s-8-6-8-12a8 8 0 0 1 16 0Z\"></path>\n              <circle cx=\"12\" cy=\"10\" r=\"3\"></circle>\n            </svg>\n            <span className=\"text-xs font-semibold text-foreground\">\n              {dotCount} Countries Selected\n            </span>\n          </div>\n        )}\n      </div>\n    );\n  }\n);\n\nDataMaps.displayName = \"DataMaps\";\n",
      "type": "registry:component",
      "target": "components/custom/blocks/DataMapsBlock.tsx"
    }
  ],
  "docs": "Named exports: DataMaps, DataPoint. `dataPoints` take { id, name, active, new, color?, flag? } keyed by country id; `variant` is fill | dots, with `center` and `zoom` for the initial view. **Import Leaflet's CSS yourself** and supply your own GeoJSON. Uses next/image, so it needs edits outside Next.js.",
  "categories": [
    "maps",
    "data-display"
  ],
  "type": "registry:component"
}
