feat: table virtualization (#1408)
* feat: poc table virtualization * feat: table virtualization * feat: add overscan of 15 * fix: increase overscan to 50 * fix: dead code * fix: debug mode * feat: styled space
This commit is contained in:
@ -44,6 +44,7 @@ export function useApolloFactory() {
|
||||
fetchPolicy: 'cache-first',
|
||||
},
|
||||
},
|
||||
connectToDevTools: isDebugMode,
|
||||
// We don't want to re-create the client on token change or it will cause infinite loop
|
||||
initialTokenPair: tokenPair,
|
||||
onTokenPairChange(tokenPair) {
|
||||
|
||||
@ -1,6 +1,9 @@
|
||||
import styled from '@emotion/styled';
|
||||
import { useVirtual } from '@tanstack/react-virtual';
|
||||
import { useRecoilValue } from 'recoil';
|
||||
|
||||
import { isNavbarSwitchingSizeState } from '@/ui/layout/states/isNavbarSwitchingSizeState';
|
||||
import { useScrollWrapperScopedRef } from '@/ui/utilities/scroll/hooks/useScrollWrapperScopedRef';
|
||||
|
||||
import { RowIdContext } from '../contexts/RowIdContext';
|
||||
import { RowIndexContext } from '../contexts/RowIndexContext';
|
||||
@ -9,28 +12,70 @@ import { tableRowIdsState } from '../states/tableRowIdsState';
|
||||
|
||||
import { EntityTableRow } from './EntityTableRow';
|
||||
|
||||
type SpaceProps = {
|
||||
top?: number;
|
||||
bottom?: number;
|
||||
};
|
||||
|
||||
const StyledSpace = styled.td<SpaceProps>`
|
||||
${({ top }) => top && `padding-top: ${top}px;`}
|
||||
${({ bottom }) => bottom && `padding-bottom: ${bottom}px;`}
|
||||
`;
|
||||
|
||||
export function EntityTableBody() {
|
||||
const scrollWrapperRef = useScrollWrapperScopedRef();
|
||||
|
||||
const rowIds = useRecoilValue(tableRowIdsState);
|
||||
|
||||
const isNavbarSwitchingSize = useRecoilValue(isNavbarSwitchingSizeState);
|
||||
|
||||
const isFetchingEntityTableData = useRecoilValue(
|
||||
isFetchingEntityTableDataState,
|
||||
);
|
||||
|
||||
const rowVirtualizer = useVirtual({
|
||||
size: rowIds.length,
|
||||
parentRef: scrollWrapperRef,
|
||||
overscan: 50,
|
||||
});
|
||||
|
||||
const items = rowVirtualizer.virtualItems;
|
||||
const paddingTop = items.length > 0 ? items[0].start : 0;
|
||||
const paddingBottom =
|
||||
items.length > 0
|
||||
? rowVirtualizer.totalSize - items[items.length - 1].end
|
||||
: 0;
|
||||
|
||||
if (isFetchingEntityTableData || isNavbarSwitchingSize) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<tbody>
|
||||
{rowIds.map((rowId, index) => (
|
||||
<RowIdContext.Provider value={rowId} key={rowId}>
|
||||
<RowIndexContext.Provider value={index}>
|
||||
<EntityTableRow rowId={rowId} />
|
||||
</RowIndexContext.Provider>
|
||||
</RowIdContext.Provider>
|
||||
))}
|
||||
</tbody>
|
||||
<>
|
||||
{paddingTop > 0 && (
|
||||
<tr>
|
||||
<StyledSpace top={paddingTop} />
|
||||
</tr>
|
||||
)}
|
||||
{items.map((virtualItem) => {
|
||||
const rowId = rowIds[virtualItem.index];
|
||||
|
||||
return (
|
||||
<RowIdContext.Provider value={rowId} key={rowId}>
|
||||
<RowIndexContext.Provider value={virtualItem.index}>
|
||||
<EntityTableRow
|
||||
key={virtualItem.index}
|
||||
ref={virtualItem.measureRef}
|
||||
rowId={rowId}
|
||||
/>
|
||||
</RowIndexContext.Provider>
|
||||
</RowIdContext.Provider>
|
||||
);
|
||||
})}
|
||||
{paddingBottom > 0 && (
|
||||
<tr>
|
||||
<StyledSpace bottom={paddingBottom} />
|
||||
</tr>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
@ -1,3 +1,4 @@
|
||||
import { forwardRef } from 'react';
|
||||
import styled from '@emotion/styled';
|
||||
|
||||
import { useRecoilScopedValue } from '@/ui/utilities/recoil-scope/hooks/useRecoilScopedValue';
|
||||
@ -15,7 +16,14 @@ const StyledRow = styled.tr<{ selected: boolean }>`
|
||||
props.selected ? props.theme.accent.quaternary : 'none'};
|
||||
`;
|
||||
|
||||
export function EntityTableRow({ rowId }: { rowId: string }) {
|
||||
type EntityTableRowProps = {
|
||||
rowId: string;
|
||||
};
|
||||
|
||||
export const EntityTableRow = forwardRef<
|
||||
HTMLTableRowElement,
|
||||
EntityTableRowProps
|
||||
>(function EntityTableRow({ rowId }, ref) {
|
||||
const columns = useRecoilScopedValue(
|
||||
visibleTableColumnsScopedSelector,
|
||||
TableRecoilScopeContext,
|
||||
@ -24,6 +32,7 @@ export function EntityTableRow({ rowId }: { rowId: string }) {
|
||||
|
||||
return (
|
||||
<StyledRow
|
||||
ref={ref}
|
||||
data-testid={`row-id-${rowId}`}
|
||||
selected={currentRowSelected}
|
||||
data-selectable-id={rowId}
|
||||
@ -41,4 +50,4 @@ export function EntityTableRow({ rowId }: { rowId: string }) {
|
||||
<td></td>
|
||||
</StyledRow>
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
@ -1,8 +1,12 @@
|
||||
import { useRef } from 'react';
|
||||
import { createContext, RefObject, useRef } from 'react';
|
||||
import styled from '@emotion/styled';
|
||||
|
||||
import { useListenScroll } from '../hooks/useListenScroll';
|
||||
|
||||
export const ScrollWrapperContext = createContext<RefObject<HTMLDivElement>>({
|
||||
current: null,
|
||||
});
|
||||
|
||||
const StyledScrollWrapper = styled.div`
|
||||
display: flex;
|
||||
height: 100%;
|
||||
@ -28,8 +32,10 @@ export function ScrollWrapper({ children, className }: ScrollWrapperProps) {
|
||||
});
|
||||
|
||||
return (
|
||||
<StyledScrollWrapper ref={scrollableRef} className={className}>
|
||||
{children}
|
||||
</StyledScrollWrapper>
|
||||
<ScrollWrapperContext.Provider value={scrollableRef}>
|
||||
<StyledScrollWrapper ref={scrollableRef} className={className}>
|
||||
{children}
|
||||
</StyledScrollWrapper>
|
||||
</ScrollWrapperContext.Provider>
|
||||
);
|
||||
}
|
||||
|
||||
@ -0,0 +1,14 @@
|
||||
import { useContext } from 'react';
|
||||
|
||||
import { ScrollWrapperContext } from '../components/ScrollWrapper';
|
||||
|
||||
export function useScrollWrapperScopedRef() {
|
||||
const scrollWrapperRef = useContext(ScrollWrapperContext);
|
||||
|
||||
if (!scrollWrapperRef)
|
||||
throw new Error(
|
||||
`Using a scoped ref without a ScrollWrapper : verify that you are using a ScrollWrapper if you intended to do so.`,
|
||||
);
|
||||
|
||||
return scrollWrapperRef;
|
||||
}
|
||||
Reference in New Issue
Block a user