48 lines
1.3 KiB
TypeScript
48 lines
1.3 KiB
TypeScript
import { useMemo } from 'react';
|
|
|
|
export type LayoutItem<T> = {
|
|
item: T;
|
|
x: number;
|
|
y: number;
|
|
width: number;
|
|
height: number;
|
|
};
|
|
|
|
export type MasonryLayout<T> = {
|
|
items: LayoutItem<T>[];
|
|
totalHeight: number;
|
|
};
|
|
|
|
export function useMasonryLayout<T>(
|
|
data: T[],
|
|
numColumns: number,
|
|
containerWidth: number,
|
|
getItemHeight: (item: T, width: number) => number | undefined,
|
|
gap: number,
|
|
estimatedHeight: number
|
|
): MasonryLayout<T> {
|
|
return useMemo(() => {
|
|
if (containerWidth <= 0 || data.length === 0) {
|
|
return { items: [], totalHeight: 0 };
|
|
}
|
|
|
|
const columnHeights = Array(numColumns).fill(0);
|
|
const columnWidth = (containerWidth - gap * (numColumns - 1)) / numColumns;
|
|
|
|
const layoutItems = data.map((item) => {
|
|
const shortestColumnIndex = columnHeights.indexOf(Math.min(...columnHeights));
|
|
const x = shortestColumnIndex * (columnWidth + gap);
|
|
const y = columnHeights[shortestColumnIndex];
|
|
const height = getItemHeight(item, columnWidth) ?? estimatedHeight;
|
|
|
|
columnHeights[shortestColumnIndex] = y + height + gap;
|
|
|
|
return { item, x, y, width: columnWidth, height };
|
|
});
|
|
|
|
const totalHeight = Math.max(...columnHeights) - gap;
|
|
|
|
return { items: layoutItems, totalHeight: Math.max(0, totalHeight) };
|
|
}, [data, numColumns, containerWidth, getItemHeight, gap, estimatedHeight]);
|
|
}
|