{
  "$schema": "https://uipkge.dev/schema/registry-item.json",
  "name": "leaflet-fleet-vehicle-map",
  "title": "Leaflet Fleet Vehicle Map",
  "type": "registry:block",
  "files": [
    {
      "path": "packages/registry-angular/blocks/leaflet-fleet-vehicle-map/leaflet-fleet-vehicle-map.component.ts",
      "content": "// Live fleet map on free OpenStreetMap/Esri tiles — no API key. Select a unit to fly the\n// camera and read status. Port of the React block 1:1.\nimport { ChangeDetectionStrategy, Component, Input, ViewChild, signal } from '@angular/core'\nimport { cn } from '@/lib/utils'\nimport { UiBadgeComponent } from '@/ui/badge/badge.component'\nimport {\n  UiCardComponent,\n  UiCardContentComponent,\n  UiCardHeaderComponent,\n  UiCardTitleComponent,\n} from '@/ui/card/card.component'\nimport { UiLeafletMapComponent, UiLeafletMarkerComponent } from '@/ui/leaflet-map/leaflet-map.component'\n\nexport type VehicleStatus = 'moving' | 'idling' | 'parked'\n\nexport interface FleetVehicle {\n  id: string\n  identifier: string\n  model: string\n  driver: string\n  status: VehicleStatus\n  speed: number\n  lat: number\n  lng: number\n}\n\nexport const DEFAULT_VEHICLES: FleetVehicle[] = [\n  {\n    id: 'v-104',\n    identifier: 'Truck #104',\n    model: 'Volvo VNL 860',\n    driver: 'Marcus Vance',\n    status: 'moving',\n    speed: 64,\n    lat: 41.5868,\n    lng: -93.625,\n  },\n  {\n    id: 'v-208',\n    identifier: 'Van #208',\n    model: 'Ford E-Transit 350',\n    driver: 'Elena Rostova',\n    status: 'moving',\n    speed: 48,\n    lat: 41.6005,\n    lng: -93.712,\n  },\n  {\n    id: 'v-109',\n    identifier: 'Truck #109',\n    model: 'Freightliner Cascadia',\n    driver: 'Derrick Hayes',\n    status: 'idling',\n    speed: 0,\n    lat: 41.984,\n    lng: -93.582,\n  },\n  {\n    id: 'v-212',\n    identifier: 'Van #212',\n    model: 'Mercedes Sprinter',\n    driver: 'Aaliyah Patel',\n    status: 'moving',\n    speed: 52,\n    lat: 41.591,\n    lng: -93.604,\n  },\n]\n\nconst PIN_SELECTED =\n  'border-primary bg-primary text-primary-foreground rounded-full border px-2 py-0.5 font-mono text-xs font-semibold shadow-xs'\nconst PIN_IDLE =\n  'border-border bg-card text-foreground rounded-full border px-2 py-0.5 font-mono text-xs font-semibold shadow-xs'\n\n@Component({\n  changeDetection: ChangeDetectionStrategy.Eager,\n  selector: 'ui-leaflet-fleet-vehicle-map, [ui-leaflet-fleet-vehicle-map]',\n  standalone: true,\n  imports: [\n    UiBadgeComponent,\n    UiCardComponent,\n    UiCardHeaderComponent,\n    UiCardTitleComponent,\n    UiCardContentComponent,\n    UiLeafletMapComponent,\n    UiLeafletMarkerComponent,\n  ],\n  host: {\n    'data-slot': 'leaflet-fleet-vehicle-map',\n    '[class]': 'hostClass',\n  },\n  template: `\n    <div class=\"flex flex-wrap items-center justify-between gap-3\">\n      <h2 class=\"text-foreground text-lg font-semibold tracking-tight\">Fleet</h2>\n      <p class=\"text-muted-foreground text-xs\">{{ vehicles.length }} units</p>\n    </div>\n\n    <div class=\"grid min-h-0 flex-1 grid-cols-1 gap-4 lg:grid-cols-12\">\n      <ui-card class=\"border-border flex min-h-0 flex-col overflow-hidden shadow-xs lg:col-span-8\">\n        <div class=\"relative min-h-[360px] flex-1 overflow-hidden\">\n          <ui-leaflet-map variant=\"dark\" [center]=\"mapCenter()\" [zoom]=\"11\" class=\"absolute inset-0 size-full\">\n            @for (vehicle of vehicles; track vehicle.id) {\n              <ui-leaflet-marker [lngLat]=\"[vehicle.lng, vehicle.lat]\" anchor=\"center\">\n                <button type=\"button\" [class]=\"pinClass(vehicle)\" (click)=\"selectVehicle(vehicle)\">\n                  {{ shortId(vehicle) }}\n                </button>\n              </ui-leaflet-marker>\n            }\n          </ui-leaflet-map>\n        </div>\n      </ui-card>\n\n      <ui-card class=\"border-border flex min-h-0 flex-col overflow-hidden shadow-xs lg:col-span-4\">\n        <ui-card-header class=\"border-border border-b p-4\">\n          <ui-card-title class=\"text-sm font-semibold\">Vehicles</ui-card-title>\n        </ui-card-header>\n        <ui-card-content class=\"divide-border min-h-0 flex-1 divide-y overflow-auto p-0\">\n          @for (vehicle of vehicles; track vehicle.id) {\n            <button type=\"button\" [class]=\"rowClass(vehicle)\" (click)=\"selectVehicle(vehicle)\">\n              <div class=\"min-w-0\">\n                <p class=\"text-foreground truncate text-sm font-medium\">{{ vehicle.identifier }}</p>\n                <p class=\"text-muted-foreground truncate text-xs\">{{ vehicle.driver }} · {{ vehicle.model }}</p>\n              </div>\n              <span ui-badge [variant]=\"statusVariant(vehicle.status)\" class=\"shrink-0 text-xs capitalize\">\n                {{ statusLabel(vehicle) }}\n              </span>\n            </button>\n          }\n        </ui-card-content>\n      </ui-card>\n    </div>\n  `,\n})\nexport class UiLeafletFleetVehicleMapComponent {\n  @Input() vehicles: FleetVehicle[] = DEFAULT_VEHICLES\n  @Input('class') className?: string\n\n  @ViewChild(UiLeafletMapComponent) map?: UiLeafletMapComponent\n\n  readonly selectedId = signal(DEFAULT_VEHICLES[0]?.id ?? '')\n\n  get hostClass(): string {\n    return cn('flex h-full min-h-0 flex-col gap-4', this.className)\n  }\n\n  selected(): FleetVehicle {\n    return this.vehicles.find((v) => v.id === this.selectedId()) ?? this.vehicles[0]!\n  }\n\n  mapCenter(): [number, number] {\n    const s = this.selected()\n    return s ? [s.lng, s.lat] : [-93.625, 41.59]\n  }\n\n  selectVehicle(vehicle: FleetVehicle): void {\n    this.selectedId.set(vehicle.id)\n    this.map?.flyTo?.({\n      center: [vehicle.lng, vehicle.lat],\n      zoom: 12,\n      duration: 800,\n    })\n  }\n\n  statusVariant(status: VehicleStatus): 'info' | 'warning' | 'secondary' {\n    if (status === 'moving') return 'info'\n    if (status === 'idling') return 'warning'\n    return 'secondary'\n  }\n\n  statusLabel(vehicle: FleetVehicle): string {\n    return vehicle.status === 'moving' ? `${vehicle.speed} mph` : vehicle.status\n  }\n\n  shortId(vehicle: FleetVehicle): string {\n    return vehicle.identifier.replace('Truck ', '').replace('Van ', '')\n  }\n\n  pinClass(vehicle: FleetVehicle): string {\n    return this.selectedId() === vehicle.id ? PIN_SELECTED : PIN_IDLE\n  }\n\n  rowClass(vehicle: FleetVehicle): string {\n    return cn(\n      'hover:bg-accent/50 flex w-full items-center justify-between gap-3 px-4 py-3 text-left transition-colors',\n      this.selectedId() === vehicle.id && 'bg-accent/60',\n    )\n  }\n}\n",
      "type": "registry:block",
      "target": "~/src/app/components/blocks/leaflet-fleet-vehicle-map/leaflet-fleet-vehicle-map.component.ts"
    },
    {
      "path": "packages/registry-angular/blocks/leaflet-fleet-vehicle-map/index.ts",
      "content": "export {\n  UiLeafletFleetVehicleMapComponent,\n  DEFAULT_VEHICLES,\n  type FleetVehicle,\n  type VehicleStatus,\n} from './leaflet-fleet-vehicle-map.component'\n",
      "type": "registry:block",
      "target": "~/src/app/components/blocks/leaflet-fleet-vehicle-map/index.ts"
    }
  ],
  "dependencies": [],
  "devDependencies": [],
  "registryDependencies": [
    "https://uipkge.dev/r/angular/badge.json",
    "https://uipkge.dev/r/angular/card.json",
    "https://uipkge.dev/r/angular/leaflet-map.json"
  ],
  "description": "Live fleet map on free OpenStreetMap/Esri tiles — no API key. Select a unit to fly the camera and read status.",
  "categories": [
    "logistics",
    "app",
    "dashboard"
  ]
}