Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[RFR] Added isRowExpandable prop to Datagrid component #5941

Merged
merged 16 commits into from
Feb 26, 2021
Merged
26 changes: 26 additions & 0 deletions docs/List.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)

Expand Down Expand Up @@ -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 `<DatagridRow>` 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 }) => (
<div dangerouslySetInnerHTML={{ __html: record.body }} />
);

const PostList = props => (
<List {...props}>
<Datagrid
expand={<PostPanel />}
isRowExpandable={row => row.has_detail}
>
<TextField source="id" />
<TextField source="title" />
<DateField source="published_at" />
<BooleanField source="commentable" />
<EditButton />
</Datagrid>
</List>
)
```

### `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 `<DatagridRow>` and returns a boolean expression. For instance, this code shows a checkbox only for rows with an id greater than 300:
Expand Down
163 changes: 88 additions & 75 deletions packages/ra-ui-materialui/src/list/datagrid/Datagrid.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import {
useEffect,
FC,
ReactElement,
useMemo,
} from 'react';
import PropTypes from 'prop-types';
import {
Expand All @@ -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.
Expand Down Expand Up @@ -116,6 +118,7 @@ const Datagrid: FC<DatagridProps> = React.forwardRef((props, ref) => {
hasBulkActions = false,
hover,
isRowSelectable,
isRowExpandable,
resource,
rowClick,
rowStyle,
Expand All @@ -137,6 +140,10 @@ const Datagrid: FC<DatagridProps> = React.forwardRef((props, ref) => {
} = useListContext(props);
const version = useVersion();

const contextValue = useMemo(() => ({ isRowExpandable }), [
isRowExpandable,
]);

const updateSort = useCallback(
event => {
event.stopPropagation();
Expand Down Expand Up @@ -246,83 +253,87 @@ const Datagrid: FC<DatagridProps> = React.forwardRef((props, ref) => {
* the datagrid displays the current data.
*/
return (
<Table
ref={ref}
className={classnames(classes.table, className)}
size={size}
{...sanitizeListRestProps(rest)}
>
<TableHead className={classes.thead}>
<TableRow
className={classnames(classes.row, classes.headerRow)}
>
{expand && (
<TableCell
padding="none"
className={classnames(
classes.headerCell,
classes.expandHeader
)}
/>
)}
{hasBulkActions && (
<TableCell
padding="checkbox"
className={classes.headerCell}
>
<Checkbox
className="select-all"
color="primary"
checked={
selectedIds.length > 0 &&
all.length > 0 &&
all.every(id => selectedIds.includes(id))
}
onChange={handleSelectAll}
<DatagridContextProvider value={contextValue}>
<Table
ref={ref}
className={classnames(classes.table, className)}
size={size}
{...sanitizeListRestProps(rest)}
>
<TableHead className={classes.thead}>
<TableRow
className={classnames(classes.row, classes.headerRow)}
>
{expand && (
<TableCell
padding="none"
className={classnames(
classes.headerCell,
classes.expandHeader
)}
/>
</TableCell>
)}
{Children.map(children, (field, index) =>
isValidElement(field) ? (
<DatagridHeaderCell
)}
{hasBulkActions && (
<TableCell
padding="checkbox"
className={classes.headerCell}
currentSort={currentSort}
field={field}
isSorting={
currentSort.field ===
((field.props as any).sortBy ||
(field.props as any).source)
}
key={(field.props as any).source || index}
resource={resource}
updateSort={updateSort}
/>
) : null
)}
</TableRow>
</TableHead>
{cloneElement(
body,
{
basePath,
className: classes.tbody,
classes,
expand,
rowClick,
data,
hasBulkActions,
hover,
ids,
onToggleItem: handleToggleItem,
resource,
rowStyle,
selectedIds,
isRowSelectable,
version,
},
children
)}
</Table>
>
<Checkbox
className="select-all"
color="primary"
checked={
selectedIds.length > 0 &&
all.length > 0 &&
all.every(id =>
selectedIds.includes(id)
)
}
onChange={handleSelectAll}
/>
</TableCell>
)}
{Children.map(children, (field, index) =>
isValidElement(field) ? (
<DatagridHeaderCell
className={classes.headerCell}
currentSort={currentSort}
field={field}
isSorting={
currentSort.field ===
((field.props as any).sortBy ||
(field.props as any).source)
}
key={(field.props as any).source || index}
resource={resource}
updateSort={updateSort}
/>
) : null
)}
</TableRow>
</TableHead>
{cloneElement(
body,
{
basePath,
className: classes.tbody,
classes,
expand,
rowClick,
data,
hasBulkActions,
hover,
ids,
onToggleItem: handleToggleItem,
resource,
rowStyle,
selectedIds,
isRowSelectable,
version,
},
children
)}
</Table>
</DatagridContextProvider>
);
});

Expand Down Expand Up @@ -353,6 +364,7 @@ Datagrid.propTypes = {
total: PropTypes.number,
version: PropTypes.number,
isRowSelectable: PropTypes.func,
isRowExpandable: PropTypes.func,
};

type RowClickFunction = (
Expand All @@ -376,6 +388,7 @@ export interface DatagridProps extends Omit<TableProps, 'size' | 'classes'> {
hasBulkActions?: boolean;
hover?: boolean;
isRowSelectable?: (record: Record) => boolean;
isRowExpandable?: (record: Record) => boolean;
optimized?: boolean;
rowClick?: string | RowClickFunction;
rowStyle?: (record: Record, index: number) => any;
Expand Down
12 changes: 12 additions & 0 deletions packages/ra-ui-materialui/src/list/datagrid/DatagridContext.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { createContext } from 'react';
import { Record as RaRecord } from 'ra-core';

const DatagridContext = createContext<DatagridContextValue>({});

DatagridContext.displayName = 'DatagridContext';

export type DatagridContextValue = {
isRowExpandable?: (record: RaRecord) => boolean;
};

export default DatagridContext;
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import React, { ReactElement, ReactNode } from 'react';
import DatagridContext, { DatagridContextValue } from './DatagridContext';

const DatagridContextProvider = ({
children,
value,
}: {
children: ReactNode;
value: DatagridContextValue;
}): ReactElement => (
<DatagridContext.Provider value={value}>
{children}
</DatagridContext.Provider>
);

export default DatagridContextProvider;
41 changes: 41 additions & 0 deletions packages/ra-ui-materialui/src/list/datagrid/DatagridRow.spec.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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 => (
<span>{record.title}</span>
Expand Down Expand Up @@ -48,6 +49,46 @@ describe('<DatagridRow />', () => {
};
};

describe('isRowExpandable', () => {
it('should show the expand button if it returns true', () => {
const contextValue = { isRowExpandable: () => true };

const { queryAllByText, getByText } = renderWithRouter(
<DatagridContextProvider value={contextValue}>
<DatagridRow
{...defaultProps}
rowClick="expand"
expand={<ExpandPanel />}
>
<TitleField />
</DatagridRow>
</DatagridContextProvider>
);
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(
<DatagridContextProvider value={contextValue}>
<DatagridRow
{...defaultProps}
rowClick="expand"
expand={<ExpandPanel />}
>
<TitleField />
</DatagridRow>
</DatagridContextProvider>
);
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(
Expand Down
Loading