Skip to content

Commit

Permalink
Horizontal support (#5)
Browse files Browse the repository at this point in the history
* horizontal

* changelog
  • Loading branch information
niikeec authored Nov 13, 2023
1 parent d966727 commit 290ee02
Show file tree
Hide file tree
Showing 14 changed files with 235 additions and 67 deletions.
12 changes: 7 additions & 5 deletions apps/docs/pages/api-reference/react/use-grid.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -25,15 +25,17 @@ const Page = () => {
| scrollRef | `RefObject<Element>` | Yes | Reference to the scrollable element |
| count | number | Yes | Number of items to render |
| totalCount | number | No | Total number of items to render. Can be used to achieve a seamless scroll behaviour when combined with `onLoadMore` |
| size | number \| `{width: number, height: number}` | No / Yes | Size of rendered items <br /> **Required if `columns` isn't passed** |
| columns | number | No / Yes | Number of columns to render <br /> **Required if `size` isn't passed** |
| size | number \| `{width: number, height: number}` | No / Yes | Size of grid items |
| columns | number \| "auto" | No | Number of columns to render |
| rows | number | No | Number of rows to render <br /> **Only applies when `horizontal` is `true`** |
| width | number | No | Width of the grid container |
| padding | number \| `{x?: number, y?: number, top?: number, bottom?: number, left?: number, right?: number}` | No | Grid padding |
| gap | number \| `{x?: number, y?: number}` | No | Grid gap |
| invert | boolean | No | Invert grid |
| getItemId | function | No | Callback for overriding the grid item id |
| invert | boolean | No | Invert items in grid |
| horizontal | boolean | No | Horizontal mode places items in rows from top to bottom. `onLoadMore` area is placed on the x-axis |
| getItemId | function | No | Callback for grid item id |
| getItemData | function | No | Callback for grid item data |
| rowVirtualizer | VirtualizerOptions | No | Row virtualizer options |
| columnVirtualizer | VirtualizerOptions | No | Column virtualizer options |
| onLoadMore | function | No | Renders an area which triggers the callback when scrolled into view |
| loadMoreHeight | number | No | Set the height of the load more area |
| loadMoreSize | number | No | Set the size of the load more area |
2 changes: 1 addition & 1 deletion apps/docs/pages/getting-started/react.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ npm install @virtual-grid/react
{virtualRows.map((virtualRow) => (
<React.Fragment key={virtualRow.index}>
{virtualColumns.map((virtualColumn) => {
// let index = virtualRow.index * grid.columnCount + virtualColumn.index;
// const index = virtualRow.index * grid.columnCount + virtualColumn.index;
// if (index >= grid.count) return null;

return (
Expand Down
7 changes: 7 additions & 0 deletions apps/web/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,12 @@
# web

## 1.0.3

### Patch Changes

- Updated dependencies
- @virtual-grid/react@1.1.0

## 1.0.2

### Patch Changes
Expand Down
22 changes: 17 additions & 5 deletions apps/web/components/demo/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,23 @@ export const Demo = () => {
const grid = useGrid({
scrollRef: ref,
count: controls.count,
...(controls.size.enabled && {
size: { width: controls.size.width, height: controls.size.height },
columns: controls.columns.enabled ? controls.columns.count : undefined
}),
...(controls.columns.enabled ? { columns: controls.columns.count } : { columns: 0 }),
...(controls.columns.enabled
? {
columns: controls.columns.count,
...(controls.size.enabled && {
size: {
width: controls.size.width || undefined,
height: controls.size.height || undefined
}
})
}
: {
columns: 'auto',
size: {
width: controls.size.enabled ? controls.size.width : 0,
height: controls.size.enabled ? controls.size.height : 0
}
}),
padding: controls.padding,
gap: controls.gap
});
Expand Down
2 changes: 1 addition & 1 deletion apps/web/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "web",
"version": "1.0.2",
"version": "1.0.3",
"private": true,
"scripts": {
"dev": "next dev",
Expand Down
3 changes: 2 additions & 1 deletion packages/config/eslint-config/base.js
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,8 @@ const config = {
'@typescript-eslint/no-unsafe-call': 'off',
'typescript-eslint/no-redundant-type-constituents': 'off',
'@typescript-eslint/no-empty-interface': 'off',
'@typescript-eslint/no-misused-promises': 'off'
'@typescript-eslint/no-misused-promises': 'off',
'@typescript-eslint/no-redundant-type-constituents': 'off'
},
ignorePatterns: [
'node_modules/',
Expand Down
6 changes: 6 additions & 0 deletions packages/core/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
# @virtual-grid/core

## 1.1.0

### Minor Changes

- Grid horizontal support

## 1.0.1

### Patch Changes
Expand Down
2 changes: 1 addition & 1 deletion packages/core/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@virtual-grid/core",
"version": "1.0.1",
"version": "1.1.0",
"license": "MIT",
"exports": {
"./package.json": "./package.json",
Expand Down
144 changes: 112 additions & 32 deletions packages/core/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,36 +2,96 @@ import type { GridItem, GridItemData, GridItemId, GridPadding } from './types';
import type { RequireAtLeastOne } from './utils/types';

export interface DefaultGridProps<IdT extends GridItemId, DataT extends GridItemData> {
/**
* Number of grid items.
*/
count: number;
/**
* Total number of grid items.
*/
totalCount?: number;
size?: number | { width: number; height: number };
columns?: number;
/**
* Number of columns. Only applies when `horizontal` is `false`.
* @defaultValue 1
*/
columns?: number | 'auto';
/**
* Number of rows. Only applies when `horizontal` is `true`.
* @defaultValue 1
*/
rows?: number;
/**
* Grid item size.
*/
size?: number | { width?: number; height?: number };
/**
* Grid width.
*/
width?: number;
/**
* Grid padding.
*/
padding?: number | GridPadding;
/**
* Grid gap.
*/
gap?: number | { x?: number; y?: number };
/**
* Invert items in grid.
*/
invert?: boolean;
/**
* Horizontal mode places items in rows from top to bottom. `onLoadMore` area is placed on the x-axis.
*/
horizontal?: boolean;
/**
* Callback function for grid item `id` in `getItem` function.
*/
getItemId?: (index: number) => IdT | undefined;
/**
* Callback function for grid item `data` in `getItem` function.
*/
getItemData?: (index: number) => DataT;
}

export interface SizeGridProps<IdT extends GridItemId, DataT extends GridItemData>
export interface BaseGridProps<IdT extends GridItemId, DataT extends GridItemData>
extends DefaultGridProps<IdT, DataT> {
horizontal?: false;
columns?: number;
}

export interface AutoColumnsGridProps<IdT extends GridItemId, DataT extends GridItemData>
extends DefaultGridProps<IdT, DataT> {
columns: 'auto';
size: number | { width: number; height: number };
}

export interface ColumnsGridProps<IdT extends GridItemId, DataT extends GridItemData>
export interface HorizontalGridProps<IdT extends GridItemId, DataT extends GridItemData>
extends DefaultGridProps<IdT, DataT> {
columns: number;
horizontal: true;
size: number | { width: number; height: number };
}

export type GridProps<IdT extends GridItemId, DataT extends GridItemData> =
| RequireAtLeastOne<SizeGridProps<IdT, DataT>, 'columns' | 'width'>
| RequireAtLeastOne<ColumnsGridProps<IdT, DataT>, 'size' | 'width'>;
| RequireAtLeastOne<BaseGridProps<IdT, DataT>, 'size' | 'width'>
| RequireAtLeastOne<AutoColumnsGridProps<IdT, DataT>, 'width'>
| HorizontalGridProps<IdT, DataT>;

export const grid = <IdT extends GridItemId, DataT extends GridItemData>(
props: GridProps<IdT, DataT>
) => {
const count = !props.totalCount ? props.count : Math.max(props.count, props.totalCount);
const count = Math.trunc(props.count);
const totalCount = props.totalCount ? Math.trunc(props.totalCount) : undefined;
const maxCount = !totalCount ? count : Math.max(count, totalCount);

const rows = props.rows !== undefined ? Math.trunc(props.rows) : 1;

const columns =
props.columns !== undefined
? props.columns !== 'auto'
? Math.trunc(props.columns)
: props.columns
: 1;

const getPadding = (key: keyof GridPadding) => {
return typeof props.padding === 'object' ? props.padding[key] : props.padding;
Expand All @@ -54,29 +114,41 @@ export const grid = <IdT extends GridItemId, DataT extends GridItemData>(
height: typeof props.size === 'object' ? props.size.height : props.size
};

const gridWidth = props.width ? props.width - (padding.left + padding.right) : 0;
const gridWidth = props.width ? props.width - (padding.left + padding.right) : undefined;

let columnCount = props.columns ?? 0;
let columnCount = 0;

if (!columnCount && size.width && gridWidth) {
let columns = Math.floor(gridWidth / size.width);
if (gap.x) columns = Math.floor((gridWidth - (columns - 1) * gap.x) / size.width);
columnCount = columns;
if (!props.horizontal) {
if (columns !== 'auto') columnCount = columns;
else if (gridWidth && size.width) {
let columns = Math.floor(gridWidth / size.width);
if (gap.x) columns = Math.floor((gridWidth - (columns - 1) * gap.x) / size.width);
columnCount = columns;
}
} else {
columnCount = rows > 0 ? Math.round(count / rows) : 0;
}

const rowCount = columnCount > 0 ? Math.ceil(props.count / columnCount) : 0;
const totalRowCount = columnCount > 0 ? Math.ceil(count / columnCount) : 0;
const totalColumnCount = props.horizontal && rows > 0 ? Math.round(maxCount / rows) : columnCount;

const virtualItemWidth =
columnCount > 0
? (props.columns && size.width) ||
(gridWidth ? (gridWidth - (columnCount - 1) * gap.x) / columnCount : 0)
: 0;
const getRowCount = (count: number) => {
return columnCount > 0 ? Math.ceil(count / columnCount) : 0;
};

const rowCount = props.horizontal ? rows : getRowCount(count);
const totalRowCount = props.horizontal ? rowCount : getRowCount(maxCount);

const virtualItemWidth = props.horizontal
? size.width ?? 0
: columnCount > 0
? (columns !== 'auto' && size.width) ||
(gridWidth ? (gridWidth - (columnCount - 1) * gap.x) / columnCount : 0)
: 0;

const virtualItemHeight = size.height ?? virtualItemWidth;

const getItem = (index: number) => {
if (index < 0 || index >= props.count) return;
if (index < 0 || index >= count) return;

const id = props.getItemId?.(index) ?? index;
const data = props.getItemData?.(index);
Expand All @@ -97,29 +169,35 @@ export const grid = <IdT extends GridItemId, DataT extends GridItemData>(
};

const getItemHeight = (index: number) => {
return virtualItemHeight ? virtualItemHeight + (index !== 0 ? gap.y : 0) : 0;
const _index = Math.trunc(index);
return virtualItemHeight ? virtualItemHeight + (_index !== 0 ? gap.y : 0) : 0;
};

const getItemWidth = (index: number) => {
return virtualItemWidth ? virtualItemWidth + (index !== 0 ? gap.x : 0) : 0;
const _index = Math.trunc(index);
return virtualItemWidth ? virtualItemWidth + (_index !== 0 ? gap.x : 0) : 0;
};

const getItemPosition = (index: number) => ({
row: Math.floor(index / columnCount),
column: index % columnCount
});
const getItemPosition = (index: number) => {
const _index = Math.trunc(index);
return {
row: props.horizontal ? _index % rowCount : Math.floor(_index / columnCount),
column: props.horizontal ? Math.floor(_index / rowCount) : _index % columnCount
};
};

const getItemRect = (index: number) => {
const { row, column } = getItemPosition(index);
const position = getItemPosition(index);

const _row = props.invert ? props.count - 1 - row : row;
const row = props.invert && !props.horizontal ? count - 1 - position.row : position.row;
const column = props.invert && props.horizontal ? count - 1 - position.column : position.column;

const x = virtualItemWidth
? padding.left + (column !== 0 ? gap.x : 0) * column + virtualItemWidth * column
: 0;

const y = virtualItemHeight
? padding.top + (_row !== 0 ? gap.y : 0) * _row + virtualItemHeight * _row
? padding.top + (row !== 0 ? gap.y : 0) * row + virtualItemHeight * row
: 0;

return {
Expand All @@ -140,8 +218,10 @@ export const grid = <IdT extends GridItemId, DataT extends GridItemData>(
rowCount,
totalRowCount,
columnCount,
count: props.count,
totalColumnCount,
count: count,
invert: props.invert,
horizontal: props.horizontal,
itemSize: {
width: size.width,
height: size.height
Expand Down
11 changes: 11 additions & 0 deletions packages/react/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,16 @@
# @virtual-grid/react

## 1.1.0

### Minor Changes

- Grid horizontal support

### Patch Changes

- Updated dependencies
- @virtual-grid/core@1.1.0

## 1.0.2

### Patch Changes
Expand Down
4 changes: 2 additions & 2 deletions packages/react/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@virtual-grid/react",
"version": "1.0.2",
"version": "1.1.0",
"license": "MIT",
"exports": {
"./package.json": "./package.json",
Expand All @@ -23,7 +23,7 @@
},
"dependencies": {
"@tanstack/react-virtual": "3.0.0-beta.66",
"@virtual-grid/core": "^1.0.1",
"@virtual-grid/core": "^1.1.0",
"react-intersection-observer": "^9.5.2",
"use-resize-observer": "^9.1.0"
},
Expand Down
Loading

1 comment on commit 290ee02

@vercel
Copy link

@vercel vercel bot commented on 290ee02 Nov 13, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please sign in to comment.