Skip to content

Commit

Permalink
Merge pull request askides#14 from itsrennyman/dev
Browse files Browse the repository at this point in the history
Dev
  • Loading branch information
Renato Pozzi authored Feb 6, 2022
2 parents 35a86bd + 6cc1356 commit 5236924
Show file tree
Hide file tree
Showing 4 changed files with 171 additions and 82 deletions.
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
178 changes: 109 additions & 69 deletions src/components/Plock.js
Original file line number Diff line number Diff line change
@@ -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.
Expand All @@ -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 (
<div
data-testid="plock-container"
className={className}
style={{ style, ...defaultStyles.mainGrid }}
>
{columns.map((column, index) => {
return (
<div
data-testid="plock-column"
style={defaultStyles.columnGrid}
key={index}
>
{column}
</div>
);
})}
</div>
);
}
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 (
<Comp
ref={forwardedRef}
data-testid="plock-container"
className={className}
style={{ style, ...defaultStyles.mainGrid }}
>
{columns.map((column, index) => {
return (
<div
data-testid="plock-column"
style={defaultStyles.columnGrid}
key={index}
>
{column}
</div>
);
})}
</Comp>
);
}
);
70 changes: 58 additions & 12 deletions src/components/Plock.test.js
Original file line number Diff line number Diff line change
@@ -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(() => {
Expand Down Expand Up @@ -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(
<Plock nColumns={breakpoints}>
Expand All @@ -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(
<Plock nColumns={breakpoints}>
Expand All @@ -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(
<Plock nColumns={breakpoints}>
Expand All @@ -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(
<Plock nColumns={breakpoints}>
Expand All @@ -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(
<Plock nColumns={breakpoints}>
Expand All @@ -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(
<Plock nColumns={breakpoints}>
Expand All @@ -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(
<Plock nColumns={breakpoints}>
Expand All @@ -287,15 +301,47 @@ it("should keep the default number of columns if a number is passed", () => {
</Plock>
);

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(<Plock />);

const element = screen.getByTestId("plock-container");
expect(element).toBeInTheDocument();
expect(element.tagName).toEqual("DIV");
});

it("should be possible to override the container", () => {
render(<Plock as="section" />);

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) => <section className="sku" {...props} />;
render(<Plock as={Component} />);

const element = screen.getByTestId("plock-container");
expect(element).not.toHaveClass("sku");
});
3 changes: 2 additions & 1 deletion src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,11 @@ function App() {
{ size: 768, columns: 2 },
{ size: 1024, columns: 3 },
{ size: 1280, columns: 6 },
{ size: 1680, columns: 8 },
];

return (
<Plock gap={20} nColumns={configuration}>
<Plock gap={20} nColumns={configuration} debounce={200}>
<div style={{ height: 100, width: "100%", background: "yellow" }}>1</div>
<div style={{ height: 200, width: "100%", background: "blue" }}>2</div>
<div style={{ height: 150, width: "100%", background: "red" }}>3</div>
Expand Down

0 comments on commit 5236924

Please sign in to comment.