Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 29 additions & 0 deletions src/components/map/map-view.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ import { afterEach, describe, expect, it, vi } from "vitest";

import MapView from "@/components/map/map-view";

const { flyTo } = vi.hoisted(() => ({ flyTo: vi.fn() }));

vi.mock("react-leaflet", async () => {
const React = await import("react");
const map = {
Expand All @@ -15,6 +17,7 @@ vi.mock("react-leaflet", async () => {
getZoom: () => 18,
getContainer: () => document.createElement("div"),
fitBounds: vi.fn(),
flyTo,
scrollWheelZoom: { disable: vi.fn(), enable: vi.fn(), enabled: () => false },
on: vi.fn(),
off: vi.fn(),
Expand Down Expand Up @@ -88,4 +91,30 @@ describe("MapView configuration", () => {
screen.getByRole("button", { name: /Heathrow Airport, Airport/ }),
).toBeTruthy();
});

it("flies to the user without animation when prefers-reduced-motion is set", () => {
vi.stubEnv("NEXT_PUBLIC_MAPTILER_KEY", "test-key");
vi.stubGlobal(
"ResizeObserver",
class {
observe() {}
disconnect() {}
},
);
vi.stubGlobal("matchMedia", (query: string) => ({
matches: query === "(prefers-reduced-motion: reduce)",
media: query,
addEventListener: () => undefined,
removeEventListener: () => undefined,
}));
flyTo.mockClear();

render(<MapView places={[]} userLocation={{ lat: 19.2, lng: 72.9 }} />);

expect(flyTo).toHaveBeenCalledWith(
[19.2, 72.9],
14,
expect.objectContaining({ animate: false }),
);
});
});
33 changes: 28 additions & 5 deletions src/components/map/map-view.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,14 @@ function pinIcon(color: string): L.DivIcon {
});
}

/** True when the visitor has asked for reduced motion. Guarded for SSR and jsdom. */
function prefersReducedMotion(): boolean {
if (typeof window === "undefined" || typeof window.matchMedia !== "function") {
return false;
}
return window.matchMedia("(prefers-reduced-motion: reduce)").matches;
}

function boundsToBbox(bounds: L.LatLngBounds): [number, number, number, number] {
return [bounds.getWest(), bounds.getSouth(), bounds.getEast(), bounds.getNorth()];
}
Expand Down Expand Up @@ -152,7 +160,10 @@ function ClusteredMarkers({ places }: { places: Place[] }) {
index.getClusterExpansionZoom(clusterId),
18,
);
map.flyTo([lat, lng], expansionZoom, { duration: 0.5 });
map.flyTo([lat, lng], expansionZoom, {
duration: 0.5,
animate: !prefersReducedMotion(),
});
},
keydown: (e) => {
if (e.originalEvent.key !== "Enter" && e.originalEvent.key !== " ")
Expand All @@ -162,7 +173,10 @@ function ClusteredMarkers({ places }: { places: Place[] }) {
index.getClusterExpansionZoom(clusterId),
18,
);
map.flyTo([lat, lng], expansionZoom, { duration: 0.5 });
map.flyTo([lat, lng], expansionZoom, {
duration: 0.5,
animate: !prefersReducedMotion(),
});
},
}}
/>
Expand Down Expand Up @@ -263,7 +277,7 @@ function ClosePopupOnTrigger({ trigger }: { trigger: number }) {
function FlyToUser({ lat, lng }: { lat: number; lng: number }) {
const map = useMap();
useEffect(() => {
map.flyTo([lat, lng], 14, { duration: 1 });
map.flyTo([lat, lng], 14, { duration: 1, animate: !prefersReducedMotion() });
}, [map, lat, lng]);
return null;
}
Expand All @@ -272,7 +286,7 @@ function FlyToUser({ lat, lng }: { lat: number; lng: number }) {
function FlyToPlace({ id, lat, lng }: { id: string; lat: number; lng: number }) {
const map = useMap();
useEffect(() => {
map.flyTo([lat, lng], 15, { duration: 1 });
map.flyTo([lat, lng], 15, { duration: 1, animate: !prefersReducedMotion() });
}, [map, id, lat, lng]);
return null;
}
Expand Down Expand Up @@ -307,7 +321,12 @@ function FlyToBounds({ bounds }: { bounds: Bounds }) {
[bounds.minLat, bounds.minLng],
[bounds.maxLat, bounds.maxLng],
],
{ padding: [48, 48], duration: 1, maxZoom: 15 },
{
padding: [48, 48],
duration: 1,
maxZoom: 15,
animate: !prefersReducedMotion(),
},
);
}, [map, bounds]);
return null;
Expand Down Expand Up @@ -458,6 +477,7 @@ export default function MapView({
}

const focusPlace = focusId ? places.find((place) => place.id === focusId) : undefined;
const reduceMotion = prefersReducedMotion();

// Same colorful MapTiler "streets" style in both themes — every dark-mode
// map style (CARTO, MapTiler, Mapbox alike) is conventionally muted for
Expand Down Expand Up @@ -486,6 +506,9 @@ export default function MapView({
zoomControl={interactive}
attributionControl={interactive}
closePopupOnClick={false}
zoomAnimation={!reduceMotion}
fadeAnimation={!reduceMotion}
markerZoomAnimation={!reduceMotion}
className="size-full"
>
<TileLayer
Expand Down
Loading