add more grid configuration

- gap
- manual items per row
This commit is contained in:
jeffvli
2025-10-09 13:13:19 -07:00
parent e43ee465e4
commit 6e5f8a5014
2 changed files with 59 additions and 52 deletions
@@ -20,6 +20,25 @@
width: 100%; width: 100%;
max-width: calc(100% / var(--columns)); max-width: calc(100% / var(--columns));
height: 100%; height: 100%;
padding: var(--theme-spacing-sm);
overflow: hidden; overflow: hidden;
} }
.item-row.gap-xs {
padding: var(--theme-spacing-xs);
}
.item-row.gap-sm {
padding: var(--theme-spacing-sm);
}
.item-row.gap-md {
padding: var(--theme-spacing-md);
}
.item-row.gap-lg {
padding: var(--theme-spacing-lg);
}
.item-row.gap-xl {
padding: var(--theme-spacing-xl);
}
@@ -1,6 +1,7 @@
import { useElementSize, useMergedRef } from '@mantine/hooks'; import { useElementSize, useMergedRef } from '@mantine/hooks';
import clsx from 'clsx';
import { throttle } from 'lodash'; import { throttle } from 'lodash';
import { AnimatePresence, motion, Variants } from 'motion/react'; import { AnimatePresence } from 'motion/react';
import { useOverlayScrollbars } from 'overlayscrollbars-react'; import { useOverlayScrollbars } from 'overlayscrollbars-react';
import { import {
CSSProperties, CSSProperties,
@@ -33,6 +34,7 @@ export interface GridItemProps {
data: any[]; data: any[];
enableExpansion?: boolean; enableExpansion?: boolean;
enableSelection?: boolean; enableSelection?: boolean;
gap: 'lg' | 'md' | 'sm' | 'xl' | 'xs';
internalState: ItemListStateActions; internalState: ItemListStateActions;
itemType: LibraryItem; itemType: LibraryItem;
} }
@@ -41,11 +43,13 @@ export interface ItemGridListProps {
data: unknown[]; data: unknown[];
enableExpansion?: boolean; enableExpansion?: boolean;
enableSelection?: boolean; enableSelection?: boolean;
gap?: 'lg' | 'md' | 'sm' | 'xl' | 'xs';
initialTop?: { initialTop?: {
behavior?: 'auto' | 'smooth'; behavior?: 'auto' | 'smooth';
to: number; to: number;
type: 'index' | 'offset'; type: 'index' | 'offset';
}; };
itemsPerRow?: number;
itemType: LibraryItem; itemType: LibraryItem;
onEndReached?: (index: number, handle: ItemListHandle) => void; onEndReached?: (index: number, handle: ItemListHandle) => void;
onRangeChanged?: (range: { endIndex: number; startIndex: number }) => void; onRangeChanged?: (range: { endIndex: number; startIndex: number }) => void;
@@ -53,33 +57,19 @@ export interface ItemGridListProps {
onScrollEnd?: (offset: number, handle: ItemListHandle) => void; onScrollEnd?: (offset: number, handle: ItemListHandle) => void;
onStartReached?: (index: number, handle: ItemListHandle) => void; onStartReached?: (index: number, handle: ItemListHandle) => void;
ref: Ref<ListImperativeAPI>; ref: Ref<ListImperativeAPI>;
totalItemCount?: number;
} }
const expandedAnimationVariants: Variants = {
hidden: {
height: 0,
minHeight: 0,
},
show: {
minHeight: '300px',
transition: {
duration: 0.3,
ease: 'easeInOut',
},
},
};
export const ItemGridList = ({ export const ItemGridList = ({
data, data,
enableExpansion = false, enableExpansion = true,
enableSelection = false, enableSelection = true,
gap = 'sm',
itemsPerRow,
itemType, itemType,
onEndReached, onEndReached,
onRangeChanged, onRangeChanged,
onScroll, onScroll,
onStartReached, onStartReached,
totalItemCount = 0,
}: ItemGridListProps) => { }: ItemGridListProps) => {
const itemGridRef = useListRef(null); const itemGridRef = useListRef(null);
const scrollContainerRef = useRef<HTMLDivElement | null>(null); const scrollContainerRef = useRef<HTMLDivElement | null>(null);
@@ -105,7 +95,6 @@ export const ItemGridList = ({
autoHideDelay: 500, autoHideDelay: 500,
pointers: ['mouse', 'pen', 'touch'], pointers: ['mouse', 'pen', 'touch'],
theme: 'feishin-os-scrollbar', theme: 'feishin-os-scrollbar',
visibility: 'visible',
}, },
}, },
}); });
@@ -141,29 +130,35 @@ export const ItemGridList = ({
return throttle((width: number, dataLength: number, type: LibraryItem) => { return throttle((width: number, dataLength: number, type: LibraryItem) => {
const isSm = width >= 600; const isSm = width >= 600;
const isMd = width >= 768; const isMd = width >= 768;
const isLg = width >= 1200; const isLg = width >= 960;
const isXl = width >= 1500; const isXl = width >= 1200;
const is2xl = width >= 1920; const is2xl = width >= 1440;
const is3xl = width >= 2560; const is3xl = width >= 1920;
const is4xl = width >= 2560;
let itemsPerRow = 2; let dynamicItemsPerRow = 2;
if (is3xl) { if (is4xl) {
itemsPerRow = 12; dynamicItemsPerRow = 12;
} else if (is3xl) {
dynamicItemsPerRow = 10;
} else if (is2xl) { } else if (is2xl) {
itemsPerRow = 10; dynamicItemsPerRow = 8;
} else if (isXl) { } else if (isXl) {
itemsPerRow = 8; dynamicItemsPerRow = 6;
} else if (isLg) { } else if (isLg) {
itemsPerRow = 6; dynamicItemsPerRow = 5;
} else if (isMd) { } else if (isMd) {
itemsPerRow = 4; dynamicItemsPerRow = 4;
} else if (isSm) { } else if (isSm) {
itemsPerRow = 3; dynamicItemsPerRow = 3;
} else { } else {
itemsPerRow = 2; dynamicItemsPerRow = 2;
} }
const widthPerItem = Number(width) / itemsPerRow;
const setItemsPerRow = itemsPerRow || dynamicItemsPerRow;
const widthPerItem = Number(width) / setItemsPerRow;
const itemHeight = widthPerItem + getDataRowsCount(type) * 26; const itemHeight = widthPerItem + getDataRowsCount(type) * 26;
if (widthPerItem === 0) { if (widthPerItem === 0) {
@@ -171,12 +166,12 @@ export const ItemGridList = ({
} }
setTableMeta({ setTableMeta({
columnCount: itemsPerRow, columnCount: setItemsPerRow,
itemHeight, itemHeight,
rowCount: Math.ceil(dataLength / itemsPerRow), rowCount: Math.ceil(dataLength / setItemsPerRow),
}); });
}, 200); }, 200);
}, []); }, [itemsPerRow]);
useLayoutEffect(() => { useLayoutEffect(() => {
throttledSetTableMeta(containerWidth, data.length, itemType); throttledSetTableMeta(containerWidth, data.length, itemType);
@@ -190,7 +185,7 @@ export const ItemGridList = ({
}); });
if (onStartReached || onEndReached) { if (onStartReached || onEndReached) {
const totalRows = Math.ceil(totalItemCount / (tableMeta?.columnCount || 0)); const totalRows = Math.ceil(data.length / (tableMeta?.columnCount || 0));
const startRow = visibleRows.startIndex; const startRow = visibleRows.startIndex;
const endRow = visibleRows.stopIndex; const endRow = visibleRows.stopIndex;
@@ -207,7 +202,7 @@ export const ItemGridList = ({
tableMeta?.columnCount, tableMeta?.columnCount,
onStartReached, onStartReached,
onEndReached, onEndReached,
totalItemCount, data.length,
itemGridRef, itemGridRef,
], ],
); );
@@ -242,23 +237,15 @@ export const ItemGridList = ({
data: elements, data: elements,
enableExpansion, enableExpansion,
enableSelection, enableSelection,
gap,
internalState, internalState,
itemType, itemType,
}; };
return ( return (
<motion.div <div
animate={{
height: '100%',
opacity: 1,
transition: {
duration: 1,
ease: 'backInOut',
},
}}
className={styles.itemGridContainer} className={styles.itemGridContainer}
data-overlayscrollbars-initialize="" data-overlayscrollbars-initialize=""
initial={{ opacity: 0 }}
ref={mergedContainerRef} ref={mergedContainerRef}
> >
<List <List
@@ -277,7 +264,7 @@ export const ItemGridList = ({
</ExpandedListContainer> </ExpandedListContainer>
)} )}
</AnimatePresence> </AnimatePresence>
</motion.div> </div>
); );
}; };
@@ -286,6 +273,7 @@ const ListComponent = ({
data, data,
enableExpansion, enableExpansion,
enableSelection, enableSelection,
gap,
index, index,
internalState, internalState,
itemType, itemType,
@@ -295,7 +283,7 @@ const ListComponent = ({
<div className={styles.itemList} style={style}> <div className={styles.itemList} style={style}>
{data[index].map((d) => ( {data[index].map((d) => (
<div <div
className={styles.itemRow} className={clsx(styles.itemRow, styles[`gap-${gap}`])}
key={d.index} key={d.index}
style={{ '--columns': columns } as CSSProperties} style={{ '--columns': columns } as CSSProperties}
> >