Skip to content

Commit

Permalink
Merge pull request #29 from WeAreNova/28-pinned-grouped-header-column…
Browse files Browse the repository at this point in the history
…s-overlapping-issue

fix: overlapping issue fixed for pinned group headers
  • Loading branch information
noumannawazfusiontech authored Aug 4, 2023
2 parents b4af508 + 1288ebc commit 481265c
Show file tree
Hide file tree
Showing 7 changed files with 221 additions and 74 deletions.
1 change: 1 addition & 0 deletions .github/workflows/code-scan.yml
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
name: Code Scan
on:
workflow_dispatch:
push:
branches:
- main
Expand Down
75 changes: 62 additions & 13 deletions lib/__tests__/app.test.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import MomentUtils from "@date-io/moment";
import { MuiPickersUtilsProvider } from "@material-ui/pickers";
import { render, RenderResult } from "@testing-library/react";
import { fireEvent, render, RenderResult } from "@testing-library/react";
import React from "react";
import DataTable, { ColumnDefinition } from "../src";
import { getRowId } from "../src/utils";
Expand All @@ -10,22 +10,40 @@ const DATA = Array(30)
.map((_, i) => ({
id: `id_${i}`,
name: `Name ${i}`,
firstName: `FirstName ${i}`,
lastName: `Name ${i}`,
registrationDate: new Date(2020, 0, i),
}));

const STRUCTURE = [
{
key: "id",
title: "ID",
dataIndex: "id",
dataIndex: "id"
},
{
key: "name",
title: "User's Name",
dataIndex: "name",
dataType: "string",
filterColumn: true,
sorter: true,
pinnable: true,
footer: (data) => `total: ${data.length}`,
colGroup: [
{
key: "firstName",
title: "first name",
dataIndex: "firstName",
sorter: true,
filterColumn: true,
pinnable: true,
},
{
key: "lastName",
title: "lastName",
dataIndex: "lastName",
sorter: true,
filterColumn: true,
pinnable: true,
}
]
},
{
key: "registrationDate",
Expand All @@ -34,6 +52,7 @@ const STRUCTURE = [
render: (record) => record.registrationDate.toLocaleDateString(),
filterColumn: true,
sorter: true,
pinnable: true,
},
] as ColumnDefinition<typeof DATA[number]>[];

Expand Down Expand Up @@ -63,18 +82,48 @@ it("should render 25 rows", async function () {
});

it("should render row cells correctly", async function () {
const renderCell = (rowData: any, column: any, index: number) => column.render
? column.render(rowData, false, getRowId(rowData, index), index) as string
: rowData[column.dataIndex as keyof typeof rowData] as string;

const assertCellRenderedCorrectly = (rowData: any, column: any, index: number) => {
const rendered = renderCell(rowData, column, index);
const cell = component.getByText(rendered);
expect(cell).toBeTruthy();
expect(rows[index].contains(cell)).toBe(true);
};

const rows = component.getAllByTestId("tableRow");
const cells = component.getAllByTestId("DataTable-BodyCell");
expect(cells).toHaveLength(25 * STRUCTURE.length);
let columns = 0;
STRUCTURE.forEach(struct => columns += (struct.colGroup ? struct.colGroup?.length : 1));
expect(cells).toHaveLength(25 * columns);
for (let i = 0; i < rows.length; i++) {
const rowData = DATA[i];
STRUCTURE.forEach((column) => {
const rendered = column.render
? (column.render(rowData, false, getRowId(rowData, i), i) as string)
: (rowData[column.dataIndex as keyof typeof rowData] as string);
const cell = component.getByText(rendered);
expect(cell).toBeTruthy();
expect(rows[i].contains(cell)).toBe(true);
if (column.colGroup) {
column.colGroup.forEach(subColumn => {
assertCellRenderedCorrectly(rowData, subColumn, i);
})
} else {
assertCellRenderedCorrectly(rowData, column, i);

}
});
}
});

it("should pin and unpin columns", async function () {
// Check if columns can be pinned
const pinnableColumns = STRUCTURE.filter((column) => column.pinnable);
pinnableColumns.forEach((column) => {
const columnHeader = component.getByTestId(`${column.key}`);
const pinButton = columnHeader.querySelector(".MuiIconButton-root");
expect(pinButton).toBeTruthy(); // Pin button should exist
if (pinButton) {
fireEvent.click(pinButton); // Simulate a click on the pin button
expect(pinButton.classList.contains("MuiIconButton-colorPrimary")).toBe(true); // Check if the class is applied after the click
}
});
});

8 changes: 6 additions & 2 deletions lib/src/Header/HeaderCell.component.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -145,7 +145,7 @@ const HeaderCell = <RowType extends BaseData, AllDataType extends RowType[] = Ro
...props
}: PropsWithChildren<HeaderCellProps<RowType, AllDataType>>) => {
const classes = useStyles(props);
const { activeFilters, sort, enableHiddenColumns, hiddenColumns, pinnedColumns, allTableData, update, resizeable } =
const { activeFilters, sort, enableHiddenColumns, hiddenColumns, pinnedColumns, allTableData, update, resizeable, headerCellsSiblingsMap } =
useTableContext<RowType, AllDataType>();
const tableCellRef = useRef<HTMLTableCellElement>(null);

Expand Down Expand Up @@ -222,6 +222,7 @@ const HeaderCell = <RowType extends BaseData, AllDataType extends RowType[] = Ro
[className, classes.stickyColGroup, classes.root, colGroupHeader, isPinned],
);

