move item table list

This commit is contained in:
jeffvli
2025-10-02 00:47:03 -07:00
parent 18f448c733
commit 74473427df
3 changed files with 557 additions and 411 deletions
@@ -1,411 +0,0 @@
import { useMergedRef } from '@mantine/hooks';
import { useOverlayScrollbars } from 'overlayscrollbars-react';
import { type JSXElementConstructor, useCallback, useEffect, useRef } from 'react';
import { type CellComponentProps, Grid, type GridProps } from 'react-window-v2';
export function VirtualizedTable<CellProps extends object>(props: {
cell: JSXElementConstructor<CellComponentProps<CellProps>>;
cellProps: GridProps<CellProps>['cellProps'];
columnCount: number;
columnWidth: ((index: number, cellProps: CellProps) => number) | number;
onCellsRendered: GridProps<CellProps>['onCellsRendered'];
overscanCount: number;
rowCount: number;
rowHeight: ((index: number, cellProps: CellProps) => number) | number;
stickyColumnCount: number;
stickyRowCount: number;
}) {
const rowCount = props.rowCount - (props.stickyRowCount ?? 0);
const columnCount = props.columnCount - (props.stickyColumnCount ?? 0);
const stickyRowRef = useRef<HTMLDivElement>(null);
const rowRef = useRef<HTMLDivElement>(null);
const stickyColumnRef = useRef<HTMLDivElement>(null);
const scrollContainerRef = useRef<HTMLDivElement | null>(null);
const mergedRowRef = useMergedRef(rowRef, scrollContainerRef);
const [initialize] = useOverlayScrollbars({
defer: true,
events: {
initialized(osInstance) {
const { viewport } = osInstance.elements();
viewport.style.overflowX = `var(--os-viewport-overflow-x)`;
viewport.style.overflowY = `var(--os-viewport-overflow-y)`;
},
},
options: {
overflow: { x: 'scroll', y: 'scroll' },
paddingAbsolute: true,
scrollbars: {
autoHide: 'leave',
autoHideDelay: 500,
pointers: ['mouse', 'pen', 'touch'],
theme: 'feishin-os-scrollbar',
visibility: 'visible',
},
},
});
useEffect(() => {
const { current: root } = scrollContainerRef;
if (root) {
initialize({
elements: { viewport: root.firstElementChild as HTMLElement },
target: root,
});
}
return undefined;
}, [initialize]);
useEffect(() => {
const header = stickyRowRef.current?.childNodes[0] as HTMLDivElement;
const row = rowRef.current?.childNodes[0] as HTMLDivElement;
const sticky = stickyColumnRef.current?.childNodes[0] as HTMLDivElement;
if (header && row && sticky) {
// Ensure all containers have the same height
const syncHeights = () => {
const rowHeight = row.scrollHeight;
const stickyHeight = sticky.scrollHeight;
// Set consistent heights - use the larger of the two
const targetHeight = Math.max(rowHeight, stickyHeight);
if (sticky.style.height !== `${targetHeight}px`) {
sticky.style.height = `${targetHeight}px`;
}
if (row.style.height !== `${targetHeight}px`) {
row.style.height = `${targetHeight}px`;
}
};
const activeElement = { element: null } as { element: HTMLDivElement | null };
const setActiveElement = (e: HTMLElementEventMap['pointermove']) => {
activeElement.element = e.currentTarget as HTMLDivElement;
};
const syncScroll = (e: HTMLElementEventMap['scroll']) => {
if (e.currentTarget !== activeElement.element) {
return;
}
const scrollTop = (e.currentTarget as HTMLDivElement).scrollTop;
const scrollLeft = (e.currentTarget as HTMLDivElement).scrollLeft;
// Prevent recursive scroll events
const isScrolling = { header: false, row: false, sticky: false };
// Sync horizontal scroll between header and main content
if (e.currentTarget === header && !isScrolling.row) {
isScrolling.row = true;
row.scrollTo({
behavior: 'instant',
left: scrollLeft,
});
setTimeout(() => {
isScrolling.row = false;
}, 0);
}
if (e.currentTarget === row && !isScrolling.header && !isScrolling.sticky) {
isScrolling.header = true;
isScrolling.sticky = true;
header.scrollTo({
behavior: 'instant',
left: scrollLeft,
});
sticky.scrollTo({
behavior: 'instant',
top: scrollTop,
});
setTimeout(() => {
isScrolling.header = false;
isScrolling.sticky = false;
}, 0);
}
// Sync vertical scroll between sticky column and main content
if (e.currentTarget === sticky && !isScrolling.row) {
isScrolling.row = true;
row.scrollTo({
behavior: 'instant',
top: scrollTop,
});
setTimeout(() => {
isScrolling.row = false;
}, 0);
}
};
// Add event listeners
header.addEventListener('pointermove', setActiveElement);
row.addEventListener('pointermove', setActiveElement);
sticky.addEventListener('pointermove', setActiveElement);
header.addEventListener('scroll', syncScroll);
row.addEventListener('scroll', syncScroll);
sticky.addEventListener('scroll', syncScroll);
// Add resize observer to maintain height sync
const resizeObserver = new ResizeObserver(() => {
syncHeights();
});
resizeObserver.observe(row);
resizeObserver.observe(sticky);
return () => {
header.removeEventListener('pointermove', setActiveElement);
row.removeEventListener('pointermove', setActiveElement);
sticky.removeEventListener('pointermove', setActiveElement);
header.removeEventListener('scroll', syncScroll);
row.removeEventListener('scroll', syncScroll);
sticky.removeEventListener('scroll', syncScroll);
resizeObserver.disconnect();
};
}
return undefined;
}, []);
const StickyRowCell = useCallback(
(cellProps: CellComponentProps & CellProps) => {
return (
<props.cell
{...cellProps}
columnIndex={cellProps.columnIndex + (props.stickyColumnCount ?? 0)}
/>
);
},
[props.cell, props.stickyColumnCount],
);
const StickyColumnCell = useCallback(
(cellProps: CellComponentProps & CellProps) => {
return (
<props.cell
{...cellProps}
rowIndex={cellProps.rowIndex + (props.stickyRowCount ?? 0)}
/>
);
},
[props.cell, props.stickyRowCount],
);
const RowCell = useCallback(
(cellProps: CellComponentProps & CellProps) => {
return (
<props.cell
{...cellProps}
columnIndex={cellProps.columnIndex + (props.stickyColumnCount ?? 0)}
rowIndex={cellProps.rowIndex + (props.stickyRowCount ?? 0)}
/>
);
},
[props.cell, props.stickyColumnCount, props.stickyRowCount],
);
const minHeight = 0;
const minWidth = 0;
return (
<div
style={{
display: 'flex',
flexDirection: 'row',
height: '100%',
minHeight,
minWidth,
width: '100%',
}}
>
<div
style={{
display: 'flex',
flex: '0 1 auto',
flexDirection: 'column',
minHeight,
minWidth: `${Array.from(
{ length: props.stickyColumnCount ?? 0 },
() => 0,
).reduce(
(a, _, i) =>
a +
(typeof props.columnWidth === 'number'
? props.columnWidth
: props.columnWidth(i, props.cellProps)),
0,
)}px`,
}}
>
{(props.stickyColumnCount || props.stickyRowCount) && (
<div
style={{
flex: '0 1 auto',
minHeight: `${Array.from(
{ length: props.stickyRowCount ?? 0 },
() => 0,
).reduce(
(a, _, i) =>
a +
(typeof props.rowHeight === 'number'
? props.rowHeight
: props.rowHeight(i, props.cellProps)),
0,
)}px`,
minWidth,
}}
>
<Grid
cellComponent={props.cell as any}
cellProps={props.cellProps}
columnCount={props.stickyColumnCount}
columnWidth={props.columnWidth}
overscanCount={props.overscanCount}
rowCount={props.stickyRowCount}
rowHeight={props.rowHeight}
style={{
scrollbarWidth: 'none',
}}
/>
</div>
)}
{props.stickyColumnCount && (
<div
ref={stickyColumnRef}
style={{
flex: '1 1 auto',
height: '100%',
minHeight,
minWidth,
}}
>
<Grid
cellComponent={StickyColumnCell}
cellProps={props.cellProps}
columnCount={props.stickyColumnCount}
columnWidth={props.columnWidth}
overscanCount={props.overscanCount}
rowCount={rowCount}
rowHeight={(index, cellProps) => {
return typeof props.rowHeight === 'number'
? props.rowHeight
: props.rowHeight(
index + (props.stickyRowCount ?? 0),
cellProps,
);
}}
style={{
height: '100%',
scrollbarWidth: 'none',
}}
/>
</div>
)}
</div>
<div
style={{
display: 'flex',
flex: '1 1 auto',
flexDirection: 'column',
minHeight,
minWidth,
}}
>
{props.stickyRowCount && (
<div
ref={stickyRowRef}
style={{
flex: '0 1 auto',
minHeight: `${Array.from(
{ length: props.stickyRowCount ?? 0 },
() => 0,
).reduce(
(a, _, i) =>
a +
(typeof props.rowHeight === 'number'
? props.rowHeight
: props.rowHeight(i, props.cellProps)),
0,
)}px`,
minWidth,
}}
>
<Grid
cellComponent={StickyRowCell}
cellProps={props.cellProps}
columnCount={columnCount}
columnWidth={(index, cellProps) => {
return typeof props.columnWidth === 'number'
? props.columnWidth
: props.columnWidth(
index + (props.stickyColumnCount ?? 0),
cellProps,
);
}}
overscanCount={props.overscanCount}
rowCount={
Array.from({ length: props.stickyRowCount ?? 0 }, () => 0).length
}
rowHeight={(index, cellProps) => {
return typeof props.rowHeight === 'number'
? props.rowHeight
: props.rowHeight(index, cellProps);
}}
style={{
scrollbarWidth: 'none',
}}
/>
</div>
)}
<div
ref={mergedRowRef}
style={{ flex: '1 1 auto', height: '100%', minHeight, minWidth }}
>
<Grid
cellComponent={RowCell}
cellProps={props.cellProps}
columnCount={columnCount}
columnWidth={(index, cellProps) => {
return typeof props.columnWidth === 'number'
? props.columnWidth
: props.columnWidth(
index + (props.stickyColumnCount ?? 0),
cellProps,
);
}}
onCellsRendered={
props.onCellsRendered
? ({
columnStartIndex,
columnStopIndex,
rowStartIndex,
rowStopIndex,
}) => {
return props.onCellsRendered!({
columnStartIndex:
columnStartIndex + (props.stickyColumnCount ?? 0),
columnStopIndex:
columnStopIndex + (props.stickyColumnCount ?? 0),
rowStartIndex:
rowStartIndex + (props.stickyRowCount ?? 0),
rowStopIndex: rowStopIndex + (props.stickyRowCount ?? 0),
});
}
: undefined
}
overscanCount={props.overscanCount}
rowCount={rowCount}
rowHeight={(index, cellProps) => {
return typeof props.rowHeight === 'number'
? props.rowHeight
: props.rowHeight(index + (props.stickyRowCount ?? 0), cellProps);
}}
style={{
height: '100%',
}}
/>
</div>
</div>
</div>
);
}
@@ -0,0 +1,55 @@
.item-table-container {
display: flex;
flex-direction: row;
width: 100%;
min-width: 0;
height: 100%;
min-height: 0;
}
.item-table-grid-container {
flex: 1 1 auto;
width: 100%;
height: 100%;
min-height: 0;
}
.item-table-sticky-rows-container {
display: flex;
flex: 1 1 auto;
flex-direction: column;
min-width: 0;
min-height: 0;
}
.item-table-sticky-rows-grid-container {
flex: 0 1 auto;
min-width: 0;
}
.item-table-sticky-columns-grid-container {
display: flex;
flex: 0 1 auto;
flex-direction: column;
min-height: 0;
}
.item-table-sticky-intersection-grid-container {
flex: 0 1 auto;
min-width: 0;
}
.item-table-sticky-columns-container {
flex: 1 1 auto;
min-width: 0;
height: 100%;
min-height: 0;
}
.no-scrollbar {
scrollbar-width: none;
}
.height-100 {
height: 100%;
}
@@ -0,0 +1,502 @@
// Component adapted from https://github.com/bvaughn/react-window/issues/826
import { useMergedRef } from '@mantine/hooks';
import clsx from 'clsx';
import { AnimatePresence, motion, Variants } from 'motion/react';
import { useOverlayScrollbars } from 'overlayscrollbars-react';
import {
type JSXElementConstructor,
MouseEvent,
Ref,
UIEvent,
useCallback,
useEffect,
useRef,
} from 'react';
import { type CellComponentProps, Grid, GridImperativeAPI, type GridProps } from 'react-window-v2';
import styles from './item-table-list.module.css';
import { ExpandedListItem } from '/@/renderer/components/item-list/expanded-list-item';
import { useItemListState } from '/@/renderer/components/item-list/helpers/item-list-state';
import { LibraryItem } from '/@/shared/types/domain-types';
interface ItemTableListProps<CellProps extends object> {
CellComponent: JSXElementConstructor<CellComponentProps<CellProps>>;
cellProps: GridProps<CellProps>['cellProps'];
columnCount: number;
columnWidth: ((index: number, cellProps: CellProps) => number) | number;
data: unknown[];
enableExpansion?: boolean;
enableSelection?: boolean;
initialTopMostItemIndex?:
| number
| {
align: 'center' | 'end' | 'start';
behavior: 'auto' | 'smooth';
index: number;
offset?: number;
};
itemType: LibraryItem;
onCellsRendered: GridProps<CellProps>['onCellsRendered'];
onEndReached?: (index: number) => void;
onItemClick?: (item: unknown, index: number) => void;
onItemContextMenu?: (item: unknown, index: number) => void;
onItemDoubleClick?: (item: unknown, index: number) => void;
onRangeChanged?: (range: { endIndex: number; startIndex: number }) => void;
onScroll?: (e: UIEvent<HTMLDivElement>) => void;
onScrollEnd?: () => void;
onStartReached?: (index: number) => void;
ref?: Ref<GridImperativeAPI>;
rowHeight: ((index: number, cellProps: CellProps) => number) | number;
stickyColumnCount: number;
stickyRowCount: number;
totalItemCount: number;
}
const expandedAnimationVariants: Variants = {
hidden: {
height: 0,
minHeight: 0,
},
show: {
minHeight: '300px',
transition: {
duration: 0.3,
ease: 'easeInOut',
},
},
};
export const ItemTableList = <CellProps extends object>({
CellComponent,
cellProps,
columnCount,
columnWidth,
initialTopMostItemIndex,
itemType,
onCellsRendered,
onEndReached,
onItemClick,
onItemContextMenu,
onItemDoubleClick,
onRangeChanged,
onScroll,
onScrollEnd,
onStartReached,
ref,
rowHeight,
stickyColumnCount,
stickyRowCount,
totalItemCount,
}: ItemTableListProps<CellProps>) => {
const totalRowCount = totalItemCount - (stickyRowCount ?? 0);
const totalColumnCount = columnCount - (stickyColumnCount ?? 0);
const stickyRowRef = useRef<HTMLDivElement>(null);
const rowRef = useRef<HTMLDivElement>(null);
const stickyColumnRef = useRef<HTMLDivElement>(null);
const scrollContainerRef = useRef<HTMLDivElement | null>(null);
const mergedRowRef = useMergedRef(rowRef, scrollContainerRef);
const [initialize] = useOverlayScrollbars({
defer: true,
events: {
initialized(osInstance) {
const { viewport } = osInstance.elements();
viewport.style.overflowX = `var(--os-viewport-overflow-x)`;
viewport.style.overflowY = `var(--os-viewport-overflow-y)`;
},
},
options: {
overflow: { x: 'scroll', y: 'scroll' },
paddingAbsolute: true,
scrollbars: {
autoHide: 'leave',
autoHideDelay: 500,
pointers: ['mouse', 'pen', 'touch'],
theme: 'feishin-os-scrollbar',
visibility: 'visible',
},
},
});
useEffect(() => {
const { current: root } = scrollContainerRef;
if (root) {
initialize({
elements: { viewport: root.firstElementChild as HTMLElement },
target: root,
});
}
return undefined;
}, [initialize]);
useEffect(() => {
const header = stickyRowRef.current?.childNodes[0] as HTMLDivElement;
const row = rowRef.current?.childNodes[0] as HTMLDivElement;
const sticky = stickyColumnRef.current?.childNodes[0] as HTMLDivElement;
// At minimum, we need the main row element
if (row) {
// Ensure all containers have the same height
const syncHeights = () => {
if (sticky) {
const rowHeight = row.scrollHeight;
const stickyHeight = sticky.scrollHeight;
// Set consistent heights - use the larger of the two
const targetHeight = Math.max(rowHeight, stickyHeight);
if (sticky.style.height !== `${targetHeight}px`) {
sticky.style.height = `${targetHeight}px`;
}
if (row.style.height !== `${targetHeight}px`) {
row.style.height = `${targetHeight}px`;
}
}
};
const timeoutId = setTimeout(syncHeights, 0);
const activeElement = { element: null } as { element: HTMLDivElement | null };
const setActiveElement = (e: HTMLElementEventMap['pointermove']) => {
activeElement.element = e.currentTarget as HTMLDivElement;
};
const setActiveElementFromWheel = (e: HTMLElementEventMap['wheel']) => {
activeElement.element = e.currentTarget as HTMLDivElement;
};
const syncScroll = (e: HTMLElementEventMap['scroll']) => {
if (e.currentTarget !== activeElement.element) {
return;
}
const scrollTop = (e.currentTarget as HTMLDivElement).scrollTop;
const scrollLeft = (e.currentTarget as HTMLDivElement).scrollLeft;
// Prevent recursive scroll events
const isScrolling = { header: false, row: false, sticky: false };
// Sync horizontal scroll between header and main content (only if header exists)
if (header && e.currentTarget === header && !isScrolling.row) {
isScrolling.row = true;
row.scrollTo({
behavior: 'instant',
left: scrollLeft,
});
setTimeout(() => {
isScrolling.row = false;
}, 0);
}
// Sync from main content to header and sticky column
if (e.currentTarget === row && !isScrolling.header && !isScrolling.sticky) {
if (header) {
isScrolling.header = true;
header.scrollTo({
behavior: 'instant',
left: scrollLeft,
});
}
if (sticky) {
isScrolling.sticky = true;
sticky.scrollTo({
behavior: 'instant',
top: scrollTop,
});
}
setTimeout(() => {
isScrolling.header = false;
isScrolling.sticky = false;
}, 0);
}
// Sync vertical scroll between sticky column and main content (only if sticky exists)
if (sticky && e.currentTarget === sticky && !isScrolling.row) {
isScrolling.row = true;
row.scrollTo({
behavior: 'instant',
top: scrollTop,
});
setTimeout(() => {
isScrolling.row = false;
}, 0);
}
};
// Add event listeners for elements that exist
if (header) {
header.addEventListener('pointermove', setActiveElement);
header.addEventListener('wheel', setActiveElementFromWheel);
header.addEventListener('scroll', syncScroll);
}
row.addEventListener('pointermove', setActiveElement);
row.addEventListener('wheel', setActiveElementFromWheel);
row.addEventListener('scroll', syncScroll);
if (sticky) {
sticky.addEventListener('pointermove', setActiveElement);
sticky.addEventListener('wheel', setActiveElementFromWheel);
sticky.addEventListener('scroll', syncScroll);
}
// Add resize observer to maintain height sync
const resizeObserver = new ResizeObserver(() => {
syncHeights();
});
resizeObserver.observe(row);
if (sticky) {
resizeObserver.observe(sticky);
}
return () => {
clearTimeout(timeoutId);
if (header) {
header.removeEventListener('pointermove', setActiveElement);
header.removeEventListener('wheel', setActiveElementFromWheel);
header.removeEventListener('scroll', syncScroll);
}
row.removeEventListener('pointermove', setActiveElement);
row.removeEventListener('wheel', setActiveElementFromWheel);
row.removeEventListener('scroll', syncScroll);
if (sticky) {
sticky.removeEventListener('pointermove', setActiveElement);
sticky.removeEventListener('wheel', setActiveElementFromWheel);
sticky.removeEventListener('scroll', syncScroll);
}
resizeObserver.disconnect();
};
}
return undefined;
}, []);
const internalState = useItemListState();
const hasExpanded = internalState.hasExpanded();
const handleExpand = useCallback(
(_e: MouseEvent<HTMLDivElement>, item: unknown, itemType: LibraryItem) => {
if (item && typeof item === 'object' && 'id' in item && 'serverId' in item) {
internalState.toggleExpanded({
id: item.id as string,
itemType: itemType,
serverId: item.serverId as string,
});
}
},
[internalState],
);
const handleOnCellsRendered = useCallback(
(cells: {
columnStartIndex: number;
columnStopIndex: number;
rowStartIndex: number;
rowStopIndex: number;
}) => {
return onCellsRendered
? ({ columnStartIndex, columnStopIndex, rowStartIndex, rowStopIndex }) => {
return onCellsRendered!(
{
columnStartIndex: columnStartIndex + (stickyColumnCount ?? 0),
columnStopIndex: columnStopIndex + (stickyColumnCount ?? 0),
rowStartIndex: rowStartIndex + (stickyRowCount ?? 0),
rowStopIndex: rowStopIndex + (stickyRowCount ?? 0),
},
cells,
);
}
: undefined;
},
[onCellsRendered, stickyColumnCount, stickyRowCount],
);
const StickyRowCell = useCallback(
(cellProps: CellComponentProps & CellProps) => {
return (
<CellComponent
{...cellProps}
columnIndex={cellProps.columnIndex + (stickyColumnCount ?? 0)}
/>
);
},
[stickyColumnCount, CellComponent],
);
const StickyColumnCell = useCallback(
(cellProps: CellComponentProps & CellProps) => {
return (
<CellComponent
{...cellProps}
rowIndex={cellProps.rowIndex + (stickyRowCount ?? 0)}
/>
);
},
[stickyRowCount, CellComponent],
);
const RowCell = useCallback(
(cellProps: CellComponentProps & CellProps) => {
return (
<CellComponent
{...cellProps}
columnIndex={cellProps.columnIndex + (stickyColumnCount ?? 0)}
onClick={() => {
console.log('click', cellProps.rowIndex);
}}
rowIndex={cellProps.rowIndex + (stickyRowCount ?? 0)}
/>
);
},
[stickyColumnCount, stickyRowCount, CellComponent],
);
return (
<motion.div
animate={{
height: '100%',
opacity: 1,
transition: {
duration: 1,
ease: 'backInOut',
},
}}
className={styles.itemTableContainer}
initial={{ opacity: 0 }}
>
<div
className={styles.itemTableStickyColumnsGridContainer}
style={{
minWidth: `${Array.from({ length: stickyColumnCount ?? 0 }, () => 0).reduce(
(a, _, i) =>
a +
(typeof columnWidth === 'number'
? columnWidth
: columnWidth(i, cellProps)),
0,
)}px`,
}}
>
{!!(stickyColumnCount || stickyRowCount) && (
<div
className={styles.itemTableStickyIntersectionGridContainer}
style={{
minHeight: `${Array.from(
{ length: stickyRowCount ?? 0 },
() => 0,
).reduce(
(a, _, i) =>
a +
(typeof rowHeight === 'number'
? rowHeight
: rowHeight(i, cellProps)),
0,
)}px`,
}}
>
<Grid
cellComponent={CellComponent as any}
cellProps={cellProps}
className={styles.noScrollbar}
columnCount={stickyColumnCount}
columnWidth={columnWidth}
rowCount={stickyRowCount}
rowHeight={rowHeight}
/>
</div>
)}
{!!stickyColumnCount && (
<div className={styles.itemTableStickyColumnsContainer} ref={stickyColumnRef}>
<Grid
cellComponent={StickyColumnCell}
cellProps={cellProps}
className={clsx(styles.noScrollbar, styles.height100)}
columnCount={stickyColumnCount}
columnWidth={columnWidth}
rowCount={totalRowCount}
rowHeight={(index, cellProps) => {
return typeof rowHeight === 'number'
? rowHeight
: rowHeight(index + (stickyRowCount ?? 0), cellProps);
}}
/>
</div>
)}
</div>
<div className={styles.itemTableStickyRowsContainer}>
{!!stickyRowCount && (
<div
className={styles.itemTableStickyRowsGridContainer}
ref={stickyRowRef}
style={{
minHeight: `${Array.from(
{ length: stickyRowCount ?? 0 },
() => 0,
).reduce(
(a, _, i) =>
a +
(typeof rowHeight === 'number'
? rowHeight
: rowHeight(i, cellProps)),
0,
)}px`,
}}
>
<Grid
cellComponent={StickyRowCell}
cellProps={cellProps}
className={styles.noScrollbar}
columnCount={totalColumnCount}
columnWidth={(index, cellProps) => {
return typeof columnWidth === 'number'
? columnWidth
: columnWidth(index + (stickyColumnCount ?? 0), cellProps);
}}
rowCount={Array.from({ length: stickyRowCount ?? 0 }, () => 0).length}
rowHeight={(index, cellProps) => {
return typeof rowHeight === 'number'
? rowHeight
: rowHeight(index, cellProps);
}}
/>
</div>
)}
<div className={styles.itemTableGridContainer} ref={mergedRowRef}>
<Grid
cellComponent={RowCell}
cellProps={cellProps}
className={styles.height100}
columnCount={totalColumnCount}
columnWidth={(index, cellProps) => {
return typeof columnWidth === 'number'
? columnWidth
: columnWidth(index + (stickyColumnCount ?? 0), cellProps);
}}
onCellsRendered={handleOnCellsRendered}
rowCount={totalRowCount}
rowHeight={(index, cellProps) => {
return typeof rowHeight === 'number'
? rowHeight
: rowHeight(index + (stickyRowCount ?? 0), cellProps);
}}
/>
</div>
</div>
<AnimatePresence>
{hasExpanded && (
<motion.div
animate="show"
className={styles.listExpandedContainer}
exit="hidden"
initial="hidden"
style={{ height: '500px' }}
variants={expandedAnimationVariants}
>
<ExpandedListItem internalState={internalState} itemType={itemType} />
</motion.div>
)}
</AnimatePresence>
</motion.div>
);
};