mirror of https://github.com/buster-so/buster.git
more elegant infinite list component
Co-Authored-By: ellipsis-dev[bot] <65095814+ellipsis-dev[bot]@users.noreply.github.com>
This commit is contained in:
parent
221a4a6280
commit
58fa171a8e
|
@ -156,20 +156,17 @@ const DatasetGroupAssignedCell: React.FC<{
|
||||||
id: string;
|
id: string;
|
||||||
assigned: boolean;
|
assigned: boolean;
|
||||||
onSelect: (params: { id: string; assigned: boolean }) => Promise<void>;
|
onSelect: (params: { id: string; assigned: boolean }) => Promise<void>;
|
||||||
}> = React.memo(
|
}> = React.memo(({ id, assigned, onSelect }) => {
|
||||||
({ id, assigned, onSelect }) => {
|
return (
|
||||||
return (
|
<Select
|
||||||
<Select
|
options={options}
|
||||||
options={options}
|
value={assigned || false}
|
||||||
defaultValue={assigned || false}
|
popupMatchSelectWidth
|
||||||
popupMatchSelectWidth
|
onSelect={(value) => {
|
||||||
onSelect={(value) => {
|
onSelect({ id, assigned: value });
|
||||||
onSelect({ id, assigned: value });
|
}}
|
||||||
}}
|
/>
|
||||||
/>
|
);
|
||||||
);
|
});
|
||||||
},
|
|
||||||
() => true
|
|
||||||
);
|
|
||||||
|
|
||||||
DatasetGroupAssignedCell.displayName = 'DatasetGroupAssignedCell';
|
DatasetGroupAssignedCell.displayName = 'DatasetGroupAssignedCell';
|
||||||
|
|
|
@ -0,0 +1,56 @@
|
||||||
|
'use client';
|
||||||
|
|
||||||
|
import { useRef } from 'react';
|
||||||
|
import { Virtualizer } from 'virtua';
|
||||||
|
import React from 'react';
|
||||||
|
import { useMount } from 'ahooks';
|
||||||
|
|
||||||
|
const headerHeight = 300;
|
||||||
|
|
||||||
|
const ItemSwag = React.memo(({ index }: { index: number }) => {
|
||||||
|
useMount(() => {
|
||||||
|
console.log('useMount', index);
|
||||||
|
});
|
||||||
|
return <div className="h-[48px] border bg-red-200">Swag {index}</div>;
|
||||||
|
});
|
||||||
|
ItemSwag.displayName = 'ItemSwag';
|
||||||
|
|
||||||
|
const createRows = (numberToGenerate: number) => {
|
||||||
|
return Array.from({ length: numberToGenerate }, (_, index) => {
|
||||||
|
return <ItemSwag key={index} index={index} />;
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
export default function ListTest2() {
|
||||||
|
const ref = useRef<HTMLDivElement>(null);
|
||||||
|
const outerPadding = 30;
|
||||||
|
const innerPadding = 50;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
ref={ref}
|
||||||
|
style={{
|
||||||
|
width: '100%',
|
||||||
|
height: '100vh',
|
||||||
|
overflowY: 'auto',
|
||||||
|
// opt out browser's scroll anchoring on header/footer because it will conflict to scroll anchoring of virtualizer
|
||||||
|
overflowAnchor: 'none'
|
||||||
|
}}>
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
backgroundColor: 'burlywood',
|
||||||
|
padding: outerPadding
|
||||||
|
}}>
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
backgroundColor: 'steelblue',
|
||||||
|
padding: innerPadding
|
||||||
|
}}>
|
||||||
|
<Virtualizer scrollRef={ref} startMargin={outerPadding + innerPadding}>
|
||||||
|
{createRows(1000)}
|
||||||
|
</Virtualizer>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
|
@ -1,9 +1,8 @@
|
||||||
import React from 'react';
|
import React, { useRef } from 'react';
|
||||||
import { useMemoizedFn } from 'ahooks';
|
import { useMemoizedFn } from 'ahooks';
|
||||||
import { BusterListProps } from '../BusterList';
|
import { BusterListProps } from '../BusterList';
|
||||||
import { getAllIdsInSection } from '../BusterList/helpers';
|
import { getAllIdsInSection } from '../BusterList/helpers';
|
||||||
import { WindowVirtualizer } from 'virtua';
|
import { useEffect, useMemo } from 'react';
|
||||||
import { useEffect, useMemo, useRef, useCallback } from 'react';
|
|
||||||
import { BusterListHeader } from '../BusterList/BusterListHeader';
|
import { BusterListHeader } from '../BusterList/BusterListHeader';
|
||||||
import { BusterListRowComponentSelector } from '../BusterList/BusterListRowComponentSelector';
|
import { BusterListRowComponentSelector } from '../BusterList/BusterListRowComponentSelector';
|
||||||
|
|
||||||
|
@ -26,8 +25,10 @@ export const BusterInfiniteList: React.FC<BusterInfiniteListProps> = ({
|
||||||
showSelectAll = true,
|
showSelectAll = true,
|
||||||
onScrollEnd,
|
onScrollEnd,
|
||||||
loadingNewContent,
|
loadingNewContent,
|
||||||
scrollEndThreshold = 200 // Default threshold of 200px
|
scrollEndThreshold = 48 // Default threshold of 200px
|
||||||
}) => {
|
}) => {
|
||||||
|
const containerRef = useRef<HTMLDivElement>(null);
|
||||||
|
const scrollRef = useRef<HTMLDivElement | null>(null);
|
||||||
const showEmptyState = useMemo(
|
const showEmptyState = useMemo(
|
||||||
() => (!rows || rows.length === 0 || !rows.some((row) => !row.rowSection)) && !!emptyState,
|
() => (!rows || rows.length === 0 || !rows.some((row) => !row.rowSection)) && !!emptyState,
|
||||||
[rows, emptyState]
|
[rows, emptyState]
|
||||||
|
@ -86,27 +87,44 @@ export const BusterInfiniteList: React.FC<BusterInfiniteListProps> = ({
|
||||||
selectedRowKeys
|
selectedRowKeys
|
||||||
]);
|
]);
|
||||||
|
|
||||||
// Add scroll handler
|
useEffect(() => {
|
||||||
const handleScroll = useCallback(() => {
|
if (!onScrollEnd) return;
|
||||||
// const { scrollTop, scrollHeight, clientHeight } = containerRef.current;
|
|
||||||
// const distanceToBottom = scrollHeight - scrollTop - clientHeight;
|
// Find the first scrollable parent element
|
||||||
// console.log('distanceToBottom', distanceToBottom);
|
const findScrollableParent = (element: HTMLElement | null): HTMLDivElement | null => {
|
||||||
// if (distanceToBottom <= scrollEndThreshold) {
|
while (element) {
|
||||||
// onScrollEnd();
|
const { overflowY } = window.getComputedStyle(element);
|
||||||
// console.log('onScrollEnd');
|
if (overflowY === 'auto' || overflowY === 'scroll') {
|
||||||
// }
|
return element as HTMLDivElement;
|
||||||
|
}
|
||||||
|
element = element.parentElement;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
};
|
||||||
|
|
||||||
|
const scrollableParent = findScrollableParent(containerRef.current?.parentElement ?? null);
|
||||||
|
if (!scrollableParent) return;
|
||||||
|
|
||||||
|
scrollRef.current = scrollableParent;
|
||||||
|
|
||||||
|
// Check if we've scrolled near the bottom
|
||||||
|
const handleScroll = () => {
|
||||||
|
if (!scrollRef.current) return;
|
||||||
|
|
||||||
|
const { scrollTop, scrollHeight, clientHeight } = scrollRef.current;
|
||||||
|
const distanceFromBottom = scrollHeight - scrollTop - clientHeight;
|
||||||
|
|
||||||
|
if (distanceFromBottom <= scrollEndThreshold) {
|
||||||
|
onScrollEnd();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
scrollableParent.addEventListener('scroll', handleScroll);
|
||||||
|
return () => scrollableParent.removeEventListener('scroll', handleScroll);
|
||||||
}, [onScrollEnd, scrollEndThreshold]);
|
}, [onScrollEnd, scrollEndThreshold]);
|
||||||
|
|
||||||
// Add scroll event listener
|
|
||||||
useEffect(() => {
|
|
||||||
// const container = containerRef.current;
|
|
||||||
// if (!container || !onScrollEnd) return;
|
|
||||||
// container.addEventListener('scroll', handleScroll);
|
|
||||||
// return () => container.removeEventListener('scroll', handleScroll);
|
|
||||||
}, [handleScroll, onScrollEnd]);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="infinite-list-container relative flex h-full w-full flex-col">
|
<div ref={containerRef} className="infinite-list-container relative">
|
||||||
{showHeader && !showEmptyState && (
|
{showHeader && !showEmptyState && (
|
||||||
<BusterListHeader
|
<BusterListHeader
|
||||||
columns={columns}
|
columns={columns}
|
||||||
|
@ -117,13 +135,10 @@ export const BusterInfiniteList: React.FC<BusterInfiniteListProps> = ({
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{!showEmptyState && (
|
{!showEmptyState &&
|
||||||
<>
|
rows.map((row) => (
|
||||||
{rows.map((row) => (
|
<BusterListRowComponentSelector key={row.id} row={row} id={row.id} {...itemData} />
|
||||||
<BusterListRowComponentSelector key={row.id} row={row} id={row.id} {...itemData} />
|
))}
|
||||||
))}
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{showEmptyState && (
|
{showEmptyState && (
|
||||||
<div className="flex h-full items-center justify-center">{emptyState}</div>
|
<div className="flex h-full items-center justify-center">{emptyState}</div>
|
||||||
|
|
Loading…
Reference in New Issue