Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

DOP-5124: reconstruct table data using tanstack tables #1316

Merged
merged 12 commits into from
Dec 5, 2024
172 changes: 113 additions & 59 deletions src/components/ListTable.js
Original file line number Diff line number Diff line change
@@ -1,10 +1,18 @@
import React, { useMemo } from 'react';
import React, { useMemo, useRef, useState } from 'react';
import PropTypes from 'prop-types';
import { Cell, HeaderCell, HeaderRow, Row, Table, TableBody, TableHead } from '@leafygreen-ui/table';
import {
Cell,
HeaderCell,
flexRender,
HeaderRow,
Row,
Table,
TableBody,
TableHead,
useLeafyGreenTable,
} from '@leafygreen-ui/table';
import { palette } from '@leafygreen-ui/palette';
import { css, cx } from '@leafygreen-ui/emotion';
import { useDarkMode } from '@leafygreen-ui/leafygreen-provider';
import { Theme } from '@leafygreen-ui/lib';
import { theme } from '../theme/docsTheme';
import { AncestorComponentContextProvider, useAncestorComponentContext } from '../context/ancestor-components-context';
import ComponentFactory from './ComponentFactory';
Expand Down Expand Up @@ -62,6 +70,14 @@ const baseCellStyle = css`
const bodyCellStyle = css`
overflow-wrap: anywhere;
word-break: break-word;
align-content: flex-start;

& > div {
min-height: unset;
max-height: unset;
flex-direction: column;
align-items: flex-start;
}

*,
p,
Expand All @@ -70,13 +86,14 @@ const bodyCellStyle = css`
}

// Target any nested components (paragraphs, admonitions, tables) and any paragraphs within those nested components
& > div > div > *,
& > div > *,
& > div > div p {
margin: 0 0 12px;
}

// Prevent extra margin below last element (such as when multiple paragraphs are present)
& > div > div > *:last-child {
& > div > div *:last-child,
& > div > *:last-child {
margin-bottom: 0;
}
`;
Expand All @@ -85,6 +102,7 @@ const headerCellStyle = css`
line-height: 24px;
font-weight: 600;
font-size: ${theme.fontSize.small};
width: auto;
`;

