diff --git a/docs/List.md b/docs/List.md
index 3586d408647..2c3aa6983b3 100644
--- a/docs/List.md
+++ b/docs/List.md
@@ -1978,6 +1978,7 @@ Here are all the props accepted by the component:
* [`rowStyle`](#row-style-function)
* [`rowClick`](#rowclick)
* [`expand`](#expand)
+* [`isRowExpandable`](#isrowexpandable)
* [`isRowSelectable`](#isrowselectable)
* [`optimized`](#performance)
@@ -2198,6 +2199,31 @@ const PostList = props => (
)
```
+### `isRowExpandable`
+
+You can customize which rows will allow to show an expandable panel below them using the `isRowExpandable` prop. It expects a function that will receive the record of each `` and returns a boolean expression. For instance, this code shows an expand button only for rows that has a detail to show:
+
+```jsx
+const PostPanel = ({ id, record, resource }) => (
+
+);
+
+const PostList = props => (
+
+ }
+ isRowExpandable={row => row.has_detail}
+ >
+
+
+
+
+
+
+
+)
+```
+
### `isRowSelectable`
You can customize which rows will show a selection checkbox using the `isRowSelectable` prop. It expects a function that will receive the record of each `` and returns a boolean expression. For instance, this code shows a checkbox only for rows with an id greater than 300:
diff --git a/packages/ra-ui-materialui/src/list/datagrid/Datagrid.tsx b/packages/ra-ui-materialui/src/list/datagrid/Datagrid.tsx
index 3f25423a42f..17e69d8d1c4 100644
--- a/packages/ra-ui-materialui/src/list/datagrid/Datagrid.tsx
+++ b/packages/ra-ui-materialui/src/list/datagrid/Datagrid.tsx
@@ -8,6 +8,7 @@ import {
useEffect,
FC,
ReactElement,
+ useMemo,
} from 'react';
import PropTypes from 'prop-types';
import {
@@ -34,6 +35,7 @@ import DatagridLoading from './DatagridLoading';
import DatagridBody, { PureDatagridBody } from './DatagridBody';
import useDatagridStyles from './useDatagridStyles';
import { ClassesOverride } from '../../types';
+import DatagridContextProvider from './DatagridContextProvider';
/**
* The Datagrid component renders a list of records as a table.
@@ -116,6 +118,7 @@ const Datagrid: FC = React.forwardRef((props, ref) => {
hasBulkActions = false,
hover,
isRowSelectable,
+ isRowExpandable,
resource,
rowClick,
rowStyle,
@@ -137,6 +140,10 @@ const Datagrid: FC = React.forwardRef((props, ref) => {
} = useListContext(props);
const version = useVersion();
+ const contextValue = useMemo(() => ({ isRowExpandable }), [
+ isRowExpandable,
+ ]);
+
const updateSort = useCallback(
event => {
event.stopPropagation();
@@ -246,83 +253,87 @@ const Datagrid: FC = React.forwardRef((props, ref) => {
* the datagrid displays the current data.
*/
return (
-
-
-
- {expand && (
-
- )}
- {hasBulkActions && (
-
- 0 &&
- all.length > 0 &&
- all.every(id => selectedIds.includes(id))
- }
- onChange={handleSelectAll}
+
+
+
+
+ {expand && (
+
-
- )}
- {Children.map(children, (field, index) =>
- isValidElement(field) ? (
-
- ) : null
- )}
-
-
- {cloneElement(
- body,
- {
- basePath,
- className: classes.tbody,
- classes,
- expand,
- rowClick,
- data,
- hasBulkActions,
- hover,
- ids,
- onToggleItem: handleToggleItem,
- resource,
- rowStyle,
- selectedIds,
- isRowSelectable,
- version,
- },
- children
- )}
-
+ >
+ 0 &&
+ all.length > 0 &&
+ all.every(id =>
+ selectedIds.includes(id)
+ )
+ }
+ onChange={handleSelectAll}
+ />
+
+ )}
+ {Children.map(children, (field, index) =>
+ isValidElement(field) ? (
+
+ ) : null
+ )}
+
+
+ {cloneElement(
+ body,
+ {
+ basePath,
+ className: classes.tbody,
+ classes,
+ expand,
+ rowClick,
+ data,
+ hasBulkActions,
+ hover,
+ ids,
+ onToggleItem: handleToggleItem,
+ resource,
+ rowStyle,
+ selectedIds,
+ isRowSelectable,
+ version,
+ },
+ children
+ )}
+
+
);
});
@@ -353,6 +364,7 @@ Datagrid.propTypes = {
total: PropTypes.number,
version: PropTypes.number,
isRowSelectable: PropTypes.func,
+ isRowExpandable: PropTypes.func,
};
type RowClickFunction = (
@@ -376,6 +388,7 @@ export interface DatagridProps extends Omit {
hasBulkActions?: boolean;
hover?: boolean;
isRowSelectable?: (record: Record) => boolean;
+ isRowExpandable?: (record: Record) => boolean;
optimized?: boolean;
rowClick?: string | RowClickFunction;
rowStyle?: (record: Record, index: number) => any;
diff --git a/packages/ra-ui-materialui/src/list/datagrid/DatagridContext.ts b/packages/ra-ui-materialui/src/list/datagrid/DatagridContext.ts
new file mode 100644
index 00000000000..119658216ef
--- /dev/null
+++ b/packages/ra-ui-materialui/src/list/datagrid/DatagridContext.ts
@@ -0,0 +1,12 @@
+import { createContext } from 'react';
+import { Record as RaRecord } from 'ra-core';
+
+const DatagridContext = createContext({});
+
+DatagridContext.displayName = 'DatagridContext';
+
+export type DatagridContextValue = {
+ isRowExpandable?: (record: RaRecord) => boolean;
+};
+
+export default DatagridContext;
diff --git a/packages/ra-ui-materialui/src/list/datagrid/DatagridContextProvider.tsx b/packages/ra-ui-materialui/src/list/datagrid/DatagridContextProvider.tsx
new file mode 100644
index 00000000000..fb0baf972f5
--- /dev/null
+++ b/packages/ra-ui-materialui/src/list/datagrid/DatagridContextProvider.tsx
@@ -0,0 +1,16 @@
+import React, { ReactElement, ReactNode } from 'react';
+import DatagridContext, { DatagridContextValue } from './DatagridContext';
+
+const DatagridContextProvider = ({
+ children,
+ value,
+}: {
+ children: ReactNode;
+ value: DatagridContextValue;
+}): ReactElement => (
+
+ {children}
+
+);
+
+export default DatagridContextProvider;
diff --git a/packages/ra-ui-materialui/src/list/datagrid/DatagridRow.spec.tsx b/packages/ra-ui-materialui/src/list/datagrid/DatagridRow.spec.tsx
index e2f691cfbf1..d2c23fb20b8 100644
--- a/packages/ra-ui-materialui/src/list/datagrid/DatagridRow.spec.tsx
+++ b/packages/ra-ui-materialui/src/list/datagrid/DatagridRow.spec.tsx
@@ -6,6 +6,7 @@ import { createMemoryHistory } from 'history';
import { Router } from 'react-router-dom';
import DatagridRow from './DatagridRow';
+import DatagridContextProvider from './DatagridContextProvider';
const TitleField = ({ record }: any): JSX.Element => (
{record.title}
@@ -48,6 +49,46 @@ describe('', () => {
};
};
+ describe('isRowExpandable', () => {
+ it('should show the expand button if it returns true', () => {
+ const contextValue = { isRowExpandable: () => true };
+
+ const { queryAllByText, getByText } = renderWithRouter(
+
+ }
+ >
+
+
+
+ );
+ expect(queryAllByText('expanded')).toHaveLength(0);
+ fireEvent.click(getByText('hello'));
+ expect(queryAllByText('expanded')).toHaveLength(1);
+ });
+
+ it('should not show the expand button if it returns false', () => {
+ const contextValue = { isRowExpandable: () => false };
+
+ const { queryAllByText, getByText } = renderWithRouter(
+
+ }
+ >
+
+
+
+ );
+ expect(queryAllByText('expanded')).toHaveLength(0);
+ fireEvent.click(getByText('hello'));
+ expect(queryAllByText('expanded')).toHaveLength(0);
+ });
+ });
+
describe('rowClick', () => {
it("should redirect to edit page if the 'edit' option is selected", () => {
const { getByText, history } = renderWithRouter(
diff --git a/packages/ra-ui-materialui/src/list/datagrid/DatagridRow.tsx b/packages/ra-ui-materialui/src/list/datagrid/DatagridRow.tsx
index 467c9836b81..a62b2f2382c 100644
--- a/packages/ra-ui-materialui/src/list/datagrid/DatagridRow.tsx
+++ b/packages/ra-ui-materialui/src/list/datagrid/DatagridRow.tsx
@@ -31,6 +31,7 @@ import { useHistory } from 'react-router-dom';
import DatagridCell from './DatagridCell';
import ExpandRowButton from './ExpandRowButton';
import useDatagridStyles from './useDatagridStyles';
+import { useDatagridContext } from './useDatagridContext';
const computeNbColumns = (expand, children, hasBulkActions) =>
expand
@@ -59,20 +60,31 @@ const DatagridRow: FC = React.forwardRef((props, ref) => {
selectable,
...rest
} = props;
+
+ const context = useDatagridContext();
+ const expandable =
+ (!context ||
+ !context.isRowExpandable ||
+ context.isRowExpandable(record)) &&
+ expand;
const resource = useResourceContext(props);
const [expanded, toggleExpanded] = useExpanded(resource, id);
const [nbColumns, setNbColumns] = useState(
- computeNbColumns(expand, children, hasBulkActions)
+ computeNbColumns(expandable, children, hasBulkActions)
);
useEffect(() => {
// Fields can be hidden dynamically based on permissions;
// The expand panel must span over the remaining columns
// So we must recompute the number of columns to span on
- const newNbColumns = computeNbColumns(expand, children, hasBulkActions);
+ const newNbColumns = computeNbColumns(
+ expandable,
+ children,
+ hasBulkActions
+ );
if (newNbColumns !== nbColumns) {
setNbColumns(newNbColumns);
}
- }, [expand, nbColumns, children, hasBulkActions]);
+ }, [expandable, nbColumns, children, hasBulkActions]);
const history = useHistory();
@@ -145,12 +157,14 @@ const DatagridRow: FC = React.forwardRef((props, ref) => {
padding="none"
className={classes.expandIconCell}
>
-
+ {expandable && (
+
+ )}
)}
{hasBulkActions && (
@@ -181,7 +195,7 @@ const DatagridRow: FC = React.forwardRef((props, ref) => {
) : null
)}
- {expand && expanded && (
+ {expandable && expanded && (
{isValidElement(expand)
diff --git a/packages/ra-ui-materialui/src/list/datagrid/useDatagridContext.ts b/packages/ra-ui-materialui/src/list/datagrid/useDatagridContext.ts
new file mode 100644
index 00000000000..726843d94c7
--- /dev/null
+++ b/packages/ra-ui-materialui/src/list/datagrid/useDatagridContext.ts
@@ -0,0 +1,20 @@
+import { useContext, useMemo } from 'react';
+import { DatagridProps } from './Datagrid';
+import DatagridContext, { DatagridContextValue } from './DatagridContext';
+import merge from 'lodash/merge';
+
+export const useDatagridContext = (
+ props?: DatagridProps
+): DatagridContextValue => {
+ const context = useContext(DatagridContext);
+
+ return useMemo(
+ () =>
+ merge(
+ {},
+ context,
+ props != null ? { isRowExpandable: props.isRowExpandable } : {}
+ ),
+ [context, props]
+ );
+};