diff --git a/.github/workflows/publish-and-deploy.yml b/.github/workflows/publish-and-deploy.yml index cf0a6a562..6c83937e8 100644 --- a/.github/workflows/publish-and-deploy.yml +++ b/.github/workflows/publish-and-deploy.yml @@ -29,7 +29,7 @@ jobs: - name: Setup Node.js uses: actions/setup-node@v4 with: - node-version: '20' + node-version: '22' cache: yarn - name: Restore cache uses: actions/cache@v4 diff --git a/CHANGELOG.md b/CHANGELOG.md index 4192cb232..eac468fec 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,11 @@ The following is a list of notable changes to the Mantine DataTable component. Minor versions that are not listed in the changelog are bug fixes and small improvements. +## 7.11.3 (2024-07-30) + +- Update dev dependencies +- Implement drag and drop support (see [#616](https://github.com/icflorescu/mantine-datatable/pull/616)) + ## 7.11.2 (2024-07-10) - Update dev dependencies diff --git a/README.md b/README.md index 244297ef5..c1c95a6ad 100644 --- a/README.md +++ b/README.md @@ -33,6 +33,7 @@ The [lightweight](https://bundlephobia.com/package/mantine-datatable), dependenc - **[Automatically-scrollable](https://icflorescu.github.io/mantine-datatable/examples/scrollable-vs-auto-height)** - automatically scrollable or auto-height - **[AutoAnimate support](https://icflorescu.github.io/mantine-datatable/examples/using-with-auto-animate)** - animate row sorting, addition and removal - **[Column reordering, toggling](https://icflorescu.github.io/mantine-datatable/examples/column-dragging-and-toggling)** and **[resizing](https://icflorescu.github.io/mantine-datatable/examples/column-resizing)** - thanks to the outstanding work of [Giovambattista Fazioli](https://github.com/gfazioli) +- **[Drag-and-drop support](https://icflorescu.github.io/mantine-datatable/examples/row-dragging)** - implemented using [@hello-pangea/dnd](https://github.com/hello-pangea/dnd) thanks to the outstanding work of [Mohd Ahmad](https://github.com/MohdAhmad1) - **More** - check out the [full documentation](https://icflorescu.github.io/mantine-datatable/) ## Trusted by the community diff --git a/app/(home)/page.tsx b/app/(home)/page.tsx index 0e860a645..cbc2e447f 100644 --- a/app/(home)/page.tsx +++ b/app/(home)/page.tsx @@ -54,7 +54,8 @@ export default function HomePage() { cell data rendering,{' '} context menus,{' '} row expansion,{' '} - nesting and{' '} + nesting,{' '} + drag-and-drop reordering support and{' '} more diff --git a/app/config.ts b/app/config.ts index a2ae05b23..251386c35 100644 --- a/app/config.ts +++ b/app/config.ts @@ -152,6 +152,11 @@ export const ROUTES: RouteInfo[] = [ title: 'Column dragging and toggling', description: `Example: dragging & toggling ${PRODUCT_NAME} columns`, }, + { + href: '/examples/row-dragging', + title: 'Row dragging', + description: `Example: dragging ${PRODUCT_NAME} rows`, + }, { href: '/examples/column-resizing', title: 'Column resizing', diff --git a/app/examples/row-dragging/RowDraggingExample.tsx b/app/examples/row-dragging/RowDraggingExample.tsx new file mode 100644 index 000000000..03d232c37 --- /dev/null +++ b/app/examples/row-dragging/RowDraggingExample.tsx @@ -0,0 +1,84 @@ +'use client'; + +import { DragDropContext, Draggable, type DropResult, Droppable } from '@hello-pangea/dnd'; +import { TableTd } from '@mantine/core'; +import { notifications } from '@mantine/notifications'; +import { IconGripVertical } from '@tabler/icons-react'; +import { DataTable, DataTableColumn, DataTableDraggableRow } from '__PACKAGE__'; +import { useState } from 'react'; +import companies from '~/data/companies.json'; + +interface RecordData { + id: string; + name: string; + streetAddress: string; + city: string; + state: string; + missionStatement: string; +} + +export function RowDraggingExample() { + const [records, setRecords] = useState(companies); + + const handleDragEnd = (result: DropResult) => { + if (!result.destination) return; + + const items = Array.from(records); + const sourceIndex = result.source.index; + const destinationIndex = result.destination.index; + const [reorderedItem] = items.splice(sourceIndex, 1); + items.splice(destinationIndex, 0, reorderedItem); + + setRecords(items); + notifications.show({ + title: 'Table reordered', + message: `The company named "${items[sourceIndex].name}" has been moved from position ${sourceIndex + 1} to ${destinationIndex + 1}.`, + color: 'blue', + }); + }; + + const columns: DataTableColumn[] = [ + // add empty header column for the drag handle + { accessor: '', hiddenContent: true, width: 30 }, + { accessor: 'name', width: 150 }, + { accessor: 'streetAddress', width: 150 }, + { accessor: 'city', width: 150 }, + { accessor: 'state', width: 150 }, + ]; + + return ( + + + columns={columns} + records={records} + height={400} + withTableBorder + withColumnBorders + tableWrapper={({ children }) => ( + + {(provided) => ( +
+ {children} + {provided.placeholder} +
+ )} +
+ )} + styles={{ table: { tableLayout: 'fixed' } }} + rowFactory={({ record, index, rowProps, children }) => ( + + {(provided, snapshot) => ( + + {/** custom drag handle */} + + + + {children} + + )} + + )} + /> +
+ ); +} diff --git a/app/examples/row-dragging/page.tsx b/app/examples/row-dragging/page.tsx new file mode 100644 index 000000000..32e255640 --- /dev/null +++ b/app/examples/row-dragging/page.tsx @@ -0,0 +1,40 @@ +import { Code } from '@mantine/core'; +import type { Route } from 'next'; +import { PRODUCT_NAME, REPO_LINK } from '~/app/config'; +import { CodeBlock } from '~/components/CodeBlock'; +import { ExternalLink } from '~/components/ExternalLink'; +import { PageNavigation } from '~/components/PageNavigation'; +import { PageTitle } from '~/components/PageTitle'; +import { Txt } from '~/components/Txt'; +import { readCodeFile } from '~/lib/code'; +import { allPromiseProps, getRouteMetadata } from '~/lib/utils'; +import { RowDraggingExample } from './RowDraggingExample'; + +const PATH: Route = '/examples/row-dragging'; + +export const metadata = getRouteMetadata(PATH); + +export default async function BasicUsageExamplePage() { + const code = await allPromiseProps({ + 'RowDraggingExample.tsx': readCodeFile(`${PATH}/RowDraggingExample.tsx`), + 'companies.json': readCodeFile('/../data/companies.json'), + }); + + return ( + <> + + + Starting with v7.11.3, {PRODUCT_NAME} also supports row dragging (implemented with{' '} + @hello-pangea/dnd library in{' '} + this PR). +
+ Here is how you would implement it in your project: +
+ + The code above will produce the following result: + + + + + ); +} diff --git a/data/companies.json b/data/companies.json index b004e4c37..e3b882d48 100644 --- a/data/companies.json +++ b/data/companies.json @@ -78,5 +78,29 @@ "city": "West Gerry", "state": "KS", "missionStatement": "Synthesize customized portals." + }, + { + "id": "a9c0b7f0-3a1b-4b0c-8f3d-6a6a7b7b7b7q2", + "name": "Kling - McLaughlin", + "streetAddress": "8346 Kertzmann Square", + "city": "South Joesph", + "state": "ID", + "missionStatement": "Reinvent cross-platform channels." + }, + { + "id": "a9c0b7f0-3a1b-4b0c-8f3d-6a6a7b7b7b72b", + "name": "Jogi - McLaughlin", + "streetAddress": "83462 Shazam Street", + "city": "North Joesph", + "state": "ID", + "missionStatement": "Eliminate best-of-breed e-markets." + }, + { + "id": "a9c0b7f0-3a1b-4b0c-8f3d-6a6af7b7b7b7b", + "name": "Jogi - McLaughlin", + "streetAddress": "83462 Shazam Street", + "city": "North Joesph", + "state": "ID", + "missionStatement": "Eliminate best-of-breed e-markets." } ] \ No newline at end of file diff --git a/package.json b/package.json index 10eca4dac..fcf56581f 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "mantine-datatable", - "version": "7.11.2", + "version": "7.11.3", "description": "The lightweight, dependency-free, dark-theme aware table component for your Mantine UI data-rich applications, featuring asynchronous data loading support, pagination, intuitive Gmail-style additive batch rows selection, column sorting, custom cell data rendering, row expansion, nesting, context menus, and much more", "keywords": [ "mantine", @@ -71,46 +71,47 @@ "format": "prettier --write ." }, "devDependencies": { - "@docsearch/react": "^3.6.0", - "@ducanh2912/next-pwa": "^10.2.7", + "@docsearch/react": "^3.6.1", + "@ducanh2912/next-pwa": "^10.2.8", "@faker-js/faker": "^8.4.1", "@formkit/auto-animate": "^0.8.2", - "@mantine/code-highlight": "^7.11.1", - "@mantine/core": "^7.11.1", - "@mantine/dates": "^7.11.1", - "@mantine/hooks": "^7.11.1", - "@mantine/modals": "^7.11.1", - "@mantine/notifications": "^7.11.1", - "@tabler/icons-react": "^3.10.0", - "@tanstack/react-query": "^5.50.1", - "@types/lodash": "^4.17.6", - "@types/node": "^20.14.10", + "@hello-pangea/dnd": "^16.6.0", + "@mantine/code-highlight": "^7.11.2", + "@mantine/core": "^7.11.2", + "@mantine/dates": "^7.11.2", + "@mantine/hooks": "^7.11.2", + "@mantine/modals": "^7.11.2", + "@mantine/notifications": "^7.11.2", + "@tabler/icons-react": "^3.11.0", + "@tanstack/react-query": "^5.51.15", + "@types/lodash": "^4.17.7", + "@types/node": "^22.0.0", "@types/react": "^18.3.3", "@types/react-dom": "^18.3.0", - "@typescript-eslint/eslint-plugin": "^7.16.0", - "@typescript-eslint/parser": "^7.16.0", + "@typescript-eslint/eslint-plugin": "^7.18.0", + "@typescript-eslint/parser": "^7.18.0", "clsx": "^2.1.1", "cssnano": "^7.0.4", - "dayjs": "^1.11.11", + "dayjs": "^1.11.12", "eslint": "^8", - "eslint-config-next": "^14.2.4", + "eslint-config-next": "^14.2.5", "eslint-config-prettier": "^9.1.0", "lodash": "^4.17.21", - "mantine-contextmenu": "^7.11.2", - "next": "^14.2.4", - "postcss": "^8.4.39", + "mantine-contextmenu": "^7.11.3", + "next": "^14.2.5", + "postcss": "^8.4.40", "postcss-cli": "^11.0.0", "postcss-import": "^16.1.0", - "postcss-preset-mantine": "^1.15.0", + "postcss-preset-mantine": "^1.17.0", "postcss-simple-vars": "^7.0.1", - "prettier": "^3.3.2", + "prettier": "^3.3.3", "react": "^18.3.1", "react-dom": "^18.3.1", "sharp": "^0.33.4", "swr": "^2.2.5", - "tsup": "^8.1.0", - "typescript": "^5.5.3", - "webpack": "^5.92.1" + "tsup": "^8.2.3", + "typescript": "^5.5.4", + "webpack": "^5.93.0" }, "peerDependencies": { "@mantine/core": ">=7.8", diff --git a/package/DataTable.tsx b/package/DataTable.tsx index 1b1b92ff9..86e5c8bba 100644 --- a/package/DataTable.tsx +++ b/package/DataTable.tsx @@ -127,6 +127,8 @@ export function DataTable({ classNames, style, styles, + rowFactory, + tableWrapper, ...otherProps }: DataTableProps) { const { @@ -161,8 +163,8 @@ export function DataTable({ const rowExpansionInfo = useRowExpansion({ rowExpansion, records, idAccessor }); const processScrolling = useCallback(() => { - const scrollTop = localScrollViewportRef.current?.scrollTop || 0; - const scrollLeft = localScrollViewportRef.current?.scrollLeft || 0; + const scrollTop = localScrollViewportRef.current?.scrollTop ?? 0; + const scrollLeft = localScrollViewportRef.current?.scrollLeft ?? 0; if (fetching || tableHeight <= scrollViewportHeight) { setScrolledToTop(true); @@ -261,6 +263,14 @@ export function DataTable({ const marginProperties = { m, my, mx, mt, mb, ml, mr }; + const TableWrapper = useCallback( + ({ children }: { children: React.ReactNode }) => { + if (tableWrapper) return tableWrapper({ children }); + return children; + }, + [tableWrapper] + ); + return ( ({ }), style, styles?.root, + { + position: 'relative', + }, ]} > ({ onScrollPositionChange={handleScrollPositionChange} scrollAreaProps={scrollAreaProps} > - - {noHeader ? null : ( - - - ref={headerRef} - selectionColumnHeaderRef={selectionColumnHeaderRef} - className={classNames?.header} - style={styles?.header} + +
+ {noHeader ? null : ( + + + ref={headerRef} + selectionColumnHeaderRef={selectionColumnHeaderRef} + className={classNames?.header} + style={styles?.header} + columns={effectiveColumns} + defaultColumnProps={defaultColumnProps} + groups={groups} + sortStatus={sortStatus} + sortIcons={sortIcons} + onSortStatusChange={onSortStatusChange} + selectionTrigger={selectionTrigger} + selectionVisible={selectionColumnVisible} + selectionChecked={allSelectableRecordsSelected} + selectionIndeterminate={someRecordsSelected && !allSelectableRecordsSelected} + onSelectionChange={handleHeaderSelectionChange} + selectionCheckboxProps={{ ...selectionCheckboxProps, ...allRecordsSelectionCheckboxProps }} + selectorCellShadowVisible={selectorCellShadowVisible} + selectionColumnClassName={selectionColumnClassName} + selectionColumnStyle={selectionColumnStyle} + /> + + )} + + {recordsLength ? ( + records.map((record, index) => { + const recordId = getRecordId(record, idAccessor); + const isSelected = selectedRecordIds?.includes(recordId) || false; + + let handleSelectionChange: React.MouseEventHandler | undefined; + + if (onSelectedRecordsChange && selectedRecords) { + handleSelectionChange = (e) => { + if (e.nativeEvent.shiftKey && lastSelectionChangeIndex !== null) { + const targetRecords = records.filter( + index > lastSelectionChangeIndex + ? (rec, idx) => + idx >= lastSelectionChangeIndex && + idx <= index && + (isRecordSelectable ? isRecordSelectable(rec, idx) : true) + : (rec, idx) => + idx >= index && + idx <= lastSelectionChangeIndex && + (isRecordSelectable ? isRecordSelectable(rec, idx) : true) + ); + onSelectedRecordsChange( + isSelected + ? differenceBy(selectedRecords, targetRecords, (r) => getRecordId(r, idAccessor)) + : uniqBy([...selectedRecords, ...targetRecords], (r) => getRecordId(r, idAccessor)) + ); + } else { + onSelectedRecordsChange( + isSelected + ? selectedRecords.filter((rec) => getRecordId(rec, idAccessor) !== recordId) + : uniqBy([...selectedRecords, record], (rec) => getRecordId(rec, idAccessor)) + ); + } + setLastSelectionChangeIndex(index); + }; + } + + return ( + + key={recordId as React.Key} + record={record} + index={index} + columns={effectiveColumns} + defaultColumnProps={defaultColumnProps} + defaultColumnRender={defaultColumnRender} + selectionTrigger={selectionTrigger} + selectionVisible={selectionColumnVisible} + selectionChecked={isSelected} + onSelectionChange={handleSelectionChange} + isRecordSelectable={isRecordSelectable} + selectionCheckboxProps={selectionCheckboxProps} + getSelectionCheckboxProps={getRecordSelectionCheckboxProps} + onClick={onRowClick} + onDoubleClick={onRowDoubleClick} + onCellClick={onCellClick} + onCellDoubleClick={onCellDoubleClick} + onContextMenu={onRowContextMenu} + onCellContextMenu={onCellContextMenu} + expansion={rowExpansionInfo} + color={rowColor} + backgroundColor={rowBackgroundColor} + className={rowClassName} + style={rowStyle} + customAttributes={customRowAttributes} + selectorCellShadowVisible={selectorCellShadowVisible} + selectionColumnClassName={selectionColumnClassName} + selectionColumnStyle={selectionColumnStyle} + idAccessor={idAccessor as string} + rowFactory={rowFactory} + /> + ); + }) + ) : ( + + )} + + + {effectiveColumns.some(({ footer }) => footer) && ( + + ref={footerRef} + className={classNames?.footer} + style={styles?.footer} columns={effectiveColumns} defaultColumnProps={defaultColumnProps} - groups={groups} - sortStatus={sortStatus} - sortIcons={sortIcons} - onSortStatusChange={onSortStatusChange} - selectionTrigger={selectionTrigger} selectionVisible={selectionColumnVisible} - selectionChecked={allSelectableRecordsSelected} - selectionIndeterminate={someRecordsSelected && !allSelectableRecordsSelected} - onSelectionChange={handleHeaderSelectionChange} - selectionCheckboxProps={{ ...selectionCheckboxProps, ...allRecordsSelectionCheckboxProps }} selectorCellShadowVisible={selectorCellShadowVisible} - selectionColumnClassName={selectionColumnClassName} - selectionColumnStyle={selectionColumnStyle} + scrollDiff={tableHeight - scrollViewportHeight} /> - - )} - - {recordsLength ? ( - records.map((record, index) => { - const recordId = getRecordId(record, idAccessor); - const isSelected = selectedRecordIds?.includes(recordId) || false; - - let handleSelectionChange: React.MouseEventHandler | undefined; - if (onSelectedRecordsChange && selectedRecords) { - handleSelectionChange = (e) => { - if (e.nativeEvent.shiftKey && lastSelectionChangeIndex !== null) { - const targetRecords = records.filter( - index > lastSelectionChangeIndex - ? (rec, idx) => - idx >= lastSelectionChangeIndex && - idx <= index && - (isRecordSelectable ? isRecordSelectable(rec, idx) : true) - : (rec, idx) => - idx >= index && - idx <= lastSelectionChangeIndex && - (isRecordSelectable ? isRecordSelectable(rec, idx) : true) - ); - onSelectedRecordsChange( - isSelected - ? differenceBy(selectedRecords, targetRecords, (r) => getRecordId(r, idAccessor)) - : uniqBy([...selectedRecords, ...targetRecords], (r) => getRecordId(r, idAccessor)) - ); - } else { - onSelectedRecordsChange( - isSelected - ? selectedRecords.filter((rec) => getRecordId(rec, idAccessor) !== recordId) - : uniqBy([...selectedRecords, record], (rec) => getRecordId(rec, idAccessor)) - ); - } - setLastSelectionChangeIndex(index); - }; - } - - return ( - - key={recordId as React.Key} - record={record} - index={index} - columns={effectiveColumns} - defaultColumnProps={defaultColumnProps} - defaultColumnRender={defaultColumnRender} - selectionTrigger={selectionTrigger} - selectionVisible={selectionColumnVisible} - selectionChecked={isSelected} - onSelectionChange={handleSelectionChange} - isRecordSelectable={isRecordSelectable} - selectionCheckboxProps={selectionCheckboxProps} - getSelectionCheckboxProps={getRecordSelectionCheckboxProps} - onClick={onRowClick} - onDoubleClick={onRowDoubleClick} - onCellClick={onCellClick} - onCellDoubleClick={onCellDoubleClick} - onContextMenu={onRowContextMenu} - onCellContextMenu={onCellContextMenu} - expansion={rowExpansionInfo} - color={rowColor} - backgroundColor={rowBackgroundColor} - className={rowClassName} - style={rowStyle} - customAttributes={customRowAttributes} - selectorCellShadowVisible={selectorCellShadowVisible} - selectionColumnClassName={selectionColumnClassName} - selectionColumnStyle={selectionColumnStyle} - /> - ); - }) - ) : ( - )} - - {effectiveColumns.some(({ footer }) => footer) && ( - - ref={footerRef} - className={classNames?.footer} - style={styles?.footer} - columns={effectiveColumns} - defaultColumnProps={defaultColumnProps} - selectionVisible={selectionColumnVisible} - selectorCellShadowVisible={selectorCellShadowVisible} - scrollDiff={tableHeight - scrollViewportHeight} - /> - )} -
+ +
+ {page && ( (function ( + { children, isDragging, ...props }, + passedRef +) { + const ref = useRef>(null); + const mergedRef = useMergedRef(ref, passedRef); + + useEffect(() => { + // a simple fix to keep column width as in table + if (!ref.current) return; + if (!isDragging) return; + + const tbody = ref.current.parentElement!; + const table = tbody.parentElement!; + const thead = table.children[0]; + const headerRow = thead.children[0]; + + for (let index = 0; index < headerRow.children.length; index++) { + const headerCell = headerRow.children[index]; + const headerCellDimensions = headerCell.getBoundingClientRect(); + + const cell = ref.current.children[index] as HTMLTableCellElement; + + cell.style.height = headerCellDimensions.height + 'px'; + cell.style.width = headerCellDimensions.width + 'px'; + cell.style.minWidth = headerCellDimensions.width + 'px'; + cell.style.maxWidth = headerCellDimensions.width + 'px'; + } + }, [isDragging, children]); + + return ( + + {children} + + ); +}); + +DataTableDraggableRow.displayName = 'DataTableDraggableRow'; + +export { DataTableDraggableRow }; diff --git a/package/DataTableHeader.tsx b/package/DataTableHeader.tsx index ea1109495..8890daf24 100644 --- a/package/DataTableHeader.tsx +++ b/package/DataTableHeader.tsx @@ -61,6 +61,7 @@ export const DataTableHeader = forwardRef(function DataTableHeader( selectorCellShadowVisible, selectionColumnClassName, selectionColumnStyle, + // draggableRows, }: DataTableHeaderProps, ref: React.ForwardedRef ) { @@ -109,8 +110,10 @@ export const DataTableHeader = forwardRef(function DataTableHeader( ))} )} + {!groups && allRecordsSelectorCell} + {columns.map(({ hidden, ...columnProps }, index) => { if (hidden) return null; diff --git a/package/DataTableRow.tsx b/package/DataTableRow.tsx index 5b4bdf3a6..5d8c85e74 100644 --- a/package/DataTableRow.tsx +++ b/package/DataTableRow.tsx @@ -1,4 +1,4 @@ -import { TableTr, type CheckboxProps, type MantineColor, type MantineStyleProp } from '@mantine/core'; +import { MantineTheme, TableTr, type CheckboxProps, type MantineColor, type MantineStyleProp } from '@mantine/core'; import clsx from 'clsx'; import { DataTableRowCell } from './DataTableRowCell'; import { DataTableRowExpansion } from './DataTableRowExpansion'; @@ -9,6 +9,7 @@ import type { DataTableCellClickHandler, DataTableColumn, DataTableDefaultColumnProps, + DataTableProps, DataTableRowClickHandler, DataTableSelectionTrigger, } from './types'; @@ -48,7 +49,8 @@ type DataTableRowProps = { selectorCellShadowVisible: boolean; selectionColumnClassName: string | undefined; selectionColumnStyle: MantineStyleProp | undefined; -}; + idAccessor: string; +} & Pick, 'rowFactory'>; export function DataTableRow({ record, @@ -78,47 +80,11 @@ export function DataTableRow({ selectorCellShadowVisible, selectionColumnClassName, selectionColumnStyle, -}: DataTableRowProps) { - return ( - <> - { - if (expansion) { - const { isExpandable, isRowExpanded, expandOnClick, expandRow, collapseRow } = expansion; - if (isExpandable({ record, index }) && expandOnClick) { - if (isRowExpanded(record)) { - collapseRow(record); - } else { - expandRow(record); - } - } - } - onClick?.({ event: e, record, index }); - }} - onDoubleClick={onDoubleClick ? (e) => onDoubleClick({ event: e, record, index }) : undefined} - onContextMenu={onContextMenu ? (e) => onContextMenu({ event: e, record, index }) : undefined} - style={[ - color || backgroundColor - ? (theme) => { - const colorValue = color?.(record, index); - const backgroundColorValue = backgroundColor?.(record, index); - return getRowCssVariables({ theme, color: colorValue, backgroundColor: backgroundColorValue }); - } - : undefined, - style?.(record, index), - ]} - {...customAttributes?.(record, index)} - > + rowFactory, +}: Readonly>) { + function TableCols() { + return ( + <> {selectionVisible && ( className={selectionColumnClassName} @@ -134,8 +100,9 @@ export function DataTableRow({ getCheckboxProps={getSelectionCheckboxProps} /> )} - {columns.map(({ hidden, ...columnProps }, columnIndex) => { - if (hidden) return null; + + {columns.map(({ hidden, hiddenContent, ...columnProps }, columnIndex) => { + if (hidden || hiddenContent) return null; const { accessor, @@ -184,15 +151,129 @@ export function DataTableRow({ /> ); })} + + ); + } + + const expandedElement = expansion && ( +