fix(search): keep the phone results toolbar inside the screen #159

Merged
tudor merged 1 commits from fix/phone-toolbar-width into main 2026-09-30 22:21:17 +00:00
2 changed files with 34 additions and 4 deletions

No files matched your search

+26 -4
View File
@@ -576,7 +576,8 @@ test('a desktop postcode search opens on the map with the list beside it', async
await expect(page.locator('.sc-popup')).toContainText(name);
});
for (const width of [360, 390, 430]) {
// 402 is the iPhone 17, where the toolbar overflowed (see below).
for (const width of [360, 390, 402, 430]) {
test(`a phone opens on the map, with the list a tap away, at ${width}px`, async ({ page }) => {
await page.setViewportSize({ width, height: 800 });
await searchByName(page, 'B1 1BB');
@@ -597,13 +598,34 @@ for (const width of [360, 390, 430]) {
await page.locator('.sc-pin').first().dispatchEvent('click');
const sheet = page.locator('[class*="bottomSheet"]');
await expect(sheet).toBeVisible();
const sheetBox = (await sheet.boundingBox())!;
expect(sheetBox.y + sheetBox.height).toBeLessThanOrEqual(barTop);
expect(sheetBox.y).toBeGreaterThanOrEqual(fabBox.y + fabBox.height);
// The sheet slides up over 0.3s; measure where it comes to rest, not a
// frame of the animation (which reads as below the tab bar).
await expect.poll(async () => {
const box = (await sheet.boundingBox())!;
return Math.round(box.y + box.height);
}).toBeLessThanOrEqual(barTop);
// The dock is anchored at the bottom, so the sheet pushes the button up:
// measure the button again now that the sheet is in.
const fabNow = (await toList.boundingBox())!;
expect((await sheet.boundingBox())!.y).toBeGreaterThanOrEqual(fabNow.y + fabNow.height);
// MOBILE.md: no horizontal overflow, and 44px targets in the new chrome.
expect(await page.evaluate(() => document.documentElement.scrollWidth - window.innerWidth))
.toBe(0);
// scrollWidth alone cannot see this page's overflow: .main clips on x, so
// the search summary ran 40px off an iPhone 17 screen with scrollWidth
// still equal to the viewport. Measure the toolbar's own edges instead;
// the controls row scrolls by design, so only its box is held to the edge.
const offscreen = await page.evaluate(() => {
const toolbar = document.querySelector('[class*="resultsToolbar"]');
return [...(toolbar?.querySelectorAll('*') ?? [])]
.filter((el) => (el as HTMLElement).offsetParent
&& !el.parentElement?.closest('[class*="controlsRow"]'))
.map((el) => ({ el: (el.className?.toString() || el.tagName).slice(0, 40),
right: Math.round(el.getBoundingClientRect().right) }))
.filter((o) => o.right > window.innerWidth);
});
expect(offscreen).toEqual([]);
const small = await page.evaluate(() => {
const toolbar = document.querySelector('[class*="resultsToolbar"]');
const fabEl = document.querySelector('[class*="viewFab"]');
@@ -522,8 +522,16 @@
* everything else, so it must never be the chip scrolled out of sight.
*/
@media (max-width: 640px) {
/*
* nowrap matters as much as column. The desktop rule wraps, and in a
* wrapping flex container each line is as wide as its widest item's content,
* not the container: the search summary ("SW196AR · within 1 mile Edit",
* about 410px) stretched the line, and the controls row with it, 40px past a
* 402px iPhone 17 screen. Single-line, stretch means the container's width.
*/
.filterBar:not(.heroMode) {
flex-direction: column;
flex-wrap: nowrap;
align-items: stretch;
gap: 0.625rem;
}