const index = useMemo(() => headerCellsSiblingsMap[id].index, [headerCellsSiblingsMap, id]);
const handleFilterClick = useCallback<MouseEventHandler<HTMLButtonElement>>(
(e) => {
if (!filterPath) return;
Expand All @@ -243,6 +244,8 @@ const HeaderCell = <RowType extends BaseData, AllDataType extends RowType[] = Ro
<Fragment key={id}>
<Tooltip title={isHidden ? `Unhide '${structure.title}' Column` : ""} placement="top">
<TableCell
id={id}
data-testid={id}
onClick={handleUnhide}
ref={tableCellRef}
hidden={Boolean(isHidden)}
Expand All @@ -251,7 +254,8 @@ const HeaderCell = <RowType extends BaseData, AllDataType extends RowType[] = Ro
rowSpan={rowSpan}
align={structure.align}
className={headerClasses}
style={style}
style={{ ...style, zIndex: isPinned ? index?.pinned : index?.default }}
type={"Header"}
>
<div className={classes.resizeContainer}>
<div
Expand Down
37 changes: 29 additions & 8 deletions lib/src/TableCell.component.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,15 @@ import clsx from "clsx";
import useTableContext from "hooks/useTableContext.hook";
import PropTypes from "prop-types";
import React, { useCallback, useEffect, useMemo, useRef } from "react";
import { CellType } from "table.types";

interface Props extends TableCellProps {
className?: string;
hidden?: boolean;
pinned?: boolean;
maxWidth?: "lg" | "sm";
type?: CellType
customKey?: string;
}

const useStyles = makeStyles(
Expand Down Expand Up @@ -79,11 +82,11 @@ const useStyles = makeStyles(
* @component
*/
const TableCell = React.forwardRef<HTMLTableCellElement, Props>(function _TableCell(
{ hidden = false, pinned = false, maxWidth, className, ...props }: Props,
{ hidden = false, pinned = false, maxWidth, className, type, customKey, ...props }: Props,
forwardedRef,
) {
const cellRef = useRef<HTMLTableCellElement>();
const { resizeable, pinnedColumns } = useTableContext();
const { resizeable, pinnedColumns, headerCellsSiblingsMap } = useTableContext();
const classes = useStyles(props);
const cellClasses = useMemo(
() =>
Expand Down Expand Up @@ -122,27 +125,45 @@ const TableCell = React.forwardRef<HTMLTableCellElement, Props>(function _TableC
);

const getPinnedOffset = useCallback(
(cell: HTMLTableCellElement, offset: "left" | "right") => {
(cell: HTMLTableCellElement, offset: "left" | "right", useMap?: boolean) => {
const getNextCell = (c: HTMLTableCellElement) => {
return (offset === "right" ? c.nextElementSibling : c.previousElementSibling) as HTMLTableCellElement | null;
};

const getNextCellFromMap = (c: HTMLTableCellElement) => {
return (offset === "right" ? document.getElementById(headerCellsSiblingsMap[c.id].rightSibling) : document.getElementById(headerCellsSiblingsMap[c.id].leftSibling)) as HTMLTableCellElement | null;
};

let totalOffset = 0;
for (let nextCell = getNextCell(cell); nextCell; nextCell = getNextCell(nextCell)) {
for (let nextCell = useMap ? getNextCellFromMap(cell) : getNextCell(cell); nextCell; nextCell = useMap ? getNextCellFromMap(nextCell) : getNextCell(nextCell)) {
if (nextCell.classList.contains(classes.pinned)) totalOffset += nextCell.offsetWidth;
}
return `${totalOffset}px`;
},
[classes.pinned],
[classes.pinned, headerCellsSiblingsMap],
);

useEffect(() => {
if (!pinned || typeof cellRef === "function" || !cellRef?.current) return;
const cell = cellRef.current;
cell.style.right = getPinnedOffset(cell, "right");
cell.style.left = getPinnedOffset(cell, "left");

if (type === "Header") {
cell.style.right = getPinnedOffset(cell, "right", true);
cell.style.left = getPinnedOffset(cell, "left", true);
} else if (type === "Footer" && customKey) {
//finding pinned offset for footer with colgroup is problematic, because of multiple footer rows, and having empty footer columns, so we will find footer offset based on the header
const headerCell = document.getElementById(customKey);
if (headerCell && headerCell instanceof HTMLTableCellElement) {
cell.style.right = getPinnedOffset(headerCell, "right", true);
cell.style.left = getPinnedOffset(headerCell, "left", true);
}
} else {
cell.style.right = getPinnedOffset(cell, "right");
cell.style.left = getPinnedOffset(cell, "left");
}
cell.classList.add(classes.pinnedStyling);
}, [classes.pinned, classes.pinnedStyling, getPinnedOffset, pinned, pinnedColumns]);

}, [classes.pinned, classes.pinnedStyling, customKey, getPinnedOffset, pinned, pinnedColumns, type]);
useEffect(
() => () => {
if (!cellRef.current) return;
Expand Down
8 changes: 6 additions & 2 deletions lib/src/_Table.component.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ interface _TableProps<RowType extends BaseData, AllDataType extends RowType[]>
extends Pick<
TableProps<RowType, AllDataType>,
"tableProps" | "rowsPerPageOptions" | "exportToCSVOption" | "disablePagination"
> {}
> { }

const useStyles = makeStyles(
(theme) =>
Expand Down Expand Up @@ -114,6 +114,7 @@ const _Table = <RowType extends BaseData, AllDataType extends RowType[]>({
count,
isMacOS,
resizeable,
pinnedColumns
} = useTableContext<RowType, AllDataType>();

const allColumnsVisible = useMemo(() => {
Expand Down Expand Up @@ -201,7 +202,10 @@ const _Table = <RowType extends BaseData, AllDataType extends RowType[]>({
{structure.flattened.map((struct) => (
<TableCell
key={struct.key}
customKey={struct.key}
rowSpan={hasColGroupFooter && (!struct.isColGroup || !struct.hasColGroupFooter) ? 2 : 1}
pinned={Boolean(pinnedColumns[struct.key])}
type={"Footer"}
>
{struct.footer ? struct.footer(allTableData) : ""}
</TableCell>
Expand All @@ -212,7 +216,7 @@ const _Table = <RowType extends BaseData, AllDataType extends RowType[]>({
<TableRow>
{structure.map(({ colGroup, footer, ...struct }) =>
colGroup && footer ? (
<TableCell key={struct.key} colSpan={colGroup.length} align="center">
<TableCell key={struct.key} customKey={struct.key} colSpan={colGroup.length} align="center" pinned={Boolean(pinnedColumns[struct.key])} type={"Footer"} >
{footer(allTableData)}
</TableCell>
) : null,
Expand Down
Loading

0 comments on commit 481265c

Please sign in to comment.