diff --git a/src/renderer/components/item-list/item-table-list/item-table-list.module.css b/src/renderer/components/item-list/item-table-list/item-table-list.module.css index a62cc5170..885cba58d 100644 --- a/src/renderer/components/item-list/item-table-list/item-table-list.module.css +++ b/src/renderer/components/item-list/item-table-list/item-table-list.module.css @@ -1,3 +1,11 @@ +.item-table-list-container { + display: flex; + flex-direction: column; + height: 100%; + min-height: 0; + overflow: hidden; +} + .item-table-container { display: flex; flex-direction: row; @@ -8,6 +16,7 @@ } .item-table-grid-container { + position: relative; flex: 1 1 auto; width: 100%; height: 100%; @@ -23,6 +32,7 @@ } .item-table-sticky-rows-grid-container { + position: relative; flex: 0 1 auto; min-width: 0; } @@ -35,6 +45,7 @@ } .item-table-sticky-intersection-grid-container { + position: relative; flex: 0 1 auto; min-width: 0; } @@ -53,3 +64,39 @@ .height-100 { height: 100%; } + +.item-table-sticky-header-shadow { + position: absolute; + top: 100%; + right: 0; + left: 0; + z-index: 1; + height: 8px; + pointer-events: none; + background: linear-gradient( + to bottom, + rgb(0 0 0 / 50%) 0%, + rgb(0 0 0 / 5%) 50%, + transparent 100% + ); +} + +.item-table-left-scroll-shadow { + position: absolute; + top: 0; + bottom: 0; + left: 0; + z-index: 1; + width: 8px; + pointer-events: none; + background: linear-gradient( + to right, + rgb(0 0 0 / 50%) 0%, + rgb(0 0 0 / 5%) 50%, + transparent 100% + ); +} + +.list-expanded-container { + height: 500px; +} diff --git a/src/renderer/components/item-list/item-table-list/item-table-list.tsx b/src/renderer/components/item-list/item-table-list/item-table-list.tsx index 38fdbe583..c1077cefe 100644 --- a/src/renderer/components/item-list/item-table-list/item-table-list.tsx +++ b/src/renderer/components/item-list/item-table-list/item-table-list.tsx @@ -12,6 +12,7 @@ import { useCallback, useEffect, useRef, + useState, } from 'react'; import { type CellComponentProps, Grid, GridImperativeAPI, type GridProps } from 'react-window-v2'; @@ -21,17 +22,20 @@ import { ExpandedListItem } from '/@/renderer/components/item-list/expanded-list import { useItemListState } from '/@/renderer/components/item-list/helpers/item-list-state'; import { LibraryItem } from '/@/shared/types/domain-types'; +export interface CellProps { + columns: ItemTableListColumn[]; + data: unknown[]; + handleExpand: (e: MouseEvent, item: unknown, itemType: LibraryItem) => void; + itemType: LibraryItem; + size?: 'compact' | 'default'; +} + export interface ItemTableListColumn { id: string; label: string; width: number; } -interface CellProps { - columns: ItemTableListColumn[]; - data: unknown[]; -} - interface ItemTableListProps { CellComponent: JSXElementConstructor>; columnCount: number; @@ -39,7 +43,9 @@ interface ItemTableListProps { columnWidth: ((index: number, cellProps: CellProps) => number) | number; data: unknown[]; enableExpansion?: boolean; + enableHeader?: boolean; enableSelection?: boolean; + headerHeight?: number; initialTopMostItemIndex?: | number | { @@ -60,8 +66,8 @@ interface ItemTableListProps { onStartReached?: (index: number) => void; ref?: Ref; rowHeight: ((index: number, cellProps: CellProps) => number) | number; + size?: 'compact' | 'default'; stickyColumnCount: number; - stickyRowCount: number; totalItemCount: number; } @@ -85,6 +91,8 @@ export const ItemTableList = ({ columns, columnWidth, data, + enableHeader = true, + headerHeight = 40, initialTopMostItemIndex, itemType, onCellsRendered, @@ -98,10 +106,11 @@ export const ItemTableList = ({ onStartReached, ref, rowHeight, + size = 'default', stickyColumnCount, - stickyRowCount, totalItemCount, }: ItemTableListProps) => { + const stickyRowCount = enableHeader ? 1 : 0; const totalRowCount = totalItemCount - (stickyRowCount ?? 0); const totalColumnCount = columnCount - (stickyColumnCount ?? 0); const stickyRowRef = useRef(null); @@ -109,6 +118,7 @@ export const ItemTableList = ({ const stickyColumnRef = useRef(null); const scrollContainerRef = useRef(null); const mergedRowRef = useMergedRef(rowRef, scrollContainerRef); + const [showLeftShadow, setShowLeftShadow] = useState(false); const [initialize] = useOverlayScrollbars({ defer: true, @@ -321,10 +331,43 @@ export const ItemTableList = ({ return undefined; }, []); - const cellProps = { - columns, - data, - }; + // Handle left shadow visibility based on horizontal scroll + useEffect(() => { + const row = rowRef.current?.childNodes[0] as HTMLDivElement; + + if (!row || !stickyColumnCount) { + setShowLeftShadow(false); + return; + } + + const checkScrollPosition = () => { + const scrollLeft = row.scrollLeft; + setShowLeftShadow(scrollLeft > 0); + }; + + checkScrollPosition(); + + row.addEventListener('scroll', checkScrollPosition); + + return () => { + row.removeEventListener('scroll', checkScrollPosition); + }; + }, [stickyColumnCount]); + + const getRowHeight = useCallback( + (index: number, cellProps: CellProps) => { + const baseHeight = + typeof rowHeight === 'number' ? rowHeight : rowHeight(index, cellProps); + + // If enableHeader is true and this is the first sticky row, use fixed header height + if (enableHeader && index === 0 && stickyRowCount > 0) { + return headerHeight; + } + + return baseHeight; + }, + [enableHeader, headerHeight, rowHeight, stickyRowCount], + ); const internalState = useItemListState(); @@ -342,6 +385,7 @@ export const ItemTableList = ({ }, [internalState], ); + const handleOnCellsRendered = useCallback( (cells: { columnStartIndex: number; @@ -349,6 +393,11 @@ export const ItemTableList = ({ rowStartIndex: number; rowStopIndex: number; }) => { + onRangeChanged?.({ + endIndex: cells.rowStopIndex, + startIndex: cells.rowStartIndex, + }); + return onCellsRendered ? ({ columnStartIndex, columnStopIndex, rowStartIndex, rowStopIndex }) => { return onCellsRendered!( @@ -363,7 +412,7 @@ export const ItemTableList = ({ } : undefined; }, - [onCellsRendered, stickyColumnCount, stickyRowCount], + [onCellsRendered, onRangeChanged, stickyColumnCount, stickyRowCount], ); const StickyRowCell = useCallback( @@ -406,6 +455,14 @@ export const ItemTableList = ({ [stickyColumnCount, stickyRowCount, CellComponent], ); + const cellProps = { + columns, + data, + handleExpand, + itemType, + size, + }; + return ( -
0).reduce( - (a, _, i) => - a + - (typeof columnWidth === 'number' - ? columnWidth - : columnWidth(i, cellProps)), - 0, - )}px`, - }} - > - {!!(stickyColumnCount || stickyRowCount) && ( -
0, - ).reduce( - (a, _, i) => - a + - (typeof rowHeight === 'number' - ? rowHeight - : rowHeight(i, cellProps)), - 0, - )}px`, - }} - > - -
- )} - {!!stickyColumnCount && ( -
- { - return typeof rowHeight === 'number' - ? rowHeight - : rowHeight(index + (stickyRowCount ?? 0), cellProps); +
+
0).reduce( + (a, _, i) => + a + + (typeof columnWidth === 'number' + ? columnWidth + : columnWidth(i, cellProps)), + 0, + )}px`, + }} + > + {!!(stickyColumnCount || stickyRowCount) && ( +
0, + ).reduce((a, _, i) => a + getRowHeight(i, cellProps), 0)}px`, }} - /> -
- )} -
-
- {!!stickyRowCount && ( -
0, - ).reduce( - (a, _, i) => - a + - (typeof rowHeight === 'number' - ? rowHeight - : rowHeight(i, cellProps)), - 0, - )}px`, - }} - > + > + + {enableHeader &&
} +
+ )} + {!!stickyColumnCount && ( +
+ { + return getRowHeight(index + (stickyRowCount ?? 0), cellProps); + }} + /> +
+ )} +
+
+ {!!stickyRowCount && ( +
0, + ).reduce((a, _, i) => a + getRowHeight(i, cellProps), 0)}px`, + } as React.CSSProperties + } + > + { + return typeof columnWidth === 'number' + ? columnWidth + : columnWidth(index + (stickyColumnCount ?? 0), cellProps); + }} + rowCount={ + Array.from({ length: stickyRowCount ?? 0 }, () => 0).length + } + rowHeight={getRowHeight} + /> + {enableHeader &&
} +
+ )} +
{ return typeof columnWidth === 'number' ? columnWidth : columnWidth(index + (stickyColumnCount ?? 0), cellProps); }} - rowCount={Array.from({ length: stickyRowCount ?? 0 }, () => 0).length} + onCellsRendered={handleOnCellsRendered} + rowCount={totalRowCount} rowHeight={(index, cellProps) => { - return typeof rowHeight === 'number' - ? rowHeight - : rowHeight(index, cellProps); + return getRowHeight(index + (stickyRowCount ?? 0), cellProps); }} /> + {stickyColumnCount > 0 && showLeftShadow && ( +
+ )}
- )} -
- { - 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); - }} - />
- + {hasExpanded && (