Location Finder
Loading playground
Overview
LocationFinder is the composite store-locator. It pairs a sidebar (heading, search input with autocomplete, optional filter sheet, scrollable list) with a Google Maps view of the same locations. The layout shifts between two responsive modes:
- Desktop (
>= --lg): sidebar and map render side-by-side. Selecting a row in the list re-centers the map and pops a Google MapsInfoWindowover the marker. - Mobile (
< --lg): sidebar and map become two tabs (List/Map). Selecting a row activates the map tab; the selected store renders as aLocationCardbottom sheet over the map (noInfoWindow).
Search filters by name and address. Filters work via the same AvailableFilter / SelectedFilters shape used by FilterPanelContent. Both are state-only: the parent owns the data and the filter definitions.
LocationFinder composes LocationFinderList and LocationFinderMap internally. Both are exported from @laioutr-core/ui so you can drop them in independently when you need just the list or just the map.
Key Business & UX Benefits
- Search-by-name and search-by-address from the same input means shoppers can find a store by whatever they remember (the brand of the location, the street it sits on, or the neighborhood).
- The Google Maps view with marker selection lets shoppers visually scan a region and tap the closest store, lifting "find a store" conversion on mobile where typing an address is friction.
- Filter sheet shares the
AvailableFilter/SelectedFiltersshape withFilterOffCanvas, so the same filter UI works on a category page and a store finder without per-surface forks. - Tabbed mobile layout keeps the map full-bleed when needed, then collapses cleanly into a list scroll for shoppers who prefer to read text over panning.
API Reference
LocationFinder
| Prop | Default | Type |
|---|---|---|
locationsrequired | LocationFinderMapItem[] { image, id, name, 9 more } | |
apiPromiserequired | GoogleMapsApiPromise | |
mapIdrequired | string | |
heading | string | |
containerStyle | full-width | "full-width" | "boxed" |
suggestions | [] | InputAutocompleteOption[] { value, label, location, 3 more } |
filters | [] | AvailableFilter[] |
filteredCount | number | |
userLocation | { lat, lng } | |
defaultCenter | { lat, lng } | |
defaultZoom | number | |
selectedZoom | number | |
isLoading | false | boolean |
selectedLocationId | string | |
searchQuery | string | |
selectedFilters | object | |
view | "list" | "map" |
| Slot | Type |
|---|---|
empty | {} |
| Event | Type |
|---|---|
details | (event: "details", id: string): void |
navigate | (event: "navigate", id: string): void |
call | (event: "call", id: string): void |
update:selectedLocationId | (event: "update:selectedLocationId", value: string | undefined): void |
update:searchQuery | (event: "update:searchQuery", value: string): void |
update:selectedFilters | (event: "update:selectedFilters", value: SelectedFilters): void |
update:view | (event: "update:view", value: "list" | "map"): void |
The component takes a locations: LocationFinderMapItem[] array (the shape exported from LocationFinderMap), a Google Maps apiPromise and mapId, and a set of optional v-models:
v-model:selectedLocationId: the currently focused store; emitted on row click or marker click.v-model:searchQuery: the text in the search input.v-model:selectedFilters: the active filter selections.v-model:view:'list'or'map'. Only meaningful at mobile breakpoints; ignored at desktop.
Event emits (navigate, call, details) all carry the location id. They forward unchanged from the inner LocationCard / map InfoWindow / list items, so the parent handles routing in one place.
LocationFinderList
Standalone list of locations. Use it when you need a list without the map (e.g. a printable directory page, an admin overview).
| Prop | Default | Type |
|---|---|---|
locationsrequired | LocationFinderListItem[] | |
selectedLocationId | string |
| Event | Type |
|---|---|
details | (event: "details", id: string): void |
navigate | (event: "navigate", id: string): void |
call | (event: "call", id: string): void |
selectLocation | (event: "selectLocation", id: string): void |
Each row renders a LocationCard in the list variant. The list emits select-location (highlight without navigation), navigate, call, and details.
LocationFinderMap
Standalone Google Maps view. Use it when you need a map without the surrounding chrome.
| Prop | Default | Type |
|---|---|---|
locationsrequired | LocationFinderMapItem[] { image, id, name, 9 more } | |
apiPromiserequired | GoogleMapsApiPromiseResolved by the storefront Nuxt plugin (Google Maps JS API). | |
mapIdrequired | string | |
selectedLocationId | string | |
userLocation | { lat, lng } | |
defaultCenter | { lat, lng } | |
defaultZoom | 12 | number |
selectedZoom | 15 | number |
noPopup | false | boolean |
| Event | Type |
|---|---|
details | (event: "details", id: string): void |
navigate | (event: "navigate", id: string): void |
call | (event: "call", id: string): void |
selectLocation | (event: "selectLocation", id: string): void |
Requires a Google Maps apiPromise (a resolved promise of the loaded google.maps namespace) and a mapId (your Google Cloud Map Style ID). The no-popup prop suppresses the InfoWindow overlay. Set it on mobile when a bottom-sheet card handles the selection UI instead.
Composition
The default desktop layout is roughly:
+--------------------------------+--------------------+
| heading | |
| search input | |
| [List | Map] [Filter] | |
| ------------------------------ | map |
| <LocationCard> | |
| <LocationCard> | |
| <LocationCard> | |
| ... | |
+--------------------------------+--------------------+
The sidebar width is themable via --location-finder-sidebar-width (default 414px), and the maximum desktop height via --location-finder-max-height-lg (default 740px).
Slots
#empty: replaces the defaultEmptyStatewhen no locations match the search/filter combination. Use this to surface a brand-tone empty state ("No bakeries within 25 km. Try expanding the radius.") in place of the generic message.
Related
LocationCard: the card rendered for each result row and inside the mobile bottom sheet.LocationDetail: the per-store detail page this component links to viadetails.OpeningHourstype: the shape each location's opening data takes.
Location Detail
Single-location detail layout with a top-of-page Google Maps view and a slot for header, info list, and any other store-detail content.
Marketplace
The Marketplace package provides components and page templates for platform and marketplace business models with multi-vendor support, discovery-driven navigation, and scalable listing structures.