Location

Location Finder

Storybook
Store-locator layout combining search, filters, a list panel, and a Google Maps view. Side-by-side at desktop, tabbed at mobile, with a selected-location bottom sheet over the map.

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 Maps InfoWindow over the marker.
  • Mobile (< --lg): sidebar and map become two tabs (List / Map). Selecting a row activates the map tab; the selected store renders as a LocationCard bottom sheet over the map (no InfoWindow).

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 / SelectedFilters shape with FilterOffCanvas, 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

PropDefaultType
locationsrequired
apiPromiserequiredGoogleMapsApiPromise
mapIdrequiredstring
headingstring
containerStylefull-width"full-width" | "boxed"
suggestions[]
filters[]
filteredCountnumber
userLocation
defaultCenter
defaultZoomnumber
selectedZoomnumber
isLoadingfalseboolean
selectedLocationIdstring
searchQuerystring
selectedFiltersobject
view"list" | "map"
SlotType
empty{}
EventType
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).

PropDefaultType
locationsrequiredLocationFinderListItem[]
selectedLocationIdstring
EventType
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.

PropDefaultType
locationsrequired
apiPromiserequiredGoogleMapsApiPromise

Resolved by the storefront Nuxt plugin (Google Maps JS API).

mapIdrequiredstring
selectedLocationIdstring
userLocation
defaultCenter
defaultZoom12number
selectedZoom15number
noPopupfalseboolean
EventType
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 default EmptyState when 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.
  • LocationCard: the card rendered for each result row and inside the mobile bottom sheet.
  • LocationDetail: the per-store detail page this component links to via details.
  • OpeningHours type: the shape each location's opening data takes.
Copyright © 2026 Laioutr GmbH