Skip to content

Commit 6368f89

Browse files
authored
polish map search controls (#85)
* polish map search controls * fix map control focus ring * harden map focus coverage * stabilise map search close test
1 parent 2104b82 commit 6368f89

3 files changed

Lines changed: 53 additions & 23 deletions

File tree

e2e/map.spec.ts

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -135,6 +135,19 @@ async function zoomInUntilMapPinsAreDetailed(page: Page) {
135135
await expectMapPinDetailScale(page, 1);
136136
}
137137

138+
async function focusMapControlByKeyboard(page: Page, testId: string) {
139+
for (let i = 0; i < 20; i += 1) {
140+
await page.keyboard.press("Tab");
141+
const activeTestId = await page.evaluate(() =>
142+
document.activeElement?.getAttribute("data-testid")
143+
);
144+
145+
if (activeTestId === testId) return;
146+
}
147+
148+
throw new Error(`Could not focus ${testId} by keyboard`);
149+
}
150+
138151
test("map mounts when IP location is unavailable and restores the last view", async ({
139152
page,
140153
}) => {
@@ -155,6 +168,25 @@ test("map mounts when IP location is unavailable and restores the last view", as
155168
});
156169
expect(await readStoredMapView(page)).toBeNull();
157170

171+
await focusMapControlByKeyboard(page, "map-control-search");
172+
await expect(page.getByTestId("map-control-search")).toBeFocused();
173+
await expect
174+
.poll(() =>
175+
page
176+
.getByTestId("map-control-search")
177+
.evaluate((element) => getComputedStyle(element).boxShadow)
178+
)
179+
.toContain("inset");
180+
await focusMapControlByKeyboard(page, "map-control-zoom-in");
181+
await expect(page.getByTestId("map-control-zoom-in")).toBeFocused();
182+
await expect
183+
.poll(() =>
184+
page
185+
.getByTestId("map-control-zoom-in")
186+
.evaluate((element) => getComputedStyle(element).boxShadow)
187+
)
188+
.toContain("inset");
189+
158190
await page.getByTestId("map-control-zoom-in").click();
159191

160192
await expect
@@ -264,6 +296,12 @@ test("map search palette flies to a picked geocoding result", async ({
264296
await page.getByTestId("map-control-search").click();
265297
const searchInput = page.getByTestId("geocoding-search-input");
266298
await expect(searchInput).toBeFocused();
299+
await expect(page.getByTestId("map-search-close")).toHaveCount(0);
300+
await page.keyboard.press("Escape");
301+
await expect(page.getByTestId("map-search-dialog")).toBeHidden();
302+
303+
await page.getByTestId("map-control-search").click();
304+
await expect(searchInput).toBeFocused();
267305
await searchInput.fill("Newtown");
268306
await expect
269307
.poll(async () =>

src/features/map/components/MapControls.tsx

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ const controlButtonStyles = css`
3838
appearance: none;
3939
border: 0;
4040
background: ${theme.colors.background.top};
41+
box-shadow: var(--map-control-shadow, none);
4142
color: ${theme.colors.text.ui.emptyState};
4243
cursor: pointer;
4344
width: 2rem;
@@ -73,7 +74,17 @@ const controlButtonStyles = css`
7374
}
7475
7576
&:focus-visible {
76-
outline: 3px solid ${theme.colors.focus.outline};
77+
outline: none;
78+
box-shadow:
79+
inset 0 0 0 2px ${theme.colors.focus.outline},
80+
var(--map-control-shadow, 0 0 0 0 transparent);
81+
}
82+
83+
@media (forced-colors: active) {
84+
&:focus-visible {
85+
outline: 2px solid Highlight;
86+
outline-offset: -2px;
87+
}
7788
}
7889
7990
&:disabled {
@@ -112,12 +123,12 @@ const ControlAnchor = styled.div<{ $gap?: boolean }>`
112123

113124
const ControlButton = styled.button<{ $active?: boolean }>`
114125
${controlButtonStyles}
126+
--map-control-shadow:
127+
0 0 0 2px ${theme.colors.border.elevated},
128+
0 0.25rem 0.75rem rgba(0, 0, 0, 0.13);
115129
color: ${({ $active }) =>
116130
$active ? theme.colors.text.primary : theme.colors.text.ui.emptyState};
117131
border-radius: 0.7rem;
118-
box-shadow:
119-
0 0 0 2px ${theme.colors.border.elevated},
120-
0 0.25rem 0.75rem rgba(0, 0, 0, 0.13);
121132
`;
122133

123134
const ZoomGroup = styled.div`

src/features/map/components/MapSearch.tsx

Lines changed: 0 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@ import { useTranslations } from "next-intl";
66
import { keyframes, styled } from "next-yak";
77
import type { GeocodingFeature } from "@maptiler/client";
88

9-
import IconButton from "@/components/IconButton";
109
import { theme } from "@/styles/theme.yak";
1110
import GeocodingSearch from "./GeocodingSearch";
1211

@@ -116,23 +115,13 @@ const DialogContent = styled(Dialog.Content)`
116115
}
117116
`;
118117

119-
const DialogCloseButton = styled(IconButton)`
120-
position: absolute;
121-
right: -0.625rem;
122-
top: -0.625rem;
123-
border-radius: 0.7rem;
124-
box-shadow: 0 0.25rem 0.75rem rgba(0, 0, 0, 0.13);
125-
`;
126-
127118
export default function MapSearch({
128119
countryCode,
129120
onOpenChange,
130121
onPick,
131122
open,
132123
}: MapSearchProps) {
133-
const actionsT = useTranslations("Actions");
134124
const mapT = useTranslations("Map");
135-
const closeLabel = actionsT("close");
136125

137126
return (
138127
<Dialog.Root open={open} onOpenChange={onOpenChange}>
@@ -159,14 +148,6 @@ export default function MapSearch({
159148
proximity="ip"
160149
variant="palette"
161150
/>
162-
<Dialog.Close asChild>
163-
<DialogCloseButton
164-
icon="close"
165-
aria-label={closeLabel}
166-
title={closeLabel}
167-
type="button"
168-
/>
169-
</Dialog.Close>
170151
</DialogContent>
171152
</Dialog.Portal>
172153
</Dialog.Root>

0 commit comments

Comments
 (0)