Delivery Tracking Map
blocklogisticsLive order delivery route showing courier location, dropoff destination, and real-time ETA countdown.
Installation
$pnpm dlx uipkge-ng@latest add delivery-tracking-map$npx uipkge-ng@latest add delivery-tracking-map$yarn dlx uipkge-ng@latest add delivery-tracking-map$bunx uipkge-ng@latest add delivery-tracking-mapManifest:
https://uipkge.dev/r/angular/delivery-tracking-map.jsonInstalls to:src/app/components/blocks/delivery-tracking-map/Variants
Loading interactive previews…
Loading interactive previews…
Files installed (2)
src/app/components/blocks/delivery-tracking-map/delivery-tracking-map.component.ts9.3 kB
// Live order delivery route on a Mapbox map: courier location, dropoff destination and // the merchant origin with a real-time ETA countdown side panel. Port of the React block // 1:1; markers follow the last-mile-courier-dispatch Angular pattern. import { type AfterViewInit, ChangeDetectionStrategy, Component, type ElementRef, Input, type OnDestroy, type QueryList, ViewChildren, } from '@angular/core' import type * as mapboxgl from 'mapbox-gl' 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 { UiMapComponent, type MapLayerInput, type MapSourceInput } from '@/ui/map/map.component' export const DELIVERY_ORIGIN: [number, number] = [-122.4194, 37.7749] export const DELIVERY_DESTINATION: [number, number] = [-122.392, 37.7915] export const COURIER_POSITION: [number, number] = [-122.405, 37.783] export const DELIVERY_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-delivery-tracking-map, [ui-delivery-tracking-map]', standalone: true, host: { '[attr.data-slot]': '"delivery-tracking-map"', '[class]': 'hostClass', }, imports: [ UiBadgeComponent, UiButtonComponent, UiCardComponent, UiCardContentComponent, UiCardDescriptionComponent, UiCardHeaderComponent, UiCardTitleComponent, UiMapComponent, ], template: ` <div class="grid gap-4 lg:grid-cols-[1fr_320px]"> <div class="border-border bg-card relative h-[480px] overflow-hidden rounded-xl border"> <div ui-map variant="light" [accessToken]="accessToken ?? ''" [center]="courierPosition" [zoom]="13.5" class="h-full w-full" [sources]="routeSources" [layers]="routeLayers" (created)="onMapCreated($event)" > <!-- Marker elements: handed to mapbox-gl Markers once the map exists, which moves each inner #marker node onto the map; the outer node stays put for Angular's @for. --> <div class="hidden"> <div> <!-- Restaurant / Merchant --> <div #marker> <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> </div> </div> <div> <!-- Live Courier --> <div #marker> <div class="relative flex cursor-pointer items-center justify-center" (click)="focusCourier()"> <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> </div> </div> <div> <!-- Destination --> <div #marker> <div class="flex cursor-pointer flex-col items-center" (click)="focusDestination()"> <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> </div> </div> </div> </div> </div> <!-- Telemetry & Order Status --> <div ui-card class="flex flex-col justify-between"> <div 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> <h3 ui-card-title class="mt-2 text-lg">Express Courier</h3> <p ui-card-description>Estimated delivery in <strong class="text-foreground">8 mins</strong> (0.8 mi away)</p> </div> <div 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 & 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 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> </div> </div> </div> `, }) export class UiDeliveryTrackingMapComponent implements AfterViewInit, OnDestroy { @Input() accessToken?: string @Input('class') className?: string @ViewChildren('marker') private markerEls?: QueryList<ElementRef<HTMLDivElement>> readonly origin: [number, number] = [...DELIVERY_ORIGIN] as [number, number] readonly destination: [number, number] = [...DELIVERY_DESTINATION] as [number, number] readonly courierPosition: [number, number] = [...COURIER_POSITION] as [number, number] /** The delivery route polyline (the React block's MapSource). */ readonly routeSources: MapSourceInput[] = [ { id: 'delivery-route', type: 'geojson', data: { type: 'Feature', properties: {}, geometry: { type: 'LineString', coordinates: DELIVERY_ROUTE_COORDS.map((c) => [...c]) }, }, }, ] /** The blue route line (the React block's MapLayer). */ readonly routeLayers: MapLayerInput[] = [ { id: 'delivery-route-line', type: 'line', source: 'delivery-route', layout: { 'line-cap': 'round', 'line-join': 'round' }, paint: { 'line-color': '#2563eb', 'line-width': 4 }, }, ] private map: mapboxgl.Map | null = null private markers: mapboxgl.Marker[] = [] private destroyed = false get hostClass(): string { return cn('block w-full', this.className) } ngAfterViewInit(): void { // A new marker list re-renders the marker elements; put the new set on the map. this.markerEls?.changes.subscribe(() => void this.syncMarkers()) } ngOnDestroy(): void { this.destroyed = true this.clearMarkers() this.map = null } focusCourier(): void { this.map?.flyTo?.({ center: this.courierPosition, zoom: 14.5 }) } focusDestination(): void { this.map?.flyTo?.({ center: this.destination, zoom: 15 }) } onMapCreated(map: mapboxgl.Map): void { this.map = map void this.syncMarkers() } /** One mapbox-gl Marker per waypoint: merchant and dropoff anchored at the bottom (the React MapMarkers), the courier centred (React anchor="center"). */ private async syncMarkers(): Promise<void> { const map = this.map const els = this.markerEls?.toArray() ?? [] if (!map) return const mod = await import('mapbox-gl') const mapbox = mod.default ?? mod if (this.destroyed || map !== this.map) return this.clearMarkers() const waypoints: { coords: [number, number]; anchor: 'bottom' | 'center' }[] = [ { coords: this.origin, anchor: 'bottom' }, { coords: this.courierPosition, anchor: 'center' }, { coords: this.destination, anchor: 'bottom' }, ] this.markers = els.flatMap((el, i) => { const waypoint = waypoints[i] if (!waypoint) return [] return [ new mapbox.Marker({ element: el.nativeElement, anchor: waypoint.anchor }).setLngLat(waypoint.coords).addTo(map), ] }) } private clearMarkers(): void { for (const m of this.markers) m.remove() this.markers = [] } }src/app/components/blocks/delivery-tracking-map/index.ts0.2 kB
export { UiDeliveryTrackingMapComponent, DELIVERY_ORIGIN, DELIVERY_DESTINATION, COURIER_POSITION, DELIVERY_ROUTE_COORDS, } from './delivery-tracking-map.component'
Raw manifest:https://uipkge.dev/r/angular/delivery-tracking-map.json