{
  "$schema": "https://uipkge.dev/schema/registry-item.json",
  "name": "leaflet-store-locator-map",
  "title": "Leaflet Store Locator Map",
  "type": "registry:block",
  "files": [
    {
      "path": "packages/registry-angular/blocks/leaflet-store-locator-map/leaflet-store-locator-map.component.ts",
      "content": "import { Component, Input, OnInit, ViewChild, signal, ChangeDetectionStrategy } 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 {\n  UiLeafletMapComponent,\n  UiLeafletMarkerComponent,\n  UiLeafletPopupComponent,\n} from '@/ui/leaflet-map/leaflet-map.component'\n\nexport interface RetailStore {\n  id: string\n  name: string\n  address: string\n  neighborhood: string\n  lat: number\n  lng: number\n  distanceMiles: number\n  hours: string\n  isOpen: boolean\n  amenities: string[]\n}\n\nexport const DEFAULT_STORES: RetailStore[] = [\n  {\n    id: 'nyc-herald',\n    name: 'Herald Square',\n    address: '151 W 34th St',\n    neighborhood: 'Midtown',\n    lat: 40.7484,\n    lng: -73.9879,\n    distanceMiles: 0.3,\n    hours: 'Open until 10 PM',\n    isOpen: true,\n    amenities: ['Pickup', 'Returns', 'Cafe'],\n  },\n  {\n    id: 'nyc-chelsea',\n    name: 'Chelsea Market',\n    address: '75 9th Ave',\n    neighborhood: 'Chelsea',\n    lat: 40.7424,\n    lng: -74.0061,\n    distanceMiles: 0.9,\n    hours: 'Open until 9 PM',\n    isOpen: true,\n    amenities: ['Parking', 'Cafe'],\n  },\n  {\n    id: 'nyc-soho',\n    name: 'SoHo Flagship',\n    address: '112 Greene St',\n    neighborhood: 'SoHo',\n    lat: 40.7248,\n    lng: -73.9984,\n    distanceMiles: 1.6,\n    hours: 'Open until 9 PM',\n    isOpen: true,\n    amenities: ['Parking', 'Pickup', 'Returns'],\n  },\n  {\n    id: 'nyc-ues',\n    name: 'Lexington & 78th',\n    address: '1120 Lexington Ave',\n    neighborhood: 'Upper East Side',\n    lat: 40.7736,\n    lng: -73.9614,\n    distanceMiles: 1.8,\n    hours: 'Open until 8 PM',\n    isOpen: true,\n    amenities: ['Pickup', 'Returns'],\n  },\n  {\n    id: 'nyc-tribeca',\n    name: 'Tribeca',\n    address: '60 Hudson St',\n    neighborhood: 'Tribeca',\n    lat: 40.7175,\n    lng: -74.0089,\n    distanceMiles: 2.1,\n    hours: 'Closed · Opens 10 AM',\n    isOpen: false,\n    amenities: ['Parking', 'Returns'],\n  },\n]\n\n// Midtown Manhattan as [lng, lat] — a stable reference so the map's center input never re-fires.\nconst MIDTOWN_CENTER: [number, number] = [-73.985, 40.748]\nconst POPUP_OFFSET: [number, number] = [0, -18]\n\nconst PIN_SELECTED =\n  'border-primary bg-primary text-primary-foreground rounded-full border px-2 py-0.5 text-xs font-semibold shadow-xs'\nconst PIN_IDLE = 'border-border bg-card text-foreground rounded-full border px-2 py-0.5 text-xs font-semibold shadow-xs'\n\n/**\n * Store locator on free OpenStreetMap tiles — no API key. Pins with bound\n * popups, distance/hours/amenities list, fly-to on select (map pin or row).\n */\n@Component({\n  changeDetection: ChangeDetectionStrategy.Eager,\n  selector: 'ui-leaflet-store-locator-map, [ui-leaflet-store-locator-map]',\n  standalone: true,\n  imports: [\n    UiBadgeComponent,\n    UiCardComponent,\n    UiCardHeaderComponent,\n    UiCardTitleComponent,\n    UiCardContentComponent,\n    UiLeafletMapComponent,\n    UiLeafletMarkerComponent,\n    UiLeafletPopupComponent,\n  ],\n  host: {\n    'data-slot': 'leaflet-store-locator-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\">Stores</h2>\n      <p class=\"text-muted-foreground text-xs\">{{ openCount }} open · {{ stores.length }} locations</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=\"streets\" [center]=\"midtownCenter\" [zoom]=\"13\" class=\"absolute inset-0 size-full\">\n            @for (store of stores; track store.id) {\n              <ui-leaflet-marker [lngLat]=\"[store.lng, store.lat]\" anchor=\"center\">\n                <button type=\"button\" [class]=\"pinClass(store)\" (click)=\"selectStore(store)\">{{ store.name }}</button>\n                <ui-leaflet-popup [offset]=\"popupOffset\" className=\"space-y-1 text-xs\">\n                  <p class=\"text-foreground font-semibold\">{{ store.name }}</p>\n                  <p class=\"text-muted-foreground\">{{ store.address }}, {{ store.neighborhood }}</p>\n                  <p class=\"text-muted-foreground\">{{ store.hours }} · {{ store.distanceMiles }} mi away</p>\n                </ui-leaflet-popup>\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\">Locations</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 (store of stores; track store.id) {\n            <button type=\"button\" [class]=\"rowClass(store)\" (click)=\"selectStore(store)\">\n              <div class=\"min-w-0 space-y-1\">\n                <p class=\"text-foreground truncate text-sm font-medium\">{{ store.name }}</p>\n                <p class=\"text-muted-foreground truncate text-xs\">\n                  {{ store.address }}, {{ store.neighborhood }} · {{ store.hours }}\n                </p>\n                <div class=\"flex flex-wrap gap-1\">\n                  @for (amenity of store.amenities; track amenity) {\n                    <ui-badge variant=\"secondary\" class=\"text-xs\">{{ amenity }}</ui-badge>\n                  }\n                </div>\n              </div>\n              <div class=\"flex shrink-0 flex-col items-end gap-1\">\n                <span class=\"text-muted-foreground font-mono text-xs tabular-nums\">{{ store.distanceMiles }} mi</span>\n                <ui-badge [variant]=\"store.isOpen ? 'info' : 'secondary'\" class=\"text-xs\">\n                  {{ store.isOpen ? 'Open' : 'Closed' }}\n                </ui-badge>\n              </div>\n            </button>\n          }\n        </ui-card-content>\n      </ui-card>\n    </div>\n  `,\n})\nexport class UiLeafletStoreLocatorMapComponent implements OnInit {\n  @Input() stores: RetailStore[] = DEFAULT_STORES\n  @Input('class') className?: string\n\n  @ViewChild(UiLeafletMapComponent) map?: UiLeafletMapComponent\n\n  protected readonly midtownCenter = MIDTOWN_CENTER\n  protected readonly popupOffset = POPUP_OFFSET\n  readonly selectedId = signal('')\n\n  get hostClass(): string {\n    return cn('flex h-full min-h-0 flex-col gap-4', this.className)\n  }\n\n  get openCount(): number {\n    return this.stores.filter((s) => s.isOpen).length\n  }\n\n  ngOnInit(): void {\n    this.selectedId.set(this.stores[0]?.id ?? '')\n  }\n\n  selectStore(store: RetailStore): void {\n    this.selectedId.set(store.id)\n    this.map?.flyTo({\n      center: [store.lng, store.lat],\n      zoom: 14,\n      duration: 900,\n    })\n  }\n\n  pinClass(store: RetailStore): string {\n    return this.selectedId() === store.id ? PIN_SELECTED : PIN_IDLE\n  }\n\n  rowClass(store: RetailStore): 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() === store.id && 'bg-accent/60',\n    )\n  }\n}\n",
      "type": "registry:block",
      "target": "~/src/app/components/blocks/leaflet-store-locator-map/leaflet-store-locator-map.component.ts"
    },
    {
      "path": "packages/registry-angular/blocks/leaflet-store-locator-map/index.ts",
      "content": "export {\n  UiLeafletStoreLocatorMapComponent,\n  DEFAULT_STORES,\n  type RetailStore,\n} from './leaflet-store-locator-map.component'\n",
      "type": "registry:block",
      "target": "~/src/app/components/blocks/leaflet-store-locator-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": "Store locator on free OpenStreetMap tiles — no API key. Pins with bound popups, distance/hours/amenities list, fly-to on select.",
  "categories": [
    "dashboard",
    "app",
    "ecommerce"
  ]
}