From b7873870596ce9069987a91b76591ae744d12c89 Mon Sep 17 00:00:00 2001 From: Dmitry Artemov Date: Thu, 31 Oct 2024 16:49:57 +0100 Subject: [PATCH] feat(BaseTable): add footer rendering customization and stop rendering emptyContent if not passed (#79) --- src/components/BaseTable/BaseTable.scss | 4 + src/components/BaseTable/BaseTable.tsx | 116 ++++++++++++++---------- src/components/BaseTable/README.md | 2 + 3 files changed, 76 insertions(+), 46 deletions(-) diff --git a/src/components/BaseTable/BaseTable.scss b/src/components/BaseTable/BaseTable.scss index dd34519..17121be 100644 --- a/src/components/BaseTable/BaseTable.scss +++ b/src/components/BaseTable/BaseTable.scss @@ -92,6 +92,10 @@ $block: '.#{variables.$ns}table'; position: absolute; height: auto; + + &_empty { + position: relative; + } } } } diff --git a/src/components/BaseTable/BaseTable.tsx b/src/components/BaseTable/BaseTable.tsx index fdd8cd4..5a30b30 100644 --- a/src/components/BaseTable/BaseTable.tsx +++ b/src/components/BaseTable/BaseTable.tsx @@ -1,14 +1,9 @@ import React from 'react'; -import type {Row, Table} from '@tanstack/react-table'; +import type {HeaderGroup, Row, Table} from '@tanstack/react-table'; import type {VirtualItem, Virtualizer} from '@tanstack/react-virtual'; -import { - getAriaMultiselectable, - getAriaRowIndexMap, - getCellClassModes, - shouldRenderFooterRow, -} from '../../utils'; +import {getAriaMultiselectable, getAriaRowIndexMap, shouldRenderFooterRow} from '../../utils'; import {BaseDraggableRow} from '../BaseDraggableRow'; import type {BaseFooterRowProps} from '../BaseFooterRow'; import {BaseFooterRow} from '../BaseFooterRow'; @@ -39,6 +34,8 @@ export interface BaseTableProps['cellClassName']; /** CSS classes for the `` element */ className?: string; + /** Should be used together with `renderCustomFooterContent` to set the correct `aria-rowcount` */ + customFooterRowCount?: number; /** Content displayed when the table has no data rows */ emptyContent?: React.ReactNode | (() => React.ReactNode); /** HTML attributes for the `` element */ @@ -75,6 +72,13 @@ export interface BaseTableProps['className']; /** Click handler for rows inside `` */ onRowClick?: BaseRowProps['onClick']; + /** Function to render custom footer content */ + renderCustomFooterContent?: (props: { + cellClassName: string; + footerGroups: HeaderGroup[]; + rowClassName: string; + rowIndex: number; + }) => React.ReactNode; /** Function to render custom rows */ renderCustomRowContent?: BaseRowProps['renderCustomRowContent']; /** Function to override the default rendering of group headers */ @@ -118,6 +122,7 @@ export const BaseTable = React.forwardRef( cellAttributes, cellClassName, className, + customFooterRowCount, emptyContent, footerAttributes, footerCellAttributes, @@ -136,6 +141,7 @@ export const BaseTable = React.forwardRef( headerRowAttributes, headerRowClassName, onRowClick, + renderCustomFooterContent, renderCustomRowContent, renderGroupHeader, renderGroupHeaderRowContent, @@ -163,37 +169,48 @@ export const BaseTable = React.forwardRef( return getAriaRowIndexMap(rows); }, [rows]); - const headerGroups = withHeader && table.getHeaderGroups(); - const footerGroups = withFooter && table.getFooterGroups(); + const headerGroups = withHeader ? table.getHeaderGroups() : []; + const footerGroups = withFooter ? table.getFooterGroups() : []; const colCount = table.getVisibleLeafColumns().length; - const headerRowCount = headerGroups ? headerGroups.length : 0; + const headerRowCount = headerGroups.length; const bodyRowCount = Object.keys(rowsById).length; - const footerRowCount = footerGroups ? footerGroups.length : 0; + + const footerRowCount = + (withFooter && + ((renderCustomFooterContent && customFooterRowCount) || footerGroups.length)) || + 0; + const rowCount = bodyRowCount + headerRowCount + footerRowCount; + const bodyRows = rowVirtualizer?.getVirtualItems() || rows; - const renderBodyRows = () => { - const bodyRows = rowVirtualizer?.getVirtualItems() || rows; + const renderEmptyContent = () => { + if (!emptyContent) { + return null; + } - if (bodyRows.length === 0) { - const emptyRowClassName = - typeof rowClassName === 'function' ? rowClassName() : rowClassName; + const emptyRowClassName = + typeof rowClassName === 'function' ? rowClassName() : rowClassName; - const emptyCellClassName = - typeof cellClassName === 'function' ? cellClassName() : cellClassName; + const emptyCellClassName = + typeof cellClassName === 'function' ? cellClassName() : cellClassName; - return ( - - - - ); - } + return ( + + + + ); + }; + const renderBodyRows = () => { return bodyRows.map((virtualItemOrRow) => { const row = rowVirtualizer ? rows[virtualItemOrRow.index] @@ -243,7 +260,7 @@ export const BaseTable = React.forwardRef( aria-multiselectable={getAriaMultiselectable(table)} {...attributes} > - {headerGroups && ( + {withHeader && ( - {renderBodyRows()} + {bodyRows.length ? renderBodyRows() : renderEmptyContent()} - {footerGroups && ( + {withFooter && ( - {footerGroups.map((footerGroup, index) => - shouldRenderFooterRow(footerGroup) ? ( - - ) : null, - )} + {renderCustomFooterContent + ? renderCustomFooterContent({ + cellClassName: b('footer-cell'), + footerGroups, + rowClassName: b('footer-row'), + rowIndex: headerRowCount + bodyRowCount + 1, + }) + : footerGroups.map((footerGroup, index) => + shouldRenderFooterRow(footerGroup) ? ( + + ) : null, + )} )}
- {typeof emptyContent === 'function' ? emptyContent() : emptyContent} -
+ {typeof emptyContent === 'function' ? emptyContent() : emptyContent} +
diff --git a/src/components/BaseTable/README.md b/src/components/BaseTable/README.md index ee86988..2aa17a2 100644 --- a/src/components/BaseTable/README.md +++ b/src/components/BaseTable/README.md @@ -12,6 +12,7 @@ | cellAttributes | HTML attributes for the `` elements inside `` | `React.TdHTMLAttributes`
`((cell?: Cell) => React.TdHTMLAttributes)` | | cellClassName | CSS classes for the `` elements inside `` | `string`
`((cell?: Cell) => string)` | | className | CSS classes for the `` element | `string` | +| customFooterRowCount | Should be used together with `renderCustomFooterContent` to set the correct `aria-rowcount` | `number` | | emptyContent | Content displayed when the table has no data rows | `React.ReactNode`
`(() => React.ReactNode)` | | footerAttributes | HTML attributes for the `` element | `React.HTMLAttributes` | | footerCellAttributes | HTML attributes for the `` | `React.ThHTMLAttributes`
`((header: Header) => React.ThHTMLAttributes)` | @@ -30,6 +31,7 @@ | headerRowAttributes | HTML attributes for the `
` elements inside `` | `React.HTMLAttributes`
`((headerGroup: HeaderGroup, parentHeaderGroup?: HeaderGroup) => React.HTMLAttributes)` | | headerRowClassName | CSS classes for the `
` elements inside `` | `string`
`((headerGroup: HeaderGroup, parentHeaderGroup?: HeaderGroup) => string)` | | onRowClick | Click handler for rows inside `` | `(row: Row, event: React.MouseEvent) => void` | +| renderCustomFooterContent | Function to render custom footer content | `(props: {cellClassName: string; footerGroups: HeaderGroup[]; rowClassName: string; rowIndex: number}) => React.ReactNode` | | renderCustomRowContent | Function to render custom rows | `(props: {row: Row; Cell: React.FunctionComponent>; cellClassName?: BaseCellProps['className']}) => React.ReactNode` | | renderGroupHeader | Function to override the default rendering of group headers | `(props: {row: Row; className?: string; getGroupTitle?: (row: Row) => React.ReactNode}) => React.ReactNode` | | renderGroupHeaderRowContent | Function to override the default rendering of the entire group header row | `(props: {row: Row; Cell: React.FunctionComponent>; cellClassName?: BaseCellProps['className']; getGroupTitle?: (row: Row) => React.ReactNode;}) => React.ReactNode` |
` elements inside `