Menu

Change language

GitHub repo

Leaflet Delivery Tracking Map

blocklogistics

Live order delivery route on free OpenStreetMap tiles — no API key. Courier position, dropoff destination, and ETA telemetry.

Installation

$npx uipkge-ng@latest add leaflet-delivery-tracking-map
Manifest:https://uipkge.dev/r/angular/leaflet-delivery-tracking-map.jsonInstalls to:src/app/components/blocks/leaflet-delivery-tracking-map/

Variants

Loading interactive previews…

Files installed (2)

  • src/app/components/blocks/leaflet-delivery-tracking-map/leaflet-delivery-tracking-map.component.ts7 kB
    // Live order delivery route on free OpenStreetMap tiles — no API key. Courier position,
    // dropoff destination, and ETA telemetry. Port of the React block 1:1.
    import { ChangeDetectionStrategy, Component, Input, ViewChild } from '@angular/core'
    import { cn } from '@/lib/utils'
    import { UiBadgeComponent } from '@/ui/badge/badge.component'
    import { UiButtonComponent } from '@/ui/button/button.component'
    import {
      UiCardComponent,
      UiCardContentComponent,
      UiCardDescriptionComponent,
      UiCardHeaderComponent,
      UiCardTitleComponent,
    } from '@/ui/card/card.component'
    import {
      UiLeafletMapComponent,
      UiLeafletMarkerComponent,
      UiLeafletPolylineComponent,
    } from '@/ui/leaflet-map/leaflet-map.component'
    
    const ORIGIN: [number, number] = [-122.4194, 37.7749]
    const DESTINATION: [number, number] = [-122.392, 37.7915]
    const COURIER_POSITION: [number, number] = [-122.405, 37.783]
    
    const ROUTE_COORDS: [number, number][] = [
      [-122.4194, 37.7749],
      [-122.414, 37.778],
      [-122.405, 37.783],
      [-122.399, 37.787],
      [-122.392, 37.7915],
    ]
    
    @Component({
      changeDetection: ChangeDetectionStrategy.Eager,
      selector: 'ui-leaflet-delivery-tracking-map, [ui-leaflet-delivery-tracking-map]',
      standalone: true,
      imports: [
        UiBadgeComponent,
        UiButtonComponent,
        UiCardComponent,
        UiCardContentComponent,
        UiCardDescriptionComponent,
        UiCardHeaderComponent,
        UiCardTitleComponent,
        UiLeafletMapComponent,
        UiLeafletMarkerComponent,
        UiLeafletPolylineComponent,
      ],
      host: {
        'data-slot': 'leaflet-delivery-tracking-map',
        '[class]': 'hostClass',
      },
      template: `
        <div class="border-border bg-card relative h-[480px] overflow-hidden rounded-xl border">
          <ui-leaflet-map variant="dark" [center]="courierPosition" [zoom]="13.5" class="h-full w-full">
            <ui-leaflet-polyline [lngLatPath]="routeCoords" color="#2563eb" [weight]="4" lineCap="round" lineJoin="round" />
            <!-- Restaurant / Merchant -->
            <ui-leaflet-marker [lngLat]="origin" anchor="bottom">
              <div class="flex flex-col items-center">
                <span class="bg-muted-foreground ring-muted size-3 rounded-full ring-4"></span>
                <span class="border-border bg-background/90 mt-1 rounded border px-1.5 py-0.5 font-mono text-xs">
                  Merchant
                </span>
              </div>
            </ui-leaflet-marker>
            <!-- Live Courier -->
            <ui-leaflet-marker [lngLat]="courierPosition" anchor="center">
              <div
                class="relative flex cursor-pointer items-center justify-center"
                (click)="focusCourier()"
                (keydown.enter)="focusCourier()"
                (keydown.space)="focusCourierKey($event)"
                role="button"
                tabindex="0"
                aria-label="Track courier"
              >
                <span class="bg-primary/25 absolute size-7 animate-ping rounded-full"></span>
                <div
                  class="bg-primary text-primary-foreground ring-background flex size-7 items-center justify-center rounded-full shadow-md ring-2"
                >
                  🛵
                </div>
              </div>
            </ui-leaflet-marker>
            <!-- Destination -->
            <ui-leaflet-marker [lngLat]="destination" anchor="bottom">
              <div
                class="flex cursor-pointer flex-col items-center"
                (click)="focusDestination()"
                (keydown.enter)="focusDestination()"
                (keydown.space)="focusDestinationKey($event)"
                role="button"
                tabindex="0"
                aria-label="Dropoff point"
              >
                <span class="bg-success ring-success/20 size-3 rounded-full ring-4"></span>
                <span
                  class="border-border bg-background/90 text-success mt-1 rounded border px-1.5 py-0.5 font-mono text-xs font-semibold"
                >
                  Delivery Dropoff
                </span>
              </div>
            </ui-leaflet-marker>
          </ui-leaflet-map>
        </div>
    
        <!-- Telemetry & Order Status -->
        <ui-card class="flex flex-col justify-between">
          <ui-card-header>
            <div class="flex items-center justify-between">
              <span ui-badge variant="outline" class="font-mono text-xs"> ORDER #8924 </span>
              <span ui-badge class="border-success/20 bg-success/10 text-success">Approaching</span>
            </div>
            <ui-card-title class="mt-2 text-lg">Express Courier</ui-card-title>
            <ui-card-description>
              Estimated delivery in <strong class="text-foreground">8 mins</strong> (0.8 mi away)
            </ui-card-description>
          </ui-card-header>
          <ui-card-content class="space-y-4">
            <div class="border-border space-y-2 border-t pt-3">
              <div class="flex items-center justify-between text-xs">
                <span class="text-muted-foreground">Driver</span>
                <span class="text-foreground font-medium">Carlos M. (★ 4.98)</span>
              </div>
              <div class="flex items-center justify-between text-xs">
                <span class="text-muted-foreground">Vehicle</span>
                <span class="text-foreground font-mono">E-Scooter #412</span>
              </div>
              <div class="flex items-center justify-between text-xs">
                <span class="text-muted-foreground">Destination</span>
                <span class="text-foreground max-w-[170px] truncate font-medium">Market St &amp; 2nd Ave</span>
              </div>
            </div>
    
            <div class="border-border space-y-1.5 border-t pt-3 font-mono text-xs">
              <div class="text-muted-foreground flex items-center gap-2">
                <span class="bg-success size-1.5 rounded-full"></span>
                <span>12:42 PM — Order picked up</span>
              </div>
              <div class="text-foreground flex items-center gap-2 font-medium">
                <span class="bg-primary size-1.5 animate-pulse rounded-full"></span>
                <span>12:51 PM — Approaching building</span>
              </div>
            </div>
    
            <div class="flex gap-2 pt-2">
              <button ui-button variant="outline" size="sm" class="flex-1" (click)="focusCourier()">Track Courier</button>
              <button ui-button variant="outline" size="sm" class="flex-1" (click)="focusDestination()">
                Dropoff Point
              </button>
            </div>
          </ui-card-content>
        </ui-card>
      `,
    })
    export class UiLeafletDeliveryTrackingMapComponent {
      @Input('class') className?: string
    
      @ViewChild(UiLeafletMapComponent) map?: UiLeafletMapComponent
    
      protected readonly origin = ORIGIN
      protected readonly destination = DESTINATION
      protected readonly courierPosition = COURIER_POSITION
      protected readonly routeCoords = ROUTE_COORDS
    
      get hostClass(): string {
        return cn('grid gap-4 lg:grid-cols-[1fr_320px]', this.className)
      }
    
      focusCourier(): void {
        this.map?.flyTo({ center: COURIER_POSITION, zoom: 14.5, duration: 800 })
      }
    
      focusDestination(): void {
        this.map?.flyTo({ center: DESTINATION, zoom: 15, duration: 800 })
      }
    
      focusCourierKey(e: KeyboardEvent): void {
        e.preventDefault()
        this.focusCourier()
      }
    
      focusDestinationKey(e: KeyboardEvent): void {
        e.preventDefault()
        this.focusDestination()
      }
    }
    
  • src/app/components/blocks/leaflet-delivery-tracking-map/index.ts0.1 kB

Raw manifest:https://uipkge.dev/r/angular/leaflet-delivery-tracking-map.json