const stubCellStyle = css`
Expand Down Expand Up @@ -159,43 +177,69 @@ const includesNestedTable = (rows) => {
return rows.some((row) => checkNodeForTable(row));
};

const ListTableRow = ({ row = [], stubColumnCount, siteTheme, className, ...rest }) => (
<Row className={className}>
{row.map((cell, colIndex) => {
const skipPTag = hasOneChild(cell.children);
const contents = cell.children.map((child, i) => (
<ComponentFactory {...rest} key={`${colIndex}-${i}`} nodeData={child} skipPTag={skipPTag} />
));

const isStub = colIndex <= stubColumnCount - 1;
const role = isStub ? 'rowheader' : null;

return (
<Cell key={colIndex} className={cx(baseCellStyle, bodyCellStyle, isStub && stubCellStyle)} role={role}>
{/* Wrap in div to ensure contents are structured properly */}
<div>{contents}</div>
</Cell>
const generateColumns = (headerRow, bodyRows) => {
if (!headerRow?.children) {
// generate columns from bodyRows
const flattenedRows = bodyRows.map((bodyRow) => bodyRow.children[0].children);
const maxColumns = Math.max(...flattenedRows.map((row) => row.length));
const res = [];
seungpark marked this conversation as resolved.
Show resolved Hide resolved
for (let colIndex = 0; colIndex < maxColumns; colIndex++) {
res.push({
id: `column-${colIndex}`,
accessorKey: `column-${colIndex}`,
header: '',
});
}
return res;
}

return headerRow.children.map((listItemNode, index) => {
const skipPTag = hasOneChild(listItemNode.children);
return {
id: `column-${index}`,
accessorKey: `column-${index}`,
header: (
<>
{listItemNode.children.map((childNode) => (
<ComponentFactory nodeData={childNode} skipPTag={skipPTag} />
))}
</>
),
};
});
};

const generateRowsData = (bodyRowNodes, columns) => {
const rowNodes = bodyRowNodes.map((node) => node?.children[0]?.children ?? []);
const rows = rowNodes.map((rowNode) => {
return rowNode.reduce((res, columnNode, colIndex) => {
res[columns[colIndex].accessorKey] = (
<>
{columnNode.children.map((cellNode) => (
<ComponentFactory nodeData={cellNode} />
))}
</>
);
})}
</Row>
);

ListTableRow.propTypes = {
row: PropTypes.arrayOf(PropTypes.object),
stubColumnCount: PropTypes.number.isRequired,
siteTheme: PropTypes.oneOf(Object.values(Theme)).isRequired,
return res;
}, {});
});

return rows;
};

const ListTable = ({ nodeData: { children, options }, ...rest }) => {
const ancestors = useAncestorComponentContext();
const { theme: siteTheme } = useDarkMode();
// TODO: header row count should not be more than 1
// need a warning in parser
const headerRowCount = parseInt(options?.['header-rows'], 10) || 0;
seungpark marked this conversation as resolved.
Show resolved Hide resolved
const stubColumnCount = parseInt(options?.['stub-columns'], 10) || 0;
const bodyRows = children[0].children.slice(headerRowCount);
const columnCount = bodyRows[0].children[0].children.length;

// Check if :header-rows: 0 is specified or :header-rows: is omitted
const headerRows = headerRowCount > 0 ? children[0].children[0].children.slice(0, headerRowCount) : [];
const [headerRows] = useState(() =>
headerRowCount > 0 ? children[0].children[0].children.slice(0, headerRowCount) : []
seungpark marked this conversation as resolved.
Show resolved Hide resolved
);

let widths = null;
const customWidths = options?.widths;
Expand All @@ -215,6 +259,16 @@ const ListTable = ({ nodeData: { children, options }, ...rest }) => {
const noTableNesting = !hasNestedTable && !ancestors?.table;
const shouldAlternateRowColor = noTableNesting && bodyRows.length > 4;

const tableRef = useRef();
const [columns] = useState(() => generateColumns(headerRows[0], bodyRows));
const [data] = useState(() => generateRowsData(bodyRows, columns));
const table = useLeafyGreenTable({
containerRef: tableRef,
columns: columns,
data: data,
});
const { rows } = table.getRowModel();

return (
<AncestorComponentContextProvider component={'table'}>
{elmIdsForScroll.map((id) => (
Expand All @@ -227,6 +281,7 @@ const ListTable = ({ nodeData: { children, options }, ...rest }) => {
customWidth: options?.width,
})
)}
ref={tableRef}
shouldAlternateRowColor={shouldAlternateRowColor}
>
{widths && (
Expand All @@ -237,37 +292,36 @@ const ListTable = ({ nodeData: { children, options }, ...rest }) => {
</colgroup>
)}
<TableHead className={cx(theadStyle)}>
{headerRows.map((row, rowIndex) => (
<HeaderRow key={rowIndex} data-testid="leafygreen-ui-header-row">
{row.children.map((cell, colIndex) => {
const skipPTag = hasOneChild(cell.children);
{headerRowCount > 0 &&
table.getHeaderGroups().map((headerGroup) => (
<HeaderRow key={headerGroup.id} data-testid="leafygreen-ui-header-row">
{headerGroup.headers.map((header) => {
return (
<HeaderCell className={cx(baseCellStyle, headerCellStyle)} key={header.id} header={header}>
{flexRender(header.column.columnDef.header, header.getContext())}
</HeaderCell>
);
})}
</HeaderRow>
))}
</TableHead>
<TableBody>
{rows.map((row) => (
<Row key={row.id} row={row} className={cx(shouldAlternateRowColor && zebraStripingStyle)}>
{row.getVisibleCells().map((cell, colIndex) => {
const isStub = colIndex <= stubColumnCount - 1;
const role = isStub ? 'rowheader' : null;
return (
<HeaderCell
className={cx(baseCellStyle, headerCellStyle)}
key={`${rowIndex}-${colIndex}`}
role="columnheader"
<Cell
key={cell.id}
className={cx(baseCellStyle, bodyCellStyle, 'body-cell', isStub && stubCellStyle)}
role={role}
>
<div>
{cell.children.map((child, i) => (
<ComponentFactory {...rest} key={i} nodeData={child} skipPTag={skipPTag} />
))}
</div>
</HeaderCell>
{cell.renderValue()}
</Cell>
);
})}
</HeaderRow>
))}
</TableHead>
<TableBody>
{bodyRows.map((row, i) => (
<ListTableRow
key={i}
{...rest}
stubColumnCount={stubColumnCount}
row={row.children?.[0]?.children}
siteTheme={siteTheme}
className={shouldAlternateRowColor && zebraStripingStyle}
/>
</Row>
))}
</TableBody>
</Table>
Expand Down
Loading
Loading