{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "map-flows-01",
  "title": "Migration flows example",
  "description": "A year of Statistics Canada migration between the western provinces, as flows; hover a province or pin one below the map, composed from the Unmap map, the flow-layer item and your ToggleGroup.",
  "registryDependencies": [
    "https://registry.unmap.dev/r/map.json",
    "https://registry.unmap.dev/r/flow-layer.json",
    "toggle-group"
  ],
  "files": [
    {
      "path": "registry/blocks/map-flows-01.tsx",
      "content": "\"use client\";\n\nimport { useEffect, useState } from \"react\";\nimport { Map } from \"@/components/ui/map\";\nimport { FlowLayer, type Flow, type FlowLocation } from \"@/components/ui/flow-layer\";\nimport { ToggleGroup, ToggleGroupItem } from \"@/components/ui/toggle-group\";\nimport type { MapFeatureEvent } from \"@/lib/map-layers\";\nimport { useMap } from \"@/lib/map-context\";\nimport { cn } from \"@/lib/utils\";\n\nconst COPY = {\n  en: {\n    noKey: \"Add your unmap key to load the map.\",\n    title: \"Migration between the western provinces\",\n    hint: \"Hover or tap a province for its totals in and out.\",\n    pick: \"Province\",\n    place: (name: string, arrived: string, left: string) =>\n      `${name}: ${arrived} arrived from the other three provinces, ${left} left for them`,\n    flow: (from: string, to: string, people: string) => `${from} to ${to}: ${people} people`,\n    credit:\n      \"Adapted from Statistics Canada, Table 17-10-0022-01 Estimates of interprovincial migrants by province or territory of origin and destination, annual, 2024/2025. This does not constitute an endorsement by Statistics Canada of this product. Capitals: Canadian Geographical Names Database. Contains information licensed under the Open Government Licence – Canada.\",\n  },\n  fr: {\n    noKey: \"Ajoutez votre clé unmap pour charger la carte.\",\n    title: \"Migration entre les provinces de l'Ouest\",\n    hint: \"Survolez ou touchez une province pour voir ses totaux entrants et sortants.\",\n    pick: \"Province\",\n    place: (name: string, arrived: string, left: string) =>\n      `${name} : ${arrived} arrivées des trois autres provinces, ${left} départs vers elles`,\n    flow: (from: string, to: string, people: string) => `${from} vers ${to} : ${people} personnes`,\n    credit:\n      \"Adapté de Statistique Canada, Tableau 17-10-0022-01 Estimations des migrants interprovinciaux selon la province ou territoire d'origine et de destination, annuelles, 2024/2025. Cela ne constitue pas une approbation de ce produit par Statistique Canada. Capitales : Base de données toponymiques du Canada. Contient de l'information visée par la Licence du gouvernement ouvert – Canada.\",\n  },\n} as const;\n\nconst NAMES_FR: Record<string, string> = { BC: \"Colombie-Britannique\", AB: \"Alberta\", SK: \"Saskatchewan\", MB: \"Manitoba\" };\n\n/**\n * Real data, a small extract: interprovincial migrants between the four western provinces, 1 July\n * 2024 to 30 June 2025. Adapted from Statistics Canada, Table 17-10-0022-01 Estimates of\n * interprovincial migrants by province or territory of origin and destination, annual\n * (DOI 10.25318/1710002201-eng), Statistics Canada Open Licence. Each province sits at its capital,\n * from the Canadian Geographical Names Database (Natural Resources Canada), Open Government Licence,\n * Canada.\n */\nconst PROVINCES: FlowLocation[] = [\n  { id: \"BC\", name: \"British Columbia\", lat: 48.42833, lon: -123.36472 },\n  { id: \"AB\", name: \"Alberta\", lat: 53.53444, lon: -113.49028 },\n  { id: \"SK\", name: \"Saskatchewan\", lat: 50.45484, lon: -104.60661 },\n  { id: \"MB\", name: \"Manitoba\", lat: 49.88444, lon: -97.14639 },\n];\nconst MIGRANTS: Flow[] = (\n  [\n    [\"MB\", \"SK\", 1111], [\"MB\", \"AB\", 4607], [\"MB\", \"BC\", 3404],\n    [\"SK\", \"MB\", 1142], [\"SK\", \"AB\", 7502], [\"SK\", \"BC\", 3333],\n    [\"AB\", \"MB\", 2152], [\"AB\", \"SK\", 4300], [\"AB\", \"BC\", 19245],\n    [\"BC\", \"MB\", 2172], [\"BC\", \"SK\", 2931], [\"BC\", \"AB\", 23456],\n  ] as const\n).map(([origin, dest, count]) => ({ origin, dest, count }));\n\n/** A province's totals in and out, from the same flows the map draws: what a picked province reports. */\nfunction provinceTotals(place: FlowLocation): Record<string, unknown> {\n  const arrived = MIGRANTS.filter((flow) => flow.dest === place.id).reduce((sum, flow) => sum + flow.count, 0);\n  const left = MIGRANTS.filter((flow) => flow.origin === place.id).reduce((sum, flow) => sum + flow.count, 0);\n  return { id: place.id, name: place.name, in: arrived, out: left, total: arrived + left };\n}\n\n/** Frames the four capitals once the map exists, so the flows fit a phone as well as a desktop. */\nfunction FitProvinces() {\n  const map = useMap();\n  useEffect(() => {\n    map?.fitBounds([[-125, 45.8], [-95.5, 55]], { padding: 32, animate: false });\n  }, [map]);\n  return null;\n}\n\n/**\n * A minimal example: a year of migration between British Columbia, Alberta, Saskatchewan and\n * Manitoba, from Statistics Canada, as flows whose width follows the number of people. Hover (or\n * tap) a province for its totals in and out, or a flow for its count; or pin a province below the\n * map (a keyboard cannot reach a circle on a canvas). The flows are drawn in the browser: the only\n * requests are the map's own.\n */\nexport function MapFlows({\n  apiKey,\n  lang = \"en\",\n  className,\n}: {\n  /** Your Unmap browser key. */\n  apiKey: string;\n  /** Language of the copy and the map labels: \"en\" (default) or \"fr\". */\n  lang?: \"en\" | \"fr\";\n  className?: string;\n}) {\n  const copy = COPY[lang];\n  // A hover shows while the pointer is there; a click (or a tap, which has no hover) stays.\n  const [hovered, setHovered] = useState<Record<string, unknown> | null>(null);\n  const [pinned, setPinned] = useState<Record<string, unknown> | null>(null);\n  const over = hovered ?? pinned;\n  const pinnedProvince = pinned && \"in\" in pinned ? String(pinned.id) : null;\n  const number = new Intl.NumberFormat(lang === \"fr\" ? \"fr-CA\" : \"en-CA\");\n  const name = (id: unknown, fallback: unknown) => (lang === \"fr\" ? NAMES_FR[String(id)] : undefined) ?? String(fallback ?? id);\n\n  let result: string | null = null;\n  if (over && \"in\" in over) result = copy.place(name(over.id, over.name), number.format(Number(over.in)), number.format(Number(over.out)));\n  else if (over && \"origin\" in over)\n    result = copy.flow(name(over.origin, over.originName), name(over.dest, over.destName), number.format(Number(over.count)));\n\n  return (\n    <div className={cn(\"flex min-w-0 flex-col gap-3\", className)}>\n      <div role=\"region\" aria-label={copy.title} className=\"relative h-96\">\n        <Map apiKey={apiKey} center={[-108, 51]} zoom={4.5} lang={lang}>\n          <FitProvinces />\n          <FlowLayer\n            locations={PROVINCES}\n            flows={MIGRANTS}\n            onHover={(event: MapFeatureEvent | null) => setHovered(event?.feature.properties ?? null)}\n            onClick={(event: MapFeatureEvent) => setPinned(event.feature.properties)}\n          />\n        </Map>\n      </div>\n      {/* No key, no map: one line says what to add. */}\n      {apiKey.trim() === \"\" ? <p className=\"text-xs text-muted-foreground\">{copy.noKey}</p> : null}\n      <ToggleGroup\n        aria-label={copy.pick}\n        variant=\"outline\"\n        size=\"sm\"\n        className=\"flex-wrap\"\n        value={pinnedProvince ? [pinnedProvince] : []}\n        onValueChange={(next: unknown[]) => {\n          const place = PROVINCES.find((entry) => entry.id === next[0]);\n          setPinned(place ? provinceTotals(place) : null);\n        }}\n      >\n        {PROVINCES.map((place) => (\n          <ToggleGroupItem key={place.id} value={place.id}>\n            {name(place.id, place.name)}\n          </ToggleGroupItem>\n        ))}\n      </ToggleGroup>\n      <p aria-live=\"polite\" className=\"text-sm text-muted-foreground tabular-nums\">\n        {result ? <span className=\"font-medium text-foreground\">{result}</span> : copy.hint}\n      </p>\n      <p className=\"text-xs text-muted-foreground\">{copy.credit}</p>\n    </div>\n  );\n}\n",
      "type": "registry:component",
      "target": "@components/unmap-map-flows.tsx"
    }
  ],
  "docs": "Import MapFlows from your components/unmap-map-flows file and pass apiKey (your Unmap browser key). Use lang=\"fr\" for French. Allow your site origin on the key. Preview: https://unmap.dev/templates/map-flows",
  "categories": [
    "examples",
    "maps"
  ],
  "type": "registry:block"
}