From 12972a0720914625df218bb112885f169a60f947 Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 6 Jul 2026 10:59:21 +0000 Subject: [PATCH] Hide document viewer bottom composer on phone scroll-down The document viewer's fixed bottom search composer on /documents/[id] was the one remaining mobile surface that stayed on screen while scrolling. Wire it into the existing phone-only useHideOnScroll hook (window scroll) so it slides off on scroll-down and returns on scroll-up, mirroring the bottom search dock's data-scroll-hidden pattern. Guards keep it visible while the mobile actions sheet is open or focus is inside the composer, so keyboard users never tab into invisible controls and typing with the iOS keyboard open cannot hide it. Covered by a phone-viewport Playwright spec in ui-smoke. Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_01UULp5Leufjv9WfbEHrRSgF --- src/app/globals.css | 9 +++++++++ src/components/DocumentViewer.tsx | 14 +++++++++++++- tests/ui-smoke.spec.ts | 28 ++++++++++++++++++++++++++++ 3 files changed, 50 insertions(+), 1 deletion(-) diff --git a/src/app/globals.css b/src/app/globals.css index 409fa258a1..16f8e6bb0c 100644 --- a/src/app/globals.css +++ b/src/app/globals.css @@ -1528,6 +1528,15 @@ summary::-webkit-details-marker { pointer-events: none; } + /* Document viewer bottom composer mirrors the dock's hide-on-scroll. Unlike + the full-bleed dock it floats with a bottom gap (.floating-composer-edge, + bottom: max(0.75rem, safe-area)), so the exit transform must also clear + that gap. translateY-only: this form centres with mx-auto, not translateX. */ + .document-viewer-composer[data-scroll-hidden="true"] { + transform: translateY(calc(100% + max(0.75rem, var(--safe-area-bottom)))); + pointer-events: none; + } + .answer-footer-search-dock .answer-footer-search-pill { border-color: var(--border-strong); background: var(--surface); diff --git a/src/components/DocumentViewer.tsx b/src/components/DocumentViewer.tsx index 630790c43f..e0d618a748 100644 --- a/src/components/DocumentViewer.tsx +++ b/src/components/DocumentViewer.tsx @@ -43,6 +43,7 @@ import { documentFileKind, documentTileTone, } from "@/components/clinical-dashboard/document-ui"; +import { useHideOnScroll } from "@/components/clinical-dashboard/use-hide-on-scroll"; import { DocumentTagCloud } from "@/components/DocumentTagCloud"; import type { PDFDocumentLoadingTask, PDFDocumentProxy, RenderTask } from "pdfjs-dist"; import { @@ -1916,6 +1917,12 @@ export function DocumentViewer({ const [isOnline, setIsOnline] = useState(true); const [localProjectReady, setLocalProjectReady] = useState(true); const [mobileActionsOpen, setMobileActionsOpen] = useState(false); + // Phone-only hide-on-scroll for the bottom composer: never hide while the + // mobile actions sheet is open or while focus sits inside the composer + // (keyboard users must not tab into invisible controls). + const [composerChromeFocused, setComposerChromeFocused] = useState(false); + const scrollHidden = useHideOnScroll({}); + const composerScrollHidden = scrollHidden && !mobileActionsOpen && !composerChromeFocused; const [useNativePdfViewer, setUseNativePdfViewer] = useState(() => getInitialPdfViewerMode().useNativePdfViewer); const [hasExplicitPdfViewerMode, setHasExplicitPdfViewerMode] = useState( () => getInitialPdfViewerMode().hasExplicitPdfViewerMode, @@ -2964,7 +2971,12 @@ export function DocumentViewer({ event.preventDefault(); if (canSummarizeDocument) void summarize(); }} - className="floating-composer-edge dashboard-composer-edge fixed z-40 mx-auto flex min-h-[56px] max-w-3xl items-center gap-2 rounded-full border border-[color:var(--border-lux)] bg-[color:var(--surface-lux)] px-2 shadow-[var(--shadow-lux)] ring-1 ring-white/35 backdrop-blur-xl" + data-scroll-hidden={composerScrollHidden ? "true" : undefined} + onFocusCapture={() => setComposerChromeFocused(true)} + onBlurCapture={(event) => { + if (!event.currentTarget.contains(event.relatedTarget as Node | null)) setComposerChromeFocused(false); + }} + className="document-viewer-composer floating-composer-edge dashboard-composer-edge fixed z-40 mx-auto flex min-h-[56px] max-w-3xl items-center gap-2 rounded-full border border-[color:var(--border-lux)] bg-[color:var(--surface-lux)] px-2 shadow-[var(--shadow-lux)] ring-1 ring-white/35 backdrop-blur-xl max-sm:transition-transform max-sm:duration-200 max-sm:ease-out motion-reduce:transition-none" >