diff --git a/packages/mantine-react-table/src/components/body/MRT_TableBody.tsx b/packages/mantine-react-table/src/components/body/MRT_TableBody.tsx index 8a8dde29d..3b020d212 100644 --- a/packages/mantine-react-table/src/components/body/MRT_TableBody.tsx +++ b/packages/mantine-react-table/src/components/body/MRT_TableBody.tsx @@ -5,7 +5,6 @@ import { type TableProps, TableTbody, type TableTbodyProps, - Text, } from '@mantine/core'; import { MRT_TableBodyRow, Memo_MRT_TableBodyRow } from './MRT_TableBodyRow'; import { useMRT_RowVirtualizer } from '../../hooks/useMRT_RowVirtualizer'; @@ -18,6 +17,7 @@ import { type MRT_VirtualItem, } from '../../types'; import { parseFromValuesOrFunc } from '../../utils/utils'; +import { MRT_TableBodyEmptyRow } from './MRT_TableBodyEmptyRow'; export interface MRT_TableBodyProps extends TableTbodyProps { @@ -42,16 +42,14 @@ export const MRT_TableBody = ({ enableStickyFooter, enableStickyHeader, layoutMode, - localization, mantineTableBodyProps, memoMode, renderDetailPanel, - renderEmptyRowsFallback, rowPinningDisplayMode, }, - refs: { tableFooterRef, tableHeadRef, tablePaperRef }, + refs: { tableFooterRef, tableHeadRef }, } = table; - const { columnFilters, globalFilter, isFullScreen, rowPinning } = getState(); + const { isFullScreen, rowPinning } = getState(); const tableBodyProps = { ...parseFromValuesOrFunc(mantineTableBodyProps, { table }), @@ -133,34 +131,7 @@ export const MRT_TableBody = ({ > {tableBodyProps?.children ?? (!rows.length ? ( - - - {renderEmptyRowsFallback?.({ table }) ?? ( - - {globalFilter || columnFilters.length - ? localization.noResultsFound - : localization.noRecordsToDisplay} - - )} - - + ) : ( <> {(virtualRows ?? rows).map( diff --git a/packages/mantine-react-table/src/components/body/MRT_TableBodyEmptyRow.tsx b/packages/mantine-react-table/src/components/body/MRT_TableBodyEmptyRow.tsx new file mode 100644 index 000000000..c680fda37 --- /dev/null +++ b/packages/mantine-react-table/src/components/body/MRT_TableBodyEmptyRow.tsx @@ -0,0 +1,98 @@ +import clsx from 'clsx'; +import classes from './MRT_TableBody.module.css'; +import { useMemo } from 'react'; +import { type TableProps, Text, TableTd, TableTrProps } from '@mantine/core'; +import { MRT_TableBodyRow } from './MRT_TableBodyRow'; +import { + type MRT_Row, + type MRT_RowData, + type MRT_TableInstance, +} from '../../types'; +import { createRow } from '@tanstack/react-table'; +import { MRT_ExpandButton } from '../buttons/MRT_ExpandButton'; + +interface Props extends TableTrProps { + table: MRT_TableInstance; + tableProps: Partial; +} + +export const MRT_TableBodyEmptyRow = ({ + table, + tableProps, + ...commonRowProps +}: Props) => { + const { + getState, + options: { + layoutMode, + localization, + renderDetailPanel, + renderEmptyRowsFallback, + }, + refs: { tablePaperRef }, + } = table; + const { columnFilters, globalFilter } = getState(); + + const emptyRow = useMemo( + () => + createRow( + table as any, + 'mrt-row-empty', + {} as TData, + 0, + 0, + ) as MRT_Row, + [], + ); + + const emptyRowProps = { + ...commonRowProps, + renderedRowIndex: 0, + row: emptyRow, + virtualRow: undefined, + }; + + return ( + + {renderDetailPanel && ( + + + + )} + + {renderEmptyRowsFallback?.({ table }) ?? ( + + {globalFilter || columnFilters.length + ? localization.noResultsFound + : localization.noRecordsToDisplay} + + )} + + + ); +}; diff --git a/packages/mantine-react-table/src/components/body/MRT_TableBodyRow.tsx b/packages/mantine-react-table/src/components/body/MRT_TableBodyRow.tsx index ca17e85b8..8da13085c 100644 --- a/packages/mantine-react-table/src/components/body/MRT_TableBodyRow.tsx +++ b/packages/mantine-react-table/src/components/body/MRT_TableBodyRow.tsx @@ -34,6 +34,7 @@ interface Props extends TableTrProps { } export const MRT_TableBodyRow = ({ + children, columnVirtualizer, numRows, pinnedRowIds, @@ -189,7 +190,7 @@ export const MRT_TableBodyRow = ({ {virtualPaddingLeft ? ( ) : null} - {(virtualColumns ?? row.getVisibleCells()).map( + {children ? children : (virtualColumns ?? row.getVisibleCells()).map( (cellOrVirtualCell, renderedColumnIndex) => { let cell = cellOrVirtualCell as MRT_Cell; if (columnVirtualizer) { diff --git a/packages/mantine-react-table/src/components/body/MRT_TableDetailPanel.tsx b/packages/mantine-react-table/src/components/body/MRT_TableDetailPanel.tsx index 232ce8941..a155a1959 100644 --- a/packages/mantine-react-table/src/components/body/MRT_TableDetailPanel.tsx +++ b/packages/mantine-react-table/src/components/body/MRT_TableDetailPanel.tsx @@ -10,6 +10,7 @@ import { type MRT_VirtualItem, } from '../../types'; import { parseFromValuesOrFunc } from '../../utils/utils'; +import { MRT_EditCellTextInput } from '../inputs/MRT_EditCellTextInput'; interface Props extends TableTdProps { parentRowRef: RefObject; @@ -57,8 +58,15 @@ export const MRT_TableDetailPanel = ({ ...rest, }; + const internalEditComponents = row + .getAllCells() + .filter((cell) => cell.column.columnDef.columnDefType === 'data') + .map((cell) => ( + + )); + const DetailPanel = - !isLoading && row.getIsExpanded() && renderDetailPanel?.({ row, table }); + !isLoading && row.getIsExpanded() && renderDetailPanel?.({ row, table, internalEditComponents }); return ( extends ActionIconProps { row: MRT_Row; @@ -42,10 +43,18 @@ export const MRT_ExpandButton = ({ }), ...rest, }; + + const internalEditComponents = row + .getAllCells() + .filter((cell) => cell.column.columnDef.columnDefType === 'data') + .map((cell) => ( + + )); + const canExpand = row.getCanExpand(); const isExpanded = row.getIsExpanded(); - const DetailPanel = !!renderDetailPanel?.({ row, table }); + const DetailPanel = !!renderDetailPanel?.({ row, table, internalEditComponents }); const handleToggleExpand = (event: MouseEvent) => { event.stopPropagation(); @@ -59,9 +68,8 @@ export const MRT_ExpandButton = ({ = Omit< renderDetailPanel?: (props: { row: MRT_Row; table: MRT_TableInstance; + internalEditComponents: ReactNode[]; }) => ReactNode; renderEditRowModalContent?: (props: { internalEditComponents: ReactNode[]; diff --git a/packages/mantine-react-table/stories/features/Editing.stories.tsx b/packages/mantine-react-table/stories/features/Editing.stories.tsx index b84214a42..dfa173c82 100644 --- a/packages/mantine-react-table/stories/features/Editing.stories.tsx +++ b/packages/mantine-react-table/stories/features/Editing.stories.tsx @@ -1,5 +1,5 @@ import { useState } from 'react'; -import { Flex, Stack, Switch, Title } from '@mantine/core'; +import { Center, Flex, Group, Stack, Switch, Title, Text } from '@mantine/core'; import { type MRT_Cell, type MRT_ColumnOrderState, @@ -7,6 +7,7 @@ import { type MRT_TableOptions, MantineReactTable, MRT_ColumnDef, + useMantineReactTable, } from '../../src'; import { faker } from '@faker-js/faker'; import { type Meta } from '@storybook/react'; @@ -1238,3 +1239,64 @@ export const EditingTurnedOnDynamically = () => { ); }; + +export const EditingInDetailPannel = () => { + const [withData, setWithData] = useState(false); + + const columns = [ + { + accessorKey: 'firstName', + header: 'First Name', + }, + { + accessorKey: 'lastName', + header: 'Last Name', + }, + { + accessorKey: 'address', + header: 'Address', + }, + { + accessorKey: 'state', + header: 'State', + }, + { + accessorKey: 'phoneNumber', + enableEditing: false, + header: 'Phone Number', + }, + ]; + + const table = useMantineReactTable({ + columns, + data: withData ? data : [], + renderDetailPanel: ({ table, row, internalEditComponents }) => ( +
+
e.preventDefault()}> + + {internalEditComponents} + +
+ + + +
+ ), + renderEmptyRowsFallback: () => ( +
+ This table is empty, click on the chevron to add a record +
+ ), + }); + + return ( + + setWithData(e.currentTarget.checked)} + /> + + + ); +}; diff --git a/packages/mantine-react-table/stories/features/EmptyRow.stories.tsx b/packages/mantine-react-table/stories/features/EmptyRow.stories.tsx new file mode 100644 index 000000000..930e687af --- /dev/null +++ b/packages/mantine-react-table/stories/features/EmptyRow.stories.tsx @@ -0,0 +1,105 @@ +import { useContextMenu } from 'mantine-contextmenu'; +import { + type MRT_ColumnDef, + MRT_EditActionButtons, + MantineReactTable, + useMantineReactTable, +} from '../../src'; +import { Center, Flex, Group, Stack, Text, Switch } from '@mantine/core'; +import { type Meta } from '@storybook/react'; +import { useState } from 'react'; + +const meta: Meta = { + title: 'Features/Empty Row Examples', +}; + +export default meta; + +type Person = { + firstName: string; + lastName: string; + address?: string; + city?: string; +}; + +const data: Person[] = []; + +const columns: MRT_ColumnDef[] = [ + { + accessorKey: 'firstName', + header: 'First Name', + }, + { + accessorKey: 'lastName', + header: 'Last Name', + }, + { + accessorKey: 'address', + header: 'Address', + }, + { + accessorKey: 'city', + header: 'City', + }, +]; + +export const DefaultEmptyRow = () => { + const table = useMantineReactTable({ columns, data }); + + return ; +}; + +export const CustomEmptyRow = () => { + const table = useMantineReactTable({ + columns, + data, + renderEmptyRowsFallback: () => ( +
+ OMG THERE ARE NO ROWS 😳 +
+ ), + }); + + return ; +}; + +export const EmptyRowContextMenu = () => { + //Now that empty row is an actual row, same context menu can be used, that is used on actual row data + + const { showContextMenu } = useContextMenu(); + const table = useMantineReactTable({ + columns, + data, + mantineTableBodyRowProps: { + onContextMenu: showContextMenu([ + { + key: 'add', + title: 'Insert new row', + onClick: () => console.log('Insert new row'), + }, + { + key: 'download', + onClick: () => console.log('download'), + }, + ]), + }, + }); + + return ; +}; + +export const EmptyRowExplanationPannel = () => { + //Now that empty row is an actual row, detail pannel is available for empty row as well + const table = useMantineReactTable({ + columns, + data, + renderDetailPanel: () => ( +
+ There are no records to display, check if there are any active filters + on the table, clearing them might help. +
+ ), + }); + + return ; +};