Skip to content

Commit

Permalink
✨ - feat: alow fill-available-space keywoard to be used DataGrid "hei…
Browse files Browse the repository at this point in the history
…ght" prop to automatially set the height
  • Loading branch information
svenvandescheur committed Oct 28, 2024
1 parent 784da98 commit 6f6108d
Show file tree
Hide file tree
Showing 5 changed files with 104 additions and 76 deletions.
18 changes: 5 additions & 13 deletions src/components/data/datagrid/datagrid.scss
Original file line number Diff line number Diff line change
Expand Up @@ -15,20 +15,12 @@

&__scrollpane {
height: 100%;
}

&__scrollpane--overflow-x {
overflow-x: auto;
overflow-y: hidden;
}

&__scrollpane--overflow-y {
overflow-x: hidden;
overflow-y: auto;
}
&__scrollpane--overflow-x#{&}__scrollpane--overflow-y {
overflow-x: auto;
overflow-y: auto;

&__scrollpane--allow-overflow-x {
height: 100%;
overflow: auto;
}

&__scrollpane ~ .mykn-toolbar {
Expand All @@ -53,7 +45,7 @@
}

&__toolbar ~ &__scrollpane:not(&__scrollpane--overflow-y) &__thead {
top: var(--mykn-datagrid-thead-top-base, 50px);
top: var(--mykn-datagrid-thead-top-base, 0px);
}

&__thead &__row--filter {
Expand Down
35 changes: 35 additions & 0 deletions src/components/data/datagrid/datagrid.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -300,6 +300,41 @@ export const DateRangeFilter: Story = {
},
};

export const FillAvailableSpace: Story = {
args: {
objectList: FIXTURE_PRODUCTS,
height: "fill-available-space",
fields: [
"name",
"category",
{ name: "price", type: "number", filterable: false },
{ name: "stock", type: "number", filterable: false },
{ name: "isAvailable", type: "boolean" },
],
filterable: true,
paginatorProps: {
count: 100,
page: 1,
pageSize: 10,
pageSizeOptions: [
{ label: 10 },
{ label: 20 },
{ label: 30 },
{ label: 40 },
{ label: 50 },
],
},
},
argTypes: {
...DataGridComponent.argTypes,
onFilter: { action: "onFilter" },
onSelect: { action: "onSelect" },
onSelectionChange: { action: "onSelectionChange" },
onSelectAllPages: { action: "onSelectAllPages" },
onSort: { action: "onSort" },
},
};

