{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "route-map",
  "title": "Route Map",
  "description": "A movement route map with origin and destination markers and a brand line with a dashed progress overlay.",
  "dependencies": [
    "@untitledui/icons@^0.0.22",
    "react-map-gl@^8.1.1"
  ],
  "registryDependencies": [
    "@nx-ui/map-marker",
    "@nx-ui/map-view",
    "@nx-ui/utils"
  ],
  "files": [
    {
      "path": "components/blocks/maps/route-map.tsx",
      "content": "\"use client\"\n\nimport * as React from \"react\"\nimport { Flag01, MarkerPin01 } from \"@untitledui/icons\"\nimport type { Feature, LineString } from \"geojson\"\nimport { Layer, type MapRef, Source } from \"react-map-gl/mapbox\"\n\nimport { cn } from \"@/lib/utils\"\nimport { MapPin } from \"@/components/blocks/maps/map-marker\"\nimport { MapView, type MapViewProps } from \"@/components/blocks/maps/map-view\"\n\n/**\n * route-map — an origin → destination movement route for nx-ui.\n *\n * Draws the route as two stacked line layers: a solid brand-navy base line and\n * a dashed accent overlay clipped to the completed portion (a \"progress\"\n * treatment). Origin and destination render as branded pins, and the camera\n * fit-bounds to the whole route on load and whenever the route changes.\n */\n\nconst BRAND_NAVY = \"#121541\"\nconst ACCENT_YELLOW = \"#F7EC33\"\n\nexport interface RouteMapProps\n  extends Omit<MapViewProps, \"children\" | \"initialViewState\"> {\n  /** Ordered [longitude, latitude] points making up the route line. */\n  route: [number, number][]\n  /** Origin label shown under the start pin. */\n  originLabel?: string\n  /** Destination label shown under the end pin. */\n  destinationLabel?: string\n  /** 0–1 fraction of the route completed; drives the dashed progress overlay. */\n  progress?: number\n  className?: string\n}\n\n/** Compute a padded bounding box [[minLng,minLat],[maxLng,maxLat]] for coords. */\nfunction boundsOf(\n  coords: [number, number][]\n): [[number, number], [number, number]] | null {\n  if (coords.length === 0) return null\n  let minLng = coords[0][0]\n  let maxLng = coords[0][0]\n  let minLat = coords[0][1]\n  let maxLat = coords[0][1]\n  for (const [lng, lat] of coords) {\n    minLng = Math.min(minLng, lng)\n    maxLng = Math.max(maxLng, lng)\n    minLat = Math.min(minLat, lat)\n    maxLat = Math.max(maxLat, lat)\n  }\n  return [\n    [minLng, minLat],\n    [maxLng, maxLat],\n  ]\n}\n\n/** Slice the first `fraction` of the route by cumulative length. */\nfunction progressSlice(\n  coords: [number, number][],\n  fraction: number\n): [number, number][] {\n  if (coords.length < 2 || fraction <= 0) return []\n  if (fraction >= 1) return coords\n  // Approximate lengths in degrees (fine for a progress overlay at UK scale).\n  const segLengths: number[] = []\n  let total = 0\n  for (let i = 1; i < coords.length; i += 1) {\n    const dx = coords[i][0] - coords[i - 1][0]\n    const dy = coords[i][1] - coords[i - 1][1]\n    const len = Math.hypot(dx, dy)\n    segLengths.push(len)\n    total += len\n  }\n  const target = total * fraction\n  const out: [number, number][] = [coords[0]]\n  let acc = 0\n  for (let i = 1; i < coords.length; i += 1) {\n    const len = segLengths[i - 1]\n    if (acc + len >= target) {\n      const t = (target - acc) / len\n      out.push([\n        coords[i - 1][0] + (coords[i][0] - coords[i - 1][0]) * t,\n        coords[i - 1][1] + (coords[i][1] - coords[i - 1][1]) * t,\n      ])\n      break\n    }\n    acc += len\n    out.push(coords[i])\n  }\n  return out\n}\n\nexport function RouteMap({\n  route,\n  originLabel,\n  destinationLabel,\n  progress,\n  className,\n  ...mapViewProps\n}: RouteMapProps) {\n  const mapRef = React.useRef<MapRef>(null)\n\n  const origin = route[0]\n  const destination = route[route.length - 1]\n\n  const lineFeature = React.useMemo<Feature<LineString>>(\n    () => ({\n      type: \"Feature\",\n      geometry: { type: \"LineString\", coordinates: route },\n      properties: {},\n    }),\n    [route]\n  )\n\n  const progressFeature = React.useMemo<Feature<LineString> | null>(() => {\n    if (progress == null) return null\n    const coords = progressSlice(route, progress)\n    if (coords.length < 2) return null\n    return {\n      type: \"Feature\",\n      geometry: { type: \"LineString\", coordinates: coords },\n      properties: {},\n    }\n  }, [route, progress])\n\n  const fitToRoute = React.useCallback(() => {\n    const map = mapRef.current?.getMap()\n    const bounds = boundsOf(route)\n    if (!map || !bounds) return\n    map.fitBounds(bounds, { padding: 56, maxZoom: 13, duration: 500 })\n  }, [route])\n\n  // Re-fit whenever the route changes (initial fit is handled by onLoad).\n  React.useEffect(() => {\n    if (mapRef.current?.getMap()?.isStyleLoaded()) fitToRoute()\n  }, [fitToRoute])\n\n  return (\n    <MapView\n      ref={mapRef}\n      className={cn(className)}\n      onLoad={fitToRoute}\n      {...mapViewProps}\n    >\n      <Source id=\"route\" type=\"geojson\" data={lineFeature}>\n        {/* Solid brand-navy base line. */}\n        <Layer\n          id=\"route-line\"\n          type=\"line\"\n          layout={{ \"line-cap\": \"round\", \"line-join\": \"round\" }}\n          paint={{ \"line-color\": BRAND_NAVY, \"line-width\": 4, \"line-opacity\": 0.9 }}\n        />\n      </Source>\n\n      {progressFeature ? (\n        <Source id=\"route-progress\" type=\"geojson\" data={progressFeature}>\n          {/* Dashed accent overlay clipped to the completed portion. */}\n          <Layer\n            id=\"route-progress-line\"\n            type=\"line\"\n            layout={{ \"line-cap\": \"round\", \"line-join\": \"round\" }}\n            paint={{\n              \"line-color\": ACCENT_YELLOW,\n              \"line-width\": 4,\n              \"line-dasharray\": [1, 1.6],\n            }}\n          />\n        </Source>\n      ) : null}\n\n      {origin ? (\n        <MapPin\n          longitude={origin[0]}\n          latitude={origin[1]}\n          label={originLabel}\n          selected\n        >\n          <MarkerPin01 className=\"size-3.5 text-accent-400\" />\n        </MapPin>\n      ) : null}\n\n      {destination ? (\n        <MapPin\n          longitude={destination[0]}\n          latitude={destination[1]}\n          label={destinationLabel}\n        >\n          <Flag01 className=\"size-3 text-accent-400\" />\n        </MapPin>\n      ) : null}\n    </MapView>\n  )\n}\n",
      "type": "registry:component",
      "target": "components/blocks/maps/route-map.tsx"
    }
  ],
  "type": "registry:block"
}