use derived column props, add table header styles

This commit is contained in:
jeffvli
2025-10-03 22:06:07 -07:00
parent 070aca44bf
commit 90efdd586c
2 changed files with 69 additions and 86 deletions
@@ -37,6 +37,10 @@
min-width: 0; min-width: 0;
} }
.item-table-pinned-rows-grid-container.with-header {
background-color: var(--theme-colors-surface);
}
.item-table-pinned-columns-grid-container { .item-table-pinned-columns-grid-container {
display: flex; display: flex;
flex: 0 1 auto; flex: 0 1 auto;
@@ -50,6 +54,10 @@
min-width: 0; min-width: 0;
} }
.item-table-pinned-intersection-grid-container.with-header {
background-color: var(--theme-colors-surface);
}
.item-table-pinned-columns-container { .item-table-pinned-columns-container {
flex: 1 1 auto; flex: 1 1 auto;
min-width: 0; min-width: 0;
@@ -21,26 +21,27 @@ import styles from './item-table-list.module.css';
import { ExpandedListItem } from '/@/renderer/components/item-list/expanded-list-item'; import { ExpandedListItem } from '/@/renderer/components/item-list/expanded-list-item';
import { useItemListState } from '/@/renderer/components/item-list/helpers/item-list-state'; import { useItemListState } from '/@/renderer/components/item-list/helpers/item-list-state';
import { LibraryItem } from '/@/shared/types/domain-types'; import { LibraryItem } from '/@/shared/types/domain-types';
import { TableColumn } from '/@/shared/types/types';
export interface CellProps { export interface CellProps {
columns: ItemTableListColumn[]; columns: ItemTableListColumnConfig[];
data: unknown[]; data: unknown[];
enableHeader?: boolean;
handleExpand: (e: MouseEvent<HTMLDivElement>, item: unknown, itemType: LibraryItem) => void; handleExpand: (e: MouseEvent<HTMLDivElement>, item: unknown, itemType: LibraryItem) => void;
itemType: LibraryItem; itemType: LibraryItem;
size?: 'compact' | 'default'; size?: 'compact' | 'default';
} }
export interface ItemTableListColumn { export interface ItemTableListColumnConfig {
id: string; align: 'center' | 'end' | 'start';
label: string; id: TableColumn;
pinned: 'left' | 'right' | null;
width: number; width: number;
} }
interface ItemTableListProps { interface ItemTableListProps {
CellComponent: JSXElementConstructor<CellComponentProps<CellProps>>; CellComponent: JSXElementConstructor<CellComponentProps<CellProps>>;
columnCount: number; columns: ItemTableListColumnConfig[];
columns: ItemTableListColumn[];
columnWidth: ((index: number, cellProps: CellProps) => number) | number;
data: unknown[]; data: unknown[];
enableExpansion?: boolean; enableExpansion?: boolean;
enableHeader?: boolean; enableHeader?: boolean;
@@ -64,8 +65,6 @@ interface ItemTableListProps {
onScroll?: (event: UIEvent<HTMLDivElement>) => void; onScroll?: (event: UIEvent<HTMLDivElement>) => void;
onScrollEnd?: () => void; onScrollEnd?: () => void;
onStartReached?: (index: number) => void; onStartReached?: (index: number) => void;
pinnedLeftColumnCount: number;
pinnedRightColumnCount?: number;
ref?: Ref<GridImperativeAPI>; ref?: Ref<GridImperativeAPI>;
rowHeight: ((index: number, cellProps: CellProps) => number) | number; rowHeight: ((index: number, cellProps: CellProps) => number) | number;
size?: 'compact' | 'default'; size?: 'compact' | 'default';
@@ -88,9 +87,7 @@ const expandedAnimationVariants: Variants = {
export const ItemTableList = ({ export const ItemTableList = ({
CellComponent, CellComponent,
columnCount,
columns, columns,
columnWidth,
data, data,
enableHeader = true, enableHeader = true,
headerHeight = 40, headerHeight = 40,
@@ -105,17 +102,20 @@ export const ItemTableList = ({
onScroll, onScroll,
onScrollEnd, onScrollEnd,
onStartReached, onStartReached,
pinnedLeftColumnCount,
pinnedRightColumnCount = 0,
ref, ref,
rowHeight, rowHeight,
size = 'default', size = 'default',
totalItemCount, totalItemCount,
}: ItemTableListProps) => { }: ItemTableListProps) => {
const columnCount = columns.length;
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const columnWidth = (index: number, _cellProps: CellProps) => columns[index].width;
const pinnedLeftColumnCount = columns.filter((col) => col.pinned === 'left').length;
const pinnedRightColumnCount = columns.filter((col) => col.pinned === 'right').length;
const pinnedRowCount = enableHeader ? 1 : 0; const pinnedRowCount = enableHeader ? 1 : 0;
const totalRowCount = totalItemCount - (pinnedRowCount ?? 0); const totalRowCount = totalItemCount - pinnedRowCount;
const totalColumnCount = const totalColumnCount = columnCount - pinnedLeftColumnCount - pinnedRightColumnCount;
columnCount - (pinnedLeftColumnCount ?? 0) - (pinnedRightColumnCount ?? 0);
const pinnedRowRef = useRef<HTMLDivElement>(null); const pinnedRowRef = useRef<HTMLDivElement>(null);
const rowRef = useRef<HTMLDivElement>(null); const rowRef = useRef<HTMLDivElement>(null);
const pinnedLeftColumnRef = useRef<HTMLDivElement>(null); const pinnedLeftColumnRef = useRef<HTMLDivElement>(null);
@@ -166,7 +166,6 @@ export const ItemTableList = ({
const pinnedLeft = pinnedLeftColumnRef.current?.childNodes[0] as HTMLDivElement; const pinnedLeft = pinnedLeftColumnRef.current?.childNodes[0] as HTMLDivElement;
const pinnedRight = pinnedRightColumnRef.current?.childNodes[0] as HTMLDivElement; const pinnedRight = pinnedRightColumnRef.current?.childNodes[0] as HTMLDivElement;
// At minimum, we need the main row element
if (row) { if (row) {
// Ensure all containers have the same height // Ensure all containers have the same height
const syncHeights = () => { const syncHeights = () => {
@@ -465,10 +464,10 @@ export const ItemTableList = ({
? ({ columnStartIndex, columnStopIndex, rowStartIndex, rowStopIndex }) => { ? ({ columnStartIndex, columnStopIndex, rowStartIndex, rowStopIndex }) => {
return onCellsRendered!( return onCellsRendered!(
{ {
columnStartIndex: columnStartIndex + (pinnedLeftColumnCount ?? 0), columnStartIndex: columnStartIndex + pinnedLeftColumnCount,
columnStopIndex: columnStopIndex + (pinnedLeftColumnCount ?? 0), columnStopIndex: columnStopIndex + pinnedLeftColumnCount,
rowStartIndex: rowStartIndex + (pinnedRowCount ?? 0), rowStartIndex: rowStartIndex + pinnedRowCount,
rowStopIndex: rowStopIndex + (pinnedRowCount ?? 0), rowStopIndex: rowStopIndex + pinnedRowCount,
}, },
cells, cells,
); );
@@ -483,7 +482,7 @@ export const ItemTableList = ({
return ( return (
<CellComponent <CellComponent
{...cellProps} {...cellProps}
columnIndex={cellProps.columnIndex + (pinnedLeftColumnCount ?? 0)} columnIndex={cellProps.columnIndex + pinnedLeftColumnCount}
/> />
); );
}, },
@@ -492,12 +491,7 @@ export const ItemTableList = ({
const PinnedColumnCell = useCallback( const PinnedColumnCell = useCallback(
(cellProps: CellComponentProps & CellProps) => { (cellProps: CellComponentProps & CellProps) => {
return ( return <CellComponent {...cellProps} rowIndex={cellProps.rowIndex + pinnedRowCount} />;
<CellComponent
{...cellProps}
rowIndex={cellProps.rowIndex + (pinnedRowCount ?? 0)}
/>
);
}, },
[pinnedRowCount, CellComponent], [pinnedRowCount, CellComponent],
); );
@@ -507,10 +501,8 @@ export const ItemTableList = ({
return ( return (
<CellComponent <CellComponent
{...cellProps} {...cellProps}
columnIndex={ columnIndex={cellProps.columnIndex + pinnedLeftColumnCount + totalColumnCount}
cellProps.columnIndex + (pinnedLeftColumnCount ?? 0) + totalColumnCount rowIndex={cellProps.rowIndex + pinnedRowCount}
}
rowIndex={cellProps.rowIndex + (pinnedRowCount ?? 0)}
/> />
); );
}, },
@@ -522,9 +514,7 @@ export const ItemTableList = ({
return ( return (
<CellComponent <CellComponent
{...cellProps} {...cellProps}
columnIndex={ columnIndex={cellProps.columnIndex + pinnedLeftColumnCount + totalColumnCount}
cellProps.columnIndex + (pinnedLeftColumnCount ?? 0) + totalColumnCount
}
/> />
); );
}, },
@@ -536,11 +526,11 @@ export const ItemTableList = ({
return ( return (
<CellComponent <CellComponent
{...cellProps} {...cellProps}
columnIndex={cellProps.columnIndex + (pinnedLeftColumnCount ?? 0)} columnIndex={cellProps.columnIndex + pinnedLeftColumnCount}
// onClick={(e) => { // onClick={(e) => {
// onItemClick?.(cellProps.data[cellProps.rowIndex], cellProps.rowIndex, e); // onItemClick?.(cellProps.data[cellProps.rowIndex], cellProps.rowIndex, e);
// }} // }}
rowIndex={cellProps.rowIndex + (pinnedRowCount ?? 0)} rowIndex={cellProps.rowIndex + pinnedRowCount}
/> />
); );
}, },
@@ -550,6 +540,7 @@ export const ItemTableList = ({
const cellProps = { const cellProps = {
columns, columns,
data, data,
enableHeader,
handleExpand, handleExpand,
itemType, itemType,
size, size,
@@ -572,25 +563,20 @@ export const ItemTableList = ({
<div <div
className={styles.itemTablePinnedColumnsGridContainer} className={styles.itemTablePinnedColumnsGridContainer}
style={{ style={{
minWidth: `${Array.from( minWidth: `${Array.from({ length: pinnedLeftColumnCount }, () => 0).reduce(
{ length: pinnedLeftColumnCount ?? 0 }, (a, _, i) => a + columnWidth(i, cellProps),
() => 0,
).reduce(
(a, _, i) =>
a +
(typeof columnWidth === 'number'
? columnWidth
: columnWidth(i, cellProps)),
0, 0,
)}px`, )}px`,
}} }}
> >
{!!(pinnedLeftColumnCount || pinnedRowCount) && ( {!!(pinnedLeftColumnCount || pinnedRowCount) && (
<div <div
className={styles.itemTablePinnedIntersectionGridContainer} className={clsx(styles.itemTablePinnedIntersectionGridContainer, {
[styles.withHeader]: enableHeader,
})}
style={{ style={{
minHeight: `${Array.from( minHeight: `${Array.from(
{ length: pinnedRowCount ?? 0 }, { length: pinnedRowCount },
() => 0, () => 0,
).reduce((a, _, i) => a + getRowHeight(i, cellProps), 0)}px`, ).reduce((a, _, i) => a + getRowHeight(i, cellProps), 0)}px`,
}} }}
@@ -620,7 +606,7 @@ export const ItemTableList = ({
columnWidth={columnWidth} columnWidth={columnWidth}
rowCount={totalRowCount} rowCount={totalRowCount}
rowHeight={(index, cellProps) => { rowHeight={(index, cellProps) => {
return getRowHeight(index + (pinnedRowCount ?? 0), cellProps); return getRowHeight(index + pinnedRowCount, cellProps);
}} }}
/> />
</div> </div>
@@ -629,13 +615,15 @@ export const ItemTableList = ({
<div className={styles.itemTablePinnedRowsContainer}> <div className={styles.itemTablePinnedRowsContainer}>
{!!pinnedRowCount && ( {!!pinnedRowCount && (
<div <div
className={styles.itemTablePinnedRowsGridContainer} className={clsx(styles.itemTablePinnedRowsGridContainer, {
[styles.withHeader]: enableHeader,
})}
ref={pinnedRowRef} ref={pinnedRowRef}
style={ style={
{ {
'--header-height': `${headerHeight}px`, '--header-height': `${headerHeight}px`,
minHeight: `${Array.from( minHeight: `${Array.from(
{ length: pinnedRowCount ?? 0 }, { length: pinnedRowCount },
() => 0, () => 0,
).reduce((a, _, i) => a + getRowHeight(i, cellProps), 0)}px`, ).reduce((a, _, i) => a + getRowHeight(i, cellProps), 0)}px`,
} as React.CSSProperties } as React.CSSProperties
@@ -647,16 +635,9 @@ export const ItemTableList = ({
className={styles.noScrollbar} className={styles.noScrollbar}
columnCount={totalColumnCount} columnCount={totalColumnCount}
columnWidth={(index, cellProps) => { columnWidth={(index, cellProps) => {
return typeof columnWidth === 'number' return columnWidth(index + pinnedLeftColumnCount, cellProps);
? columnWidth
: columnWidth(
index + (pinnedLeftColumnCount ?? 0),
cellProps,
);
}} }}
rowCount={ rowCount={Array.from({ length: pinnedRowCount }, () => 0).length}
Array.from({ length: pinnedRowCount ?? 0 }, () => 0).length
}
rowHeight={getRowHeight} rowHeight={getRowHeight}
/> />
{enableHeader && <div className={styles.itemTablePinnedHeaderShadow} />} {enableHeader && <div className={styles.itemTablePinnedHeaderShadow} />}
@@ -669,14 +650,12 @@ export const ItemTableList = ({
className={styles.height100} className={styles.height100}
columnCount={totalColumnCount} columnCount={totalColumnCount}
columnWidth={(index, cellProps) => { columnWidth={(index, cellProps) => {
return typeof columnWidth === 'number' return columnWidth(index + pinnedLeftColumnCount, cellProps);
? columnWidth
: columnWidth(index + (pinnedLeftColumnCount ?? 0), cellProps);
}} }}
onCellsRendered={handleOnCellsRendered} onCellsRendered={handleOnCellsRendered}
rowCount={totalRowCount} rowCount={totalRowCount}
rowHeight={(index, cellProps) => { rowHeight={(index, cellProps) => {
return getRowHeight(index + (pinnedRowCount ?? 0), cellProps); return getRowHeight(index + pinnedRowCount, cellProps);
}} }}
/> />
{pinnedLeftColumnCount > 0 && showLeftShadow && ( {pinnedLeftColumnCount > 0 && showLeftShadow && (
@@ -692,27 +671,27 @@ export const ItemTableList = ({
className={styles.itemTablePinnedColumnsGridContainer} className={styles.itemTablePinnedColumnsGridContainer}
style={{ style={{
minWidth: `${Array.from( minWidth: `${Array.from(
{ length: pinnedRightColumnCount ?? 0 }, { length: pinnedRightColumnCount },
() => 0, () => 0,
).reduce( ).reduce(
(a, _, i) => (a, _, i) =>
a + a +
(typeof columnWidth === 'number' columnWidth(
? columnWidth
: columnWidth(
i + pinnedLeftColumnCount + totalColumnCount, i + pinnedLeftColumnCount + totalColumnCount,
cellProps, cellProps,
)), ),
0, 0,
)}px`, )}px`,
}} }}
> >
{!!(pinnedRightColumnCount || pinnedRowCount) && ( {!!(pinnedRightColumnCount || pinnedRowCount) && (
<div <div
className={styles.itemTablePinnedIntersectionGridContainer} className={clsx(styles.itemTablePinnedIntersectionGridContainer, {
[styles.withHeader]: enableHeader,
})}
style={{ style={{
minHeight: `${Array.from( minHeight: `${Array.from(
{ length: pinnedRowCount ?? 0 }, { length: pinnedRowCount },
() => 0, () => 0,
).reduce((a, _, i) => a + getRowHeight(i, cellProps), 0)}px`, ).reduce((a, _, i) => a + getRowHeight(i, cellProps), 0)}px`,
}} }}
@@ -723,9 +702,7 @@ export const ItemTableList = ({
className={styles.noScrollbar} className={styles.noScrollbar}
columnCount={pinnedRightColumnCount} columnCount={pinnedRightColumnCount}
columnWidth={(index, cellProps) => { columnWidth={(index, cellProps) => {
return typeof columnWidth === 'number' return columnWidth(
? columnWidth
: columnWidth(
index + pinnedLeftColumnCount + totalColumnCount, index + pinnedLeftColumnCount + totalColumnCount,
cellProps, cellProps,
); );
@@ -748,16 +725,14 @@ export const ItemTableList = ({
className={clsx(styles.noScrollbar, styles.height100)} className={clsx(styles.noScrollbar, styles.height100)}
columnCount={pinnedRightColumnCount} columnCount={pinnedRightColumnCount}
columnWidth={(index, cellProps) => { columnWidth={(index, cellProps) => {
return typeof columnWidth === 'number' return columnWidth(
? columnWidth
: columnWidth(
index + pinnedLeftColumnCount + totalColumnCount, index + pinnedLeftColumnCount + totalColumnCount,
cellProps, cellProps,
); );
}} }}
rowCount={totalRowCount} rowCount={totalRowCount}
rowHeight={(index, cellProps) => { rowHeight={(index, cellProps) => {
return getRowHeight(index + (pinnedRowCount ?? 0), cellProps); return getRowHeight(index + pinnedRowCount, cellProps);
}} }}
/> />
</div> </div>