Skip to content

Commit dbec4ff

Browse files
authored
feat(use-positioner): add maxColumnCount property (#132)
1 parent d86293f commit dbec4ff

File tree

5 files changed

+208
-121
lines changed

5 files changed

+208
-121
lines changed

README.md

Lines changed: 24 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -167,12 +167,13 @@ const MasonryCard = ({ index, data: { id }, width }) => (
167167

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

170-
| Prop | Type | Default | Required? | Description |
171-
| ------------ | -------- | ---------------------- | --------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
172-
| 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. |
173-
| 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. |
174-
| rowGutter | `number` | Same as `columnGutter` | No | This sets the vertical space between cells within a column in pixels. |
175-
| 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). |
170+
| Prop | Type | Default | Required? | Description |
171+
| -------------- | -------- | ---------------------- | --------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
172+
| 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. |
173+
| 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. |
174+
| rowGutter | `number` | Same as `columnGutter` | No | This sets the vertical space between cells within a column in pixels. |
175+
| 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). |
176+
| maxColumnCount | `number` | | No | Limits the number of columns used by `Masonic`. Useful for implementing responsive layouts. |
176177

177178
**Grid container props**
178179

@@ -273,7 +274,7 @@ const MyMasonry = (props) => {
273274
#### Props
274275

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

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

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

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

443444
#### UsePositionerOptions
444445

445-
| Argument | Type | Default | Required? | Description |
446-
| ------------ | -------- | ---------------------- | --------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
447-
| 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. |
448-
| 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`. |
449-
| 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. |
450-
| rowGutter | `number` | Same as `columnGutter` | No | This sets the vertical space between cells within a column in pixels. |
451-
| 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). |
446+
| Argument | Type | Default | Required? | Description |
447+
| -------------- | -------- | ---------------------- | --------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
448+
| 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. |
449+
| 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`. |
450+
| 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. |
451+
| rowGutter | `number` | Same as `columnGutter` | No | This sets the vertical space between cells within a column in pixels. |
452+
| 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). |
453+
| maxColumnCount | `number` | | No | Limits the number of columns used by [`usePositioner()`](#usepositioneroptions-deps). Useful for implementing responsive layouts. |
452454

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

@@ -782,12 +784,13 @@ this utility under the hood.
782784

783785
#### Arguments
784786

785-
| Argument | Type | Description |
786-
| ------------ | -------- | ---------------------------------------------------------------------------------------------------- |
787-
| columnCount | `number` | The number of columns in the grid |
788-
| columnWidth | `number` | The width of each column in the grid |
789-
| columnGutter | `number` | The amount of horizontal space between columns in pixels. |
790-
| rowGutter | `number` | The amount of vertical space between cells within a column in pixels (falls back to `columnGutter`). |
787+
| Argument | Type | Description |
788+
| -------------- | -------- | ---------------------------------------------------------------------------------------------------- |
789+
| columnCount | `number` | The number of columns in the grid |
790+
| columnWidth | `number` | The width of each column in the grid |
791+
| columnGutter | `number` | The amount of horizontal space between columns in pixels. |
792+
| rowGutter | `number` | The amount of vertical space between cells within a column in pixels (falls back to `columnGutter`). |
793+
| maxColumnCount | `number` | The upper bound of column count. |
791794

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

src/index.test.tsx

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -408,6 +408,41 @@ describe("usePositioner()", () => {
408408
expect(result.current.columnWidth).toBe(418);
409409
});
410410

411+
it("should automatically derive column width when a maximum column count is defined", () => {
412+
const { result, rerender } = renderHook((props) => usePositioner(props), {
413+
initialProps: {
414+
width: 1280,
415+
columnCount: undefined,
416+
columnWidth: 20,
417+
columnGutter: 10,
418+
maxColumnCount: 4,
419+
},
420+
});
421+
422+
expect(result.current.columnCount).toBe(4);
423+
expect(result.current.columnWidth).toBe(312);
424+
425+
rerender({
426+
width: 1280,
427+
columnCount: undefined,
428+
columnWidth: 20,
429+
columnGutter: 10,
430+
maxColumnCount: 5,
431+
});
432+
expect(result.current.columnCount).toBe(5);
433+
expect(result.current.columnWidth).toBe(248);
434+
435+
rerender({
436+
width: 1280,
437+
columnCount: 1,
438+
columnWidth: 20,
439+
columnGutter: 10,
440+
maxColumnCount: 5,
441+
});
442+
expect(result.current.columnCount).toBe(1);
443+
expect(result.current.columnWidth).toBe(1280);
444+
});
445+
411446
it("should create a new positioner when sizing deps change", () => {
412447
const { result, rerender } = renderHook((props) => usePositioner(props), {
413448
initialProps: { width: 1280, columnCount: 4, columnGutter: 10 },

src/masonry.tsx

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,11 @@ export interface MasonryProps<Item>
6363
>,
6464
Pick<
6565
UsePositionerOptions,
66-
"columnWidth" | "columnGutter" | "rowGutter" | "columnCount"
66+
| "columnWidth"
67+
| "columnGutter"
68+
| "rowGutter"
69+
| "columnCount"
70+
| "maxColumnCount"
6771
> {
6872
/**
6973
* Scrolls to a given index within the grid. The grid will re-scroll

src/use-positioner.ts

Lines changed: 25 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import { createIntervalTree } from "./interval-tree";
1515
* @param options.columnGutter
1616
* @param options.rowGutter
1717
* @param options.columnCount
18+
* @param options.maxColumnCount
1819
*/
1920
export function usePositioner(
2021
{
@@ -23,6 +24,7 @@ export function usePositioner(
2324
columnGutter = 0,
2425
rowGutter,
2526
columnCount,
27+
maxColumnCount,
2628
}: UsePositionerOptions,
2729
deps: React.DependencyList = emptyArr
2830
): Positioner {
@@ -31,7 +33,8 @@ export function usePositioner(
3133
width,
3234
columnWidth,
3335
columnGutter,
34-
columnCount
36+
columnCount,
37+
maxColumnCount
3538
);
3639
return createPositioner(
3740
computedColumnCount,
@@ -45,7 +48,14 @@ export function usePositioner(
4548
positionerRef.current = initPositioner();
4649

4750
const prevDeps = React.useRef(deps);
48-
const opts = [width, columnWidth, columnGutter, rowGutter, columnCount];
51+
const opts = [
52+
width,
53+
columnWidth,
54+
columnGutter,
55+
rowGutter,
56+
columnCount,
57+
maxColumnCount,
58+
];
4959
const prevOpts = React.useRef(opts);
5060
const optsChanged = !opts.every((item, i) => prevOpts.current[i] === item);
5161

@@ -113,6 +123,10 @@ export interface UsePositionerOptions {
113123
* (e.g. creating a `List` component).
114124
*/
115125
columnCount?: number;
126+
/**
127+
* The upper bound of column count. This property won't work if `columnCount` is set.
128+
*/
129+
maxColumnCount?: number;
116130
}
117131

118132
/**
@@ -334,9 +348,16 @@ const getColumns = (
334348
width = 0,
335349
minimumWidth = 0,
336350
gutter = 8,
337-
columnCount?: number
351+
columnCount?: number,
352+
maxColumnCount?: number
338353
): [number, number] => {
339-
columnCount = columnCount || Math.floor((width + gutter) / (minimumWidth + gutter)) || 1;
354+
columnCount =
355+
columnCount ||
356+
Math.min(
357+
Math.floor((width + gutter) / (minimumWidth + gutter)),
358+
maxColumnCount || Infinity
359+
) ||
360+
1;
340361
const columnWidth = Math.floor(
341362
(width - gutter * (columnCount - 1)) / columnCount
342363
);

0 commit comments

Comments
 (0)