@prairielearn/ui 1.0.0 → 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md
ADDED
|
@@ -3,10 +3,12 @@ import type { JSX } from 'preact/jsx-runtime';
|
|
|
3
3
|
import { type TanstackTableDownloadButtonProps } from './TanstackTableDownloadButton.js';
|
|
4
4
|
interface TanstackTableProps<RowDataModel> {
|
|
5
5
|
table: Table<RowDataModel>;
|
|
6
|
+
title: string;
|
|
6
7
|
filters?: Record<string, (props: {
|
|
7
8
|
header: Header<RowDataModel, unknown>;
|
|
8
9
|
}) => JSX.Element>;
|
|
9
10
|
rowHeight?: number;
|
|
11
|
+
noResultsState?: JSX.Element;
|
|
10
12
|
emptyState?: JSX.Element;
|
|
11
13
|
}
|
|
12
14
|
/**
|
|
@@ -16,17 +18,10 @@ interface TanstackTableProps<RowDataModel> {
|
|
|
16
18
|
* @param params.title - The title of the table
|
|
17
19
|
* @param params.filters - The filters for the table
|
|
18
20
|
* @param params.rowHeight - The height of the rows in the table
|
|
21
|
+
* @param params.noResultsState - The no results state for the table
|
|
19
22
|
* @param params.emptyState - The empty state for the table
|
|
20
23
|
*/
|
|
21
|
-
export declare function TanstackTable<RowDataModel>({ table, title, filters, rowHeight, emptyState, }:
|
|
22
|
-
table: Table<RowDataModel>;
|
|
23
|
-
title: string;
|
|
24
|
-
emptyState?: JSX.Element;
|
|
25
|
-
rowHeight?: number;
|
|
26
|
-
filters?: Record<string, (props: {
|
|
27
|
-
header: Header<RowDataModel, unknown>;
|
|
28
|
-
}) => JSX.Element>;
|
|
29
|
-
}): JSX.Element;
|
|
24
|
+
export declare function TanstackTable<RowDataModel>({ table, title, filters, rowHeight, noResultsState, emptyState, }: TanstackTableProps<RowDataModel>): JSX.Element;
|
|
30
25
|
/**
|
|
31
26
|
* A generic component that wraps the TanstackTable component in a card.
|
|
32
27
|
* @param params
|
|
@@ -48,7 +48,8 @@ function ResizeHandle({ header, setColumnSizing, }) {
|
|
|
48
48
|
transition: 'background-color 0.2s',
|
|
49
49
|
}, onMouseDown: header.getResizeHandler(), onTouchStart: header.getResizeHandler(), onKeyDown: handleKeyDown }) }));
|
|
50
50
|
}
|
|
51
|
-
const
|
|
51
|
+
const DefaultNoResultsState = (_jsxs(_Fragment, { children: [_jsx("i", { class: "bi bi-search display-4 mb-2", "aria-hidden": "true" }), _jsx("p", { class: "mb-0", children: "No results found matching your search criteria." })] }));
|
|
52
|
+
const DefaultEmptyState = (_jsxs(_Fragment, { children: [_jsx("i", { class: "bi bi-eye-slash display-4 mb-2", "aria-hidden": "true" }), _jsx("p", { class: "mb-0", children: "No results found." })] }));
|
|
52
53
|
const DEFAULT_FILTER_MAP = {};
|
|
53
54
|
/**
|
|
54
55
|
* A generic component that renders a full-width, resizeable Tanstack Table.
|
|
@@ -57,9 +58,10 @@ const DEFAULT_FILTER_MAP = {};
|
|
|
57
58
|
* @param params.title - The title of the table
|
|
58
59
|
* @param params.filters - The filters for the table
|
|
59
60
|
* @param params.rowHeight - The height of the rows in the table
|
|
61
|
+
* @param params.noResultsState - The no results state for the table
|
|
60
62
|
* @param params.emptyState - The empty state for the table
|
|
61
63
|
*/
|
|
62
|
-
export function TanstackTable({ table, title, filters = DEFAULT_FILTER_MAP, rowHeight = 42, emptyState = DefaultEmptyState, }) {
|
|
64
|
+
export function TanstackTable({ table, title, filters = DEFAULT_FILTER_MAP, rowHeight = 42, noResultsState = DefaultNoResultsState, emptyState = DefaultEmptyState, }) {
|
|
63
65
|
const parentRef = useRef(null);
|
|
64
66
|
const tableRef = useRef(null);
|
|
65
67
|
const rows = [...table.getTopRows(), ...table.getCenterRows()];
|
|
@@ -149,6 +151,8 @@ export function TanstackTable({ table, title, filters = DEFAULT_FILTER_MAP, rowH
|
|
|
149
151
|
return 'none';
|
|
150
152
|
}
|
|
151
153
|
};
|
|
154
|
+
const displayedCount = table.getRowModel().rows.length;
|
|
155
|
+
const totalCount = table.getCoreRowModel().rows.length;
|
|
152
156
|
return (_jsxs("div", { style: { position: 'relative' }, class: "d-flex flex-column h-100", children: [_jsx("div", { ref: parentRef, style: {
|
|
153
157
|
position: 'absolute',
|
|
154
158
|
top: 0,
|
|
@@ -224,14 +228,14 @@ export function TanstackTable({ table, title, filters = DEFAULT_FILTER_MAP, rowH
|
|
|
224
228
|
right: 0,
|
|
225
229
|
bottom: 0,
|
|
226
230
|
background: 'var(--bs-body-bg)',
|
|
227
|
-
}, role: "status", "aria-live": "polite", children: [_jsx("i", { class: "bi bi-eye-slash display-4 mb-2", "aria-hidden": "true" }), _jsx("p", { class: "mb-0", children: "No columns selected. Use the View menu to show columns." })] }) })),
|
|
231
|
+
}, role: "status", "aria-live": "polite", children: [_jsx("i", { class: "bi bi-eye-slash display-4 mb-2", "aria-hidden": "true" }), _jsx("p", { class: "mb-0", children: "No columns selected. Use the View menu to show columns." })] }) })), displayedCount === 0 && (_jsx("div", { class: "d-flex flex-column justify-content-center align-items-center text-muted py-4", style: {
|
|
228
232
|
position: 'absolute',
|
|
229
233
|
top: 0,
|
|
230
234
|
left: 0,
|
|
231
235
|
right: 0,
|
|
232
236
|
bottom: 0,
|
|
233
237
|
background: 'var(--bs-body-bg)',
|
|
234
|
-
}, role: "status", "aria-live": "polite", children: emptyState }))] }));
|
|
238
|
+
}, role: "status", "aria-live": "polite", children: totalCount > 0 ? noResultsState : emptyState }))] }));
|
|
235
239
|
}
|
|
236
240
|
/**
|
|
237
241
|
* A generic component that wraps the TanstackTable component in a card.
|
|
@@ -274,10 +278,12 @@ export function TanstackTableCard({ table, title, headerButtons, globalFilter, t
|
|
|
274
278
|
document.addEventListener('keydown', onKeyDown);
|
|
275
279
|
return () => document.removeEventListener('keydown', onKeyDown);
|
|
276
280
|
}, []);
|
|
281
|
+
const displayedCount = table.getRowModel().rows.length;
|
|
282
|
+
const totalCount = table.getCoreRowModel().rows.length;
|
|
277
283
|
return (_jsxs("div", { class: "card d-flex flex-column h-100", children: [_jsx("div", { class: "card-header bg-primary text-white", children: _jsxs("div", { class: "d-flex align-items-center justify-content-between gap-2", children: [_jsx("div", { children: title }), _jsxs("div", { class: "d-flex gap-2", children: [headerButtons, downloadButtonOptions && (_jsx(TanstackTableDownloadButton, { table: table, ...downloadButtonOptions }))] })] }) }), _jsxs("div", { class: "card-body d-flex flex-column", children: [_jsxs("div", { class: "d-flex flex-row flex-wrap align-items-center mb-3 gap-2", children: [_jsxs("div", { class: "flex-grow-1 flex-lg-grow-0 col-xl-6 col-lg-7 d-flex flex-row gap-2", children: [_jsxs("div", { class: "input-group", children: [_jsx("input", { ref: searchInputRef, type: "text", class: "form-control", "aria-label": globalFilter.placeholder, placeholder: globalFilter.placeholder, value: globalFilter.value, onInput: (e) => {
|
|
278
284
|
if (!(e.target instanceof HTMLInputElement))
|
|
279
285
|
return;
|
|
280
286
|
globalFilter.setValue(e.target.value);
|
|
281
|
-
} }), _jsx("button", { type: "button", class: "btn btn-outline-secondary", "aria-label": "Clear search", title: "Clear search", "data-bs-toggle": "tooltip", onClick: () => globalFilter.setValue(''), children: _jsx("i", { class: "bi bi-x-circle", "aria-hidden": "true" }) })] }), isMediumOrLarger && _jsx(ColumnManager, { table: table })] }), !isMediumOrLarger && _jsx(ColumnManager, { table: table }), _jsx("div", { class: "flex-lg-grow-1 d-flex flex-row justify-content-end", children: _jsxs("div", { class: "text-muted text-nowrap", children: ["Showing ",
|
|
287
|
+
} }), _jsx("button", { type: "button", class: "btn btn-outline-secondary", "aria-label": "Clear search", title: "Clear search", "data-bs-toggle": "tooltip", onClick: () => globalFilter.setValue(''), children: _jsx("i", { class: "bi bi-x-circle", "aria-hidden": "true" }) })] }), isMediumOrLarger && _jsx(ColumnManager, { table: table })] }), !isMediumOrLarger && _jsx(ColumnManager, { table: table }), _jsx("div", { class: "flex-lg-grow-1 d-flex flex-row justify-content-end", children: _jsxs("div", { class: "text-muted text-nowrap", children: ["Showing ", displayedCount, " of ", totalCount, " ", title.toLowerCase()] }) })] }), _jsx("div", { class: "flex-grow-1", children: _jsx(TanstackTable, { table: table, title: title, ...tableOptions }) })] })] }));
|
|
282
288
|
}
|
|
283
289
|
//# sourceMappingURL=TanstackTable.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"TanstackTable.js","sourceRoot":"","sources":["../../src/components/TanstackTable.tsx"],"names":[],"mappings":"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","sourcesContent":["import { flexRender } from '@tanstack/react-table';\nimport { notUndefined, useVirtualizer } from '@tanstack/react-virtual';\nimport type { Header, Row, SortDirection, Table } from '@tanstack/table-core';\nimport clsx from 'clsx';\nimport { useEffect, useRef, useState } from 'preact/hooks';\nimport type { JSX } from 'preact/jsx-runtime';\n\nimport { ColumnManager } from './ColumnManager.js';\nimport {\n TanstackTableDownloadButton,\n type TanstackTableDownloadButtonProps,\n} from './TanstackTableDownloadButton.js';\n\nfunction SortIcon({ sortMethod }: { sortMethod: false | SortDirection }) {\n if (sortMethod === 'asc') {\n return <i class=\"bi bi-sort-up-alt\" aria-hidden=\"true\" />;\n } else if (sortMethod === 'desc') {\n return <i class=\"bi bi-sort-down\" aria-hidden=\"true\" />;\n } else {\n return <i class=\"bi bi-arrow-down-up opacity-75 text-muted\" aria-hidden=\"true\" />;\n }\n}\n\nfunction ResizeHandle<RowDataModel>({\n header,\n setColumnSizing,\n}: {\n header: Header<RowDataModel, unknown>;\n setColumnSizing: Table<RowDataModel>['setColumnSizing'];\n}) {\n const minSize = header.column.columnDef.minSize ?? 0;\n const maxSize = header.column.columnDef.maxSize ?? 0;\n const handleKeyDown = (e: KeyboardEvent) => {\n if (e.key === 'ArrowLeft' || e.key === 'ArrowRight') {\n e.preventDefault();\n const currentSize = header.getSize();\n const increment = e.shiftKey ? 20 : 5; // Larger increment with Shift key\n const newSize =\n e.key === 'ArrowLeft'\n ? Math.max(minSize, currentSize - increment)\n : Math.min(maxSize, currentSize + increment);\n\n setColumnSizing((prevSizing) => ({\n ...prevSizing,\n [header.column.id]: newSize,\n }));\n } else if (e.key === 'Home') {\n e.preventDefault();\n header.column.resetSize();\n }\n };\n\n const columnName =\n typeof header.column.columnDef.header === 'string'\n ? header.column.columnDef.header\n : header.column.id;\n\n return (\n <div class=\"py-1 h-100\" style={{ position: 'absolute', right: 0, top: 0, width: '4px' }}>\n {/* separator role is focusable, so these jsx-a11y-x rules are false positives.\n https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/Reference/Roles/separator_role#focusable_separator\n */}\n {/* eslint-disable-next-line jsx-a11y-x/no-noninteractive-element-interactions */}\n <div\n role=\"separator\"\n aria-label={`Resize '${columnName}' column`}\n aria-valuetext={`${header.getSize()}px`}\n aria-orientation=\"vertical\"\n aria-valuemin={minSize}\n aria-valuemax={maxSize}\n aria-valuenow={header.getSize()}\n // eslint-disable-next-line jsx-a11y-x/no-noninteractive-tabindex\n tabIndex={0}\n class=\"h-100\"\n style={{\n background: header.column.getIsResizing() ? 'var(--bs-primary)' : 'var(--bs-gray-400)',\n cursor: 'col-resize',\n transition: 'background-color 0.2s',\n }}\n onMouseDown={header.getResizeHandler()}\n onTouchStart={header.getResizeHandler()}\n onKeyDown={handleKeyDown}\n />\n </div>\n );\n}\n\nconst DefaultEmptyState = (\n <>\n <i class=\"bi bi-search display-4 mb-2\" aria-hidden=\"true\" />\n <p class=\"mb-0\">No results found matching your search criteria.</p>\n </>\n);\n\ninterface TanstackTableProps<RowDataModel> {\n table: Table<RowDataModel>;\n filters?: Record<string, (props: { header: Header<RowDataModel, unknown> }) => JSX.Element>;\n rowHeight?: number;\n emptyState?: JSX.Element;\n}\n\nconst DEFAULT_FILTER_MAP = {};\n\n/**\n * A generic component that renders a full-width, resizeable Tanstack Table.\n * @param params\n * @param params.table - The table model\n * @param params.title - The title of the table\n * @param params.filters - The filters for the table\n * @param params.rowHeight - The height of the rows in the table\n * @param params.emptyState - The empty state for the table\n */\nexport function TanstackTable<RowDataModel>({\n table,\n title,\n filters = DEFAULT_FILTER_MAP,\n rowHeight = 42,\n emptyState = DefaultEmptyState,\n}: {\n table: Table<RowDataModel>;\n title: string;\n emptyState?: JSX.Element;\n rowHeight?: number;\n filters?: Record<string, (props: { header: Header<RowDataModel, unknown> }) => JSX.Element>;\n}) {\n const parentRef = useRef<HTMLDivElement>(null);\n const tableRef = useRef<HTMLDivElement>(null);\n const rows = [...table.getTopRows(), ...table.getCenterRows()];\n const rowVirtualizer = useVirtualizer({\n count: rows.length,\n getScrollElement: () => parentRef.current,\n estimateSize: () => rowHeight,\n overscan: 10,\n });\n\n // Track focused cell for grid navigation\n const [focusedCell, setFocusedCell] = useState<{ row: number; col: number }>({ row: 0, col: 0 });\n\n const getVisibleCells = (row: Row<RowDataModel>) => [\n ...row.getLeftVisibleCells(),\n ...row.getCenterVisibleCells(),\n ];\n\n const handleGridKeyDown = (e: KeyboardEvent, rowIdx: number, colIdx: number) => {\n const rowLength = getVisibleCells(rows[rowIdx]).length;\n const adjacentCells: Record<KeyboardEvent['key'], { row: number; col: number }> = {\n ArrowDown: {\n row: Math.min(rows.length - 1, rowIdx + 1),\n col: colIdx,\n },\n ArrowUp: {\n row: Math.max(0, rowIdx - 1),\n col: colIdx,\n },\n ArrowRight: {\n row: rowIdx,\n col: Math.min(rowLength - 1, colIdx + 1),\n },\n ArrowLeft: {\n row: rowIdx,\n col: Math.max(0, colIdx - 1),\n },\n };\n\n const next = adjacentCells[e.key];\n\n if (!next) {\n return;\n }\n\n setFocusedCell({ row: next.row, col: next.col });\n // If we are on the leftmost column, we should allow left scrolling.\n if (colIdx === 0 && e.key === 'ArrowLeft') {\n return;\n }\n\n // If we are on the top row, we should allow up scrolling.\n if (rowIdx === 0 && e.key === 'ArrowUp') {\n return;\n }\n\n // If we are on the rightmost column, we should allow right scrolling.\n if (colIdx === rowLength - 1 && e.key === 'ArrowRight') {\n return;\n }\n\n e.preventDefault();\n };\n\n useEffect(() => {\n const selector = `[data-grid-cell-row=\"${focusedCell.row}\"][data-grid-cell-col=\"${focusedCell.col}\"]`;\n const cell = tableRef.current?.querySelector(selector) as HTMLElement | null;\n if (!cell) {\n return;\n }\n cell.focus();\n }, [focusedCell]);\n\n const virtualRows = rowVirtualizer.getVirtualItems();\n const [before, after] =\n virtualRows.length > 0\n ? [\n notUndefined(virtualRows[0]).start - rowVirtualizer.options.scrollMargin,\n rowVirtualizer.getTotalSize() - notUndefined(virtualRows.at(-1)).end,\n ]\n : [0, 0];\n const headerGroups = table.getHeaderGroups();\n const isTableResizing = headerGroups.some((headerGroup) =>\n headerGroup.headers.some((header) => header.column.getIsResizing()),\n );\n const lastColumnId = table.getAllLeafColumns()[table.getAllLeafColumns().length - 1].id;\n\n const tableRect = tableRef.current?.getBoundingClientRect();\n\n // We toggle this here instead of in the parent since this component logically manages all UI for the table.\n // eslint-disable-next-line react-you-might-not-need-an-effect/no-manage-parent\n useEffect(() => {\n document.body.classList.toggle('no-user-select', isTableResizing);\n }, [isTableResizing]);\n\n // Helper function to get aria-sort value\n const getAriaSort = (sortDirection: false | SortDirection) => {\n switch (sortDirection) {\n case 'asc':\n return 'ascending';\n case 'desc':\n return 'descending';\n default:\n return 'none';\n }\n };\n\n return (\n <div style={{ position: 'relative' }} class=\"d-flex flex-column h-100\">\n <div\n ref={parentRef}\n style={{\n position: 'absolute',\n top: 0,\n left: 0,\n right: 0,\n bottom: 0,\n overflow: 'auto',\n overflowAnchor: 'none',\n }}\n >\n <div\n ref={tableRef}\n style={{\n position: 'relative',\n width: `max(${table.getTotalSize()}px, 100%)`,\n }}\n >\n <table\n class=\"table table-hover mb-0 border border-top-0\"\n style={{ tableLayout: 'fixed' }}\n aria-label={title}\n role=\"grid\"\n >\n <thead>\n {headerGroups.map((headerGroup) => (\n <tr key={headerGroup.id}>\n {headerGroup.headers.map((header, index) => {\n const isPinned = header.column.getIsPinned();\n const sortDirection = header.column.getIsSorted();\n const canSort = header.column.getCanSort();\n const canFilter = header.column.getCanFilter();\n const columnName =\n typeof header.column.columnDef.header === 'string'\n ? header.column.columnDef.header\n : header.column.id;\n\n const style: JSX.CSSProperties = {\n width:\n header.column.id === lastColumnId\n ? `max(100%, ${header.getSize()}px)`\n : header.getSize(),\n position: 'sticky',\n top: 0,\n zIndex: isPinned === 'left' ? 2 : 1,\n left: isPinned === 'left' ? header.getStart() : undefined,\n boxShadow:\n 'inset 0 calc(-1 * var(--bs-border-width)) 0 0 rgba(0, 0, 0, 1), inset 0 var(--bs-border-width) 0 0 var(--bs-border-color)',\n };\n\n return (\n <th\n key={header.id}\n class={clsx(isPinned === 'left' && 'bg-light')}\n style={style}\n aria-sort={canSort ? getAriaSort(sortDirection) : undefined}\n role=\"columnheader\"\n >\n <div class=\"d-flex align-items-center justify-content-between gap-2\">\n <button\n class=\"text-nowrap flex-grow-1 text-start\"\n style={{\n cursor: canSort ? 'pointer' : 'default',\n overflow: 'hidden',\n textOverflow: 'ellipsis',\n background: 'transparent',\n border: 'none',\n }}\n type=\"button\"\n aria-label={\n canSort\n ? `'${columnName}' column, current sort is ${getAriaSort(sortDirection)}`\n : undefined\n }\n onClick={canSort ? header.column.getToggleSortingHandler() : undefined}\n onKeyDown={\n canSort\n ? (e) => {\n const handleSort = header.column.getToggleSortingHandler();\n if (e.key === 'Enter' && handleSort) {\n e.preventDefault();\n handleSort(e);\n }\n }\n : undefined\n }\n >\n {header.isPlaceholder\n ? null\n : flexRender(header.column.columnDef.header, header.getContext())}\n {canSort && (\n <span class=\"ms-2\" aria-hidden=\"true\">\n <SortIcon sortMethod={sortDirection || false} />\n </span>\n )}\n {canSort && (\n <span class=\"visually-hidden\">\n , {getAriaSort(sortDirection)}, click to sort\n </span>\n )}\n </button>\n\n {canFilter && filters[header.column.id]?.({ header })}\n </div>\n {tableRect?.width &&\n tableRect.width > table.getTotalSize() &&\n index === headerGroup.headers.length - 1 ? null : (\n <ResizeHandle header={header} setColumnSizing={table.setColumnSizing} />\n )}\n </th>\n );\n })}\n </tr>\n ))}\n </thead>\n <tbody>\n {before > 0 && (\n <tr tabIndex={-1}>\n <td colSpan={headerGroups[0].headers.length} style={{ height: before }} />\n </tr>\n )}\n {virtualRows.map((virtualRow) => {\n const row = rows[virtualRow.index];\n const visibleCells = getVisibleCells(row);\n const rowIdx = virtualRow.index;\n\n return (\n <tr key={row.id} style={{ height: rowHeight }}>\n {visibleCells.map((cell, colIdx) => (\n <td\n key={cell.id}\n // You can tab to the most-recently focused cell.\n tabIndex={focusedCell.row === rowIdx && focusedCell.col === colIdx ? 0 : -1}\n // We store this so you can navigate around the grid.\n data-grid-cell-row={rowIdx}\n data-grid-cell-col={colIdx}\n style={{\n width:\n cell.column.id === lastColumnId\n ? `max(100%, ${cell.column.getSize()}px)`\n : cell.column.getSize(),\n position: cell.column.getIsPinned() === 'left' ? 'sticky' : undefined,\n left:\n cell.column.getIsPinned() === 'left'\n ? cell.column.getStart()\n : undefined,\n whiteSpace: 'nowrap',\n overflow: 'hidden',\n textOverflow: 'ellipsis',\n }}\n onFocus={() => setFocusedCell({ row: rowIdx, col: colIdx })}\n onKeyDown={(e) => handleGridKeyDown(e, rowIdx, colIdx)}\n >\n {flexRender(cell.column.columnDef.cell, cell.getContext())}\n </td>\n ))}\n </tr>\n );\n })}\n {after > 0 && (\n <tr tabIndex={-1}>\n <td colSpan={headerGroups[0].headers.length} style={{ height: after }} />\n </tr>\n )}\n </tbody>\n </table>\n </div>\n </div>\n\n {table.getVisibleLeafColumns().length === 0 && (\n <div>\n <div\n class=\"d-flex flex-column justify-content-center align-items-center text-muted py-4\"\n style={{\n position: 'absolute',\n top: 0,\n left: 0,\n right: 0,\n bottom: 0,\n background: 'var(--bs-body-bg)',\n }}\n role=\"status\"\n aria-live=\"polite\"\n >\n <i class=\"bi bi-eye-slash display-4 mb-2\" aria-hidden=\"true\" />\n <p class=\"mb-0\">No columns selected. Use the View menu to show columns.</p>\n </div>\n </div>\n )}\n {table.getRowModel().rows.length === 0 && (\n <div\n class=\"d-flex flex-column justify-content-center align-items-center text-muted py-4\"\n style={{\n position: 'absolute',\n top: 0,\n left: 0,\n right: 0,\n bottom: 0,\n background: 'var(--bs-body-bg)',\n }}\n role=\"status\"\n aria-live=\"polite\"\n >\n {emptyState}\n </div>\n )}\n </div>\n );\n}\n\n/**\n * A generic component that wraps the TanstackTable component in a card.\n * @param params\n * @param params.table - The table model\n * @param params.title - The title of the card\n * @param params.headerButtons - The buttons to display in the header\n * @param params.globalFilter - State management for the global filter\n * @param params.globalFilter.value\n * @param params.globalFilter.setValue\n * @param params.globalFilter.placeholder\n * @param params.tableOptions - Specific options for the table. See {@link TanstackTableProps} for more details.\n * @param params.downloadButtonOptions - Specific options for the download button. See {@link TanstackTableDownloadButtonProps} for more details.\n */\nexport function TanstackTableCard<RowDataModel>({\n table,\n title,\n headerButtons,\n globalFilter,\n tableOptions,\n downloadButtonOptions = null,\n}: {\n table: Table<RowDataModel>;\n title: string;\n headerButtons: JSX.Element;\n globalFilter: {\n value: string;\n setValue: (value: string) => void;\n placeholder: string;\n };\n tableOptions: Partial<Omit<TanstackTableProps<RowDataModel>, 'table'>>;\n downloadButtonOptions?: Omit<TanstackTableDownloadButtonProps<RowDataModel>, 'table'> | null;\n}) {\n const searchInputRef = useRef<HTMLInputElement>(null);\n\n // Track screen size for aria-hidden\n const mediaQuery = typeof window !== 'undefined' ? window.matchMedia('(min-width: 768px)') : null;\n const [isMediumOrLarger, setIsMediumOrLarger] = useState(false);\n\n useEffect(() => {\n // TODO: This is a workaround to avoid a hydration mismatch.\n // eslint-disable-next-line @eslint-react/hooks-extra/no-direct-set-state-in-use-effect\n setIsMediumOrLarger(mediaQuery?.matches ?? true);\n }, [mediaQuery]);\n\n useEffect(() => {\n const handler = (e: MediaQueryListEvent) => setIsMediumOrLarger(e.matches);\n mediaQuery?.addEventListener('change', handler);\n return () => mediaQuery?.removeEventListener('change', handler);\n }, [mediaQuery]);\n\n // Focus the search input when Ctrl+F is pressed\n useEffect(() => {\n function onKeyDown(event: KeyboardEvent) {\n if ((event.ctrlKey || event.metaKey) && event.key.toLowerCase() === 'f') {\n if (searchInputRef.current && searchInputRef.current !== document.activeElement) {\n searchInputRef.current.focus();\n event.preventDefault();\n }\n }\n }\n document.addEventListener('keydown', onKeyDown);\n return () => document.removeEventListener('keydown', onKeyDown);\n }, []);\n\n return (\n <div class=\"card d-flex flex-column h-100\">\n <div class=\"card-header bg-primary text-white\">\n <div class=\"d-flex align-items-center justify-content-between gap-2\">\n <div>{title}</div>\n <div class=\"d-flex gap-2\">\n {headerButtons}\n\n {downloadButtonOptions && (\n <TanstackTableDownloadButton table={table} {...downloadButtonOptions} />\n )}\n </div>\n </div>\n </div>\n <div class=\"card-body d-flex flex-column\">\n <div class=\"d-flex flex-row flex-wrap align-items-center mb-3 gap-2\">\n <div class=\"flex-grow-1 flex-lg-grow-0 col-xl-6 col-lg-7 d-flex flex-row gap-2\">\n <div class=\"input-group\">\n <input\n ref={searchInputRef}\n type=\"text\"\n class=\"form-control\"\n aria-label={globalFilter.placeholder}\n placeholder={globalFilter.placeholder}\n value={globalFilter.value}\n onInput={(e) => {\n if (!(e.target instanceof HTMLInputElement)) return;\n globalFilter.setValue(e.target.value);\n }}\n />\n <button\n type=\"button\"\n class=\"btn btn-outline-secondary\"\n aria-label=\"Clear search\"\n title=\"Clear search\"\n data-bs-toggle=\"tooltip\"\n onClick={() => globalFilter.setValue('')}\n >\n <i class=\"bi bi-x-circle\" aria-hidden=\"true\" />\n </button>\n </div>\n {/* We do this instead of CSS properties for the accessibility checker.\n We can't have two elements with the same id of 'column-manager-button'. */}\n {isMediumOrLarger && <ColumnManager table={table} />}\n </div>\n {/* We do this instead of CSS properties for the accessibility checker.\n We can't have two elements with the same id of 'column-manager-button'. */}\n {!isMediumOrLarger && <ColumnManager table={table} />}\n <div class=\"flex-lg-grow-1 d-flex flex-row justify-content-end\">\n <div class=\"text-muted text-nowrap\">\n Showing {table.getRowModel().rows.length} of {table.getCoreRowModel().rows.length}{' '}\n {title.toLowerCase()}\n </div>\n </div>\n </div>\n <div class=\"flex-grow-1\">\n <TanstackTable table={table} title={title} {...tableOptions} />\n </div>\n </div>\n </div>\n );\n}\n"]}
|
|
1
|
+
{"version":3,"file":"TanstackTable.js","sourceRoot":"","sources":["../../src/components/TanstackTable.tsx"],"names":[],"mappings":"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","sourcesContent":["import { flexRender } from '@tanstack/react-table';\nimport { notUndefined, useVirtualizer } from '@tanstack/react-virtual';\nimport type { Header, Row, SortDirection, Table } from '@tanstack/table-core';\nimport clsx from 'clsx';\nimport { useEffect, useRef, useState } from 'preact/hooks';\nimport type { JSX } from 'preact/jsx-runtime';\n\nimport { ColumnManager } from './ColumnManager.js';\nimport {\n TanstackTableDownloadButton,\n type TanstackTableDownloadButtonProps,\n} from './TanstackTableDownloadButton.js';\n\nfunction SortIcon({ sortMethod }: { sortMethod: false | SortDirection }) {\n if (sortMethod === 'asc') {\n return <i class=\"bi bi-sort-up-alt\" aria-hidden=\"true\" />;\n } else if (sortMethod === 'desc') {\n return <i class=\"bi bi-sort-down\" aria-hidden=\"true\" />;\n } else {\n return <i class=\"bi bi-arrow-down-up opacity-75 text-muted\" aria-hidden=\"true\" />;\n }\n}\n\nfunction ResizeHandle<RowDataModel>({\n header,\n setColumnSizing,\n}: {\n header: Header<RowDataModel, unknown>;\n setColumnSizing: Table<RowDataModel>['setColumnSizing'];\n}) {\n const minSize = header.column.columnDef.minSize ?? 0;\n const maxSize = header.column.columnDef.maxSize ?? 0;\n const handleKeyDown = (e: KeyboardEvent) => {\n if (e.key === 'ArrowLeft' || e.key === 'ArrowRight') {\n e.preventDefault();\n const currentSize = header.getSize();\n const increment = e.shiftKey ? 20 : 5; // Larger increment with Shift key\n const newSize =\n e.key === 'ArrowLeft'\n ? Math.max(minSize, currentSize - increment)\n : Math.min(maxSize, currentSize + increment);\n\n setColumnSizing((prevSizing) => ({\n ...prevSizing,\n [header.column.id]: newSize,\n }));\n } else if (e.key === 'Home') {\n e.preventDefault();\n header.column.resetSize();\n }\n };\n\n const columnName =\n typeof header.column.columnDef.header === 'string'\n ? header.column.columnDef.header\n : header.column.id;\n\n return (\n <div class=\"py-1 h-100\" style={{ position: 'absolute', right: 0, top: 0, width: '4px' }}>\n {/* separator role is focusable, so these jsx-a11y-x rules are false positives.\n https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/Reference/Roles/separator_role#focusable_separator\n */}\n {/* eslint-disable-next-line jsx-a11y-x/no-noninteractive-element-interactions */}\n <div\n role=\"separator\"\n aria-label={`Resize '${columnName}' column`}\n aria-valuetext={`${header.getSize()}px`}\n aria-orientation=\"vertical\"\n aria-valuemin={minSize}\n aria-valuemax={maxSize}\n aria-valuenow={header.getSize()}\n // eslint-disable-next-line jsx-a11y-x/no-noninteractive-tabindex\n tabIndex={0}\n class=\"h-100\"\n style={{\n background: header.column.getIsResizing() ? 'var(--bs-primary)' : 'var(--bs-gray-400)',\n cursor: 'col-resize',\n transition: 'background-color 0.2s',\n }}\n onMouseDown={header.getResizeHandler()}\n onTouchStart={header.getResizeHandler()}\n onKeyDown={handleKeyDown}\n />\n </div>\n );\n}\n\nconst DefaultNoResultsState = (\n <>\n <i class=\"bi bi-search display-4 mb-2\" aria-hidden=\"true\" />\n <p class=\"mb-0\">No results found matching your search criteria.</p>\n </>\n);\n\nconst DefaultEmptyState = (\n <>\n <i class=\"bi bi-eye-slash display-4 mb-2\" aria-hidden=\"true\" />\n <p class=\"mb-0\">No results found.</p>\n </>\n);\n\ninterface TanstackTableProps<RowDataModel> {\n table: Table<RowDataModel>;\n title: string;\n filters?: Record<string, (props: { header: Header<RowDataModel, unknown> }) => JSX.Element>;\n rowHeight?: number;\n noResultsState?: JSX.Element;\n emptyState?: JSX.Element;\n}\n\nconst DEFAULT_FILTER_MAP = {};\n\n/**\n * A generic component that renders a full-width, resizeable Tanstack Table.\n * @param params\n * @param params.table - The table model\n * @param params.title - The title of the table\n * @param params.filters - The filters for the table\n * @param params.rowHeight - The height of the rows in the table\n * @param params.noResultsState - The no results state for the table\n * @param params.emptyState - The empty state for the table\n */\nexport function TanstackTable<RowDataModel>({\n table,\n title,\n filters = DEFAULT_FILTER_MAP,\n rowHeight = 42,\n noResultsState = DefaultNoResultsState,\n emptyState = DefaultEmptyState,\n}: TanstackTableProps<RowDataModel>) {\n const parentRef = useRef<HTMLDivElement>(null);\n const tableRef = useRef<HTMLDivElement>(null);\n const rows = [...table.getTopRows(), ...table.getCenterRows()];\n const rowVirtualizer = useVirtualizer({\n count: rows.length,\n getScrollElement: () => parentRef.current,\n estimateSize: () => rowHeight,\n overscan: 10,\n });\n\n // Track focused cell for grid navigation\n const [focusedCell, setFocusedCell] = useState<{ row: number; col: number }>({ row: 0, col: 0 });\n\n const getVisibleCells = (row: Row<RowDataModel>) => [\n ...row.getLeftVisibleCells(),\n ...row.getCenterVisibleCells(),\n ];\n\n const handleGridKeyDown = (e: KeyboardEvent, rowIdx: number, colIdx: number) => {\n const rowLength = getVisibleCells(rows[rowIdx]).length;\n const adjacentCells: Record<KeyboardEvent['key'], { row: number; col: number }> = {\n ArrowDown: {\n row: Math.min(rows.length - 1, rowIdx + 1),\n col: colIdx,\n },\n ArrowUp: {\n row: Math.max(0, rowIdx - 1),\n col: colIdx,\n },\n ArrowRight: {\n row: rowIdx,\n col: Math.min(rowLength - 1, colIdx + 1),\n },\n ArrowLeft: {\n row: rowIdx,\n col: Math.max(0, colIdx - 1),\n },\n };\n\n const next = adjacentCells[e.key];\n\n if (!next) {\n return;\n }\n\n setFocusedCell({ row: next.row, col: next.col });\n // If we are on the leftmost column, we should allow left scrolling.\n if (colIdx === 0 && e.key === 'ArrowLeft') {\n return;\n }\n\n // If we are on the top row, we should allow up scrolling.\n if (rowIdx === 0 && e.key === 'ArrowUp') {\n return;\n }\n\n // If we are on the rightmost column, we should allow right scrolling.\n if (colIdx === rowLength - 1 && e.key === 'ArrowRight') {\n return;\n }\n\n e.preventDefault();\n };\n\n useEffect(() => {\n const selector = `[data-grid-cell-row=\"${focusedCell.row}\"][data-grid-cell-col=\"${focusedCell.col}\"]`;\n const cell = tableRef.current?.querySelector(selector) as HTMLElement | null;\n if (!cell) {\n return;\n }\n cell.focus();\n }, [focusedCell]);\n\n const virtualRows = rowVirtualizer.getVirtualItems();\n const [before, after] =\n virtualRows.length > 0\n ? [\n notUndefined(virtualRows[0]).start - rowVirtualizer.options.scrollMargin,\n rowVirtualizer.getTotalSize() - notUndefined(virtualRows.at(-1)).end,\n ]\n : [0, 0];\n const headerGroups = table.getHeaderGroups();\n const isTableResizing = headerGroups.some((headerGroup) =>\n headerGroup.headers.some((header) => header.column.getIsResizing()),\n );\n const lastColumnId = table.getAllLeafColumns()[table.getAllLeafColumns().length - 1].id;\n\n const tableRect = tableRef.current?.getBoundingClientRect();\n\n // We toggle this here instead of in the parent since this component logically manages all UI for the table.\n // eslint-disable-next-line react-you-might-not-need-an-effect/no-manage-parent\n useEffect(() => {\n document.body.classList.toggle('no-user-select', isTableResizing);\n }, [isTableResizing]);\n\n // Helper function to get aria-sort value\n const getAriaSort = (sortDirection: false | SortDirection) => {\n switch (sortDirection) {\n case 'asc':\n return 'ascending';\n case 'desc':\n return 'descending';\n default:\n return 'none';\n }\n };\n\n const displayedCount = table.getRowModel().rows.length;\n const totalCount = table.getCoreRowModel().rows.length;\n\n return (\n <div style={{ position: 'relative' }} class=\"d-flex flex-column h-100\">\n <div\n ref={parentRef}\n style={{\n position: 'absolute',\n top: 0,\n left: 0,\n right: 0,\n bottom: 0,\n overflow: 'auto',\n overflowAnchor: 'none',\n }}\n >\n <div\n ref={tableRef}\n style={{\n position: 'relative',\n width: `max(${table.getTotalSize()}px, 100%)`,\n }}\n >\n <table\n class=\"table table-hover mb-0 border border-top-0\"\n style={{ tableLayout: 'fixed' }}\n aria-label={title}\n role=\"grid\"\n >\n <thead>\n {headerGroups.map((headerGroup) => (\n <tr key={headerGroup.id}>\n {headerGroup.headers.map((header, index) => {\n const isPinned = header.column.getIsPinned();\n const sortDirection = header.column.getIsSorted();\n const canSort = header.column.getCanSort();\n const canFilter = header.column.getCanFilter();\n const columnName =\n typeof header.column.columnDef.header === 'string'\n ? header.column.columnDef.header\n : header.column.id;\n\n const style: JSX.CSSProperties = {\n width:\n header.column.id === lastColumnId\n ? `max(100%, ${header.getSize()}px)`\n : header.getSize(),\n position: 'sticky',\n top: 0,\n zIndex: isPinned === 'left' ? 2 : 1,\n left: isPinned === 'left' ? header.getStart() : undefined,\n boxShadow:\n 'inset 0 calc(-1 * var(--bs-border-width)) 0 0 rgba(0, 0, 0, 1), inset 0 var(--bs-border-width) 0 0 var(--bs-border-color)',\n };\n\n return (\n <th\n key={header.id}\n class={clsx(isPinned === 'left' && 'bg-light')}\n style={style}\n aria-sort={canSort ? getAriaSort(sortDirection) : undefined}\n role=\"columnheader\"\n >\n <div class=\"d-flex align-items-center justify-content-between gap-2\">\n <button\n class=\"text-nowrap flex-grow-1 text-start\"\n style={{\n cursor: canSort ? 'pointer' : 'default',\n overflow: 'hidden',\n textOverflow: 'ellipsis',\n background: 'transparent',\n border: 'none',\n }}\n type=\"button\"\n aria-label={\n canSort\n ? `'${columnName}' column, current sort is ${getAriaSort(sortDirection)}`\n : undefined\n }\n onClick={canSort ? header.column.getToggleSortingHandler() : undefined}\n onKeyDown={\n canSort\n ? (e) => {\n const handleSort = header.column.getToggleSortingHandler();\n if (e.key === 'Enter' && handleSort) {\n e.preventDefault();\n handleSort(e);\n }\n }\n : undefined\n }\n >\n {header.isPlaceholder\n ? null\n : flexRender(header.column.columnDef.header, header.getContext())}\n {canSort && (\n <span class=\"ms-2\" aria-hidden=\"true\">\n <SortIcon sortMethod={sortDirection || false} />\n </span>\n )}\n {canSort && (\n <span class=\"visually-hidden\">\n , {getAriaSort(sortDirection)}, click to sort\n </span>\n )}\n </button>\n\n {canFilter && filters[header.column.id]?.({ header })}\n </div>\n {tableRect?.width &&\n tableRect.width > table.getTotalSize() &&\n index === headerGroup.headers.length - 1 ? null : (\n <ResizeHandle header={header} setColumnSizing={table.setColumnSizing} />\n )}\n </th>\n );\n })}\n </tr>\n ))}\n </thead>\n <tbody>\n {before > 0 && (\n <tr tabIndex={-1}>\n <td colSpan={headerGroups[0].headers.length} style={{ height: before }} />\n </tr>\n )}\n {virtualRows.map((virtualRow) => {\n const row = rows[virtualRow.index];\n const visibleCells = getVisibleCells(row);\n const rowIdx = virtualRow.index;\n\n return (\n <tr key={row.id} style={{ height: rowHeight }}>\n {visibleCells.map((cell, colIdx) => (\n <td\n key={cell.id}\n // You can tab to the most-recently focused cell.\n tabIndex={focusedCell.row === rowIdx && focusedCell.col === colIdx ? 0 : -1}\n // We store this so you can navigate around the grid.\n data-grid-cell-row={rowIdx}\n data-grid-cell-col={colIdx}\n style={{\n width:\n cell.column.id === lastColumnId\n ? `max(100%, ${cell.column.getSize()}px)`\n : cell.column.getSize(),\n position: cell.column.getIsPinned() === 'left' ? 'sticky' : undefined,\n left:\n cell.column.getIsPinned() === 'left'\n ? cell.column.getStart()\n : undefined,\n whiteSpace: 'nowrap',\n overflow: 'hidden',\n textOverflow: 'ellipsis',\n }}\n onFocus={() => setFocusedCell({ row: rowIdx, col: colIdx })}\n onKeyDown={(e) => handleGridKeyDown(e, rowIdx, colIdx)}\n >\n {flexRender(cell.column.columnDef.cell, cell.getContext())}\n </td>\n ))}\n </tr>\n );\n })}\n {after > 0 && (\n <tr tabIndex={-1}>\n <td colSpan={headerGroups[0].headers.length} style={{ height: after }} />\n </tr>\n )}\n </tbody>\n </table>\n </div>\n </div>\n\n {table.getVisibleLeafColumns().length === 0 && (\n <div>\n <div\n class=\"d-flex flex-column justify-content-center align-items-center text-muted py-4\"\n style={{\n position: 'absolute',\n top: 0,\n left: 0,\n right: 0,\n bottom: 0,\n background: 'var(--bs-body-bg)',\n }}\n role=\"status\"\n aria-live=\"polite\"\n >\n <i class=\"bi bi-eye-slash display-4 mb-2\" aria-hidden=\"true\" />\n <p class=\"mb-0\">No columns selected. Use the View menu to show columns.</p>\n </div>\n </div>\n )}\n {displayedCount === 0 && (\n <div\n class=\"d-flex flex-column justify-content-center align-items-center text-muted py-4\"\n style={{\n position: 'absolute',\n top: 0,\n left: 0,\n right: 0,\n bottom: 0,\n background: 'var(--bs-body-bg)',\n }}\n role=\"status\"\n aria-live=\"polite\"\n >\n {totalCount > 0 ? noResultsState : emptyState}\n </div>\n )}\n </div>\n );\n}\n\n/**\n * A generic component that wraps the TanstackTable component in a card.\n * @param params\n * @param params.table - The table model\n * @param params.title - The title of the card\n * @param params.headerButtons - The buttons to display in the header\n * @param params.globalFilter - State management for the global filter\n * @param params.globalFilter.value\n * @param params.globalFilter.setValue\n * @param params.globalFilter.placeholder\n * @param params.tableOptions - Specific options for the table. See {@link TanstackTableProps} for more details.\n * @param params.downloadButtonOptions - Specific options for the download button. See {@link TanstackTableDownloadButtonProps} for more details.\n */\nexport function TanstackTableCard<RowDataModel>({\n table,\n title,\n headerButtons,\n globalFilter,\n tableOptions,\n downloadButtonOptions = null,\n}: {\n table: Table<RowDataModel>;\n title: string;\n headerButtons: JSX.Element;\n globalFilter: {\n value: string;\n setValue: (value: string) => void;\n placeholder: string;\n };\n tableOptions: Partial<Omit<TanstackTableProps<RowDataModel>, 'table'>>;\n downloadButtonOptions?: Omit<TanstackTableDownloadButtonProps<RowDataModel>, 'table'> | null;\n}) {\n const searchInputRef = useRef<HTMLInputElement>(null);\n\n // Track screen size for aria-hidden\n const mediaQuery = typeof window !== 'undefined' ? window.matchMedia('(min-width: 768px)') : null;\n const [isMediumOrLarger, setIsMediumOrLarger] = useState(false);\n\n useEffect(() => {\n // TODO: This is a workaround to avoid a hydration mismatch.\n // eslint-disable-next-line @eslint-react/hooks-extra/no-direct-set-state-in-use-effect\n setIsMediumOrLarger(mediaQuery?.matches ?? true);\n }, [mediaQuery]);\n\n useEffect(() => {\n const handler = (e: MediaQueryListEvent) => setIsMediumOrLarger(e.matches);\n mediaQuery?.addEventListener('change', handler);\n return () => mediaQuery?.removeEventListener('change', handler);\n }, [mediaQuery]);\n\n // Focus the search input when Ctrl+F is pressed\n useEffect(() => {\n function onKeyDown(event: KeyboardEvent) {\n if ((event.ctrlKey || event.metaKey) && event.key.toLowerCase() === 'f') {\n if (searchInputRef.current && searchInputRef.current !== document.activeElement) {\n searchInputRef.current.focus();\n event.preventDefault();\n }\n }\n }\n document.addEventListener('keydown', onKeyDown);\n return () => document.removeEventListener('keydown', onKeyDown);\n }, []);\n\n const displayedCount = table.getRowModel().rows.length;\n const totalCount = table.getCoreRowModel().rows.length;\n\n return (\n <div class=\"card d-flex flex-column h-100\">\n <div class=\"card-header bg-primary text-white\">\n <div class=\"d-flex align-items-center justify-content-between gap-2\">\n <div>{title}</div>\n <div class=\"d-flex gap-2\">\n {headerButtons}\n\n {downloadButtonOptions && (\n <TanstackTableDownloadButton table={table} {...downloadButtonOptions} />\n )}\n </div>\n </div>\n </div>\n <div class=\"card-body d-flex flex-column\">\n <div class=\"d-flex flex-row flex-wrap align-items-center mb-3 gap-2\">\n <div class=\"flex-grow-1 flex-lg-grow-0 col-xl-6 col-lg-7 d-flex flex-row gap-2\">\n <div class=\"input-group\">\n <input\n ref={searchInputRef}\n type=\"text\"\n class=\"form-control\"\n aria-label={globalFilter.placeholder}\n placeholder={globalFilter.placeholder}\n value={globalFilter.value}\n onInput={(e) => {\n if (!(e.target instanceof HTMLInputElement)) return;\n globalFilter.setValue(e.target.value);\n }}\n />\n <button\n type=\"button\"\n class=\"btn btn-outline-secondary\"\n aria-label=\"Clear search\"\n title=\"Clear search\"\n data-bs-toggle=\"tooltip\"\n onClick={() => globalFilter.setValue('')}\n >\n <i class=\"bi bi-x-circle\" aria-hidden=\"true\" />\n </button>\n </div>\n {/* We do this instead of CSS properties for the accessibility checker.\n We can't have two elements with the same id of 'column-manager-button'. */}\n {isMediumOrLarger && <ColumnManager table={table} />}\n </div>\n {/* We do this instead of CSS properties for the accessibility checker.\n We can't have two elements with the same id of 'column-manager-button'. */}\n {!isMediumOrLarger && <ColumnManager table={table} />}\n <div class=\"flex-lg-grow-1 d-flex flex-row justify-content-end\">\n <div class=\"text-muted text-nowrap\">\n Showing {displayedCount} of {totalCount} {title.toLowerCase()}\n </div>\n </div>\n </div>\n <div class=\"flex-grow-1\">\n <TanstackTable table={table} title={title} {...tableOptions} />\n </div>\n </div>\n </div>\n );\n}\n"]}
|
package/package.json
CHANGED
|
@@ -85,17 +85,26 @@ function ResizeHandle<RowDataModel>({
|
|
|
85
85
|
);
|
|
86
86
|
}
|
|
87
87
|
|
|
88
|
-
const
|
|
88
|
+
const DefaultNoResultsState = (
|
|
89
89
|
<>
|
|
90
90
|
<i class="bi bi-search display-4 mb-2" aria-hidden="true" />
|
|
91
91
|
<p class="mb-0">No results found matching your search criteria.</p>
|
|
92
92
|
</>
|
|
93
93
|
);
|
|
94
94
|
|
|
95
|
+
const DefaultEmptyState = (
|
|
96
|
+
<>
|
|
97
|
+
<i class="bi bi-eye-slash display-4 mb-2" aria-hidden="true" />
|
|
98
|
+
<p class="mb-0">No results found.</p>
|
|
99
|
+
</>
|
|
100
|
+
);
|
|
101
|
+
|
|
95
102
|
interface TanstackTableProps<RowDataModel> {
|
|
96
103
|
table: Table<RowDataModel>;
|
|
104
|
+
title: string;
|
|
97
105
|
filters?: Record<string, (props: { header: Header<RowDataModel, unknown> }) => JSX.Element>;
|
|
98
106
|
rowHeight?: number;
|
|
107
|
+
noResultsState?: JSX.Element;
|
|
99
108
|
emptyState?: JSX.Element;
|
|
100
109
|
}
|
|
101
110
|
|
|
@@ -108,6 +117,7 @@ const DEFAULT_FILTER_MAP = {};
|
|
|
108
117
|
* @param params.title - The title of the table
|
|
109
118
|
* @param params.filters - The filters for the table
|
|
110
119
|
* @param params.rowHeight - The height of the rows in the table
|
|
120
|
+
* @param params.noResultsState - The no results state for the table
|
|
111
121
|
* @param params.emptyState - The empty state for the table
|
|
112
122
|
*/
|
|
113
123
|
export function TanstackTable<RowDataModel>({
|
|
@@ -115,14 +125,9 @@ export function TanstackTable<RowDataModel>({
|
|
|
115
125
|
title,
|
|
116
126
|
filters = DEFAULT_FILTER_MAP,
|
|
117
127
|
rowHeight = 42,
|
|
128
|
+
noResultsState = DefaultNoResultsState,
|
|
118
129
|
emptyState = DefaultEmptyState,
|
|
119
|
-
}: {
|
|
120
|
-
table: Table<RowDataModel>;
|
|
121
|
-
title: string;
|
|
122
|
-
emptyState?: JSX.Element;
|
|
123
|
-
rowHeight?: number;
|
|
124
|
-
filters?: Record<string, (props: { header: Header<RowDataModel, unknown> }) => JSX.Element>;
|
|
125
|
-
}) {
|
|
130
|
+
}: TanstackTableProps<RowDataModel>) {
|
|
126
131
|
const parentRef = useRef<HTMLDivElement>(null);
|
|
127
132
|
const tableRef = useRef<HTMLDivElement>(null);
|
|
128
133
|
const rows = [...table.getTopRows(), ...table.getCenterRows()];
|
|
@@ -230,6 +235,9 @@ export function TanstackTable<RowDataModel>({
|
|
|
230
235
|
}
|
|
231
236
|
};
|
|
232
237
|
|
|
238
|
+
const displayedCount = table.getRowModel().rows.length;
|
|
239
|
+
const totalCount = table.getCoreRowModel().rows.length;
|
|
240
|
+
|
|
233
241
|
return (
|
|
234
242
|
<div style={{ position: 'relative' }} class="d-flex flex-column h-100">
|
|
235
243
|
<div
|
|
@@ -422,7 +430,7 @@ export function TanstackTable<RowDataModel>({
|
|
|
422
430
|
</div>
|
|
423
431
|
</div>
|
|
424
432
|
)}
|
|
425
|
-
{
|
|
433
|
+
{displayedCount === 0 && (
|
|
426
434
|
<div
|
|
427
435
|
class="d-flex flex-column justify-content-center align-items-center text-muted py-4"
|
|
428
436
|
style={{
|
|
@@ -436,7 +444,7 @@ export function TanstackTable<RowDataModel>({
|
|
|
436
444
|
role="status"
|
|
437
445
|
aria-live="polite"
|
|
438
446
|
>
|
|
439
|
-
{emptyState}
|
|
447
|
+
{totalCount > 0 ? noResultsState : emptyState}
|
|
440
448
|
</div>
|
|
441
449
|
)}
|
|
442
450
|
</div>
|
|
@@ -507,6 +515,9 @@ export function TanstackTableCard<RowDataModel>({
|
|
|
507
515
|
return () => document.removeEventListener('keydown', onKeyDown);
|
|
508
516
|
}, []);
|
|
509
517
|
|
|
518
|
+
const displayedCount = table.getRowModel().rows.length;
|
|
519
|
+
const totalCount = table.getCoreRowModel().rows.length;
|
|
520
|
+
|
|
510
521
|
return (
|
|
511
522
|
<div class="card d-flex flex-column h-100">
|
|
512
523
|
<div class="card-header bg-primary text-white">
|
|
@@ -557,8 +568,7 @@ export function TanstackTableCard<RowDataModel>({
|
|
|
557
568
|
{!isMediumOrLarger && <ColumnManager table={table} />}
|
|
558
569
|
<div class="flex-lg-grow-1 d-flex flex-row justify-content-end">
|
|
559
570
|
<div class="text-muted text-nowrap">
|
|
560
|
-
Showing {
|
|
561
|
-
{title.toLowerCase()}
|
|
571
|
+
Showing {displayedCount} of {totalCount} {title.toLowerCase()}
|
|
562
572
|
</div>
|
|
563
573
|
</div>
|
|
564
574
|
</div>
|