Skip to content

Commit

Permalink
[DataGrid] Add support for dialogs in menu actions (#11909)
Browse files Browse the repository at this point in the history
  • Loading branch information
cherniavskii authored Feb 5, 2024
1 parent 00b6b27 commit 3b2d066
Show file tree
Hide file tree
Showing 6 changed files with 293 additions and 66 deletions.
92 changes: 92 additions & 0 deletions docs/data/data-grid/column-definition/ActionsWithModalGrid.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
import * as React from 'react';
import { DataGrid, GridActionsCellItem } from '@mui/x-data-grid';
import DeleteIcon from '@mui/icons-material/Delete';
import { randomUserName } from '@mui/x-data-grid-generator';
import Dialog from '@mui/material/Dialog';
import DialogTitle from '@mui/material/DialogTitle';
import DialogContent from '@mui/material/DialogContent';
import DialogContentText from '@mui/material/DialogContentText';
import DialogActions from '@mui/material/DialogActions';
import Button from '@mui/material/Button';

const initialRows = [
{ id: 1, name: randomUserName() },
{ id: 2, name: randomUserName() },
{ id: 3, name: randomUserName() },
];

function DeleteUserActionItem({ deleteUser, ...props }) {
const [open, setOpen] = React.useState(false);

return (
<React.Fragment>
<GridActionsCellItem {...props} onClick={() => setOpen(true)} />
<Dialog
open={open}
onClose={() => setOpen(false)}
aria-labelledby="alert-dialog-title"
aria-describedby="alert-dialog-description"
>
<DialogTitle id="alert-dialog-title">Delete this user?</DialogTitle>
<DialogContent>
<DialogContentText id="alert-dialog-description">
This action cannot be undone.
</DialogContentText>
</DialogContent>
<DialogActions>
<Button onClick={() => setOpen(false)}>Cancel</Button>
<Button
onClick={() => {
setOpen(false);
deleteUser();
}}
color="warning"
autoFocus
>
Delete
</Button>
</DialogActions>
</Dialog>
</React.Fragment>
);
}

export default function ActionsWithModalGrid() {
const [rows, setRows] = React.useState(initialRows);

const deleteUser = React.useCallback(
(id) => () => {
setTimeout(() => {
setRows((prevRows) => prevRows.filter((row) => row.id !== id));
});
},
[],
);

const columns = React.useMemo(
() => [
{ field: 'name', type: 'string' },
{
field: 'actions',
type: 'actions',
width: 80,
getActions: (params) => [
<DeleteUserActionItem
label="Delete"
showInMenu
icon={<DeleteIcon />}
deleteUser={deleteUser(params.id)}
closeMenuOnClick={false}
/>,
],
},
],
[deleteUser],
);

return (
<div style={{ height: 300, width: '100%' }}>
<DataGrid columns={columns} rows={rows} />
</div>
);
}
103 changes: 103 additions & 0 deletions docs/data/data-grid/column-definition/ActionsWithModalGrid.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
import * as React from 'react';
import {
DataGrid,
GridActionsCellItem,
GridRowId,
GridColDef,
GridActionsCellItemProps,
} from '@mui/x-data-grid';
import DeleteIcon from '@mui/icons-material/Delete';
import { randomUserName } from '@mui/x-data-grid-generator';
import Dialog from '@mui/material/Dialog';
import DialogTitle from '@mui/material/DialogTitle';
import DialogContent from '@mui/material/DialogContent';
import DialogContentText from '@mui/material/DialogContentText';
import DialogActions from '@mui/material/DialogActions';
import Button from '@mui/material/Button';

const initialRows = [
{ id: 1, name: randomUserName() },
{ id: 2, name: randomUserName() },
{ id: 3, name: randomUserName() },
];

function DeleteUserActionItem({
deleteUser,
...props
}: GridActionsCellItemProps & { deleteUser: () => void }) {
const [open, setOpen] = React.useState(false);

return (
<React.Fragment>
<GridActionsCellItem {...props} onClick={() => setOpen(true)} />
<Dialog
open={open}
onClose={() => setOpen(false)}
aria-labelledby="alert-dialog-title"
aria-describedby="alert-dialog-description"
>
<DialogTitle id="alert-dialog-title">Delete this user?</DialogTitle>
<DialogContent>
<DialogContentText id="alert-dialog-description">
This action cannot be undone.
</DialogContentText>
</DialogContent>
<DialogActions>
<Button onClick={() => setOpen(false)}>Cancel</Button>
<Button
onClick={() => {
setOpen(false);
deleteUser();
}}
color="warning"
autoFocus
>
Delete
</Button>
</DialogActions>
</Dialog>
</React.Fragment>
);
}

type Row = (typeof initialRows)[number];

export default function ActionsWithModalGrid() {
const [rows, setRows] = React.useState<Row[]>(initialRows);

const deleteUser = React.useCallback(
(id: GridRowId) => () => {
setTimeout(() => {
setRows((prevRows) => prevRows.filter((row) => row.id !== id));
});
},
[],
);

const columns = React.useMemo<GridColDef<Row>[]>(
() => [
{ field: 'name', type: 'string' },
{
field: 'actions',
type: 'actions',
width: 80,
getActions: (params) => [
<DeleteUserActionItem
label="Delete"
showInMenu
icon={<DeleteIcon />}
deleteUser={deleteUser(params.id)}
closeMenuOnClick={false}
/>,
],
},
],
[deleteUser],
);

return (
<div style={{ height: 300, width: '100%' }}>
<DataGrid columns={columns} rows={rows} />
</div>
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
<DataGrid columns={columns} rows={rows} />
102 changes: 57 additions & 45 deletions docs/data/data-grid/column-definition/column-definition.md
Original file line number Diff line number Diff line change
Expand Up @@ -230,6 +230,8 @@ The following are the native column types with their required value types:
| `'singleSelect'` | A value in `.valueOptions` |
| `'actions'` | Not applicable |

{{"demo": "ColumnTypesGrid.js", "bg": "inline"}}

### Converting types

Default methods, such as filtering and sorting, assume that the type of the values will match the type of the column specified in `type`.
Expand All @@ -249,57 +251,67 @@ If for any reason, your data type is not the correct one, you can use `valueGett
To use most of the column types, you only need to define the `type` property in your column definition.
However, some types require additional properties to be set to make them work correctly:

- If the column type is `'singleSelect'`, you also need to set the `valueOptions` property in the respective column definition. These values are options used for filtering and editing.
#### Single select

```tsx
{
field: 'country',
type: 'singleSelect',
valueOptions: ['United Kingdom', 'Spain', 'Brazil']
}
```

:::warning
When using objects values for `valueOptions` you need to provide the `value` and `label` attributes for each option.
However, you can customize which attribute is used as value and label by using `getOptionValue` and `getOptionLabel`, respectively.

```tsx
// Without getOptionValue and getOptionLabel
{
valueOptions: [
{ value: 'BR', label: 'Brazil' },
{ value: 'FR', label: 'France' }
]
}
If the column type is `'singleSelect'`, you also need to set the `valueOptions` property in the respective column definition. These values are options used for filtering and editing.

// With getOptionValue and getOptionLabel
{
getOptionValue: (value: any) => value.code,
getOptionLabel: (value: any) => value.name,
valueOptions: [
{ code: 'BR', name: 'Brazil' },
{ code: 'FR', name: 'France' }
]
}
```
```tsx
{
field: 'country',
type: 'singleSelect',
valueOptions: ['United Kingdom', 'Spain', 'Brazil']
}
```

:::
:::warning
When using objects values for `valueOptions` you need to provide the `value` and `label` attributes for each option.
However, you can customize which attribute is used as value and label by using `getOptionValue` and `getOptionLabel`, respectively.

- If the column type is `'actions'`, you need to provide a `getActions` function that returns an array of actions available for each row (React elements).
You can add the `showInMenu` prop on the returned React elements to signal the data grid to group these actions inside a row menu.
```tsx
// Without getOptionValue and getOptionLabel
{
valueOptions: [
{ value: 'BR', label: 'Brazil' },
{ value: 'FR', label: 'France' }
]
}
```tsx
{
field: 'actions',
type: 'actions',
getActions: (params: GridRowParams) => [
<GridActionsCellItem icon={...} onClick={...} label="Delete" />,
<GridActionsCellItem icon={...} onClick={...} label="Print" showInMenu />,
]
}
```
// With getOptionValue and getOptionLabel
{
getOptionValue: (value: any) => value.code,
getOptionLabel: (value: any) => value.name,
valueOptions: [
{ code: 'BR', name: 'Brazil' },
{ code: 'FR', name: 'France' }
]
}
```

{{"demo": "ColumnTypesGrid.js", "bg": "inline"}}
:::

#### Actions

If the column type is `'actions'`, you need to provide a `getActions` function that returns an array of actions available for each row (React elements).
You can add the `showInMenu` prop on the returned React elements to signal the data grid to group these actions inside a row menu.

```tsx
{
field: 'actions',
type: 'actions',
getActions: (params: GridRowParams) => [
<GridActionsCellItem icon={...} onClick={...} label="Delete" />,
<GridActionsCellItem icon={...} onClick={...} label="Print" showInMenu />,
]
}
```

By default, actions shown in the menu will close the menu on click.
But in some cases, you might want to keep the menu open after clicking an action.
You can achieve this by setting the `closeMenuOnClick` prop to `false`.

In the following example, the "Delete" action opens a confirmation dialog and therefore needs to keep the menu mounted:

{{"demo": "ActionsWithModalGrid.js", "bg": "inline"}}

### Custom column types

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -180,7 +180,7 @@ function GridActionsCell(props: GridActionsCellProps) {
if (event.key === 'Tab') {
event.preventDefault();
}
if (['Tab', 'Enter', 'Escape'].includes(event.key)) {
if (['Tab', 'Escape'].includes(event.key)) {
hideMenu();
}
};
Expand Down Expand Up @@ -223,13 +223,7 @@ function GridActionsCell(props: GridActionsCellProps) {
)}

{menuButtons.length > 0 && (
<GridMenu
open={open}
target={buttonRef.current}
position={position}
onClose={hideMenu}
onClick={hideMenu}
>
<GridMenu open={open} target={buttonRef.current} position={position} onClose={hideMenu}>
<MenuList
id={menuId}
className={gridClasses.menuList}
Expand All @@ -238,7 +232,9 @@ function GridActionsCell(props: GridActionsCellProps) {
variant="menu"
autoFocusItem
>
{menuButtons.map((button, index) => React.cloneElement(button, { key: index }))}
{menuButtons.map((button, index) =>
React.cloneElement(button, { key: index, closeMenu: hideMenu }),
)}
</MenuList>
</GridMenu>
)}
Expand Down
Loading

0 comments on commit 3b2d066

Please sign in to comment.