diff --git a/README.md b/README.md
index f6cbdad..9f9c4c8 100644
--- a/README.md
+++ b/README.md
@@ -96,6 +96,8 @@ export function Komponent() {
}
```
+> Note: The element resizing is automatically debounced with a delay of 200ms. You can override this delay by setting the `debounce` prop with a number in milliseconds.
+
Also the gap between columns can be set by setting the `gap` prop:
```jsx
diff --git a/src/components/Plock.js b/src/components/Plock.js
index e0e4982..6190902 100644
--- a/src/components/Plock.js
+++ b/src/components/Plock.js
@@ -1,7 +1,5 @@
import * as React from "react";
-const uuid = () => Math.random().toString(36).substring(2, 12);
-
/**
* Configuration for Plock.
* This is a map of breakpoints to the number of columns to use for that breakpoint.
@@ -14,84 +12,126 @@ const uuid = () => Math.random().toString(36).substring(2, 12);
* ];
*/
-export function useWindowWidth() {
+export function useWindowWidth({ debounceMs }) {
const [width, setWidth] = React.useState(window.innerWidth);
+ const handleResize = useDebounce(
+ () => setWidth(window.innerWidth),
+ debounceMs
+ );
React.useEffect(() => {
- const handleResize = () => setWidth(window.innerWidth);
window.addEventListener("resize", handleResize);
return () => window.removeEventListener("resize", handleResize);
- }, []);
+ }, [handleResize]);
return width;
}
-export function Plock({ children, className, style, nColumns = 3, gap = 10 }) {
- const width = useWindowWidth();
- const [columns, setColumns] = React.useState([]);
-
- React.useLayoutEffect(() => {
- let columnsElements = [];
-
- if (typeof nColumns === "number") {
- columnsElements = Array.from({ length: nColumns }, (e) => []);
- } else {
- let breakpoint = nColumns
- .filter((el) => el.size <= width)
- .sort((a, b) => a.size - b.size)
- .pop();
-
- if (!breakpoint) {
- breakpoint = nColumns.sort((a, b) => a.size - b.size)[0];
- }
-
- columnsElements = Array.from({ length: breakpoint.columns }, (e) => []);
- }
-
- React.Children.forEach(children, (child, index) => {
- const key = uuid();
- const cloned = React.cloneElement(child, {
- ...child.props,
- key: key,
- });
+export function useDebounce(fn, ms) {
+ let timeout = null;
- columnsElements[index % columnsElements.length].push(cloned);
- });
+ return (...args) => {
+ clearTimeout(timeout);
+ timeout = setTimeout(() => fn(args), ms);
+ };
+}
- setColumns(columnsElements);
- }, [children, nColumns, setColumns, width]);
+export const Plock = React.forwardRef(
+ (
+ {
+ as: Comp = "div",
+ children,
+ className,
+ style,
+ gap = 10,
+ debounce = 200,
- const defaultStyles = {
- mainGrid: {
- display: "grid",
- gridTemplateColumns: `repeat(${columns.length}, 1fr)`,
- columnGap: gap,
- alignItems: "start",
- },
- columnGrid: {
- display: "grid",
- gridTemplateColumns: "100%",
- rowGap: gap,
+ /**
+ * TODO:
+ * This will be renamed to breakpoints in a future major release!
+ */
+ nColumns: breakpoints = 3,
},
- };
+ forwardedRef
+ ) => {
+ const width = useWindowWidth({ debounceMs: debounce });
+ const [columns, setColumns] = React.useState([]);
- return (
-
- {columns.map((column, index) => {
- return (
-
- {column}
-
- );
- })}
-
- );
-}
+ React.useLayoutEffect(() => {
+ const first = (breakpoints) => {
+ return breakpoints?.[0];
+ };
+
+ const last = (breakpoints) => {
+ return breakpoints?.[breakpoints.length - 1];
+ };
+
+ const sorted = (breakpoints) => {
+ return breakpoints.sort((a, b) => a.size - b.size);
+ };
+
+ const contained = (breakpoints, width) => {
+ return breakpoints.filter((el) => el.size <= width);
+ };
+
+ const isNumber = (element) => typeof element === "number";
+
+ const breakpoint = isNumber(breakpoints)
+ ? { columns: breakpoints }
+ : last(sorted(contained(breakpoints, width))) ??
+ first(sorted(breakpoints));
+
+ const columnsForBreakpoint = Array.from(
+ { length: breakpoint.columns },
+ (e) => []
+ );
+
+ React.Children.forEach(children, (child, index) => {
+ const key = `item-${index}`;
+ const cloned = React.cloneElement(child, {
+ ...child.props,
+ key: key,
+ });
+
+ columnsForBreakpoint[index % columnsForBreakpoint.length].push(cloned);
+ });
+
+ setColumns(columnsForBreakpoint);
+ }, [children, breakpoints, width]);
+
+ const defaultStyles = {
+ mainGrid: {
+ display: "grid",
+ gridTemplateColumns: `repeat(${columns.length}, 1fr)`,
+ columnGap: gap,
+ alignItems: "start",
+ },
+ columnGrid: {
+ display: "grid",
+ gridTemplateColumns: "100%",
+ rowGap: gap,
+ },
+ };
+
+ return (
+
+ {columns.map((column, index) => {
+ return (
+
+ {column}
+
+ );
+ })}
+
+ );
+ }
+);
diff --git a/src/components/Plock.test.js b/src/components/Plock.test.js
index da2ab53..1b4ee2d 100644
--- a/src/components/Plock.test.js
+++ b/src/components/Plock.test.js
@@ -1,5 +1,5 @@
import matchMediaPolyfill from "mq-polyfill";
-import { render, screen } from "@testing-library/react";
+import { render, screen, act } from "@testing-library/react";
import { Plock } from "./Plock";
beforeAll(() => {
@@ -148,7 +148,9 @@ it("should render one column with a 500px window", () => {
{ size: 1280, columns: 6 },
];
- window.resizeTo(500, 1000);
+ act(() => {
+ window.resizeTo(500, 1000);
+ });
render(
@@ -168,7 +170,9 @@ it("should render one column with a 639px window", () => {
{ size: 1280, columns: 6 },
];
- window.resizeTo(639, 1000);
+ act(() => {
+ window.resizeTo(639, 1000);
+ });
render(
@@ -188,7 +192,9 @@ it("should render two columns with a 768px window", () => {
{ size: 1280, columns: 6 },
];
- window.resizeTo(768, 1000);
+ act(() => {
+ window.resizeTo(768, 1000);
+ });
render(
@@ -208,7 +214,9 @@ it("should render two columns with a 800px window", () => {
{ size: 1280, columns: 6 },
];
- window.resizeTo(800, 1000);
+ act(() => {
+ window.resizeTo(800, 1000);
+ });
render(
@@ -228,7 +236,9 @@ it("should render two columns with a 1023px window", () => {
{ size: 1280, columns: 6 },
];
- window.resizeTo(1023, 1000);
+ act(() => {
+ window.resizeTo(1023, 1000);
+ });
render(
@@ -248,7 +258,9 @@ it("should render three columns with a 1024px window", () => {
{ size: 1280, columns: 6 },
];
- window.resizeTo(1024, 1000);
+ act(() => {
+ window.resizeTo(1024, 1000);
+ });
render(
@@ -268,7 +280,9 @@ it("should render six columns with a 1280px window", () => {
{ size: 1280, columns: 6 },
];
- window.resizeTo(1280, 1000);
+ act(() => {
+ window.resizeTo(1280, 1000);
+ });
render(
@@ -287,15 +301,47 @@ it("should keep the default number of columns if a number is passed", () => {
);
- window.resizeTo(100, 1000);
+ act(() => {
+ window.resizeTo(100, 1000);
+ });
expect(screen.getAllByTestId("plock-column")).toHaveLength(3);
- window.resizeTo(500, 1000);
+ act(() => {
+ window.resizeTo(500, 1000);
+ });
expect(screen.getAllByTestId("plock-column")).toHaveLength(3);
- window.resizeTo(1000, 1000);
+ act(() => {
+ window.resizeTo(1000, 1000);
+ });
expect(screen.getAllByTestId("plock-column")).toHaveLength(3);
- window.resizeTo(2000, 1000);
+ act(() => {
+ window.resizeTo(2000, 1000);
+ });
expect(screen.getAllByTestId("plock-column")).toHaveLength(3);
});
+
+it("should be render a div as a container by default", () => {
+ render();
+
+ const element = screen.getByTestId("plock-container");
+ expect(element).toBeInTheDocument();
+ expect(element.tagName).toEqual("DIV");
+});
+
+it("should be possible to override the container", () => {
+ render();
+
+ const element = screen.getByTestId("plock-container");
+ expect(element).toBeInTheDocument();
+ expect(element.tagName).toEqual("SECTION");
+});
+
+it("should have not the component class if i override it", () => {
+ const Component = (props) => ;
+ render();
+
+ const element = screen.getByTestId("plock-container");
+ expect(element).not.toHaveClass("sku");
+});
diff --git a/src/index.js b/src/index.js
index 8d6d2b6..0f3ceb0 100644
--- a/src/index.js
+++ b/src/index.js
@@ -8,10 +8,11 @@ function App() {
{ size: 768, columns: 2 },
{ size: 1024, columns: 3 },
{ size: 1280, columns: 6 },
+ { size: 1680, columns: 8 },
];
return (
-
+
1
2
3