Skip to content

Commit

Permalink
feat(use-positioner): add maxColumnCount property (#132)
Browse files Browse the repository at this point in the history
  • Loading branch information
V3RON authored Sep 16, 2022
1 parent d86293f commit dbec4ff
Show file tree
Hide file tree
Showing 5 changed files with 208 additions and 121 deletions.
45 changes: 24 additions & 21 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -167,12 +167,13 @@ const MasonryCard = ({ index, data: { id }, width }) => (

Props for tuning the column width, count, and gutter of your component.

| Prop | Type | Default | Required? | Description |
| ------------ | -------- | ---------------------- | --------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| columnWidth | `number` | `240` | No | This is the minimum column width. `Masonic` will automatically size your columns to fill its container based on your provided `columnWidth` and `columnGutter` values. It will never render anything smaller than this defined width unless its container is smaller than its value. |
| columnGutter | `number` | `0` | No | This sets the horizontal space between grid columns in pixels. If `rowGutter` is not set, this also sets the vertical space between cells within a column in pixels. |
| rowGutter | `number` | Same as `columnGutter` | No | This sets the vertical space between cells within a column in pixels. |
| columnCount | `number` | | No | By default, `Masonic` derives the column count from the `columnWidth` prop. However, in some situations it is nice to be able to override that behavior e.g. when creating a [`<List>`](#list). |
| Prop | Type | Default | Required? | Description |
| -------------- | -------- | ---------------------- | --------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| columnWidth | `number` | `240` | No | This is the minimum column width. `Masonic` will automatically size your columns to fill its container based on your provided `columnWidth` and `columnGutter` values. It will never render anything smaller than this defined width unless its container is smaller than its value. |
| columnGutter | `number` | `0` | No | This sets the horizontal space between grid columns in pixels. If `rowGutter` is not set, this also sets the vertical space between cells within a column in pixels. |
| rowGutter | `number` | Same as `columnGutter` | No | This sets the vertical space between cells within a column in pixels. |
| columnCount | `number` | | No | By default, `Masonic` derives the column count from the `columnWidth` prop. However, in some situations it is nice to be able to override that behavior e.g. when creating a [`<List>`](#list). |
| maxColumnCount | `number` | | No | Limits the number of columns used by `Masonic`. Useful for implementing responsive layouts. |

**Grid container props**

Expand Down Expand Up @@ -273,7 +274,7 @@ const MyMasonry = (props) => {
#### Props

In addition to these props, this component accepts all of the props outlined in [`<Masonry>`](#masonry)
with exception to `columnGutter`, `rowGutter`, `columnWidth`, `columnCount`, `ssrWidth`, and `ssrHeight`.
with exception to `columnGutter`, `rowGutter`, `columnWidth`, `columnCount`, `maxColumntCount`, `ssrWidth`, and `ssrHeight`.

| Prop | Type | Default | Required? | Description |
| -------------- | ------------------------------------------------------------------- | ------- | --------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
Expand Down Expand Up @@ -315,7 +316,7 @@ const ListCard = ({ index, data: { id }, width }) => (
#### Props

In addition to these props, this component accepts all of the props outlined in [`<Masonry>`](#masonry)
with exception to `columnGutter`, `columnWidth`, and `columnCount`.
with exception to `columnGutter`, `columnWidth`, `columnCount`, and `maxColumnCount`.

| Prop | Type | Default | Required? | Description |
| --------- | -------- | ------- | --------- | ---------------------------------------------------------------------- |
Expand Down Expand Up @@ -442,13 +443,14 @@ const MyMasonry = ({ columnWidth = 300, columnGutter = 16, ...props }) => {

#### UsePositionerOptions

| Argument | Type | Default | Required? | Description |
| ------------ | -------- | ---------------------- | --------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| width | `number` | | Yes | The width of the container you're rendering the grid within, e.g. the container element's `element.offsetWidth`. That said, you can provide any width here. |
| columnWidth | `number` | `200` | No | The minimum column width. The [`usePositioner()`](#usepositioneroptions-deps) hook will automatically size the columns to fill their container based upon the `columnWidth` and `columnGutter` values. It will never render anything smaller than this width unless its container itself is smaller than its value. This property has no effect if you're providing a `columnCount`. |
| columnGutter | `number` | `0` | No | This sets the horizontal space between grid columns in pixels. If `rowGutter` is not set, this also sets the vertical space between cells within a column in pixels. |
| rowGutter | `number` | Same as `columnGutter` | No | This sets the vertical space between cells within a column in pixels. |
| columnCount | `number` | | No | By default, [`usePositioner()`](#usepositioneroptions-deps) derives the column count from the `columnWidth`, `columnGutter`, and `width` props. However, in some situations it is nice to be able to override that behavior (e.g. creating a [`<List>`-like](#list) component). |
| Argument | Type | Default | Required? | Description |
| -------------- | -------- | ---------------------- | --------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| width | `number` | | Yes | The width of the container you're rendering the grid within, e.g. the container element's `element.offsetWidth`. That said, you can provide any width here. |
| columnWidth | `number` | `200` | No | The minimum column width. The [`usePositioner()`](#usepositioneroptions-deps) hook will automatically size the columns to fill their container based upon the `columnWidth` and `columnGutter` values. It will never render anything smaller than this width unless its container itself is smaller than its value. This property has no effect if you're providing a `columnCount`. |
| columnGutter | `number` | `0` | No | This sets the horizontal space between grid columns in pixels. If `rowGutter` is not set, this also sets the vertical space between cells within a column in pixels. |
| rowGutter | `number` | Same as `columnGutter` | No | This sets the vertical space between cells within a column in pixels. |
| columnCount | `number` | | No | By default, [`usePositioner()`](#usepositioneroptions-deps) derives the column count from the `columnWidth`, `columnGutter`, and `width` props. However, in some situations it is nice to be able to override that behavior (e.g. creating a [`<List>`-like](#list) component). |
| maxColumnCount | `number` | | No | Limits the number of columns used by [`usePositioner()`](#usepositioneroptions-deps). Useful for implementing responsive layouts. |

#### Returns a [`Positioner`](#positioner)

Expand Down Expand Up @@ -782,12 +784,13 @@ this utility under the hood.

#### Arguments

| Argument | Type | Description |
| ------------ | -------- | ---------------------------------------------------------------------------------------------------- |
| columnCount | `number` | The number of columns in the grid |
| columnWidth | `number` | The width of each column in the grid |
| columnGutter | `number` | The amount of horizontal space between columns in pixels. |
| rowGutter | `number` | The amount of vertical space between cells within a column in pixels (falls back to `columnGutter`). |
| Argument | Type | Description |
| -------------- | -------- | ---------------------------------------------------------------------------------------------------- |
| columnCount | `number` | The number of columns in the grid |
| columnWidth | `number` | The width of each column in the grid |
| columnGutter | `number` | The amount of horizontal space between columns in pixels. |
| rowGutter | `number` | The amount of vertical space between cells within a column in pixels (falls back to `columnGutter`). |
| maxColumnCount | `number` | The upper bound of column count. |

#### Returns [`Positioner`](#positioner)

Expand Down
35 changes: 35 additions & 0 deletions src/index.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -408,6 +408,41 @@ describe("usePositioner()", () => {
expect(result.current.columnWidth).toBe(418);
});

it("should automatically derive column width when a maximum column count is defined", () => {
const { result, rerender } = renderHook((props) => usePositioner(props), {
initialProps: {
width: 1280,
columnCount: undefined,
columnWidth: 20,
columnGutter: 10,
maxColumnCount: 4,
},
});

expect(result.current.columnCount).toBe(4);
expect(result.current.columnWidth).toBe(312);

rerender({
width: 1280,
columnCount: undefined,
columnWidth: 20,
columnGutter: 10,
maxColumnCount: 5,
});
expect(result.current.columnCount).toBe(5);
expect(result.current.columnWidth).toBe(248);

rerender({
width: 1280,
columnCount: 1,
columnWidth: 20,
columnGutter: 10,
maxColumnCount: 5,
});
expect(result.current.columnCount).toBe(1);
expect(result.current.columnWidth).toBe(1280);
});

it("should create a new positioner when sizing deps change", () => {
const { result, rerender } = renderHook((props) => usePositioner(props), {
initialProps: { width: 1280, columnCount: 4, columnGutter: 10 },
Expand Down
6 changes: 5 additions & 1 deletion src/masonry.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,11 @@ export interface MasonryProps<Item>
>,
Pick<
UsePositionerOptions,
"columnWidth" | "columnGutter" | "rowGutter" | "columnCount"
| "columnWidth"
| "columnGutter"
| "rowGutter"
| "columnCount"
| "maxColumnCount"
> {
/**
* Scrolls to a given index within the grid. The grid will re-scroll
Expand Down
29 changes: 25 additions & 4 deletions src/use-positioner.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import { createIntervalTree } from "./interval-tree";
* @param options.columnGutter
* @param options.rowGutter
* @param options.columnCount
* @param options.maxColumnCount
*/
export function usePositioner(
{
Expand All @@ -23,6 +24,7 @@ export function usePositioner(
columnGutter = 0,
rowGutter,
columnCount,
maxColumnCount,
}: UsePositionerOptions,
deps: React.DependencyList = emptyArr
): Positioner {
Expand All @@ -31,7 +33,8 @@ export function usePositioner(
width,
columnWidth,
columnGutter,
columnCount
columnCount,
maxColumnCount
);
return createPositioner(
computedColumnCount,
Expand All @@ -45,7 +48,14 @@ export function usePositioner(
positionerRef.current = initPositioner();

const prevDeps = React.useRef(deps);
const opts = [width, columnWidth, columnGutter, rowGutter, columnCount];
const opts = [
width,
columnWidth,
columnGutter,
rowGutter,
columnCount,
maxColumnCount,
];
const prevOpts = React.useRef(opts);
const optsChanged = !opts.every((item, i) => prevOpts.current[i] === item);

Expand Down Expand Up @@ -113,6 +123,10 @@ export interface UsePositionerOptions {
* (e.g. creating a `List` component).
*/
columnCount?: number;
/**
* The upper bound of column count. This property won't work if `columnCount` is set.
*/
maxColumnCount?: number;
}

/**
Expand Down Expand Up @@ -334,9 +348,16 @@ const getColumns = (
width = 0,
minimumWidth = 0,
gutter = 8,
columnCount?: number
columnCount?: number,
maxColumnCount?: number
): [number, number] => {
columnCount = columnCount || Math.floor((width + gutter) / (minimumWidth + gutter)) || 1;
columnCount =
columnCount ||
Math.min(
Math.floor((width + gutter) / (minimumWidth + gutter)),
maxColumnCount || Infinity
) ||
1;
const columnWidth = Math.floor(
(width - gutter * (columnCount - 1)) / columnCount
);
Expand Down
Loading

0 comments on commit dbec4ff

Please sign in to comment.