Skip to content

Commit

Permalink
feat(BaseTable): add footer rendering customization and stop renderin…
Browse files Browse the repository at this point in the history
…g emptyContent if not passed (#79)
  • Loading branch information
beliarh authored Oct 31, 2024
1 parent 935896b commit b787387
Show file tree
Hide file tree
Showing 3 changed files with 76 additions and 46 deletions.
4 changes: 4 additions & 0 deletions src/components/BaseTable/BaseTable.scss
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,10 @@ $block: '.#{variables.$ns}table';
position: absolute;

height: auto;

&_empty {
position: relative;
}
}
}
}
116 changes: 70 additions & 46 deletions src/components/BaseTable/BaseTable.tsx
Original file line number Diff line number Diff line change
@@ -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';
Expand Down Expand Up @@ -39,6 +34,8 @@ export interface BaseTableProps<TData, TScrollElement extends Element | Window =
cellClassName?: BaseRowProps<TData>['cellClassName'];
/** CSS classes for the `<table>` 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 `<tfoot>` element */
Expand Down Expand Up @@ -75,6 +72,13 @@ export interface BaseTableProps<TData, TScrollElement extends Element | Window =
headerRowClassName?: BaseHeaderRowProps<TData>['className'];
/** Click handler for rows inside `<tbody>` */
onRowClick?: BaseRowProps<TData>['onClick'];
/** Function to render custom footer content */
renderCustomFooterContent?: (props: {
cellClassName: string;
footerGroups: HeaderGroup<TData>[];
rowClassName: string;
rowIndex: number;
}) => React.ReactNode;
/** Function to render custom rows */
renderCustomRowContent?: BaseRowProps<TData>['renderCustomRowContent'];
/** Function to override the default rendering of group headers */
Expand Down Expand Up @@ -118,6 +122,7 @@ export const BaseTable = React.forwardRef(
cellAttributes,
cellClassName,
className,
customFooterRowCount,
emptyContent,
footerAttributes,
footerCellAttributes,
Expand All @@ -136,6 +141,7 @@ export const BaseTable = React.forwardRef(
headerRowAttributes,
headerRowClassName,
onRowClick,
renderCustomFooterContent,
renderCustomRowContent,
renderGroupHeader,
renderGroupHeaderRowContent,
Expand Down Expand Up @@ -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 (
<tr className={b('row', {}, emptyRowClassName)}>
<td
className={b('cell', getCellClassModes(), emptyCellClassName)}
colSpan={colCount}
>
{typeof emptyContent === 'function' ? emptyContent() : emptyContent}
</td>
</tr>
);
}
return (
<tr className={b('row', {empty: true}, emptyRowClassName)}>
<td
className={b('cell', {}, emptyCellClassName)}
colSpan={colCount}
style={{
width: rowVirtualizer ? table.getTotalSize() : undefined,
}}
>
{typeof emptyContent === 'function' ? emptyContent() : emptyContent}
</td>
</tr>
);
};

