Skip to content

Commit 74c318f

Browse files
authored
feat: add support for tdStyle in meta column definition (#981)
feat: add supports for `emptyIcon` and `emptyContent` (#981) It's rendered when the table is empty (rows.length === 0)
1 parent da8ffa2 commit 74c318f

File tree

6 files changed

+132
-5
lines changed

6 files changed

+132
-5
lines changed

src/components/table/index.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,5 +14,10 @@ declare module '@tanstack/react-table' {
1414
* Merged into the `style` prop of the default-rendered `<th>` element.
1515
*/
1616
thStyle?: CSSProperties;
17+
18+
/**
19+
* Merged into the `style` prop of the default-rendered `<td>` element.
20+
*/
21+
tdStyle?: CSSProperties;
1722
}
1823
}

src/components/table/table_body.tsx

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
import { Icon } from '@blueprintjs/core';
2+
import styled from '@emotion/styled';
13
import type { Row, RowData } from '@tanstack/react-table';
24
import type { VirtualItem, Virtualizer } from '@tanstack/react-virtual';
35
import { notUndefined } from '@tanstack/react-virtual';
@@ -23,6 +25,14 @@ interface TableBodyProps<TData extends RowData> {
2325
virtualizer: Virtualizer<HTMLDivElement, Element>;
2426
isReorderingEnabled: boolean;
2527
renderRowPreview?: TableRowPreviewRenderer<TData>;
28+
29+
emptyContent: ReactNode;
30+
emptyIcon: ReactNode;
31+
32+
/**
33+
* Bound to `EmptyRow`, it is needed for td colSpan.
34+
*/
35+
columns: number;
2636
}
2737

2838
export function TableBody<TData extends RowData>(props: TableBodyProps<TData>) {
@@ -36,6 +46,9 @@ export function TableBody<TData extends RowData>(props: TableBodyProps<TData>) {
3646
) as TableRowTrRenderer<TData>,
3747
virtualizer,
3848
virtualizeRows,
49+
emptyContent,
50+
emptyIcon,
51+
columns,
3952
} = props;
4053

4154
if (virtualizeRows) {
@@ -72,6 +85,13 @@ export function TableBody<TData extends RowData>(props: TableBodyProps<TData>) {
7285
}}
7386
/>
7487
))}
88+
{virtualItems.length === 0 && (
89+
<EmptyRow
90+
emptyContent={emptyContent}
91+
emptyIcon={emptyIcon}
92+
columns={columns}
93+
/>
94+
)}
7595
{after > 0 && (
7696
<tr>
7797
<td style={{ height: after }} />
@@ -91,10 +111,50 @@ export function TableBody<TData extends RowData>(props: TableBodyProps<TData>) {
91111
}
92112
/>
93113
))}
114+
{rows.length === 0 && (
115+
<EmptyRow
116+
emptyContent={emptyContent}
117+
emptyIcon={emptyIcon}
118+
columns={columns}
119+
/>
120+
)}
94121
</tbody>
95122
);
96123
}
97124

125+
interface EmptyRowProps {
126+
emptyContent: ReactNode;
127+
emptyIcon: ReactNode;
128+
columns: number;
129+
}
130+
131+
function EmptyRow(props: EmptyRowProps) {
132+
const {
133+
emptyIcon = <Icon icon="eye-off" />,
134+
emptyContent = 'No data',
135+
columns,
136+
} = props;
137+
138+
return (
139+
<tr>
140+
<td colSpan={columns}>
141+
<EmptyState>
142+
{emptyIcon}
143+
{emptyContent}
144+
</EmptyState>
145+
</td>
146+
</tr>
147+
);
148+
}
149+
150+
const EmptyState = styled.div`
151+
display: flex;
152+
align-items: center;
153+
justify-content: center;
154+
padding: 0.25em;
155+
gap: 0.5em;
156+
`;
157+
98158
type TableRowRenderer<TData extends RowData> = (row: Row<TData>) => ReactNode;
99159

