Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 15 additions & 5 deletions packages/@react-stately/layout/src/GridLayout.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,11 @@ export interface GridLayoutOptions {
* @default 18 x 18
*/
minSpace?: Size,
/**
* The maximum allowed horizontal space between items.
* @default Infinity
*/
maxHorizontalSpace?: number,
/**
* The maximum number of columns.
* @default Infinity
Expand All @@ -53,6 +58,7 @@ const DEFAULT_OPTIONS = {
maxItemSize: new Size(Infinity, Infinity),
preserveAspectRatio: false,
minSpace: new Size(18, 18),
maxSpace: Infinity,
maxColumns: Infinity,
dropIndicatorThickness: 2
};
Expand All @@ -69,14 +75,16 @@ export class GridLayout<T, O extends GridLayoutOptions = GridLayoutOptions> exte
protected numColumns: number = 0;
private contentSize: Size = new Size();
private layoutInfos: Map<Key, LayoutInfo> = new Map();
private margin: number = 0;

shouldInvalidateLayoutOptions(newOptions: O, oldOptions: O): boolean {
return newOptions.maxColumns !== oldOptions.maxColumns
|| newOptions.dropIndicatorThickness !== oldOptions.dropIndicatorThickness
|| newOptions.preserveAspectRatio !== oldOptions.preserveAspectRatio
|| (!(newOptions.minItemSize || DEFAULT_OPTIONS.minItemSize).equals(oldOptions.minItemSize || DEFAULT_OPTIONS.minItemSize))
|| (!(newOptions.maxItemSize || DEFAULT_OPTIONS.maxItemSize).equals(oldOptions.maxItemSize || DEFAULT_OPTIONS.maxItemSize))
|| (!(newOptions.minSpace || DEFAULT_OPTIONS.minSpace).equals(oldOptions.minSpace || DEFAULT_OPTIONS.minSpace));
|| (!(newOptions.minSpace || DEFAULT_OPTIONS.minSpace).equals(oldOptions.minSpace || DEFAULT_OPTIONS.minSpace))
|| newOptions.maxHorizontalSpace !== oldOptions.maxHorizontalSpace;
}

update(invalidationContext: InvalidationContext<O>): void {
Expand All @@ -85,6 +93,7 @@ export class GridLayout<T, O extends GridLayoutOptions = GridLayoutOptions> exte
maxItemSize = DEFAULT_OPTIONS.maxItemSize,
preserveAspectRatio = DEFAULT_OPTIONS.preserveAspectRatio,
minSpace = DEFAULT_OPTIONS.minSpace,
maxHorizontalSpace = DEFAULT_OPTIONS.maxSpace,
maxColumns = DEFAULT_OPTIONS.maxColumns,
dropIndicatorThickness = DEFAULT_OPTIONS.dropIndicatorThickness
} = invalidationContext.layoutOptions || {};
Expand Down Expand Up @@ -116,9 +125,10 @@ export class GridLayout<T, O extends GridLayoutOptions = GridLayoutOptions> exte
let itemHeight = minItemSize.height + Math.floor((maxItemHeight - minItemSize.height) * t);
itemHeight = Math.max(minItemSize.height, Math.min(maxItemHeight, itemHeight));

// Compute the horizontal spacing and content height
let horizontalSpacing = Math.floor((visibleWidth - numColumns * itemWidth) / (numColumns + 1));
// Compute the horizontal spacing, content height and horizontal margin
let horizontalSpacing = Math.min(maxHorizontalSpace, Math.floor((visibleWidth - numColumns * itemWidth) / (numColumns + 1)));
this.gap = new Size(horizontalSpacing, minSpace.height);
this.margin = Math.floor((visibleWidth - numColumns * itemWidth - horizontalSpacing * (numColumns + 1)) / 2);

// If there is a skeleton loader within the last 2 items in the collection, increment the collection size
// so that an additional row is added for the skeletons.
Expand All @@ -133,7 +143,7 @@ export class GridLayout<T, O extends GridLayoutOptions = GridLayoutOptions> exte
}
lastKey = collection.getKeyBefore(lastKey);
}

let rows = Math.ceil(collectionSize / numColumns);
let iterator = collection[Symbol.iterator]();
let y = rows > 0 ? minSpace.height : 0;
Expand Down Expand Up @@ -165,7 +175,7 @@ export class GridLayout<T, O extends GridLayoutOptions = GridLayoutOptions> exte
if (skeleton) {
content = oldLayoutInfo && oldLayoutInfo.content.key === key ? oldLayoutInfo.content : {...skeleton, key};
}
let x = horizontalSpacing + col * (itemWidth + horizontalSpacing);
let x = horizontalSpacing + col * (itemWidth + horizontalSpacing) + this.margin;
let height = itemHeight;
let estimatedSize = !preserveAspectRatio;
if (oldLayoutInfo && estimatedSize) {
Expand Down
52 changes: 50 additions & 2 deletions packages/react-aria-components/stories/GridList.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -200,7 +200,20 @@ export const VirtualizedGridList: StoryObj<typeof VirtualizedGridListRender> = {
}
};

export let VirtualizedGridListGrid: GridListStory = () => {
interface VirtualizedGridListGridProps {
maxItemSizeWidth?: number,
maxColumns?: number,
minHorizontalSpace?: number,
maxHorizontalSpace?: number
}

export let VirtualizedGridListGrid: StoryFn<VirtualizedGridListGridProps> = (args) => {
const {
maxItemSizeWidth = 65,
maxColumns = Infinity,
minHorizontalSpace = 0,
maxHorizontalSpace = Infinity
} = args;
let items: {id: number, name: string}[] = [];
for (let i = 0; i < 10000; i++) {
items.push({id: i, name: `Item ${i}`});
Expand All @@ -210,7 +223,11 @@ export let VirtualizedGridListGrid: GridListStory = () => {
<Virtualizer
layout={GridLayout}
layoutOptions={{
minItemSize: new Size(40, 40)
minItemSize: new Size(40, 40),
maxItemSize: new Size(maxItemSizeWidth, 40),
minSpace: new Size(minHorizontalSpace, 18),
maxColumns,
maxHorizontalSpace
}}>
<GridList className={styles.menu} layout="grid" style={{height: 400, width: 400}} aria-label="virtualized listbox" items={items}>
{item => <MyGridListItem>{item.name}</MyGridListItem>}
Expand All @@ -219,6 +236,37 @@ export let VirtualizedGridListGrid: GridListStory = () => {
);
};

VirtualizedGridListGrid.story = {
args: {
maxItemSizeWidth: 65,
maxColumns: undefined,
minHorizontalSpace: 0,
maxHorizontalSpace: undefined
},
argTypes: {
maxItemSizeWidth: {
control: 'number',
description: 'Maximum width of each item in the grid list.',
defaultValue: 65
},
maxColumns: {
control: 'number',
description: 'Maximum number of columns in the grid list.',
defaultValue: undefined
},
minHorizontalSpace: {
control: 'number',
description: 'Minimum horizontal space between grid items.',
defaultValue: 0
},
maxHorizontalSpace: {
control: 'number',
description: 'Maximum horizontal space between grid items.',
defaultValue: undefined
}
}
};

let renderEmptyState = ({isLoading}) => {
return (
<div style={{height: 30, width: '100%'}}>
Expand Down