mirror of
https://github.com/jeffvli/feishin.git
synced 2026-05-07 04:20:12 +02:00
list component optimizations
This commit is contained in:
@@ -4,7 +4,7 @@ import debounce from 'lodash/debounce';
|
|||||||
import throttle from 'lodash/throttle';
|
import throttle from 'lodash/throttle';
|
||||||
import { AnimatePresence } from 'motion/react';
|
import { AnimatePresence } from 'motion/react';
|
||||||
import { useOverlayScrollbars } from 'overlayscrollbars-react';
|
import { useOverlayScrollbars } from 'overlayscrollbars-react';
|
||||||
import {
|
import React, {
|
||||||
CSSProperties,
|
CSSProperties,
|
||||||
Ref,
|
Ref,
|
||||||
UIEvent,
|
UIEvent,
|
||||||
@@ -31,6 +31,143 @@ import {
|
|||||||
import { ItemListHandle } from '/@/renderer/components/item-list/types';
|
import { ItemListHandle } from '/@/renderer/components/item-list/types';
|
||||||
import { LibraryItem } from '/@/shared/types/domain-types';
|
import { LibraryItem } from '/@/shared/types/domain-types';
|
||||||
|
|
||||||
|
interface VirtualizedGridListProps {
|
||||||
|
data: unknown[];
|
||||||
|
enableExpansion: boolean;
|
||||||
|
enableSelection: boolean;
|
||||||
|
gap: 'lg' | 'md' | 'sm' | 'xl' | 'xs';
|
||||||
|
internalState: ItemListStateActions;
|
||||||
|
itemGridRef: React.RefObject<any>;
|
||||||
|
itemType: LibraryItem;
|
||||||
|
onRowsRendered: (visibleRows: { startIndex: number; stopIndex: number }) => void;
|
||||||
|
onScroll: (e: UIEvent<HTMLDivElement>) => void;
|
||||||
|
tableMeta: null | {
|
||||||
|
columnCount: number;
|
||||||
|
itemHeight: number;
|
||||||
|
rowCount: number;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const VirtualizedGridList = React.memo(
|
||||||
|
({
|
||||||
|
data,
|
||||||
|
enableExpansion,
|
||||||
|
enableSelection,
|
||||||
|
gap,
|
||||||
|
internalState,
|
||||||
|
itemGridRef,
|
||||||
|
itemType,
|
||||||
|
onRowsRendered,
|
||||||
|
onScroll,
|
||||||
|
tableMeta,
|
||||||
|
}: VirtualizedGridListProps) => {
|
||||||
|
const elements = useMemo(() => {
|
||||||
|
if (!tableMeta) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
return data
|
||||||
|
.map((d, i) => {
|
||||||
|
return {
|
||||||
|
data: d,
|
||||||
|
index: i,
|
||||||
|
};
|
||||||
|
})
|
||||||
|
.reduce(
|
||||||
|
(acc, d) => {
|
||||||
|
if (d.index % (tableMeta?.columnCount || 0) === 0) {
|
||||||
|
acc.push([]);
|
||||||
|
}
|
||||||
|
const prev = acc[acc.length - 1];
|
||||||
|
prev.push(d);
|
||||||
|
return acc;
|
||||||
|
},
|
||||||
|
[] as { data: any; index: number }[][],
|
||||||
|
);
|
||||||
|
}, [tableMeta, data]);
|
||||||
|
|
||||||
|
const itemProps: GridItemProps = {
|
||||||
|
columns: tableMeta?.columnCount || 0,
|
||||||
|
data: elements,
|
||||||
|
enableExpansion,
|
||||||
|
enableSelection,
|
||||||
|
gap,
|
||||||
|
internalState,
|
||||||
|
itemType,
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<List
|
||||||
|
listRef={itemGridRef}
|
||||||
|
onRowsRendered={onRowsRendered}
|
||||||
|
onScroll={onScroll}
|
||||||
|
rowComponent={ListComponent}
|
||||||
|
rowCount={tableMeta?.rowCount || 0}
|
||||||
|
rowHeight={tableMeta?.itemHeight || 0}
|
||||||
|
rowProps={itemProps}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
VirtualizedGridList.displayName = 'VirtualizedGridList';
|
||||||
|
|
||||||
|
// Throttled function moved outside component for better performance
|
||||||
|
const createThrottledSetTableMeta = (itemsPerRow?: number) => {
|
||||||
|
return throttle(
|
||||||
|
(
|
||||||
|
width: number,
|
||||||
|
dataLength: number,
|
||||||
|
type: LibraryItem,
|
||||||
|
setTableMeta: (meta: any) => void,
|
||||||
|
) => {
|
||||||
|
const isSm = width >= 600;
|
||||||
|
const isMd = width >= 768;
|
||||||
|
const isLg = width >= 960;
|
||||||
|
const isXl = width >= 1200;
|
||||||
|
const is2xl = width >= 1440;
|
||||||
|
const is3xl = width >= 1920;
|
||||||
|
const is4xl = width >= 2560;
|
||||||
|
|
||||||
|
let dynamicItemsPerRow = 2;
|
||||||
|
|
||||||
|
if (is4xl) {
|
||||||
|
dynamicItemsPerRow = 12;
|
||||||
|
} else if (is3xl) {
|
||||||
|
dynamicItemsPerRow = 10;
|
||||||
|
} else if (is2xl) {
|
||||||
|
dynamicItemsPerRow = 8;
|
||||||
|
} else if (isXl) {
|
||||||
|
dynamicItemsPerRow = 6;
|
||||||
|
} else if (isLg) {
|
||||||
|
dynamicItemsPerRow = 5;
|
||||||
|
} else if (isMd) {
|
||||||
|
dynamicItemsPerRow = 4;
|
||||||
|
} else if (isSm) {
|
||||||
|
dynamicItemsPerRow = 3;
|
||||||
|
} else {
|
||||||
|
dynamicItemsPerRow = 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
const setItemsPerRow = itemsPerRow || dynamicItemsPerRow;
|
||||||
|
|
||||||
|
const widthPerItem = Number(width) / setItemsPerRow;
|
||||||
|
const itemHeight = widthPerItem + getDataRowsCount(type) * 26;
|
||||||
|
|
||||||
|
if (widthPerItem === 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
setTableMeta({
|
||||||
|
columnCount: setItemsPerRow,
|
||||||
|
itemHeight,
|
||||||
|
rowCount: Math.ceil(dataLength / setItemsPerRow),
|
||||||
|
});
|
||||||
|
},
|
||||||
|
200,
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
export interface GridItemProps {
|
export interface GridItemProps {
|
||||||
columns: number;
|
columns: number;
|
||||||
data: any[];
|
data: any[];
|
||||||
@@ -155,56 +292,13 @@ export const ItemGridList = ({
|
|||||||
rowCount: number;
|
rowCount: number;
|
||||||
}>(null);
|
}>(null);
|
||||||
|
|
||||||
// Throttled function to update table meta
|
// Use throttled function created outside component for better performance
|
||||||
const throttledSetTableMeta = useMemo(() => {
|
const throttledSetTableMeta = useMemo(() => {
|
||||||
return throttle((width: number, dataLength: number, type: LibraryItem) => {
|
return createThrottledSetTableMeta(itemsPerRow);
|
||||||
const isSm = width >= 600;
|
|
||||||
const isMd = width >= 768;
|
|
||||||
const isLg = width >= 960;
|
|
||||||
const isXl = width >= 1200;
|
|
||||||
const is2xl = width >= 1440;
|
|
||||||
const is3xl = width >= 1920;
|
|
||||||
const is4xl = width >= 2560;
|
|
||||||
|
|
||||||
let dynamicItemsPerRow = 2;
|
|
||||||
|
|
||||||
if (is4xl) {
|
|
||||||
dynamicItemsPerRow = 12;
|
|
||||||
} else if (is3xl) {
|
|
||||||
dynamicItemsPerRow = 10;
|
|
||||||
} else if (is2xl) {
|
|
||||||
dynamicItemsPerRow = 8;
|
|
||||||
} else if (isXl) {
|
|
||||||
dynamicItemsPerRow = 6;
|
|
||||||
} else if (isLg) {
|
|
||||||
dynamicItemsPerRow = 5;
|
|
||||||
} else if (isMd) {
|
|
||||||
dynamicItemsPerRow = 4;
|
|
||||||
} else if (isSm) {
|
|
||||||
dynamicItemsPerRow = 3;
|
|
||||||
} else {
|
|
||||||
dynamicItemsPerRow = 2;
|
|
||||||
}
|
|
||||||
|
|
||||||
const setItemsPerRow = itemsPerRow || dynamicItemsPerRow;
|
|
||||||
|
|
||||||
const widthPerItem = Number(width) / setItemsPerRow;
|
|
||||||
const itemHeight = widthPerItem + getDataRowsCount(type) * 26;
|
|
||||||
|
|
||||||
if (widthPerItem === 0) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
setTableMeta({
|
|
||||||
columnCount: setItemsPerRow,
|
|
||||||
itemHeight,
|
|
||||||
rowCount: Math.ceil(dataLength / setItemsPerRow),
|
|
||||||
});
|
|
||||||
}, 200);
|
|
||||||
}, [itemsPerRow]);
|
}, [itemsPerRow]);
|
||||||
|
|
||||||
useLayoutEffect(() => {
|
useLayoutEffect(() => {
|
||||||
throttledSetTableMeta(containerWidth, data.length, itemType);
|
throttledSetTableMeta(containerWidth, data.length, itemType, setTableMeta);
|
||||||
}, [containerWidth, data.length, itemType, throttledSetTableMeta]);
|
}, [containerWidth, data.length, itemType, throttledSetTableMeta]);
|
||||||
|
|
||||||
const handleOnRowsRendered = useCallback(
|
const handleOnRowsRendered = useCallback(
|
||||||
@@ -237,41 +331,6 @@ export const ItemGridList = ({
|
|||||||
],
|
],
|
||||||
);
|
);
|
||||||
|
|
||||||
const elements = useMemo(() => {
|
|
||||||
if (!tableMeta) {
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
|
|
||||||
return data
|
|
||||||
.map((d, i) => {
|
|
||||||
return {
|
|
||||||
data: d,
|
|
||||||
index: i,
|
|
||||||
};
|
|
||||||
})
|
|
||||||
.reduce(
|
|
||||||
(acc, d) => {
|
|
||||||
if (d.index % (tableMeta?.columnCount || 0) === 0) {
|
|
||||||
acc.push([]);
|
|
||||||
}
|
|
||||||
const prev = acc[acc.length - 1];
|
|
||||||
prev.push(d);
|
|
||||||
return acc;
|
|
||||||
},
|
|
||||||
[] as { data: any; index: number }[][],
|
|
||||||
);
|
|
||||||
}, [tableMeta, data]);
|
|
||||||
|
|
||||||
const itemProps: GridItemProps = {
|
|
||||||
columns: tableMeta?.columnCount || 0,
|
|
||||||
data: elements,
|
|
||||||
enableExpansion,
|
|
||||||
enableSelection,
|
|
||||||
gap,
|
|
||||||
internalState,
|
|
||||||
itemType,
|
|
||||||
};
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!initialTop || isInitialScrollPositionSet.current || !tableMeta?.itemHeight) return;
|
if (!initialTop || isInitialScrollPositionSet.current || !tableMeta?.itemHeight) return;
|
||||||
isInitialScrollPositionSet.current = true;
|
isInitialScrollPositionSet.current = true;
|
||||||
@@ -319,14 +378,17 @@ export const ItemGridList = ({
|
|||||||
data-overlayscrollbars-initialize=""
|
data-overlayscrollbars-initialize=""
|
||||||
ref={mergedContainerRef}
|
ref={mergedContainerRef}
|
||||||
>
|
>
|
||||||
<List
|
<VirtualizedGridList
|
||||||
listRef={itemGridRef}
|
data={data}
|
||||||
|
enableExpansion={enableExpansion}
|
||||||
|
enableSelection={enableSelection}
|
||||||
|
gap={gap}
|
||||||
|
internalState={internalState}
|
||||||
|
itemGridRef={itemGridRef}
|
||||||
|
itemType={itemType}
|
||||||
onRowsRendered={handleOnRowsRendered}
|
onRowsRendered={handleOnRowsRendered}
|
||||||
onScroll={handleScroll}
|
onScroll={handleScroll}
|
||||||
rowComponent={ListComponent}
|
tableMeta={tableMeta}
|
||||||
rowCount={tableMeta?.rowCount || 0}
|
|
||||||
rowHeight={tableMeta?.itemHeight || 0}
|
|
||||||
rowProps={itemProps}
|
|
||||||
/>
|
/>
|
||||||
<AnimatePresence>
|
<AnimatePresence>
|
||||||
{hasExpanded && (
|
{hasExpanded && (
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import { useMergedRef } from '@mantine/hooks';
|
|||||||
import clsx from 'clsx';
|
import clsx from 'clsx';
|
||||||
import { AnimatePresence } from 'motion/react';
|
import { AnimatePresence } from 'motion/react';
|
||||||
import { useOverlayScrollbars } from 'overlayscrollbars-react';
|
import { useOverlayScrollbars } from 'overlayscrollbars-react';
|
||||||
import {
|
import React, {
|
||||||
type JSXElementConstructor,
|
type JSXElementConstructor,
|
||||||
Ref,
|
Ref,
|
||||||
useCallback,
|
useCallback,
|
||||||
@@ -28,6 +28,354 @@ import { parseTableColumns } from '/@/renderer/components/item-list/helpers/pars
|
|||||||
import { ItemListHandle, ItemTableListColumnConfig } from '/@/renderer/components/item-list/types';
|
import { ItemListHandle, ItemTableListColumnConfig } from '/@/renderer/components/item-list/types';
|
||||||
import { LibraryItem } from '/@/shared/types/domain-types';
|
import { LibraryItem } from '/@/shared/types/domain-types';
|
||||||
|
|
||||||
|
interface VirtualizedTableGridProps {
|
||||||
|
calculatedColumnWidths: number[];
|
||||||
|
CellComponent: JSXElementConstructor<CellComponentProps<TableItemProps>>;
|
||||||
|
cellPadding: 'lg' | 'md' | 'sm' | 'xl' | 'xs';
|
||||||
|
data: unknown[];
|
||||||
|
enableAlternateRowColors: boolean;
|
||||||
|
enableExpansion: boolean;
|
||||||
|
enableHeader: boolean;
|
||||||
|
enableHorizontalBorders: boolean;
|
||||||
|
enableRowHoverHighlight: boolean;
|
||||||
|
enableSelection: boolean;
|
||||||
|
enableVerticalBorders: boolean;
|
||||||
|
getRowHeight: (index: number, cellProps: TableItemProps) => number;
|
||||||
|
headerHeight: number;
|
||||||
|
internalState: ItemListStateActions;
|
||||||
|
itemType: LibraryItem;
|
||||||
|
mergedRowRef: React.Ref<HTMLDivElement>;
|
||||||
|
onCellsRendered?: GridProps<TableItemProps>['onCellsRendered'];
|
||||||
|
parsedColumns: ReturnType<typeof parseTableColumns>;
|
||||||
|
pinnedLeftColumnCount: number;
|
||||||
|
pinnedLeftColumnRef: React.RefObject<HTMLDivElement>;
|
||||||
|
pinnedRightColumnCount: number;
|
||||||
|
pinnedRightColumnRef: React.RefObject<HTMLDivElement>;
|
||||||
|
pinnedRowCount: number;
|
||||||
|
pinnedRowRef: React.RefObject<HTMLDivElement>;
|
||||||
|
showLeftShadow: boolean;
|
||||||
|
showRightShadow: boolean;
|
||||||
|
size: 'compact' | 'default' | 'large';
|
||||||
|
totalColumnCount: number;
|
||||||
|
totalRowCount: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
const VirtualizedTableGrid = React.memo(
|
||||||
|
({
|
||||||
|
calculatedColumnWidths,
|
||||||
|
CellComponent,
|
||||||
|
cellPadding,
|
||||||
|
data,
|
||||||
|
enableAlternateRowColors,
|
||||||
|
enableExpansion,
|
||||||
|
enableHeader,
|
||||||
|
enableHorizontalBorders,
|
||||||
|
enableRowHoverHighlight,
|
||||||
|
enableSelection,
|
||||||
|
enableVerticalBorders,
|
||||||
|
getRowHeight,
|
||||||
|
headerHeight,
|
||||||
|
internalState,
|
||||||
|
itemType,
|
||||||
|
mergedRowRef,
|
||||||
|
onCellsRendered,
|
||||||
|
parsedColumns,
|
||||||
|
pinnedLeftColumnCount,
|
||||||
|
pinnedLeftColumnRef,
|
||||||
|
pinnedRightColumnCount,
|
||||||
|
pinnedRightColumnRef,
|
||||||
|
pinnedRowCount,
|
||||||
|
pinnedRowRef,
|
||||||
|
showLeftShadow,
|
||||||
|
showRightShadow,
|
||||||
|
size,
|
||||||
|
totalColumnCount,
|
||||||
|
totalRowCount,
|
||||||
|
}: VirtualizedTableGridProps) => {
|
||||||
|
const columnWidth = useCallback(
|
||||||
|
(index: number) => calculatedColumnWidths[index],
|
||||||
|
[calculatedColumnWidths],
|
||||||
|
);
|
||||||
|
|
||||||
|
const itemProps: TableItemProps = useMemo(
|
||||||
|
() => ({
|
||||||
|
cellPadding,
|
||||||
|
columns: parsedColumns,
|
||||||
|
data: enableHeader ? [null, ...data] : data,
|
||||||
|
enableAlternateRowColors,
|
||||||
|
enableExpansion,
|
||||||
|
enableHeader,
|
||||||
|
enableHorizontalBorders,
|
||||||
|
enableRowHoverHighlight,
|
||||||
|
enableSelection,
|
||||||
|
enableVerticalBorders,
|
||||||
|
getRowHeight,
|
||||||
|
internalState,
|
||||||
|
itemType,
|
||||||
|
size,
|
||||||
|
}),
|
||||||
|
[
|
||||||
|
cellPadding,
|
||||||
|
parsedColumns,
|
||||||
|
enableHeader,
|
||||||
|
data,
|
||||||
|
enableAlternateRowColors,
|
||||||
|
enableExpansion,
|
||||||
|
enableHorizontalBorders,
|
||||||
|
enableRowHoverHighlight,
|
||||||
|
enableSelection,
|
||||||
|
enableVerticalBorders,
|
||||||
|
getRowHeight,
|
||||||
|
internalState,
|
||||||
|
itemType,
|
||||||
|
size,
|
||||||
|
],
|
||||||
|
);
|
||||||
|
|
||||||
|
const PinnedRowCell = useCallback(
|
||||||
|
(cellProps: CellComponentProps & TableItemProps) => {
|
||||||
|
return (
|
||||||
|
<CellComponent
|
||||||
|
{...cellProps}
|
||||||
|
columnIndex={cellProps.columnIndex + pinnedLeftColumnCount}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
[pinnedLeftColumnCount, CellComponent],
|
||||||
|
);
|
||||||
|
|
||||||
|
const PinnedColumnCell = useCallback(
|
||||||
|
(cellProps: CellComponentProps & TableItemProps) => {
|
||||||
|
return (
|
||||||
|
<CellComponent {...cellProps} rowIndex={cellProps.rowIndex + pinnedRowCount} />
|
||||||
|
);
|
||||||
|
},
|
||||||
|
[pinnedRowCount, CellComponent],
|
||||||
|
);
|
||||||
|
|
||||||
|
const PinnedRightColumnCell = useCallback(
|
||||||
|
(cellProps: CellComponentProps & TableItemProps) => {
|
||||||
|
return (
|
||||||
|
<CellComponent
|
||||||
|
{...cellProps}
|
||||||
|
columnIndex={
|
||||||
|
cellProps.columnIndex + pinnedLeftColumnCount + totalColumnCount
|
||||||
|
}
|
||||||
|
rowIndex={cellProps.rowIndex + pinnedRowCount}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
[pinnedLeftColumnCount, pinnedRowCount, totalColumnCount, CellComponent],
|
||||||
|
);
|
||||||
|
|
||||||
|
const PinnedRightIntersectionCell = useCallback(
|
||||||
|
(cellProps: CellComponentProps & TableItemProps) => {
|
||||||
|
return (
|
||||||
|
<CellComponent
|
||||||
|
{...cellProps}
|
||||||
|
columnIndex={
|
||||||
|
cellProps.columnIndex + pinnedLeftColumnCount + totalColumnCount
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
[pinnedLeftColumnCount, totalColumnCount, CellComponent],
|
||||||
|
);
|
||||||
|
|
||||||
|
const RowCell = useCallback(
|
||||||
|
(cellProps: CellComponentProps<TableItemProps>) => {
|
||||||
|
return (
|
||||||
|
<CellComponent
|
||||||
|
{...cellProps}
|
||||||
|
columnIndex={cellProps.columnIndex + pinnedLeftColumnCount}
|
||||||
|
rowIndex={cellProps.rowIndex + pinnedRowCount}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
[pinnedLeftColumnCount, pinnedRowCount, CellComponent],
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={styles.itemTableContainer}>
|
||||||
|
<div
|
||||||
|
className={styles.itemTablePinnedColumnsGridContainer}
|
||||||
|
style={{
|
||||||
|
minWidth: `${Array.from({ length: pinnedLeftColumnCount }, () => 0).reduce(
|
||||||
|
(a, _, i) => a + columnWidth(i),
|
||||||
|
0,
|
||||||
|
)}px`,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{!!(pinnedLeftColumnCount || pinnedRowCount) && (
|
||||||
|
<div
|
||||||
|
className={clsx(styles.itemTablePinnedIntersectionGridContainer, {
|
||||||
|
[styles.withHeader]: enableHeader,
|
||||||
|
})}
|
||||||
|
style={{
|
||||||
|
minHeight: `${Array.from(
|
||||||
|
{ length: pinnedRowCount },
|
||||||
|
() => 0,
|
||||||
|
).reduce((a, _, i) => a + getRowHeight(i, itemProps), 0)}px`,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Grid
|
||||||
|
cellComponent={CellComponent as any}
|
||||||
|
cellProps={itemProps}
|
||||||
|
className={styles.noScrollbar}
|
||||||
|
columnCount={pinnedLeftColumnCount}
|
||||||
|
columnWidth={columnWidth}
|
||||||
|
rowCount={pinnedRowCount}
|
||||||
|
rowHeight={getRowHeight}
|
||||||
|
/>
|
||||||
|
{enableHeader && <div className={styles.itemTablePinnedHeaderShadow} />}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
{!!pinnedLeftColumnCount && (
|
||||||
|
<div
|
||||||
|
className={styles.itemTablePinnedColumnsContainer}
|
||||||
|
ref={pinnedLeftColumnRef}
|
||||||
|
>
|
||||||
|
<Grid
|
||||||
|
cellComponent={PinnedColumnCell}
|
||||||
|
cellProps={itemProps}
|
||||||
|
className={clsx(styles.noScrollbar, styles.height100)}
|
||||||
|
columnCount={pinnedLeftColumnCount}
|
||||||
|
columnWidth={columnWidth}
|
||||||
|
rowCount={totalRowCount}
|
||||||
|
rowHeight={(index, cellProps) => {
|
||||||
|
return getRowHeight(index + pinnedRowCount, cellProps);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
<div className={styles.itemTablePinnedRowsContainer}>
|
||||||
|
{!!pinnedRowCount && (
|
||||||
|
<div
|
||||||
|
className={clsx(styles.itemTablePinnedRowsGridContainer, {
|
||||||
|
[styles.withHeader]: enableHeader,
|
||||||
|
})}
|
||||||
|
ref={pinnedRowRef}
|
||||||
|
style={
|
||||||
|
{
|
||||||
|
'--header-height': `${headerHeight}px`,
|
||||||
|
minHeight: `${Array.from(
|
||||||
|
{ length: pinnedRowCount },
|
||||||
|
() => 0,
|
||||||
|
).reduce((a, _, i) => a + getRowHeight(i, itemProps), 0)}px`,
|
||||||
|
} as React.CSSProperties
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<Grid
|
||||||
|
cellComponent={PinnedRowCell}
|
||||||
|
cellProps={itemProps}
|
||||||
|
className={styles.noScrollbar}
|
||||||
|
columnCount={totalColumnCount}
|
||||||
|
columnWidth={(index) => {
|
||||||
|
return columnWidth(index + pinnedLeftColumnCount);
|
||||||
|
}}
|
||||||
|
rowCount={Array.from({ length: pinnedRowCount }, () => 0).length}
|
||||||
|
rowHeight={getRowHeight}
|
||||||
|
/>
|
||||||
|
{enableHeader && <div className={styles.itemTablePinnedHeaderShadow} />}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
<div className={styles.itemTableGridContainer} ref={mergedRowRef}>
|
||||||
|
<Grid
|
||||||
|
cellComponent={RowCell}
|
||||||
|
cellProps={itemProps}
|
||||||
|
className={styles.height100}
|
||||||
|
columnCount={totalColumnCount}
|
||||||
|
columnWidth={(index) => {
|
||||||
|
return columnWidth(index + pinnedLeftColumnCount);
|
||||||
|
}}
|
||||||
|
onCellsRendered={onCellsRendered}
|
||||||
|
rowCount={totalRowCount}
|
||||||
|
rowHeight={(index, cellProps) => {
|
||||||
|
return getRowHeight(index + pinnedRowCount, cellProps);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
{pinnedLeftColumnCount > 0 && showLeftShadow && (
|
||||||
|
<div className={styles.itemTableLeftScrollShadow} />
|
||||||
|
)}
|
||||||
|
{pinnedRightColumnCount > 0 && showRightShadow && (
|
||||||
|
<div className={styles.itemTableRightScrollShadow} />
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{!!pinnedRightColumnCount && (
|
||||||
|
<div
|
||||||
|
className={styles.itemTablePinnedColumnsGridContainer}
|
||||||
|
style={{
|
||||||
|
minWidth: `${Array.from(
|
||||||
|
{ length: pinnedRightColumnCount },
|
||||||
|
() => 0,
|
||||||
|
).reduce(
|
||||||
|
(a, _, i) =>
|
||||||
|
a + columnWidth(i + pinnedLeftColumnCount + totalColumnCount),
|
||||||
|
0,
|
||||||
|
)}px`,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{!!(pinnedRightColumnCount || pinnedRowCount) && (
|
||||||
|
<div
|
||||||
|
className={clsx(styles.itemTablePinnedIntersectionGridContainer, {
|
||||||
|
[styles.withHeader]: enableHeader,
|
||||||
|
})}
|
||||||
|
style={{
|
||||||
|
minHeight: `${Array.from(
|
||||||
|
{ length: pinnedRowCount },
|
||||||
|
() => 0,
|
||||||
|
).reduce((a, _, i) => a + getRowHeight(i, itemProps), 0)}px`,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Grid
|
||||||
|
cellComponent={PinnedRightIntersectionCell}
|
||||||
|
cellProps={itemProps}
|
||||||
|
className={styles.noScrollbar}
|
||||||
|
columnCount={pinnedRightColumnCount}
|
||||||
|
columnWidth={(index) => {
|
||||||
|
return columnWidth(
|
||||||
|
index + pinnedLeftColumnCount + totalColumnCount,
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
rowCount={pinnedRowCount}
|
||||||
|
rowHeight={getRowHeight}
|
||||||
|
/>
|
||||||
|
{enableHeader && (
|
||||||
|
<div className={styles.itemTablePinnedHeaderShadow} />
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
<div
|
||||||
|
className={styles.itemTablePinnedRightColumnsContainer}
|
||||||
|
ref={pinnedRightColumnRef}
|
||||||
|
>
|
||||||
|
<Grid
|
||||||
|
cellComponent={PinnedRightColumnCell}
|
||||||
|
cellProps={itemProps}
|
||||||
|
className={clsx(styles.noScrollbar, styles.height100)}
|
||||||
|
columnCount={pinnedRightColumnCount}
|
||||||
|
columnWidth={(index) => {
|
||||||
|
return columnWidth(
|
||||||
|
index + pinnedLeftColumnCount + totalColumnCount,
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
rowCount={totalRowCount}
|
||||||
|
rowHeight={(index, cellProps) => {
|
||||||
|
return getRowHeight(index + pinnedRowCount, cellProps);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
VirtualizedTableGrid.displayName = 'VirtualizedTableGrid';
|
||||||
|
|
||||||
export interface TableItemProps {
|
export interface TableItemProps {
|
||||||
cellPadding?: ItemTableListProps['cellPadding'];
|
cellPadding?: ItemTableListProps['cellPadding'];
|
||||||
columns: ItemTableListColumnConfig[];
|
columns: ItemTableListColumnConfig[];
|
||||||
@@ -166,9 +514,6 @@ export const ItemTableList = ({
|
|||||||
return distributed;
|
return distributed;
|
||||||
}, [parsedColumns, centerContainerWidth]);
|
}, [parsedColumns, centerContainerWidth]);
|
||||||
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
||||||
const columnWidth = (index: number, _cellProps: TableItemProps) =>
|
|
||||||
calculatedColumnWidths[index];
|
|
||||||
const pinnedLeftColumnCount = parsedColumns.filter((col) => col.pinned === 'left').length;
|
const pinnedLeftColumnCount = parsedColumns.filter((col) => col.pinned === 'left').length;
|
||||||
const pinnedRightColumnCount = parsedColumns.filter((col) => col.pinned === 'right').length;
|
const pinnedRightColumnCount = parsedColumns.filter((col) => col.pinned === 'right').length;
|
||||||
|
|
||||||
@@ -602,80 +947,6 @@ export const ItemTableList = ({
|
|||||||
],
|
],
|
||||||
);
|
);
|
||||||
|
|
||||||
const PinnedRowCell = useCallback(
|
|
||||||
(cellProps: CellComponentProps & TableItemProps) => {
|
|
||||||
return (
|
|
||||||
<CellComponent
|
|
||||||
{...cellProps}
|
|
||||||
columnIndex={cellProps.columnIndex + pinnedLeftColumnCount}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
},
|
|
||||||
[pinnedLeftColumnCount, CellComponent],
|
|
||||||
);
|
|
||||||
|
|
||||||
const PinnedColumnCell = useCallback(
|
|
||||||
(cellProps: CellComponentProps & TableItemProps) => {
|
|
||||||
return <CellComponent {...cellProps} rowIndex={cellProps.rowIndex + pinnedRowCount} />;
|
|
||||||
},
|
|
||||||
[pinnedRowCount, CellComponent],
|
|
||||||
);
|
|
||||||
|
|
||||||
const PinnedRightColumnCell = useCallback(
|
|
||||||
(cellProps: CellComponentProps & TableItemProps) => {
|
|
||||||
return (
|
|
||||||
<CellComponent
|
|
||||||
{...cellProps}
|
|
||||||
columnIndex={cellProps.columnIndex + pinnedLeftColumnCount + totalColumnCount}
|
|
||||||
rowIndex={cellProps.rowIndex + pinnedRowCount}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
},
|
|
||||||
[pinnedLeftColumnCount, pinnedRowCount, totalColumnCount, CellComponent],
|
|
||||||
);
|
|
||||||
|
|
||||||
const PinnedRightIntersectionCell = useCallback(
|
|
||||||
(cellProps: CellComponentProps & TableItemProps) => {
|
|
||||||
return (
|
|
||||||
<CellComponent
|
|
||||||
{...cellProps}
|
|
||||||
columnIndex={cellProps.columnIndex + pinnedLeftColumnCount + totalColumnCount}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
},
|
|
||||||
[pinnedLeftColumnCount, totalColumnCount, CellComponent],
|
|
||||||
);
|
|
||||||
|
|
||||||
const RowCell = useCallback(
|
|
||||||
(cellProps: CellComponentProps<TableItemProps>) => {
|
|
||||||
return (
|
|
||||||
<CellComponent
|
|
||||||
{...cellProps}
|
|
||||||
columnIndex={cellProps.columnIndex + pinnedLeftColumnCount}
|
|
||||||
rowIndex={cellProps.rowIndex + pinnedRowCount}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
},
|
|
||||||
[pinnedLeftColumnCount, pinnedRowCount, CellComponent],
|
|
||||||
);
|
|
||||||
|
|
||||||
const itemProps: TableItemProps = {
|
|
||||||
cellPadding,
|
|
||||||
columns: parsedColumns,
|
|
||||||
data: enableHeader ? [null, ...data] : data,
|
|
||||||
enableAlternateRowColors,
|
|
||||||
enableExpansion,
|
|
||||||
enableHeader,
|
|
||||||
enableHorizontalBorders,
|
|
||||||
enableRowHoverHighlight,
|
|
||||||
enableSelection,
|
|
||||||
enableVerticalBorders,
|
|
||||||
getRowHeight,
|
|
||||||
internalState,
|
|
||||||
itemType,
|
|
||||||
size,
|
|
||||||
};
|
|
||||||
|
|
||||||
const isInitialScrollPositionSet = useRef<boolean>(false);
|
const isInitialScrollPositionSet = useRef<boolean>(false);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -718,186 +989,37 @@ export const ItemTableList = ({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={styles.itemTableListContainer}>
|
<div className={styles.itemTableListContainer}>
|
||||||
<div className={styles.itemTableContainer}>
|
<VirtualizedTableGrid
|
||||||
<div
|
calculatedColumnWidths={calculatedColumnWidths}
|
||||||
className={styles.itemTablePinnedColumnsGridContainer}
|
CellComponent={CellComponent}
|
||||||
style={{
|
cellPadding={cellPadding}
|
||||||
minWidth: `${Array.from({ length: pinnedLeftColumnCount }, () => 0).reduce(
|
data={data}
|
||||||
(a, _, i) => a + columnWidth(i, itemProps),
|
enableAlternateRowColors={enableAlternateRowColors}
|
||||||
0,
|
enableExpansion={enableExpansion}
|
||||||
)}px`,
|
enableHeader={enableHeader}
|
||||||
}}
|
enableHorizontalBorders={enableHorizontalBorders}
|
||||||
>
|
enableRowHoverHighlight={enableRowHoverHighlight}
|
||||||
{!!(pinnedLeftColumnCount || pinnedRowCount) && (
|
enableSelection={enableSelection}
|
||||||
<div
|
enableVerticalBorders={enableVerticalBorders}
|
||||||
className={clsx(styles.itemTablePinnedIntersectionGridContainer, {
|
getRowHeight={getRowHeight}
|
||||||
[styles.withHeader]: enableHeader,
|
headerHeight={headerHeight}
|
||||||
})}
|
internalState={internalState}
|
||||||
style={{
|
itemType={itemType}
|
||||||
minHeight: `${Array.from(
|
mergedRowRef={mergedRowRef}
|
||||||
{ length: pinnedRowCount },
|
onCellsRendered={handleOnCellsRendered}
|
||||||
() => 0,
|
parsedColumns={parsedColumns}
|
||||||
).reduce((a, _, i) => a + getRowHeight(i, itemProps), 0)}px`,
|
pinnedLeftColumnCount={pinnedLeftColumnCount}
|
||||||
}}
|
pinnedLeftColumnRef={pinnedLeftColumnRef}
|
||||||
>
|
pinnedRightColumnCount={pinnedRightColumnCount}
|
||||||
<Grid
|
pinnedRightColumnRef={pinnedRightColumnRef}
|
||||||
cellComponent={CellComponent as any}
|
pinnedRowCount={pinnedRowCount}
|
||||||
cellProps={itemProps}
|
pinnedRowRef={pinnedRowRef}
|
||||||
className={styles.noScrollbar}
|
showLeftShadow={showLeftShadow}
|
||||||
columnCount={pinnedLeftColumnCount}
|
showRightShadow={showRightShadow}
|
||||||
columnWidth={columnWidth}
|
size={size}
|
||||||
rowCount={pinnedRowCount}
|
totalColumnCount={totalColumnCount}
|
||||||
rowHeight={getRowHeight}
|
totalRowCount={totalRowCount}
|
||||||
/>
|
/>
|
||||||
{enableHeader && <div className={styles.itemTablePinnedHeaderShadow} />}
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
{!!pinnedLeftColumnCount && (
|
|
||||||
<div
|
|
||||||
className={styles.itemTablePinnedColumnsContainer}
|
|
||||||
ref={pinnedLeftColumnRef}
|
|
||||||
>
|
|
||||||
<Grid
|
|
||||||
cellComponent={PinnedColumnCell}
|
|
||||||
cellProps={itemProps}
|
|
||||||
className={clsx(styles.noScrollbar, styles.height100)}
|
|
||||||
columnCount={pinnedLeftColumnCount}
|
|
||||||
columnWidth={columnWidth}
|
|
||||||
rowCount={totalRowCount}
|
|
||||||
rowHeight={(index, cellProps) => {
|
|
||||||
return getRowHeight(index + pinnedRowCount, cellProps);
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
<div className={styles.itemTablePinnedRowsContainer}>
|
|
||||||
{!!pinnedRowCount && (
|
|
||||||
<div
|
|
||||||
className={clsx(styles.itemTablePinnedRowsGridContainer, {
|
|
||||||
[styles.withHeader]: enableHeader,
|
|
||||||
})}
|
|
||||||
ref={pinnedRowRef}
|
|
||||||
style={
|
|
||||||
{
|
|
||||||
'--header-height': `${headerHeight}px`,
|
|
||||||
minHeight: `${Array.from(
|
|
||||||
{ length: pinnedRowCount },
|
|
||||||
() => 0,
|
|
||||||
).reduce((a, _, i) => a + getRowHeight(i, itemProps), 0)}px`,
|
|
||||||
} as React.CSSProperties
|
|
||||||
}
|
|
||||||
>
|
|
||||||
<Grid
|
|
||||||
cellComponent={PinnedRowCell}
|
|
||||||
cellProps={itemProps}
|
|
||||||
className={styles.noScrollbar}
|
|
||||||
columnCount={totalColumnCount}
|
|
||||||
columnWidth={(index, cellProps) => {
|
|
||||||
return columnWidth(index + pinnedLeftColumnCount, cellProps);
|
|
||||||
}}
|
|
||||||
rowCount={Array.from({ length: pinnedRowCount }, () => 0).length}
|
|
||||||
rowHeight={getRowHeight}
|
|
||||||
/>
|
|
||||||
{enableHeader && <div className={styles.itemTablePinnedHeaderShadow} />}
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
<div className={styles.itemTableGridContainer} ref={mergedRowRef}>
|
|
||||||
<Grid
|
|
||||||
cellComponent={RowCell}
|
|
||||||
cellProps={itemProps}
|
|
||||||
className={styles.height100}
|
|
||||||
columnCount={totalColumnCount}
|
|
||||||
columnWidth={(index, cellProps) => {
|
|
||||||
return columnWidth(index + pinnedLeftColumnCount, cellProps);
|
|
||||||
}}
|
|
||||||
onCellsRendered={handleOnCellsRendered}
|
|
||||||
rowCount={totalRowCount}
|
|
||||||
rowHeight={(index, cellProps) => {
|
|
||||||
return getRowHeight(index + pinnedRowCount, cellProps);
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
{pinnedLeftColumnCount > 0 && showLeftShadow && (
|
|
||||||
<div className={styles.itemTableLeftScrollShadow} />
|
|
||||||
)}
|
|
||||||
{pinnedRightColumnCount > 0 && showRightShadow && (
|
|
||||||
<div className={styles.itemTableRightScrollShadow} />
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{!!pinnedRightColumnCount && (
|
|
||||||
<div
|
|
||||||
className={styles.itemTablePinnedColumnsGridContainer}
|
|
||||||
style={{
|
|
||||||
minWidth: `${Array.from(
|
|
||||||
{ length: pinnedRightColumnCount },
|
|
||||||
() => 0,
|
|
||||||
).reduce(
|
|
||||||
(a, _, i) =>
|
|
||||||
a +
|
|
||||||
columnWidth(
|
|
||||||
i + pinnedLeftColumnCount + totalColumnCount,
|
|
||||||
itemProps,
|
|
||||||
),
|
|
||||||
0,
|
|
||||||
)}px`,
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{!!(pinnedRightColumnCount || pinnedRowCount) && (
|
|
||||||
<div
|
|
||||||
className={clsx(styles.itemTablePinnedIntersectionGridContainer, {
|
|
||||||
[styles.withHeader]: enableHeader,
|
|
||||||
})}
|
|
||||||
style={{
|
|
||||||
minHeight: `${Array.from(
|
|
||||||
{ length: pinnedRowCount },
|
|
||||||
() => 0,
|
|
||||||
).reduce((a, _, i) => a + getRowHeight(i, itemProps), 0)}px`,
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Grid
|
|
||||||
cellComponent={PinnedRightIntersectionCell}
|
|
||||||
cellProps={itemProps}
|
|
||||||
className={styles.noScrollbar}
|
|
||||||
columnCount={pinnedRightColumnCount}
|
|
||||||
columnWidth={(index, cellProps) => {
|
|
||||||
return columnWidth(
|
|
||||||
index + pinnedLeftColumnCount + totalColumnCount,
|
|
||||||
cellProps,
|
|
||||||
);
|
|
||||||
}}
|
|
||||||
rowCount={pinnedRowCount}
|
|
||||||
rowHeight={getRowHeight}
|
|
||||||
/>
|
|
||||||
{enableHeader && (
|
|
||||||
<div className={styles.itemTablePinnedHeaderShadow} />
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
<div
|
|
||||||
className={styles.itemTablePinnedRightColumnsContainer}
|
|
||||||
ref={pinnedRightColumnRef}
|
|
||||||
>
|
|
||||||
<Grid
|
|
||||||
cellComponent={PinnedRightColumnCell}
|
|
||||||
cellProps={itemProps}
|
|
||||||
className={clsx(styles.noScrollbar, styles.height100)}
|
|
||||||
columnCount={pinnedRightColumnCount}
|
|
||||||
columnWidth={(index, cellProps) => {
|
|
||||||
return columnWidth(
|
|
||||||
index + pinnedLeftColumnCount + totalColumnCount,
|
|
||||||
cellProps,
|
|
||||||
);
|
|
||||||
}}
|
|
||||||
rowCount={totalRowCount}
|
|
||||||
rowHeight={(index, cellProps) => {
|
|
||||||
return getRowHeight(index + pinnedRowCount, cellProps);
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
<AnimatePresence initial={false}>
|
<AnimatePresence initial={false}>
|
||||||
{hasExpanded && (
|
{hasExpanded && (
|
||||||
<ExpandedListContainer>
|
<ExpandedListContainer>
|
||||||
|
|||||||
Reference in New Issue
Block a user