Add stickyHeader prop to table component

This commit is contained in:
jeffvli 2023-07-21 17:53:54 -07:00
parent 92d7560362
commit 9d18384b2d
2 changed files with 93 additions and 67 deletions

View file

@ -1,42 +1,54 @@
import { useEffect, useRef } from 'react';
import { useInView } from 'framer-motion'; import { useInView } from 'framer-motion';
import { useEffect, useRef } from 'react';
import { useWindowSettings } from '/@/renderer/store/settings.store'; import { useWindowSettings } from '/@/renderer/store/settings.store';
import { Platform } from '/@/renderer/types'; import { Platform } from '/@/renderer/types';
export const useFixedTableHeader = () => { export const useFixedTableHeader = ({ enabled }: { enabled: boolean }) => {
const intersectRef = useRef<HTMLDivElement | null>(null); const tableHeaderRef = useRef<HTMLDivElement | null>(null);
const tableContainerRef = useRef<HTMLDivElement | null>(null); const tableContainerRef = useRef<HTMLDivElement | null>(null);
const { windowBarStyle } = useWindowSettings(); const { windowBarStyle } = useWindowSettings();
const isNotPastTableIntersection = useInView(intersectRef, { const topMargin =
margin: windowBarStyle === Platform.WINDOWS || windowBarStyle === Platform.MACOS
windowBarStyle === Platform.WEB || windowBarStyle === Platform.LINUX ? '-128px'
? '-68px 0px 0px 0px' : '-98px';
: '-98px 0px 0px 0px',
const isTableHeaderInView = useInView(tableHeaderRef, {
margin: `${topMargin} 0px 0px 0px`,
}); });
const tableInView = useInView(tableContainerRef, { const isTableInView = useInView(tableContainerRef, {
margin: '-128px 0px 0px 0px', margin: `${topMargin} 0px 0px 0px`,
}); });
useEffect(() => { useEffect(() => {
if (!enabled) {
return;
}
const header = document.querySelector('main .ag-header'); const header = document.querySelector('main .ag-header');
const root = document.querySelector('main .ag-root'); const root = document.querySelector('main .ag-root');
if (isNotPastTableIntersection || !tableInView) { if (!isTableHeaderInView && isTableInView) {
if (windowBarStyle === Platform.WINDOWS || windowBarStyle === Platform.MACOS) {
header?.classList.remove('window-frame');
}
header?.classList.remove('ag-header-fixed');
root?.classList.remove('ag-header-fixed-margin');
} else {
if (windowBarStyle === Platform.WINDOWS || windowBarStyle === Platform.MACOS) { if (windowBarStyle === Platform.WINDOWS || windowBarStyle === Platform.MACOS) {
header?.classList.add('window-frame'); header?.classList.add('window-frame');
} }
header?.classList.add('ag-header-fixed'); header?.classList.add('ag-header-fixed');
root?.classList.add('ag-header-fixed-margin'); root?.classList.add('ag-header-fixed-margin');
} else if (!isTableInView) {
if (windowBarStyle === Platform.WINDOWS || windowBarStyle === Platform.MACOS) {
header?.classList.remove('window-frame');
}
header?.classList.remove('ag-header-fixed');
root?.classList.remove('ag-header-fixed-margin');
} else if (isTableHeaderInView) {
if (windowBarStyle === Platform.WINDOWS || windowBarStyle === Platform.MACOS) {
header?.classList.remove('window-frame');
}
header?.classList.remove('ag-header-fixed');
root?.classList.remove('ag-header-fixed-margin');
} }
}, [isNotPastTableIntersection, tableInView, windowBarStyle]); }, [enabled, isTableHeaderInView, isTableInView, windowBarStyle]);
return { intersectRef, tableContainerRef }; return { tableContainerRef, tableHeaderRef };
}; };

View file

