Skip to content
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion packages/ra-core/src/core/useGetRecordRepresentation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import get from 'lodash/get';
import { useResourceDefinition } from './useResourceDefinition';

/**
* Get default string representation of a record
* Get the default representation of a record (either a string or a React node)
*
* @example // No customization
* const getRecordRepresentation = useGetRecordRepresentation('posts');
Expand Down
8 changes: 4 additions & 4 deletions packages/ra-language-english/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -100,12 +100,12 @@ const englishMessages: TranslationMessages = {
bulk_delete_title:
'Delete %{name} |||| Delete %{smart_count} %{name}',
bulk_update_content:
'Are you sure you want to update this %{name}? |||| Are you sure you want to update these %{smart_count} items?',
'Are you sure you want to update %{name} %{recordRepresentation}? |||| Are you sure you want to update these %{smart_count} items?',
bulk_update_title:
'Update %{name} |||| Update %{smart_count} %{name}',
'Update %{name} %{recordRepresentation} |||| Update %{smart_count} %{name}',
clear_array_input: 'Are you sure you want to clear the whole list?',
delete_content: 'Are you sure you want to delete this item?',
delete_title: 'Delete %{name} #%{id}',
delete_content: 'Are you sure you want to delete this %{name}?',
delete_title: 'Delete %{name} %{recordRepresentation}',
details: 'Details',
error: "A client error occurred and your request couldn't be completed.",
invalid_form: 'The form is not valid. Please check for errors',
Expand Down
3 changes: 2 additions & 1 deletion packages/ra-language-french/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,8 @@ const frenchMessages: TranslationMessages = {
'Êtes-vous sûr(e) de vouloir supprimer tous les éléments de la liste ?',
delete_content:
'Êtes-vous sûr(e) de vouloir supprimer cet élément ?',
delete_title: 'Supprimer %{name} #%{id}',
delete_title: 'Supprimer %{name} %{recordRepresentation}',
delete_title_record_representation: 'Supprimer %{name}',
details: 'Détails',
error: "En raison d'une erreur côté navigateur, votre requête n'a pas pu aboutir.",
invalid_form: "Le formulaire n'est pas valide.",
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
import * as React from 'react';
import { screen, render, waitFor, fireEvent } from '@testing-library/react';
import {
screen,
render,
waitFor,
fireEvent,
within,
} from '@testing-library/react';
import expect from 'expect';
import {
CoreAdminContext,
Expand All @@ -14,6 +20,11 @@ import { Toolbar, SimpleForm } from '../form';
import { Edit } from '../detail';
import { TextInput } from '../input';
import { Notification } from '../layout';
import {
Basic,
NoRecordRepresentation,
WithDefaultTranslation,
} from './DeleteWithConfirmButton.stories';

const theme = createTheme();

Expand Down Expand Up @@ -329,4 +340,45 @@ describe('<DeleteWithConfirmButton />', () => {
]);
});
});

it('should use the record representation in the confirmation title and content with a resource specific translation', async () => {
render(<Basic />);
fireEvent.click(
within(
(await screen.findByText('War and Peace')).closest(
'tr'
) as HTMLElement
).getByText('Delete')
);
await screen.findByText('Delete the book "War and Peace"?');
await screen.findByText(
'Do you really want to delete the book "War and Peace"?'
);
});

it('should use the record representation in the confirmation title and content without a resource specific translation', async () => {
render(<WithDefaultTranslation />);
fireEvent.click(
within(
(await screen.findByText('War and Peace')).closest(
'tr'
) as HTMLElement
).getByText('Delete')
);
await screen.findByText('Delete book War and Peace');
await screen.findByText('Are you sure you want to delete this book?');
});

it('should use the default record representation in the confirmation title and title when no record representation is available', async () => {
render(<NoRecordRepresentation />);
fireEvent.click(
within(
(await screen.findByText('Leo Tolstoy')).closest(
'tr'
) as HTMLElement
).getByText('Delete')
);
await screen.findByText('Delete author #1');
await screen.findByText('Are you sure you want to delete this author?');
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,50 @@ import { TextField } from '../field';
export default { title: 'ra-ui-materialui/button/DeleteWithConfirmButton' };

const i18nProvider = polyglotI18nProvider(
locale =>
locale === 'fr'
? {
...frenchMessages,
resources: {
books: {
name: 'Livre |||| Livres',
fields: {
id: 'Id',
title: 'Titre',
author: 'Auteur',
year: 'Année',
},
message: {
delete_title:
'Supprimer le livre "%{recordRepresentation}" ?',
delete_content:
'Souhaitez-vous vraiment supprimer le livre "%{recordRepresentation}" ?',
},
},
},
}
: {
...englishMessages,
resources: {
books: {
message: {
delete_title:
'Delete the book "%{recordRepresentation}"?',
delete_content:
'Do you really want to delete the book "%{recordRepresentation}"?',
},
},
},
},
// Default locale
'en',
[
{ locale: 'en', name: 'English' },
{ locale: 'fr', name: 'Français' },
]
);

const i18nProviderDefault = polyglotI18nProvider(
locale =>
locale === 'fr'
? {
Expand All @@ -32,7 +76,12 @@ const i18nProvider = polyglotI18nProvider(
},
}
: englishMessages,
'en' // Default locale
// Default locale
'en',
[
{ locale: 'en', name: 'English' },
{ locale: 'fr', name: 'Français' },
]
);

const dataProvider = fakeRestDataProvider({
Expand Down Expand Up @@ -104,7 +153,19 @@ const dataProvider = fakeRestDataProvider({
year: 1922,
},
],
authors: [],
authors: [
{ id: 1, fullName: 'Leo Tolstoy' },
{ id: 2, fullName: 'Jane Austen' },
{ id: 3, fullName: 'Oscar Wilde' },
{ id: 4, fullName: 'Antoine de Saint-Exupéry' },
{ id: 5, fullName: 'Lewis Carroll' },
{ id: 6, fullName: 'Gustave Flaubert' },
{ id: 7, fullName: 'J. R. R. Tolkien' },
{ id: 8, fullName: 'J. K. Rowling' },
{ id: 9, fullName: 'Paulo Coelho' },
{ id: 10, fullName: 'J. D. Salinger' },
{ id: 11, fullName: 'James Joyce' },
],
});

const BookList = ({ children }) => {
Expand All @@ -121,6 +182,18 @@ const BookList = ({ children }) => {
);
};

const AuthorList = ({ children }) => {
return (
<List>
<Datagrid>
<TextField source="id" />
<TextField source="fullName" />
{children}
</Datagrid>
</List>
);
};

export const Basic = () => (
<TestMemoryRouter initialEntries={['/books']}>
<AdminContext dataProvider={dataProvider} i18nProvider={i18nProvider}>
Expand All @@ -138,6 +211,43 @@ export const Basic = () => (
</TestMemoryRouter>
);

export const WithDefaultTranslation = () => (
<TestMemoryRouter initialEntries={['/books']}>
<AdminContext
dataProvider={dataProvider}
i18nProvider={i18nProviderDefault}
>
<AdminUI>
<Resource
name="books"
list={
<BookList>
<DeleteWithConfirmButton />
</BookList>
}
/>
</AdminUI>
</AdminContext>
</TestMemoryRouter>
);

export const NoRecordRepresentation = () => (
<TestMemoryRouter initialEntries={['/authors']}>
<AdminContext dataProvider={dataProvider} i18nProvider={i18nProvider}>
<AdminUI>
<Resource
name="authors"
list={
<AuthorList>
<DeleteWithConfirmButton />
</AuthorList>
}
/>
</AdminUI>
</AdminContext>
</TestMemoryRouter>
);

export const WithCustomDialogContent = () => (
<TestMemoryRouter initialEntries={['/books']}>
<AdminContext dataProvider={dataProvider} i18nProvider={i18nProvider}>
Expand Down
66 changes: 50 additions & 16 deletions packages/ra-ui-materialui/src/button/DeleteWithConfirmButton.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import React, { Fragment, ReactEventHandler } from 'react';
import React, { Fragment, isValidElement, ReactEventHandler } from 'react';
import ActionDelete from '@mui/icons-material/Delete';
import clsx from 'clsx';

Expand All @@ -12,6 +12,7 @@ import {
useResourceContext,
useTranslate,
RedirectionSideEffect,
useGetRecordRepresentation,
} from 'ra-core';

import { Confirm } from '../layout';
Expand All @@ -23,15 +24,17 @@ export const DeleteWithConfirmButton = <RecordType extends RaRecord = any>(
) => {
const {
className,
confirmTitle = 'ra.message.delete_title',
confirmContent = 'ra.message.delete_content',
confirmTitle: confirmTitleProp,
confirmContent: confirmContentProp,
confirmColor = 'primary',
icon = defaultIcon,
label = 'ra.action.delete',
mutationMode = 'pessimistic',
onClick,
redirect = 'list',
translateOptions = {},
titleTranslateOptions = translateOptions,
contentTranslateOptions = translateOptions,
mutationOptions,
color = 'error',
successMessage,
Expand All @@ -56,6 +59,24 @@ export const DeleteWithConfirmButton = <RecordType extends RaRecord = any>(
resource,
successMessage,
});
const getRecordRepresentation = useGetRecordRepresentation(resource);
let recordRepresentation = getRecordRepresentation(record);
let confirmTitle = `resources.${resource}.message.delete_title`;
let confirmContent = `resources.${resource}.message.delete_content`;
const resourceName = translate(`resources.${resource}.forcedCaseName`, {
smart_count: 1,
_: humanize(
translate(`resources.${resource}.name`, {
smart_count: 1,
_: resource ? singularize(resource) : undefined,
}),
true
),
});
// We don't support React elements for this
if (isValidElement(recordRepresentation)) {
recordRepresentation = `#${record?.id}`;
}

return (
<Fragment>
Expand All @@ -72,22 +93,30 @@ export const DeleteWithConfirmButton = <RecordType extends RaRecord = any>(
<Confirm
isOpen={open}
loading={isPending}
title={confirmTitle}
content={confirmContent}
title={confirmTitleProp ?? confirmTitle}
content={confirmContentProp ?? confirmContent}
confirmColor={confirmColor}
translateOptions={{
name: translate(`resources.${resource}.forcedCaseName`, {
smart_count: 1,
_: humanize(
translate(`resources.${resource}.name`, {
smart_count: 1,
_: resource ? singularize(resource) : undefined,
}),
true
),
titleTranslateOptions={{
recordRepresentation,
name: resourceName,
id: record?.id,
_: translate('ra.message.delete_title', {
recordRepresentation,
name: resourceName,
id: record?.id,
}),
...titleTranslateOptions,
}}
contentTranslateOptions={{
recordRepresentation,
name: resourceName,
id: record?.id,
...translateOptions,
_: translate('ra.message.delete_content', {
recordRepresentation,
name: resourceName,
id: record?.id,
}),
...contentTranslateOptions,
}}
onConfirm={handleDelete}
onClose={handleDialogClose}
Expand All @@ -109,7 +138,12 @@ export interface DeleteWithConfirmButtonProps<
mutationMode?: MutationMode;
onClick?: ReactEventHandler<any>;
// May be injected by Toolbar - sanitized in Button
/**
* @deprecated use `titleTranslateOptions` and `contentTranslateOptions` instead
*/
translateOptions?: object;
titleTranslateOptions?: object;
contentTranslateOptions?: object;
mutationOptions?: UseMutationOptions<
RecordType,
MutationOptionsError,
Expand Down
Loading
Loading