Skip to content

Commit

Permalink
feat(utils): added useGridList hook
Browse files Browse the repository at this point in the history
It's helpful to be able to use the grid list API without needing to use
the `GridList` component so this hook was created. This also ended up
updating to the new `useResizeObserver` API as well.
  • Loading branch information
mlaursen committed Sep 1, 2020
1 parent b8f638a commit 56ecc19
Show file tree
Hide file tree
Showing 5 changed files with 357 additions and 171 deletions.
149 changes: 23 additions & 126 deletions packages/utils/src/layout/GridList.tsx
Original file line number Diff line number Diff line change
@@ -1,30 +1,14 @@
import React, {
Children,
forwardRef,
HTMLAttributes,
ReactNode,
useCallback,
useRef,
useState,
} from "react";
import cn from "classnames";
import React, { Children, forwardRef, HTMLAttributes, ReactNode } from "react";

import applyRef from "../applyRef";
import bem from "../bem";
import { useResizeObserver } from "../sizing/useResizeObserver";
import GridListCell from "./GridListCell";
import getScrollbarSize from "./scrollbarSize";
import { GridListSizeProvider, GridListSize } from "./context";

/**
* This is the css variable that is used store the current size of each cell.
*/
export const CELL_SIZE_VAR = "--rmd-cell-size";

/**
* This is the css variable that is used store the current margin of each cell.
*/
export const CELL_MARGIN_VAR = "--rmd-cell-margin";
import {
DEFAULT_GRID_LIST_MAX_CELL_SIZE,
DEFAULT_GRID_LIST_PADDING,
useGridList,
UseGridListOptions,
GridListSize,
GridListSizeProvider,
} from "./useGridList";

/**
* The children render function that will be provided the current grid list size
Expand All @@ -40,47 +24,9 @@ export const CELL_MARGIN_VAR = "--rmd-cell-margin";
*/
export type RenderGridListChildren = (size: GridListSize) => ReactNode;

export interface GridListProps extends HTMLAttributes<HTMLDivElement> {
/**
* An optional margin to apply to each cell as the `CELL_MARGIN_VAR` css
* variable only when it is defined. This has to be a number string with a
* `px`, `em`, `rem` or `%` suffix or else the grid will break.
*/
cellMargin?: string;

/**
* The max size that each cell can be.
*/
maxCellSize?: number;

/**
* Since the `GridList` requires being fully rendered in the DOM to be able to
* correctly calculate the number of `columns` and `cellWidth`, this _might_
* cause problems when server-side rendering when using the children renderer
* to create a grid list dynamically based on the number of columns. If the
* number of columns and default `cellWidth` can be guessed server-side, you
* should provide this prop. Otherwise it will be: `{ cellSize; maxCellSize,
* columns: -1 }`
*/
defaultSize?: GridListSize | (() => GridListSize);

/**
* This is _normally_ the amount of padding on the grid list item itself to
* subtract from the `offsetWidth` since `padding`, `border`, and vertical
* scrollbars will be included. If you add a border or change the padding or
* add borders to this component, you'll need to update the `containerPadding`
* to be the new number.
*/
containerPadding?: number;

/**
* Boolean if the current scrollbar width should no longer be subtracted from
* the total width of the grid list. This should only be disabled if your
* `containerPadding` is updated to include scrollbar width as well since
* it'll mess up the grid on OSes that display scrollbars.
*/
disableScrollbarWidth?: boolean;

export interface GridListProps
extends HTMLAttributes<HTMLDivElement>,
UseGridListOptions {
/**
* Boolean if the resize observer should stop tracking width changes within
* the `GridList`. This should normally stay as `false` since tracking width
Expand Down Expand Up @@ -120,12 +66,6 @@ export interface GridListProps extends HTMLAttributes<HTMLDivElement> {
wrapOnly?: boolean;
}

type CSSProperties = React.CSSProperties & {
[CELL_SIZE_VAR]: string;
[CELL_MARGIN_VAR]?: string;
};

const block = bem("rmd-grid-list");
const isRenderFunction = (
children: GridListProps["children"]
): children is RenderGridListChildren => typeof children === "function";
Expand All @@ -148,63 +88,25 @@ const GridList = forwardRef<HTMLDivElement, GridListProps>(function GridList(
wrapOnly = false,
cellMargin,
defaultSize,
maxCellSize = 150,
containerPadding = 16,
maxCellSize = DEFAULT_GRID_LIST_MAX_CELL_SIZE,
containerPadding = DEFAULT_GRID_LIST_PADDING,
disableHeightObserver = false,
disableWidthObserver = false,
...props
},
forwardedRef
) {
const [gridSize, setGridSize] = useState(
defaultSize || { columns: -1, cellWidth: maxCellSize }
);
const ref = useRef<HTMLDivElement | null>(null);
const recalculate = useCallback(() => {
if (!ref.current) {
return;
}

// need to use rect instead of offsetWidth since we need decimal precision
// for the width since offsetWidth is basically Math.ceil(width). the
// calculations for max columns will be off on high-pixel-density monitors
// or some zoom levels.
let { width } = ref.current.getBoundingClientRect();
width -= containerPadding;

// just need to see if there is a scrollbar visible and subtract that width.
// don't need decimal precision here since both values will be rounded
if (ref.current.offsetHeight < ref.current.scrollHeight) {
width -= getScrollbarSize("width");
}

const columns = Math.ceil(width / maxCellSize);
setGridSize({ cellWidth: width / columns, columns });
}, [maxCellSize, containerPadding]);

const refHandler = useCallback(
(instance: HTMLDivElement | null) => {
applyRef(instance, forwardedRef);
ref.current = instance;

if (instance) {
recalculate();
}
},
[forwardedRef, recalculate]
);

useResizeObserver({
const [gridListProps, gridSize] = useGridList({
ref: forwardedRef,
style,
className,
cellMargin,
defaultSize,
maxCellSize,
containerPadding,
disableHeight: disableHeightObserver,
disableWidth: disableWidthObserver,
onResize: recalculate,
target: ref,
});
const mergedStyle: CSSProperties = {
...style,
[CELL_SIZE_VAR]: `${gridSize.cellWidth}px`,
[CELL_MARGIN_VAR]: cellMargin || undefined,
};

let content: ReactNode = null;
if (isRenderFunction(children)) {
Expand All @@ -220,12 +122,7 @@ const GridList = forwardRef<HTMLDivElement, GridListProps>(function GridList(

return (
<GridListSizeProvider value={gridSize}>
<div
{...props}
ref={refHandler}
style={mergedStyle}
className={cn(block(), className)}
>
<div {...props} {...gridListProps}>
{content}
</div>
</GridListSizeProvider>
Expand Down
Loading

0 comments on commit 56ecc19

Please sign in to comment.