Design-system component · Media

DsMap

An interactive map with markers, built like the site's: Leaflet, the site's map style, prices on the markers.

import DsMap from "#core/design-system/components/DsMap.vue";

Demo

Markers with prices, a dot without one; the selected one is dark. Click to select.
Drawing an area: by its corners (the site on desktop) or with a finger (the site on phones). Once drawn, its corners can be dragged.
An area already drawn, the rest dimmed (mask)
An area that can't be used (invalid), with a page control over it

Use it wherever the site shows a map: the results on a map, a listing's position, the agencies in a zone. It works like the live map: drag, zoom, and click a marker to select it.

The tiles are the site's MapTiler style when there's a key, and OpenStreetMap's otherwise: same streets and places, other colors. The credits in the corner say which (© MapTiler or only © OpenStreetMap contributors). The key goes in .env as VITE_MAPTILER_KEY (copy .env.example, then restart the dev server); devs have it.

It doesn't group nearby markers into clusters: show the state with fewer markers, and describe the behaviour in the handoff.

Drawing an area

People can draw the area to search on the map, as on the site. Two ways, the site's two:

  • points: click each corner, then the first one again (or double click) to close. The site does this on desktop.
  • freehand: drag a finger (or the mouse) around the area. The site does this on phones.

The page decides everything around it, so each step can be a mockup state: start drawing with v-model:draw (set it to a mode; it goes back to false when the area is closed, or when Esc cancels), read and set the area with v-model:area, and put the buttons and hints over the map. editable lets people drag the corners once it's drawn; invalid draws it red ("area troppo grande"); mask dims everything outside it, like the results for a drawn area.

<script setup lang="ts">
import DsMap, { type DsMapArea, type DsMapDrawMode } from "#core/design-system/components/DsMap.vue";

const area = ref<DsMapArea | null>(null);
const draw = ref<DsMapDrawMode | false>(false);
</script>

<template>
  <DsMap v-model:area="area" v-model:draw="draw" label="Disegna l'area della ricerca" editable class="tw-h-full">
    <BaseButton v-if="!area && !draw" class="tw-absolute tw-bottom-4 tw-left-1/2 -tw-translate-x-1/2" @click="draw = 'points'">
      Disegna area
    </BaseButton>
  </DsMap>
</template>

For a state with an area already drawn, give area corners from the mocks ([[45.4548, 9.1655], …]). Drawing has no keyboard version, on the site either: design another way to choose the place (a search, "vicino a me") next to it.

Usage

<script setup lang="ts">
import DsMap, { type DsMapMarker } from "#core/design-system/components/DsMap.vue";

const selected = ref<string | number | null>(null);
const markers: DsMapMarker[] = [
  { id: 1, lat: 45.4523, lng: 9.1637, label: "385.000 €", name: "Trilocale in via Tortona, 385.000 €" },
  { id: 2, lat: 45.4581, lng: 9.1808, label: "520.000 €", name: "Quadrilocale in corso Genova, 520.000 €" },
];
</script>

<template>
  <DsMap v-model:selected="selected" label="Mappa degli annunci a Milano" :markers="markers" />
</template>
  • Size: full width and 16:9 by default. A class on <DsMap> changes it: tw-h-full to fill a panel that has a height, tw-aspect-square for a square, tw-w-1/2 for half the width.
  • Where it looks: without center, it fits its markers; with no markers either, it shows Milan, like the site.
  • Markers: label is what the marker shows (the price); without it, the marker is a dot. name is what a screen reader says.
  • On phones there are no zoom buttons, as on every map of the site: people pinch. On a phone, the site's map usually fills the screen (tw-h-full).
  • Controls over the map: what you put inside <DsMap> sits over it, above the tiles and markers: the round "my position" button, "Disegna area" and "Salva ricerca", the strip of cards for the selected marker. Position each control with classes (tw-absolute tw-bottom-4 tw-left-3); the map still drags everywhere else.
<DsMap label="Mappa degli annunci" :markers="markers" class="tw-h-full">
  <button type="button" aria-label="Usa la mia posizione" class="tw-absolute tw-bottom-4 tw-left-3 …">
    <GpsIcon :width="24" :height="24" />
  </button>
</DsMap>

Do / Don't

  • Do give every marker a name that says what it is, not only its price.
  • Do design what happens on selection (the listing card opens, the list scrolls to it) as a state of the page, with v-model:selected.
  • Don't use a screenshot of a map: designers can't move it, and devs can't tell which markers it shows.
  • Don't put the map in a box with no height: it needs one to show.

API

PropTypeDefaultDescription
labelstringWhat the map shows, for screen readers ("Mappa degli annunci a Milano").
markers?DsMapMarker[][]The markers. Changing the list redraws them.
center?[number, number] | nullnull[latitude, longitude]. Without it, the map fits its area or its markers, or shows Milan like the site.
zoom?number13Zoom level with `center`: 13 shows a city, 16 a few streets.
editable?booleanfalseThe area's corners can be dragged, like the site's on desktop once it's drawn.
invalid?booleanfalseThe area is drawn red: it can't be used (too large, outside Italy…).
mask?booleanfalseEverything outside the area is dimmed, like the site's results for a drawn area.
selected?string | number | nullnullThe selected marker's id (`v-model:selected`). It's drawn dark; clicking a marker selects it.
area?DsMapArea | nullnullThe drawn area (`v-model:area`), or null. Set it to show an area already drawn.
draw?false | DsMapDrawModefalseDrawing (`v-model:draw`): set it to a mode to start. It goes back to `false` once the area is closed, or when Esc cancels. While drawing, a new area replaces the old one.

Slots: default