diff --git a/packages/web/src/app/[domain]/search/components/filterPanel/index.tsx b/packages/web/src/app/[domain]/search/components/filterPanel/index.tsx index 231cda18..2826d78b 100644 --- a/packages/web/src/app/[domain]/search/components/filterPanel/index.tsx +++ b/packages/web/src/app/[domain]/search/components/filterPanel/index.tsx @@ -15,6 +15,7 @@ import { useGetSelectedFromQuery } from "./useGetSelectedFromQuery"; interface FilePanelProps { matches: SearchResultFile[]; repoInfo: Record; + onFilterChange?: () => void; } /** @@ -31,10 +32,12 @@ interface FilePanelProps { * * @param matches - Array of search result files to filter * @param repoInfo - Information about repositories including their display names and icons + * @param onFilterChange - Optional callback that is called whenever a filter is applied or removed */ export const FilterPanel = ({ matches, repoInfo, + onFilterChange, }: FilePanelProps) => { const router = useRouter(); const searchParams = useSearchParams(); @@ -148,6 +151,7 @@ export const FilterPanel = ({ if (newParams.toString() !== searchParams.toString()) { router.replace(`?${newParams.toString()}`, { scroll: false }); + onFilterChange?.(); } }} className="max-h-[50%]" @@ -170,6 +174,7 @@ export const FilterPanel = ({ if (newParams.toString() !== searchParams.toString()) { router.replace(`?${newParams.toString()}`, { scroll: false }); + onFilterChange?.(); } }} className="overflow-auto" diff --git a/packages/web/src/app/[domain]/search/components/searchResultsPage.tsx b/packages/web/src/app/[domain]/search/components/searchResultsPage.tsx index 7bacb12c..bbbc6727 100644 --- a/packages/web/src/app/[domain]/search/components/searchResultsPage.tsx +++ b/packages/web/src/app/[domain]/search/components/searchResultsPage.tsx @@ -31,7 +31,7 @@ import { useStreamedSearch } from "../useStreamedSearch"; import { CodePreviewPanel } from "./codePreviewPanel"; import { FilterPanel } from "./filterPanel"; import { useFilteredMatches } from "./filterPanel/useFilterMatches"; -import { SearchResultsPanel } from "./searchResultsPanel"; +import { SearchResultsPanel, SearchResultsPanelHandle } from "./searchResultsPanel"; interface SearchResultsPageProps { searchQuery: string; @@ -198,6 +198,7 @@ const PanelGroup = ({ const [previewedFile, setPreviewedFile] = useState(undefined); const filteredFileMatches = useFilteredMatches(fileMatches); const filterPanelRef = useRef(null); + const searchResultsPanelRef = useRef(null); const [selectedMatchIndex, setSelectedMatchIndex] = useState(0); const [isFilterPanelCollapsed, setIsFilterPanelCollapsed] = useLocalStorage('isFilterPanelCollapsed', false); @@ -238,6 +239,9 @@ const PanelGroup = ({ { + searchResultsPanelRef.current?.resetScroll(); + }} /> {isFilterPanelCollapsed && ( @@ -325,6 +329,7 @@ const PanelGroup = ({ {filteredFileMatches.length > 0 ? ( { setSelectedMatchIndex(matchIndex ?? 0); diff --git a/packages/web/src/app/[domain]/search/components/searchResultsPanel/index.tsx b/packages/web/src/app/[domain]/search/components/searchResultsPanel/index.tsx index 61e41332..978a254d 100644 --- a/packages/web/src/app/[domain]/search/components/searchResultsPanel/index.tsx +++ b/packages/web/src/app/[domain]/search/components/searchResultsPanel/index.tsx @@ -3,8 +3,8 @@ import { RepositoryInfo, SearchResultFile } from "@/features/search/types"; import { FileMatchContainer, MAX_MATCHES_TO_PREVIEW } from "./fileMatchContainer"; import { useVirtualizer, VirtualItem } from "@tanstack/react-virtual"; -import { useCallback, useEffect, useRef, useState } from "react"; -import { useDebounce, usePrevious } from "@uidotdev/usehooks"; +import { useCallback, useEffect, useImperativeHandle, useRef, useState, forwardRef } from "react"; +import { useDebounce } from "@uidotdev/usehooks"; interface SearchResultsPanelProps { fileMatches: SearchResultFile[]; @@ -15,6 +15,10 @@ interface SearchResultsPanelProps { repoInfo: Record; } +export interface SearchResultsPanelHandle { + resetScroll: () => void; +} + const ESTIMATED_LINE_HEIGHT_PX = 20; const ESTIMATED_NUMBER_OF_LINES_PER_CODE_CELL = 10; const ESTIMATED_MATCH_CONTAINER_HEIGHT_PX = 30; @@ -25,14 +29,14 @@ type ScrollHistoryState = { showAllMatchesStates?: boolean[]; } -export const SearchResultsPanel = ({ +export const SearchResultsPanel = forwardRef(({ fileMatches, onOpenFilePreview, isLoadMoreButtonVisible, onLoadMoreButtonClicked, isBranchFilteringEnabled, repoInfo, -}: SearchResultsPanelProps) => { +}, ref) => { const parentRef = useRef(null); // Restore the scroll offset, measurements cache, and other state from the history @@ -73,18 +77,16 @@ export const SearchResultsPanel = ({ debug: false, }); - // When the number of file matches changes, we need to reset our scroll state. - const prevFileMatches = usePrevious(fileMatches); - useEffect(() => { - if (!prevFileMatches) { - return; - } + const resetScroll = useCallback(() => { + setShowAllMatchesStates(Array(fileMatches.length).fill(false)); + virtualizer.scrollToIndex(0); + }, [fileMatches.length, virtualizer]); + + // Expose the resetScroll function to parent components + useImperativeHandle(ref, () => ({ + resetScroll, + }), [resetScroll]); - if (prevFileMatches.length !== fileMatches.length) { - setShowAllMatchesStates(Array(fileMatches.length).fill(false)); - virtualizer.scrollToIndex(0); - } - }, [fileMatches.length, prevFileMatches, virtualizer]); // Save the scroll state to the history stack. const debouncedScrollOffset = useDebounce(virtualizer.scrollOffset, 100); @@ -177,4 +179,6 @@ export const SearchResultsPanel = ({ )} ) -} \ No newline at end of file +}); + +SearchResultsPanel.displayName = 'SearchResultsPanel'; \ No newline at end of file