diff --git a/src/components/ListTable.js b/src/components/ListTable.js index 75109f624..e967e906e 100644 --- a/src/components/ListTable.js +++ b/src/components/ListTable.js @@ -1,10 +1,18 @@ -import React, { useMemo } from 'react'; +import React, { useMemo, useRef } 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'; @@ -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, @@ -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; } `; @@ -85,6 +102,7 @@ const headerCellStyle = css` line-height: 24px; font-weight: 600; font-size: ${theme.fontSize.small}; + width: auto; `; const stubCellStyle = css` @@ -159,43 +177,92 @@ const includesNestedTable = (rows) => { return rows.some((row) => checkNodeForTable(row)); }; -const ListTableRow = ({ row = [], stubColumnCount, siteTheme, className, ...rest }) => ( - - {row.map((cell, colIndex) => { - const skipPTag = hasOneChild(cell.children); - const contents = cell.children.map((child, i) => ( - - )); - - const isStub = colIndex <= stubColumnCount - 1; - const role = isStub ? 'rowheader' : null; - - return ( - - {/* Wrap in div to ensure contents are structured properly */} -
{contents}
-
+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 = []; + 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, index) => ( + + ))} + + ), + }; + }); +}; + +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, index) => ( + + ))} + ); - })} -
-); - -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(); - const headerRowCount = parseInt(options?.['header-rows'], 10) || 0; const stubColumnCount = parseInt(options?.['stub-columns'], 10) || 0; - const bodyRows = children[0].children.slice(headerRowCount); - const columnCount = bodyRows[0].children[0].children.length; + const headerRowCount = parseInt(options?.['header-rows'], 10) || 0; // 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 = useMemo(() => { + const MAX_HEADER_ROW = 1; + return headerRowCount > 0 + ? children[0].children[0].children.slice(0, Math.min(MAX_HEADER_ROW, headerRowCount)) + : []; + }, [children, headerRowCount]); + + const bodyRows = useMemo(() => { + return children[0].children.slice(headerRowCount); + }, [children, headerRowCount]); + + // get all ID's for elements within header, or first two rows of body + const firstHeaderRowChildren = headerRows[0]?.children ?? []; + const elmIdsForScroll = getReferenceIds(firstHeaderRowChildren.concat(bodyRows.slice(0, 3))); + + const hasNestedTable = useMemo(() => includesNestedTable(bodyRows), [bodyRows]); + const noTableNesting = !hasNestedTable && !ancestors?.table; + const shouldAlternateRowColor = noTableNesting && bodyRows.length > 4; + + const tableRef = useRef(); + const columns = useMemo(() => generateColumns(headerRows[0], bodyRows), [bodyRows, headerRows]); + const data = useMemo(() => generateRowsData(bodyRows, columns), [bodyRows, columns]); + const table = useLeafyGreenTable({ + containerRef: tableRef, + columns: columns, + data: data, + }); + const { rows } = table.getRowModel(); + + const columnCount = columns.length; let widths = null; const customWidths = options?.widths; @@ -207,14 +274,6 @@ const ListTable = ({ nodeData: { children, options }, ...rest }) => { } } - // get all ID's for elements within header, or first two rows of body - const firstHeaderRowChildren = headerRows[0]?.children ?? []; - const elmIdsForScroll = getReferenceIds(firstHeaderRowChildren.concat(bodyRows.slice(0, 3))); - - const hasNestedTable = useMemo(() => includesNestedTable(bodyRows), [bodyRows]); - const noTableNesting = !hasNestedTable && !ancestors?.table; - const shouldAlternateRowColor = noTableNesting && bodyRows.length > 4; - return ( {elmIdsForScroll.map((id) => ( @@ -227,6 +286,7 @@ const ListTable = ({ nodeData: { children, options }, ...rest }) => { customWidth: options?.width, }) )} + ref={tableRef} shouldAlternateRowColor={shouldAlternateRowColor} > {widths && ( @@ -237,37 +297,32 @@ const ListTable = ({ nodeData: { children, options }, ...rest }) => { )} - {headerRows.map((row, rowIndex) => ( - - {row.children.map((cell, colIndex) => { - const skipPTag = hasOneChild(cell.children); + {headerRowCount > 0 && + table.getHeaderGroups().map((headerGroup) => ( + + {headerGroup.headers.map((header) => { + return ( + + {flexRender(header.column.columnDef.header, header.getContext())} + + ); + })} + + ))} + + + {rows.map((row) => ( + + {row.getVisibleCells().map((cell, colIndex) => { + const isStub = colIndex <= stubColumnCount - 1; + const role = isStub ? 'rowheader' : null; return ( - -
- {cell.children.map((child, i) => ( - - ))} -
-
+ + {cell.renderValue()} + ); })} - - ))} - - - {bodyRows.map((row, i) => ( - +
))}
diff --git a/tests/unit/__snapshots__/ListTable.test.js.snap b/tests/unit/__snapshots__/ListTable.test.js.snap index b4a02eee6..361037d02 100644 --- a/tests/unit/__snapshots__/ListTable.test.js.snap +++ b/tests/unit/__snapshots__/ListTable.test.js.snap @@ -35,11 +35,15 @@ exports[`when rendering a list table with fixed widths renders correctly 1`] = ` .emotion-3 { padding: 0 8px; overflow: hidden; + padding-left: 32px; padding: 10px 8px!important; vertical-align: top; color: var(--font-color-primary); overflow-wrap: anywhere; word-break: break-word; + -webkit-align-content: flex-start; + -ms-flex-line-pack: flex-start; + align-content: flex-start; } .emotion-3:focus-visible { @@ -50,10 +54,6 @@ exports[`when rendering a list table with fixed widths renders correctly 1`] = ` padding-right: 24px; } -.emotion-3:first-child { - padding-left: 32px; -} - .emotion-3 * { font-size: 13px!important; line-height: inherit; @@ -64,18 +64,31 @@ exports[`when rendering a list table with fixed widths renders correctly 1`] = ` min-height: unset; } +.emotion-3>div { + min-height: unset; + max-height: unset; + -webkit-flex-direction: column; + -ms-flex-direction: column; + flex-direction: column; + -webkit-align-items: flex-start; + -webkit-box-align: flex-start; + -ms-flex-align: flex-start; + align-items: flex-start; +} + .emotion-3 *, .emotion-3 p, .emotion-3 a { line-height: 20px; } -.emotion-3>div>div>*, +.emotion-3>div>*, .emotion-3>div>div p { margin: 0 0 12px; } -.emotion-3>div>div>*:last-child { +.emotion-3>div>div *:last-child, +.emotion-3>div>*:last-child { margin-bottom: 0; } @@ -92,6 +105,9 @@ exports[`when rendering a list table with fixed widths renders correctly 1`] = ` transition-property: min-height,max-height,opacity,padding,transform; transition-duration: 150ms; transition-timing-function: ease; + opacity: 1; + min-height: 40px; + max-height: 40px; -webkit-box-pack: left; -ms-flex-pack: left; -webkit-justify-content: left; @@ -99,6 +115,82 @@ exports[`when rendering a list table with fixed widths renders correctly 1`] = ` text-align: left; } +.emotion-5 { + margin: unset; + font-family: 'Euclid Circular A','Helvetica Neue',Helvetica,Arial,sans-serif; + color: #001E2B; + font-size: 13px; + line-height: 20px; + color: #001E2B; + font-weight: 400; + margin-bottom: 16px; + color: var(--font-color-primary); +} + +.emotion-5 strong, +.emotion-5 b { + font-weight: 700; +} + +.emotion-6 { + padding: 0 8px; + overflow: hidden; + padding: 10px 8px!important; + vertical-align: top; + color: var(--font-color-primary); + overflow-wrap: anywhere; + word-break: break-word; + -webkit-align-content: flex-start; + -ms-flex-line-pack: flex-start; + align-content: flex-start; +} + +.emotion-6:focus-visible { + box-shadow: inset; +} + +.emotion-6:last-child { + padding-right: 24px; +} + +.emotion-6 * { + font-size: 13px!important; + line-height: inherit; +} + +.emotion-6>div { + height: unset; + min-height: unset; +} + +.emotion-6>div { + min-height: unset; + max-height: unset; + -webkit-flex-direction: column; + -ms-flex-direction: column; + flex-direction: column; + -webkit-align-items: flex-start; + -webkit-box-align: flex-start; + -ms-flex-align: flex-start; + align-items: flex-start; +} + +.emotion-6 *, +.emotion-6 p, +.emotion-6 a { + line-height: 20px; +} + +.emotion-6>div>*, +.emotion-6>div>div p { + margin: 0 0 12px; +} + +.emotion-6>div>div *:last-child, +.emotion-6>div>*:last-child { + margin-bottom: 0; +} +
-
+

notebook -

+

-
+

50 -

+

-
+

8.5x11,in -

+

-
+

A -

+

-
+

college-ruled,perforated -

+

-
+

8 -

+

@@ -242,12 +355,14 @@ exports[`when rendering a list-table directive renders correctly 1`] = ` padding: 0 8px; overflow: hidden; padding-left: 32px; + width: 150px; padding: 10px 8px!important; vertical-align: top; color: var(--font-color-primary); line-height: 24px; font-weight: 600; font-size: 13px; + width: auto; } .emotion-3:focus-visible { @@ -292,12 +407,14 @@ exports[`when rendering a list-table directive renders correctly 1`] = ` .emotion-5 { padding: 0 8px; overflow: hidden; + width: 150px; padding: 10px 8px!important; vertical-align: top; color: var(--font-color-primary); line-height: 24px; font-weight: 600; font-size: 13px; + width: auto; } .emotion-5:focus-visible { @@ -333,11 +450,15 @@ exports[`when rendering a list-table directive renders correctly 1`] = ` .emotion-16 { padding: 0 8px; overflow: hidden; + padding-left: 32px; padding: 10px 8px!important; vertical-align: top; color: var(--font-color-primary); overflow-wrap: anywhere; word-break: break-word; + -webkit-align-content: flex-start; + -ms-flex-line-pack: flex-start; + align-content: flex-start; background-color: #F9FBFA; border-right: 3px solid #E8EDEB; font-weight: 600; @@ -351,10 +472,6 @@ exports[`when rendering a list-table directive renders correctly 1`] = ` padding-right: 24px; } -.emotion-16:first-child { - padding-left: 32px; -} - .emotion-16 * { font-size: 13px!important; line-height: inherit; @@ -365,18 +482,31 @@ exports[`when rendering a list-table directive renders correctly 1`] = ` min-height: unset; } +.emotion-16>div { + min-height: unset; + max-height: unset; + -webkit-flex-direction: column; + -ms-flex-direction: column; + flex-direction: column; + -webkit-align-items: flex-start; + -webkit-box-align: flex-start; + -ms-flex-align: flex-start; + align-items: flex-start; +} + .emotion-16 *, .emotion-16 p, .emotion-16 a { line-height: 20px; } -.emotion-16>div>div>*, +.emotion-16>div>*, .emotion-16>div>div p { margin: 0 0 12px; } -.emotion-16>div>div>*:last-child { +.emotion-16>div>div *:last-child, +.emotion-16>div>*:last-child { margin-bottom: 0; } @@ -398,6 +528,9 @@ exports[`when rendering a list-table directive renders correctly 1`] = ` transition-property: min-height,max-height,opacity,padding,transform; transition-duration: 150ms; transition-timing-function: ease; + opacity: 1; + min-height: 40px; + max-height: 40px; -webkit-box-pack: left; -ms-flex-pack: left; -webkit-justify-content: left; @@ -406,6 +539,23 @@ exports[`when rendering a list-table directive renders correctly 1`] = ` } .emotion-18 { + margin: unset; + font-family: 'Euclid Circular A','Helvetica Neue',Helvetica,Arial,sans-serif; + color: #001E2B; + font-size: 13px; + line-height: 20px; + color: #001E2B; + font-weight: 400; + margin-bottom: 16px; + color: var(--font-color-primary); +} + +.emotion-18 strong, +.emotion-18 b { + font-weight: 700; +} + +.emotion-19 { padding: 0 8px; overflow: hidden; padding: 10px 8px!important; @@ -413,42 +563,54 @@ exports[`when rendering a list-table directive renders correctly 1`] = ` color: var(--font-color-primary); overflow-wrap: anywhere; word-break: break-word; + -webkit-align-content: flex-start; + -ms-flex-line-pack: flex-start; + align-content: flex-start; } -.emotion-18:focus-visible { +.emotion-19:focus-visible { box-shadow: inset; } -.emotion-18:last-child { +.emotion-19:last-child { padding-right: 24px; } -.emotion-18:first-child { - padding-left: 32px; -} - -.emotion-18 * { +.emotion-19 * { font-size: 13px!important; line-height: inherit; } -.emotion-18>div { +.emotion-19>div { height: unset; min-height: unset; } -.emotion-18 *, -.emotion-18 p, -.emotion-18 a { +.emotion-19>div { + min-height: unset; + max-height: unset; + -webkit-flex-direction: column; + -ms-flex-direction: column; + flex-direction: column; + -webkit-align-items: flex-start; + -webkit-box-align: flex-start; + -ms-flex-align: flex-start; + align-items: flex-start; +} + +.emotion-19 *, +.emotion-19 p, +.emotion-19 a { line-height: 20px; } -.emotion-18>div>div>*, -.emotion-18>div>div p { +.emotion-19>div>*, +.emotion-19>div>div p { margin: 0 0 12px; } -.emotion-18>div>div>*:last-child { +.emotion-19>div>div *:last-child, +.emotion-19>div>*:last-child { margin-bottom: 0; } @@ -467,87 +629,72 @@ exports[`when rendering a list-table directive renders correctly 1`] = ` >
-
- name -
+ name
-
- quantity -
+ quantity
-
- size -
+ size
-
- status -
+ status
-
- tags -
+ tags
-
- rating -
+ rating
-
+

journal -

+

-
+

25 -

+

-
+

14x21,cm -

+

-
+

A -

+

-
+

brown, lined -

+

-
+

9 -

+

-
+

notebook -

+

-
+

50 -

+

-
+

8.5x11,in -

+

-
+

A -

+

-
+

college-ruled,perforated -

+

-
+

8 -

+

-
+

paper -

+

-
+

100 -

+

-
+

8.5x11,in -

+

-
+

D -

+

-
+

watercolor -

+

-
+

10 -

+

-
+

planner -

+

-
+

75 -

+

-
+

22.85x30,cm -

+

-
+

D -

+

-
+

2019 -

+

-
+

10 -

+

-
+

postcard -

+

-
+

45 -

+

-
+

10x,cm -

+

-
+

D -

+

-
+

double-sided,white -

+

-
+

2 -

+