Skip to content

Commit 8bbd8a4

Browse files
authored
Merge pull request #5941 from WiXSL/add-isrowexpandable
[RFR] Added isRowExpandable prop to Datagrid component
2 parents 037af98 + 71aeac9 commit 8bbd8a4

File tree

7 files changed

+227
-85
lines changed

7 files changed

+227
-85
lines changed

docs/List.md

+26
Original file line numberDiff line numberDiff line change
@@ -1978,6 +1978,7 @@ Here are all the props accepted by the component:
19781978
* [`rowStyle`](#row-style-function)
19791979
* [`rowClick`](#rowclick)
19801980
* [`expand`](#expand)
1981+
* [`isRowExpandable`](#isrowexpandable)
19811982
* [`isRowSelectable`](#isrowselectable)
19821983
* [`optimized`](#performance)
19831984

@@ -2198,6 +2199,31 @@ const PostList = props => (
21982199
)
21992200
```
22002201

2202+
### `isRowExpandable`
2203+
2204+
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 `<DatagridRow>` and returns a boolean expression. For instance, this code shows an expand button only for rows that has a detail to show:
2205+
2206+
```jsx
2207+
const PostPanel = ({ id, record, resource }) => (
2208+
<div dangerouslySetInnerHTML={{ __html: record.body }} />
2209+
);
2210+
2211+
const PostList = props => (
2212+
<List {...props}>
2213+
<Datagrid
2214+
expand={<PostPanel />}
2215+
isRowExpandable={row => row.has_detail}
2216+
>
2217+
<TextField source="id" />
2218+
<TextField source="title" />
2219+
<DateField source="published_at" />
2220+
<BooleanField source="commentable" />
2221+
<EditButton />
2222+
</Datagrid>
2223+
</List>
2224+
)
2225+
```
2226+
22012227
### `isRowSelectable`
22022228

22032229
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 `<DatagridRow>` and returns a boolean expression. For instance, this code shows a checkbox only for rows with an id greater than 300:

packages/ra-ui-materialui/src/list/datagrid/Datagrid.tsx

+88-75
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import {
88
useEffect,
99
FC,
1010
ReactElement,
11+
useMemo,
1112
} from 'react';
1213
import PropTypes from 'prop-types';
1314
import {
@@ -34,6 +35,7 @@ import DatagridLoading from './DatagridLoading';
3435
import DatagridBody, { PureDatagridBody } from './DatagridBody';
3536
import useDatagridStyles from './useDatagridStyles';
3637
import { ClassesOverride } from '../../types';
38+
import DatagridContextProvider from './DatagridContextProvider';
3739

3840
/**
3941
* The Datagrid component renders a list of records as a table.
@@ -116,6 +118,7 @@ const Datagrid: FC<DatagridProps> = React.forwardRef((props, ref) => {
116118
hasBulkActions = false,
117119
hover,
118120
isRowSelectable,
121+
isRowExpandable,
119122
resource,
120123
rowClick,
121124
rowStyle,
@@ -137,6 +140,10 @@ const Datagrid: FC<DatagridProps> = React.forwardRef((props, ref) => {
137140
} = useListContext(props);
138141
const version = useVersion();
139142

143+
const contextValue = useMemo(() => ({ isRowExpandable }), [
144+
isRowExpandable,
145+
]);
146+
140147
const updateSort = useCallback(
141148
event => {
142149
event.stopPropagation();
@@ -246,83 +253,87 @@ const Datagrid: FC<DatagridProps> = React.forwardRef((props, ref) => {
246253
* the datagrid displays the current data.
247254
*/
248255
return (
249-
<Table
250-
ref={ref}
251-
className={classnames(classes.table, className)}
252-
size={size}
253-
{...sanitizeListRestProps(rest)}
254-
>
255-
<TableHead className={classes.thead}>
256-
<TableRow
257-
className={classnames(classes.row, classes.headerRow)}
258-
>
259-
{expand && (
260-
<TableCell
261-
padding="none"
262-
className={classnames(
263-
classes.headerCell,
264-
classes.expandHeader
265-
)}
266-
/>
267-
)}
268-
{hasBulkActions && (
269-
<TableCell
270-
padding="checkbox"
271-
className={classes.headerCell}
272-
>
273-
<Checkbox
274-
className="select-all"
275-
color="primary"
276-
checked={
277-
selectedIds.length > 0 &&
278-
all.length > 0 &&
279-
all.every(id => selectedIds.includes(id))
280-
}
281-
onChange={handleSelectAll}
256+
<DatagridContextProvider value={contextValue}>
257+
<Table
258+
ref={ref}
259+
className={classnames(classes.table, className)}
260+
size={size}
261+
{...sanitizeListRestProps(rest)}
262+
>
263+
<TableHead className={classes.thead}>
264+
<TableRow
265+
className={classnames(classes.row, classes.headerRow)}
266+
>
267+
{expand && (
268+
<TableCell
269+
padding="none"
270+
className={classnames(
271+
classes.headerCell,
272+
classes.expandHeader
273+
)}
282274
/>
283-
</TableCell>
284-
)}
285-
{Children.map(children, (field, index) =>
286-
isValidElement(field) ? (
287-
<DatagridHeaderCell
275+
)}
276+
{hasBulkActions && (
277+
<TableCell
278+
padding="checkbox"
288279
className={classes.headerCell}
289-
currentSort={currentSort}
290-
field={field}
291-
isSorting={
292-
currentSort.field ===
293-
((field.props as any).sortBy ||
294-
(field.props as any).source)
295-
}
296-
key={(field.props as any).source || index}
297-
resource={resource}
298-
updateSort={updateSort}
299-
/>
300-
) : null
301-
)}
302-
</TableRow>
303-
</TableHead>
304-
{cloneElement(
305-
body,
306-
{
307-
basePath,
308-
className: classes.tbody,
309-
classes,
310-
expand,
311-
rowClick,
312-
data,
313-
hasBulkActions,
314-
hover,
315-
ids,
316-
onToggleItem: handleToggleItem,
317-
resource,
318-
rowStyle,
319-
selectedIds,
320-
isRowSelectable,
321-
version,
322-
},
323-
children
324-
)}
325-
</Table>
280+
>
281+
<Checkbox
282+
className="select-all"
283+
color="primary"
284+
checked={
285+
selectedIds.length > 0 &&
286+
all.length > 0 &&
287+
all.every(id =>
288+
selectedIds.includes(id)
289+
)
290+
}
291+
onChange={handleSelectAll}
292+
/>
293+
</TableCell>
294+
)}
295+
{Children.map(children, (field, index) =>
296+
isValidElement(field) ? (
297+
<DatagridHeaderCell
298+
className={classes.headerCell}
299+
currentSort={currentSort}
300+
field={field}
301+
isSorting={
302+
currentSort.field ===
303+
((field.props as any).sortBy ||
304+
(field.props as any).source)
305+
}
306+
key={(field.props as any).source || index}
307+
resource={resource}
308+
updateSort={updateSort}
309+
/>
310+
) : null
311+
)}
312+
</TableRow>
313+
</TableHead>
314+
{cloneElement(
315+
body,
316+
{
317+
basePath,
318+
className: classes.tbody,
319+
classes,
320+
expand,
321+
rowClick,
322+
data,
323+
hasBulkActions,
324+
hover,
325+
ids,
326+
onToggleItem: handleToggleItem,
327+
resource,
328+
rowStyle,
329+
selectedIds,
330+
isRowSelectable,
331+
version,
332+
},
333+
children
334+
)}
335+
</Table>
336+
</DatagridContextProvider>
326337
);
327338
});
328339

@@ -353,6 +364,7 @@ Datagrid.propTypes = {
353364
total: PropTypes.number,
354365
version: PropTypes.number,
355366
isRowSelectable: PropTypes.func,
367+
isRowExpandable: PropTypes.func,
356368
};
357369

358370
type RowClickFunction = (
@@ -376,6 +388,7 @@ export interface DatagridProps extends Omit<TableProps, 'size' | 'classes'> {
376388
hasBulkActions?: boolean;
377389
hover?: boolean;
378390
isRowSelectable?: (record: Record) => boolean;
391+
isRowExpandable?: (record: Record) => boolean;
379392
optimized?: boolean;
380393
rowClick?: string | RowClickFunction;
381394
rowStyle?: (record: Record, index: number) => any;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
import { createContext } from 'react';
2+
import { Record as RaRecord } from 'ra-core';
3+
4+
const DatagridContext = createContext<DatagridContextValue>({});
5+
6+
DatagridContext.displayName = 'DatagridContext';
7+
8+
export type DatagridContextValue = {
9+
isRowExpandable?: (record: RaRecord) => boolean;
10+
};
11+
12+
export default DatagridContext;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
import React, { ReactElement, ReactNode } from 'react';
2+
import DatagridContext, { DatagridContextValue } from './DatagridContext';
3+
4+
const DatagridContextProvider = ({
5+
children,
6+
value,
7+
}: {
8+
children: ReactNode;
9+
value: DatagridContextValue;
10+
}): ReactElement => (
11+
<DatagridContext.Provider value={value}>
12+
{children}
13+
</DatagridContext.Provider>
14+
);
15+
16+
export default DatagridContextProvider;

packages/ra-ui-materialui/src/list/datagrid/DatagridRow.spec.tsx

+41
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import { createMemoryHistory } from 'history';
66
import { Router } from 'react-router-dom';
77

88
import DatagridRow from './DatagridRow';
9+
import DatagridContextProvider from './DatagridContextProvider';
910

1011
const TitleField = ({ record }: any): JSX.Element => (
1112
<span>{record.title}</span>
@@ -48,6 +49,46 @@ describe('<DatagridRow />', () => {
4849
};
4950
};
5051

52+
describe('isRowExpandable', () => {
53+
it('should show the expand button if it returns true', () => {
54+
const contextValue = { isRowExpandable: () => true };
55+
56+
const { queryAllByText, getByText } = renderWithRouter(
57+
<DatagridContextProvider value={contextValue}>
58+
<DatagridRow
59+
{...defaultProps}
60+
rowClick="expand"
61+
expand={<ExpandPanel />}
62+
>
63+
<TitleField />
64+
</DatagridRow>
65+
</DatagridContextProvider>
66+
);
67+
expect(queryAllByText('expanded')).toHaveLength(0);
68+
fireEvent.click(getByText('hello'));
69+
expect(queryAllByText('expanded')).toHaveLength(1);
70+
});
71+
72+
it('should not show the expand button if it returns false', () => {
73+
const contextValue = { isRowExpandable: () => false };
74+
75+
const { queryAllByText, getByText } = renderWithRouter(
76+
<DatagridContextProvider value={contextValue}>
77+
<DatagridRow
78+
{...defaultProps}
79+
rowClick="expand"
80+
expand={<ExpandPanel />}
81+
>
82+
<TitleField />
83+
</DatagridRow>
84+
</DatagridContextProvider>
85+
);
86+
expect(queryAllByText('expanded')).toHaveLength(0);
87+
fireEvent.click(getByText('hello'));
88+
expect(queryAllByText('expanded')).toHaveLength(0);
89+
});
90+
});
91+
5192
describe('rowClick', () => {
5293
it("should redirect to edit page if the 'edit' option is selected", () => {
5394
const { getByText, history } = renderWithRouter(

0 commit comments

Comments
 (0)