diff --git a/frontend/src/core/components/viewer/EmbedPdfViewer.tsx b/frontend/src/core/components/viewer/EmbedPdfViewer.tsx index 9213719c6e..03170acaa2 100644 --- a/frontend/src/core/components/viewer/EmbedPdfViewer.tsx +++ b/frontend/src/core/components/viewer/EmbedPdfViewer.tsx @@ -43,7 +43,6 @@ const EmbedPdfViewerContent = ({ isThumbnailSidebarVisible, toggleThumbnailSidebar, isBookmarkSidebarVisible, - isSearchInterfaceVisible, searchInterfaceActions, zoomActions, panActions: _panActions, diff --git a/frontend/src/core/components/viewer/SearchInterface.tsx b/frontend/src/core/components/viewer/SearchInterface.tsx index 73640e4699..3970ed2c76 100644 --- a/frontend/src/core/components/viewer/SearchInterface.tsx +++ b/frontend/src/core/components/viewer/SearchInterface.tsx @@ -13,6 +13,7 @@ export function SearchInterface({ visible, onClose }: SearchInterfaceProps) { const { t } = useTranslation(); const viewerContext = React.useContext(ViewerContext); const inputRef = useRef(null); + const searchTimeoutRef = useRef(null); const searchState = viewerContext?.getSearchState(); const searchResults = searchState?.results; @@ -34,6 +35,31 @@ export function SearchInterface({ visible, onClose }: SearchInterfaceProps) { } }, [visible]); + // Auto-search as user types (debounced) + useEffect(() => { + // Clear existing timeout + if (searchTimeoutRef.current) { + clearTimeout(searchTimeoutRef.current); + } + + // If query is empty, clear search immediately + if (!searchQuery.trim()) { + handleClearSearch(); + return; + } + + // Debounce search by 300ms + searchTimeoutRef.current = setTimeout(() => { + handleSearch(searchQuery); + }, 300); + + return () => { + if (searchTimeoutRef.current) { + clearTimeout(searchTimeoutRef.current); + } + }; + }, [searchQuery]); + // Monitor search state changes useEffect(() => { if (!visible) return; @@ -131,7 +157,14 @@ export function SearchInterface({ visible, onClose }: SearchInterfaceProps) { } }; - const _handleClose = () => { + const handleInputBlur = () => { + // Close popover on blur if no text is entered + if (!searchQuery.trim()) { + onClose(); + } + }; + + const handleCloseClick = () => { handleClearSearch(); onClose(); }; @@ -143,11 +176,19 @@ export function SearchInterface({ visible, onClose }: SearchInterfaceProps) { padding: '0px' }} > - {/* Header */} - + {/* Header with close button */} + {t('search.title', 'Search PDF')} + + + {/* Search input */} @@ -159,22 +200,20 @@ export function SearchInterface({ visible, onClose }: SearchInterfaceProps) { onChange={(e) => { const newValue = e.currentTarget.value; setSearchQuery(newValue); - // If user clears the input, clear the search highlights - if (!newValue.trim()) { - handleClearSearch(); - } }} onKeyDown={handleKeyDown} + onBlur={handleInputBlur} style={{ flex: 1 }} rightSection={ - handleSearch(searchQuery)} - disabled={!searchQuery.trim() || isSearching} - loading={isSearching} - > - - + searchQuery.trim() && ( + + + + ) } /> diff --git a/frontend/src/core/components/viewer/useViewerRightRailButtons.tsx b/frontend/src/core/components/viewer/useViewerRightRailButtons.tsx index 745dd5ad3b..9ef19dd71e 100644 --- a/frontend/src/core/components/viewer/useViewerRightRailButtons.tsx +++ b/frontend/src/core/components/viewer/useViewerRightRailButtons.tsx @@ -42,7 +42,7 @@ export function useViewerRightRailButtons() { shadow="md" offset={8} opened={viewer.isSearchInterfaceVisible} - onChange={viewer.searchInterfaceActions.toggle} + onClose={viewer.searchInterfaceActions.close} >
@@ -52,6 +52,7 @@ export function useViewerRightRailButtons() { className="right-rail-icon" disabled={disabled} aria-label={searchLabel} + onClick={viewer.searchInterfaceActions.toggle} >