From 455b50796c00ce1162271e28697215e6f244ee3a Mon Sep 17 00:00:00 2001 From: EdwardEB Date: Tue, 19 Nov 2024 16:58:45 +0200 Subject: [PATCH 1/4] Add MRT_TableBodyEmptyRow component to render an actual MRT_TableBodyRow, not just a tr --- .../src/components/body/MRT_TableBody.tsx | 37 +---- .../components/body/MRT_TableBodyEmptyRow.tsx | 104 ++++++++++++++ .../src/components/body/MRT_TableBodyRow.tsx | 3 +- .../components/body/MRT_TableDetailPanel.tsx | 10 +- .../components/buttons/MRT_ExpandButton.tsx | 16 ++- packages/mantine-react-table/src/types.ts | 1 + .../stories/features/EmptyRow.stories.tsx | 135 ++++++++++++++++++ 7 files changed, 267 insertions(+), 39 deletions(-) create mode 100644 packages/mantine-react-table/src/components/body/MRT_TableBodyEmptyRow.tsx create mode 100644 packages/mantine-react-table/stories/features/EmptyRow.stories.tsx 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..f70f92bc8 --- /dev/null +++ b/packages/mantine-react-table/src/components/body/MRT_TableBodyEmptyRow.tsx @@ -0,0 +1,104 @@ +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'; + +export interface Props extends TableTrProps { + renderedRowIndex?: number; + 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/EmptyRow.stories.tsx b/packages/mantine-react-table/stories/features/EmptyRow.stories.tsx new file mode 100644 index 000000000..ed1f74528 --- /dev/null +++ b/packages/mantine-react-table/stories/features/EmptyRow.stories.tsx @@ -0,0 +1,135 @@ +import { useContextMenu } from 'mantine-contextmenu'; +import { + type MRT_ColumnDef, + MRT_EditActionButtons, + MantineReactTable, +} from '../../src'; +import { Center, Flex, Group, Text } from '@mantine/core'; +import { type Meta } from '@storybook/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 = () => { + return ; +}; + +export const CustomEmptyRow = () => { + return ( + ( +
+ OMG THERE ARE NO ROWS 😳 +
+ )} + /> + ); +}; + +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(); + + return ( + console.log('Insert new row'), + }, + { + key: 'download', + onClick: () => console.log('download'), + }, + ]), + }} + /> + ); +}; + +export const EmptyRowExplanationPannel = () => { + //Now that empty row is an actual row, detail pannel is available for empty row as well + + return ( + { + return ( +
+ There are no records to display, check if there are any active + filters on the table, clearing them might help. +
+ ); + }} + /> + ); +}; + +export const FormInEmptyRow = () => { + //Now that empty row is an actual row, detail pannel is available, and can ne used as a form, maybe? + + return ( + ( +
+
e.preventDefault()}> + + {internalEditComponents} + +
+ + + +
+ )} + renderEmptyRowsFallback={()=>{ + return ( +
+ This table is empty, click on the chevron to add a record +
+ ) + }} + /> + ); +}; From 5ac7112ba156c6ed9c2ddb3f8f498d4c89cde92d Mon Sep 17 00:00:00 2001 From: Edward Engelbrecht Date: Thu, 28 Nov 2024 09:59:25 +0200 Subject: [PATCH 2/4] Update empty row examples and export MRT_TableBodyEmptyRow --- .../components/body/MRT_TableBodyEmptyRow.tsx | 14 +- packages/mantine-react-table/src/index.ts | 1 + .../stories/features/EmptyRow.stories.tsx | 136 +++++++++--------- 3 files changed, 71 insertions(+), 80 deletions(-) diff --git a/packages/mantine-react-table/src/components/body/MRT_TableBodyEmptyRow.tsx b/packages/mantine-react-table/src/components/body/MRT_TableBodyEmptyRow.tsx index f70f92bc8..c680fda37 100644 --- a/packages/mantine-react-table/src/components/body/MRT_TableBodyEmptyRow.tsx +++ b/packages/mantine-react-table/src/components/body/MRT_TableBodyEmptyRow.tsx @@ -1,12 +1,7 @@ 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 { type TableProps, Text, TableTd, TableTrProps } from '@mantine/core'; import { MRT_TableBodyRow } from './MRT_TableBodyRow'; import { type MRT_Row, @@ -16,8 +11,7 @@ import { import { createRow } from '@tanstack/react-table'; import { MRT_ExpandButton } from '../buttons/MRT_ExpandButton'; -export interface Props extends TableTrProps { - renderedRowIndex?: number; +interface Props extends TableTrProps { table: MRT_TableInstance; tableProps: Partial; } @@ -26,7 +20,7 @@ export const MRT_TableBodyEmptyRow = ({ table, tableProps, ...commonRowProps -}:Props) => { +}: Props) => { const { getState, options: { @@ -35,7 +29,7 @@ export const MRT_TableBodyEmptyRow = ({ renderDetailPanel, renderEmptyRowsFallback, }, - refs: { tablePaperRef }, + refs: { tablePaperRef }, } = table; const { columnFilters, globalFilter } = getState(); diff --git a/packages/mantine-react-table/src/index.ts b/packages/mantine-react-table/src/index.ts index f0825071c..93561251f 100644 --- a/packages/mantine-react-table/src/index.ts +++ b/packages/mantine-react-table/src/index.ts @@ -27,6 +27,7 @@ export * from './components/MantineReactTable'; export * from './components/body/MRT_TableBody'; export * from './components/body/MRT_TableBodyCell'; export * from './components/body/MRT_TableBodyCellValue'; +export * from './components/body/MRT_TableBodyEmptyRow'; export * from './components/body/MRT_TableBodyRow'; export * from './components/body/MRT_TableBodyRowGrabHandle'; export * from './components/body/MRT_TableBodyRowPinButton'; diff --git a/packages/mantine-react-table/stories/features/EmptyRow.stories.tsx b/packages/mantine-react-table/stories/features/EmptyRow.stories.tsx index ed1f74528..1241b27c9 100644 --- a/packages/mantine-react-table/stories/features/EmptyRow.stories.tsx +++ b/packages/mantine-react-table/stories/features/EmptyRow.stories.tsx @@ -3,6 +3,7 @@ import { type MRT_ColumnDef, MRT_EditActionButtons, MantineReactTable, + useMantineReactTable, } from '../../src'; import { Center, Flex, Group, Text } from '@mantine/core'; import { type Meta } from '@storybook/react'; @@ -42,94 +43,89 @@ const columns: MRT_ColumnDef[] = [ ]; export const DefaultEmptyRow = () => { - return ; + const table = useMantineReactTable({ columns, data }); + + return ; }; export const CustomEmptyRow = () => { - return ( - ( -
- OMG THERE ARE NO ROWS 😳 -
- )} - /> - ); + 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 ( - 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 ( - { - return ( -
- There are no records to display, check if there are any active - filters on the table, clearing them might help. -
- ); - }} - /> - ); + return ; }; export const FormInEmptyRow = () => { //Now that empty row is an actual row, detail pannel is available, and can ne used as a form, maybe? + const table = useMantineReactTable({ + columns, + data, + renderDetailPanel: ({ table, row, internalEditComponents }) => ( +
+
e.preventDefault()}> + + {internalEditComponents} + +
+ + + +
+ ), + renderEmptyRowsFallback: () => ( +
+ This table is empty, click on the chevron to add a record +
+ ), + }); - return ( - ( -
-
e.preventDefault()}> - - {internalEditComponents} - -
- - - -
- )} - renderEmptyRowsFallback={()=>{ - return ( -
- This table is empty, click on the chevron to add a record -
- ) - }} - /> - ); + return ; }; From 7bea27b9563978ca9be36d08cf281bc819ccfae2 Mon Sep 17 00:00:00 2001 From: Edward Engelbrecht Date: Thu, 28 Nov 2024 12:53:06 +0200 Subject: [PATCH 3/4] changed story to showcase form in detail panel when table is empty and not empty. --- .../stories/features/EmptyRow.stories.tsx | 39 ++++++++++++++++--- 1 file changed, 33 insertions(+), 6 deletions(-) diff --git a/packages/mantine-react-table/stories/features/EmptyRow.stories.tsx b/packages/mantine-react-table/stories/features/EmptyRow.stories.tsx index 1241b27c9..ef29a1e54 100644 --- a/packages/mantine-react-table/stories/features/EmptyRow.stories.tsx +++ b/packages/mantine-react-table/stories/features/EmptyRow.stories.tsx @@ -5,8 +5,9 @@ import { MantineReactTable, useMantineReactTable, } from '../../src'; -import { Center, Flex, Group, Text } from '@mantine/core'; +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', @@ -17,8 +18,8 @@ export default meta; type Person = { firstName: string; lastName: string; - address: string; - city: string; + address?: string; + city?: string; }; const data: Person[] = []; @@ -103,11 +104,28 @@ export const EmptyRowExplanationPannel = () => { return ; }; -export const FormInEmptyRow = () => { +const sampleData: Person[] = [ + { + firstName: 'Alice', + lastName: 'Smith', + }, + { + firstName: 'Bob', + lastName: 'Johnson', + }, + { + firstName: 'Charlie', + lastName: 'Williams', + }, +]; + +export const AddingOrEditingInDetailPannel = () => { //Now that empty row is an actual row, detail pannel is available, and can ne used as a form, maybe? + const [withData, setWithData] = useState(false); + const table = useMantineReactTable({ columns, - data, + data: withData ? sampleData: data, renderDetailPanel: ({ table, row, internalEditComponents }) => (
e.preventDefault()}> @@ -127,5 +145,14 @@ export const FormInEmptyRow = () => { ), }); - return ; + return ( + + setWithData(e.currentTarget.checked)} + /> + + + ); }; From 23b0a59ebd3deb0a6b26a307de7c0e6704772312 Mon Sep 17 00:00:00 2001 From: Edward Date: Fri, 29 Nov 2024 14:01:09 +0200 Subject: [PATCH 4/4] Move story to Editing Examples --- .../stories/features/Editing.stories.tsx | 64 ++++++++++++++++++- .../stories/features/EmptyRow.stories.tsx | 53 --------------- 2 files changed, 63 insertions(+), 54 deletions(-) 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 index ef29a1e54..930e687af 100644 --- a/packages/mantine-react-table/stories/features/EmptyRow.stories.tsx +++ b/packages/mantine-react-table/stories/features/EmptyRow.stories.tsx @@ -103,56 +103,3 @@ export const EmptyRowExplanationPannel = () => { return ; }; - -const sampleData: Person[] = [ - { - firstName: 'Alice', - lastName: 'Smith', - }, - { - firstName: 'Bob', - lastName: 'Johnson', - }, - { - firstName: 'Charlie', - lastName: 'Williams', - }, -]; - -export const AddingOrEditingInDetailPannel = () => { - //Now that empty row is an actual row, detail pannel is available, and can ne used as a form, maybe? - const [withData, setWithData] = useState(false); - - const table = useMantineReactTable({ - columns, - data: withData ? sampleData: 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)} - /> - - - ); -};