fix(map): group collocated user markers
CI / validate (push) Successful in 5m47s
Release / release (push) Successful in 7m55s

This commit is contained in:
dmg
2026-08-01 21:24:49 -04:00
parent ebc7c7df17
commit aa0b757814
10 changed files with 176 additions and 31 deletions
+59 -15
View File
@@ -2,6 +2,7 @@
import type { ReactNode } from "react";
import { useEffect, useRef, useState } from "react";
import { groupMapLocations } from "@/lib/user-location-map";
import type { UserMapLocation } from "./user-world-map";
export function MapViewToggle({ locations, children }: { locations: UserMapLocation[]; children: ReactNode }) {
@@ -40,33 +41,76 @@ function InteractiveMap({ locations }: { locations: UserMapLocation[] }) {
}).addTo(map);
const bounds: [number, number][] = [];
for (const user of locations) {
const marker = leaflet.circleMarker([user.latitude, user.longitude], {
radius: 8,
color: "#eee8d8",
weight: 3,
fillColor: "#a32f1b",
fillOpacity: 1,
}).addTo(map);
for (const group of groupMapLocations(locations)) {
const firstUser = group.locations[0]!;
const isGrouped = group.count > 1;
const marker = isGrouped
? leaflet.marker([group.latitude, group.longitude], {
icon: leaflet.divIcon({
className: "map-user-cluster",
html: `<span aria-hidden="true">${group.count}</span>`,
iconAnchor: [18, 18],
iconSize: [36, 36],
}),
keyboard: true,
}).addTo(map)
: leaflet.circleMarker([group.latitude, group.longitude], {
radius: 8,
color: "#eee8d8",
weight: 3,
fillColor: "#a32f1b",
fillOpacity: 1,
}).addTo(map);
const tooltip = document.createElement("span");
tooltip.textContent = `${user.nickname} · ${user.location}`;
tooltip.textContent = isGrouped
? `${group.count} users · ${group.nicknames.join(" · ")}`
: `${firstUser.nickname} · ${firstUser.location}`;
marker.bindTooltip(tooltip, { direction: "top" });
const userPath = `/admin/users/${user.userId}`;
marker.on("click", () => window.location.assign(userPath));
if (isGrouped) {
const popup = document.createElement("div");
const heading = document.createElement("strong");
heading.textContent = `${group.count} users near ${firstUser.location}`;
popup.append(heading);
const list = document.createElement("ul");
for (const user of group.locations) {
const item = document.createElement("li");
const link = document.createElement("a");
link.href = `/admin/users/${user.userId}`;
link.textContent = user.nickname;
item.append(link);
list.append(item);
}
popup.append(list);
marker.bindPopup(popup);
} else {
marker.on("click", () => window.location.assign(`/admin/users/${firstUser.userId}`));
}
const element = marker.getElement();
element?.setAttribute("aria-label", `${user.nickname}, ${user.location}`);
element?.setAttribute("role", "link");
const label = isGrouped
? `${group.count} users near ${firstUser.location}: ${group.nicknames.join(", ")}`
: `${firstUser.nickname}, ${firstUser.location}`;
element?.setAttribute("aria-label", label);
element?.setAttribute("role", isGrouped ? "button" : "link");
element?.setAttribute("tabindex", "0");
if (isGrouped) {
element?.setAttribute("aria-haspopup", "dialog");
element?.setAttribute("aria-expanded", "false");
marker.on("popupopen", () => element?.setAttribute("aria-expanded", "true"));
marker.on("popupclose", () => element?.setAttribute("aria-expanded", "false"));
}
element?.addEventListener("focus", () => marker.openTooltip());
element?.addEventListener("blur", () => marker.closeTooltip());
element?.addEventListener("keydown", (event) => {
const keyboardEvent = event as KeyboardEvent;
if (keyboardEvent.key === "Enter" || keyboardEvent.key === " ") {
keyboardEvent.preventDefault();
window.location.assign(userPath);
if (isGrouped) marker.openPopup();
else window.location.assign(`/admin/users/${firstUser.userId}`);
}
});
bounds.push([user.latitude, user.longitude]);
bounds.push([group.latitude, group.longitude]);
}
if (bounds.length) map.fitBounds(bounds, { padding: [40, 40], maxZoom: 6 });
cleanup = () => map.remove();