fix scrolling issue

This commit is contained in:
bkellam 2025-11-14 21:21:56 -08:00
parent 9cd32362e8
commit 9f66c458c4
3 changed files with 31 additions and 17 deletions

View file

@ -15,6 +15,7 @@ import { useGetSelectedFromQuery } from "./useGetSelectedFromQuery";
interface FilePanelProps { interface FilePanelProps {
matches: SearchResultFile[]; matches: SearchResultFile[];
repoInfo: Record<number, RepositoryInfo>; repoInfo: Record<number, RepositoryInfo>;
onFilterChange?: () => void;
} }
/** /**
@ -31,10 +32,12 @@ interface FilePanelProps {
* *
* @param matches - Array of search result files to filter * @param matches - Array of search result files to filter
* @param repoInfo - Information about repositories including their display names and icons * @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 = ({ export const FilterPanel = ({
matches, matches,
repoInfo, repoInfo,
onFilterChange,
}: FilePanelProps) => { }: FilePanelProps) => {
const router = useRouter(); const router = useRouter();
const searchParams = useSearchParams(); const searchParams = useSearchParams();
@ -148,6 +151,7 @@ export const FilterPanel = ({
if (newParams.toString() !== searchParams.toString()) { if (newParams.toString() !== searchParams.toString()) {
router.replace(`?${newParams.toString()}`, { scroll: false }); router.replace(`?${newParams.toString()}`, { scroll: false });
onFilterChange?.();
} }
}} }}
className="max-h-[50%]" className="max-h-[50%]"
@ -170,6 +174,7 @@ export const FilterPanel = ({
if (newParams.toString() !== searchParams.toString()) { if (newParams.toString() !== searchParams.toString()) {
router.replace(`?${newParams.toString()}`, { scroll: false }); router.replace(`?${newParams.toString()}`, { scroll: false });
onFilterChange?.();
} }
}} }}
className="overflow-auto" className="overflow-auto"

View file

@ -31,7 +31,7 @@ import { useStreamedSearch } from "../useStreamedSearch";
import { CodePreviewPanel } from "./codePreviewPanel"; import { CodePreviewPanel } from "./codePreviewPanel";
import { FilterPanel } from "./filterPanel"; import { FilterPanel } from "./filterPanel";
import { useFilteredMatches } from "./filterPanel/useFilterMatches"; import { useFilteredMatches } from "./filterPanel/useFilterMatches";
import { SearchResultsPanel } from "./searchResultsPanel"; import { SearchResultsPanel, SearchResultsPanelHandle } from "./searchResultsPanel";
interface SearchResultsPageProps { interface SearchResultsPageProps {
searchQuery: string; searchQuery: string;
@ -198,6 +198,7 @@ const PanelGroup = ({
const [previewedFile, setPreviewedFile] = useState<SearchResultFile | undefined>(undefined); const [previewedFile, setPreviewedFile] = useState<SearchResultFile | undefined>(undefined);
const filteredFileMatches = useFilteredMatches(fileMatches); const filteredFileMatches = useFilteredMatches(fileMatches);
const filterPanelRef = useRef<ImperativePanelHandle>(null); const filterPanelRef = useRef<ImperativePanelHandle>(null);
const searchResultsPanelRef = useRef<SearchResultsPanelHandle>(null);
const [selectedMatchIndex, setSelectedMatchIndex] = useState(0); const [selectedMatchIndex, setSelectedMatchIndex] = useState(0);
const [isFilterPanelCollapsed, setIsFilterPanelCollapsed] = useLocalStorage('isFilterPanelCollapsed', false); const [isFilterPanelCollapsed, setIsFilterPanelCollapsed] = useLocalStorage('isFilterPanelCollapsed', false);
@ -238,6 +239,9 @@ const PanelGroup = ({
<FilterPanel <FilterPanel
matches={fileMatches} matches={fileMatches}
repoInfo={repoInfo} repoInfo={repoInfo}
onFilterChange={() => {
searchResultsPanelRef.current?.resetScroll();
}}
/> />
</ResizablePanel> </ResizablePanel>
{isFilterPanelCollapsed && ( {isFilterPanelCollapsed && (
@ -325,6 +329,7 @@ const PanelGroup = ({
</div> </div>
{filteredFileMatches.length > 0 ? ( {filteredFileMatches.length > 0 ? (
<SearchResultsPanel <SearchResultsPanel
ref={searchResultsPanelRef}
fileMatches={filteredFileMatches} fileMatches={filteredFileMatches}
onOpenFilePreview={(fileMatch, matchIndex) => { onOpenFilePreview={(fileMatch, matchIndex) => {
setSelectedMatchIndex(matchIndex ?? 0); setSelectedMatchIndex(matchIndex ?? 0);

View file

@ -3,8 +3,8 @@
import { RepositoryInfo, SearchResultFile } from "@/features/search/types"; import { RepositoryInfo, SearchResultFile } from "@/features/search/types";
import { FileMatchContainer, MAX_MATCHES_TO_PREVIEW } from "./fileMatchContainer"; import { FileMatchContainer, MAX_MATCHES_TO_PREVIEW } from "./fileMatchContainer";
import { useVirtualizer, VirtualItem } from "@tanstack/react-virtual"; import { useVirtualizer, VirtualItem } from "@tanstack/react-virtual";
import { useCallback, useEffect, useRef, useState } from "react"; import { useCallback, useEffect, useImperativeHandle, useRef, useState, forwardRef } from "react";
import { useDebounce, usePrevious } from "@uidotdev/usehooks"; import { useDebounce } from "@uidotdev/usehooks";
interface SearchResultsPanelProps { interface SearchResultsPanelProps {
fileMatches: SearchResultFile[]; fileMatches: SearchResultFile[];
@ -15,6 +15,10 @@ interface SearchResultsPanelProps {
repoInfo: Record<number, RepositoryInfo>; repoInfo: Record<number, RepositoryInfo>;
} }
export interface SearchResultsPanelHandle {
resetScroll: () => void;
}
const ESTIMATED_LINE_HEIGHT_PX = 20; const ESTIMATED_LINE_HEIGHT_PX = 20;
const ESTIMATED_NUMBER_OF_LINES_PER_CODE_CELL = 10; const ESTIMATED_NUMBER_OF_LINES_PER_CODE_CELL = 10;
const ESTIMATED_MATCH_CONTAINER_HEIGHT_PX = 30; const ESTIMATED_MATCH_CONTAINER_HEIGHT_PX = 30;
@ -25,14 +29,14 @@ type ScrollHistoryState = {
showAllMatchesStates?: boolean[]; showAllMatchesStates?: boolean[];
} }
export const SearchResultsPanel = ({ export const SearchResultsPanel = forwardRef<SearchResultsPanelHandle, SearchResultsPanelProps>(({
fileMatches, fileMatches,
onOpenFilePreview, onOpenFilePreview,
isLoadMoreButtonVisible, isLoadMoreButtonVisible,
onLoadMoreButtonClicked, onLoadMoreButtonClicked,
isBranchFilteringEnabled, isBranchFilteringEnabled,
repoInfo, repoInfo,
}: SearchResultsPanelProps) => { }, ref) => {
const parentRef = useRef<HTMLDivElement>(null); const parentRef = useRef<HTMLDivElement>(null);
// Restore the scroll offset, measurements cache, and other state from the history // Restore the scroll offset, measurements cache, and other state from the history
@ -73,18 +77,16 @@ export const SearchResultsPanel = ({
debug: false, debug: false,
}); });
// When the number of file matches changes, we need to reset our scroll state. const resetScroll = useCallback(() => {
const prevFileMatches = usePrevious(fileMatches);
useEffect(() => {
if (!prevFileMatches) {
return;
}
if (prevFileMatches.length !== fileMatches.length) {
setShowAllMatchesStates(Array(fileMatches.length).fill(false)); setShowAllMatchesStates(Array(fileMatches.length).fill(false));
virtualizer.scrollToIndex(0); virtualizer.scrollToIndex(0);
} }, [fileMatches.length, virtualizer]);
}, [fileMatches.length, prevFileMatches, virtualizer]);
// Expose the resetScroll function to parent components
useImperativeHandle(ref, () => ({
resetScroll,
}), [resetScroll]);
// Save the scroll state to the history stack. // Save the scroll state to the history stack.
const debouncedScrollOffset = useDebounce(virtualizer.scrollOffset, 100); const debouncedScrollOffset = useDebounce(virtualizer.scrollOffset, 100);
@ -177,4 +179,6 @@ export const SearchResultsPanel = ({
)} )}
</div> </div>
) )
} });
SearchResultsPanel.displayName = 'SearchResultsPanel';