diff --git a/docs/site-map.md b/docs/site-map.md index 21ca3e0ce0..be3d1c2d06 100644 --- a/docs/site-map.md +++ b/docs/site-map.md @@ -994,6 +994,7 @@ This file is generated by `npm run sitemap:update`. Run `npm run sitemap:check` - `/mockups/document-search/source` - Route discovered from app directory Source: `src/app/mockups/document-search/source/page.tsx`. - `/mockups/document-search/source-overlays` - Route discovered from app directory Source: `src/app/mockups/document-search/source-overlays/page.tsx`. - `/mockups/document-search/source/evidence` - Route discovered from app directory Source: `src/app/mockups/document-search/source/evidence/page.tsx`. +- `/mockups/document-top-navigation` - Route discovered from app directory Source: `src/app/mockups/document-top-navigation/page.tsx`. - `/mockups/favourites-command-console` - Route discovered from app directory Source: `src/app/mockups/favourites-command-console/page.tsx`. - `/mockups/favourites-command-desk` - Route discovered from app directory Source: `src/app/mockups/favourites-command-desk/page.tsx`. - `/mockups/favourites-hub` - Route discovered from app directory Source: `src/app/mockups/favourites-hub/page.tsx`. diff --git a/src/app/mockups/document-top-navigation/page.tsx b/src/app/mockups/document-top-navigation/page.tsx new file mode 100644 index 0000000000..844752266d --- /dev/null +++ b/src/app/mockups/document-top-navigation/page.tsx @@ -0,0 +1,5 @@ +import { DocumentTopNavigationMockups } from "@/components/document-top-navigation-mockups"; + +export default function DocumentTopNavigationMockupPage() { + return ; +} diff --git a/src/app/mockups/mockups-layout-client.tsx b/src/app/mockups/mockups-layout-client.tsx index ed508daadb..db459d35c5 100644 --- a/src/app/mockups/mockups-layout-client.tsx +++ b/src/app/mockups/mockups-layout-client.tsx @@ -13,6 +13,7 @@ export function MockupsLayoutClient({ children }: { children: ReactNode }) { const isSourceOverlayRedesignMockup = pathname === "/mockups/document-search/source-overlays"; const isStandaloneDocumentFlow = pathname === "/mockups/document-search"; const isUniversalSearchRedesignMockup = pathname === "/mockups/universal-search-redesign"; + const isDocumentTopNavigationMockup = pathname === "/mockups/document-top-navigation"; // The calculators search page owns its own search input (top on desktop, docked // at the bottom on phones), so the shared universal composer is suppressed here // to avoid a second, floating search bar. @@ -34,9 +35,10 @@ export function MockupsLayoutClient({ children }: { children: ReactNode }) { !isFavouritesPageMockup && !isStandaloneDocumentFlow && !isUniversalSearchRedesignMockup && + !isDocumentTopNavigationMockup && !isCalculatorsSearchPageMockup } - chromeVisible={!isSourceOverlayRedesignMockup} + chromeVisible={!isSourceOverlayRedesignMockup && !isDocumentTopNavigationMockup} > {children} diff --git a/src/components/DocumentViewer.tsx b/src/components/DocumentViewer.tsx index a98e0fd06e..2813e5465b 100644 --- a/src/components/DocumentViewer.tsx +++ b/src/components/DocumentViewer.tsx @@ -14,7 +14,6 @@ import { Plus, RefreshCw, Search, - Send, Sparkles, Target, } from "lucide-react"; @@ -302,6 +301,8 @@ export function DocumentViewer({ const [isOnline, setIsOnline] = useState(true); const [localProjectReady, setLocalProjectReady] = useState(true); const [mobileActionsOpen, setMobileActionsOpen] = useState(false); + const documentSearchInputRef = useRef(null); + const focusDocumentSearchAfterSheetCloseRef = useRef(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). @@ -333,6 +334,20 @@ export function DocumentViewer({ resetKey: `${documentId}:${activePage}:${activeChunkId ?? ""}`, }); const composerScrollHidden = scrollHidden && !mobileActionsOpen && !composerChromeFocused; + const focusDocumentSearch = useCallback(() => { + setComposerChromeFocused(true); + window.requestAnimationFrame(() => { + const input = documentSearchInputRef.current; + input?.focus({ preventScroll: true }); + input?.select(); + }); + }, []); + useEffect(() => { + if (mobileActionsOpen || !focusDocumentSearchAfterSheetCloseRef.current) return; + focusDocumentSearchAfterSheetCloseRef.current = false; + const timeout = window.setTimeout(focusDocumentSearch, 0); + return () => window.clearTimeout(timeout); + }, [focusDocumentSearch, mobileActionsOpen]); // Read localStorage once on mount, then seed both derived states from it. const [initialPdfViewerMode] = useState(getInitialPdfViewerMode); const [useNativePdfViewer, setUseNativePdfViewer] = useState(initialPdfViewerMode.useNativePdfViewer); @@ -857,8 +872,7 @@ export function DocumentViewer({ setSummaryError("Load a source document before summarising."); return; } - const summaryMode = sourceSearch.trim().length === 0; - const query = summaryMode ? documentSummaryQuestion : sourceSearch.trim(); + const query = documentSummaryQuestion; const controller = new AbortController(); summaryAbortRef.current?.abort(); summaryAbortRef.current = controller; @@ -886,7 +900,7 @@ export function DocumentViewer({ "Content-Type": "application/json", ...(clientDemoMode ? {} : authorizationHeader), }, - body: JSON.stringify({ query, documentId, ...(summaryMode ? { summaryMode: true } : {}) }), + body: JSON.stringify({ query, documentId, summaryMode: true }), signal: controller.signal, }); if (response.status === 401) markSessionExpired(); @@ -976,6 +990,7 @@ export function DocumentViewer({ : documentHomeHref; const usefulPageHref = (page: number) => documentPageHref(documentId, page); const canSummarizeDocument = viewerState === "ready" && !loadingSummary && canUsePrivateApis; + const canSubmitDocumentSearch = viewerState === "ready" && canViewSourceDocuments && sourceSearch.trim().length >= 2; const summarizeTitle = !canUsePrivateApis ? "Sign in before answering from this document" : viewerState !== "ready" || loadingSummary @@ -1111,6 +1126,17 @@ export function DocumentViewer({
+ { + focusDocumentSearchAfterSheetCloseRef.current = true; setMobileActionsOpen(false); - setSourceSearch(documentDisplayTitle(readyDocument)); }} className={cn(secondaryButton, "min-h-12 justify-start text-xs")} > @@ -1330,7 +1356,7 @@ export function DocumentViewer({ ) : null}
- +
@@ -1491,7 +1517,7 @@ export function DocumentViewer({ ) : null}
- + { event.preventDefault(); - if (canSummarizeDocument) void summarize(); + if (!canSubmitDocumentSearch) return; + window.document + .getElementById("source-text") + ?.scrollIntoView({ block: "start", behavior: resolveScrollBehavior() }); }} + role="search" + aria-label="Search within this document" data-scroll-hidden={composerScrollHidden ? "true" : undefined} onFocusCapture={() => setComposerChromeFocused(true)} onBlurCapture={(event) => { @@ -1707,25 +1738,27 @@ export function DocumentViewer({