Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(use-positioner): add maxColumnCount property #132

Merged
merged 1 commit into from
Sep 16, 2022
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
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