# Map View

> Points on a map: price pins for listings or bubbles for where orders come from.

Component · Data · Pro kit · [Live page](https://ui.twentyfirst.media/pro/components/MapView/)

Points on a map: price pins for listings or bubbles for where orders come from. The map itself (tiles from MapLibre, Leaflet or Google, an SVG or an image) goes in `background`; Halo draws pins, popups, legend and controls on top in its own style.

Props: `points` ({id, label, sub, lat, lng, value, text (pin label), tint, pulse}), `bounds` ({n, s, w, e}; what the background shows), `background` (node), `bubbles` (size by value instead of pins), `selected` / `onSelect`, `renderPopup(point)`, `legend`, `controls`, `height`, `label`.

- With a real map library, pass its projection's bounds or render the pins through its overlay API with the same classes (`tf-map-pin`, `tf-map-pop`).
- Price pins show the short price; the popup carries the details.

## Example

```tsx
import * as React from "react";
import { IconButton, MapView } from "@twentyfirstmedia/pro";
import type { MapPoint } from "@twentyfirstmedia/pro";

const world: MapPoint[] = [
  {
    id: "de",
    label: "Germany",
    lat: 51,
    lng: 10,
    value: 820,
    sub: "820 orders today",
  },
  {
    id: "at",
    label: "Austria",
    lat: 47.5,
    lng: 14.5,
    value: 260,
    tint: "mint",
  },
  {
    id: "ch",
    label: "Switzerland",
    lat: 46.8,
    lng: 8.2,
    value: 210,
    tint: "mint",
  },
  {
    id: "us",
    label: "United States",
    lat: 39,
    lng: -98,
    value: 140,
    tint: "butter",
  },
  {
    id: "ae",
    label: "UAE",
    lat: 24,
    lng: 54,
    value: 90,
    tint: "lilac",
    pulse: true,
  },
  {
    id: "br",
    label: "Brazil",
    lat: -12,
    lng: -52,
    value: 60,
    tint: "blush",
  },
];
const city: MapPoint[] = [
  {
    id: 1,
    label: "3-room flat, Eppendorf",
    sub: "€649,000 · 84 m²",
    lat: 53.59,
    lng: 9.98,
    text: "€649k",
  },
  {
    id: 2,
    label: "Loft, HafenCity",
    sub: "€1,190,000 · 112 m²",
    lat: 53.541,
    lng: 10.0,
    text: "€1.19m",
    tint: "lilac",
  },
  {
    id: 3,
    label: "2-room flat, Altona",
    sub: "€1,480 / month",
    lat: 53.55,
    lng: 9.935,
    text: "€1,480",
    tint: "mint",
  },
  {
    id: 4,
    label: "House, Blankenese",
    lat: 53.565,
    lng: 9.81,
    text: "€2.4m",
    tint: "butter",
  },
];
export default function Preview() {
  return (
    <div
      className="tf-grid"
      style={
        {
          "--cols": "1fr 1fr",
          gap: 16,
        } as React.CSSProperties
      }
    >
      <div
        className="tf-panel"
        style={{
          gap: 12,
        }}
      >
        <b>Orders by country</b>
        <MapView bubbles points={world} height={360} legend="Bubble size = orders today" />
      </div>
      <div
        className="tf-panel"
        style={{
          gap: 12,
        }}
      >
        <b>Listings</b>
        <MapView
          points={city}
          bounds={{
            n: 53.62,
            s: 53.52,
            w: 9.78,
            e: 10.06,
          }}
          height={360}
          selected={2}
          controls={[
            <IconButton key={1} icon="plus" label="Zoom in" size="sm" />,
            <IconButton key={2} icon="minus" label="Zoom out" size="sm" />,
          ]}
        />
      </div>
    </div>
  );
}
```

## Props

### IconButton

| Prop | Type | Notes |
| --- | --- | --- |
| `icon` | `IconName` | Required |
| `label` | `string` | Accessible name and tooltip (`aria-label` and `title`); `aria-label` or `title` override them. |
| `variant?` | `"soft" \| "solid" \| "outline" \| "ghost"` |  |
| `size?` | `"sm" \| "md" \| "lg"` |  |
| `type?` | `"button" \| "submit" \| "reset"` |  |
| `onClick?` | `(e: MouseEvent<HTMLButtonElement>) => void` |  |
| `disabled?` | `boolean` |  |
| `asChild?` | `boolean` | Render the single child element (a link) with the button's look; the icon is placed inside it. |
| `children?` | `ReactNode` |  |
| `id?` | `string` |  |
| `name?` | `string` | Form field name and value, sent with the form when this button submits it. |
| `value?` | `string` |  |
| `form?` | `string` | The id of the form this button belongs to, when it sits outside it. |
| `title?` | `string` |  |
| `tabIndex?` | `number` |  |
| `autoFocus?` | `boolean` |  |
| `aria-label?` | `string` |  |
| `aria-labelledby?` | `string` |  |
| `aria-describedby?` | `string` |  |
| `aria-expanded?` | `boolean \| "true" \| "false"` | For disclosure and menu triggers: whether what the button controls is open. |
| `aria-controls?` | `string` |  |
| `aria-haspopup?` | `boolean \| "true" \| "false" \| "menu" \| "listbox" \| "tree" \| "grid" \| "dialog"` |  |
| `aria-pressed?` | `boolean \| "true" \| "false" \| "mixed"` | For toggle buttons. |
| `aria-current?` | `boolean \| "true" \| "false" \| "page" \| "step" \| "location" \| "date" \| "time"` |  |
| `className?` | `string` | Extra class on the root element. |
| `style?` | `CSSProperties` | Inline style on the root element. |

### MapView

| Prop | Type | Notes |
| --- | --- | --- |
| `points?` | `MapPoint[]` |  |
| `bounds?` | `{ n: number; s: number; w: number; e: number }` |  |
| `background?` | `ReactNode` |  |
| `bubbles?` | `boolean` |  |
| `selected?` | `MapPoint["id"] \| null` |  |
| `onSelect?` | `(id: MapPoint["id"] \| null) => void` |  |
| `renderPopup?` | `(p: MapPoint) => ReactNode` |  |
| `legend?` | `ReactNode` |  |
| `controls?` | `ReactNode` |  |
| `height?` | `number \| string` | Height in px (default 320) or any CSS length, e.g. "100%" to fill a FloatingLayout. |
| `label?` | `string` |  |
| `surface?` | `Surface` | `"glass"`: the legend, controls and popup float as glass over the map (for a live map in `background`). |
| `className?` | `string` |  |
| `style?` | `CSSProperties` | Inline style on the root element. |
