diff --git a/src/components/table/index.ts b/src/components/table/index.ts
index 49f1841c..51ab06fa 100644
--- a/src/components/table/index.ts
+++ b/src/components/table/index.ts
@@ -14,5 +14,10 @@ declare module '@tanstack/react-table' {
* Merged into the `style` prop of the default-rendered `
` element.
*/
thStyle?: CSSProperties;
+
+ /**
+ * Merged into the `style` prop of the default-rendered ` | ` element.
+ */
+ tdStyle?: CSSProperties;
}
}
diff --git a/src/components/table/table_body.tsx b/src/components/table/table_body.tsx
index b67aea30..4335dd35 100644
--- a/src/components/table/table_body.tsx
+++ b/src/components/table/table_body.tsx
@@ -1,3 +1,5 @@
+import { Icon } from '@blueprintjs/core';
+import styled from '@emotion/styled';
import type { Row, RowData } from '@tanstack/react-table';
import type { VirtualItem, Virtualizer } from '@tanstack/react-virtual';
import { notUndefined } from '@tanstack/react-virtual';
@@ -23,6 +25,14 @@ interface TableBodyProps {
virtualizer: Virtualizer;
isReorderingEnabled: boolean;
renderRowPreview?: TableRowPreviewRenderer;
+
+ emptyContent: ReactNode;
+ emptyIcon: ReactNode;
+
+ /**
+ * Bound to `EmptyRow`, it is needed for td colSpan.
+ */
+ columns: number;
}
export function TableBody(props: TableBodyProps) {
@@ -36,6 +46,9 @@ export function TableBody(props: TableBodyProps) {
) as TableRowTrRenderer,
virtualizer,
virtualizeRows,
+ emptyContent,
+ emptyIcon,
+ columns,
} = props;
if (virtualizeRows) {
@@ -72,6 +85,13 @@ export function TableBody(props: TableBodyProps) {
}}
/>
))}
+ {virtualItems.length === 0 && (
+
+ )}
{after > 0 && (
|
@@ -91,10 +111,50 @@ export function TableBody(props: TableBodyProps) {
}
/>
))}
+ {rows.length === 0 && (
+
+ )}
);
}
+interface EmptyRowProps {
+ emptyContent: ReactNode;
+ emptyIcon: ReactNode;
+ columns: number;
+}
+
+function EmptyRow(props: EmptyRowProps) {
+ const {
+ emptyIcon = ,
+ emptyContent = 'No data',
+ columns,
+ } = props;
+
+ return (
+
+ |
+
+ {emptyIcon}
+ {emptyContent}
+
+ |
+
+ );
+}
+
+const EmptyState = styled.div`
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ padding: 0.25em;
+ gap: 0.5em;
+`;
+
type TableRowRenderer = (row: Row) => ReactNode;
function TableRow({
diff --git a/src/components/table/table_root.tsx b/src/components/table/table_root.tsx
index d515dbf0..27c46847 100644
--- a/src/components/table/table_root.tsx
+++ b/src/components/table/table_root.tsx
@@ -12,7 +12,12 @@ import {
useReactTable,
} from '@tanstack/react-table';
import { useVirtualizer } from '@tanstack/react-virtual';
-import type { CSSProperties, RefObject, TableHTMLAttributes } from 'react';
+import type {
+ CSSProperties,
+ ReactNode,
+ RefObject,
+ TableHTMLAttributes,
+} from 'react';
import { useEffect, useMemo, useRef } from 'react';
import { match } from 'ts-pattern';
@@ -185,6 +190,18 @@ interface TableBaseProps {
* Ignored when using custom row rendering with `renderRowTr`.
*/
renderRowPreview?: TableRowPreviewRenderer;
+
+ /**
+ * Icon to display when the table is empty.
+ * @default ``
+ */
+ emptyIcon?: ReactNode;
+
+ /**
+ * Content to display when the table is empty.
+ * @default `'No data'`
+ */
+ emptyContent?: ReactNode;
}
interface RegularTableProps<
@@ -244,6 +261,9 @@ export function Table(props: TableProps) {
renderRowPreview,
scrollToRowRef,
+
+ emptyIcon,
+ emptyContent,
} = props;
const isReorderingEnabled = !!onRowOrderChanged;
const virtualScrollElementRef = useRef(null);
@@ -294,13 +314,16 @@ export function Table(props: TableProps) {
}),
[className, getTdProps, renderRowTr, columns, compact],
);
+
+ const rows = table.getRowModel().rows;
+
return (
}
>
{
onRowOrderChanged?.(items.map((item) => item.original));
}}
@@ -341,7 +364,7 @@ export function Table(props: TableProps) {
/>
)}
(props: TableProps) {
virtualizeRows={virtualizeRows}
renderRowPreview={renderRowPreview}
isReorderingEnabled={isReorderingEnabled}
+ emptyIcon={emptyIcon}
+ emptyContent={emptyContent}
+ columns={table.getAllColumns().length}
/>
diff --git a/src/components/table/table_row_cell.tsx b/src/components/table/table_row_cell.tsx
index 8b6f80a4..e580b3fb 100644
--- a/src/components/table/table_row_cell.tsx
+++ b/src/components/table/table_row_cell.tsx
@@ -15,8 +15,9 @@ export function TableRowCell(
) {
const { cell, tdStyle, getTdProps } = props;
+ const tdStyleMeta = cell.column.columnDef.meta?.tdStyle;
const tdProps = getTdProps?.(cell);
- const style = { ...tdStyle, ...tdProps?.style };
+ const style = { ...tdStyleMeta, ...tdStyle, ...tdProps?.style };
return (
diff --git a/stories/components/table.stories.tsx b/stories/components/table.stories.tsx
index 904e0d10..f58b4b9a 100644
--- a/stories/components/table.stories.tsx
+++ b/stories/components/table.stories.tsx
@@ -6,7 +6,7 @@ import type { GetTdProps, TableProps } from '../../src/components/index.js';
import { Table, TableRowTr } from '../../src/components/index.js';
import { table } from '../data/data.js';
-import { columns } from './table_columns.js';
+import { columns, columnsNativeMeta } from './table_columns.js';
type TableRecord = (typeof table)[number];
@@ -111,3 +111,18 @@ export const WithTdStyle = {
export const Virtualized: Story = {
args: { virtualizeRows: true, estimatedRowHeight: () => 172 },
} satisfies Story;
+
+export const WithMetaThAndTdStyle: Story = {
+ args: {
+ data: table.slice(0, 1),
+ columns: columnsNativeMeta,
+ },
+};
+
+export const EmptyTable: Story = {
+ args: {
+ data: [],
+ emptyContent: 'No molecules',
+ emptyIcon: 🪹,
+ },
+};
diff --git a/stories/components/table_columns.tsx b/stories/components/table_columns.tsx
index c132f2a5..89476f69 100644
--- a/stories/components/table_columns.tsx
+++ b/stories/components/table_columns.tsx
@@ -65,3 +65,23 @@ export const columns = [
},
}),
];
+
+export const columnsNativeMeta = [
+ columnHelper.accessor('ocl.idCode', {
+ header: 'Molecule',
+ cell: ({ getValue }) => ,
+ }),
+ columnHelper.accessor('name', {
+ header: 'Name',
+ meta: {
+ thStyle: {
+ backgroundColor: 'black',
+ color: '#FFF903',
+ },
+ tdStyle: {
+ backgroundColor: '#DB261D',
+ color: '#FFF903',
+ },
+ },
+ }),
+];
| |