Skip to content

Commit 5338363

Browse files
authored
Merge pull request #6496 from marmelab/datagrid-header-custom
Add ability to override the Datagrid header row
2 parents 98babf8 + 5468eae commit 5338363

File tree

4 files changed

+251
-106
lines changed

4 files changed

+251
-106
lines changed

docs/List.md

+34
Original file line numberDiff line numberDiff line change
@@ -2016,6 +2016,7 @@ The `Datagrid` component renders a list of records as a table. It is usually use
20162016
Here are all the props accepted by the component:
20172017

20182018
* [`body`](#body-element)
2019+
* [`header`](#header-element)
20192020
* [`rowStyle`](#row-style-function)
20202021
* [`rowClick`](#rowclick)
20212022
* [`expand`](#expand)
@@ -2101,6 +2102,39 @@ const PostList = props => (
21012102
export default PostList;
21022103
```
21032104

2105+
### Header Element
2106+
2107+
By default, `<Datagrid>` renders its header using `<DatagridHeader>`, an internal react-admin component. You can pass a custom component as the `header` prop to override that default. This can be useful e.g. to add a second header row, or to create headers spanning multiple columns.
2108+
2109+
For instance, here is a simple datagrid header that displays column names with no sort and no "select all" button:
2110+
2111+
```jsx
2112+
import { TableHead, TableRow, TableCell } from '@material-ui/core';
2113+
2114+
const DatagridHeader = ({ children }) => (
2115+
<TableHead>
2116+
<TableRow>
2117+
<TableCell></TableCell> {/* empty cell to account for the select row checkbox in the body */}
2118+
{Children.map(children, child => (
2119+
<TableCell key={child.props.source}>
2120+
{child.props.source}
2121+
</TableCell>
2122+
))}
2123+
</TableRow>
2124+
</TableHead>
2125+
);
2126+
2127+
const PostList = props => (
2128+
<List {...props}>
2129+
<Datagrid header={<DatagridHeader />}>
2130+
{/* ... */}
2131+
</Datagrid>
2132+
</List>
2133+
);
2134+
```
2135+
2136+
**Tip**: To handle sorting in your custom Datagrid header component, check out the [Building a custom sort control](#building-a-custom-sort-control) section.
2137+
21042138
### Row Style Function
21052139

21062140
You can customize the `<Datagrid>` row style (applied to the `<tr>` element) based on the record, thanks to the `rowStyle` prop, which expects a function. React-admin calls this function for each row, passing the current record and index as arguments. The function should return a style object, which react-admin uses as a `<tr style>` prop.

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

+38-106
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,13 @@
11
import * as React from 'react';
22
import {
3-
isValidElement,
4-
Children,
53
cloneElement,
4+
createElement,
5+
isValidElement,
66
useCallback,
77
useRef,
88
useEffect,
99
FC,
10+
ComponentType,
1011
ReactElement,
1112
useMemo,
1213
} from 'react';
@@ -20,19 +21,12 @@ import {
2021
RecordMap,
2122
SortPayload,
2223
} from 'ra-core';
23-
import {
24-
Checkbox,
25-
Table,
26-
TableProps,
27-
TableCell,
28-
TableHead,
29-
TableRow,
30-
} from '@material-ui/core';
24+
import { Table, TableProps } from '@material-ui/core';
3125
import classnames from 'classnames';
3226
import union from 'lodash/union';
3327
import difference from 'lodash/difference';
3428

35-
import DatagridHeaderCell from './DatagridHeaderCell';
29+
import { DatagridHeader } from './DatagridHeader';
3630
import DatagridLoading from './DatagridLoading';
3731
import DatagridBody, { PureDatagridBody } from './DatagridBody';
3832
import useDatagridStyles from './useDatagridStyles';
@@ -113,7 +107,8 @@ const Datagrid: FC<DatagridProps> = React.forwardRef((props, ref) => {
113107
const classes = useDatagridStyles(props);
114108
const {
115109
optimized = false,
116-
body = optimized ? <PureDatagridBody /> : <DatagridBody />,
110+
body = optimized ? PureDatagridBody : DatagridBody,
111+
header = DatagridHeader,
117112
children,
118113
classes: classesOverride,
119114
className,
@@ -148,42 +143,6 @@ const Datagrid: FC<DatagridProps> = React.forwardRef((props, ref) => {
148143
isRowExpandable,
149144
]);
150145

151-
const updateSortCallback = useCallback(
152-
event => {
153-
event.stopPropagation();
154-
const newField = event.currentTarget.dataset.field;
155-
const newOrder =
156-
currentSort.field === newField
157-
? currentSort.order === 'ASC'
158-
? 'DESC'
159-
: 'ASC'
160-
: event.currentTarget.dataset.order;
161-
162-
setSort(newField, newOrder);
163-
},
164-
[currentSort.field, currentSort.order, setSort]
165-
);
166-
167-
const updateSort = setSort ? updateSortCallback : null;
168-
169-
const handleSelectAll = useCallback(
170-
event => {
171-
if (event.target.checked) {
172-
const all = ids.concat(
173-
selectedIds.filter(id => !ids.includes(id))
174-
);
175-
onSelect(
176-
isRowSelectable
177-
? all.filter(id => isRowSelectable(data[id]))
178-
: all
179-
);
180-
} else {
181-
onSelect([]);
182-
}
183-
},
184-
[data, ids, onSelect, isRowSelectable, selectedIds]
185-
);
186-
187146
const lastSelected = useRef(null);
188147

189148
useEffect(() => {
@@ -253,10 +212,6 @@ const Datagrid: FC<DatagridProps> = React.forwardRef((props, ref) => {
253212
return null;
254213
}
255214

256-
const all = isRowSelectable
257-
? ids.filter(id => isRowSelectable(data[id]))
258-
: ids;
259-
260215
/**
261216
* After the initial load, if the data for the list isn't empty,
262217
* and even if the data is refreshing (e.g. after a filter change),
@@ -270,58 +225,26 @@ const Datagrid: FC<DatagridProps> = React.forwardRef((props, ref) => {
270225
size={size}
271226
{...sanitizeListRestProps(rest)}
272227
>
273-
<TableHead className={classes.thead}>
274-
<TableRow
275-
className={classnames(classes.row, classes.headerRow)}
276-
>
277-
{expand && (
278-
<TableCell
279-
padding="none"
280-
className={classnames(
281-
classes.headerCell,
282-
classes.expandHeader
283-
)}
284-
/>
285-
)}
286-
{hasBulkActions && selectedIds && (
287-
<TableCell
288-
padding="checkbox"
289-
className={classes.headerCell}
290-
>
291-
<Checkbox
292-
className="select-all"
293-
color="primary"
294-
checked={
295-
selectedIds.length > 0 &&
296-
all.length > 0 &&
297-
all.every(id =>
298-
selectedIds.includes(id)
299-
)
300-
}
301-
onChange={handleSelectAll}
302-
/>
303-
</TableCell>
304-
)}
305-
{Children.map(children, (field, index) =>
306-
isValidElement(field) ? (
307-
<DatagridHeaderCell
308-
className={classes.headerCell}
309-
currentSort={currentSort}
310-
field={field}
311-
isSorting={
312-
currentSort.field ===
313-
((field.props as any).sortBy ||
314-
(field.props as any).source)
315-
}
316-
key={(field.props as any).source || index}
317-
resource={resource}
318-
updateSort={updateSort}
319-
/>
320-
) : null
321-
)}
322-
</TableRow>
323-
</TableHead>
324-
{cloneElement(
228+
{createOrCloneElement(
229+
header,
230+
{
231+
children,
232+
classes,
233+
className,
234+
currentSort,
235+
data,
236+
hasExpand: !!expand,
237+
hasBulkActions,
238+
ids,
239+
isRowSelectable,
240+
onSelect,
241+
resource,
242+
selectedIds,
243+
setSort,
244+
},
245+
children
246+
)}
247+
{createOrCloneElement(
325248
body,
326249
{
327250
basePath,
@@ -347,9 +270,15 @@ const Datagrid: FC<DatagridProps> = React.forwardRef((props, ref) => {
347270
);
348271
});
349272

273+
const createOrCloneElement = (element, props, children) =>
274+
isValidElement(element)
275+
? cloneElement(element, props, children)
276+
: createElement(element, props, children);
277+
350278
Datagrid.propTypes = {
351279
basePath: PropTypes.string,
352-
body: PropTypes.element,
280+
// @ts-ignore
281+
body: PropTypes.oneOfType([PropTypes.element, PropTypes.elementType]),
353282
children: PropTypes.node.isRequired,
354283
classes: PropTypes.object,
355284
className: PropTypes.string,
@@ -362,6 +291,8 @@ Datagrid.propTypes = {
362291
// @ts-ignore
363292
expand: PropTypes.oneOfType([PropTypes.element, PropTypes.elementType]),
364293
hasBulkActions: PropTypes.bool,
294+
// @ts-ignore
295+
header: PropTypes.oneOfType([PropTypes.element, PropTypes.elementType]),
365296
hover: PropTypes.bool,
366297
ids: PropTypes.arrayOf(PropTypes.any),
367298
loading: PropTypes.bool,
@@ -380,7 +311,7 @@ Datagrid.propTypes = {
380311

381312
export interface DatagridProps<RecordType extends Record = Record>
382313
extends Omit<TableProps, 'size' | 'classes' | 'onSelect'> {
383-
body?: ReactElement;
314+
body?: ReactElement | ComponentType;
384315
classes?: ClassesOverride<typeof useDatagridStyles>;
385316
className?: string;
386317
expand?:
@@ -392,6 +323,7 @@ export interface DatagridProps<RecordType extends Record = Record>
392323
resource: string;
393324
}>;
394325
hasBulkActions?: boolean;
326+
header?: ReactElement | ComponentType;
395327
hover?: boolean;
396328
empty?: ReactElement;
397329
isRowSelectable?: (record: Record) => boolean;

0 commit comments

Comments
 (0)