100160
function TableRow<TData>({

src/components/table/table_root.tsx

Lines changed: 29 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,12 @@ import {
1212
useReactTable,
1313
} from '@tanstack/react-table';
1414
import { useVirtualizer } from '@tanstack/react-virtual';
15-
import type { CSSProperties, RefObject, TableHTMLAttributes } from 'react';
15+
import type {
16+
CSSProperties,
17+
ReactNode,
18+
RefObject,
19+
TableHTMLAttributes,
20+
} from 'react';
1621
import { useEffect, useMemo, useRef } from 'react';
1722
import { match } from 'ts-pattern';
1823

@@ -185,6 +190,18 @@ interface TableBaseProps<TData extends RowData> {
185190
* Ignored when using custom row rendering with `renderRowTr`.
186191
*/
187192
renderRowPreview?: TableRowPreviewRenderer<TData>;
193+
194+
/**
195+
* Icon to display when the table is empty.
196+
* @default `<Icon icon="eye-off" />`
197+
*/
198+
emptyIcon?: ReactNode;
199+
200+
/**
201+
* Content to display when the table is empty.
202+
* @default `'No data'`
203+
*/
204+
emptyContent?: ReactNode;
188205
}
189206

190207
interface RegularTableProps<
@@ -244,6 +261,9 @@ export function Table<TData extends RowData>(props: TableProps<TData>) {
244261
renderRowPreview,
245262

246263
scrollToRowRef,
264+
265+
emptyIcon,
266+
emptyContent,
247267
} = props;
248268
const isReorderingEnabled = !!onRowOrderChanged;
249269
const virtualScrollElementRef = useRef<HTMLDivElement>(null);
@@ -294,13 +314,16 @@ export function Table<TData extends RowData>(props: TableProps<TData>) {
294314
}),
295315
[className, getTdProps, renderRowTr, columns, compact],
296316
);
317+
318+
const rows = table.getRowModel().rows;
319+
297320
return (
298321
<FlashedRowProvider>
299322
<PreviewTablePropsContextProvider
300323
value={tablePreviewProps as PreviewTablePropsContextValue<unknown>}
301324
>
302325
<ItemOrderProvider
303-
items={table.getRowModel().rows}
326+
items={rows}
304327
onOrderChanged={(items) => {
305328
onRowOrderChanged?.(items.map((item) => item.original));
306329
}}
@@ -341,14 +364,17 @@ export function Table<TData extends RowData>(props: TableProps<TData>) {
341364
/>
342365
)}
343366
<TableBody
344-
rows={table.getRowModel().rows}
367+
rows={rows}
345368
renderRowTr={renderRowTr}
346369
tdStyle={tdStyle}
347370
getTdProps={getTdProps}
348371
virtualizer={tanstackVirtualizer}
349372
virtualizeRows={virtualizeRows}
350373
renderRowPreview={renderRowPreview}
351374
isReorderingEnabled={isReorderingEnabled}
375+
emptyIcon={emptyIcon}
376+
emptyContent={emptyContent}
377+
columns={table.getAllColumns().length}
352378
/>
353379
</CustomHTMLTable>
354380
</ScrollContainer>

src/components/table/table_row_cell.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,9 @@ export function TableRowCell<TData extends RowData>(
1515
) {
1616
const { cell, tdStyle, getTdProps } = props;
1717

18+
const tdStyleMeta = cell.column.columnDef.meta?.tdStyle;
1819
const tdProps = getTdProps?.(cell);
19-
const style = { ...tdStyle, ...tdProps?.style };
20+
const style = { ...tdStyleMeta, ...tdStyle, ...tdProps?.style };
2021

2122
return (
2223
<td {...tdProps} style={style}>

stories/components/table.stories.tsx

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import type { GetTdProps, TableProps } from '../../src/components/index.js';
66
import { Table, TableRowTr } from '../../src/components/index.js';
77
import { table } from '../data/data.js';
88

9-
import { columns } from './table_columns.js';
9+
import { columns, columnsNativeMeta } from './table_columns.js';
1010

1111
type TableRecord = (typeof table)[number];
1212

@@ -111,3 +111,18 @@ export const WithTdStyle = {
111111
export const Virtualized: Story = {
112112
args: { virtualizeRows: true, estimatedRowHeight: () => 172 },
113113
} satisfies Story;
114+
115+
export const WithMetaThAndTdStyle: Story = {
116+
args: {
117+
data: table.slice(0, 1),
118+
columns: columnsNativeMeta,
119+
},
120+
};
121+
122+
export const EmptyTable: Story = {
123+
args: {
124+
data: [],
125+
emptyContent: 'No molecules',
126+
emptyIcon: <span aria-hidden="true">🪹</span>,
127+
},
128+
};

stories/components/table_columns.tsx

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,3 +65,23 @@ export const columns = [
6565
},
6666
}),
6767
];
68+
69+
export const columnsNativeMeta = [
70+
columnHelper.accessor('ocl.idCode', {
71+
header: 'Molecule',
72+
cell: ({ getValue }) => <IdcodeSvgRenderer idcode={getValue()} />,
73+
}),
74+
columnHelper.accessor('name', {
75+
header: 'Name',
76+
meta: {
77+
thStyle: {
78+
backgroundColor: 'black',
79+
color: '#FFF903',
80+
},
81+
tdStyle: {
82+
backgroundColor: '#DB261D',
83+
color: '#FFF903',
84+
},
85+
},
86+
}),
87+
];

0 commit comments

Comments
 (0)