From 5df8c93420842eabc40162bf27e4f7293c57c2e5 Mon Sep 17 00:00:00 2001 From: Tudor Date: Thu, 1 Oct 2026 10:18:26 +0100 Subject: [PATCH] fix(search): keep the map list's count and sort on one line Beside the map, "19 schools within 1.0 miles of SW196AR" (359px) and the sort menu (281px, as wide as its longest option) could not share the pane's 427px, so the sort dropped to a second line. In the map view the heading now reads "19 schools within 1 mile": the postcode is already in the search box just above, and the radius reads as the toolbar's does. The sort menu is capped at 11rem, truncating a long choice, and the header no longer wraps; on a narrow pane the count truncates rather than pushing the sort down. List view keeps the full heading. Tried on staging by injecting the change: 243px + 176px, one line at 1440. Co-Authored-By: Claude Opus 5.5 --- e2e/tests/journeys.spec.ts | 6 +++++ .../components/ResultsMapView.test.tsx | 9 +++++++- nextjs-app/components/HomeView.module.css | 23 +++++++++++++++++-- nextjs-app/components/HomeView.tsx | 12 +++++++--- 4 files changed, 44 insertions(+), 6 deletions(-) diff --git a/e2e/tests/journeys.spec.ts b/e2e/tests/journeys.spec.ts index b7ab141..2d1f881 100644 --- a/e2e/tests/journeys.spec.ts +++ b/e2e/tests/journeys.spec.ts @@ -567,6 +567,12 @@ test('a desktop postcode search opens on the map with the list beside it', async await expect(card.getByRole('button', { name: /Compar/ })).toBeVisible(); await expect(page.locator('.sc-pin').first()).toBeVisible({ timeout: 15_000 }); + // The count and the sort share one line at the top of the list. + const heading = await pane.getByRole('heading', { level: 2 }).boundingBox(); + const sort = await pane.getByRole('combobox').boundingBox(); + expect(Math.abs((heading!.y + heading!.height / 2) - (sort!.y + sort!.height / 2))) + .toBeLessThan(8); + // The split runs to the bottom of the screen rather than stopping short. const split = await page.locator('[class*="mapViewContainer"]').boundingBox(); expect(split!.y + split!.height).toBeGreaterThan(800 - 40); diff --git a/nextjs-app/__tests__/components/ResultsMapView.test.tsx b/nextjs-app/__tests__/components/ResultsMapView.test.tsx index 332dbea..812a81f 100644 --- a/nextjs-app/__tests__/components/ResultsMapView.test.tsx +++ b/nextjs-app/__tests__/components/ResultsMapView.test.tsx @@ -94,7 +94,8 @@ it('lists a name search, which has no map', async () => { it('puts the count and the sort in the list beside the map, once', async () => { await renderMap(); - expect(screen.getAllByRole('heading', { name: /3 schools within 1.0 miles of SW196AR/ })).toHaveLength(1); + // Short beside the map, so it shares one line with the sort. + expect(screen.getAllByRole('heading', { name: '3 schools within 1 mile' })).toHaveLength(1); expect(screen.getAllByRole('combobox')).toHaveLength(1); }); @@ -172,3 +173,9 @@ it('lets a keyboard pick a pin from the list, with a real button', async () => { expect(screen.getByTestId('map')).toHaveAttribute('data-selected', '2'); expect(show).toHaveAttribute('aria-pressed', 'true'); }); + +it('keeps the postcode in the heading in list view, where there is room', async () => { + await renderMap(); + fireEvent.click(screen.getByRole('button', { name: 'List' })); + expect(screen.getByRole('heading', { name: '3 schools within 1.0 miles of SW196AR' })).toBeInTheDocument(); +}); diff --git a/nextjs-app/components/HomeView.module.css b/nextjs-app/components/HomeView.module.css index 7ea8c08..75ce66d 100644 --- a/nextjs-app/components/HomeView.module.css +++ b/nextjs-app/components/HomeView.module.css @@ -608,13 +608,32 @@ border-right: 1px solid var(--border); } +/* One line: the count takes what the sort leaves, and truncates rather than + wrap if a narrow pane cannot fit both. */ .mapListPane .resultsHeader { - gap: 0.5rem; + flex-wrap: nowrap; + gap: 0.75rem; padding: 0.875rem 1rem 0.75rem; } .mapListPane .resultsHeader h2 { - font-size: var(--step-1); + flex: 1; + min-width: 0; + font-size: var(--step-0); + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; +} + +.mapListPane .resultsHeaderActions { + flex-shrink: 0; +} + +/* A select is as wide as its longest option ("Highest Reading, Writing & + Maths %", 281px), more than half the pane. Cap it; a long choice truncates. */ +.mapListPane .sortSelect { + max-width: 11rem; + text-overflow: ellipsis; } .mapContainer { diff --git a/nextjs-app/components/HomeView.tsx b/nextjs-app/components/HomeView.tsx index ceb5ce8..bcbec00 100644 --- a/nextjs-app/components/HomeView.tsx +++ b/nextjs-app/components/HomeView.tsx @@ -583,14 +583,20 @@ export function HomeView({ initialSchools, filters, totalSchools, howItWorks, ed ) ); + const schoolCount = `${initialSchools.total.toLocaleString()} school${initialSchools.total !== 1 ? 's' : ''}`; + // The count and the sort. Above the list in list view; at the top of the // list pane, beside the map, in map view. const resultsHeader = (

- {isLocationSearch && initialSchools.location_info - ? `${initialSchools.total.toLocaleString()} school${initialSchools.total !== 1 ? 's' : ''} within ${(initialSchools.location_info.radius / 1.60934).toFixed(1)} miles of ${initialSchools.location_info.postcode}` - : `${initialSchools.total.toLocaleString()} school${initialSchools.total !== 1 ? 's' : ''} found` + {/* Beside the map the heading shares one line with the sort, so it + drops the postcode, which the search box just above already shows. */} + {isMapView && radiusMiles != null + ? `${schoolCount} within ${radiusMiles} mile${radiusMiles === 1 ? '' : 's'}` + : isLocationSearch && initialSchools.location_info + ? `${schoolCount} within ${(initialSchools.location_info.radius / 1.60934).toFixed(1)} miles of ${initialSchools.location_info.postcode}` + : `${schoolCount} found` }