Skip to content

Commit d30278a

Browse files
authored
Merge pull request marmelab#10654 from marmelab/confirm-record-representation
Improve `<DeleteButton>` and `<UpdateButton>` confirmation wording using record representation
2 parents b2135e5 + fba8da3 commit d30278a

16 files changed

+698
-71
lines changed

docs/Architecture.md

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -246,7 +246,11 @@ const DeleteButton = () => {
246246
loading={isPending}
247247
title="ra.message.delete_title"
248248
content="ra.message.delete_content"
249-
translateOptions={{
249+
titleTranslateOptions={{
250+
name: resource,
251+
id: record.id,
252+
}}
253+
contentTranslateOptions={{
250254
name: resource,
251255
id: record.id,
252256
}}

docs/Buttons.md

Lines changed: 24 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -898,18 +898,19 @@ If your `authProvider` implements [Access Control](./Permissions.md#access-contr
898898

899899
Delete the current record after a confirm dialog has been accepted. To be used inside a `<Toolbar/>` component.
900900

901-
| Prop | Required | Type | Default | Description |
902-
|------------------- |----------|--------------------------------------------------|-----------------------------|-------------------------------------------------------------------------|
903-
| `className` | Optional | `string` | - | Class name to customize the look and feel of the button element itself |
904-
| `label` | Optional | `string` | 'ra.action.delete' | label or translation message to use |
905-
| `icon` | Optional | `ReactElement` | `<DeleteIcon>` | iconElement, e.g. `<CommentIcon />` |
906-
| `confirmTitle` | Optional | `ReactNode` | 'ra.message.delete_title' | Title of the confirm dialog |
907-
| `confirmContent` | Optional | `ReactNode` | 'ra.message.delete_content' | Message or React component to be used as the body of the confirm dialog |
908-
| `confirmColor` | Optional | <code>'primary' &#124; 'warning'</code> | 'primary' | The color of the confirm dialog's "Confirm" button |
909-
| `redirect` | Optional | <code>string &#124; false &#124; Function</code> | 'list' | Custom redirection after success side effect |
910-
| `translateOptions` | Optional | `{ id?: string, name?: string }` | {} | Custom id and name to be used in the confirm dialog's title |
911-
| `mutationOptions` | Optional | | null | options for react-query `useMutation` hook |
912-
| `successMessage` | Optional | `string` | 'ra.notification.deleted' | Lets you customize the success notification message. |
901+
| Prop | Required | Type | Default | Description |
902+
|-------------------------- |----------|--------------------------------------------------|-----------------------------|-------------------------------------------------------------------------|
903+
| `className` | Optional | `string` | - | Class name to customize the look and feel of the button element itself |
904+
| `confirmTitle` | Optional | `ReactNode` | 'ra.message.delete_title' | Title of the confirm dialog |
905+
| `confirmContent` | Optional | `ReactNode` | 'ra.message.delete_content' | Message or React component to be used as the body of the confirm dialog |
906+
| `confirmColor` | Optional | <code>'primary' &#124; 'warning'</code> | 'primary' | The color of the confirm dialog's "Confirm" button |
907+
| `contentTranslateOptions` | Optional | `Object` | {} | Custom id, name and record representation to be used in the confirm dialog's content |
908+
| `icon` | Optional | `ReactElement` | `<DeleteIcon>` | iconElement, e.g. `<CommentIcon />` |
909+
| `label` | Optional | `string` | 'ra.action.delete' | label or translation message to use |
910+
| `mutationOptions` | Optional | | null | options for react-query `useMutation` hook |
911+
| `redirect` | Optional | <code>string &#124; false &#124; Function</code> | 'list' | Custom redirection after success side effect |
912+
| `titleTranslateOptions` | Optional | `Object` | {} | Custom id, name and record representation to be used in the confirm dialog's title |
913+
| `successMessage` | Optional | `string` | 'ra.notification.deleted' | Lets you customize the success notification message. |
913914

914915
{% raw %}
915916
```jsx
@@ -924,7 +925,8 @@ const EditToolbar = () => {
924925
<DeleteWithConfirmButton
925926
confirmContent="You will not be able to recover this record. Are you sure?"
926927
confirmColor="warning"
927-
translateOptions={{ name: record.name }}
928+
contentTranslateOptions={{ name: record.name }}
929+
titleTranslateOptions={{ name: record.name }}
928930
/>
929931
</Toolbar>
930932
};
@@ -1361,13 +1363,15 @@ export const PostEdit = () => (
13611363

13621364
`<UpdateButton>` accepts the following props:
13631365

1364-
| Prop | Required | Type | Default | Description |
1365-
| ---------------- | -------- | ----------- | ---------- | -------------------------------------------------------- |
1366-
| `data` | Required | `object` | | The data used to update the record |
1367-
| `mutationMode` | Optional | `string` | `undoable` | Mutation mode (`'undoable'`, `'pessimistic'` or `'optimistic'`) |
1368-
| `confirmTitle` | Optional | `ReactNode` | `ra.message.bulk_update_title` | The title of the confirmation dialog when `mutationMode` is not `undoable` |
1369-
| `confirmContent` | Optional | `ReactNode` | `ra.message.bulk_update_content` | The content of the confirmation dialog when `mutationMode` is not `undoable` |
1370-
| `mutationOptions` | Optional | `Object` | | The react-query mutation options |
1366+
| Prop | Required | Type | Default | Description |
1367+
| ------------------------- | -------- | ----------- | -------------------------------- | -------------------------------------------------------- |
1368+
| `data` | Required | `Object` | | The data used to update the record |
1369+
| `confirmTitle` | Optional | `ReactNode` | `ra.message.bulk_update_title` | The title of the confirmation dialog when `mutationMode` is not `undoable` |
1370+
| `confirmContent` | Optional | `ReactNode` | `ra.message.bulk_update_content` | The content of the confirmation dialog when `mutationMode` is not `undoable` |
1371+
| `contentTranslateOptions` | Optional | `Object` | {} | Custom id, name and record representation to be used in the confirm dialog's content |
1372+
| `mutationMode` | Optional | `string` | `undoable` | Mutation mode (`'undoable'`, `'pessimistic'` or `'optimistic'`) |
1373+
| `mutationOptions` | Optional | `Object` | | The react-query mutation options |
1374+
| `titleTranslateOptions` | Optional | `Object` | {} | Custom id, name and record representation to be used in the confirm dialog's title |
13711375

13721376
`<UpdateButton>` also accepts the [Button props](./Buttons.md#button).
13731377

docs/Confirm.md

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -71,10 +71,11 @@ const BulkResetViewsButton = () => {
7171
| `confirmColor` | Optional | `string` | 'primary' | Color of the confirm button |
7272
| `ConfirmIcon` | Optional | `Component` | `CheckCircleIcon` | Icon component of the confirm button |
7373
| `CancelIcon` | Optional | `Component` | `ErrorOutlineIcon` | Icon component of the cancel button |
74-
| `translate Options` | Optional | `{ id?: string, name?: string }` | {} | Custom id and name to be used in the dialog title |
74+
| `title Translate Options` | Optional | `{ id?: string, name?: string }` | {} | Custom id and name to be used in the dialog title |
75+
| `content Translate Options` | Optional | `{ id?: string, name?: string }` | {} | Custom id and name to be used in the dialog content |
7576
| `sx` | Optional | `SxProps` | '' | Material UI shortcut for defining custom styles with access to the theme |
7677

77-
Text props such as `title`, `content`, `cancel`, `confirm` and `translateOptions` are translatable. You can pass translation keys in these props. Note: `content` is only translatable when value is `string`, otherwise it renders the content as a `ReactNode`.
78+
Text props such as `title`, `content`, `cancel` and `confirm` are translatable. You can pass translation keys in these props. Note: `content` is only translatable when value is `string`, otherwise it renders the content as a `ReactNode`.
7879

7980
## `sx`: CSS API
8081

packages/ra-core/src/controller/button/useDeleteWithConfirmController.tsx

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,11 @@ import { useTranslate } from '../../i18n';
5353
* loading={isPending}
5454
* title="ra.message.delete_title"
5555
* content="ra.message.delete_content"
56-
* translateOptions={{
56+
* titleTranslateOptions={{
57+
* name: resource,
58+
* id: record.id,
59+
* }}
60+
* contentTranslateOptions={{
5761
* name: resource,
5862
* id: record.id,
5963
* }}

packages/ra-core/src/core/useGetRecordRepresentation.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import get from 'lodash/get';
55
import { useResourceDefinition } from './useResourceDefinition';
66

77
/**
8-
* Get default string representation of a record
8+
* Get the default representation of a record (either a string or a React node)
99
*
1010
* @example // No customization
1111
* const getRecordRepresentation = useGetRecordRepresentation('posts');

packages/ra-language-english/src/index.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -100,12 +100,12 @@ const englishMessages: TranslationMessages = {
100100
bulk_delete_title:
101101
'Delete %{name} |||| Delete %{smart_count} %{name}',
102102
bulk_update_content:
103-
'Are you sure you want to update this %{name}? |||| Are you sure you want to update these %{smart_count} items?',
103+
'Are you sure you want to update %{name} %{recordRepresentation}? |||| Are you sure you want to update these %{smart_count} items?',
104104
bulk_update_title:
105-
'Update %{name} |||| Update %{smart_count} %{name}',
105+
'Update %{name} %{recordRepresentation} |||| Update %{smart_count} %{name}',
106106
clear_array_input: 'Are you sure you want to clear the whole list?',
107-
delete_content: 'Are you sure you want to delete this item?',
108-
delete_title: 'Delete %{name} #%{id}',
107+
delete_content: 'Are you sure you want to delete this %{name}?',
108+
delete_title: 'Delete %{name} %{recordRepresentation}',
109109
details: 'Details',
110110
error: "A client error occurred and your request couldn't be completed.",
111111
invalid_form: 'The form is not valid. Please check for errors',

packages/ra-language-french/src/index.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -103,12 +103,12 @@ const frenchMessages: TranslationMessages = {
103103
bulk_update_content:
104104
'Êtes-vous sûr(e) de vouloir modifier cet élément ? |||| Êtes-vous sûr(e) de vouloir modifier ces %{smart_count} éléments ?',
105105
bulk_update_title:
106-
'Modifier %{name} |||| Modifier %{smart_count} %{name}',
106+
'Modifier %{name} %{recordRepresentation} |||| Modifier %{smart_count} %{name}',
107107
clear_array_input:
108108
'Êtes-vous sûr(e) de vouloir supprimer tous les éléments de la liste ?',
109109
delete_content:
110110
'Êtes-vous sûr(e) de vouloir supprimer cet élément ?',
111-
delete_title: 'Supprimer %{name} #%{id}',
111+
delete_title: 'Supprimer %{name} %{recordRepresentation}',
112112
details: 'Détails',
113113
error: "En raison d'une erreur côté navigateur, votre requête n'a pas pu aboutir.",
114114
invalid_form: "Le formulaire n'est pas valide.",

packages/ra-ui-materialui/src/button/BulkDeleteWithConfirmButton.tsx

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -129,7 +129,22 @@ export const BulkDeleteWithConfirmButton = (
129129
title={confirmTitle}
130130
content={confirmContent}
131131
confirmColor={confirmColor}
132-
translateOptions={{
132+
titleTranslateOptions={{
133+
smart_count: selectedIds.length,
134+
name: translate(`resources.${resource}.forcedCaseName`, {
135+
smart_count: selectedIds.length,
136+
_: humanize(
137+
translate(`resources.${resource}.name`, {
138+
smart_count: selectedIds.length,
139+
_: resource
140+
? inflect(resource, selectedIds.length)
141+
: undefined,
142+
}),
143+
true
144+
),
145+
}),
146+
}}
147+
contentTranslateOptions={{
133148
smart_count: selectedIds.length,
134149
name: translate(`resources.${resource}.forcedCaseName`, {
135150
smart_count: selectedIds.length,

packages/ra-ui-materialui/src/button/BulkUpdateWithConfirmButton.tsx

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -125,7 +125,22 @@ export const BulkUpdateWithConfirmButton = (
125125
loading={isPending}
126126
title={confirmTitle}
127127
content={confirmContent}
128-
translateOptions={{
128+
titleTranslateOptions={{
129+
smart_count: selectedIds.length,
130+
name: translate(`resources.${resource}.forcedCaseName`, {
131+
smart_count: selectedIds.length,
132+
_: humanize(
133+
translate(`resources.${resource}.name`, {
134+
smart_count: selectedIds.length,
135+
_: resource
136+
? inflect(resource, selectedIds.length)
137+
: undefined,
138+
}),
139+
true
140+
),
141+
}),
142+
}}
143+
contentTranslateOptions={{
129144
smart_count: selectedIds.length,
130145
name: translate(`resources.${resource}.forcedCaseName`, {
131146
smart_count: selectedIds.length,

packages/ra-ui-materialui/src/button/DeleteWithConfirmButton.spec.tsx

Lines changed: 53 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,11 @@
11
import * as React from 'react';
2-
import { screen, render, waitFor, fireEvent } from '@testing-library/react';
2+
import {
3+
screen,
4+
render,
5+
waitFor,
6+
fireEvent,
7+
within,
8+
} from '@testing-library/react';
39
import expect from 'expect';
410
import {
511
CoreAdminContext,
@@ -14,6 +20,11 @@ import { Toolbar, SimpleForm } from '../form';
1420
import { Edit } from '../detail';
1521
import { TextInput } from '../input';
1622
import { Notification } from '../layout';
23+
import {
24+
Basic,
25+
NoRecordRepresentation,
26+
WithDefaultTranslation,
27+
} from './DeleteWithConfirmButton.stories';
1728

1829
const theme = createTheme();
1930

@@ -329,4 +340,45 @@ describe('<DeleteWithConfirmButton />', () => {
329340
]);
330341
});
331342
});
343+
344+
it('should use the record representation in the confirmation title and content with a resource specific translation', async () => {
345+
render(<Basic />);
346+
fireEvent.click(
347+
within(
348+
(await screen.findByText('War and Peace')).closest(
349+
'tr'
350+
) as HTMLElement
351+
).getByText('Delete')
352+
);
353+
await screen.findByText('Delete the book "War and Peace"?');
354+
await screen.findByText(
355+
'Do you really want to delete the book "War and Peace"?'
356+
);
357+
});
358+
359+
it('should use the record representation in the confirmation title and content without a resource specific translation', async () => {
360+
render(<WithDefaultTranslation />);
361+
fireEvent.click(
362+
within(
363+
(await screen.findByText('War and Peace')).closest(
364+
'tr'
365+
) as HTMLElement
366+
).getByText('Delete')
367+
);
368+
await screen.findByText('Delete book War and Peace');
369+
await screen.findByText('Are you sure you want to delete this book?');
370+
});
371+
372+
it('should use the default record representation in the confirmation title and title when no record representation is available', async () => {
373+
render(<NoRecordRepresentation />);
374+
fireEvent.click(
375+
within(
376+
(await screen.findByText('Leo Tolstoy')).closest(
377+
'tr'
378+
) as HTMLElement
379+
).getByText('Delete')
380+
);
381+
await screen.findByText('Delete author #1');
382+
await screen.findByText('Are you sure you want to delete this author?');
383+
});
332384
});

0 commit comments

Comments
 (0)