@prairielearn/ui 1.9.1 → 2.0.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 +23 -0
- package/dist/components/CategoricalColumnFilter.d.ts +9 -5
- package/dist/components/CategoricalColumnFilter.d.ts.map +1 -1
- package/dist/components/CategoricalColumnFilter.js +25 -9
- package/dist/components/CategoricalColumnFilter.js.map +1 -1
- package/dist/components/ColumnManager.d.ts +2 -2
- package/dist/components/ColumnManager.d.ts.map +1 -1
- package/dist/components/ColumnManager.js +40 -13
- package/dist/components/ColumnManager.js.map +1 -1
- package/dist/components/MultiSelectColumnFilter.d.ts +9 -6
- package/dist/components/MultiSelectColumnFilter.d.ts.map +1 -1
- package/dist/components/MultiSelectColumnFilter.js +20 -8
- package/dist/components/MultiSelectColumnFilter.js.map +1 -1
- package/dist/components/NumericInputColumnFilter.d.ts +2 -2
- package/dist/components/NumericInputColumnFilter.d.ts.map +1 -1
- package/dist/components/NumericInputColumnFilter.js +27 -10
- package/dist/components/NumericInputColumnFilter.js.map +1 -1
- package/dist/components/NumericInputColumnFilter.test.d.ts.map +1 -1
- package/dist/components/NumericInputColumnFilter.test.js.map +1 -1
- package/dist/components/OverlayTrigger.d.ts +1 -1
- package/dist/components/OverlayTrigger.d.ts.map +1 -1
- package/dist/components/OverlayTrigger.js +4 -3
- package/dist/components/OverlayTrigger.js.map +1 -1
- package/dist/components/PresetFilterDropdown.d.ts +2 -2
- package/dist/components/PresetFilterDropdown.d.ts.map +1 -1
- package/dist/components/PresetFilterDropdown.js +11 -5
- package/dist/components/PresetFilterDropdown.js.map +1 -1
- package/dist/components/TanstackTable.d.ts +7 -9
- package/dist/components/TanstackTable.d.ts.map +1 -1
- package/dist/components/TanstackTable.js +30 -15
- package/dist/components/TanstackTable.js.map +1 -1
- package/dist/components/TanstackTableDownloadButton.d.ts +1 -1
- package/dist/components/TanstackTableDownloadButton.d.ts.map +1 -1
- package/dist/components/TanstackTableDownloadButton.js +10 -2
- package/dist/components/TanstackTableDownloadButton.js.map +1 -1
- package/dist/components/TanstackTableHeaderCell.d.ts +3 -3
- package/dist/components/TanstackTableHeaderCell.d.ts.map +1 -1
- package/dist/components/TanstackTableHeaderCell.js +11 -9
- package/dist/components/TanstackTableHeaderCell.js.map +1 -1
- package/dist/components/nuqs.d.ts +1 -2
- package/dist/components/nuqs.d.ts.map +1 -1
- package/dist/components/nuqs.js +5 -5
- package/dist/components/nuqs.js.map +1 -1
- package/dist/components/nuqs.test.d.ts.map +1 -1
- package/dist/components/nuqs.test.js.map +1 -1
- package/dist/components/useAutoSizeColumns.d.ts +2 -3
- package/dist/components/useAutoSizeColumns.d.ts.map +1 -1
- package/dist/components/useAutoSizeColumns.js +12 -8
- package/dist/components/useAutoSizeColumns.js.map +1 -1
- package/dist/components/useShiftClickCheckbox.d.ts +3 -3
- package/dist/components/useShiftClickCheckbox.d.ts.map +1 -1
- package/dist/components/useShiftClickCheckbox.js +1 -1
- package/dist/components/useShiftClickCheckbox.js.map +1 -1
- package/dist/hooks/use-modal-state.d.ts.map +1 -1
- package/dist/hooks/use-modal-state.js +1 -1
- package/dist/hooks/use-modal-state.js.map +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/react-table.d.ts.map +1 -1
- package/package.json +13 -9
- package/src/components/CategoricalColumnFilter.tsx +49 -39
- package/src/components/ColumnManager.tsx +33 -24
- package/src/components/MultiSelectColumnFilter.tsx +29 -24
- package/src/components/NumericInputColumnFilter.tsx +13 -16
- package/src/components/OverlayTrigger.tsx +1 -1
- package/src/components/PresetFilterDropdown.tsx +4 -4
- package/src/components/TanstackTable.tsx +38 -33
- package/src/components/TanstackTableDownloadButton.tsx +11 -11
- package/src/components/TanstackTableHeaderCell.tsx +14 -14
- package/src/components/nuqs.tsx +5 -5
- package/src/components/useAutoSizeColumns.tsx +12 -11
- package/src/components/useShiftClickCheckbox.tsx +1 -1
- package/src/hooks/use-modal-state.ts +1 -1
- package/tsconfig.json +1 -1
|
@@ -1,7 +1,5 @@
|
|
|
1
1
|
import type { Header, Table } from '@tanstack/table-core';
|
|
2
|
-
import type
|
|
3
|
-
import type { JSX } from 'preact/jsx-runtime';
|
|
4
|
-
import type { ComponentProps } from '@prairielearn/preact-cjs';
|
|
2
|
+
import { type ComponentProps, type JSX, type ReactNode } from 'react';
|
|
5
3
|
import { type TanstackTableDownloadButtonProps } from './TanstackTableDownloadButton.js';
|
|
6
4
|
interface TanstackTableProps<RowDataModel> {
|
|
7
5
|
table: Table<RowDataModel>;
|
|
@@ -12,7 +10,7 @@ interface TanstackTableProps<RowDataModel> {
|
|
|
12
10
|
rowHeight?: number;
|
|
13
11
|
noResultsState?: JSX.Element;
|
|
14
12
|
emptyState?: JSX.Element;
|
|
15
|
-
scrollRef?: React.RefObject<HTMLDivElement> | null;
|
|
13
|
+
scrollRef?: React.RefObject<HTMLDivElement | null> | null;
|
|
16
14
|
}
|
|
17
15
|
/**
|
|
18
16
|
* A generic component that renders a full-width, resizeable Tanstack Table.
|
|
@@ -25,7 +23,7 @@ interface TanstackTableProps<RowDataModel> {
|
|
|
25
23
|
* @param params.emptyState - The empty state for the table
|
|
26
24
|
* @param params.scrollRef - Optional ref that will be attached to the scroll container element.
|
|
27
25
|
*/
|
|
28
|
-
export declare function TanstackTable<RowDataModel>({ table, title, filters, rowHeight, noResultsState, emptyState, scrollRef
|
|
26
|
+
export declare function TanstackTable<RowDataModel>({ table, title, filters, rowHeight, noResultsState, emptyState, scrollRef }: TanstackTableProps<RowDataModel>): import("react/jsx-runtime").JSX.Element;
|
|
29
27
|
/**
|
|
30
28
|
* A generic component that wraps the TanstackTable component in a card.
|
|
31
29
|
* @param params
|
|
@@ -62,10 +60,10 @@ export declare function TanstackTableCard<RowDataModel>({ table, title, singular
|
|
|
62
60
|
pluralLabel?: string;
|
|
63
61
|
singularLabel?: string;
|
|
64
62
|
};
|
|
65
|
-
} & Omit<ComponentProps<'div'>, 'class'>): JSX.Element;
|
|
66
|
-
export declare function TanstackTableEmptyState({ iconName, children
|
|
63
|
+
} & Omit<ComponentProps<'div'>, 'class'>): import("react/jsx-runtime").JSX.Element;
|
|
64
|
+
export declare function TanstackTableEmptyState({ iconName, children }: {
|
|
67
65
|
iconName: `bi-${string}`;
|
|
68
|
-
children:
|
|
69
|
-
}): JSX.Element;
|
|
66
|
+
children: ReactNode;
|
|
67
|
+
}): import("react/jsx-runtime").JSX.Element;
|
|
70
68
|
export {};
|
|
71
69
|
//# sourceMappingURL=TanstackTable.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"TanstackTable.d.ts","sourceRoot":"","sources":["../../src/components/TanstackTable.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAQ,MAAM,EAAO,KAAK,EAAE,MAAM,sBAAsB,CAAC;AAErE,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,QAAQ,CAAC;AAEhD,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,oBAAoB,CAAC;AAK9C,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,0BAA0B,CAAC;AAI/D,OAAO,EAEL,KAAK,gCAAgC,EACtC,MAAM,kCAAkC,CAAC;AAoE1C,UAAU,kBAAkB,CAAC,YAAY;IACvC,KAAK,EAAE,KAAK,CAAC,YAAY,CAAC,CAAC;IAC3B,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,CAAC,KAAK,EAAE;QAAE,MAAM,EAAE,MAAM,CAAC,YAAY,EAAE,OAAO,CAAC,CAAA;KAAE,KAAK,GAAG,CAAC,OAAO,CAAC,CAAC;IAC5F,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,cAAc,CAAC,EAAE,GAAG,CAAC,OAAO,CAAC;IAC7B,UAAU,CAAC,EAAE,GAAG,CAAC,OAAO,CAAC;IACzB,SAAS,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC,cAAc,CAAC,GAAG,IAAI,CAAC;CACpD;AAID;;;;;;;;;;GAUG;AACH,wBAAgB,aAAa,CAAC,YAAY,EAAE,EAC1C,KAAK,EACL,KAAK,EACL,OAA4B,EAC5B,SAAc,EACd,cAAsC,EACtC,UAA8B,EAC9B,SAAS,GACV,EAAE,kBAAkB,CAAC,YAAY,CAAC,eA4WlC;AAED;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,iBAAiB,CAAC,YAAY,EAAE,EAC9C,KAAK,EACL,KAAK,EACL,aAAa,EACb,WAAW,EACX,aAAa,EACb,aAAa,EACb,YAAY,EACZ,YAAY,EACZ,qBAAqB,EACrB,SAAS,EACT,GAAG,QAAQ,EACZ,EAAE;IACD,KAAK,EAAE,KAAK,CAAC,YAAY,CAAC,CAAC;IAC3B,KAAK,EAAE,MAAM,CAAC;IACd,aAAa,EAAE,MAAM,CAAC;IACtB,WAAW,EAAE,MAAM,CAAC;IACpB,aAAa,CAAC,EAAE,GAAG,CAAC,OAAO,CAAC;IAC5B,aAAa,CAAC,EAAE;QACd,OAAO,CAAC,EAAE,GAAG,CAAC,OAAO,CAAC;QACtB,UAAU,CAAC,EAAE,GAAG,CAAC,OAAO,CAAC;KAC1B,CAAC;IACF,YAAY,EAAE;QACZ,WAAW,EAAE,MAAM,CAAC;KACrB,CAAC;IACF,YAAY,EAAE,OAAO,CAAC,IAAI,CAAC,kBAAkB,CAAC,YAAY,CAAC,EAAE,OAAO,CAAC,CAAC,CAAC;IACvE,qBAAqB,CAAC,EAAE,IAAI,CAC1B,gCAAgC,CAAC,YAAY,CAAC,EAC9C,OAAO,GAAG,eAAe,GAAG,aAAa,CAC1C,GAAG;QAAE,WAAW,CAAC,EAAE,MAAM,CAAC;QAAC,aAAa,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;CACtD,GAAG,IAAI,CAAC,cAAc,CAAC,KAAK,CAAC,EAAE,OAAO,CAAC,eA8FvC;AAED,wBAAgB,uBAAuB,CAAC,EACtC,QAAQ,EACR,QAAQ,GACT,EAAE;IACD,QAAQ,EAAE,MAAM,MAAM,EAAE,CAAC;IACzB,QAAQ,EAAE,iBAAiB,CAAC;CAC7B,eAOA"}
|
|
1
|
+
{"version":3,"file":"TanstackTable.d.ts","sourceRoot":"","sources":["../../src/components/TanstackTable.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAQ,MAAM,EAAO,KAAK,EAAE,MAAM,sBAAsB,CAAC;AAErE,OAAO,EACL,KAAK,cAAc,EACnB,KAAK,GAAG,EACR,KAAK,SAAS,EAKf,MAAM,OAAO,CAAC;AAQf,OAAO,EAEL,KAAK,gCAAgC,EACtC,MAAM,kCAAkC,CAAC;AAoE1C,UAAU,kBAAkB,CAAC,YAAY;IACvC,KAAK,EAAE,KAAK,CAAC,YAAY,CAAC,CAAC;IAC3B,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,CAAC,KAAK,EAAE;QAAE,MAAM,EAAE,MAAM,CAAC,YAAY,EAAE,OAAO,CAAC,CAAA;KAAE,KAAK,GAAG,CAAC,OAAO,CAAC,CAAC;IAC5F,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,cAAc,CAAC,EAAE,GAAG,CAAC,OAAO,CAAC;IAC7B,UAAU,CAAC,EAAE,GAAG,CAAC,OAAO,CAAC;IACzB,SAAS,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC,cAAc,GAAG,IAAI,CAAC,GAAG,IAAI,CAAC;CAC3D;AAID;;;;;;;;;;GAUG;AACH,wBAAgB,aAAa,CAAC,YAAY,EAAE,EAC1C,KAAK,EACL,KAAK,EACL,OAA4B,EAC5B,SAAc,EACd,cAAsC,EACtC,UAA8B,EAC9B,SAAS,EACV,EAAE,kBAAkB,CAAC,YAAY,CAAC,2CA4WlC;AAED;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,iBAAiB,CAAC,YAAY,EAAE,EAC9C,KAAK,EACL,KAAK,EACL,aAAa,EACb,WAAW,EACX,aAAa,EACb,aAAa,EACb,YAAY,EACZ,YAAY,EACZ,qBAAqB,EACrB,SAAS,EACT,GAAG,QAAQ,EACZ,EAAE;IACD,KAAK,EAAE,KAAK,CAAC,YAAY,CAAC,CAAC;IAC3B,KAAK,EAAE,MAAM,CAAC;IACd,aAAa,EAAE,MAAM,CAAC;IACtB,WAAW,EAAE,MAAM,CAAC;IACpB,aAAa,CAAC,EAAE,GAAG,CAAC,OAAO,CAAC;IAC5B,aAAa,CAAC,EAAE;QACd,OAAO,CAAC,EAAE,GAAG,CAAC,OAAO,CAAC;QACtB,UAAU,CAAC,EAAE,GAAG,CAAC,OAAO,CAAC;KAC1B,CAAC;IACF,YAAY,EAAE;QACZ,WAAW,EAAE,MAAM,CAAC;KACrB,CAAC;IACF,YAAY,EAAE,OAAO,CAAC,IAAI,CAAC,kBAAkB,CAAC,YAAY,CAAC,EAAE,OAAO,CAAC,CAAC,CAAC;IACvE,qBAAqB,CAAC,EAAE,IAAI,CAC1B,gCAAgC,CAAC,YAAY,CAAC,EAC9C,OAAO,GAAG,eAAe,GAAG,aAAa,CAC1C,GAAG;QAAE,WAAW,CAAC,EAAE,MAAM,CAAC;QAAC,aAAa,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;CACtD,GAAG,IAAI,CAAC,cAAc,CAAC,KAAK,CAAC,EAAE,OAAO,CAAC,2CA8FvC;AAED,wBAAgB,uBAAuB,CAAC,EACtC,QAAQ,EACR,QAAQ,EACT,EAAE;IACD,QAAQ,EAAE,MAAM,MAAM,EAAE,CAAC;IACzB,QAAQ,EAAE,SAAS,CAAC;CACrB,2CAOA","sourcesContent":["import { flexRender } from '@tanstack/react-table';\nimport { useVirtualizer } from '@tanstack/react-virtual';\nimport type { Cell, Header, Row, Table } from '@tanstack/table-core';\nimport clsx from 'clsx';\nimport {\n type ComponentProps,\n type JSX,\n type ReactNode,\n useEffect,\n useMemo,\n useRef,\n useState,\n} from 'react';\nimport OverlayTrigger from 'react-bootstrap/OverlayTrigger';\nimport Tooltip from 'react-bootstrap/Tooltip';\nimport { useDebouncedCallback } from 'use-debounce';\n\nimport { run } from '@prairielearn/run';\n\nimport { ColumnManager } from './ColumnManager.js';\nimport {\n TanstackTableDownloadButton,\n type TanstackTableDownloadButtonProps,\n} from './TanstackTableDownloadButton.js';\nimport { TanstackTableHeaderCell } from './TanstackTableHeaderCell.js';\nimport { useAutoSizeColumns } from './useAutoSizeColumns.js';\n\nfunction TableCell<RowDataModel>({\n cell,\n rowIdx,\n colIdx,\n canSort,\n canFilter,\n wrapText,\n handleGridKeyDown,\n}: {\n cell: Cell<RowDataModel, unknown>;\n rowIdx: number;\n colIdx: number;\n canSort: boolean;\n canFilter: boolean;\n wrapText: boolean;\n handleGridKeyDown: (e: React.KeyboardEvent, rowIdx: number, colIdx: number) => void;\n}) {\n return (\n <td\n key={cell.id}\n tabIndex={0}\n data-grid-cell-row={rowIdx}\n data-grid-cell-col={colIdx}\n className={clsx(!canSort && !canFilter && 'text-center')}\n style={{\n display: 'flex',\n width: cell.column.getSize(),\n minWidth: 0,\n maxWidth: cell.column.getSize(),\n flexShrink: 0,\n position: cell.column.getIsPinned() === 'left' ? 'sticky' : undefined,\n left: cell.column.getIsPinned() === 'left' ? cell.column.getStart() : undefined,\n verticalAlign: 'middle',\n }}\n onKeyDown={(e) => handleGridKeyDown(e, rowIdx, colIdx)}\n >\n <div\n style={{\n display: 'block',\n minWidth: 0,\n maxWidth: '100%',\n overflow: wrapText ? 'visible' : 'hidden',\n textOverflow: wrapText ? undefined : 'ellipsis',\n whiteSpace: wrapText ? 'normal' : 'nowrap',\n flex: '1 1 0%',\n width: 0, // Allow flex to control width, but start from 0\n }}\n >\n {flexRender(cell.column.columnDef.cell, cell.getContext())}\n </div>\n </td>\n );\n}\n\nconst DefaultNoResultsState = (\n <TanstackTableEmptyState iconName=\"bi-search\">\n No results found matching your search criteria.\n </TanstackTableEmptyState>\n);\n\nconst DefaultEmptyState = (\n <TanstackTableEmptyState iconName=\"bi-eye-slash\">No results found.</TanstackTableEmptyState>\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 scrollRef?: React.RefObject<HTMLDivElement | null> | null;\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 * @param params.scrollRef - Optional ref that will be attached to the scroll container element.\n */\nexport function TanstackTable<RowDataModel>({\n table,\n title,\n filters = DEFAULT_FILTER_MAP,\n rowHeight = 42,\n noResultsState = DefaultNoResultsState,\n emptyState = DefaultEmptyState,\n scrollRef,\n}: TanstackTableProps<RowDataModel>) {\n const parentRef = useRef<HTMLDivElement>(null);\n const tableRef = useRef<HTMLDivElement>(null);\n const scrollContainerRef = scrollRef ?? parentRef;\n\n const rows = [...table.getTopRows(), ...table.getCenterRows()];\n const rowVirtualizer = useVirtualizer({\n count: rows.length,\n getScrollElement: () => scrollContainerRef.current,\n estimateSize: () => rowHeight,\n overscan: 10,\n measureElement: (el) => el?.getBoundingClientRect().height ?? rowHeight,\n });\n\n const visibleColumns = table.getVisibleLeafColumns();\n const centerColumns = visibleColumns.filter((col) => !col.getIsPinned());\n\n const columnVirtualizer = useVirtualizer({\n count: centerColumns.length,\n estimateSize: (index) => centerColumns[index]?.getSize(),\n // `useAutoSizeColumns` solves a different problem (happens once when the column set changes)\n // and we don't need to measure the cells themselves, so we can use the default estimateSize.\n getScrollElement: () => scrollContainerRef.current,\n horizontal: true,\n overscan: 3,\n });\n\n const virtualColumns = columnVirtualizer.getVirtualItems();\n\n const virtualPaddingLeft = run(() => {\n if (columnVirtualizer && virtualColumns?.length > 0) {\n return virtualColumns[0]?.start ?? 0;\n }\n return null;\n });\n\n const virtualPaddingRight = run(() => {\n if (columnVirtualizer && virtualColumns?.length > 0) {\n return (\n columnVirtualizer.getTotalSize() - (virtualColumns[virtualColumns.length - 1]?.end ?? 0)\n );\n }\n return null;\n });\n\n // Check if any column has wrapping enabled\n const hasWrappedColumns = table.getAllLeafColumns().some((col) => col.columnDef.meta?.wrapText);\n\n // Create callback for remeasuring after resize\n const handleResizeEnd = useMemo(() => {\n if (!hasWrappedColumns) return undefined;\n return () => rowVirtualizer.measure();\n }, [hasWrappedColumns, rowVirtualizer]);\n\n const getVisibleCells = (row: Row<RowDataModel>) => [\n ...row.getLeftVisibleCells(),\n ...row.getCenterVisibleCells(),\n ];\n\n const handleGridKeyDown = (e: React.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 // Only handle arrow keys if we're in the cell itself, not in an interactive element\n const target = e.target as HTMLElement;\n if (target.tagName === 'TD') {\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 const selector = `[data-grid-cell-row=\"${next.row}\"][data-grid-cell-col=\"${next.col}\"]`;\n const nextCell = tableRef.current?.querySelector(selector) as HTMLElement | null;\n nextCell?.focus();\n }\n };\n\n const virtualRows = rowVirtualizer.getVirtualItems();\n\n const headerGroups = table.getHeaderGroups();\n\n const leafHeaderGroup = headerGroups[headerGroups.length - 1];\n\n const leftPinnedHeaders = leafHeaderGroup.headers.filter(\n (header) => header.column.getIsPinned() === 'left',\n );\n const centerHeaders = leafHeaderGroup.headers.filter((header) => !header.column.getIsPinned());\n\n const isTableResizing = leafHeaderGroup.headers.some((header) => header.column.getIsResizing());\n\n // We toggle this here instead of in the parent since this component logically manages all UI for the table.\n useEffect(() => {\n document.body.classList.toggle('pl-ui-no-user-select', isTableResizing);\n }, [isTableResizing]);\n\n const hasAutoSized = useAutoSizeColumns(table, tableRef, filters);\n\n // Re-measure the virtualizer when auto-sizing completes\n useEffect(() => {\n if (hasAutoSized) {\n // https://github.com/NickvanDyke/eslint-plugin-react-you-might-not-need-an-effect/issues/58\n // eslint-disable-next-line react-you-might-not-need-an-effect/no-pass-ref-to-parent\n columnVirtualizer.measure();\n }\n }, [columnVirtualizer, hasAutoSized]);\n\n const displayedCount = table.getRowModel().rows.length;\n const totalCount = table.getCoreRowModel().rows.length;\n\n return (\n <div style={{ position: 'relative' }} className=\"d-flex flex-column h-100\">\n <div\n ref={scrollContainerRef}\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 className=\"table table-hover mb-0\"\n style={{ display: 'grid', tableLayout: 'fixed' }}\n aria-label={title}\n role=\"grid\"\n >\n <thead\n className=\"position-sticky top-0 w-100 border-top\"\n style={{\n display: 'grid',\n zIndex: 1,\n borderBottom: 'var(--bs-border-width) solid black',\n }}\n >\n <tr\n key={leafHeaderGroup.id}\n className=\"d-flex w-100\"\n style={{ minWidth: `${table.getTotalSize()}px` }}\n >\n {/* Left pinned columns */}\n {leftPinnedHeaders.map((header) => {\n return (\n <TanstackTableHeaderCell\n key={header.id}\n header={header}\n filters={filters}\n table={table}\n handleResizeEnd={handleResizeEnd}\n isPinned=\"left\"\n />\n );\n })}\n\n {/* Virtual padding for left side of center columns */}\n {virtualPaddingLeft ? (\n <th style={{ display: 'flex', width: virtualPaddingLeft }} />\n ) : null}\n\n {/* Virtualized center columns */}\n {virtualColumns.map((virtualColumn) => {\n const header = centerHeaders[virtualColumn.index];\n if (!header) return null;\n\n return (\n <TanstackTableHeaderCell\n key={header.id}\n header={header}\n filters={filters}\n table={table}\n handleResizeEnd={handleResizeEnd}\n isPinned={false}\n />\n );\n })}\n\n {/* Virtual padding for right side of center columns */}\n {virtualPaddingRight ? (\n <th style={{ display: 'flex', width: virtualPaddingRight }} />\n ) : null}\n\n {/* Filler to span remaining width */}\n <th\n tabIndex={-1}\n className=\"d-flex flex-grow-1 p-0\"\n style={{ minWidth: 0 }}\n aria-hidden=\"true\"\n />\n </tr>\n </thead>\n <tbody\n className=\"position-relative w-100\"\n style={{\n display: 'grid',\n height: `${rowVirtualizer.getTotalSize()}px`,\n }}\n >\n {virtualRows.map((virtualRow) => {\n const row = rows[virtualRow.index];\n const rowIdx = virtualRow.index;\n const leftPinnedCells = row.getLeftVisibleCells();\n const centerCells = row.getCenterVisibleCells();\n\n let currentColIdx = 0;\n\n return (\n <tr\n key={row.id}\n ref={(node) => rowVirtualizer.measureElement(node)}\n data-index={virtualRow.index}\n className=\"d-flex position-absolute w-100\"\n style={{\n transform: `translateY(${virtualRow.start}px)`,\n minWidth: `${table.getTotalSize()}px`,\n }}\n >\n {leftPinnedCells.map((cell) => {\n const colIdx = currentColIdx++;\n const canSort = cell.column.getCanSort();\n const canFilter = cell.column.getCanFilter();\n const wrapText = cell.column.columnDef.meta?.wrapText ?? false;\n\n return (\n <TableCell\n key={cell.id}\n cell={cell}\n rowIdx={rowIdx}\n colIdx={colIdx}\n canSort={canSort}\n canFilter={canFilter}\n wrapText={wrapText}\n handleGridKeyDown={handleGridKeyDown}\n />\n );\n })}\n\n {virtualPaddingLeft ? (\n <td style={{ display: 'flex', width: virtualPaddingLeft }} />\n ) : null}\n\n {virtualColumns.map((virtualColumn) => {\n const cell = centerCells[virtualColumn.index];\n if (!cell) return null;\n\n const colIdx = currentColIdx++;\n const canSort = cell.column.getCanSort();\n const canFilter = cell.column.getCanFilter();\n const wrapText = cell.column.columnDef.meta?.wrapText ?? false;\n\n return (\n <TableCell\n key={cell.id}\n cell={cell}\n rowIdx={rowIdx}\n colIdx={colIdx}\n canSort={canSort}\n canFilter={canFilter}\n wrapText={wrapText}\n handleGridKeyDown={handleGridKeyDown}\n />\n );\n })}\n\n {virtualPaddingRight ? (\n <td style={{ display: 'flex', width: virtualPaddingRight }} />\n ) : null}\n\n {/* Filler to span remaining width */}\n <td\n tabIndex={-1}\n className=\"d-flex flex-grow-1 p-0\"\n style={{ minWidth: 0 }}\n aria-hidden=\"true\"\n />\n </tr>\n );\n })}\n </tbody>\n </table>\n </div>\n </div>\n {table.getVisibleLeafColumns().length === 0 || displayedCount === 0 ? (\n <div>\n <div\n className=\"d-flex flex-column justify-content-center align-items-center p-4\"\n style={{\n position: 'absolute',\n top: 0,\n left: 0,\n right: 0,\n bottom: 0,\n // Allow pointer events (e.g. scrolling) to reach the underlying table.\n pointerEvents: 'none',\n }}\n role=\"status\"\n aria-live=\"polite\"\n >\n <div\n className=\"col-lg-6\"\n style={{\n // Allow selecting and interacting with the empty state content.\n pointerEvents: 'auto',\n }}\n >\n {table.getVisibleLeafColumns().length === 0 ? (\n <TanstackTableEmptyState iconName=\"bi-eye-slash\">\n No columns selected. Use the View menu to show columns.\n </TanstackTableEmptyState>\n ) : displayedCount === 0 ? (\n totalCount > 0 ? (\n noResultsState\n ) : (\n emptyState\n )\n ) : null}\n </div>\n </div>\n </div>\n ) : null}\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.className - The class name to apply to the card\n * @param params.style - The style to apply to the card\n * @param params.singularLabel - The singular label for a single row in the table, e.g. \"student\"\n * @param params.pluralLabel - The plural label for multiple rows in the table, e.g. \"students\"\n * @param params.headerButtons - The buttons to display in the header\n * @param params.columnManager - Optional configuration for the column manager. See {@link ColumnManager} for more details.\n * @param params.columnManager.buttons - The buttons to display next to the column manager (View button)\n * @param params.columnManager.topContent - Optional content to display at the top of the column manager (View) dropdown menu\n * @param params.globalFilter - Configuration for the global filter\n * @param params.globalFilter.placeholder - Placeholder text for the search input\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 singularLabel,\n pluralLabel,\n headerButtons,\n columnManager,\n globalFilter,\n tableOptions,\n downloadButtonOptions,\n className,\n ...divProps\n}: {\n table: Table<RowDataModel>;\n title: string;\n singularLabel: string;\n pluralLabel: string;\n headerButtons?: JSX.Element;\n columnManager?: {\n buttons?: JSX.Element;\n topContent?: JSX.Element;\n };\n globalFilter: {\n placeholder: string;\n };\n tableOptions: Partial<Omit<TanstackTableProps<RowDataModel>, 'table'>>;\n downloadButtonOptions?: Omit<\n TanstackTableDownloadButtonProps<RowDataModel>,\n 'table' | 'singularLabel' | 'pluralLabel'\n > & { pluralLabel?: string; singularLabel?: string };\n} & Omit<ComponentProps<'div'>, 'class'>) {\n const searchInputRef = useRef<HTMLInputElement>(null);\n\n const [inputValue, setInputValue] = useState(\n () => (table.getState().globalFilter as string) ?? '',\n );\n\n // Debounce the filter update\n const debouncedSetFilter = useDebouncedCallback((value: string) => {\n table.setGlobalFilter(value);\n }, 150);\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 className={clsx('card d-flex flex-column', className)} {...divProps}>\n <div className=\"card-header bg-primary text-white\">\n <div className=\"d-flex align-items-center justify-content-between gap-2\">\n <div>{title}</div>\n <div className=\"d-flex gap-2\">\n {headerButtons}\n\n {downloadButtonOptions && (\n <TanstackTableDownloadButton\n table={table}\n pluralLabel={pluralLabel}\n singularLabel={singularLabel}\n {...downloadButtonOptions}\n />\n )}\n </div>\n </div>\n </div>\n <div className=\"card-body d-flex flex-row flex-wrap flex-grow-0 align-items-center gap-2\">\n <div className=\"position-relative w-100\" style={{ maxWidth: 'min(400px, 100%)' }}>\n <input\n ref={searchInputRef}\n type=\"text\"\n className=\"form-control pl-ui-tanstack-table-search-input pl-ui-tanstack-table-focusable-shadow\"\n aria-label={globalFilter.placeholder}\n placeholder={globalFilter.placeholder}\n value={inputValue}\n autoComplete=\"off\"\n onInput={(e) => {\n const value = e.currentTarget.value;\n setInputValue(value);\n debouncedSetFilter(value);\n }}\n />\n {inputValue && (\n <OverlayTrigger overlay={<Tooltip>Clear search</Tooltip>}>\n <button\n type=\"button\"\n className=\"btn btn-floating-icon\"\n aria-label=\"Clear search\"\n onClick={() => {\n setInputValue('');\n debouncedSetFilter.cancel();\n table.setGlobalFilter('');\n }}\n >\n <i className=\"bi bi-x-circle-fill\" aria-hidden=\"true\" />\n </button>\n </OverlayTrigger>\n )}\n </div>\n <div className=\"d-flex flex-wrap flex-row align-items-center gap-2\">\n <ColumnManager table={table} topContent={columnManager?.topContent} />\n {columnManager?.buttons}\n </div>\n <div className=\"ms-auto text-muted text-nowrap\">\n Showing {displayedCount} of {totalCount} {totalCount === 1 ? singularLabel : pluralLabel}\n </div>\n </div>\n <div className=\"flex-grow-1\">\n <TanstackTable table={table} title={title} {...tableOptions} />\n </div>\n </div>\n );\n}\n\nexport function TanstackTableEmptyState({\n iconName,\n children,\n}: {\n iconName: `bi-${string}`;\n children: ReactNode;\n}) {\n return (\n <div className=\"d-flex flex-column justify-content-center align-items-center text-muted\">\n <i className={clsx('bi', iconName, 'display-4 mb-2')} aria-hidden=\"true\" />\n <div>{children}</div>\n </div>\n );\n}\n"]}
|
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import { jsx as _jsx, jsxs as _jsxs } from "
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
import { flexRender } from '@tanstack/react-table';
|
|
3
3
|
import { useVirtualizer } from '@tanstack/react-virtual';
|
|
4
4
|
import clsx from 'clsx';
|
|
5
|
-
import { useEffect, useMemo, useRef, useState } from '
|
|
5
|
+
import { useEffect, useMemo, useRef, useState, } from 'react';
|
|
6
6
|
import OverlayTrigger from 'react-bootstrap/OverlayTrigger';
|
|
7
7
|
import Tooltip from 'react-bootstrap/Tooltip';
|
|
8
8
|
import { useDebouncedCallback } from 'use-debounce';
|
|
@@ -12,7 +12,7 @@ import { TanstackTableDownloadButton, } from './TanstackTableDownloadButton.js';
|
|
|
12
12
|
import { TanstackTableHeaderCell } from './TanstackTableHeaderCell.js';
|
|
13
13
|
import { useAutoSizeColumns } from './useAutoSizeColumns.js';
|
|
14
14
|
function TableCell({ cell, rowIdx, colIdx, canSort, canFilter, wrapText, handleGridKeyDown, }) {
|
|
15
|
-
return (_jsx("td", { tabIndex: 0, "data-grid-cell-row": rowIdx, "data-grid-cell-col": colIdx,
|
|
15
|
+
return (_jsx("td", { tabIndex: 0, "data-grid-cell-row": rowIdx, "data-grid-cell-col": colIdx, className: clsx(!canSort && !canFilter && 'text-center'), style: {
|
|
16
16
|
display: 'flex',
|
|
17
17
|
width: cell.column.getSize(),
|
|
18
18
|
minWidth: 0,
|
|
@@ -160,7 +160,8 @@ export function TanstackTable({ table, title, filters = DEFAULT_FILTER_MAP, rowH
|
|
|
160
160
|
}, [columnVirtualizer, hasAutoSized]);
|
|
161
161
|
const displayedCount = table.getRowModel().rows.length;
|
|
162
162
|
const totalCount = table.getCoreRowModel().rows.length;
|
|
163
|
-
return (_jsxs("div", { style: { position: 'relative' },
|
|
163
|
+
return (_jsxs("div", { style: { position: 'relative' }, className: "d-flex flex-column h-100", children: [
|
|
164
|
+
_jsx("div", { ref: scrollContainerRef, style: {
|
|
164
165
|
position: 'absolute',
|
|
165
166
|
top: 0,
|
|
166
167
|
left: 0,
|
|
@@ -171,18 +172,20 @@ export function TanstackTable({ table, title, filters = DEFAULT_FILTER_MAP, rowH
|
|
|
171
172
|
}, children: _jsx("div", { ref: tableRef, style: {
|
|
172
173
|
position: 'relative',
|
|
173
174
|
width: `max(${table.getTotalSize()}px, 100%)`,
|
|
174
|
-
}, children: _jsxs("table", {
|
|
175
|
+
}, children: _jsxs("table", { className: "table table-hover mb-0", style: { display: 'grid', tableLayout: 'fixed' }, "aria-label": title, role: "grid", children: [
|
|
176
|
+
_jsx("thead", { className: "position-sticky top-0 w-100 border-top", style: {
|
|
175
177
|
display: 'grid',
|
|
176
178
|
zIndex: 1,
|
|
177
179
|
borderBottom: 'var(--bs-border-width) solid black',
|
|
178
|
-
}, children: _jsxs("tr", {
|
|
180
|
+
}, children: _jsxs("tr", { className: "d-flex w-100", style: { minWidth: `${table.getTotalSize()}px` }, children: [leftPinnedHeaders.map((header) => {
|
|
179
181
|
return (_jsx(TanstackTableHeaderCell, { header: header, filters: filters, table: table, handleResizeEnd: handleResizeEnd, isPinned: "left" }, header.id));
|
|
180
182
|
}), virtualPaddingLeft ? (_jsx("th", { style: { display: 'flex', width: virtualPaddingLeft } })) : null, virtualColumns.map((virtualColumn) => {
|
|
181
183
|
const header = centerHeaders[virtualColumn.index];
|
|
182
184
|
if (!header)
|
|
183
185
|
return null;
|
|
184
186
|
return (_jsx(TanstackTableHeaderCell, { header: header, filters: filters, table: table, handleResizeEnd: handleResizeEnd, isPinned: false }, header.id));
|
|
185
|
-
}), virtualPaddingRight ? (_jsx("th", { style: { display: 'flex', width: virtualPaddingRight } })) : null, _jsx("th", { tabIndex: -1,
|
|
187
|
+
}), virtualPaddingRight ? (_jsx("th", { style: { display: 'flex', width: virtualPaddingRight } })) : null, _jsx("th", { tabIndex: -1, className: "d-flex flex-grow-1 p-0", style: { minWidth: 0 }, "aria-hidden": "true" })
|
|
188
|
+
] }, leafHeaderGroup.id) }), _jsx("tbody", { className: "position-relative w-100", style: {
|
|
186
189
|
display: 'grid',
|
|
187
190
|
height: `${rowVirtualizer.getTotalSize()}px`,
|
|
188
191
|
}, children: virtualRows.map((virtualRow) => {
|
|
@@ -191,7 +194,7 @@ export function TanstackTable({ table, title, filters = DEFAULT_FILTER_MAP, rowH
|
|
|
191
194
|
const leftPinnedCells = row.getLeftVisibleCells();
|
|
192
195
|
const centerCells = row.getCenterVisibleCells();
|
|
193
196
|
let currentColIdx = 0;
|
|
194
|
-
return (_jsxs("tr", { ref: (node) => rowVirtualizer.measureElement(node), "data-index": virtualRow.index,
|
|
197
|
+
return (_jsxs("tr", { ref: (node) => rowVirtualizer.measureElement(node), "data-index": virtualRow.index, className: "d-flex position-absolute w-100", style: {
|
|
195
198
|
transform: `translateY(${virtualRow.start}px)`,
|
|
196
199
|
minWidth: `${table.getTotalSize()}px`,
|
|
197
200
|
}, children: [leftPinnedCells.map((cell) => {
|
|
@@ -209,8 +212,10 @@ export function TanstackTable({ table, title, filters = DEFAULT_FILTER_MAP, rowH
|
|
|
209
212
|
const canFilter = cell.column.getCanFilter();
|
|
210
213
|
const wrapText = cell.column.columnDef.meta?.wrapText ?? false;
|
|
211
214
|
return (_jsx(TableCell, { cell: cell, rowIdx: rowIdx, colIdx: colIdx, canSort: canSort, canFilter: canFilter, wrapText: wrapText, handleGridKeyDown: handleGridKeyDown }, cell.id));
|
|
212
|
-
}), virtualPaddingRight ? (_jsx("td", { style: { display: 'flex', width: virtualPaddingRight } })) : null, _jsx("td", { tabIndex: -1,
|
|
213
|
-
|
|
215
|
+
}), virtualPaddingRight ? (_jsx("td", { style: { display: 'flex', width: virtualPaddingRight } })) : null, _jsx("td", { tabIndex: -1, className: "d-flex flex-grow-1 p-0", style: { minWidth: 0 }, "aria-hidden": "true" })
|
|
216
|
+
] }, row.id));
|
|
217
|
+
}) })
|
|
218
|
+
] }) }) }), table.getVisibleLeafColumns().length === 0 || displayedCount === 0 ? (_jsx("div", { children: _jsx("div", { className: "d-flex flex-column justify-content-center align-items-center p-4", style: {
|
|
214
219
|
position: 'absolute',
|
|
215
220
|
top: 0,
|
|
216
221
|
left: 0,
|
|
@@ -218,7 +223,7 @@ export function TanstackTable({ table, title, filters = DEFAULT_FILTER_MAP, rowH
|
|
|
218
223
|
bottom: 0,
|
|
219
224
|
// Allow pointer events (e.g. scrolling) to reach the underlying table.
|
|
220
225
|
pointerEvents: 'none',
|
|
221
|
-
}, role: "status", "aria-live": "polite", children: _jsx("div", {
|
|
226
|
+
}, role: "status", "aria-live": "polite", children: _jsx("div", { className: "col-lg-6", style: {
|
|
222
227
|
// Allow selecting and interacting with the empty state content.
|
|
223
228
|
pointerEvents: 'auto',
|
|
224
229
|
}, children: table.getVisibleLeafColumns().length === 0 ? (_jsx(TanstackTableEmptyState, { iconName: "bi-eye-slash", children: "No columns selected. Use the View menu to show columns." })) : displayedCount === 0 ? (totalCount > 0 ? (noResultsState) : (emptyState)) : null }) }) })) : null] }));
|
|
@@ -263,17 +268,27 @@ export function TanstackTableCard({ table, title, singularLabel, pluralLabel, he
|
|
|
263
268
|
}, []);
|
|
264
269
|
const displayedCount = table.getRowModel().rows.length;
|
|
265
270
|
const totalCount = table.getCoreRowModel().rows.length;
|
|
266
|
-
return (_jsxs("div", {
|
|
271
|
+
return (_jsxs("div", { className: clsx('card d-flex flex-column', className), ...divProps, children: [
|
|
272
|
+
_jsx("div", { className: "card-header bg-primary text-white", children: _jsxs("div", { className: "d-flex align-items-center justify-content-between gap-2", children: [
|
|
273
|
+
_jsx("div", { children: title }), _jsxs("div", { className: "d-flex gap-2", children: [headerButtons, downloadButtonOptions && (_jsx(TanstackTableDownloadButton, { table: table, pluralLabel: pluralLabel, singularLabel: singularLabel, ...downloadButtonOptions }))] })
|
|
274
|
+
] }) }), _jsxs("div", { className: "card-body d-flex flex-row flex-wrap flex-grow-0 align-items-center gap-2", children: [
|
|
275
|
+
_jsxs("div", { className: "position-relative w-100", style: { maxWidth: 'min(400px, 100%)' }, children: [
|
|
276
|
+
_jsx("input", { ref: searchInputRef, type: "text", className: "form-control pl-ui-tanstack-table-search-input pl-ui-tanstack-table-focusable-shadow", "aria-label": globalFilter.placeholder, placeholder: globalFilter.placeholder, value: inputValue, autoComplete: "off", onInput: (e) => {
|
|
267
277
|
const value = e.currentTarget.value;
|
|
268
278
|
setInputValue(value);
|
|
269
279
|
debouncedSetFilter(value);
|
|
270
|
-
} }), inputValue && (_jsx(OverlayTrigger, { overlay: _jsx(Tooltip, { children: "Clear search" }), children: _jsx("button", { type: "button",
|
|
280
|
+
} }), inputValue && (_jsx(OverlayTrigger, { overlay: _jsx(Tooltip, { children: "Clear search" }), children: _jsx("button", { type: "button", className: "btn btn-floating-icon", "aria-label": "Clear search", onClick: () => {
|
|
271
281
|
setInputValue('');
|
|
272
282
|
debouncedSetFilter.cancel();
|
|
273
283
|
table.setGlobalFilter('');
|
|
274
|
-
}, children: _jsx("i", {
|
|
284
|
+
}, children: _jsx("i", { className: "bi bi-x-circle-fill", "aria-hidden": "true" }) }) }))] }), _jsxs("div", { className: "d-flex flex-wrap flex-row align-items-center gap-2", children: [
|
|
285
|
+
_jsx(ColumnManager, { table: table, topContent: columnManager?.topContent }), columnManager?.buttons] }), _jsxs("div", { className: "ms-auto text-muted text-nowrap", children: ["Showing ", displayedCount, " of ", totalCount, " ", totalCount === 1 ? singularLabel : pluralLabel] })
|
|
286
|
+
] }), _jsx("div", { className: "flex-grow-1", children: _jsx(TanstackTable, { table: table, title: title, ...tableOptions }) })
|
|
287
|
+
] }));
|
|
275
288
|
}
|
|
276
289
|
export function TanstackTableEmptyState({ iconName, children, }) {
|
|
277
|
-
return (_jsxs("div", {
|
|
290
|
+
return (_jsxs("div", { className: "d-flex flex-column justify-content-center align-items-center text-muted", children: [
|
|
291
|
+
_jsx("i", { className: clsx('bi', iconName, 'display-4 mb-2'), "aria-hidden": "true" }), _jsx("div", { children: children })
|
|
292
|
+
] }));
|
|
278
293
|
}
|
|
279
294
|
//# 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 { useVirtualizer } from '@tanstack/react-virtual';\nimport type { Cell, Header, Row, Table } from '@tanstack/table-core';\nimport clsx from 'clsx';\nimport type { ComponentChildren } from 'preact';\nimport { useEffect, useMemo, useRef, useState } from 'preact/hooks';\nimport type { JSX } from 'preact/jsx-runtime';\nimport OverlayTrigger from 'react-bootstrap/OverlayTrigger';\nimport Tooltip from 'react-bootstrap/Tooltip';\nimport { useDebouncedCallback } from 'use-debounce';\n\nimport type { ComponentProps } from '@prairielearn/preact-cjs';\nimport { run } from '@prairielearn/run';\n\nimport { ColumnManager } from './ColumnManager.js';\nimport {\n TanstackTableDownloadButton,\n type TanstackTableDownloadButtonProps,\n} from './TanstackTableDownloadButton.js';\nimport { TanstackTableHeaderCell } from './TanstackTableHeaderCell.js';\nimport { useAutoSizeColumns } from './useAutoSizeColumns.js';\n\nfunction TableCell<RowDataModel>({\n cell,\n rowIdx,\n colIdx,\n canSort,\n canFilter,\n wrapText,\n handleGridKeyDown,\n}: {\n cell: Cell<RowDataModel, unknown>;\n rowIdx: number;\n colIdx: number;\n canSort: boolean;\n canFilter: boolean;\n wrapText: boolean;\n handleGridKeyDown: (e: KeyboardEvent, rowIdx: number, colIdx: number) => void;\n}) {\n return (\n <td\n key={cell.id}\n tabIndex={0}\n data-grid-cell-row={rowIdx}\n data-grid-cell-col={colIdx}\n class={clsx(!canSort && !canFilter && 'text-center')}\n style={{\n display: 'flex',\n width: cell.column.getSize(),\n minWidth: 0,\n maxWidth: cell.column.getSize(),\n flexShrink: 0,\n position: cell.column.getIsPinned() === 'left' ? 'sticky' : undefined,\n left: cell.column.getIsPinned() === 'left' ? cell.column.getStart() : undefined,\n verticalAlign: 'middle',\n }}\n onKeyDown={(e) => handleGridKeyDown(e, rowIdx, colIdx)}\n >\n <div\n style={{\n display: 'block',\n minWidth: 0,\n maxWidth: '100%',\n overflow: wrapText ? 'visible' : 'hidden',\n textOverflow: wrapText ? undefined : 'ellipsis',\n whiteSpace: wrapText ? 'normal' : 'nowrap',\n flex: '1 1 0%',\n width: 0, // Allow flex to control width, but start from 0\n }}\n >\n {flexRender(cell.column.columnDef.cell, cell.getContext())}\n </div>\n </td>\n );\n}\n\nconst DefaultNoResultsState = (\n <TanstackTableEmptyState iconName=\"bi-search\">\n No results found matching your search criteria.\n </TanstackTableEmptyState>\n);\n\nconst DefaultEmptyState = (\n <TanstackTableEmptyState iconName=\"bi-eye-slash\">No results found.</TanstackTableEmptyState>\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 scrollRef?: React.RefObject<HTMLDivElement> | null;\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 * @param params.scrollRef - Optional ref that will be attached to the scroll container element.\n */\nexport function TanstackTable<RowDataModel>({\n table,\n title,\n filters = DEFAULT_FILTER_MAP,\n rowHeight = 42,\n noResultsState = DefaultNoResultsState,\n emptyState = DefaultEmptyState,\n scrollRef,\n}: TanstackTableProps<RowDataModel>) {\n const parentRef = useRef<HTMLDivElement>(null);\n const tableRef = useRef<HTMLDivElement>(null);\n const scrollContainerRef = scrollRef ?? parentRef;\n\n const rows = [...table.getTopRows(), ...table.getCenterRows()];\n const rowVirtualizer = useVirtualizer({\n count: rows.length,\n getScrollElement: () => scrollContainerRef.current,\n estimateSize: () => rowHeight,\n overscan: 10,\n measureElement: (el) => el?.getBoundingClientRect().height ?? rowHeight,\n });\n\n const visibleColumns = table.getVisibleLeafColumns();\n const centerColumns = visibleColumns.filter((col) => !col.getIsPinned());\n\n const columnVirtualizer = useVirtualizer({\n count: centerColumns.length,\n estimateSize: (index) => centerColumns[index]?.getSize(),\n // `useAutoSizeColumns` solves a different problem (happens once when the column set changes)\n // and we don't need to measure the cells themselves, so we can use the default estimateSize.\n getScrollElement: () => scrollContainerRef.current,\n horizontal: true,\n overscan: 3,\n });\n\n const virtualColumns = columnVirtualizer.getVirtualItems();\n\n const virtualPaddingLeft = run(() => {\n if (columnVirtualizer && virtualColumns?.length > 0) {\n return virtualColumns[0]?.start ?? 0;\n }\n return null;\n });\n\n const virtualPaddingRight = run(() => {\n if (columnVirtualizer && virtualColumns?.length > 0) {\n return (\n columnVirtualizer.getTotalSize() - (virtualColumns[virtualColumns.length - 1]?.end ?? 0)\n );\n }\n return null;\n });\n\n // Check if any column has wrapping enabled\n const hasWrappedColumns = table.getAllLeafColumns().some((col) => col.columnDef.meta?.wrapText);\n\n // Create callback for remeasuring after resize\n const handleResizeEnd = useMemo(() => {\n if (!hasWrappedColumns) return undefined;\n return () => rowVirtualizer.measure();\n }, [hasWrappedColumns, rowVirtualizer]);\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 // Only handle arrow keys if we're in the cell itself, not in an interactive element\n const target = e.target as HTMLElement;\n if (target.tagName === 'TD') {\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 const selector = `[data-grid-cell-row=\"${next.row}\"][data-grid-cell-col=\"${next.col}\"]`;\n const nextCell = tableRef.current?.querySelector(selector) as HTMLElement | null;\n nextCell?.focus();\n }\n };\n\n const virtualRows = rowVirtualizer.getVirtualItems();\n\n const headerGroups = table.getHeaderGroups();\n\n const leafHeaderGroup = headerGroups[headerGroups.length - 1];\n\n const leftPinnedHeaders = leafHeaderGroup.headers.filter(\n (header) => header.column.getIsPinned() === 'left',\n );\n const centerHeaders = leafHeaderGroup.headers.filter((header) => !header.column.getIsPinned());\n\n const isTableResizing = leafHeaderGroup.headers.some((header) => header.column.getIsResizing());\n\n // We toggle this here instead of in the parent since this component logically manages all UI for the table.\n useEffect(() => {\n document.body.classList.toggle('pl-ui-no-user-select', isTableResizing);\n }, [isTableResizing]);\n\n const hasAutoSized = useAutoSizeColumns(table, tableRef, filters);\n\n // Re-measure the virtualizer when auto-sizing completes\n useEffect(() => {\n if (hasAutoSized) {\n // https://github.com/NickvanDyke/eslint-plugin-react-you-might-not-need-an-effect/issues/58\n // eslint-disable-next-line react-you-might-not-need-an-effect/no-pass-ref-to-parent\n columnVirtualizer.measure();\n }\n }, [columnVirtualizer, hasAutoSized]);\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={scrollContainerRef}\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\"\n style={{ display: 'grid', tableLayout: 'fixed' }}\n aria-label={title}\n role=\"grid\"\n >\n <thead\n class=\"position-sticky top-0 w-100 border-top\"\n style={{\n display: 'grid',\n zIndex: 1,\n borderBottom: 'var(--bs-border-width) solid black',\n }}\n >\n <tr\n key={leafHeaderGroup.id}\n class=\"d-flex w-100\"\n style={{ minWidth: `${table.getTotalSize()}px` }}\n >\n {/* Left pinned columns */}\n {leftPinnedHeaders.map((header) => {\n return (\n <TanstackTableHeaderCell\n key={header.id}\n header={header}\n filters={filters}\n table={table}\n handleResizeEnd={handleResizeEnd}\n isPinned=\"left\"\n />\n );\n })}\n\n {/* Virtual padding for left side of center columns */}\n {virtualPaddingLeft ? (\n <th style={{ display: 'flex', width: virtualPaddingLeft }} />\n ) : null}\n\n {/* Virtualized center columns */}\n {virtualColumns.map((virtualColumn) => {\n const header = centerHeaders[virtualColumn.index];\n if (!header) return null;\n\n return (\n <TanstackTableHeaderCell\n key={header.id}\n header={header}\n filters={filters}\n table={table}\n handleResizeEnd={handleResizeEnd}\n isPinned={false}\n />\n );\n })}\n\n {/* Virtual padding for right side of center columns */}\n {virtualPaddingRight ? (\n <th style={{ display: 'flex', width: virtualPaddingRight }} />\n ) : null}\n\n {/* Filler to span remaining width */}\n <th\n tabIndex={-1}\n class=\"d-flex flex-grow-1 p-0\"\n style={{ minWidth: 0 }}\n aria-hidden=\"true\"\n />\n </tr>\n </thead>\n <tbody\n class=\"position-relative w-100\"\n style={{\n display: 'grid',\n height: `${rowVirtualizer.getTotalSize()}px`,\n }}\n >\n {virtualRows.map((virtualRow) => {\n const row = rows[virtualRow.index];\n const rowIdx = virtualRow.index;\n const leftPinnedCells = row.getLeftVisibleCells();\n const centerCells = row.getCenterVisibleCells();\n\n let currentColIdx = 0;\n\n return (\n <tr\n key={row.id}\n ref={(node) => rowVirtualizer.measureElement(node)}\n data-index={virtualRow.index}\n class=\"d-flex position-absolute w-100\"\n style={{\n transform: `translateY(${virtualRow.start}px)`,\n minWidth: `${table.getTotalSize()}px`,\n }}\n >\n {leftPinnedCells.map((cell) => {\n const colIdx = currentColIdx++;\n const canSort = cell.column.getCanSort();\n const canFilter = cell.column.getCanFilter();\n const wrapText = cell.column.columnDef.meta?.wrapText ?? false;\n\n return (\n <TableCell\n key={cell.id}\n cell={cell}\n rowIdx={rowIdx}\n colIdx={colIdx}\n canSort={canSort}\n canFilter={canFilter}\n wrapText={wrapText}\n handleGridKeyDown={handleGridKeyDown}\n />\n );\n })}\n\n {virtualPaddingLeft ? (\n <td style={{ display: 'flex', width: virtualPaddingLeft }} />\n ) : null}\n\n {virtualColumns.map((virtualColumn) => {\n const cell = centerCells[virtualColumn.index];\n if (!cell) return null;\n\n const colIdx = currentColIdx++;\n const canSort = cell.column.getCanSort();\n const canFilter = cell.column.getCanFilter();\n const wrapText = cell.column.columnDef.meta?.wrapText ?? false;\n\n return (\n <TableCell\n key={cell.id}\n cell={cell}\n rowIdx={rowIdx}\n colIdx={colIdx}\n canSort={canSort}\n canFilter={canFilter}\n wrapText={wrapText}\n handleGridKeyDown={handleGridKeyDown}\n />\n );\n })}\n\n {virtualPaddingRight ? (\n <td style={{ display: 'flex', width: virtualPaddingRight }} />\n ) : null}\n\n {/* Filler to span remaining width */}\n <td\n tabIndex={-1}\n class=\"d-flex flex-grow-1 p-0\"\n style={{ minWidth: 0 }}\n aria-hidden=\"true\"\n />\n </tr>\n );\n })}\n </tbody>\n </table>\n </div>\n </div>\n {table.getVisibleLeafColumns().length === 0 || displayedCount === 0 ? (\n <div>\n <div\n class=\"d-flex flex-column justify-content-center align-items-center p-4\"\n style={{\n position: 'absolute',\n top: 0,\n left: 0,\n right: 0,\n bottom: 0,\n // Allow pointer events (e.g. scrolling) to reach the underlying table.\n pointerEvents: 'none',\n }}\n role=\"status\"\n aria-live=\"polite\"\n >\n <div\n class=\"col-lg-6\"\n style={{\n // Allow selecting and interacting with the empty state content.\n pointerEvents: 'auto',\n }}\n >\n {table.getVisibleLeafColumns().length === 0 ? (\n <TanstackTableEmptyState iconName=\"bi-eye-slash\">\n No columns selected. Use the View menu to show columns.\n </TanstackTableEmptyState>\n ) : displayedCount === 0 ? (\n totalCount > 0 ? (\n noResultsState\n ) : (\n emptyState\n )\n ) : null}\n </div>\n </div>\n </div>\n ) : null}\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.className - The class name to apply to the card\n * @param params.style - The style to apply to the card\n * @param params.singularLabel - The singular label for a single row in the table, e.g. \"student\"\n * @param params.pluralLabel - The plural label for multiple rows in the table, e.g. \"students\"\n * @param params.headerButtons - The buttons to display in the header\n * @param params.columnManager - Optional configuration for the column manager. See {@link ColumnManager} for more details.\n * @param params.columnManager.buttons - The buttons to display next to the column manager (View button)\n * @param params.columnManager.topContent - Optional content to display at the top of the column manager (View) dropdown menu\n * @param params.globalFilter - Configuration for the global filter\n * @param params.globalFilter.placeholder - Placeholder text for the search input\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 singularLabel,\n pluralLabel,\n headerButtons,\n columnManager,\n globalFilter,\n tableOptions,\n downloadButtonOptions,\n className,\n ...divProps\n}: {\n table: Table<RowDataModel>;\n title: string;\n singularLabel: string;\n pluralLabel: string;\n headerButtons?: JSX.Element;\n columnManager?: {\n buttons?: JSX.Element;\n topContent?: JSX.Element;\n };\n globalFilter: {\n placeholder: string;\n };\n tableOptions: Partial<Omit<TanstackTableProps<RowDataModel>, 'table'>>;\n downloadButtonOptions?: Omit<\n TanstackTableDownloadButtonProps<RowDataModel>,\n 'table' | 'singularLabel' | 'pluralLabel'\n > & { pluralLabel?: string; singularLabel?: string };\n} & Omit<ComponentProps<'div'>, 'class'>) {\n const searchInputRef = useRef<HTMLInputElement>(null);\n\n const [inputValue, setInputValue] = useState(\n () => (table.getState().globalFilter as string) ?? '',\n );\n\n // Debounce the filter update\n const debouncedSetFilter = useDebouncedCallback((value: string) => {\n table.setGlobalFilter(value);\n }, 150);\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={clsx('card d-flex flex-column', className)} {...divProps}>\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\n table={table}\n pluralLabel={pluralLabel}\n singularLabel={singularLabel}\n {...downloadButtonOptions}\n />\n )}\n </div>\n </div>\n </div>\n <div class=\"card-body d-flex flex-row flex-wrap flex-grow-0 align-items-center gap-2\">\n <div class=\"position-relative w-100\" style={{ maxWidth: 'min(400px, 100%)' }}>\n <input\n ref={searchInputRef}\n type=\"text\"\n class=\"form-control pl-ui-tanstack-table-search-input pl-ui-tanstack-table-focusable-shadow\"\n aria-label={globalFilter.placeholder}\n placeholder={globalFilter.placeholder}\n value={inputValue}\n autoComplete=\"off\"\n onInput={(e) => {\n const value = e.currentTarget.value;\n setInputValue(value);\n debouncedSetFilter(value);\n }}\n />\n {inputValue && (\n <OverlayTrigger overlay={<Tooltip>Clear search</Tooltip>}>\n <button\n type=\"button\"\n class=\"btn btn-floating-icon\"\n aria-label=\"Clear search\"\n onClick={() => {\n setInputValue('');\n debouncedSetFilter.cancel();\n table.setGlobalFilter('');\n }}\n >\n <i class=\"bi bi-x-circle-fill\" aria-hidden=\"true\" />\n </button>\n </OverlayTrigger>\n )}\n </div>\n <div class=\"d-flex flex-wrap flex-row align-items-center gap-2\">\n <ColumnManager table={table} topContent={columnManager?.topContent} />\n {columnManager?.buttons}\n </div>\n <div class=\"ms-auto text-muted text-nowrap\">\n Showing {displayedCount} of {totalCount} {totalCount === 1 ? singularLabel : pluralLabel}\n </div>\n </div>\n <div class=\"flex-grow-1\">\n <TanstackTable table={table} title={title} {...tableOptions} />\n </div>\n </div>\n );\n}\n\nexport function TanstackTableEmptyState({\n iconName,\n children,\n}: {\n iconName: `bi-${string}`;\n children: ComponentChildren;\n}) {\n return (\n <div class=\"d-flex flex-column justify-content-center align-items-center text-muted\">\n <i class={clsx('bi', iconName, 'display-4 mb-2')} aria-hidden=\"true\" />\n <div>{children}</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 { useVirtualizer } from '@tanstack/react-virtual';\nimport type { Cell, Header, Row, Table } from '@tanstack/table-core';\nimport clsx from 'clsx';\nimport {\n type ComponentProps,\n type JSX,\n type ReactNode,\n useEffect,\n useMemo,\n useRef,\n useState,\n} from 'react';\nimport OverlayTrigger from 'react-bootstrap/OverlayTrigger';\nimport Tooltip from 'react-bootstrap/Tooltip';\nimport { useDebouncedCallback } from 'use-debounce';\n\nimport { run } from '@prairielearn/run';\n\nimport { ColumnManager } from './ColumnManager.js';\nimport {\n TanstackTableDownloadButton,\n type TanstackTableDownloadButtonProps,\n} from './TanstackTableDownloadButton.js';\nimport { TanstackTableHeaderCell } from './TanstackTableHeaderCell.js';\nimport { useAutoSizeColumns } from './useAutoSizeColumns.js';\n\nfunction TableCell<RowDataModel>({\n cell,\n rowIdx,\n colIdx,\n canSort,\n canFilter,\n wrapText,\n handleGridKeyDown,\n}: {\n cell: Cell<RowDataModel, unknown>;\n rowIdx: number;\n colIdx: number;\n canSort: boolean;\n canFilter: boolean;\n wrapText: boolean;\n handleGridKeyDown: (e: React.KeyboardEvent, rowIdx: number, colIdx: number) => void;\n}) {\n return (\n <td\n key={cell.id}\n tabIndex={0}\n data-grid-cell-row={rowIdx}\n data-grid-cell-col={colIdx}\n className={clsx(!canSort && !canFilter && 'text-center')}\n style={{\n display: 'flex',\n width: cell.column.getSize(),\n minWidth: 0,\n maxWidth: cell.column.getSize(),\n flexShrink: 0,\n position: cell.column.getIsPinned() === 'left' ? 'sticky' : undefined,\n left: cell.column.getIsPinned() === 'left' ? cell.column.getStart() : undefined,\n verticalAlign: 'middle',\n }}\n onKeyDown={(e) => handleGridKeyDown(e, rowIdx, colIdx)}\n >\n <div\n style={{\n display: 'block',\n minWidth: 0,\n maxWidth: '100%',\n overflow: wrapText ? 'visible' : 'hidden',\n textOverflow: wrapText ? undefined : 'ellipsis',\n whiteSpace: wrapText ? 'normal' : 'nowrap',\n flex: '1 1 0%',\n width: 0, // Allow flex to control width, but start from 0\n }}\n >\n {flexRender(cell.column.columnDef.cell, cell.getContext())}\n </div>\n </td>\n );\n}\n\nconst DefaultNoResultsState = (\n <TanstackTableEmptyState iconName=\"bi-search\">\n No results found matching your search criteria.\n </TanstackTableEmptyState>\n);\n\nconst DefaultEmptyState = (\n <TanstackTableEmptyState iconName=\"bi-eye-slash\">No results found.</TanstackTableEmptyState>\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 scrollRef?: React.RefObject<HTMLDivElement | null> | null;\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 * @param params.scrollRef - Optional ref that will be attached to the scroll container element.\n */\nexport function TanstackTable<RowDataModel>({\n table,\n title,\n filters = DEFAULT_FILTER_MAP,\n rowHeight = 42,\n noResultsState = DefaultNoResultsState,\n emptyState = DefaultEmptyState,\n scrollRef,\n}: TanstackTableProps<RowDataModel>) {\n const parentRef = useRef<HTMLDivElement>(null);\n const tableRef = useRef<HTMLDivElement>(null);\n const scrollContainerRef = scrollRef ?? parentRef;\n\n const rows = [...table.getTopRows(), ...table.getCenterRows()];\n const rowVirtualizer = useVirtualizer({\n count: rows.length,\n getScrollElement: () => scrollContainerRef.current,\n estimateSize: () => rowHeight,\n overscan: 10,\n measureElement: (el) => el?.getBoundingClientRect().height ?? rowHeight,\n });\n\n const visibleColumns = table.getVisibleLeafColumns();\n const centerColumns = visibleColumns.filter((col) => !col.getIsPinned());\n\n const columnVirtualizer = useVirtualizer({\n count: centerColumns.length,\n estimateSize: (index) => centerColumns[index]?.getSize(),\n // `useAutoSizeColumns` solves a different problem (happens once when the column set changes)\n // and we don't need to measure the cells themselves, so we can use the default estimateSize.\n getScrollElement: () => scrollContainerRef.current,\n horizontal: true,\n overscan: 3,\n });\n\n const virtualColumns = columnVirtualizer.getVirtualItems();\n\n const virtualPaddingLeft = run(() => {\n if (columnVirtualizer && virtualColumns?.length > 0) {\n return virtualColumns[0]?.start ?? 0;\n }\n return null;\n });\n\n const virtualPaddingRight = run(() => {\n if (columnVirtualizer && virtualColumns?.length > 0) {\n return (\n columnVirtualizer.getTotalSize() - (virtualColumns[virtualColumns.length - 1]?.end ?? 0)\n );\n }\n return null;\n });\n\n // Check if any column has wrapping enabled\n const hasWrappedColumns = table.getAllLeafColumns().some((col) => col.columnDef.meta?.wrapText);\n\n // Create callback for remeasuring after resize\n const handleResizeEnd = useMemo(() => {\n if (!hasWrappedColumns) return undefined;\n return () => rowVirtualizer.measure();\n }, [hasWrappedColumns, rowVirtualizer]);\n\n const getVisibleCells = (row: Row<RowDataModel>) => [\n ...row.getLeftVisibleCells(),\n ...row.getCenterVisibleCells(),\n ];\n\n const handleGridKeyDown = (e: React.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 // Only handle arrow keys if we're in the cell itself, not in an interactive element\n const target = e.target as HTMLElement;\n if (target.tagName === 'TD') {\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 const selector = `[data-grid-cell-row=\"${next.row}\"][data-grid-cell-col=\"${next.col}\"]`;\n const nextCell = tableRef.current?.querySelector(selector) as HTMLElement | null;\n nextCell?.focus();\n }\n };\n\n const virtualRows = rowVirtualizer.getVirtualItems();\n\n const headerGroups = table.getHeaderGroups();\n\n const leafHeaderGroup = headerGroups[headerGroups.length - 1];\n\n const leftPinnedHeaders = leafHeaderGroup.headers.filter(\n (header) => header.column.getIsPinned() === 'left',\n );\n const centerHeaders = leafHeaderGroup.headers.filter((header) => !header.column.getIsPinned());\n\n const isTableResizing = leafHeaderGroup.headers.some((header) => header.column.getIsResizing());\n\n // We toggle this here instead of in the parent since this component logically manages all UI for the table.\n useEffect(() => {\n document.body.classList.toggle('pl-ui-no-user-select', isTableResizing);\n }, [isTableResizing]);\n\n const hasAutoSized = useAutoSizeColumns(table, tableRef, filters);\n\n // Re-measure the virtualizer when auto-sizing completes\n useEffect(() => {\n if (hasAutoSized) {\n // https://github.com/NickvanDyke/eslint-plugin-react-you-might-not-need-an-effect/issues/58\n // eslint-disable-next-line react-you-might-not-need-an-effect/no-pass-ref-to-parent\n columnVirtualizer.measure();\n }\n }, [columnVirtualizer, hasAutoSized]);\n\n const displayedCount = table.getRowModel().rows.length;\n const totalCount = table.getCoreRowModel().rows.length;\n\n return (\n <div style={{ position: 'relative' }} className=\"d-flex flex-column h-100\">\n <div\n ref={scrollContainerRef}\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 className=\"table table-hover mb-0\"\n style={{ display: 'grid', tableLayout: 'fixed' }}\n aria-label={title}\n role=\"grid\"\n >\n <thead\n className=\"position-sticky top-0 w-100 border-top\"\n style={{\n display: 'grid',\n zIndex: 1,\n borderBottom: 'var(--bs-border-width) solid black',\n }}\n >\n <tr\n key={leafHeaderGroup.id}\n className=\"d-flex w-100\"\n style={{ minWidth: `${table.getTotalSize()}px` }}\n >\n {/* Left pinned columns */}\n {leftPinnedHeaders.map((header) => {\n return (\n <TanstackTableHeaderCell\n key={header.id}\n header={header}\n filters={filters}\n table={table}\n handleResizeEnd={handleResizeEnd}\n isPinned=\"left\"\n />\n );\n })}\n\n {/* Virtual padding for left side of center columns */}\n {virtualPaddingLeft ? (\n <th style={{ display: 'flex', width: virtualPaddingLeft }} />\n ) : null}\n\n {/* Virtualized center columns */}\n {virtualColumns.map((virtualColumn) => {\n const header = centerHeaders[virtualColumn.index];\n if (!header) return null;\n\n return (\n <TanstackTableHeaderCell\n key={header.id}\n header={header}\n filters={filters}\n table={table}\n handleResizeEnd={handleResizeEnd}\n isPinned={false}\n />\n );\n })}\n\n {/* Virtual padding for right side of center columns */}\n {virtualPaddingRight ? (\n <th style={{ display: 'flex', width: virtualPaddingRight }} />\n ) : null}\n\n {/* Filler to span remaining width */}\n <th\n tabIndex={-1}\n className=\"d-flex flex-grow-1 p-0\"\n style={{ minWidth: 0 }}\n aria-hidden=\"true\"\n />\n </tr>\n </thead>\n <tbody\n className=\"position-relative w-100\"\n style={{\n display: 'grid',\n height: `${rowVirtualizer.getTotalSize()}px`,\n }}\n >\n {virtualRows.map((virtualRow) => {\n const row = rows[virtualRow.index];\n const rowIdx = virtualRow.index;\n const leftPinnedCells = row.getLeftVisibleCells();\n const centerCells = row.getCenterVisibleCells();\n\n let currentColIdx = 0;\n\n return (\n <tr\n key={row.id}\n ref={(node) => rowVirtualizer.measureElement(node)}\n data-index={virtualRow.index}\n className=\"d-flex position-absolute w-100\"\n style={{\n transform: `translateY(${virtualRow.start}px)`,\n minWidth: `${table.getTotalSize()}px`,\n }}\n >\n {leftPinnedCells.map((cell) => {\n const colIdx = currentColIdx++;\n const canSort = cell.column.getCanSort();\n const canFilter = cell.column.getCanFilter();\n const wrapText = cell.column.columnDef.meta?.wrapText ?? false;\n\n return (\n <TableCell\n key={cell.id}\n cell={cell}\n rowIdx={rowIdx}\n colIdx={colIdx}\n canSort={canSort}\n canFilter={canFilter}\n wrapText={wrapText}\n handleGridKeyDown={handleGridKeyDown}\n />\n );\n })}\n\n {virtualPaddingLeft ? (\n <td style={{ display: 'flex', width: virtualPaddingLeft }} />\n ) : null}\n\n {virtualColumns.map((virtualColumn) => {\n const cell = centerCells[virtualColumn.index];\n if (!cell) return null;\n\n const colIdx = currentColIdx++;\n const canSort = cell.column.getCanSort();\n const canFilter = cell.column.getCanFilter();\n const wrapText = cell.column.columnDef.meta?.wrapText ?? false;\n\n return (\n <TableCell\n key={cell.id}\n cell={cell}\n rowIdx={rowIdx}\n colIdx={colIdx}\n canSort={canSort}\n canFilter={canFilter}\n wrapText={wrapText}\n handleGridKeyDown={handleGridKeyDown}\n />\n );\n })}\n\n {virtualPaddingRight ? (\n <td style={{ display: 'flex', width: virtualPaddingRight }} />\n ) : null}\n\n {/* Filler to span remaining width */}\n <td\n tabIndex={-1}\n className=\"d-flex flex-grow-1 p-0\"\n style={{ minWidth: 0 }}\n aria-hidden=\"true\"\n />\n </tr>\n );\n })}\n </tbody>\n </table>\n </div>\n </div>\n {table.getVisibleLeafColumns().length === 0 || displayedCount === 0 ? (\n <div>\n <div\n className=\"d-flex flex-column justify-content-center align-items-center p-4\"\n style={{\n position: 'absolute',\n top: 0,\n left: 0,\n right: 0,\n bottom: 0,\n // Allow pointer events (e.g. scrolling) to reach the underlying table.\n pointerEvents: 'none',\n }}\n role=\"status\"\n aria-live=\"polite\"\n >\n <div\n className=\"col-lg-6\"\n style={{\n // Allow selecting and interacting with the empty state content.\n pointerEvents: 'auto',\n }}\n >\n {table.getVisibleLeafColumns().length === 0 ? (\n <TanstackTableEmptyState iconName=\"bi-eye-slash\">\n No columns selected. Use the View menu to show columns.\n </TanstackTableEmptyState>\n ) : displayedCount === 0 ? (\n totalCount > 0 ? (\n noResultsState\n ) : (\n emptyState\n )\n ) : null}\n </div>\n </div>\n </div>\n ) : null}\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.className - The class name to apply to the card\n * @param params.style - The style to apply to the card\n * @param params.singularLabel - The singular label for a single row in the table, e.g. \"student\"\n * @param params.pluralLabel - The plural label for multiple rows in the table, e.g. \"students\"\n * @param params.headerButtons - The buttons to display in the header\n * @param params.columnManager - Optional configuration for the column manager. See {@link ColumnManager} for more details.\n * @param params.columnManager.buttons - The buttons to display next to the column manager (View button)\n * @param params.columnManager.topContent - Optional content to display at the top of the column manager (View) dropdown menu\n * @param params.globalFilter - Configuration for the global filter\n * @param params.globalFilter.placeholder - Placeholder text for the search input\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 singularLabel,\n pluralLabel,\n headerButtons,\n columnManager,\n globalFilter,\n tableOptions,\n downloadButtonOptions,\n className,\n ...divProps\n}: {\n table: Table<RowDataModel>;\n title: string;\n singularLabel: string;\n pluralLabel: string;\n headerButtons?: JSX.Element;\n columnManager?: {\n buttons?: JSX.Element;\n topContent?: JSX.Element;\n };\n globalFilter: {\n placeholder: string;\n };\n tableOptions: Partial<Omit<TanstackTableProps<RowDataModel>, 'table'>>;\n downloadButtonOptions?: Omit<\n TanstackTableDownloadButtonProps<RowDataModel>,\n 'table' | 'singularLabel' | 'pluralLabel'\n > & { pluralLabel?: string; singularLabel?: string };\n} & Omit<ComponentProps<'div'>, 'class'>) {\n const searchInputRef = useRef<HTMLInputElement>(null);\n\n const [inputValue, setInputValue] = useState(\n () => (table.getState().globalFilter as string) ?? '',\n );\n\n // Debounce the filter update\n const debouncedSetFilter = useDebouncedCallback((value: string) => {\n table.setGlobalFilter(value);\n }, 150);\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 className={clsx('card d-flex flex-column', className)} {...divProps}>\n <div className=\"card-header bg-primary text-white\">\n <div className=\"d-flex align-items-center justify-content-between gap-2\">\n <div>{title}</div>\n <div className=\"d-flex gap-2\">\n {headerButtons}\n\n {downloadButtonOptions && (\n <TanstackTableDownloadButton\n table={table}\n pluralLabel={pluralLabel}\n singularLabel={singularLabel}\n {...downloadButtonOptions}\n />\n )}\n </div>\n </div>\n </div>\n <div className=\"card-body d-flex flex-row flex-wrap flex-grow-0 align-items-center gap-2\">\n <div className=\"position-relative w-100\" style={{ maxWidth: 'min(400px, 100%)' }}>\n <input\n ref={searchInputRef}\n type=\"text\"\n className=\"form-control pl-ui-tanstack-table-search-input pl-ui-tanstack-table-focusable-shadow\"\n aria-label={globalFilter.placeholder}\n placeholder={globalFilter.placeholder}\n value={inputValue}\n autoComplete=\"off\"\n onInput={(e) => {\n const value = e.currentTarget.value;\n setInputValue(value);\n debouncedSetFilter(value);\n }}\n />\n {inputValue && (\n <OverlayTrigger overlay={<Tooltip>Clear search</Tooltip>}>\n <button\n type=\"button\"\n className=\"btn btn-floating-icon\"\n aria-label=\"Clear search\"\n onClick={() => {\n setInputValue('');\n debouncedSetFilter.cancel();\n table.setGlobalFilter('');\n }}\n >\n <i className=\"bi bi-x-circle-fill\" aria-hidden=\"true\" />\n </button>\n </OverlayTrigger>\n )}\n </div>\n <div className=\"d-flex flex-wrap flex-row align-items-center gap-2\">\n <ColumnManager table={table} topContent={columnManager?.topContent} />\n {columnManager?.buttons}\n </div>\n <div className=\"ms-auto text-muted text-nowrap\">\n Showing {displayedCount} of {totalCount} {totalCount === 1 ? singularLabel : pluralLabel}\n </div>\n </div>\n <div className=\"flex-grow-1\">\n <TanstackTable table={table} title={title} {...tableOptions} />\n </div>\n </div>\n );\n}\n\nexport function TanstackTableEmptyState({\n iconName,\n children,\n}: {\n iconName: `bi-${string}`;\n children: ReactNode;\n}) {\n return (\n <div className=\"d-flex flex-column justify-content-center align-items-center text-muted\">\n <i className={clsx('bi', iconName, 'display-4 mb-2')} aria-hidden=\"true\" />\n <div>{children}</div>\n </div>\n );\n}\n"]}
|
|
@@ -24,5 +24,5 @@ export interface TanstackTableDownloadButtonProps<RowDataModel> {
|
|
|
24
24
|
* @param params.pluralLabel - The plural label for multiple rows in the table, e.g. "students"
|
|
25
25
|
* @param params.hasSelection - Whether the table has selection enabled
|
|
26
26
|
*/
|
|
27
|
-
export declare function TanstackTableDownloadButton<RowDataModel>({ table, filenameBase, mapRowToData, singularLabel, pluralLabel, hasSelection
|
|
27
|
+
export declare function TanstackTableDownloadButton<RowDataModel>({ table, filenameBase, mapRowToData, singularLabel, pluralLabel, hasSelection }: TanstackTableDownloadButtonProps<RowDataModel>): import("react/jsx-runtime").JSX.Element;
|
|
28
28
|
//# sourceMappingURL=TanstackTableDownloadButton.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"TanstackTableDownloadButton.d.ts","sourceRoot":"","sources":["../../src/components/TanstackTableDownloadButton.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,uBAAuB,CAAC;AAInD,MAAM,WAAW,oBAAoB;IACnC,KAAK,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAAC;IAC9B,8CAA8C;IAC9C,IAAI,EAAE,MAAM,CAAC;CACd;AAED,MAAM,WAAW,gCAAgC,CAAC,YAAY;IAC5D,KAAK,EAAE,KAAK,CAAC,YAAY,CAAC,CAAC;IAC3B,YAAY,EAAE,MAAM,CAAC;IACrB,YAAY,EAAE,CAAC,GAAG,EAAE,YAAY,KAAK,oBAAoB,EAAE,GAAG,IAAI,CAAC;IACnE,aAAa,EAAE,MAAM,CAAC;IACtB,WAAW,EAAE,MAAM,CAAC;IACpB,YAAY,EAAE,OAAO,CAAC;CACvB;AACD;;;;;;;;;;;GAWG;AACH,wBAAgB,2BAA2B,CAAC,YAAY,EAAE,EACxD,KAAK,EACL,YAAY,EACZ,YAAY,EACZ,aAAa,EACb,WAAW,EACX,YAAY,
|
|
1
|
+
{"version":3,"file":"TanstackTableDownloadButton.d.ts","sourceRoot":"","sources":["../../src/components/TanstackTableDownloadButton.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,uBAAuB,CAAC;AAInD,MAAM,WAAW,oBAAoB;IACnC,KAAK,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAAC;IAC9B,8CAA8C;IAC9C,IAAI,EAAE,MAAM,CAAC;CACd;AAED,MAAM,WAAW,gCAAgC,CAAC,YAAY;IAC5D,KAAK,EAAE,KAAK,CAAC,YAAY,CAAC,CAAC;IAC3B,YAAY,EAAE,MAAM,CAAC;IACrB,YAAY,EAAE,CAAC,GAAG,EAAE,YAAY,KAAK,oBAAoB,EAAE,GAAG,IAAI,CAAC;IACnE,aAAa,EAAE,MAAM,CAAC;IACtB,WAAW,EAAE,MAAM,CAAC;IACpB,YAAY,EAAE,OAAO,CAAC;CACvB;AACD;;;;;;;;;;;GAWG;AACH,wBAAgB,2BAA2B,CAAC,YAAY,EAAE,EACxD,KAAK,EACL,YAAY,EACZ,YAAY,EACZ,aAAa,EACb,WAAW,EACX,YAAY,EACb,EAAE,gCAAgC,CAAC,YAAY,CAAC,2CAmHhD","sourcesContent":["import type { Table } from '@tanstack/react-table';\n\nimport { downloadAsCSV, downloadAsJSON } from '@prairielearn/browser-utils';\n\nexport interface TanstackTableCsvCell {\n value: string | number | null;\n /** The name of the column in the CSV file. */\n name: string;\n}\n\nexport interface TanstackTableDownloadButtonProps<RowDataModel> {\n table: Table<RowDataModel>;\n filenameBase: string;\n mapRowToData: (row: RowDataModel) => TanstackTableCsvCell[] | null;\n singularLabel: string;\n pluralLabel: string;\n hasSelection: boolean;\n}\n/**\n * @param params\n * @param params.table - The table model\n * @param params.filenameBase - The base filename for the downloads\n * @param params.mapRowToData - A function that maps a row to a record where the\n * keys are the column names, and the values are the cell values. The key order is important,\n * and should match the expected order of the columns in the CSV file. If the function returns null,\n * the row will be skipped.\n * @param params.singularLabel - The singular label for a single row in the table, e.g. \"student\"\n * @param params.pluralLabel - The plural label for multiple rows in the table, e.g. \"students\"\n * @param params.hasSelection - Whether the table has selection enabled\n */\nexport function TanstackTableDownloadButton<RowDataModel>({\n table,\n filenameBase,\n mapRowToData,\n singularLabel,\n pluralLabel,\n hasSelection,\n}: TanstackTableDownloadButtonProps<RowDataModel>) {\n const allRows = table.getCoreRowModel().rows.map((row) => row.original);\n const allRowsJSON = allRows.map(mapRowToData).filter((row) => row !== null);\n const filteredRows = table.getRowModel().rows.map((row) => row.original);\n const filteredRowsJSON = filteredRows.map(mapRowToData).filter((row) => row !== null);\n const selectedRows = table.getSelectedRowModel().rows.map((row) => row.original);\n const selectedRowsJSON = selectedRows.map(mapRowToData).filter((row) => row !== null);\n\n function downloadJSONAsCSV(jsonRows: TanstackTableCsvCell[][], filename: string): void {\n if (jsonRows.length === 0) {\n throw new Error('No rows to download');\n }\n\n const header = jsonRows[0].map((cell) => cell.name);\n const csvRows = jsonRows.map((row) => row.map((cell) => cell.value));\n downloadAsCSV(header, csvRows, filename);\n }\n\n return (\n <div className=\"btn-group\">\n <button\n type=\"button\"\n data-bs-toggle=\"dropdown\"\n aria-expanded=\"false\"\n aria-haspopup=\"true\"\n aria-label={`Download ${pluralLabel} data in various formats`}\n className=\"btn btn-light btn-sm dropdown-toggle\"\n >\n <i aria-hidden=\"true\" className=\"pe-2 bi bi-download\" />\n <span className=\"d-none d-sm-inline\">Download</span>\n </button>\n <ul className=\"dropdown-menu\" role=\"menu\" aria-label=\"Download options\">\n <li role=\"presentation\">\n <button\n className=\"dropdown-item\"\n type=\"button\"\n role=\"menuitem\"\n aria-label={`Download all ${pluralLabel} as CSV file`}\n disabled={allRowsJSON.length === 0}\n onClick={() => downloadJSONAsCSV(allRowsJSON, `${filenameBase}.csv`)}\n >\n All {pluralLabel} ({allRowsJSON.length}) as CSV\n </button>\n </li>\n <li role=\"presentation\">\n <button\n className=\"dropdown-item\"\n type=\"button\"\n role=\"menuitem\"\n aria-label={`Download all ${pluralLabel} as JSON file`}\n disabled={allRowsJSON.length === 0}\n onClick={() => downloadAsJSON(allRowsJSON, `${filenameBase}.json`)}\n >\n All {pluralLabel} ({allRowsJSON.length}) as JSON\n </button>\n </li>\n {hasSelection && (\n <>\n <li role=\"presentation\">\n <button\n className=\"dropdown-item\"\n type=\"button\"\n role=\"menuitem\"\n aria-label={`Download selected ${pluralLabel} as CSV file`}\n disabled={selectedRowsJSON.length === 0}\n onClick={() => downloadJSONAsCSV(selectedRowsJSON, `${filenameBase}_selected.csv`)}\n >\n Selected {selectedRowsJSON.length === 1 ? singularLabel : pluralLabel} (\n {selectedRowsJSON.length}) as CSV\n </button>\n </li>\n <li role=\"presentation\">\n <button\n className=\"dropdown-item\"\n type=\"button\"\n role=\"menuitem\"\n aria-label={`Download selected ${pluralLabel} as JSON file`}\n disabled={selectedRowsJSON.length === 0}\n onClick={() => downloadAsJSON(selectedRowsJSON, `${filenameBase}_selected.json`)}\n >\n Selected {selectedRowsJSON.length === 1 ? singularLabel : pluralLabel} (\n {selectedRowsJSON.length}) as JSON\n </button>\n </li>\n </>\n )}\n <li role=\"presentation\">\n <button\n className=\"dropdown-item\"\n type=\"button\"\n role=\"menuitem\"\n aria-label={`Download filtered ${pluralLabel} as CSV file`}\n disabled={filteredRowsJSON.length === 0}\n onClick={() => downloadJSONAsCSV(filteredRowsJSON, `${filenameBase}_filtered.csv`)}\n >\n Filtered {filteredRowsJSON.length === 1 ? singularLabel : pluralLabel} (\n {filteredRowsJSON.length}) as CSV\n </button>\n </li>\n <li role=\"presentation\">\n <button\n className=\"dropdown-item\"\n type=\"button\"\n role=\"menuitem\"\n aria-label={`Download filtered ${pluralLabel} as JSON file`}\n disabled={filteredRowsJSON.length === 0}\n onClick={() => downloadAsJSON(filteredRowsJSON, `${filenameBase}_filtered.json`)}\n >\n Filtered {filteredRowsJSON.length === 1 ? singularLabel : pluralLabel} (\n {filteredRowsJSON.length}) as JSON\n </button>\n </li>\n </ul>\n </div>\n );\n}\n"]}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
import { downloadAsCSV, downloadAsJSON } from '@prairielearn/browser-utils';
|
|
3
3
|
/**
|
|
4
4
|
* @param params
|
|
@@ -27,6 +27,14 @@ export function TanstackTableDownloadButton({ table, filenameBase, mapRowToData,
|
|
|
27
27
|
const csvRows = jsonRows.map((row) => row.map((cell) => cell.value));
|
|
28
28
|
downloadAsCSV(header, csvRows, filename);
|
|
29
29
|
}
|
|
30
|
-
return (_jsxs("div", {
|
|
30
|
+
return (_jsxs("div", { className: "btn-group", children: [
|
|
31
|
+
_jsxs("button", { type: "button", "data-bs-toggle": "dropdown", "aria-expanded": "false", "aria-haspopup": "true", "aria-label": `Download ${pluralLabel} data in various formats`, className: "btn btn-light btn-sm dropdown-toggle", children: [
|
|
32
|
+
_jsx("i", { "aria-hidden": "true", className: "pe-2 bi bi-download" }), _jsx("span", { className: "d-none d-sm-inline", children: "Download" })
|
|
33
|
+
] }), _jsxs("ul", { className: "dropdown-menu", role: "menu", "aria-label": "Download options", children: [
|
|
34
|
+
_jsx("li", { role: "presentation", children: _jsxs("button", { className: "dropdown-item", type: "button", role: "menuitem", "aria-label": `Download all ${pluralLabel} as CSV file`, disabled: allRowsJSON.length === 0, onClick: () => downloadJSONAsCSV(allRowsJSON, `${filenameBase}.csv`), children: ["All ", pluralLabel, " (", allRowsJSON.length, ") as CSV"] }) }), _jsx("li", { role: "presentation", children: _jsxs("button", { className: "dropdown-item", type: "button", role: "menuitem", "aria-label": `Download all ${pluralLabel} as JSON file`, disabled: allRowsJSON.length === 0, onClick: () => downloadAsJSON(allRowsJSON, `${filenameBase}.json`), children: ["All ", pluralLabel, " (", allRowsJSON.length, ") as JSON"] }) }), hasSelection && (_jsxs(_Fragment, { children: [
|
|
35
|
+
_jsx("li", { role: "presentation", children: _jsxs("button", { className: "dropdown-item", type: "button", role: "menuitem", "aria-label": `Download selected ${pluralLabel} as CSV file`, disabled: selectedRowsJSON.length === 0, onClick: () => downloadJSONAsCSV(selectedRowsJSON, `${filenameBase}_selected.csv`), children: ["Selected ", selectedRowsJSON.length === 1 ? singularLabel : pluralLabel, " (", selectedRowsJSON.length, ") as CSV"] }) }), _jsx("li", { role: "presentation", children: _jsxs("button", { className: "dropdown-item", type: "button", role: "menuitem", "aria-label": `Download selected ${pluralLabel} as JSON file`, disabled: selectedRowsJSON.length === 0, onClick: () => downloadAsJSON(selectedRowsJSON, `${filenameBase}_selected.json`), children: ["Selected ", selectedRowsJSON.length === 1 ? singularLabel : pluralLabel, " (", selectedRowsJSON.length, ") as JSON"] }) })
|
|
36
|
+
] })), _jsx("li", { role: "presentation", children: _jsxs("button", { className: "dropdown-item", type: "button", role: "menuitem", "aria-label": `Download filtered ${pluralLabel} as CSV file`, disabled: filteredRowsJSON.length === 0, onClick: () => downloadJSONAsCSV(filteredRowsJSON, `${filenameBase}_filtered.csv`), children: ["Filtered ", filteredRowsJSON.length === 1 ? singularLabel : pluralLabel, " (", filteredRowsJSON.length, ") as CSV"] }) }), _jsx("li", { role: "presentation", children: _jsxs("button", { className: "dropdown-item", type: "button", role: "menuitem", "aria-label": `Download filtered ${pluralLabel} as JSON file`, disabled: filteredRowsJSON.length === 0, onClick: () => downloadAsJSON(filteredRowsJSON, `${filenameBase}_filtered.json`), children: ["Filtered ", filteredRowsJSON.length === 1 ? singularLabel : pluralLabel, " (", filteredRowsJSON.length, ") as JSON"] }) })
|
|
37
|
+
] })
|
|
38
|
+
] }));
|
|
31
39
|
}
|
|
32
40
|
//# sourceMappingURL=TanstackTableDownloadButton.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"TanstackTableDownloadButton.js","sourceRoot":"","sources":["../../src/components/TanstackTableDownloadButton.tsx"],"names":[],"mappings":"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","sourcesContent":["import type { Table } from '@tanstack/react-table';\n\nimport { downloadAsCSV, downloadAsJSON } from '@prairielearn/browser-utils';\n\nexport interface TanstackTableCsvCell {\n value: string | number | null;\n /** The name of the column in the CSV file. */\n name: string;\n}\n\nexport interface TanstackTableDownloadButtonProps<RowDataModel> {\n table: Table<RowDataModel>;\n filenameBase: string;\n mapRowToData: (row: RowDataModel) => TanstackTableCsvCell[] | null;\n singularLabel: string;\n pluralLabel: string;\n hasSelection: boolean;\n}\n/**\n * @param params\n * @param params.table - The table model\n * @param params.filenameBase - The base filename for the downloads\n * @param params.mapRowToData - A function that maps a row to a record where the\n * keys are the column names, and the values are the cell values. The key order is important,\n * and should match the expected order of the columns in the CSV file. If the function returns null,\n * the row will be skipped.\n * @param params.singularLabel - The singular label for a single row in the table, e.g. \"student\"\n * @param params.pluralLabel - The plural label for multiple rows in the table, e.g. \"students\"\n * @param params.hasSelection - Whether the table has selection enabled\n */\nexport function TanstackTableDownloadButton<RowDataModel>({\n table,\n filenameBase,\n mapRowToData,\n singularLabel,\n pluralLabel,\n hasSelection,\n}: TanstackTableDownloadButtonProps<RowDataModel>) {\n const allRows = table.getCoreRowModel().rows.map((row) => row.original);\n const allRowsJSON = allRows.map(mapRowToData).filter((row) => row !== null);\n const filteredRows = table.getRowModel().rows.map((row) => row.original);\n const filteredRowsJSON = filteredRows.map(mapRowToData).filter((row) => row !== null);\n const selectedRows = table.getSelectedRowModel().rows.map((row) => row.original);\n const selectedRowsJSON = selectedRows.map(mapRowToData).filter((row) => row !== null);\n\n function downloadJSONAsCSV(jsonRows: TanstackTableCsvCell[][], filename: string): void {\n if (jsonRows.length === 0) {\n throw new Error('No rows to download');\n }\n\n const header = jsonRows[0].map((cell) => cell.name);\n const csvRows = jsonRows.map((row) => row.map((cell) => cell.value));\n downloadAsCSV(header, csvRows, filename);\n }\n\n return (\n <div class=\"btn-group\">\n <button\n type=\"button\"\n data-bs-toggle=\"dropdown\"\n aria-expanded=\"false\"\n aria-haspopup=\"true\"\n aria-label={`Download ${pluralLabel} data in various formats`}\n class=\"btn btn-light btn-sm dropdown-toggle\"\n >\n <i aria-hidden=\"true\" class=\"pe-2 bi bi-download\" />\n <span class=\"d-none d-sm-inline\">Download</span>\n </button>\n <ul class=\"dropdown-menu\" role=\"menu\" aria-label=\"Download options\">\n <li role=\"presentation\">\n <button\n class=\"dropdown-item\"\n type=\"button\"\n role=\"menuitem\"\n aria-label={`Download all ${pluralLabel} as CSV file`}\n disabled={allRowsJSON.length === 0}\n onClick={() => downloadJSONAsCSV(allRowsJSON, `${filenameBase}.csv`)}\n >\n All {pluralLabel} ({allRowsJSON.length}) as CSV\n </button>\n </li>\n <li role=\"presentation\">\n <button\n class=\"dropdown-item\"\n type=\"button\"\n role=\"menuitem\"\n aria-label={`Download all ${pluralLabel} as JSON file`}\n disabled={allRowsJSON.length === 0}\n onClick={() => downloadAsJSON(allRowsJSON, `${filenameBase}.json`)}\n >\n All {pluralLabel} ({allRowsJSON.length}) as JSON\n </button>\n </li>\n {hasSelection && (\n <>\n <li role=\"presentation\">\n <button\n class=\"dropdown-item\"\n type=\"button\"\n role=\"menuitem\"\n aria-label={`Download selected ${pluralLabel} as CSV file`}\n disabled={selectedRowsJSON.length === 0}\n onClick={() => downloadJSONAsCSV(selectedRowsJSON, `${filenameBase}_selected.csv`)}\n >\n Selected {selectedRowsJSON.length === 1 ? singularLabel : pluralLabel} (\n {selectedRowsJSON.length}) as CSV\n </button>\n </li>\n <li role=\"presentation\">\n <button\n class=\"dropdown-item\"\n type=\"button\"\n role=\"menuitem\"\n aria-label={`Download selected ${pluralLabel} as JSON file`}\n disabled={selectedRowsJSON.length === 0}\n onClick={() => downloadAsJSON(selectedRowsJSON, `${filenameBase}_selected.json`)}\n >\n Selected {selectedRowsJSON.length === 1 ? singularLabel : pluralLabel} (\n {selectedRowsJSON.length}) as JSON\n </button>\n </li>\n </>\n )}\n <li role=\"presentation\">\n <button\n class=\"dropdown-item\"\n type=\"button\"\n role=\"menuitem\"\n aria-label={`Download filtered ${pluralLabel} as CSV file`}\n disabled={filteredRowsJSON.length === 0}\n onClick={() => downloadJSONAsCSV(filteredRowsJSON, `${filenameBase}_filtered.csv`)}\n >\n Filtered {filteredRowsJSON.length === 1 ? singularLabel : pluralLabel} (\n {filteredRowsJSON.length}) as CSV\n </button>\n </li>\n <li role=\"presentation\">\n <button\n class=\"dropdown-item\"\n type=\"button\"\n role=\"menuitem\"\n aria-label={`Download filtered ${pluralLabel} as JSON file`}\n disabled={filteredRowsJSON.length === 0}\n onClick={() => downloadAsJSON(filteredRowsJSON, `${filenameBase}_filtered.json`)}\n >\n Filtered {filteredRowsJSON.length === 1 ? singularLabel : pluralLabel} (\n {filteredRowsJSON.length}) as JSON\n </button>\n </li>\n </ul>\n </div>\n );\n}\n"]}
|
|
1
|
+
{"version":3,"file":"TanstackTableDownloadButton.js","sourceRoot":"","sources":["../../src/components/TanstackTableDownloadButton.tsx"],"names":[],"mappings":"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","sourcesContent":["import type { Table } from '@tanstack/react-table';\n\nimport { downloadAsCSV, downloadAsJSON } from '@prairielearn/browser-utils';\n\nexport interface TanstackTableCsvCell {\n value: string | number | null;\n /** The name of the column in the CSV file. */\n name: string;\n}\n\nexport interface TanstackTableDownloadButtonProps<RowDataModel> {\n table: Table<RowDataModel>;\n filenameBase: string;\n mapRowToData: (row: RowDataModel) => TanstackTableCsvCell[] | null;\n singularLabel: string;\n pluralLabel: string;\n hasSelection: boolean;\n}\n/**\n * @param params\n * @param params.table - The table model\n * @param params.filenameBase - The base filename for the downloads\n * @param params.mapRowToData - A function that maps a row to a record where the\n * keys are the column names, and the values are the cell values. The key order is important,\n * and should match the expected order of the columns in the CSV file. If the function returns null,\n * the row will be skipped.\n * @param params.singularLabel - The singular label for a single row in the table, e.g. \"student\"\n * @param params.pluralLabel - The plural label for multiple rows in the table, e.g. \"students\"\n * @param params.hasSelection - Whether the table has selection enabled\n */\nexport function TanstackTableDownloadButton<RowDataModel>({\n table,\n filenameBase,\n mapRowToData,\n singularLabel,\n pluralLabel,\n hasSelection,\n}: TanstackTableDownloadButtonProps<RowDataModel>) {\n const allRows = table.getCoreRowModel().rows.map((row) => row.original);\n const allRowsJSON = allRows.map(mapRowToData).filter((row) => row !== null);\n const filteredRows = table.getRowModel().rows.map((row) => row.original);\n const filteredRowsJSON = filteredRows.map(mapRowToData).filter((row) => row !== null);\n const selectedRows = table.getSelectedRowModel().rows.map((row) => row.original);\n const selectedRowsJSON = selectedRows.map(mapRowToData).filter((row) => row !== null);\n\n function downloadJSONAsCSV(jsonRows: TanstackTableCsvCell[][], filename: string): void {\n if (jsonRows.length === 0) {\n throw new Error('No rows to download');\n }\n\n const header = jsonRows[0].map((cell) => cell.name);\n const csvRows = jsonRows.map((row) => row.map((cell) => cell.value));\n downloadAsCSV(header, csvRows, filename);\n }\n\n return (\n <div className=\"btn-group\">\n <button\n type=\"button\"\n data-bs-toggle=\"dropdown\"\n aria-expanded=\"false\"\n aria-haspopup=\"true\"\n aria-label={`Download ${pluralLabel} data in various formats`}\n className=\"btn btn-light btn-sm dropdown-toggle\"\n >\n <i aria-hidden=\"true\" className=\"pe-2 bi bi-download\" />\n <span className=\"d-none d-sm-inline\">Download</span>\n </button>\n <ul className=\"dropdown-menu\" role=\"menu\" aria-label=\"Download options\">\n <li role=\"presentation\">\n <button\n className=\"dropdown-item\"\n type=\"button\"\n role=\"menuitem\"\n aria-label={`Download all ${pluralLabel} as CSV file`}\n disabled={allRowsJSON.length === 0}\n onClick={() => downloadJSONAsCSV(allRowsJSON, `${filenameBase}.csv`)}\n >\n All {pluralLabel} ({allRowsJSON.length}) as CSV\n </button>\n </li>\n <li role=\"presentation\">\n <button\n className=\"dropdown-item\"\n type=\"button\"\n role=\"menuitem\"\n aria-label={`Download all ${pluralLabel} as JSON file`}\n disabled={allRowsJSON.length === 0}\n onClick={() => downloadAsJSON(allRowsJSON, `${filenameBase}.json`)}\n >\n All {pluralLabel} ({allRowsJSON.length}) as JSON\n </button>\n </li>\n {hasSelection && (\n <>\n <li role=\"presentation\">\n <button\n className=\"dropdown-item\"\n type=\"button\"\n role=\"menuitem\"\n aria-label={`Download selected ${pluralLabel} as CSV file`}\n disabled={selectedRowsJSON.length === 0}\n onClick={() => downloadJSONAsCSV(selectedRowsJSON, `${filenameBase}_selected.csv`)}\n >\n Selected {selectedRowsJSON.length === 1 ? singularLabel : pluralLabel} (\n {selectedRowsJSON.length}) as CSV\n </button>\n </li>\n <li role=\"presentation\">\n <button\n className=\"dropdown-item\"\n type=\"button\"\n role=\"menuitem\"\n aria-label={`Download selected ${pluralLabel} as JSON file`}\n disabled={selectedRowsJSON.length === 0}\n onClick={() => downloadAsJSON(selectedRowsJSON, `${filenameBase}_selected.json`)}\n >\n Selected {selectedRowsJSON.length === 1 ? singularLabel : pluralLabel} (\n {selectedRowsJSON.length}) as JSON\n </button>\n </li>\n </>\n )}\n <li role=\"presentation\">\n <button\n className=\"dropdown-item\"\n type=\"button\"\n role=\"menuitem\"\n aria-label={`Download filtered ${pluralLabel} as CSV file`}\n disabled={filteredRowsJSON.length === 0}\n onClick={() => downloadJSONAsCSV(filteredRowsJSON, `${filenameBase}_filtered.csv`)}\n >\n Filtered {filteredRowsJSON.length === 1 ? singularLabel : pluralLabel} (\n {filteredRowsJSON.length}) as CSV\n </button>\n </li>\n <li role=\"presentation\">\n <button\n className=\"dropdown-item\"\n type=\"button\"\n role=\"menuitem\"\n aria-label={`Download filtered ${pluralLabel} as JSON file`}\n disabled={filteredRowsJSON.length === 0}\n onClick={() => downloadAsJSON(filteredRowsJSON, `${filenameBase}_filtered.json`)}\n >\n Filtered {filteredRowsJSON.length === 1 ? singularLabel : pluralLabel} (\n {filteredRowsJSON.length}) as JSON\n </button>\n </li>\n </ul>\n </div>\n );\n}\n"]}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { Header, Table } from '@tanstack/table-core';
|
|
2
|
-
import type { JSX } from '
|
|
3
|
-
export declare function TanstackTableHeaderCell<RowDataModel>({ header, filters, table, handleResizeEnd, isPinned, measurementMode
|
|
2
|
+
import type { JSX } from 'react';
|
|
3
|
+
export declare function TanstackTableHeaderCell<RowDataModel>({ header, filters, table, handleResizeEnd, isPinned, measurementMode }: {
|
|
4
4
|
header: Header<RowDataModel, unknown>;
|
|
5
5
|
filters: Record<string, (props: {
|
|
6
6
|
header: Header<RowDataModel, unknown>;
|
|
@@ -9,5 +9,5 @@ export declare function TanstackTableHeaderCell<RowDataModel>({ header, filters,
|
|
|
9
9
|
handleResizeEnd?: () => void;
|
|
10
10
|
isPinned: 'left' | false;
|
|
11
11
|
measurementMode?: boolean;
|
|
12
|
-
}): JSX.Element;
|
|
12
|
+
}): import("react/jsx-runtime").JSX.Element;
|
|
13
13
|
//# sourceMappingURL=TanstackTableHeaderCell.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"TanstackTableHeaderCell.d.ts","sourceRoot":"","sources":["../../src/components/TanstackTableHeaderCell.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,MAAM,EAAiB,KAAK,EAAE,MAAM,sBAAsB,CAAC;AAEzE,OAAO,KAAK,
|
|
1
|
+
{"version":3,"file":"TanstackTableHeaderCell.d.ts","sourceRoot":"","sources":["../../src/components/TanstackTableHeaderCell.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,MAAM,EAAiB,KAAK,EAAE,MAAM,sBAAsB,CAAC;AAEzE,OAAO,KAAK,EAAiB,GAAG,EAAE,MAAM,OAAO,CAAC;AA8FhD,wBAAgB,uBAAuB,CAAC,YAAY,EAAE,EACpD,MAAM,EACN,OAAO,EACP,KAAK,EACL,eAAe,EACf,QAAQ,EACR,eAAuB,EACxB,EAAE;IACD,MAAM,EAAE,MAAM,CAAC,YAAY,EAAE,OAAO,CAAC,CAAC;IACtC,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,CAAC,KAAK,EAAE;QAAE,MAAM,EAAE,MAAM,CAAC,YAAY,EAAE,OAAO,CAAC,CAAA;KAAE,KAAK,GAAG,CAAC,OAAO,CAAC,CAAC;IAC3F,KAAK,EAAE,KAAK,CAAC,YAAY,CAAC,CAAC;IAC3B,eAAe,CAAC,EAAE,MAAM,IAAI,CAAC;IAC7B,QAAQ,EAAE,MAAM,GAAG,KAAK,CAAC;IACzB,eAAe,CAAC,EAAE,OAAO,CAAC;CAC3B,2CA6FA","sourcesContent":["import { flexRender } from '@tanstack/react-table';\nimport type { Header, SortDirection, Table } from '@tanstack/table-core';\nimport clsx from 'clsx';\nimport type { CSSProperties, JSX } from 'react';\n\nfunction SortIcon({ sortMethod }: { sortMethod: false | SortDirection }) {\n if (sortMethod === 'asc') {\n return <i className=\"bi bi-sort-up-alt\" aria-hidden=\"true\" />;\n } else if (sortMethod === 'desc') {\n return <i className=\"bi bi-sort-down\" aria-hidden=\"true\" />;\n } else {\n return <i className=\"bi bi-arrow-down-up opacity-75 text-muted\" aria-hidden=\"true\" />;\n }\n}\n\nfunction ResizeHandle<RowDataModel>({\n header,\n setColumnSizing,\n onResizeEnd,\n}: {\n header: Header<RowDataModel, unknown>;\n setColumnSizing: Table<RowDataModel>['setColumnSizing'];\n onResizeEnd?: () => void;\n}) {\n const minSize = header.column.columnDef.minSize ?? 0;\n const maxSize = header.column.columnDef.maxSize ?? 0;\n const handleKeyDown = (e: React.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 className=\"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 className=\"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 onMouseUp={onResizeEnd}\n onTouchStart={header.getResizeHandler()}\n onTouchEnd={onResizeEnd}\n onKeyDown={handleKeyDown}\n />\n </div>\n );\n}\n\n/**\n * Helper function to get aria-sort value\n */\nfunction 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\nexport function TanstackTableHeaderCell<RowDataModel>({\n header,\n filters,\n table,\n handleResizeEnd,\n isPinned,\n measurementMode = false,\n}: {\n header: Header<RowDataModel, unknown>;\n filters: Record<string, (props: { header: Header<RowDataModel, unknown> }) => JSX.Element>;\n table: Table<RowDataModel>;\n handleResizeEnd?: () => void;\n isPinned: 'left' | false;\n measurementMode?: boolean;\n}) {\n const sortDirection = header.column.getIsSorted();\n const canSort = header.column.getCanSort();\n const canFilter = header.column.getCanFilter();\n const columnName =\n header.column.columnDef.meta?.label ??\n (typeof header.column.columnDef.header === 'string'\n ? header.column.columnDef.header\n : header.column.id);\n\n // In measurement mode, we don't want to set the size of the header from tanstack.\n const headerSize = measurementMode ? undefined : header.getSize();\n const style: CSSProperties = {\n display: 'flex',\n width: headerSize,\n minWidth: 0,\n maxWidth: headerSize,\n flexShrink: 0,\n position: isPinned === 'left' ? 'sticky' : 'relative',\n top: 0,\n zIndex: isPinned === 'left' ? 2 : 1,\n left: isPinned === 'left' ? header.getStart() : undefined,\n };\n\n const isNormalColumn = canSort || canFilter;\n\n return (\n <th\n key={header.id}\n data-column-id={header.column.id}\n className={clsx(isPinned === 'left' && 'bg-light')}\n style={style}\n aria-sort={canSort ? getAriaSort(sortDirection) : undefined}\n role=\"columnheader\"\n >\n <div\n className={clsx(\n 'd-flex align-items-center flex-grow-1',\n isNormalColumn ? 'justify-content-between' : 'justify-content-center',\n )}\n style={{\n minWidth: 0,\n }}\n >\n <div\n className={clsx(\n 'text-nowrap text-start',\n // e.g. checkboxes\n !isNormalColumn && 'd-flex align-items-center justify-content-center',\n )}\n style={{\n minWidth: 0,\n flex: '1 1 0%',\n overflow: 'hidden',\n textOverflow: 'ellipsis',\n background: 'transparent',\n border: 'none',\n }}\n >\n {header.isPlaceholder\n ? null\n : flexRender(header.column.columnDef.header, header.getContext())}\n {canSort && (\n <span className=\"visually-hidden\">, {getAriaSort(sortDirection)}, click to sort</span>\n )}\n </div>\n\n {(canSort || canFilter) && (\n <div className=\"d-flex align-items-center\" style={{ flexShrink: 0 }}>\n {canSort && (\n <button\n type=\"button\"\n className=\"btn btn-link text-muted p-0\"\n aria-label={`Sort ${columnName.toLowerCase()}, current sort is ${getAriaSort(sortDirection)}`}\n title={`Sort ${columnName.toLowerCase()}`}\n onClick={header.column.getToggleSortingHandler()}\n >\n <SortIcon sortMethod={sortDirection} />\n </button>\n )}\n {canFilter && filters[header.column.id]?.({ header })}\n </div>\n )}\n </div>\n {header.column.getCanResize() && (\n <ResizeHandle\n header={header}\n setColumnSizing={table.setColumnSizing}\n onResizeEnd={handleResizeEnd}\n />\n )}\n </th>\n );\n}\n"]}
|