Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions src/app/globals.css
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
14 changes: 13 additions & 1 deletion src/components/DocumentViewer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@
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 {
Expand Down Expand Up @@ -1916,6 +1917,12 @@
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,
Expand Down Expand Up @@ -2156,7 +2163,7 @@
window.clearTimeout(reset);
controller.abort();
};
}, [

Check warning on line 2166 in src/components/DocumentViewer.tsx

View workflow job for this annotation

GitHub Actions / verify

React Hook useEffect has a missing dependency: 'canUsePrivateApis'. Either include it or remove the dependency array
authStatus,
authorizationHeader,
canViewSourceDocuments,
Expand Down Expand Up @@ -2964,7 +2971,12 @@
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"
>
<button
type="button"
Expand Down
28 changes: 28 additions & 0 deletions tests/ui-smoke.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2001,6 +2001,34 @@ test.describe("Clinical KB UI smoke coverage", () => {
await expectNoPageHorizontalOverflow(page);
});

test("document viewer bottom composer hides while scrolling down on phones", async ({ page }) => {
await page.setViewportSize({ width: 390, height: 844 });
await mockDemoApi(page);
await gotoApp(
page,
"/documents/11111111-1111-4111-8111-111111111111?page=1&chunk=44444444-4444-4444-8444-444444444442",
);

await expect(page.getByRole("heading", { level: 1, name: "Synthetic lithium monitoring protocol" })).toBeVisible();
const composer = page.locator("form.document-viewer-composer");
await expect(composer).toBeVisible();
await expect(composer).not.toHaveAttribute("data-scroll-hidden", "true");

// Hide on deliberate scroll down past the activation offset.
await page.evaluate(() => window.scrollTo({ top: 120, behavior: "auto" }));
await expect(composer).toHaveAttribute("data-scroll-hidden", "true");

// Reappear on scroll up.
await page.evaluate(() => window.scrollTo({ top: 60, behavior: "auto" }));
await expect(composer).not.toHaveAttribute("data-scroll-hidden", "true");

// Keyboard focus inside the composer reveals it while hidden.
await page.evaluate(() => window.scrollTo({ top: 240, behavior: "auto" }));
await expect(composer).toHaveAttribute("data-scroll-hidden", "true");
await composer.locator("input").focus();
await expect(composer).not.toHaveAttribute("data-scroll-hidden", "true");
});

test("document summary opens at the top with cleaned bold formatting", async ({ page }) => {
await page.setViewportSize({ width: 390, height: 820 });
await mockDemoApi(page);
Expand Down
Loading