@ -35,6 +35,7 @@ import { RatingCell } from '/@/renderer/components/virtual-table/cells/rating-ce
import { TablePagination } from '/@/renderer/components/virtual-table/table-pagination'; import { TablePagination } from '/@/renderer/components/virtual-table/table-pagination';
import { ActionsCell } from '/@/renderer/components/virtual-table/cells/actions-cell'; import { ActionsCell } from '/@/renderer/components/virtual-table/cells/actions-cell';
import { TitleCell } from '/@/renderer/components/virtual-table/cells/title-cell'; import { TitleCell } from '/@/renderer/components/virtual-table/cells/title-cell';
import { useFixedTableHeader } from '/@/renderer/components/virtual-table/hooks/use-fixed-table-header';
export * from './table-config-dropdown'; export * from './table-config-dropdown';
export * from './table-pagination'; export * from './table-pagination';
@ -43,12 +44,18 @@ export * from './hooks/use-click-outside-deselect';
export * from './utils'; export * from './utils';
const TableWrapper = styled.div` const TableWrapper = styled.div`
position: relative;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
width: 100%; width: 100%;
height: 100%; height: 100%;
`; `;
const DummyHeader = styled.div<{ height?: number }>`
position: absolute;
height: ${({ height }) => height || 36}px};
`;
dayjs.extend(relativeTime); dayjs.extend(relativeTime);
const tableColumns: { [key: string]: ColDef } = { const tableColumns: { [key: string]: ColDef } = {
@ -371,6 +378,7 @@ export interface VirtualTableProps extends AgGridReactProps {
pagination: TablePaginationType; pagination: TablePaginationType;
setPagination: any; setPagination: any;
}; };
stickyHeader?: boolean;
transparentHeader?: boolean; transparentHeader?: boolean;
} }
@ -380,6 +388,7 @@ export const VirtualTable = forwardRef(
autoFitColumns, autoFitColumns,
deselectOnClickOutside, deselectOnClickOutside,
autoHeight, autoHeight,
stickyHeader,
transparentHeader, transparentHeader,
onColumnMoved, onColumnMoved,
onNewColumnsLoaded, onNewColumnsLoaded,
@ -467,55 +476,60 @@ export const VirtualTable = forwardRef(
[autoFitColumns], [autoFitColumns],
); );
const { tableHeaderRef, tableContainerRef } = useFixedTableHeader({
enabled: stickyHeader || false,
});
const mergedWrapperRef = useMergedRef(deselectRef, tableContainerRef);
return ( return (
<> <TableWrapper
<TableWrapper ref={mergedWrapperRef}
ref={deselectRef} className={
className={ transparentHeader
transparentHeader ? 'ag-theme-alpine-dark ag-header-transparent'
? 'ag-header-transparent ag-theme-alpine-dark' : 'ag-theme-alpine-dark'
: 'ag-theme-alpine-dark' }
} >
> <DummyHeader ref={tableHeaderRef} />
<AgGridReact <AgGridReact
ref={mergedRef} ref={mergedRef}
animateRows animateRows
maintainColumnOrder maintainColumnOrder
suppressAsyncEvents suppressAsyncEvents
suppressContextMenu suppressContextMenu
suppressCopyRowsToClipboard suppressCopyRowsToClipboard
suppressMoveWhenRowDragging suppressMoveWhenRowDragging
suppressPaginationPanel suppressPaginationPanel
suppressScrollOnNewData suppressScrollOnNewData
blockLoadDebounceMillis={200} blockLoadDebounceMillis={200}
cacheBlockSize={300} cacheBlockSize={300}
cacheOverflowSize={1} cacheOverflowSize={1}
defaultColDef={defaultColumnDefs} defaultColDef={defaultColumnDefs}
enableCellChangeFlash={false} enableCellChangeFlash={false}
headerHeight={36} headerHeight={36}
rowBuffer={30} rowBuffer={30}
rowSelection="multiple" rowSelection="multiple"
{...rest} {...rest}
onColumnMoved={handleColumnMoved} onColumnMoved={handleColumnMoved}
onGridReady={handleGridReady} onGridReady={handleGridReady}
onGridSizeChanged={handleGridSizeChanged} onGridSizeChanged={handleGridSizeChanged}
onModelUpdated={handleModelUpdated} onModelUpdated={handleModelUpdated}
onNewColumnsLoaded={handleNewColumnsLoaded} onNewColumnsLoaded={handleNewColumnsLoaded}
/> />
{paginationProps && ( {paginationProps && (
<AnimatePresence <AnimatePresence
presenceAffectsLayout presenceAffectsLayout
initial={false} initial={false}
mode="wait" mode="wait"
> >
<TablePagination <TablePagination
{...paginationProps} {...paginationProps}
tableRef={tableRef} tableRef={tableRef}
/> />
</AnimatePresence> </AnimatePresence>
)} )}
</TableWrapper> </TableWrapper>
</>
); );
}, },
); );