diff --git a/packages/table/package.json b/packages/table/package.json index 933c31b15e..325a589a19 100644 --- a/packages/table/package.json +++ b/packages/table/package.json @@ -33,7 +33,8 @@ "@leafygreen-ui/tokens": "^2.9.0", "@leafygreen-ui/typography": "^19.2.0", "@lg-tools/test-harnesses": "^0.1.2", - "@tanstack/react-table": "^8.13.2", + "@tanstack/react-table": "^8.20.5", + "@tanstack/react-virtual": "^3.10.7", "lodash": "^4.17.21", "polished": "^4.2.2", "react-virtual": "^2.10.4" diff --git a/packages/table/src/Cell/Cell.styles.ts b/packages/table/src/Cell/Cell.styles.ts index 4f63e81722..001c9606eb 100644 --- a/packages/table/src/Cell/Cell.styles.ts +++ b/packages/table/src/Cell/Cell.styles.ts @@ -1,4 +1,4 @@ -import { css, cx } from '@leafygreen-ui/emotion'; +import { css } from '@leafygreen-ui/emotion'; import { spacing, typeScales } from '@leafygreen-ui/tokens'; import { Align } from './Cell.types'; @@ -72,11 +72,13 @@ export const basicCellStyles = css` } `; +//TODO: update this export const cellTransitionContainerStyles = css` display: flex; align-items: center; min-height: ${standardCellHeight}px; overflow: hidden; + max-height: ${standardCellHeight}px; `; export const truncatedContentStyles = css` @@ -91,22 +93,3 @@ export const disableAnimationStyles = css` transition-duration: 0; transition: none; `; - -// TODO: remove this -export const cellContentTransitionStateStyles = ( - height?: number, - isVisible = false, -) => { - return cx({ - [css` - opacity: 0; - min-height: 0; - max-height: 0; - `]: !isVisible, - [css` - opacity: 1; - min-height: ${standardCellHeight}px; - max-height: ${height ? height + 'px' : 'unset'}; - `]: isVisible, - }); -}; diff --git a/packages/table/src/Cell/Cell.tsx b/packages/table/src/Cell/Cell.tsx index 4f1e38f9d8..89eb214137 100644 --- a/packages/table/src/Cell/Cell.tsx +++ b/packages/table/src/Cell/Cell.tsx @@ -3,15 +3,15 @@ import React from 'react'; import { cx } from '@leafygreen-ui/emotion'; import { LGIDS } from '../constants'; -import { useTableContext } from '../TableContext'; +import { useRowContext } from '../Row/RowContext'; import { alignmentStyles, baseCellStyles, basicCellStyles, cellTransitionContainerStyles, - disableAnimationStyles, } from './Cell.styles'; +import InternalCell from './InternalCell'; import { CellProps } from '.'; const Cell = ({ @@ -19,26 +19,41 @@ const Cell = ({ contentClassName, align, children, + cell, ...rest }: CellProps) => { - const { disableAnimations } = useTableContext(); + const { isReactTable } = useRowContext(); return ( - -
- {children} -
- + <> + {!isReactTable && ( + +
+ {children} +
+ + )} + + {isReactTable && ( + + {children} + + )} + ); }; diff --git a/packages/table/src/Cell/Cell.types.ts b/packages/table/src/Cell/Cell.types.ts index d1755940f0..6a3e75b80e 100644 --- a/packages/table/src/Cell/Cell.types.ts +++ b/packages/table/src/Cell/Cell.types.ts @@ -39,20 +39,25 @@ interface BaseCellProps extends HTMLElementProps<'td'> { * @default CellOverflowBehavior.Default */ overflow?: CellOverflowBehavior; + + cell?: any; //FIXME: } export type CellProps = BaseCellProps; -export interface InternalCellProps extends BaseCellProps { +export type InternalCellRequiredProps = Omit & + Required>; + +export interface InternalCellProps extends InternalCellRequiredProps { /** * Index of the cell in its parent row. */ - cellIndex: number; + cellIndex?: number; /** * Depth of nesting its parent row has. */ - depth: number; + depth?: number; /** * Defines whether the cell's row is visible (i.e. expanded) diff --git a/packages/table/src/Cell/HeaderCell/HeaderCell.styles.ts b/packages/table/src/Cell/HeaderCell/HeaderCell.styles.ts index 0eb4d9e343..b0b5672499 100644 --- a/packages/table/src/Cell/HeaderCell/HeaderCell.styles.ts +++ b/packages/table/src/Cell/HeaderCell/HeaderCell.styles.ts @@ -1,6 +1,8 @@ import { css } from '@leafygreen-ui/emotion'; import { spacing } from '@leafygreen-ui/tokens'; +import { getCellPadding } from '../Cell.styles'; + export const headerCellContentStyles = css` height: ${spacing[5] + spacing[2]}px; `; @@ -8,3 +10,9 @@ export const headerCellContentStyles = css` export const getHeaderCellWidthStyles = (size: number) => css` width: ${size}px; `; + +export const getCellPaddingStyles = (isSelectable?: boolean) => css` + &:first-of-type { + ${getCellPadding({ depth: 0, isExpandable: false, isSelectable })} + } +`; diff --git a/packages/table/src/Cell/HeaderCell/HeaderCell.tsx b/packages/table/src/Cell/HeaderCell/HeaderCell.tsx index 9616625bda..8545adc9c2 100644 --- a/packages/table/src/Cell/HeaderCell/HeaderCell.tsx +++ b/packages/table/src/Cell/HeaderCell/HeaderCell.tsx @@ -9,11 +9,11 @@ import { alignmentStyles, baseCellStyles, cellTransitionContainerStyles, - getCellPadding, } from '../Cell.styles'; import SortIcon from './SortIcon/SortIcon'; import { + getCellPaddingStyles, getHeaderCellWidthStyles, headerCellContentStyles, } from './HeaderCell.styles'; @@ -35,10 +35,7 @@ const HeaderCell = ({ header, ...rest }: PropsWithChildren>) => { - const { table } = useTableContext(); - - const isFirstCell = cellIndex === 0; - const isSelectable = !!table && !!table.hasSelectableRows; + const { isSelectable } = useTableContext(); let columnName, sortState, onSortIconClick; @@ -54,9 +51,8 @@ const HeaderCell = ({ data-lgid={LGIDS.header} className={cx( baseCellStyles, + getCellPaddingStyles(isSelectable), { - [getCellPadding({ depth: 0, isExpandable: false, isSelectable })]: - isFirstCell, [getHeaderCellWidthStyles(header?.getSize() ?? 0)]: !!header?.getSize(), }, diff --git a/packages/table/src/Cell/InternalCell.tsx b/packages/table/src/Cell/InternalCell.tsx index ccd556d188..6553bc2692 100644 --- a/packages/table/src/Cell/InternalCell.tsx +++ b/packages/table/src/Cell/InternalCell.tsx @@ -1,15 +1,15 @@ import React, { useMemo, useRef } from 'react'; -import PropTypes from 'prop-types'; import { cx } from '@leafygreen-ui/emotion'; import { LGIDS } from '../constants'; +import { useRowContext } from '../Row/RowContext'; import { useTableContext } from '../TableContext'; +import ToggleExpandedIcon from '../ToggleExpandedIcon'; import { alignmentStyles, baseCellStyles, - cellContentTransitionStateStyles, cellTransitionContainerStyles, getCellPadding, standardCellHeight, @@ -21,17 +21,20 @@ const InternalCell = ({ children, className, contentClassName, - cellIndex, - depth, - isVisible = true, - isExpandable = false, overflow, align, + cell, ...rest }: InternalCellProps) => { - const isFirstCell = cellIndex === 0; - const { table } = useTableContext(); - const isSelectable = !!table && !!table.hasSelectableRows; + const { disabled } = useRowContext(); + // TODO: log warning if cell is not passed to Cell + const { isSelectable } = useTableContext(); + const isFirstCell = (cell && cell.column.getIsFirstColumn()) || false; + const row = cell.row; + const isExpandable = row.getCanExpand(); + const isExpanded = row.getIsExpanded(); + const depth = row.depth; + const toggleExpanded = () => row.toggleExpanded(); const contentRef = useRef(null); const contentHeight = standardCellHeight; @@ -43,6 +46,7 @@ const InternalCell = ({ overflow === CellOverflowBehavior.Truncate && scrollHeight > contentHeight ); }, [contentHeight, overflow, scrollHeight]); + return ( + {isFirstCell && isExpandable && ( + + )} {children} @@ -74,11 +84,5 @@ const InternalCell = ({ }; InternalCell.displayName = 'Cell'; -InternalCell.propTypes = { - cellIndex: PropTypes.number, - depth: PropTypes.number, - isVisible: PropTypes.bool, - isExpandable: PropTypes.bool, -}; export default InternalCell; diff --git a/packages/table/src/ExpandedContent/ExpandedContent.styles.ts b/packages/table/src/ExpandedContent/ExpandedContent.styles.ts index 857fd28e61..504b911986 100644 --- a/packages/table/src/ExpandedContent/ExpandedContent.styles.ts +++ b/packages/table/src/ExpandedContent/ExpandedContent.styles.ts @@ -7,6 +7,11 @@ export const baseStyles = css` padding: 0; overflow: hidden; transition: ${transitionDuration.default}ms ease; + + //TODO: this is temp + > div { + max-height: inherit; + } `; export const expandedContentStyles: Record = { diff --git a/packages/table/src/ExpandedContent/ExpandedContent.tsx b/packages/table/src/ExpandedContent/ExpandedContent.tsx index aec5ffec41..b2c0fc4278 100644 --- a/packages/table/src/ExpandedContent/ExpandedContent.tsx +++ b/packages/table/src/ExpandedContent/ExpandedContent.tsx @@ -1,18 +1,13 @@ -import React, { useMemo, useRef } from 'react'; +import React, { useRef } from 'react'; import { RowData } from '@tanstack/react-table'; import { cx } from '@leafygreen-ui/emotion'; import { useDarkMode } from '@leafygreen-ui/leafygreen-provider'; -import { - cellContentTransitionStateStyles, - cellTransitionContainerStyles, - disableAnimationStyles, -} from '../Cell/Cell.styles'; +import { cellTransitionContainerStyles } from '../Cell/Cell.styles'; import { LGIDS } from '../constants'; import InternalRowBase from '../Row/InternalRowBase'; import { useTableContext } from '../TableContext'; -import { getAreAncestorsExpanded } from '../utils/areAncestorsExpanded'; import { baseStyles, expandedContentStyles } from './ExpandedContent.styles'; import { ExpandedContentProps } from './ExpandedContent.types'; @@ -21,27 +16,31 @@ const ExpandedContent = ({ row, ...rest }: ExpandedContentProps) => { - const { disableAnimations, getParentRow } = useTableContext(); + const { measureElement } = useTableContext(); const contentRef = useRef(null); - const areAncestorsExpanded = getAreAncestorsExpanded(row.id, getParentRow); - const isNestedRow = !!getParentRow?.(row.id); - const isExpanded = - row.getIsExpanded() && (!isNestedRow || areAncestorsExpanded); + const content = row.original.renderExpandedContent && row.original.renderExpandedContent(row); const { theme } = useDarkMode(); - const contentHeight = useMemo( - () => (contentRef.current ? contentRef.current.clientHeight : 0), - // Lint flags `content` as an unnecessary dependency, but we want to update `contentHeight` when the value of `content` changes - // eslint-disable-next-line react-hooks/exhaustive-deps - [content], - ); + // const contentHeight = useMemo( + // () => (contentRef.current ? contentRef.current.clientHeight : 0), + // // Lint flags `content` as an unnecessary dependency, but we want to update `contentHeight` when the value of `content` changes + // // eslint-disable-next-line react-hooks/exhaustive-deps + // [content], + // ); return ( - + { + // TODO: fix me + // This gets the dynamic size of the element + if (measureElement) measureElement(node); + }} + > ({
{content}
diff --git a/packages/table/src/Row/HeaderRow/HeaderRow.tsx b/packages/table/src/Row/HeaderRow/HeaderRow.tsx index 25185a0234..afaef5b3ea 100644 --- a/packages/table/src/Row/HeaderRow/HeaderRow.tsx +++ b/packages/table/src/Row/HeaderRow/HeaderRow.tsx @@ -1,6 +1,4 @@ -import React, { PropsWithChildren, ReactElement, ReactNode } from 'react'; - -import { HeaderCell } from '../../Cell'; +import React, { PropsWithChildren } from 'react'; import { HeaderRowProps } from './HeaderRow.types'; @@ -8,15 +6,7 @@ const HeaderRow = ({ children, ...rest }: PropsWithChildren) => { - return ( - - {React.Children.map(children, (child: ReactNode, index: number) => { - return ( - - ); - })} - - ); + return {children}; }; HeaderRow.displayName = 'HeaderRow'; diff --git a/packages/table/src/Row/InternalRowBase.tsx b/packages/table/src/Row/InternalRowBase.tsx index 294f2ac1a1..94e1b820d0 100644 --- a/packages/table/src/Row/InternalRowBase.tsx +++ b/packages/table/src/Row/InternalRowBase.tsx @@ -1,4 +1,4 @@ -import React from 'react'; +import React, { forwardRef } from 'react'; import { cx } from '@leafygreen-ui/emotion'; import { useDarkMode } from '@leafygreen-ui/leafygreen-provider'; @@ -12,29 +12,30 @@ import { useRowContext } from './RowContext'; /** * The base Row component, extended by `InternalRow(With/Without)RT` */ -const InternalRowBase = ({ - className, - onClick, - ...rest -}: InternalRowBaseProps) => { - const { theme } = useDarkMode(); - const { disabled } = useRowContext(); - return ( - - ); -}; +const InternalRowBase = forwardRef( + ({ className, onClick, ...rest }: InternalRowBaseProps, forwardedRef) => { + const { theme } = useDarkMode(); + const { disabled } = useRowContext(); + return ( + + ); + }, +); + +InternalRowBase.displayName = 'InternalRowBase'; export default InternalRowBase; diff --git a/packages/table/src/Row/InternalRowWithRT.tsx b/packages/table/src/Row/InternalRowWithRT.tsx index dead3b986d..6d5e93cae4 100644 --- a/packages/table/src/Row/InternalRowWithRT.tsx +++ b/packages/table/src/Row/InternalRowWithRT.tsx @@ -1,10 +1,7 @@ -import React, { Fragment, useMemo } from 'react'; -import { VirtualItem } from 'react-virtual'; +import React, { useMemo } from 'react'; import { cx } from '@leafygreen-ui/emotion'; import { useDarkMode } from '@leafygreen-ui/leafygreen-provider'; -import { HTMLElementProps } from '@leafygreen-ui/lib'; -import { Polymorph } from '@leafygreen-ui/polymorphic'; import { useTableContext } from '../TableContext'; import { LGRowData } from '../useLeafyGreenTable'; @@ -17,8 +14,7 @@ import { zebraStyles, } from './Row.styles'; import { InternalRowWithRTProps } from './Row.types'; -import RowCellChildren from './RowCellChildren'; -import { useRowContext } from './RowContext'; +import { RowContextProvider } from './RowContext'; /** * Renders row data provided by `useReactTable` @@ -28,66 +24,52 @@ const InternalRowWithRT = ({ className, row, virtualRow, + disabled = false, ...rest }: InternalRowWithRTProps) => { const { theme } = useDarkMode(); - const { disabled } = useRowContext(); - const { table, getParentRow, shouldAlternateRowColor } = useTableContext(); - const parentRow = getParentRow?.(row.id); - const rowRef = virtualRow?.measureRef; - - const isTableExpandable = table?.getCanSomeRowsExpand(); - const isNested = !!parentRow; - const isParentExpanded = !!parentRow && parentRow.getIsExpanded(); - const isRowVisible = isParentExpanded || !isNested; + const { measureElement, shouldAlternateRowColor } = useTableContext(); const isOddVSRow = !!virtualRow && virtualRow.index % 2 !== 0; const isExpanded = row.getIsExpanded(); const isSelected = row.getIsSelected(); + const isParentExpanded = row.getParentRow() + ? row.getParentRow()?.getIsExpanded() + : false; - /** - * Render the row within a `tbody` if - * the table itself has any row that is expandable - * but not if this row is nested - */ - const shouldRenderAsTBody = isTableExpandable && !isNested; - const containerAs = useMemo( - () => (shouldRenderAsTBody ? 'tbody' : Fragment), - [shouldRenderAsTBody], - ); + const contextValues = useMemo(() => { + return { + disabled, + isReactTable: true, + }; + }, [disabled]); - const tBodyProps: HTMLElementProps<'tbody'> & - Pick = { - className: cx({ - [expandedContentParentStyles[theme]]: isExpanded, - }), - 'data-expanded': isExpanded, - // @ts-expect-error - VirtualItem.measureRef is not typed as a ref - ref: rowRef, - }; + // console.log(`🪼rerender: ${row.id} ${depth}`); return ( - - - {children} - - + { + if (measureElement) measureElement(node); // can this be added to table context? + }} + data-index={virtualRow ? virtualRow!.index : ''} + {...rest} + > + {children} + ); }; diff --git a/packages/table/src/Row/Row.tsx b/packages/table/src/Row/Row.tsx index 5502d2c1de..b939b91394 100644 --- a/packages/table/src/Row/Row.tsx +++ b/packages/table/src/Row/Row.tsx @@ -6,7 +6,6 @@ import { LGRowData } from '../useLeafyGreenTable'; import InternalRowWithoutRT from './InternalRowWithoutRT'; import InternalRowWithRT from './InternalRowWithRT'; import { RowProps } from './Row.types'; -import { RowContextProvider } from './RowContext'; /** * Renders the provided cells @@ -14,17 +13,16 @@ import { RowContextProvider } from './RowContext'; const Row = ({ row, virtualRow, - disabled, ...rest }: RowProps) => { return ( - + <> {row ? ( ) : ( )} - + ); }; diff --git a/packages/table/src/Row/Row.types.ts b/packages/table/src/Row/Row.types.ts index 1a3b74600f..7f02fb1178 100644 --- a/packages/table/src/Row/Row.types.ts +++ b/packages/table/src/Row/Row.types.ts @@ -1,4 +1,4 @@ -import { VirtualItem } from 'react-virtual'; +import { VirtualItem } from '@tanstack/react-virtual'; import { HTMLElementProps } from '@leafygreen-ui/lib'; diff --git a/packages/table/src/Row/RowCellChildren.tsx b/packages/table/src/Row/RowCellChildren.tsx index eebf588282..ac377dcaf9 100644 --- a/packages/table/src/Row/RowCellChildren.tsx +++ b/packages/table/src/Row/RowCellChildren.tsx @@ -1,12 +1,12 @@ import React, { ReactElement, ReactNode } from 'react'; import InternalCell from '../Cell/InternalCell'; -import { useTableContext } from '../TableContext'; +// import { useTableContext } from '../TableContext'; import ToggleExpandedIcon from '../ToggleExpandedIcon'; import { LGRowData } from '../useLeafyGreenTable'; -import { getAreAncestorsExpanded } from '../utils/areAncestorsExpanded'; -import { useRowContext } from './RowContext'; +// import { getAreAncestorsExpanded } from '../utils/areAncestorsExpanded'; +// import { useRowContext } from './RowContext'; import { RowProps } from '.'; type RowCellChildrenProps = Required< @@ -21,13 +21,13 @@ const RowCellChildren = ({ row, children: CellChildren, }: RowCellChildrenProps) => { - const { getParentRow } = useTableContext(); - const { disabled } = useRowContext(); - const parentRow = getParentRow?.(row.id); - const isNested = !!parentRow; - const isParentExpanded = !!parentRow && parentRow.getIsExpanded(); - const areAncestorsExpanded = getAreAncestorsExpanded(row.id, getParentRow); - const isRowVisible = (areAncestorsExpanded && isParentExpanded) || !isNested; + // const { getParentRow } = useTableContext(); + // const { disabled } = useRowContext(); + // const parentRow = getParentRow?.(row.id); + // const isNested = !!parentRow; + // const isParentExpanded = !!parentRow && parentRow.getIsExpanded(); + // const areAncestorsExpanded = getAreAncestorsExpanded(row.id, getParentRow); + // const isRowVisible = (areAncestorsExpanded && isParentExpanded) || !isNested; const isExpandable = row.getCanExpand(); const isExpanded = row.getIsExpanded(); @@ -48,9 +48,9 @@ const RowCellChildren = ({ ({ )} {children} diff --git a/packages/table/src/Row/RowContext.tsx b/packages/table/src/Row/RowContext.tsx index 27b39e4c1f..7190ccbda4 100644 --- a/packages/table/src/Row/RowContext.tsx +++ b/packages/table/src/Row/RowContext.tsx @@ -1,21 +1,52 @@ -import React, { createContext, PropsWithChildren, useContext } from 'react'; +import React, { + createContext, + PropsWithChildren, + useContext, + useMemo, +} from 'react'; type RowContextProps = PropsWithChildren<{ - disabled?: boolean; + disabled: boolean; + // depth: number; + // isExpanded: boolean; + // isExpandable: boolean; + // toggleExpanded: () => void; + isReactTable: boolean; }>; -const RowContext = createContext({}); +const RowContext = createContext({ + isReactTable: false, + disabled: false, + // depth: 0, + // isExpandable: false, + // isExpanded: false, + // toggleExpanded: () => {}, +}); export const useRowContext = () => useContext(RowContext); -export const RowContextProvider = ({ children, disabled }: RowContextProps) => { +// export const RowContextProvider = ({ children, disabled }: RowContextProps) => { +export const RowContextProvider = ({ + children, + disabled, + // depth, + // isExpanded, + // isExpandable, + isReactTable, +}: // toggleExpanded, +RowContextProps) => { + const providerData = useMemo(() => { + return { + disabled, + // depth, + // isExpanded, + // isExpandable, + isReactTable, + // toggleExpanded, + }; + }, [disabled, isReactTable]); + return ( - - {children} - + {children} ); }; diff --git a/packages/table/src/Table.stories.tsx b/packages/table/src/Table.stories.tsx index b73f23e92d..fd96da6171 100644 --- a/packages/table/src/Table.stories.tsx +++ b/packages/table/src/Table.stories.tsx @@ -1,4 +1,5 @@ import React, { useState } from 'react'; +import styled from '@emotion/styled'; import { storybookExcludedControlParams, StoryMetaType, @@ -96,7 +97,7 @@ const Template: StoryFn = args => { export const LiveExample: StoryFn = args => { const tableContainerRef = React.useRef(null); - const [data] = useState(() => makeKitchenSinkData(10)); + const [data] = useState(() => makeKitchenSinkData(500)); const columns = React.useMemo>>( () => [ @@ -161,12 +162,12 @@ export const LiveExample: StoryFn = args => { ); const table = useLeafyGreenTable({ - containerRef: tableContainerRef, data, columns, }); - const { rows } = table.getRowModel(); + const { rows } = table; + // const { rows } = table.getRowModel(); return ( = args => { {rows.map((row: LeafyGreenTableRow) => { + const isExpandedContent = row.original.isExpandedContent ?? false; return ( <> - - {row.getVisibleCells().map(cell => { - return ( - - {flexRender( - cell.column.columnDef.cell, - cell.getContext(), - )} - - ); - })} - - {row.subRows && - row.subRows.map(subRow => ( - <> - - {subRow.getVisibleCells().map(cell => { - return ( - - {flexRender( - cell.column.columnDef.cell, - cell.getContext(), - )} - - ); - })} - - {subRow.original.renderExpandedContent && ( - - )} - - ))} + {!isExpandedContent && ( + + {row.getVisibleCells().map(cell => { + return ( + + {flexRender( + cell.column.columnDef.cell, + cell.getContext(), + )} + + ); + })} + + )} + {isExpandedContent && } ); })} @@ -243,14 +233,6 @@ LiveExample.argTypes = { }, }; -export const AnimationsDisabled: StoryFn = args => { - return ; -}; - -AnimationsDisabled.args = { - disableAnimations: true, -}; - export const Basic = Template.bind({}); export const ZebraStripes = Template.bind({}); @@ -258,6 +240,7 @@ ZebraStripes.args = { shouldAlternateRowColor: true, }; +// TODO: i don't think we need this story export const OverflowingCell: StoryFn = args => { const data = makeData(false, 100); const columns = Object.keys(data[0]).filter( @@ -346,19 +329,19 @@ export const NestedRows: StoryFn = args => { ); const table = useLeafyGreenTable({ - containerRef: tableContainerRef, data, columns, }); - const { rows } = table.getRowModel(); + // const { rows } = table.getRowModel(); + const { rows } = table; return (
{table.getHeaderGroups().map((headerGroup: HeaderGroup) => ( @@ -385,7 +368,7 @@ export const NestedRows: StoryFn = args => { .getVisibleCells() .map((cell: LeafyGreenTableCell) => { return ( - + {flexRender( cell.column.columnDef.cell, cell.getContext(), @@ -393,38 +376,6 @@ export const NestedRows: StoryFn = args => { ); })} - {row.subRows && - row.subRows.map(subRow => ( - <> - - {subRow.getVisibleCells().map(cell => { - return ( - - {flexRender( - cell.column.columnDef.cell, - cell.getContext(), - )} - - ); - })} - - {subRow.subRows && - subRow.subRows.map(subSubRow => ( - - {subSubRow.getVisibleCells().map(cell => { - return ( - - {flexRender( - cell.column.columnDef.cell, - cell.getContext(), - )} - - ); - })} - - ))} - - ))} ); @@ -479,19 +430,19 @@ export const ExpandableContent: StoryFn = args => { ); const table = useLeafyGreenTable({ - containerRef: tableContainerRef, data, columns, }); - const { rows } = table.getRowModel(); + // const { rows } = table.getRowModel(); + const { rows } = table; return (
{table.getHeaderGroups().map((headerGroup: HeaderGroup) => ( @@ -511,24 +462,25 @@ export const ExpandableContent: StoryFn = args => { {rows.map((row: LeafyGreenTableRow) => { + const isExpandedContent = row.original.isExpandedContent ?? false; return ( - - {row - .getVisibleCells() - .map((cell: LeafyGreenTableCell) => { - return ( - - {flexRender( - cell.column.columnDef.cell, - cell.getContext(), - )} - - ); - })} - {row.original.renderExpandedContent && ( - + <> + {!isExpandedContent && ( + + {row.getVisibleCells().map(cell => { + return ( + + {flexRender( + cell.column.columnDef.cell, + cell.getContext(), + )} + + ); + })} + )} - + {isExpandedContent && } + ); })} @@ -582,7 +534,6 @@ export const SortableRows: StoryFn = args => { ); const table = useLeafyGreenTable({ - containerRef: tableContainerRef, data, columns, }); @@ -617,7 +568,7 @@ export const SortableRows: StoryFn = args => { {row.getVisibleCells().map(cell => { return ( - + {flexRender(cell.column.columnDef.cell, cell.getContext())} ); @@ -676,7 +627,6 @@ export const SelectableRows: StoryFn = args => { ); const table = useLeafyGreenTable({ - containerRef: tableContainerRef, data, columns, state: { @@ -740,7 +690,7 @@ export const SelectableRows: StoryFn = args => { {row.getVisibleCells().map(cell => { return ( - + {flexRender( cell.column.columnDef.cell, cell.getContext(), @@ -803,7 +753,6 @@ export const SelectableRowsNoSelectAll: StoryFn = args => { ); const table = useLeafyGreenTable({ - containerRef: tableContainerRef, data, columns, state: { @@ -868,7 +817,7 @@ export const SelectableRowsNoSelectAll: StoryFn = args => { {row.getVisibleCells().map(cell => { return ( - + {flexRender( cell.column.columnDef.cell, cell.getContext(), @@ -934,7 +883,6 @@ export const WithPagination: StoryFn = ({ ); const table = useLeafyGreenTable({ - containerRef: tableContainerRef, data, columns, withPagination: true, @@ -979,7 +927,7 @@ export const WithPagination: StoryFn = ({ {row.getVisibleCells().map(cell => { return ( - + {flexRender( cell.column.columnDef.cell, cell.getContext(), @@ -1014,3 +962,158 @@ export const WithPagination: StoryFn = ({ ); }; + +export const StyledComponents: StoryFn = args => { + const tableContainerRef = React.useRef(null); + const [data] = useState(() => makeKitchenSinkData(5)); + + const columns = React.useMemo>>( + () => [ + { + accessorKey: 'dateCreated', + header: 'Date Created', + enableSorting: true, + cell: info => + (info.getValue() as Date).toLocaleDateString('en-us', { + year: 'numeric', + month: 'short', + day: 'numeric', + }), + }, + { + accessorKey: 'frequency', + header: 'Frequency', + }, + { + accessorKey: 'clusterType', + header: 'Cluster Type', + }, + { + accessorKey: 'encryptorEnabled', + header: 'Encryptor', + // eslint-disable-next-line react/display-name + cell: info => ( + + {info.getValue() ? 'Enabled' : 'Not enabled'} + + ), + }, + { + accessorKey: 'mdbVersion', + header: 'MongoDB Version', + enableSorting: true, + size: 90, + }, + { + id: 'actions', + header: '', + size: 90, + // eslint-disable-next-line react/display-name + cell: _ => { + return ( + <> + + + + + + + + + + + ); + }, + }, + ], + [], + ); + + const table = useLeafyGreenTable({ + data, + columns, + }); + + const { rows } = table; + + const StyledCell = styled(Cell)` + color: grey; + `; + + const StyledRow = styled(Row)` + background: snow; + `; + + const StyledHeaderRow = styled(HeaderRow)` + background: whitesmoke; + `; + + const StyledHeaderCell = styled(HeaderCell)` + color: black; + `; + + const StyledExpandedContent = styled(ExpandedContent)` + td > div { + background: whitesmoke; + } + `; + + return ( +
+ + {table.getHeaderGroups().map((headerGroup: HeaderGroup) => ( + + {headerGroup.headers.map(header => { + return ( + + {flexRender( + header.column.columnDef.header, + header.getContext(), + )} + + ); + })} + + ))} + + + {rows.map((row: LeafyGreenTableRow) => { + const isExpandedContent = row.original.isExpandedContent ?? false; + return ( + <> + {!isExpandedContent && ( + + {row.getVisibleCells().map(cell => { + return ( + + {flexRender( + cell.column.columnDef.cell, + cell.getContext(), + )} + + ); + })} + + )} + {isExpandedContent && ( + + )} + + ); + })} + +
+ ); +}; diff --git a/packages/table/src/Table/Table.styles.ts b/packages/table/src/Table/Table.styles.ts index 5efb933f61..4bf242c6f4 100644 --- a/packages/table/src/Table/Table.styles.ts +++ b/packages/table/src/Table/Table.styles.ts @@ -6,6 +6,12 @@ export const baseStyles = css` border-spacing: 0; border-collapse: collapse; width: 100%; + + &:after { + content: ''; + display: block; + height: var(--pseudo-height); + } `; export const themeStyles: Record = { @@ -22,3 +28,29 @@ export const tableContainerStyles = css` width: 100%; position: relative; `; + +export const getVirtualStyles = (isVirtual = false, totalSize: number) => css` + ${isVirtual && + css` + position: relative; + height: ${totalSize}px; + `} +`; + +export const getVirtualDynamicStyles = ( + isVirtual = false, + startPosition: number, +) => css` + ${isVirtual && + css` + position: absolute; + top: 0; + left: 0; + transform: translate3d(0, ${startPosition}px, 0); + width: 100%; + + thead { + top: -${startPosition}px; + } + `} +`; diff --git a/packages/table/src/Table/Table.tsx b/packages/table/src/Table/Table.tsx index 7b94d7f8f6..6ed28d36af 100644 --- a/packages/table/src/Table/Table.tsx +++ b/packages/table/src/Table/Table.tsx @@ -12,8 +12,15 @@ import { import { LGIDS } from '../constants'; import { TableContextProvider } from '../TableContext'; import { LGRowData } from '../useLeafyGreenTable'; +import { LeafyGreenVirtualTable } from '../useLeafyGreenVirtualTable/useLeafyGreenVirtualTable.types'; -import { baseStyles, tableContainerStyles, themeStyles } from './Table.styles'; +import { + baseStyles, + getVirtualDynamicStyles, + getVirtualStyles, + tableContainerStyles, + themeStyles, +} from './Table.styles'; import { TableProps } from './Table.types'; // Inferred generic type from component gets used in place of `any` @@ -26,7 +33,6 @@ const Table = forwardRef>( baseFontSize: baseFontSizeProp, darkMode: darkModeProp, table, - disableAnimations = false, 'data-lgid': lgidProp = LGIDS.root, ...rest }: TableProps, @@ -34,6 +40,21 @@ const Table = forwardRef>( ) => { const baseFontSize: BaseFontSize = useUpdatedBaseFontSize(baseFontSizeProp); const { theme, darkMode } = useDarkMode(darkModeProp); + //TODO: find a better way to do all these checks + const isVirtual = + table && (table as LeafyGreenVirtualTable).virtual ? true : false; + const virtualTable = + isVirtual && (table as LeafyGreenVirtualTable)!.virtual; + const virtualTableTotalSize = virtualTable + ? virtualTable.getTotalSize() + : 0; + const virtualTableStart = virtualTable + ? virtualTable.getVirtualItems()[0]?.start + : 0; + const isSelectable = table && table.hasSelectableRows; + const measureElement = isVirtual + ? (table as LeafyGreenVirtualTable).virtual.measureElement + : undefined; return (
>( // eslint-disable-next-line jsx-a11y/no-noninteractive-tabindex tabIndex={0} > - - +
- {children} -
-
+ + + {children} +
+
+
+
); }, diff --git a/packages/table/src/Table/Table.types.ts b/packages/table/src/Table/Table.types.ts index 5df40c00fa..8a8ad3cf3f 100644 --- a/packages/table/src/Table/Table.types.ts +++ b/packages/table/src/Table/Table.types.ts @@ -2,6 +2,7 @@ import { DarkModeProps, HTMLElementProps, LgIdProps } from '@leafygreen-ui/lib'; import { BaseFontSize } from '@leafygreen-ui/tokens'; import { LeafyGreenTable, LGRowData } from '../useLeafyGreenTable'; +import { LeafyGreenVirtualTable } from '../useLeafyGreenVirtualTable/useLeafyGreenVirtualTable.types'; export interface TableProps extends HTMLElementProps<'table'>, @@ -21,7 +22,7 @@ export interface TableProps /** * The `useLeafyGreenTable` return value */ - table?: LeafyGreenTable; + table?: LeafyGreenTable | LeafyGreenVirtualTable; //TODO: is there a better way to type this? /** * Disables all transition animations for smoother rendering of tall content where appropriate diff --git a/packages/table/src/Table/TableWithVS.stories.tsx b/packages/table/src/Table/TableWithVS.stories.tsx index e35fa044a9..d2a3f283cc 100644 --- a/packages/table/src/Table/TableWithVS.stories.tsx +++ b/packages/table/src/Table/TableWithVS.stories.tsx @@ -16,15 +16,14 @@ import { type HeaderGroup, HeaderRow, type LeafyGreenTableCell, - type LeafyGreenTableRow, + type LeafyGreenVirtualItem, Row, type SortingState, Table, TableBody, TableHead, type TableProps, - useLeafyGreenTable, - type VirtualItem, + useLeafyGreenVirtualTable, } from '..'; type StoryTableProps = TableProps; @@ -51,18 +50,19 @@ export default meta; const virtualScrollingContainerHeight = css` max-height: calc(100vh - 200px); + /* height: calc(100vh - 200px); */ `; const basicColumnDefs: Array> = [ { accessorKey: 'index', header: 'index', - size: 10, + size: 40, }, { accessorKey: 'id', header: 'ID', - size: 45, + size: 60, }, { accessorKey: 'firstName', @@ -95,23 +95,22 @@ const basicColumnDefs: Array> = [ export const Basic: StoryFn = args => { const tableContainerRef = React.useRef(null); const data = React.useMemo(() => makeData(false, 10_000), []); + // const data = React.useMemo(() => makeData(false, 100), []); const columns = useMemo(() => basicColumnDefs, []); - const table = useLeafyGreenTable({ + const table = useLeafyGreenVirtualTable({ containerRef: tableContainerRef, data, columns, - useVirtualScrolling: true, }); - const { rows } = table.getRowModel(); - return ( <>
-

{table.getRowModel().rows.length} total rows

-

{table?.virtualRows?.length} virtual rows

+

{table.rows.length} total rowsjsfjsh

+

{table?.virtual.getVirtualItems().length} virtual rows

+

{table?.virtual.getTotalSize()} virtual rows

= args => { ))} - {table.virtualRows && - table.virtualRows.map((virtualRow: VirtualItem) => { - const row = rows[virtualRow.index]; - const cells = row.getVisibleCells(); - return ( - - {cells.map((cell: LeafyGreenTableCell) => { - return ( - - {flexRender( - cell.column.columnDef.cell, - cell.getContext(), - )} - - ); - })} - - ); - })} + {table.virtual.virtualItems && + table.virtual.virtualItems.map( + (virtualRow: LeafyGreenVirtualItem) => { + const row = virtualRow.row; + const cells = row.getVisibleCells(); + return ( + + {cells.map((cell: LeafyGreenTableCell) => { + return ( + + {flexRender( + cell.column.columnDef.cell, + cell.getContext(), + )} + + ); + })} + + ); + }, + )}
@@ -168,19 +169,16 @@ export const NestedRows: StoryFn = args => { const columns = useMemo(() => basicColumnDefs, []); - const table = useLeafyGreenTable({ + const table = useLeafyGreenVirtualTable({ containerRef: tableContainerRef, data, columns, - useVirtualScrolling: true, }); - const { rows } = table.getRowModel(); - return ( <>
-

{table.getRowModel().rows.length} total rows

+

{table.rows.length} total rows

= args => { ))} - {table.virtualRows && - table.virtualRows.map((virtualRow: VirtualItem) => { - const row = rows[virtualRow.index]; - const cells = row.getVisibleCells(); + {table.virtual.virtualItems && + table.virtual.virtualItems.map( + (virtualRow: LeafyGreenVirtualItem) => { + const row = virtualRow.row; + const cells = row.getVisibleCells(); - return ( - <> - - {cells.map((cell: LeafyGreenTableCell) => { - return ( - - {flexRender( - cell.column.columnDef.cell, - cell.getContext(), - )} - - ); - })} - - {row.subRows && - row.subRows.map((subRow: LeafyGreenTableRow) => ( - <> - - {subRow - .getVisibleCells() - .map((cell: LeafyGreenTableCell) => { - return ( - - {flexRender( - cell.column.columnDef.cell, - cell.getContext(), - )} - - ); - })} - - {subRow.subRows && - subRow.subRows.map(subSubRow => ( - - {subSubRow - .getVisibleCells() - .map((cell: LeafyGreenTableCell) => { - return ( - - {flexRender( - cell.column.columnDef.cell, - cell.getContext(), - )} - - ); - })} - - ))} - - ))} - - ); - })} + return ( + <> + + {cells.map((cell: LeafyGreenTableCell) => { + return ( + + {flexRender( + cell.column.columnDef.cell, + cell.getContext(), + )} + + ); + })} + + + ); + }, + )}
@@ -326,7 +282,7 @@ export const SortableRows: StoryFn = args => { [], ); - const table = useLeafyGreenTable({ + const table = useLeafyGreenVirtualTable({ containerRef: tableContainerRef, data, columns, @@ -334,11 +290,8 @@ export const SortableRows: StoryFn = args => { sorting, }, onSortingChange: setSorting, - useVirtualScrolling: true, }); - const { rows } = table.getRowModel(); - return ( <>
@@ -368,26 +321,28 @@ export const SortableRows: StoryFn = args => { ))} - {table.virtualRows && - table.virtualRows.map((virtualRow: VirtualItem) => { - const row = rows[virtualRow.index]; - const cells = row.getVisibleCells(); - - return ( - - {cells.map((cell: LeafyGreenTableCell) => { - return ( - - {flexRender( - cell.column.columnDef.cell, - cell.getContext(), - )} - - ); - })} - - ); - })} + {table.virtual.virtualItems && + table.virtual.virtualItems.map( + (virtualRow: LeafyGreenVirtualItem) => { + const row = virtualRow.row; + const cells = row.getVisibleCells(); + + return ( + + {cells.map((cell: LeafyGreenTableCell) => { + return ( + + {flexRender( + cell.column.columnDef.cell, + cell.getContext(), + )} + + ); + })} + + ); + }, + )} @@ -401,7 +356,7 @@ export const SelectableRows: StoryFn = args => { const columns = useMemo(() => basicColumnDefs, []); - const table = useLeafyGreenTable({ + const table = useLeafyGreenVirtualTable({ containerRef: tableContainerRef, data, columns, @@ -410,11 +365,8 @@ export const SelectableRows: StoryFn = args => { }, onRowSelectionChange: setRowSelection, hasSelectableRows: true, - useVirtualScrolling: true, }); - const { rows } = table.getRowModel(); - return ( <>
@@ -443,26 +395,28 @@ export const SelectableRows: StoryFn = args => { ))} - {table.virtualRows && - table.virtualRows.map((virtualRow: VirtualItem) => { - const row = rows[virtualRow.index]; - const cells = row.getVisibleCells(); - - return ( - - {cells.map((cell: LeafyGreenTableCell) => { - return ( - - {flexRender( - cell.column.columnDef.cell, - cell.getContext(), - )} - - ); - })} - - ); - })} + {table.virtual.virtualItems && + table.virtual.virtualItems.map( + (virtualRow: LeafyGreenVirtualItem) => { + const row = virtualRow.row; + const cells = row.getVisibleCells(); + + return ( + + {cells.map((cell: LeafyGreenTableCell) => { + return ( + + {flexRender( + cell.column.columnDef.cell, + cell.getContext(), + )} + + ); + })} + + ); + }, + )} @@ -476,7 +430,7 @@ export const ExpandableContent: StoryFn = args => { const columns = useMemo(() => basicColumnDefs, []); - const table = useLeafyGreenTable({ + const table = useLeafyGreenVirtualTable({ containerRef: tableContainerRef, data, columns, @@ -484,15 +438,12 @@ export const ExpandableContent: StoryFn = args => { expanded, }, onExpandedChange: setExpanded, - useVirtualScrolling: true, }); - const { rows } = table.getRowModel(); - return ( <>
-

{table.getRowModel().rows.length} total rows

+

{table.rows.length} total rows

= args => { ))} - {table.virtualRows && - table.virtualRows.map((virtualRow: VirtualItem) => { - const row = rows[virtualRow.index]; - const cells = row.getVisibleCells(); + {table.virtual.virtualItems && + table.virtual.virtualItems.map( + (virtualRow: LeafyGreenVirtualItem) => { + const row = virtualRow.row; + const isExpandedContent = + row.original.isExpandedContent ?? false; - return ( - <> - - {cells.map((cell: LeafyGreenTableCell) => { - return ( - - {flexRender( - cell.column.columnDef.cell, - cell.getContext(), - )} - - ); - })} - - {row.original.renderExpandedContent && ( - - )} - - ); - })} + return ( + <> + {!isExpandedContent && ( + + {row + .getVisibleCells() + .map((cell: LeafyGreenTableCell) => { + return ( + + {flexRender( + cell.column.columnDef.cell, + cell.getContext(), + )} + + ); + })} + + )} + {isExpandedContent && ( + + )} + + ); + }, + )}
@@ -559,25 +520,22 @@ export const TallRows: StoryFn = args => { }, []); const columns = useMemo(() => basicColumnDefs, []); - const estimateSize = useCallback(() => 100, []); + const estimateSize = useCallback(() => 68, []); - const table = useLeafyGreenTable({ + const table = useLeafyGreenVirtualTable({ containerRef: tableContainerRef, data, columns, - useVirtualScrolling: true, virtualizerOptions: { estimateSize, }, }); - const { rows } = table.getRowModel(); - return ( <>

{table.getRowModel().rows.length} total rows

-

{table?.virtualRows?.length} virtual rows

+

{table?.virtual.getVirtualItems().length} virtual rows

= args => { ))} - {table.virtualRows && - table.virtualRows.map((virtualRow: VirtualItem) => { - const row = rows[virtualRow.index]; - const cells = row.getVisibleCells(); - return ( - - {cells.map((cell: LeafyGreenTableCell) => { - return ( - div { - max-height: unset; - } - `} - > - {flexRender( - cell.column.columnDef.cell, - cell.getContext(), - )} - - ); - })} - - ); - })} + {table.virtual.virtualItems && + table.virtual.virtualItems.map( + (virtualRow: LeafyGreenVirtualItem) => { + const row = virtualRow.row; + const cells = row.getVisibleCells(); + return ( + + {cells.map((cell: LeafyGreenTableCell) => { + return ( + div { + max-height: unset; + } + `} + cell={cell} + > + {flexRender( + cell.column.columnDef.cell, + cell.getContext(), + )} + + ); + })} + + ); + }, + )}
diff --git a/packages/table/src/TableBody/TableBody.tsx b/packages/table/src/TableBody/TableBody.tsx index 35d409fd01..737c1b397f 100644 --- a/packages/table/src/TableBody/TableBody.tsx +++ b/packages/table/src/TableBody/TableBody.tsx @@ -1,49 +1,9 @@ -import React, { Fragment, useMemo } from 'react'; - -import { Polymorph } from '@leafygreen-ui/polymorphic'; - -import { useTableContext } from '../TableContext'; +import React from 'react'; import { TableBodyProps } from './TableBody.types'; const TableBody = ({ children, ...rest }: TableBodyProps) => { - let paddingTop = 0; - let paddingBottom = 0; - - const { table } = useTableContext(); - const areSomeRowsExpandable = table?.getCanSomeRowsExpand(); - - const bodyAs = useMemo( - () => (areSomeRowsExpandable ? Fragment : 'tbody'), - [areSomeRowsExpandable], - ); - - if (table && table.virtualRows) { - const { virtualRows, totalSize } = table; - paddingTop = virtualRows.length > 0 ? virtualRows?.[0]?.start || 0 : 0; - paddingBottom = - virtualRows.length > 0 - ? totalSize - (virtualRows?.[virtualRows.length - 1]?.end || 0) - : 0; - } - - return ( - - {/* As the user scrolls down, the paddingTop grows bigger, creating the effect of virtual scrolling */} - {paddingTop > 0 && ( - - - - )} - {children} - {/* As the user scrolls down, the paddingBottom gets smaller, creating the effect of virtual scrolling */} - {paddingBottom > 0 && ( - - - - )} - - ); + return {children}; }; TableBody.displayName = 'TableBody'; diff --git a/packages/table/src/TableContext/TableContext.tsx b/packages/table/src/TableContext/TableContext.tsx index 4de7ee930e..3378e398f6 100644 --- a/packages/table/src/TableContext/TableContext.tsx +++ b/packages/table/src/TableContext/TableContext.tsx @@ -1,9 +1,13 @@ -import React, { createContext, PropsWithChildren, useContext } from 'react'; +import React, { + createContext, + PropsWithChildren, + useContext, + useMemo, +} from 'react'; import LeafyGreenProvider from '@leafygreen-ui/leafygreen-provider'; import { LGRowData } from '../useLeafyGreenTable'; -import getParentRowId from '../utils/getParentRowId'; import { type TableContextValues } from './TableContext.types'; @@ -16,36 +20,38 @@ export const useTableContext = () => TableContext as React.Context>, ); +//TODO: a seperate context for virtual scrolling? const TableContextProvider = ({ children, darkMode, - table, shouldAlternateRowColor, - disableAnimations, + isVirtual, + isSelectable, + measureElement, }: PropsWithChildren>>) => { - const getRowById = (id?: string) => - id ? table?.getRowModel().rowsById?.[id] : undefined; - - const getParentRow = (childId?: string) => - getRowById(getParentRowId(childId)); - /** The appropriately typed context provider */ const TableProvider = (TableContext as React.Context>) .Provider; + const providerData = useMemo(() => { + return { + shouldAlternateRowColor, + darkMode, + isVirtual, + isSelectable, + measureElement, + }; + }, [ + shouldAlternateRowColor, + darkMode, + isVirtual, + isSelectable, + measureElement, + ]); + return ( - - {children} - + {children} ); }; diff --git a/packages/table/src/TableContext/TableContext.types.ts b/packages/table/src/TableContext/TableContext.types.ts index 49d0d05a1c..377c5421bd 100644 --- a/packages/table/src/TableContext/TableContext.types.ts +++ b/packages/table/src/TableContext/TableContext.types.ts @@ -1,4 +1,5 @@ import { PropsWithChildren } from 'react'; +import { Virtualizer } from '@tanstack/react-virtual'; import { DarkModeProps } from '@leafygreen-ui/lib'; @@ -23,4 +24,13 @@ export type TableContextValues = PropsWithChildren< * The `useLeafyGreenTable` return value */ table?: LeafyGreenTable; + + /** + * Whether the table is using virtual scrolling + */ + isVirtual?: boolean; + + isSelectable?: boolean; + + measureElement?: Virtualizer['measureElement']; }; diff --git a/packages/table/src/ToggleExpandedIcon/ToggleExpandedIcon.tsx b/packages/table/src/ToggleExpandedIcon/ToggleExpandedIcon.tsx index d2af8da560..036517728c 100644 --- a/packages/table/src/ToggleExpandedIcon/ToggleExpandedIcon.tsx +++ b/packages/table/src/ToggleExpandedIcon/ToggleExpandedIcon.tsx @@ -7,7 +7,6 @@ import IconButton from '@leafygreen-ui/icon-button'; import { useDarkMode } from '@leafygreen-ui/leafygreen-provider'; import { LGIDS } from '../constants'; -import { useTableContext } from '../TableContext'; import { iconButtonTransitionStyles, @@ -26,17 +25,18 @@ const ToggleExpandedIcon = ({ ...rest }: ToggleExpandedIconProps) => { const { theme } = useDarkMode(); - const { disableAnimations } = useTableContext(); return ( diff --git a/packages/table/src/V11Adapter/V11Adapter.tsx b/packages/table/src/V11Adapter/V11Adapter.tsx index 0757f94065..cb0176c22d 100644 --- a/packages/table/src/V11Adapter/V11Adapter.tsx +++ b/packages/table/src/V11Adapter/V11Adapter.tsx @@ -1,3 +1,4 @@ +// @ts-nocheck import React, { ReactElement, useEffect, diff --git a/packages/table/src/V11Adapter/V11Adapter.types.ts b/packages/table/src/V11Adapter/V11Adapter.types.ts index a4c8ee109c..acb6939e01 100644 --- a/packages/table/src/V11Adapter/V11Adapter.types.ts +++ b/packages/table/src/V11Adapter/V11Adapter.types.ts @@ -1,3 +1,4 @@ +// @ts-nocheck import { PropsWithChildren } from 'react'; import { DarkModeProps } from '@leafygreen-ui/lib'; diff --git a/packages/table/src/index.ts b/packages/table/src/index.ts index e8cd1011d1..9faaf33da8 100644 --- a/packages/table/src/index.ts +++ b/packages/table/src/index.ts @@ -26,7 +26,14 @@ export { type LGTableDataType, default as useLeafyGreenTable, } from './useLeafyGreenTable'; +export { + type LeafyGreenVirtualItem, + type LeafyGreenVirtualTable, + type LeafyGreenVirtualTableOptions, + default as useLeafyGreenVirtualTable, +} from './useLeafyGreenVirtualTable'; export { getTestUtils } from './utils/getTestUtils'; export { default as V11Adapter, type V11AdapterProps } from './V11Adapter'; export * from '@tanstack/react-table'; -export { type VirtualItem } from 'react-virtual'; +// export { type VirtualItem } from 'react-virtual'; +export { type VirtualItem } from '@tanstack/react-virtual'; diff --git a/packages/table/src/useLeafyGreenTable/useLeafyGreenTable.tsx b/packages/table/src/useLeafyGreenTable/useLeafyGreenTable.tsx index 91f1989dc3..f460644a71 100644 --- a/packages/table/src/useLeafyGreenTable/useLeafyGreenTable.tsx +++ b/packages/table/src/useLeafyGreenTable/useLeafyGreenTable.tsx @@ -1,6 +1,9 @@ import React from 'react'; -import { useVirtual } from 'react-virtual'; -import { useReactTable } from '@tanstack/react-table'; +import { + ExpandedState, + getExpandedRowModel, + useReactTable, +} from '@tanstack/react-table'; import { getCoreRowModel, getPaginationRowModel, @@ -16,16 +19,17 @@ import { LeafyGreenTable, LGColumnDef, LGTableDataType } from '.'; const CHECKBOX_WIDTH = 14; function useLeafyGreenTable({ - containerRef, + // containerRef, data, columns: columnsProp, hasSelectableRows, withPagination = false, - useVirtualScrolling = false, allowSelectAll = true, - virtualizerOptions, + // virtualizerOptions, ...rest }: LeafyGreenTableOptions): LeafyGreenTable { + const [expanded, setExpanded] = React.useState({}); + /** * A `ColumnDef` object injected into `useReactTable`'s `columns` option when the user is using selectable rows. */ @@ -58,6 +62,10 @@ function useLeafyGreenTable({ ); const table = useReactTable>({ + state: { + expanded, + ...rest.state, + }, data, columns, getCoreRowModel: getCoreRowModel(), @@ -66,28 +74,41 @@ function useLeafyGreenTable({ }, enableExpanding: true, enableSortingRemoval: hasSortableColumns ? true : undefined, + onExpandedChange: setExpanded, getSubRows: row => row.subRows, getSortedRowModel: getSortedRowModel(), getPaginationRowModel: withPagination ? getPaginationRowModel() : undefined, + getExpandedRowModel: getExpandedRowModel(), ...rest, }); const { rows } = table.getRowModel(); - const _rowVirtualizer = useVirtual({ - parentRef: containerRef, - size: rows.length, - overscan: 30, - ...virtualizerOptions, - }); + + // A way to include expandableContent inside of the rows object. + // If a row has expandedContent and its expanded then add a new row below the row + const rowsCopy = [...rows]; + + for (let i = 0; i < rowsCopy.length; i++) { + if ( + rowsCopy[i].original.renderExpandedContent && + rowsCopy[i].getIsExpanded() + ) { + rowsCopy.splice(i + 1, 0, { + ...rowsCopy[i], + id: `${rowsCopy[i].id}-expandedContent`, + original: { + ...rowsCopy[i].original, + isExpandedContent: true, + }, + }); + i++; // Increment index to skip the newly added item + } + } return { ...table, - ...(useVirtualScrolling && { - virtualRows: _rowVirtualizer.virtualItems, - totalSize: _rowVirtualizer.totalSize, - scrollToIndex: _rowVirtualizer.scrollToIndex, - }), hasSelectableRows, + rows: rowsCopy, } as LeafyGreenTable; } diff --git a/packages/table/src/useLeafyGreenTable/useLeafyGreenTable.types.ts b/packages/table/src/useLeafyGreenTable/useLeafyGreenTable.types.ts index 59a35b51ac..0322401be4 100644 --- a/packages/table/src/useLeafyGreenTable/useLeafyGreenTable.types.ts +++ b/packages/table/src/useLeafyGreenTable/useLeafyGreenTable.types.ts @@ -1,8 +1,8 @@ -import { RefObject } from 'react'; -import { - type Options as VirtualizerOptions, - type VirtualItem, -} from 'react-virtual'; +// import { RefObject } from 'react'; +// import { +// type Options as VirtualizerOptions, +// type VirtualItem, +// } from 'react-virtual'; import { Cell, ColumnDef, @@ -14,7 +14,7 @@ import { import { HTMLElementProps } from '@leafygreen-ui/lib'; -import { VirtualizerValues } from './ReactVirtual.types'; +// import { VirtualizerValues } from './ReactVirtual.types'; /** LeafyGreen extension of `useReactTable` {@link RowData}*/ export type LGRowData = RowData; @@ -22,6 +22,7 @@ export type LGRowData = RowData; export type LGTableDataType = T & { renderExpandedContent?: (row: LeafyGreenTableRow) => JSX.Element; subRows?: Array>; + isExpandedContent?: boolean; }; /** LeafyGreen extension of `useReactTable` {@link Cell}*/ @@ -50,21 +51,20 @@ export type LeafyGreenTableOptions< T extends LGRowData, V extends unknown = unknown, > = Omit>, 'getCoreRowModel' | 'columns'> & { - containerRef: RefObject; + // containerRef: RefObject; hasSelectableRows?: boolean; columns: Array>; withPagination?: boolean; allowSelectAll?: boolean; - useVirtualScrolling?: boolean; - virtualizerOptions?: Partial>; + // useVirtualScrolling?: boolean; + // virtualizerOptions?: Partial>; }; /** * LeafyGreen extension of `useReactTable` {@link Table} */ export interface LeafyGreenTable - extends Table>, - Omit { - virtualRows?: Array; + extends Table> { hasSelectableRows: boolean; + rows: Array; } diff --git a/packages/table/src/useLeafyGreenVirtualTable/ReactVirtual.types.ts b/packages/table/src/useLeafyGreenVirtualTable/ReactVirtual.types.ts new file mode 100644 index 0000000000..de318d90e1 --- /dev/null +++ b/packages/table/src/useLeafyGreenVirtualTable/ReactVirtual.types.ts @@ -0,0 +1,3 @@ +import { useVirtualizer } from '@tanstack/react-virtual'; + +export type VirtualizerValues = ReturnType; diff --git a/packages/table/src/useLeafyGreenVirtualTable/index.ts b/packages/table/src/useLeafyGreenVirtualTable/index.ts new file mode 100644 index 0000000000..4395a4e266 --- /dev/null +++ b/packages/table/src/useLeafyGreenVirtualTable/index.ts @@ -0,0 +1,4 @@ +import useLeafyGreenVirtualTable from './useLeafyGreenVirtualTable'; +export * from './useLeafyGreenVirtualTable.types'; + +export default useLeafyGreenVirtualTable; diff --git a/packages/table/src/useLeafyGreenVirtualTable/useLeafyGreenVirtualTable.spec.tsx b/packages/table/src/useLeafyGreenVirtualTable/useLeafyGreenVirtualTable.spec.tsx new file mode 100644 index 0000000000..e69de29bb2 diff --git a/packages/table/src/useLeafyGreenVirtualTable/useLeafyGreenVirtualTable.tsx b/packages/table/src/useLeafyGreenVirtualTable/useLeafyGreenVirtualTable.tsx new file mode 100644 index 0000000000..381920751d --- /dev/null +++ b/packages/table/src/useLeafyGreenVirtualTable/useLeafyGreenVirtualTable.tsx @@ -0,0 +1,63 @@ +import { useVirtualizer, VirtualItem } from '@tanstack/react-virtual'; + +import useLeafyGreenTable, { LGRowData } from '../useLeafyGreenTable'; + +import { + LeafyGreenVirtualItem, + LeafyGreenVirtualTable, + LeafyGreenVirtualTableOptions, +} from './useLeafyGreenVirtualTable.types'; + +function useLeafyGreenVirtualTable< + T extends LGRowData, + V extends unknown = unknown, +>({ + containerRef, + data, + columns, + hasSelectableRows, + withPagination = false, + allowSelectAll = true, + virtualizerOptions, + ...rest +}: LeafyGreenVirtualTableOptions): LeafyGreenVirtualTable { + const table = useLeafyGreenTable({ + data, + columns, + withPagination, + allowSelectAll, + hasSelectableRows, + ...rest, + }); + + // const { rows } = table.getRowModel(); + + const { rows } = table; + + const _virtualizer = useVirtualizer({ + count: rows.length, + getScrollElement: () => containerRef.current, + estimateSize: () => 40, + overscan: 20, + measureElement: + typeof window !== 'undefined' && + navigator.userAgent.indexOf('Firefox') === -1 + ? element => element?.getBoundingClientRect().height + : undefined, + ...virtualizerOptions, + }); + + const _virtualItems: Array> = _virtualizer + .getVirtualItems() + .map((virtualRow: VirtualItem) => ({ + ...virtualRow, + row: rows[virtualRow.index], + })); + + return { + ...table, + virtual: { ..._virtualizer, virtualItems: _virtualItems }, + } as LeafyGreenVirtualTable; +} + +export default useLeafyGreenVirtualTable; diff --git a/packages/table/src/useLeafyGreenVirtualTable/useLeafyGreenVirtualTable.types.ts b/packages/table/src/useLeafyGreenVirtualTable/useLeafyGreenVirtualTable.types.ts new file mode 100644 index 0000000000..f6cb3821c1 --- /dev/null +++ b/packages/table/src/useLeafyGreenVirtualTable/useLeafyGreenVirtualTable.types.ts @@ -0,0 +1,41 @@ +import { RefObject } from 'react'; +import { + VirtualItem as TSVirtualItem, + Virtualizer, + VirtualizerOptions, +} from '@tanstack/react-virtual'; + +import { + LeafyGreenTable, + LeafyGreenTableOptions, + LeafyGreenTableRow, + LGRowData, +} from '../useLeafyGreenTable'; + +/** + * Options argument for the LeafyGreen extension of `useReactTable` + * + * See: {@link TableOptions} + */ +export interface LeafyGreenVirtualTableOptions< + T extends LGRowData, + V extends unknown = unknown, +> extends LeafyGreenTableOptions { + containerRef: RefObject; + virtualizerOptions?: Partial>; +} + +/** + * LeafyGreen extension of `useReactTable` {@link Table} + */ +export interface LeafyGreenVirtualTable + extends LeafyGreenTable { + // virtualRows?: Array; + virtual: Virtualizer & { + virtualItems: Array>; + }; +} + +export type LeafyGreenVirtualItem = TSVirtualItem & { + row: LeafyGreenTableRow; +}; diff --git a/packages/table/src/utils/testHookCalls.testutils.tsx b/packages/table/src/utils/testHookCalls.testutils.tsx index 0070a408e0..01f74a21ba 100644 --- a/packages/table/src/utils/testHookCalls.testutils.tsx +++ b/packages/table/src/utils/testHookCalls.testutils.tsx @@ -1,3 +1,4 @@ +// @ts-nocheck import React, { useRef, useState } from 'react'; import useLeafyGreenTable, { diff --git a/yarn.lock b/yarn.lock index b7e6063baa..de8a962941 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5642,17 +5642,29 @@ resolved "https://registry.yarnpkg.com/@swc/types/-/types-0.1.5.tgz#043b731d4f56a79b4897a3de1af35e75d56bc63a" integrity sha512-myfUej5naTBWnqOCc/MdVOLVjXUXtIA+NpDrDBKJtLLg2shUjBu3cZmB/85RyitKc55+lUUyl7oRfLOvkr2hsw== -"@tanstack/react-table@^8.13.2": - version "8.13.2" - resolved "https://registry.yarnpkg.com/@tanstack/react-table/-/react-table-8.13.2.tgz#a3aa737ae464abc651f68daa7e82dca17813606c" - integrity sha512-b6mR3mYkjRtJ443QZh9sc7CvGTce81J35F/XMr0OoWbx0KIM7TTTdyNP2XKObvkLpYnLpCrYDwI3CZnLezWvpg== +"@tanstack/react-table@^8.20.5": + version "8.20.5" + resolved "https://registry.yarnpkg.com/@tanstack/react-table/-/react-table-8.20.5.tgz#19987d101e1ea25ef5406dce4352cab3932449d8" + integrity sha512-WEHopKw3znbUZ61s9i0+i9g8drmDo6asTWbrQh8Us63DAk/M0FkmIqERew6P71HI75ksZ2Pxyuf4vvKh9rAkiA== dependencies: - "@tanstack/table-core" "8.13.2" + "@tanstack/table-core" "8.20.5" -"@tanstack/table-core@8.13.2": - version "8.13.2" - resolved "https://registry.yarnpkg.com/@tanstack/table-core/-/table-core-8.13.2.tgz#2512574dd3d20dc94b7db1f9f48090f0c18b5c85" - integrity sha512-/2saD1lWBUV6/uNAwrsg2tw58uvMJ07bO2F1IWMxjFRkJiXKQRuc3Oq2aufeobD3873+4oIM/DRySIw7+QsPPw== +"@tanstack/react-virtual@^3.10.7": + version "3.10.7" + resolved "https://registry.yarnpkg.com/@tanstack/react-virtual/-/react-virtual-3.10.7.tgz#428d0c29c6d2046ab905f4278446a3fe89bfd4ef" + integrity sha512-yeP+M0G8D+15ZFPivpuQ5hoM4Fa/PzERBx8P8EGcfEsXX3JOb9G9UUrqc47ZXAxvK+YqzM9T5qlJUYUFOwCZJw== + dependencies: + "@tanstack/virtual-core" "3.10.7" + +"@tanstack/table-core@8.20.5": + version "8.20.5" + resolved "https://registry.yarnpkg.com/@tanstack/table-core/-/table-core-8.20.5.tgz#3974f0b090bed11243d4107283824167a395cf1d" + integrity sha512-P9dF7XbibHph2PFRz8gfBKEXEY/HJPOhym8CHmjF8y3q5mWpKx9xtZapXQUWCgkqvsK0R46Azuz+VaxD4Xl+Tg== + +"@tanstack/virtual-core@3.10.7": + version "3.10.7" + resolved "https://registry.yarnpkg.com/@tanstack/virtual-core/-/virtual-core-3.10.7.tgz#fb1d8ae1257c9fe3a6e99c911cad0d1a1b07f598" + integrity sha512-ND5dfsU0n9F4gROzwNNDJmg6y8n9pI8YWxtgbfJ5UcNn7Hx+MxEXtXcQ189tS7sh8pmCObgz2qSiyRKTZxT4dg== "@testing-library/dom@9.3.1", "@testing-library/dom@^9.0.0": version "9.3.1"