export const JSXAsValue: Story = {
...DataGridComponent,
args: {
Expand Down
58 changes: 41 additions & 17 deletions src/components/data/datagrid/datagrid.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import React, {
CSSProperties,
useCallback,
useEffect,
useId,
Expand Down Expand Up @@ -39,9 +40,7 @@ export type DataGridProps = {

/**
* Whether to allow horizontal contents to exceed the bounding box width
* resulting in a horizontal scrollbar. When this is set to `true` (default) a
* "stickyfix" is applied in certain cases to preserve the sticky behaviour of
* nested components.
* resulting in a horizontal scrollbar.
*/
allowOverflowX?: boolean;

Expand Down Expand Up @@ -73,14 +72,10 @@ export type DataGridProps = {
filterTransform?: (value: AttributeData) => AttributeData;

/**
* This value is copied one-to-one to the style attribute of the rendered
* datagrid.
*
* NOTE: When using `allowOverflowX=true` (default). Setting this disables
* the sticky fix in favor of the native implementation. Even if style is
* overridden completely.
* Can be any valid CSS `height` property or `"fill-available-space"` to
* automatically use the available vertical in the document.
*/
height?: string;
height?: CSSProperties["height"] | "fill-available-space";

/** Whether to allow sorting/the field to sort on. */
sort?: boolean | string;
Expand Down Expand Up @@ -214,17 +209,13 @@ export type DataGridProps = {
onSort?: (sort: string) => Promise<unknown> | void;
} & PaginatorPropsAliases;

export const dataGridRef = React.createRef<HTMLDivElement>();
export const toolbarRef = React.createRef<HTMLDivElement>();
export const scrollPaneRef = React.createRef<HTMLDivElement>();

export type DataGridContextType = Omit<
DataGridProps,
"equalityChecker" | "fields" | "onSelect" | "onSort"
> & {
dataGridRef: React.RefObject<HTMLDivElement>;
toolbarRef: React.RefObject<HTMLDivElement>;
scrollPaneRef: React.RefObject<HTMLDivElement>;

amountSelected: number;
count: number;
Expand Down Expand Up @@ -410,6 +401,41 @@ export const DataGrid: React.FC<DataGridProps> = (props) => {
}
}, [objectList]);

// Sync height
const dataGridRef = useRef<HTMLDivElement>(null);
useEffect(() => {
const fn = () => {
requestAnimationFrame(() => {
const node = dataGridRef.current;
if (!node) return;

// Height should be computed.
if (height === "fill-available-space") {
const rect = node.getBoundingClientRect();
node.style.height = `auto`;

const windowHeight = document.documentElement.clientHeight;
const dataGridOffsetTop = rect.top;
const availableHeight = Math.min(windowHeight - dataGridOffsetTop);
node.style.height = `${availableHeight}px`;
}

// Height is defined.
if (height) {
node.style.height = height as string;
}
});
};
window.addEventListener("resize", fn);
window.addEventListener("scroll", fn);
fn();

return () => {
window.removeEventListener("resize", fn);
window.removeEventListener("scroll", fn);
};
});

// Convert `Array<Field|TypedField>` to `TypedField[]`.
const typedFields = useMemo(
() =>
Expand Down Expand Up @@ -566,9 +592,7 @@ export const DataGrid: React.FC<DataGridProps> = (props) => {
return (
<DataGridContext.Provider
value={{
dataGridRef,
toolbarRef,
scrollPaneRef,

...defaultedProps,
// @ts-expect-error - FIXME, line required due due to story passing undefined.
Expand Down Expand Up @@ -605,7 +629,7 @@ export const DataGrid: React.FC<DataGridProps> = (props) => {
onSelectAllPages: handleSelectAllPages,
}}
>
<div className="mykn-datagrid" style={{ height }} {...attrs}>
<div ref={dataGridRef} className="mykn-datagrid" {...attrs}>
{title && <DataGridHeader />}
{(selectable || fieldsSelectable) && <DataGridToolbar />}

Expand Down
37 changes: 7 additions & 30 deletions src/components/data/datagrid/datagridscrollpane.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import clsx from "clsx";
import React, { useContext, useEffect } from "react";
import React, { useContext } from "react";

import { DataGridContext } from "./datagrid";

Expand All @@ -11,37 +11,14 @@ import { DataGridContext } from "./datagrid";
export const DataGridScrollPane: React.FC<React.PropsWithChildren> = ({
children,
}) => {
const { allowOverflowX, scrollPaneRef } = useContext(DataGridContext);

// Overflow detection
useEffect(() => {
detectOverflowX();
window.addEventListener("resize", detectOverflowX);
window.addEventListener("scroll", detectOverflowX);
() => window.removeEventListener("resize", detectOverflowX);
});

/**
* Toggles "mykn-datagrid__scrollpane--overflow-x" to class list based on
* whether `allowOverflowX=true` and the contents are overflowing.
*/
const detectOverflowX = () => {
if (!scrollPaneRef?.current) {
return;
}
const node = scrollPaneRef.current;

const hasOverflowX = node.scrollWidth > node.clientWidth;
const expX = allowOverflowX && hasOverflowX;
node.classList.toggle("mykn-datagrid__scrollpane--overflow-x", expX);

const hasOverflowY = node.scrollHeight > node.clientHeight;
const expY = hasOverflowY;
node.classList.toggle("mykn-datagrid__scrollpane--overflow-y", expY);
};
const { allowOverflowX } = useContext(DataGridContext);

return (
<div ref={scrollPaneRef} className={clsx("mykn-datagrid__scrollpane")}>
<div
className={clsx("mykn-datagrid__scrollpane", {
"mykn-datagrid__scrollpane--allow-overflow-x": allowOverflowX,
})}
>
{children}
</div>
);
Expand Down
32 changes: 16 additions & 16 deletions src/components/data/datagrid/datagridthead.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import React, { CSSProperties, useContext, useEffect, useRef } from "react";

import { field2Title } from "../../../lib";
import { DataGridContext, scrollPaneRef } from "./datagrid";
import { DataGridContext } from "./datagrid";
import { DataGridFilter } from "./datagridfilter";
import { DataGridHeadingCell } from "./datagridheadingcell";

Expand All @@ -26,40 +26,39 @@ export const DataGridTHead: React.FC = () => {
stickyFix();
window.addEventListener("resize", stickyFix);
window.addEventListener("scroll", stickyFix);
() => {
return () => {
window.removeEventListener("resize", stickyFix);
window.addEventListener("scroll", stickyFix);
};
});
}, [ref.current]);

/**
* Fixes sticky behaviour due to `overflow-x: auto;` not being compatible
* with native sticky in all cases.
*/
const stickyFix = () => {
if (!ref.current || !scrollPaneRef.current) {
if (!ref.current) {
return;
}

const node = ref.current;
const scrollPaneNode = scrollPaneRef.current;
const indicator = "mykn-datagrid__scrollpane--overflow-x";

// No need for fallback implementation, native behaviour should work if height is set of no overflow is applied..
if (height || !scrollPaneNode?.classList?.contains(indicator)) {
node.style.top = "";
return;
}

requestAnimationFrame(() => {
node.style.top = "";
// No need for fallback implementation, native behaviour should work if height is set of no overflow is applied...
if (height) {
return;
}

const computedStyle = getComputedStyle(node);
const cssTop = parseInt(computedStyle.top);

const boundingClientRect = node.getBoundingClientRect();
const boundingTop = boundingClientRect.top;
const compensation = boundingTop * -1 + cssTop * 2;

if (!boundingTop) return;

const compensation = boundingTop * -1 + cssTop * 2;
node.style.top = compensation + "px";
});
};
Expand All @@ -71,9 +70,10 @@ export const DataGridTHead: React.FC = () => {
role="rowgroup"
style={
{
"--mykn-datagrid-thead-top-base": toolbarRef.current?.clientHeight
? toolbarRef.current?.clientHeight + "px"
: undefined,
"--mykn-datagrid-thead-top-base":
!height && toolbarRef.current?.clientHeight
? toolbarRef.current?.clientHeight + "px"
: undefined,
} as unknown as CSSProperties
}
>
Expand Down

0 comments on commit 6f6108d

Please sign in to comment.