From 6fc158dc6735d1d8c12d6411ea059584bb7e6670 Mon Sep 17 00:00:00 2001 From: BigSimmo <87357024+BigSimmo@users.noreply.github.com> Date: Sat, 4 Jul 2026 16:05:53 +0800 Subject: [PATCH] feat(ui): compact phone bottom search on search/result views for max screen space --- src/app/globals.css | 10 +++++++++ src/components/ClinicalDashboard.tsx | 13 ++++++++++-- .../global-mockup-search-shell.tsx | 8 ++++++- .../master-search-header.tsx | 12 ++++++++++- tests/ui-tools.spec.ts | 21 +++++++++++++++---- 5 files changed, 56 insertions(+), 8 deletions(-) diff --git a/src/app/globals.css b/src/app/globals.css index b6fa79258..c0b869cb0 100644 --- a/src/app/globals.css +++ b/src/app/globals.css @@ -1091,6 +1091,16 @@ summary::-webkit-details-marker { transform: translateX(-50%); } + /* Compact search/result views: no chip row below the pill, so the pill + itself hugs the bottom edge and the scrim shrinks to match. */ + .document-mobile-search-edge.answer-footer-search-edge.document-mobile-search-compact { + bottom: max(0.4rem, calc(var(--safe-area-bottom) + 0.3rem)); + } + + .document-mobile-search-compact .answer-footer-search-backdrop { + height: max(5rem, calc(var(--safe-area-bottom) + 4.4rem)); + } + .document-mobile-search-pill { min-height: 3.6rem; border-color: var(--border-strong); diff --git a/src/components/ClinicalDashboard.tsx b/src/components/ClinicalDashboard.tsx index 49ba42517..1f8deade7 100644 --- a/src/components/ClinicalDashboard.tsx +++ b/src/components/ClinicalDashboard.tsx @@ -5373,6 +5373,10 @@ export function ClinicalDashboard({ // stay top-aligned so their lists start in a stable position. const centeredModeHome = showDesktopHomeComposer && activeModeResultKind !== "tools" && activeModeResultKind !== "favourites"; + // Submitted (non-answer) searches are result views, not mode homes: on phones + // the bottom composer drops its chip row and hugs the screen edge so results + // keep maximum vertical space. Mode homes keep the default chip-row layout. + const compactMobileBottomSearch = hasMobileBottomSearch && modeSearchSubmitted; const renderDegradedNotice = () => ( @@ -5558,7 +5563,9 @@ export function ClinicalDashboard({ searchMode === "answer" ? "mb-[calc(5.25rem+env(safe-area-inset-bottom))] sm:mb-24" : hasMobileBottomSearch - ? "mb-[calc(5.25rem+env(safe-area-inset-bottom))] sm:mb-0" + ? compactMobileBottomSearch + ? "mb-[calc(4.5rem+env(safe-area-inset-bottom))] sm:mb-0" + : "mb-[calc(5.25rem+env(safe-area-inset-bottom))] sm:mb-0" : "mb-0", )} > @@ -5569,7 +5576,9 @@ export function ClinicalDashboard({ searchMode === "answer" ? "pb-32 sm:pb-36 lg:pb-40" : hasMobileBottomSearch - ? "pb-32 sm:pb-10 lg:pb-12" + ? compactMobileBottomSearch + ? "pb-8 sm:pb-10 lg:pb-12" + : "pb-32 sm:pb-10 lg:pb-12" : "pb-8 sm:pb-10 lg:pb-12", )} > diff --git a/src/components/clinical-dashboard/global-mockup-search-shell.tsx b/src/components/clinical-dashboard/global-mockup-search-shell.tsx index 98edfc237..2cfab6653 100644 --- a/src/components/clinical-dashboard/global-mockup-search-shell.tsx +++ b/src/components/clinical-dashboard/global-mockup-search-shell.tsx @@ -363,6 +363,10 @@ function GlobalMockupSearchShellClient({ queryInputRef={inputRef} headerVariant={isDifferentialPresentationWorkflow ? "workflow" : "default"} mobileSearchPlacement="bottom" + // Submitted searches that stay in the shell (services results) are + // result views: compact the phone bottom composer so results keep + // maximum screen space. Mode homes keep the chip-row layout. + mobileBottomSearchVariant={hasSubmittedModeSearch ? "compact" : "default"} desktopSearchPlacement={ (desktopSearchPlacement === "hero" || isFormsOnlyShell) && isStandaloneModeHome ? "hero" : "default" } @@ -381,7 +385,9 @@ function GlobalMockupSearchShellClient({ ? "pb-8" : searchMode === "answer" ? "pb-[calc(9rem+env(safe-area-inset-bottom))]" - : "pb-[calc(9rem+env(safe-area-inset-bottom))] sm:pb-8", + : hasSubmittedModeSearch + ? "pb-[calc(5.5rem+env(safe-area-inset-bottom))] sm:pb-8" + : "pb-[calc(9rem+env(safe-area-inset-bottom))] sm:pb-8", )} > {children} diff --git a/src/components/clinical-dashboard/master-search-header.tsx b/src/components/clinical-dashboard/master-search-header.tsx index 99cbde6b4..0d488d37a 100644 --- a/src/components/clinical-dashboard/master-search-header.tsx +++ b/src/components/clinical-dashboard/master-search-header.tsx @@ -202,6 +202,7 @@ export function MasterSearchHeader({ queryInputAutoFocus = false, headerVariant = "default", mobileSearchPlacement = "default", + mobileBottomSearchVariant = "default", desktopSearchPlacement = "default", searchComposerVisible = true, desktopHomeComposerSlotId, @@ -239,6 +240,10 @@ export function MasterSearchHeader({ queryInputAutoFocus?: boolean; headerVariant?: "default" | "workflow"; mobileSearchPlacement?: "default" | "bottom"; + /** "compact" drops the phone footer chip row and hugs the bottom edge — + * used by search/result views so results keep maximum screen space. + * Mode homes keep the default chip-row layout. */ + mobileBottomSearchVariant?: "default" | "compact"; desktopSearchPlacement?: "default" | "hero"; searchComposerVisible?: boolean; desktopHomeComposerSlotId?: string; @@ -1127,9 +1132,13 @@ export function MasterSearchHeader({ const isDesktopHomeComposer = placement === "desktop-home"; const usesAnswerFooterStyle = isAnswerFooterComposer && !isDesktopHomeComposer; const usesMobileBottomStyle = isMobileBottomComposer && !isDesktopHomeComposer; + const usesCompactMobileBottomStyle = usesMobileBottomStyle && mobileBottomSearchVariant === "compact"; const usesBottomComposerPlacement = usesAnswerFooterStyle || (usesMobileBottomStyle && usesPhoneSearchLayout); const usesFooterChipLayout = usesBottomComposerPlacement || isDesktopHomeComposer; - const showFooterSearchChips = usesFooterChipLayout; + // Compact search views drop the chip row on phones so the pill can sit + // flush with the bottom edge; the same actions stay reachable via the + // integrated "+" menu. + const showFooterSearchChips = usesFooterChipLayout && !usesCompactMobileBottomStyle; // The visible footer/hero composer chrome is universal; submit semantics still // come from the active mode. const usesSendAffordance = searchMode === "answer" || usesFooterChipLayout; @@ -1166,6 +1175,7 @@ export function MasterSearchHeader({ ) : "universal-top-search-edge sticky top-[calc(4.75rem+env(safe-area-inset-top))] z-20 mx-auto box-border w-full px-3 py-3 sm:px-4", usesBottomComposerPlacement && "answer-footer-search-edge", + usesCompactMobileBottomStyle && "document-mobile-search-compact", usesFooterChipLayout && "flex flex-col items-center gap-2.5", )} > diff --git a/tests/ui-tools.spec.ts b/tests/ui-tools.spec.ts index bc1a7dcf5..8972e349c 100644 --- a/tests/ui-tools.spec.ts +++ b/tests/ui-tools.spec.ts @@ -283,6 +283,8 @@ test.describe("Clinical KB applications launcher", () => { expect(metrics?.position).toBe("fixed"); expect(metrics?.formWidth ?? 0).toBeLessThanOrEqual(390 - 8); expect(metrics?.pillClassName).toContain("answer-footer-search-pill"); + // Mode homes keep the footer chip row under the pill on phones. + await expect(page.locator(".answer-footer-search-chip:visible").first()).toBeVisible(); await expectNoPageHorizontalOverflow(page); } }); @@ -343,21 +345,32 @@ test.describe("Clinical KB applications launcher", () => { ] as const) { await page.setViewportSize({ width: viewport.width, height: viewport.height }); - for (const route of ["/services?q=13YARN&focus=1&run=1", "/services/13yarn"] as const) { - await gotoLauncher(page, route); + for (const route of [ + { path: "/services?q=13YARN&focus=1&run=1", compactBottomSearch: true }, + { path: "/services/13yarn", compactBottomSearch: false }, + ] as const) { + await gotoLauncher(page, route.path); await expect(page.getByRole("button", { name: "Mode Services" })).toBeVisible({ timeout: 20_000 }); - await expect(visibleGlobalSearchInput(page), `${route} at ${viewport.name}`).toHaveCount(1, { + await expect(visibleGlobalSearchInput(page), `${route.path} at ${viewport.name}`).toHaveCount(1, { timeout: 20_000, }); const metrics = await globalSearchComposerMetrics(page); - expect(metrics, `${route} at ${viewport.name}`).not.toBeNull(); + expect(metrics, `${route.path} at ${viewport.name}`).not.toBeNull(); expect(metrics?.pillClassName).toContain("answer-footer-search-pill"); expect(metrics?.formWidth ?? 0).toBeLessThanOrEqual(viewport.width - 8); if (viewport.width < 640) { expect(metrics?.position).toBe("fixed"); expect(metrics?.formCenterY ?? 0).toBeGreaterThan(viewport.height * 0.72); + if (route.compactBottomSearch) { + // Search result views drop the chip row and hug the bottom edge + // on phones so results keep maximum screen space. + await expect(page.locator(".answer-footer-search-chip:visible")).toHaveCount(0); + expect(metrics?.formBottom ?? 0).toBeGreaterThanOrEqual(viewport.height - 48); + } else { + await expect(page.locator(".answer-footer-search-chip:visible").first()).toBeVisible(); + } } else { expect(metrics?.position).toBe("sticky"); expect(metrics?.formCenterY ?? viewport.height).toBeLessThan(viewport.height * 0.25);