mirror of https://github.com/buster-so/buster.git
snappier animations
This commit is contained in:
parent
79d5fba6e3
commit
d6e3ab9a51
|
@ -18,38 +18,33 @@ export const useSearch = <T = SearchTextResponse>(
|
|||
});
|
||||
};
|
||||
|
||||
export const useSearchInfinite = (
|
||||
{
|
||||
enabled,
|
||||
mounted,
|
||||
...params
|
||||
}: Pick<
|
||||
Parameters<typeof search>[0],
|
||||
| 'page_size'
|
||||
| 'assetTypes'
|
||||
| 'includeAssetAncestors'
|
||||
| 'includeScreenshots'
|
||||
| 'endDate'
|
||||
| 'startDate'
|
||||
> & {
|
||||
scrollConfig?: Parameters<typeof useInfiniteScroll>[0]['scrollConfig'];
|
||||
searchQuery: string;
|
||||
enabled?: boolean;
|
||||
mounted?: boolean;
|
||||
} = {
|
||||
page_size: 5,
|
||||
assetTypes: ['chat'],
|
||||
searchQuery: '',
|
||||
enabled: true,
|
||||
mounted: true,
|
||||
}
|
||||
) => {
|
||||
export const useSearchInfinite = ({
|
||||
enabled,
|
||||
mounted,
|
||||
scrollConfig,
|
||||
...params
|
||||
}: Pick<
|
||||
Parameters<typeof search>[0],
|
||||
| 'page_size'
|
||||
| 'assetTypes'
|
||||
| 'includeAssetAncestors'
|
||||
| 'includeScreenshots'
|
||||
| 'endDate'
|
||||
| 'startDate'
|
||||
> & {
|
||||
scrollConfig?: Parameters<typeof useInfiniteScroll>[0]['scrollConfig'];
|
||||
searchQuery: string;
|
||||
enabled?: boolean;
|
||||
mounted?: boolean;
|
||||
}) => {
|
||||
const { searchQuery } = params;
|
||||
return useInfiniteScroll<SearchTextData>({
|
||||
queryKey: ['search', 'results', 'infinite', params] as const,
|
||||
staleTime: 1000 * 30, // 30 seconds
|
||||
staleTime: 1000 * 45, // 45 seconds
|
||||
queryFn: ({ pageParam = 1 }) => search({ query: searchQuery, page: pageParam, ...params }),
|
||||
placeholderData: keepPreviousData,
|
||||
enabled: true,
|
||||
enabled,
|
||||
scrollConfig,
|
||||
mounted,
|
||||
});
|
||||
};
|
||||
|
|
|
@ -6,8 +6,9 @@ import type {
|
|||
UseInfiniteQueryResult,
|
||||
} from '@tanstack/react-query';
|
||||
import { useInfiniteQuery } from '@tanstack/react-query';
|
||||
import { useEffect, useRef } from 'react';
|
||||
import { useEffect, useMemo, useRef } from 'react';
|
||||
import type { ApiError } from '@/api/errors';
|
||||
import { useDebounce } from '@/hooks/useDebounce';
|
||||
|
||||
/**
|
||||
* Configuration for infinite scroll behavior
|
||||
|
@ -41,6 +42,7 @@ type UseInfiniteScrollOptions<TData, TError = ApiError> = Omit<
|
|||
* Infinite scroll configuration
|
||||
*/
|
||||
scrollConfig?: InfiniteScrollConfig;
|
||||
mounted?: boolean;
|
||||
};
|
||||
|
||||
/**
|
||||
|
@ -82,11 +84,12 @@ type UseInfiniteScrollResult<TData, TError = ApiError> = UseInfiniteQueryResult<
|
|||
export function useInfiniteScroll<TData, TError = ApiError>(
|
||||
options: UseInfiniteScrollOptions<TData, TError>
|
||||
): UseInfiniteScrollResult<TData, TError> {
|
||||
const { scrollConfig, ...queryOptions } = options;
|
||||
const { scrollConfig, mounted, ...queryOptions } = options;
|
||||
const scrollThreshold =
|
||||
scrollConfig?.scrollThreshold ?? InfiniteScrollConfigSchema.scrollThreshold;
|
||||
|
||||
const scrollContainerRef = useRef<HTMLDivElement>(null);
|
||||
const debouncedMounted = useDebounce(mounted, { wait: 2000 });
|
||||
|
||||
const queryResult = useInfiniteQuery({
|
||||
...queryOptions,
|
||||
|
@ -102,7 +105,10 @@ export function useInfiniteScroll<TData, TError = ApiError>(
|
|||
const { fetchNextPage, hasNextPage, isFetchingNextPage } = queryResult;
|
||||
|
||||
// Combine all pages into a single array of results
|
||||
const allResults = queryResult.data?.pages.flatMap((page) => page.data) ?? [];
|
||||
const allResults = useMemo(
|
||||
() => queryResult.data?.pages.flatMap((page) => page.data) ?? [],
|
||||
[queryResult.data]
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
const container = scrollContainerRef.current;
|
||||
|
@ -123,14 +129,7 @@ export function useInfiniteScroll<TData, TError = ApiError>(
|
|||
|
||||
container.addEventListener('scroll', handleScroll);
|
||||
return () => container.removeEventListener('scroll', handleScroll);
|
||||
}, [
|
||||
hasNextPage,
|
||||
isFetchingNextPage,
|
||||
fetchNextPage,
|
||||
scrollThreshold,
|
||||
queryResult.isFetched,
|
||||
options.enabled,
|
||||
]);
|
||||
}, [hasNextPage, isFetchingNextPage, fetchNextPage, scrollThreshold, debouncedMounted]);
|
||||
|
||||
return {
|
||||
...queryResult,
|
||||
|
|
|
@ -54,7 +54,7 @@ export const GlobalSearchModal = () => {
|
|||
includeAssetAncestors: true,
|
||||
includeScreenshots: true,
|
||||
scrollConfig: {
|
||||
scrollThreshold: 10,
|
||||
scrollThreshold: 55,
|
||||
},
|
||||
mounted: isOpen,
|
||||
});
|
||||
|
|
|
@ -59,28 +59,31 @@ export const GlobalSearchSecondaryContent: React.FC<GlobalSearchSecondaryContent
|
|||
};
|
||||
|
||||
function getFallback(assetType: SearchTextData['assetType']) {
|
||||
switch (assetType) {
|
||||
case 'chat':
|
||||
return SkeletonSearchChat;
|
||||
case 'metric_file':
|
||||
return SkeletonSearchMetric;
|
||||
case 'dashboard_file':
|
||||
return SkeletonSearchDashboard;
|
||||
case 'report_file':
|
||||
return SkeletonSearchReport;
|
||||
case 'collection':
|
||||
return SkeletonSearchMetric;
|
||||
default:
|
||||
return SkeletonSearchMetric;
|
||||
if (assetType === 'metric_file') {
|
||||
return SkeletonSearchMetric;
|
||||
} else if (assetType === 'chat') {
|
||||
return SkeletonSearchChat;
|
||||
} else if (assetType === 'dashboard_file') {
|
||||
return SkeletonSearchDashboard;
|
||||
} else if (assetType === 'report_file') {
|
||||
return SkeletonSearchReport;
|
||||
} else if (assetType === 'collection') {
|
||||
return SkeletonSearchMetric;
|
||||
} else {
|
||||
const _exhaustiveCheck: never = assetType;
|
||||
console.warn('Exhaustive check', _exhaustiveCheck);
|
||||
return SkeletonSearchMetric;
|
||||
}
|
||||
}
|
||||
|
||||
const ScreenshotImage = ({
|
||||
screenshotUrl,
|
||||
assetType,
|
||||
className,
|
||||
}: {
|
||||
screenshotUrl: string | null | undefined;
|
||||
assetType: SearchTextData['assetType'];
|
||||
className?: string;
|
||||
}) => {
|
||||
const [isLoaded, setIsLoaded] = useState(false);
|
||||
const [isCached, setIsCached] = useState(false);
|
||||
|
@ -129,7 +132,7 @@ const ScreenshotImage = ({
|
|||
key={imageUrl}
|
||||
src={imageUrl}
|
||||
alt="Screenshot"
|
||||
className="w-full h-full object-cover object-top"
|
||||
className={cn('w-full h-full object-cover object-top', className)}
|
||||
initial={
|
||||
isCached ? { opacity: 1, filter: 'blur(0px)' } : { opacity: 0, filter: 'blur(4px)' }
|
||||
}
|
||||
|
@ -172,7 +175,11 @@ const MetricScreenshotContainer = ({
|
|||
transition={{ duration: 0.2, ease: 'easeOut' }}
|
||||
>
|
||||
{isLoadingContent ? (
|
||||
<ScreenshotImage screenshotUrl={screenshotUrl} assetType={'metric_file'} />
|
||||
<ScreenshotImage
|
||||
screenshotUrl={screenshotUrl}
|
||||
assetType={'metric_file'}
|
||||
className="animate-pulse"
|
||||
/>
|
||||
) : (
|
||||
<MetricChartCard
|
||||
metricId={assetId}
|
||||
|
|
|
@ -4,7 +4,7 @@ import { cn } from '@/lib/utils';
|
|||
import { SearchModalContentItems } from './SearchModalContentItems';
|
||||
import type { SearchItem, SearchItems, SearchModalContentProps } from './search-modal.types';
|
||||
|
||||
const duration = 0.15;
|
||||
const duration = 0.12;
|
||||
|
||||
export const SearchModalItemsContainer = <M, T extends string>({
|
||||
searchItems,
|
||||
|
|
Loading…
Reference in New Issue