2024-07-02 22:53:58 +08:00
|
|
|
import { useWatch } from '@/hooks/useWatch';
|
2024-07-03 00:02:24 +08:00
|
|
|
import { useTranslation } from '@i18next-toolkit/react';
|
|
|
|
import { useVirtualizer } from '@tanstack/react-virtual';
|
2024-07-02 22:53:58 +08:00
|
|
|
import { Empty } from 'antd';
|
|
|
|
import { last } from 'lodash-es';
|
|
|
|
import React, { useRef } from 'react';
|
|
|
|
|
|
|
|
interface VirtualListProps<T = any> {
|
|
|
|
allData: T[];
|
|
|
|
hasNextPage: boolean | undefined;
|
|
|
|
isFetchingNextPage: boolean;
|
|
|
|
onFetchNextPage: () => void;
|
2024-07-03 00:02:24 +08:00
|
|
|
estimateSize: number;
|
|
|
|
renderItem: (item: T) => React.ReactElement;
|
|
|
|
renderEmpty?: () => React.ReactElement;
|
2024-07-02 22:53:58 +08:00
|
|
|
}
|
|
|
|
export const SimpleVirtualList: React.FC<VirtualListProps> = React.memo(
|
|
|
|
(props) => {
|
|
|
|
const {
|
|
|
|
allData,
|
|
|
|
hasNextPage,
|
|
|
|
isFetchingNextPage,
|
|
|
|
onFetchNextPage,
|
|
|
|
estimateSize,
|
|
|
|
renderItem,
|
2024-07-03 00:02:24 +08:00
|
|
|
renderEmpty,
|
2024-07-02 22:53:58 +08:00
|
|
|
} = props;
|
|
|
|
|
|
|
|
const parentRef = useRef<HTMLDivElement>(null);
|
2024-07-03 00:02:24 +08:00
|
|
|
const { t } = useTranslation();
|
2024-07-02 22:53:58 +08:00
|
|
|
const rowVirtualizer = useVirtualizer({
|
|
|
|
count: hasNextPage ? allData.length + 1 : allData.length,
|
|
|
|
getScrollElement: () => parentRef.current,
|
2024-07-03 00:02:24 +08:00
|
|
|
estimateSize: () => estimateSize,
|
2024-07-02 22:53:58 +08:00
|
|
|
overscan: 5,
|
|
|
|
});
|
|
|
|
|
|
|
|
const virtualItems = rowVirtualizer.getVirtualItems();
|
|
|
|
|
|
|
|
useWatch([virtualItems], () => {
|
|
|
|
const lastItem = last(virtualItems);
|
|
|
|
|
|
|
|
if (!lastItem) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (
|
|
|
|
lastItem.index >= allData.length - 1 &&
|
|
|
|
hasNextPage &&
|
|
|
|
!isFetchingNextPage
|
|
|
|
) {
|
|
|
|
onFetchNextPage();
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
|
|
|
return (
|
|
|
|
<div ref={parentRef} className="h-full w-full overflow-auto">
|
2024-07-03 00:02:24 +08:00
|
|
|
{virtualItems.length === 0 && (renderEmpty ? renderEmpty() : <Empty />)}
|
2024-07-02 22:53:58 +08:00
|
|
|
|
|
|
|
<div
|
|
|
|
className="relative w-full"
|
|
|
|
style={{
|
|
|
|
height: `${rowVirtualizer.getTotalSize()}px`,
|
|
|
|
}}
|
|
|
|
>
|
2024-07-03 00:02:24 +08:00
|
|
|
{virtualItems.map((virtualItem) => {
|
|
|
|
const isLoaderRow = virtualItem.index > allData.length - 1;
|
|
|
|
const data = allData[virtualItem.index];
|
|
|
|
|
|
|
|
return (
|
|
|
|
<div
|
|
|
|
key={virtualItem.index}
|
|
|
|
className="absolute left-0 top-0 w-full"
|
|
|
|
style={{
|
|
|
|
height: `${virtualItem.size}px`,
|
|
|
|
transform: `translateY(${virtualItem.start}px)`,
|
|
|
|
}}
|
|
|
|
>
|
|
|
|
{isLoaderRow
|
|
|
|
? hasNextPage
|
|
|
|
? t('Loading more...')
|
|
|
|
: t('Nothing more to load')
|
|
|
|
: renderItem(data)}
|
|
|
|
</div>
|
|
|
|
);
|
|
|
|
})}
|
2024-07-02 22:53:58 +08:00
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
);
|
|
|
|
}
|
|
|
|
);
|
|
|
|
SimpleVirtualList.displayName = 'SimpleVirtualList';
|