diff --git a/docs/Buttons.md b/docs/Buttons.md index 1c7ea83bf69..2eaa00af61f 100644 --- a/docs/Buttons.md +++ b/docs/Buttons.md @@ -261,6 +261,10 @@ export const PostList = () => ( Partially updates the selected rows. To be used inside [the `<Datagrid bulkActionButtons>` prop](./Datagrid.md#bulkactionbuttons). + + +#### Usage + {% raw %} ```jsx import * as React from 'react'; @@ -285,7 +289,7 @@ export const PostList = () => ( ``` {% endraw %} - +#### Props | Prop | Required | Type | Default | Description | |-------------------|----------|----------------|--------------------|---------------------------------------------------------------------------------------------------------------------| @@ -299,6 +303,311 @@ export const PostList = () => ( **Tip:** If you choose the `'pessimistic'` or `'optimistic'` mutation mode, a confirm dialog will be displayed to the user before the mutation is executed. +### `<BulkUpdateFormButton>` + +This component, part of the [enterprise edition](https://marmelab.com/ra-enterprise/modules/ra-form-layout)<img class="icon" src="./img/premium.svg" />, lets users edit multiple records at once. To be used inside [the `<Datagrid bulkActionButtons>` prop](./Datagrid.md#bulkactionbuttons). + +The button opens a dialog containing the form passed as children. When the form is submitted, it will call the dataProvider's `updateMany` method with the ids of the selected records. + +<video controls autoplay playsinline muted loop> + <source src="./img/BulkUpdateButton-SimpleForm.webm" type="video/webm"/> + <source src="./img/BulkUpdateButton-SimpleForm.mp4" type="video/mp4"/> + Your browser does not support the video tag. +</video> + +#### Usage + +`<BulkUpdateFormButton>` can be used inside `<Datagrid>`'s `bulkActionButtons`. + +```tsx +import * as React from 'react'; +import { + Admin, + BooleanField, + BooleanInput, + Datagrid, + DateField, + DateInput, + List, + Resource, + SimpleForm, + TextField, +} from 'react-admin'; +import { BulkUpdateFormButton } from '@react-admin/ra-form-layout'; + +import { dataProvider } from './dataProvider'; +import { i18nProvider } from './i18nProvider'; + +export const App = () => ( + <Admin dataProvider={dataProvider} i18nProvider={i18nProvider}> + <Resource name="posts" list={PostList} /> + </Admin> +); + +const PostBulkUpdateButton = () => ( + <BulkUpdateFormButton> + <SimpleForm> + <DateInput source="published_at" /> + <BooleanInput source="is_public" /> + </SimpleForm> + </BulkUpdateFormButton> +); + +const PostList = () => ( + <List> + <Datagrid bulkActionButtons={<PostBulkUpdateButton />}> + <TextField source="id" /> + <TextField source="title" /> + <DateField source="published_at" /> + <BooleanField source="is_public" /> + </Datagrid> + </List> +); +``` + +**Tip:** You are not limited to using a `<SimpleForm>` as children. You can for instance use an `<InputSelectorForm>`, which allows to select the fields to update. Check out the [`<InputSelectorForm>`](#usage-with-inputselectorform) below for more information. + +#### Props + +| Prop | Required | Type | Default | Description | +|-------------------|--------------|----------|-----------------|------------------------------------------------------------------------------------------------------------------------------------| +| `children` | Required (*) | Element | - | A form component to render inside the Dialog | +| `DialogProps` | - | Object | - | Additional props to pass to the [MUI Dialog](https://mui.com/material-ui/react-dialog/) | +| `mutationMode` | - | `string` | `'pessimistic'` | The mutation mode (`'undoable'`, `'pessimistic'` or `'optimistic'`) | +| `mutationOptions` | - | Object | - | Mutation options passed to [react-query](https://tanstack.com/query/v3/docs/react/reference/useMutation) when calling `updateMany` | + + +#### `children` + +`<BulkUpdateFormButton>` expects a form component as children, such as `<SimpleForm>` or `<InputSelectorForm>`. + +```tsx +import { BulkUpdateFormButton } from '@react-admin/ra-form-layout'; +import * as React from 'react'; +import { BooleanInput, DateInput, SimpleForm } from 'react-admin'; + +const PostBulkUpdateButton = () => ( + <BulkUpdateFormButton> + <SimpleForm> + <DateInput source="published_at" /> + <BooleanInput source="is_public" /> + </SimpleForm> + </BulkUpdateFormButton> +); +``` + +#### `DialogProps` + +The `DialogProps` prop can be used to pass additional props to the [MUI Dialog](https://mui.com/material-ui/react-dialog/). +{% raw %} +```tsx +import { Slide } from '@mui/material'; +import { TransitionProps } from '@mui/material/transitions'; +import { BulkUpdateFormButton } from '@react-admin/ra-form-layout'; +import * as React from 'react'; +import { BooleanInput, DateInput, SimpleForm } from 'react-admin'; + +const Transition = React.forwardRef(function Transition( + props: TransitionProps & { + children: React.ReactElement<any, any>; + }, + ref: React.Ref<unknown> +) { + return <Slide direction="left" ref={ref} {...props} />; +}); + +const PostBulkUpdateButtonWithTransition = () => ( + <BulkUpdateFormButton DialogProps={{ TransitionComponent: Transition }}> + <SimpleForm> + <DateInput source="published_at" /> + <BooleanInput source="is_public" /> + </SimpleForm> + </BulkUpdateFormButton> +); +``` +{% endraw %} + +#### `mutationMode` + +Use the `mutationMode` prop to specify the [mutation mode](https://marmelab.com/react-admin/Edit.html#mutationmode). + +```tsx +import { BulkUpdateFormButton } from '@react-admin/ra-form-layout'; +import * as React from 'react'; +import { BooleanInput, DateInput, SimpleForm } from 'react-admin'; + +const PostBulkUpdateButton = () => ( + <BulkUpdateFormButton mutationMode="undoable"> + <SimpleForm> + <DateInput source="published_at" /> + <BooleanInput source="is_public" /> + </SimpleForm> + </BulkUpdateFormButton> +); +``` + +#### `mutationOptions` and `meta` + +The `mutationOptions` prop can be used to pass options to the [react-query mutation](https://react-query.tanstack.com/reference/useMutation#options) used to call the dataProvider's `updateMany` method. + +{% raw %} +```tsx +import { BulkUpdateFormButton } from '@react-admin/ra-form-layout'; +import * as React from 'react'; +import { BooleanInput, DateInput, SimpleForm } from 'react-admin'; + +const PostBulkUpdateButton = () => ( + <BulkUpdateFormButton mutationOptions={{ retry: false }}> + <SimpleForm> + <DateInput source="published_at" /> + <BooleanInput source="is_public" /> + </SimpleForm> + </BulkUpdateFormButton> +); +``` +{% endraw %} + +You can also use this prop to pass a `meta` object, that will be passed to the dataProvider when calling `updateMany`. +{% raw %} +```tsx +import { BulkUpdateFormButton } from '@react-admin/ra-form-layout'; +import * as React from 'react'; +import { BooleanInput, DateInput, SimpleForm } from 'react-admin'; + +const PostBulkUpdateButton = () => ( + <BulkUpdateFormButton mutationOptions={{ meta: { foo: 'bar' } }}> + <SimpleForm> + <DateInput source="published_at" /> + <BooleanInput source="is_public" /> + </SimpleForm> + </BulkUpdateFormButton> +); +``` +{% endraw %} + +#### Usage with `<TabbedForm>` or other location based form layouts + +`<BulkUpdateFormButton>` can be used with any form layout. However, for form layouts that are based on location by default, such as [`<TabbedForm>`](https://marmelab.com/react-admin/TabbedForm.html), you will need to disable the location syncing feature, as it may conflict with the Edit route declared by React Admin (`/<resource>/<id>`). + +For instance, with `<TabbedForm>`, you can use the `syncWithLocation` prop to disable it: + +```tsx +import { BulkUpdateFormButton } from '@react-admin/ra-form-layout'; +import * as React from 'react'; +import { BooleanInput, DateInput, TabbedForm } from 'react-admin'; + +const PostBulkUpdateButton = () => ( + <BulkUpdateFormButton> + <TabbedForm syncWithLocation={false}> + <TabbedForm.Tab label="Publication"> + <DateInput source="published_at" /> + </TabbedForm.Tab> + <TabbedForm.Tab label="Visibility"> + <BooleanInput source="is_public" /> + </TabbedForm.Tab> + </TabbedForm> + </BulkUpdateFormButton> +); +``` + +#### Usage With `<InputSelectorForm>` + +`<BulkUpdateFormButton>` works best with `<InputSelectorForm>`, which component renders a form allowing to select the fields to update in a record. + +<video controls autoplay playsinline muted loop> + <source src="./img/BulkUpdateButton-InputSelectorForm.webm" type="video/webm"/> + <source src="./img/BulkUpdateButton-InputSelectorForm.mp4" type="video/mp4"/> + Your browser does not support the video tag. +</video> + +`<InputSelectorForm>` expects a list of inputs passed in the `inputs` prop. Each input must have a `label` and an `element`. + +```tsx +import { + BulkUpdateFormButton, + InputSelectorForm, +} from '@react-admin/ra-form-layout'; +import * as React from 'react'; +import { BooleanInput, DateInput } from 'react-admin'; + +const PostBulkUpdateButton = () => ( + <BulkUpdateFormButton> + <InputSelectorForm + inputs={[ + { + label: 'Published at', + element: <DateInput source="published_at" />, + }, + { + label: 'Is public', + element: <BooleanInput source="is_public" />, + }, + ]} + /> + </BulkUpdateFormButton> +); +``` + +Use the `inputs` prop to specify the list of inputs from which the user can pick. Each input must have a `label` and an `element`. + +```tsx +import { InputSelectorForm } from '@react-admin/ra-form-layout'; +import * as React from 'react'; +import { + BooleanInput, + DateInput, + SelectArrayInput, + TextInput, +} from 'react-admin'; + +const PostEdit = () => ( + <InputSelectorForm + inputs={[ + { + label: 'Title', + element: <TextInput source="title" />, + }, + { + label: 'Body', + element: <TextInput source="body" multiline />, + }, + { + label: 'Published at', + element: <DateInput source="published_at" />, + }, + { + label: 'Is public', + element: <BooleanInput source="is_public" />, + }, + { + label: 'Tags', + element: ( + <SelectArrayInput + source="tags" + choices={[ + { id: 'react', name: 'React' }, + { id: 'vue', name: 'Vue' }, + { id: 'solid', name: 'Solid' }, + { id: 'programming', name: 'Programming' }, + ]} + /> + ), + }, + ]} + /> +); +``` + +#### Limitations + +If you look under the hood, you will see that `<BulkUpdateFormButton>` provides a `<SaveContext>` to its children, which allows them to call `updateMany` with the ids of the selected records. + +However since we are in the context of a list, there is no `<RecordContext>` available. Hence, the following inputs cannot work inside a `<BulkUpdateFormButton>`: + +- `<ReferenceOneInput>` +- `<ReferenceManyInput>` +- `<ReferenceManyToManyInput>` + ### `<FilterButton>` This button is an internal component used by react-admin in [the Filter button/form combo](./FilteringTutorial.md#the-filter-buttonform-combo). diff --git a/docs/Datagrid.md b/docs/Datagrid.md index cb4c3472232..4cf1d2be4fd 100644 --- a/docs/Datagrid.md +++ b/docs/Datagrid.md @@ -147,7 +147,6 @@ Finally, `<Datagrid>` inspects children for props that indicate how it should be Your browser does not support the video tag. </video> - Bulk action buttons are buttons that affect several records at once, like mass deletion for instance. In the `<Datagrid>` component, the bulk actions toolbar appears when a user ticks the checkboxes in the first column of the table. The user can then choose a button from the bulk actions toolbar. By default, all Datagrids have a single bulk action button, the bulk delete button. You can add other bulk action buttons by passing a custom element as the `bulkActionButtons` prop of the `<Datagrid>` component: ```jsx @@ -173,7 +172,12 @@ export const PostList = () => ( ); ``` -**Tip**: React-admin provides three components that you can use in `bulkActionButtons`: [`<BulkDeleteButton>`](./Buttons.md#bulkdeletebutton), [`<BulkUpdateButton>`](./Buttons.md#bulkupdatebutton), and [`<BulkExportButton>`](./Buttons.md#bulkexportbutton). +**Tip**: React-admin provides four components that you can use in `bulkActionButtons`: + +- [`<BulkDeleteButton>`](./Buttons.md#bulkdeletebutton) (enabled by default) +- [`<BulkExportButton>`](./Buttons.md#bulkexportbutton) to export only the selection +- [`<BulkUpdateButton>`](./Buttons.md#bulkupdatebutton) to immediately update the selection +- [`<BulkUpdateFormButton>`](./Buttons.md#bulkupdateformbutton) to display a form allowing to update the selection **Tip**: You can also disable bulk actions altogether by passing `false` to the `bulkActionButtons` prop. In this case, the checkboxes column doesn't show up. diff --git a/docs/img/BulkUpdateButton-InputSelectorForm.mp4 b/docs/img/BulkUpdateButton-InputSelectorForm.mp4 new file mode 100644 index 00000000000..2063f7436a1 Binary files /dev/null and b/docs/img/BulkUpdateButton-InputSelectorForm.mp4 differ diff --git a/docs/img/BulkUpdateButton-InputSelectorForm.webm b/docs/img/BulkUpdateButton-InputSelectorForm.webm new file mode 100644 index 00000000000..969e59c649a Binary files /dev/null and b/docs/img/BulkUpdateButton-InputSelectorForm.webm differ diff --git a/docs/img/BulkUpdateButton-SimpleForm.mp4 b/docs/img/BulkUpdateButton-SimpleForm.mp4 new file mode 100644 index 00000000000..5d1fa5b4659 Binary files /dev/null and b/docs/img/BulkUpdateButton-SimpleForm.mp4 differ diff --git a/docs/img/BulkUpdateButton-SimpleForm.webm b/docs/img/BulkUpdateButton-SimpleForm.webm new file mode 100644 index 00000000000..5eb74bb5684 Binary files /dev/null and b/docs/img/BulkUpdateButton-SimpleForm.webm differ