const renderBodyRows = () => {
return bodyRows.map((virtualItemOrRow) => {
const row = rowVirtualizer
? rows[virtualItemOrRow.index]
Expand Down Expand Up @@ -243,7 +260,7 @@ export const BaseTable = React.forwardRef(
aria-multiselectable={getAriaMultiselectable(table)}
{...attributes}
>
{headerGroups && (
{withHeader && (
<thead
className={b('header', {sticky: stickyHeader}, headerClassName)}
{...headerAttributes}
Expand Down Expand Up @@ -272,30 +289,37 @@ export const BaseTable = React.forwardRef(
className={b('body', bodyClassName)}
{...bodyAttributes}
style={{
height: rowVirtualizer?.getTotalSize(),
height: bodyRows.length ? rowVirtualizer?.getTotalSize() : undefined,
...bodyAttributes?.style,
}}
>
{renderBodyRows()}
{bodyRows.length ? renderBodyRows() : renderEmptyContent()}
</tbody>
{footerGroups && (
{withFooter && (
<tfoot
className={b('footer', {sticky: stickyFooter}, footerClassName)}
{...footerAttributes}
>
{footerGroups.map((footerGroup, index) =>
shouldRenderFooterRow(footerGroup) ? (
<BaseFooterRow
key={footerGroup.id}
footerGroup={footerGroup}
attributes={footerRowAttributes}
cellAttributes={footerCellAttributes}
cellClassName={footerCellClassName}
className={footerRowClassName}
aria-rowindex={headerRowCount + bodyRowCount + index + 1}
/>
) : null,
)}
{renderCustomFooterContent
? renderCustomFooterContent({
cellClassName: b('footer-cell'),
footerGroups,
rowClassName: b('footer-row'),
rowIndex: headerRowCount + bodyRowCount + 1,
})
: footerGroups.map((footerGroup, index) =>
shouldRenderFooterRow(footerGroup) ? (
<BaseFooterRow
key={footerGroup.id}
footerGroup={footerGroup}
attributes={footerRowAttributes}
cellAttributes={footerCellAttributes}
cellClassName={footerCellClassName}
className={footerRowClassName}
aria-rowindex={headerRowCount + bodyRowCount + index + 1}
/>
) : null,
)}
</tfoot>
)}
</table>
Expand Down
2 changes: 2 additions & 0 deletions src/components/BaseTable/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
| cellAttributes | HTML attributes for the `<td>` elements inside `<tbody>` | `React.TdHTMLAttributes<HTMLTableCellElement>`<br>`((cell?: Cell<TData, unknown>) => React.TdHTMLAttributes<HTMLTableCellElement>)` |
| cellClassName | CSS classes for the `<td>` elements inside `<tbody>` | `string`<br>`((cell?: Cell<TData, unknown>) => string)` |
| className | CSS classes for the `<table>` 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`<br>`(() => React.ReactNode)` |
| footerAttributes | HTML attributes for the `<tfoot>` element | `React.HTMLAttributes<HTMLTableSectionElement>` |
| footerCellAttributes | HTML attributes for the `<th>` elements inside `<tfoot>` | `React.ThHTMLAttributes<HTMLTableCellElement>`<br>`((header: Header<TData, TValue>) => React.ThHTMLAttributes<HTMLTableCellElement>)` |
Expand All @@ -30,6 +31,7 @@
| headerRowAttributes | HTML attributes for the `<tr>` elements inside `<thead>` | `React.HTMLAttributes<HTMLTableRowElement>`<br>`((headerGroup: HeaderGroup<TData>, parentHeaderGroup?: HeaderGroup<TData>) => React.HTMLAttributes<HTMLTableRowElement>)` |
| headerRowClassName | CSS classes for the `<tr>` elements inside `<thead>` | `string`<br>`((headerGroup: HeaderGroup<TData>, parentHeaderGroup?: HeaderGroup<TData>) => string)` |
| onRowClick | Click handler for rows inside `<tbody>` | `(row: Row<TData>, event: React.MouseEvent<HTMLTableRowElement>) => void` |
| renderCustomFooterContent | Function to render custom footer content | `(props: {cellClassName: string; footerGroups: HeaderGroup<TData>[]; rowClassName: string; rowIndex: number}) => React.ReactNode` |
| renderCustomRowContent | Function to render custom rows | `(props: {row: Row<TData>; Cell: React.FunctionComponent<BaseCellProps<TData>>; cellClassName?: BaseCellProps<TData>['className']}) => React.ReactNode` |
| renderGroupHeader | Function to override the default rendering of group headers | `(props: {row: Row<TData>; className?: string; getGroupTitle?: (row: Row<TData>) => React.ReactNode}) => React.ReactNode` |
| renderGroupHeaderRowContent | Function to override the default rendering of the entire group header row | `(props: {row: Row<TData>; Cell: React.FunctionComponent<BaseCellProps<TData>>; cellClassName?: BaseCellProps<TData>['className']; getGroupTitle?: (row: Row<TData>) => React.ReactNode;}) => React.ReactNode` |
Expand Down

0 comments on commit b787387

Please sign in to comment.