# Location cell
> A world thumbnail with one point for table cells. Hover or focus it and a popover grows out of the point while the map inside zooms in on it.
- React: `import { GeoMiniMap } from "@/components/lumesec/geo-mini-map"`
- Collection: Location (https://elements.lumesec.ai/components/location)
- Data: takes your data (`data`); shows demo data until you pass it
- Registry item: https://elements.lumesec.ai/r/geo-mini-map.json
- Page: https://elements.lumesec.ai/components/location/mini-map



Live preview: https://elements.lumesec.ai/view/geo-mini-map

Demo source:

```tsx
import { GeoMiniMap, type GeoMiniMapTone, type MiniMapLocation } from "@/components/lumesec/geo-mini-map";
import { findCity } from "@/lib/lumesec/geo-data";

interface SignIn {
  id: string;
  user: string;
  device: string;
  time: string;
  location: MiniMapLocation;
  tone?: GeoMiniMapTone;
}

/** A demo city as a location, with an invented address from the documentation ranges. */
function place(id: string, detail: string): MiniMapLocation {
  const city = findCity(id);
  if (!city) return { lat: 0, lon: 0 };
  return { lat: city.lat, lon: city.lon, label: city.name, detail, timeZone: city.timeZone };
}

const OFFICE = findCity("fra");
const FROM = OFFICE ? { lat: OFFICE.lat, lon: OFFICE.lon, label: OFFICE.name } : undefined;

const SIGN_INS: SignIn[] = [
  { id: "s1", user: "Priya Raman", device: "Laptop", time: "09:41", location: place("blr", "203.0.113.24") },
  { id: "s2", user: "Jonas Weber", device: "Phone", time: "09:12", location: place("ham", "198.51.100.7") },
  { id: "s3", user: "Lucía Ortega", device: "Laptop", time: "08:57", location: place("gru", "192.0.2.140") },
  { id: "s4", user: "Kenji Mori", device: "Tablet", time: "08:30", location: place("nrt", "203.0.113.88") },
  {
    id: "s5",
    user: "Amara Okafor",
    device: "New device",
    time: "07:58",
    location: place("los", "198.51.100.212 · first sign-in from here"),
    tone: "warning",
  },
];

export default function GeoMiniMapDemo() {
  return (
    <div className="w-full max-w-[400px] rounded-[14px] border border-border bg-card px-4 pt-3.5 pb-1.5 shadow-[0_14px_34px_-20px_rgb(0_0_0/0.4)]">
      <div className="flex items-baseline justify-between gap-3">
        <h3 className="m-0 text-[13px] leading-5 font-medium text-foreground">Recent sign-ins</h3>
        <span className="text-[12px] leading-5 text-muted-foreground">Distances from {FROM?.label}</span>
      </div>
      <table className="mt-2 w-full border-collapse text-left">
        <thead>
          <tr className="text-[11.5px] leading-4 text-muted-foreground">
            <th scope="col" className="pb-1.5 font-normal">
              User
            </th>
            <th scope="col" className="w-[64px] pb-1.5 font-normal">
              UTC
            </th>
            <th scope="col" className="w-[98px] pb-1.5 font-normal">
              Location
            </th>
          </tr>
        </thead>
        <tbody>
          {SIGN_INS.map((signIn) => (
            <tr key={signIn.id} className="border-t border-border">
              <td className="max-w-0 py-[7px] pr-3">
                <span className="block truncate text-[13px] leading-5 text-foreground">{signIn.user}</span>
                <span className="block truncate text-[12px] leading-4 text-muted-foreground">{signIn.device}</span>
              </td>
              <td className="py-[7px] font-mono text-[12.5px] text-muted-foreground tabular-nums">{signIn.time}</td>
              <td className="py-[7px]">
                <GeoMiniMap location={signIn.location} from={FROM} tone={signIn.tone} />
              </td>
            </tr>
          ))}
        </tbody>
      </table>
    </div>
  );
}
```

> **Your data:** Pass your place as `location` (`{ lat, lon, label?, detail?, timeZone? }`) and, for the distance row, a reference point as `from`. Without `location` the cell shows a demo location (Singapore). See [Your data](/docs/data).

## Playground

Change a prop and the component re-renders. Props marked remounts set an initial value, so the component starts over.

## Installation

```bash
npx shadcn@latest add @lumesec/geo-mini-map
```

First time with the @lumesec registry? Register it once, or install by URL:

```bash
npx shadcn@latest registry add @lumesec=https://elements.lumesec.ai/r/{name}.json
npx shadcn@latest add https://elements.lumesec.ai/r/geo-mini-map.json
```

## Usage

React:

```tsx
import { GeoMiniMap, type MiniMapLocation } from "@/components/lumesec/geo-mini-map";

const office = { lat: 50.1109, lon: 8.6821, label: "Frankfurt" };

export function SignInLocation({ location }: { location: MiniMapLocation }) {
  return <GeoMiniMap location={location} from={office} />;
}
```

## Behaviour

A 96 × 48 world thumbnail with one point, sized for table cells and list rows.

Hover or focus it and a popover grows out of the point while the map inside dives toward it: finer land resolves outward from the point and a ring of dots pings where it lands. The place name, coordinates, local time and an optional distance appear as text. Thumbnails of one size share one baked lattice, so a table full of them stays light.

## API reference

### Props

Also accepts every prop of `<button>` (`React.ComponentProps<"button">`), spread onto the root element.

| Prop           | Type                                                            | Default            | Description                                                                                                                                                                                                                   |
| -------------- | --------------------------------------------------------------- | ------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `location`     | `MiniMapLocation`                                               | `Singapore (demo)` | The place: `{ lat, lon, label?, detail?, timeZone? }`. `label` titles the popover and starts the accessible name, `detail` is the line under it, and a valid IANA `timeZone` adds the local time. Missing fields are omitted. |
| `from`         | `MiniMapReference`                                              | —                  | Reference point for the distance row: `{ lat, lon, label? }`, read as 'From Frankfurt 7,406 km'. Without it the row is omitted.                                                                                               |
| `open`         | `boolean`                                                       | —                  | Controlled popover state. Use with `onOpenChange`.                                                                                                                                                                            |
| `defaultOpen`  | `boolean`                                                       | `false`            | Popover state when uncontrolled.                                                                                                                                                                                              |
| `onOpenChange` | `(open: boolean) => void`                                       | —                  | Called when hover, focus, a click, Escape or another opening popover opens or closes this one.                                                                                                                                |
| `size`         | `"sm" \| "md" \| "lg"`                                          | `"md"`             | Thumbnail size: 72 × 36, 96 × 48 or 128 × 64 px, plus a 1 px border.                                                                                                                                                          |
| `side`         | `"auto" \| "top" \| "bottom"`                                   | `"auto"`           | Where the popover opens. `auto` opens above and flips below when the card does not fit above and there is more room below.                                                                                                    |
| `zoom`         | `number`                                                        | `4`                | Zoom of the popover map, clamped to 2 to 6. At 4 the map shows about 90° of longitude around the point.                                                                                                                       |
| `unit`         | `"km" \| "mi"`                                                  | `"km"`             | Unit of the distance row and the scale bar.                                                                                                                                                                                   |
| `tone`         | `"accent" \| "info" \| "success" \| "warning" \| "destructive"` | `"accent"`         | Colour of the point, the marker and its ring, for example `warning` for a sign-in from a new place.                                                                                                                           |

### Ref

`ref` points at the root `HTMLButtonElement`.

## Keyboard

| Keys          | Action                                                                                            |
| ------------- | ------------------------------------------------------------------------------------------------- |
| Tab           | Focus the thumbnail. The popover opens at once and closes when focus leaves, unless it is pinned. |
| Enter / Space | Pin the popover, or unpin and close it.                                                           |
| Escape        | Close the most recently opened popover. Focus stays on the thumbnail.                             |

## Accessibility

* The trigger is a native `button` with `aria-expanded`, `aria-controls` and `aria-pressed` for the pinned state.
* Its accessible name is the place and its coordinates ('Tokyo, 35.6762° N, 139.6503° E'), so the cell makes sense without opening it. An `aria-label` you pass replaces it.
* `aria-describedby` points at the popover's detail line and its local time and distance rows; ids you pass in `aria-describedby` are kept.
* The popover is non-modal text in the top layer. It takes no focus and holds no controls.
* Both canvases are `aria-hidden`. Every readable value (name, coordinates, time, distance, scale) is DOM text.
* Focus shows a 2 px accent outline.
* Reduced motion: the popover fades in over 120 ms at full size with the map already zoomed in, the text rows appear without sliding, numbers swap without rolling, and there is no resolution sweep or ring ping.

## Theming

Styled with Tailwind classes on your shadcn theme tokens, so light and dark follow your theme. The accent comes from `--lumesec`. See [Theming](/docs/theming).

This component reads `--card`, `--border`, `--popover`, `--popover-foreground`, `--foreground`, `--muted-foreground`, `--lumesec`, `--lumesec-glint`, `--lumesec-info`, `--lumesec-success`, `--lumesec-warning` and `--destructive`.

## Notes

* Opening: the card grows out of the point on a spring (scale 0.04 to 1, with a slight overshoot). The map inside starts on the world and dives toward the point; finer land resolves outward from the point behind an uneven accent front, the point lands as a 4 px marker and a ring of dots pings around it. Closing shrinks the card back into the point on a faster spring.
* Hover opens the popover after 120 ms; the pointer leaving both the thumbnail and the popover closes it after 150 ms. Focus opens it at once. A click, Enter or Space pins it, and pressing inside the popover pins it too. On touch, a tap toggles the pinned popover.
* Only one unpinned popover is open per page: opening one closes the others. Pinned popovers stay until they are unpinned or closed with Escape. Pinning an open popover pings its point again.
* The popover uses the Popover API (`popover="manual"`), so a table cell with `overflow: hidden` cannot clip it. It is centred over the point, kept 8 px inside the viewport, follows the thumbnail on scroll and resize, and closes when the thumbnail scrolls out of view. Without the Popover API it is a fixed card with `z-index: 50`.
* The component renders the button and the popover as siblings. Clicks inside the popover do not bubble to row handlers around it.
* Thumbnails of one size share a single baked lattice per pixel ratio and theme, so a long table draws one image per row. The zoom levels of a place are built once, while the hover delay runs, and cached for the page.
* The local time is read after mount and formatted in the place's `timeZone`, never the viewer's. The scale bar gives a 1-2-5 distance at the point's latitude.
* The land comes from the embedded world dataset at 0.5° resolution. Some coastal cities, Singapore among them, fall on water cells, so the marker can sit just off the dotted coast.


