import React, {ReactNode, useEffect, useRef, useState} from 'react'; import clsx from 'clsx'; import {UseInfiniteQueryResult} from '@tanstack/react-query/src/types'; import {Trans} from '@common/i18n/trans'; import {Button} from '@common/ui/buttons/button'; import {AnimatePresence, m} from 'framer-motion'; import {opacityAnimation} from '@common/ui/animation/opacity-animation'; import {ProgressCircle} from '@common/ui/progress/progress-circle'; export interface InfiniteScrollSentinelProps { loaderMarginTop?: string; children?: ReactNode; loadMoreExtraContent?: ReactNode; query: UseInfiniteQueryResult; style?: React.CSSProperties; className?: string; variant?: 'infiniteScroll' | 'loadMore'; size?: 'sm' | 'md'; } export function InfiniteScrollSentinel({ query: {isInitialLoading, fetchNextPage, isFetchingNextPage, hasNextPage}, children, loaderMarginTop = 'mt-24', style, className, variant: _variant = 'infiniteScroll', loadMoreExtraContent, size = 'md', }: InfiniteScrollSentinelProps) { const sentinelRef = useRef(null); const isLoading = isFetchingNextPage || isInitialLoading; const [loadMoreClickCount, setLoadMoreClickCount] = useState(0); const innerVariant = _variant === 'loadMore' && loadMoreClickCount < 3 ? 'loadMore' : 'infiniteScroll'; useEffect(() => { const sentinelEl = sentinelRef.current; if (!sentinelEl || innerVariant === 'loadMore') return; const observer = new IntersectionObserver(([entry]) => { if (entry.isIntersecting && hasNextPage && !isLoading) { fetchNextPage(); } }); observer.observe(sentinelEl); return () => { observer.unobserve(sentinelEl); }; }, [fetchNextPage, hasNextPage, isLoading, innerVariant]); let content: ReactNode; if (children) { // children might already be wrapped in AnimatePresence, so only wrap default loader with it content = isFetchingNextPage ? children : null; } else if (innerVariant === 'loadMore') { content = !isInitialLoading && hasNextPage && (
{loadMoreExtraContent}
); } else { content = ( {isFetchingNextPage && ( )} ); } return (
{content}
); }