From a7a4a8714a702e6e277f249062be2e0fd4812f03 Mon Sep 17 00:00:00 2001 From: Nico Date: Thu, 4 Jun 2026 20:06:50 -0700 Subject: [PATCH] fix: stabilize mobile map toolbar --- .../src/styles/pages/LocationMapManager.css | 144 ++++++++++-------- frontend/tests/location-map-manager.spec.ts | 50 +++++- 2 files changed, 122 insertions(+), 72 deletions(-) diff --git a/frontend/src/styles/pages/LocationMapManager.css b/frontend/src/styles/pages/LocationMapManager.css index 091d3e8..0f4db52 100644 --- a/frontend/src/styles/pages/LocationMapManager.css +++ b/frontend/src/styles/pages/LocationMapManager.css @@ -986,6 +986,83 @@ } @media (max-width: 839px) { + .location-map-toolbar { + --location-map-mode-slot: clamp(132px, 24vw, 188px); + --location-map-history-slot: 84px; + display: grid; + grid-template-columns: var(--location-map-mode-slot) minmax(0, 1fr); + align-items: center; + justify-content: stretch; + column-gap: 0.35rem; + row-gap: 0.4rem; + padding: 0.55rem 0.65rem; + } + + .location-map-toolbar.has-history-slot { + grid-template-columns: var(--location-map-mode-slot) var(--location-map-history-slot) minmax(0, 1fr); + } + + .location-map-mode-group { + grid-column: 1; + width: var(--location-map-mode-slot); + max-width: var(--location-map-mode-slot); + min-width: 0; + } + + .location-map-mode-buttons { + width: var(--location-map-mode-slot); + max-width: var(--location-map-mode-slot); + min-width: 0; + padding: 0.18rem; + box-sizing: border-box; + } + + .location-map-mode-buttons button { + flex: 1 1 0; + min-width: 0; + min-height: 40px; + padding: 0.35rem 0.42rem; + } + + .location-map-history-buttons { + grid-column: 2; + gap: 0; + width: var(--location-map-history-slot); + justify-self: center; + } + + .location-map-history-buttons button { + min-width: 40px; + min-height: 40px; + padding-inline: 0.35rem; + } + + .location-map-zoom-controls { + grid-column: 2; + gap: 0.05rem; + margin-left: 0; + justify-self: end; + } + + .location-map-toolbar.has-history-slot .location-map-zoom-controls { + grid-column: 3; + } + + .location-map-zoom-controls button { + min-width: 40px; + min-height: 40px; + padding: 0.3rem; + } + + .location-map-zoom-controls span { + min-width: 34px; + font-size: 0.82rem; + } + + .location-map-tool-buttons { + display: none; + } + .location-map-bottom-sheet { max-height: 34dvh; } @@ -1058,43 +1135,13 @@ } .location-map-toolbar { - --location-map-mode-slot: 124px; + --location-map-mode-slot: 132px; --location-map-history-slot: 84px; - display: grid; - grid-template-columns: var(--location-map-mode-slot) minmax(0, 1fr); - align-items: center; - justify-content: stretch; column-gap: 0.15rem; row-gap: 0.35rem; padding: 0.35rem 0.25rem; } - .location-map-toolbar.has-history-slot { - grid-template-columns: var(--location-map-mode-slot) var(--location-map-history-slot) minmax(0, 1fr); - } - - .location-map-mode-group { - grid-column: 1; - width: var(--location-map-mode-slot); - max-width: var(--location-map-mode-slot); - min-width: 0; - } - - .location-map-mode-buttons { - width: var(--location-map-mode-slot); - max-width: var(--location-map-mode-slot); - min-width: 0; - padding: 0.18rem; - box-sizing: border-box; - } - - .location-map-mode-buttons button { - flex: 1 1 0; - min-width: 0; - min-height: 40px; - padding: 0.35rem 0.42rem; - } - .location-map-label-full { display: none; } @@ -1103,43 +1150,8 @@ display: inline; } - .location-map-history-buttons { - grid-column: 2; - gap: 0; - width: var(--location-map-history-slot); - justify-self: center; - } - - .location-map-history-buttons button { - min-width: 40px; - min-height: 40px; - padding-inline: 0.35rem; - } - - .location-map-zoom-controls { - grid-column: 2; - gap: 0.05rem; - margin-left: 0; - justify-self: end; - } - - .location-map-toolbar.has-history-slot .location-map-zoom-controls { - grid-column: 3; - } - - .location-map-zoom-controls button { - min-width: 40px; - min-height: 40px; - padding: 0.3rem; - } - .location-map-zoom-controls span { min-width: 28px; - font-size: 0.82rem; - } - - .location-map-tool-buttons { - display: none; } .location-map-display-controls { diff --git a/frontend/tests/location-map-manager.spec.ts b/frontend/tests/location-map-manager.spec.ts index 82d1054..9ba158f 100644 --- a/frontend/tests/location-map-manager.spec.ts +++ b/frontend/tests/location-map-manager.spec.ts @@ -1437,8 +1437,8 @@ test("mobile keeps viewing draft status compact in the topbar", async ({ page }) expect(statusBox.y).toBeLessThan(titleBox.y + titleBox.height); expect(statusBox.x).toBeGreaterThan(titleBox.x); expect(toolbarBox.height).toBeLessThanOrEqual(64); - expect(modeBox.width).toBeGreaterThanOrEqual(122); - expect(modeBox.width).toBeLessThanOrEqual(126); + expect(modeBox.width).toBeGreaterThanOrEqual(130); + expect(modeBox.width).toBeLessThanOrEqual(136); expect(reservedHistoryBox.width).toBeGreaterThanOrEqual(82); expect(reservedHistoryBox.x).toBeGreaterThan(modeBox.x + modeBox.width - 1); await expect(historyActions).toHaveCSS("visibility", "hidden"); @@ -1468,8 +1468,8 @@ test("mobile keeps viewing draft status compact in the topbar", async ({ page }) expect(Math.abs(editModeBox.x - modeBox.x)).toBeLessThanOrEqual(1); expect(Math.abs(editModeGroupBox.width - modeGroupBox.width)).toBeLessThanOrEqual(1); expect(Math.abs(editModeBox.width - modeBox.width)).toBeLessThanOrEqual(1); - expect(editModeBox.width).toBeGreaterThanOrEqual(122); - expect(editModeBox.width).toBeLessThanOrEqual(126); + expect(editModeBox.width).toBeGreaterThanOrEqual(130); + expect(editModeBox.width).toBeLessThanOrEqual(136); expect(Math.abs(editHistoryBox.width - reservedHistoryBox.width)).toBeLessThanOrEqual(1); expect(editHistoryBox.x).toBeGreaterThan(editModeBox.x + editModeBox.width); expect(editZoomBox.x).toBeGreaterThan(editHistoryBox.x + editHistoryBox.width); @@ -1528,8 +1528,8 @@ test("narrow mobile toolbar keeps map controls tappable without overflow", async expect(toolbarBox.x).toBeGreaterThanOrEqual(0); expect(toolbarBox.x + toolbarBox.width).toBeLessThanOrEqual(361); - expect(modeBox.width).toBeGreaterThanOrEqual(122); - expect(modeBox.width).toBeLessThanOrEqual(126); + expect(modeBox.width).toBeGreaterThanOrEqual(130); + expect(modeBox.width).toBeLessThanOrEqual(136); expect(modeBox.x + modeBox.width).toBeLessThanOrEqual(historyBox.x); expect(zoomBox.y).toBeGreaterThan(modeBox.y + modeBox.height - 1); expect(zoomBox.x).toBeGreaterThanOrEqual(toolbarBox.x); @@ -3898,6 +3898,44 @@ test("compact tablet map starts fitted on first load", async ({ page }) => { }); await page.goto("/stores/100/locations/10/map"); + const toolbar = page.locator(".location-map-toolbar"); + const modeButtons = page.locator(".location-map-mode-buttons"); + const historyActions = page.locator(".location-map-history-buttons"); + const zoomControls = page.locator(".location-map-zoom-controls"); + const viewModeBox = await modeButtons.boundingBox(); + const reservedHistoryBox = await historyActions.boundingBox(); + const viewZoomBox = await zoomControls.boundingBox(); + expect(viewModeBox).not.toBeNull(); + expect(reservedHistoryBox).not.toBeNull(); + expect(viewZoomBox).not.toBeNull(); + if (!viewModeBox || !reservedHistoryBox || !viewZoomBox) { + throw new Error("Compact tablet toolbar layout was not measurable"); + } + expect(viewModeBox.width).toBeGreaterThanOrEqual(180); + expect(viewModeBox.width).toBeLessThanOrEqual(190); + await expect(historyActions).toHaveCSS("visibility", "hidden"); + expect(reservedHistoryBox.x).toBeGreaterThan(viewModeBox.x + viewModeBox.width - 1); + expect(viewZoomBox.x).toBeGreaterThan(reservedHistoryBox.x + reservedHistoryBox.width - 1); + + await page.getByRole("button", { name: "Edit Draft" }).click(); + await expect(historyActions).toHaveCSS("visibility", "visible"); + const toolbarBox = await toolbar.boundingBox(); + const editModeBox = await modeButtons.boundingBox(); + const editHistoryBox = await historyActions.boundingBox(); + const editZoomBox = await zoomControls.boundingBox(); + expect(toolbarBox).not.toBeNull(); + expect(editModeBox).not.toBeNull(); + expect(editHistoryBox).not.toBeNull(); + expect(editZoomBox).not.toBeNull(); + if (!toolbarBox || !editModeBox || !editHistoryBox || !editZoomBox) { + throw new Error("Compact tablet edit toolbar layout was not measurable"); + } + expect(Math.abs(editModeBox.x - viewModeBox.x)).toBeLessThanOrEqual(1); + expect(Math.abs(editModeBox.width - viewModeBox.width)).toBeLessThanOrEqual(1); + expect(editHistoryBox.x).toBeGreaterThan(editModeBox.x + editModeBox.width - 1); + expect(editZoomBox.x).toBeGreaterThan(editHistoryBox.x + editHistoryBox.width - 1); + expect(editZoomBox.x + editZoomBox.width).toBeLessThanOrEqual(toolbarBox.x + toolbarBox.width + 1); + await expectMapFitsCanvas(page, 75, "Compact tablet"); });