mirror of
https://github.com/jeffvli/feishin.git
synced 2026-05-17 14:10:14 +02:00
190 lines
7.0 KiB
TypeScript
190 lines
7.0 KiB
TypeScript
import { useInView } from 'motion/react';
|
|
import { useEffect, useMemo, useState } from 'react';
|
|
|
|
import { useWindowSettings } from '/@/renderer/store/settings.store';
|
|
import { Platform } from '/@/shared/types/types';
|
|
|
|
export interface GroupRowInfo {
|
|
groupIndex: number;
|
|
rowIndex: number;
|
|
}
|
|
|
|
export const useStickyTableGroupRows = ({
|
|
containerRef,
|
|
enabled,
|
|
getRowHeight,
|
|
groups,
|
|
headerHeight,
|
|
mainGridRef,
|
|
shouldShowStickyHeader,
|
|
stickyHeaderTop,
|
|
}: {
|
|
containerRef: React.RefObject<HTMLDivElement | null>;
|
|
enabled: boolean;
|
|
getRowHeight: (index: number) => number;
|
|
groups?: Array<{ itemCount: number }>;
|
|
headerHeight: number;
|
|
mainGridRef: React.RefObject<HTMLDivElement | null>;
|
|
shouldShowStickyHeader?: boolean;
|
|
stickyHeaderTop?: number;
|
|
}) => {
|
|
const { windowBarStyle } = useWindowSettings();
|
|
const [stickyGroupIndex, setStickyGroupIndex] = useState<null | number>(null);
|
|
|
|
const topMargin =
|
|
windowBarStyle === Platform.WINDOWS || windowBarStyle === Platform.MACOS
|
|
? '-130px'
|
|
: '-100px';
|
|
|
|
const isTableInView = useInView(containerRef, {
|
|
margin: `${topMargin} 0px 0px 0px`,
|
|
});
|
|
|
|
const stickyTop = useMemo(() => {
|
|
// If sticky header is showing, position group row below it with 1px offset to avoid conflict
|
|
// Otherwise, use the base sticky position
|
|
if (shouldShowStickyHeader && stickyHeaderTop !== undefined) {
|
|
return stickyHeaderTop + headerHeight + 1;
|
|
}
|
|
return windowBarStyle === Platform.WINDOWS || windowBarStyle === Platform.MACOS ? 95 : 65;
|
|
}, [windowBarStyle, shouldShowStickyHeader, stickyHeaderTop, headerHeight]);
|
|
|
|
// Calculate group row indexes
|
|
const groupRowIndexes = useMemo(() => {
|
|
if (!groups || groups.length === 0) {
|
|
return [];
|
|
}
|
|
|
|
const indexes: GroupRowInfo[] = [];
|
|
let cumulativeDataIndex = 0;
|
|
const headerOffset = 1; // Assuming header is enabled
|
|
|
|
groups.forEach((group, groupIndex) => {
|
|
const groupHeaderIndex = headerOffset + cumulativeDataIndex + groupIndex;
|
|
indexes.push({
|
|
groupIndex,
|
|
rowIndex: groupHeaderIndex,
|
|
});
|
|
cumulativeDataIndex += group.itemCount;
|
|
});
|
|
|
|
return indexes;
|
|
}, [groups]);
|
|
|
|
useEffect(() => {
|
|
if (
|
|
!enabled ||
|
|
!groups ||
|
|
groups.length === 0 ||
|
|
!mainGridRef.current ||
|
|
!containerRef.current
|
|
) {
|
|
return;
|
|
}
|
|
|
|
// Get the actual scrollable grid element (first child of the container)
|
|
const mainGridContainer = mainGridRef.current;
|
|
const mainGrid = mainGridContainer.childNodes[0] as HTMLDivElement | null;
|
|
|
|
if (!mainGrid) {
|
|
return;
|
|
}
|
|
|
|
const updateStickyGroup = () => {
|
|
const scrollTop = mainGrid.scrollTop || 0;
|
|
const containerRect = containerRef.current?.getBoundingClientRect();
|
|
|
|
if (!containerRect) {
|
|
return;
|
|
}
|
|
|
|
// Calculate the sticky threshold position
|
|
// The sticky group row should appear when a group row scrolls past this position
|
|
// stickyTop already accounts for window bar style and sticky header offset
|
|
const containerTop = containerRect.top;
|
|
const baseStickyPosition = stickyTop; // Base position (window bar + sticky header if showing)
|
|
|
|
// Find which group row should be sticky
|
|
// We want to show the current group as soon as its row reaches the sticky position
|
|
// This way it updates "on scroll" when scrolling into a new group section
|
|
let targetGroupIndex: null | number = null;
|
|
|
|
// Iterate forward through groups to find which one is at or about to reach the sticky position
|
|
for (let i = 0; i < groupRowIndexes.length; i++) {
|
|
const { groupIndex, rowIndex } = groupRowIndexes[i];
|
|
|
|
// Calculate the top position of this group row relative to the grid scroll
|
|
let rowTop = headerHeight;
|
|
for (let r = 0; r < rowIndex; r++) {
|
|
rowTop += getRowHeight(r);
|
|
}
|
|
|
|
// Calculate where this row would be in the viewport (absolute position from top of viewport)
|
|
const rowViewportTop = containerTop + rowTop - scrollTop;
|
|
|
|
// Get the height of this group row to account for its own offset
|
|
// Use getRowHeight to get the actual row height for the group header row
|
|
const groupRowHeight = getRowHeight(rowIndex);
|
|
|
|
// Calculate the sticky position accounting for the sticky group row's own height
|
|
// Similar to how stickyTop accounts for sticky header height, we add the group row height
|
|
const stickyPosition = baseStickyPosition + groupRowHeight;
|
|
|
|
// Check if this group row has reached or is about to reach the sticky position
|
|
// The sticky group row appears at baseStickyPosition, but we check when the actual group row
|
|
// reaches baseStickyPosition + groupRowHeight to account for the sticky group row's own height
|
|
if (rowViewportTop <= stickyPosition) {
|
|
// This group has reached the sticky position, so show this group
|
|
targetGroupIndex = groupIndex;
|
|
// Don't break here - continue checking to see if a later group should replace it
|
|
} else {
|
|
// This group hasn't reached the sticky position yet
|
|
// If we already found a target group, keep it and stop
|
|
// Otherwise, no group should be sticky yet
|
|
if (targetGroupIndex !== null) {
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
setStickyGroupIndex((prev) => {
|
|
if (prev !== targetGroupIndex) {
|
|
return targetGroupIndex;
|
|
}
|
|
return prev;
|
|
});
|
|
};
|
|
|
|
updateStickyGroup();
|
|
|
|
mainGrid.addEventListener('scroll', updateStickyGroup, { passive: true });
|
|
window.addEventListener('scroll', updateStickyGroup, true);
|
|
window.addEventListener('resize', updateStickyGroup);
|
|
|
|
return () => {
|
|
mainGrid.removeEventListener('scroll', updateStickyGroup);
|
|
window.removeEventListener('scroll', updateStickyGroup, true);
|
|
window.removeEventListener('resize', updateStickyGroup);
|
|
};
|
|
}, [
|
|
enabled,
|
|
groups,
|
|
groupRowIndexes,
|
|
mainGridRef,
|
|
containerRef,
|
|
getRowHeight,
|
|
headerHeight,
|
|
stickyTop,
|
|
]);
|
|
|
|
const shouldShowStickyGroupRow = useMemo(() => {
|
|
return enabled && stickyGroupIndex !== null && isTableInView;
|
|
}, [enabled, stickyGroupIndex, isTableInView]);
|
|
|
|
return {
|
|
shouldShowStickyGroupRow,
|
|
stickyGroupIndex,
|
|
stickyTop,
|
|
};
|
|
};
|