{
  "$schema": "https://shadcn-vue.com/schema/registry-item.json",
  "name": "leaflet-store-locator-map",
  "title": "Leaflet Store Locator Map",
  "type": "registry:block",
  "files": [
    {
      "path": "packages/registry-vue/blocks/leaflet-store-locator-map/LeafletStoreLocatorMap.vue",
      "content": "<script setup lang=\"ts\">\nimport type { HTMLAttributes } from 'vue'\nimport { computed, ref } from 'vue'\nimport { cn } from '@/lib/utils'\nimport { Badge } from '@/components/ui/badge'\nimport { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'\nimport { LeafletMap, LeafletMarker, LeafletPopup, type LeafletMapRef } from '@/components/ui/leaflet-map'\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 interface LeafletStoreLocatorMapProps {\n  class?: HTMLAttributes['class']\n  stores?: RetailStore[]\n}\n\nconst 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\nconst props = defineProps<LeafletStoreLocatorMapProps>()\n\nconst stores = computed(() => props.stores ?? DEFAULT_STORES)\nconst selectedId = ref(DEFAULT_STORES[0]?.id ?? '')\n\nconst mapRef = ref<LeafletMapRef | null>(null)\n\nfunction selectStore(store: RetailStore) {\n  selectedId.value = store.id\n  mapRef.value?.flyTo?.({\n    center: [store.lng, store.lat],\n    zoom: 14,\n    duration: 900,\n  })\n}\n</script>\n\n<template>\n  <div data-slot=\"leaflet-store-locator-map\" :class=\"cn('flex h-full min-h-0 flex-col gap-4', props.class)\">\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\">\n        {{ stores.filter((s) => s.isOpen).length }} open · {{ stores.length }} locations\n      </p>\n    </div>\n\n    <div class=\"grid min-h-0 flex-1 grid-cols-1 gap-4 lg:grid-cols-12\">\n      <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          <LeafletMap\n            ref=\"mapRef\"\n            variant=\"streets\"\n            :center=\"[-73.985, 40.748]\"\n            :zoom=\"13\"\n            class=\"absolute inset-0 size-full\"\n          >\n            <LeafletMarker v-for=\"store in stores\" :key=\"store.id\" :lng-lat=\"[store.lng, store.lat]\" anchor=\"center\">\n              <button\n                type=\"button\"\n                class=\"rounded-full border px-2 py-0.5 text-xs font-semibold shadow-xs\"\n                :class=\"\n                  selectedId === store.id\n                    ? 'border-primary bg-primary text-primary-foreground'\n                    : 'border-border bg-card text-foreground'\n                \"\n                @click=\"selectStore(store)\"\n              >\n                {{ store.name }}\n              </button>\n              <LeafletPopup :offset=\"[0, -18]\" class=\"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              </LeafletPopup>\n            </LeafletMarker>\n          </LeafletMap>\n        </div>\n      </Card>\n\n      <Card class=\"border-border flex min-h-0 flex-col overflow-hidden shadow-xs lg:col-span-4\">\n        <CardHeader class=\"border-border border-b p-4\">\n          <CardTitle class=\"text-sm font-semibold\">Locations</CardTitle>\n        </CardHeader>\n        <CardContent class=\"divide-border min-h-0 flex-1 divide-y overflow-auto p-0\">\n          <button\n            v-for=\"store in stores\"\n            :key=\"store.id\"\n            type=\"button\"\n            :class=\"\n              cn(\n                'hover:bg-accent/50 flex w-full items-center justify-between gap-3 px-4 py-3 text-left transition-colors',\n                selectedId === store.id && 'bg-accent/60',\n              )\n            \"\n            @click=\"selectStore(store)\"\n          >\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                <Badge v-for=\"amenity in store.amenities\" :key=\"amenity\" variant=\"secondary\" class=\"text-xs\">\n                  {{ amenity }}\n                </Badge>\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              <Badge :variant=\"store.isOpen ? 'info' : 'secondary'\" class=\"text-xs\">\n                {{ store.isOpen ? 'Open' : 'Closed' }}\n              </Badge>\n            </div>\n          </button>\n        </CardContent>\n      </Card>\n    </div>\n  </div>\n</template>\n",
      "type": "registry:block",
      "target": "~/app/components/blocks/LeafletStoreLocatorMap.vue"
    }
  ],
  "dependencies": [],
  "devDependencies": [],
  "registryDependencies": [
    "https://uipkge.dev/r/vue/badge.json",
    "https://uipkge.dev/r/vue/card.json",
    "https://uipkge.dev/r/vue/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"
  ]
}