Skip to content

Commit 3ecc932

Browse files
authored
Merge pull request #10770 from marmelab/customizable-components
Allow to customize fields and delete button variants with the MUI theme
2 parents b8572fb + 54d0084 commit 3ecc932

36 files changed

+1474
-183
lines changed

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

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ import { Notification } from '../layout';
2323
import {
2424
Basic,
2525
NoRecordRepresentation,
26+
Themed,
2627
WithCustomTitleAndContent,
2728
WithDefaultTranslation,
2829
} from './DeleteWithConfirmButton.stories';
@@ -395,4 +396,10 @@ describe('<DeleteWithConfirmButton />', () => {
395396
await screen.findByText('Delete author #1');
396397
await screen.findByText('Are you sure you want to delete this author?');
397398
});
399+
400+
it('should be customized by a theme', async () => {
401+
render(<Themed />);
402+
const buttons = await screen.findAllByTestId('themed');
403+
expect(buttons[0].classList).toContain('MuiButton-outlined');
404+
});
398405
});

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

Lines changed: 37 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,14 @@ import englishMessages from 'ra-language-english';
44
import frenchMessages from 'ra-language-french';
55
import { Resource, TestMemoryRouter } from 'ra-core';
66
import fakeRestDataProvider from 'ra-data-fakerest';
7-
import { Alert } from '@mui/material';
7+
import { Alert, createTheme, ThemeOptions } from '@mui/material';
88

99
import { DeleteWithConfirmButton } from './DeleteWithConfirmButton';
1010
import { AdminContext } from '../AdminContext';
1111
import { AdminUI } from '../AdminUI';
1212
import { List, Datagrid } from '../list';
1313
import { TextField } from '../field';
14+
import { deepmerge } from '@mui/utils';
1415

1516
export default { title: 'ra-ui-materialui/button/DeleteWithConfirmButton' };
1617

@@ -300,3 +301,38 @@ export const WithCustomDialogContent = () => (
300301
</AdminContext>
301302
</TestMemoryRouter>
302303
);
304+
305+
export const Themed = () => (
306+
<TestMemoryRouter initialEntries={['/books']}>
307+
<AdminContext
308+
dataProvider={dataProvider}
309+
i18nProvider={i18nProvider}
310+
theme={deepmerge(createTheme(), {
311+
components: {
312+
RaDeleteWithConfirmButton: {
313+
defaultProps: {
314+
variant: 'outlined',
315+
'data-testid': 'themed',
316+
},
317+
styleOverrides: {
318+
root: {
319+
color: 'hotpink',
320+
},
321+
},
322+
},
323+
},
324+
} as ThemeOptions)}
325+
>
326+
<AdminUI>
327+
<Resource
328+
name="books"
329+
list={
330+
<BookList>
331+
<DeleteWithConfirmButton />
332+
</BookList>
333+
}
334+
/>
335+
</AdminUI>
336+
</AdminContext>
337+
</TestMemoryRouter>
338+
);

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

Lines changed: 39 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,10 @@
11
import React, { Fragment, isValidElement, ReactEventHandler } from 'react';
22
import ActionDelete from '@mui/icons-material/Delete';
3+
import {
4+
ComponentsOverrides,
5+
styled,
6+
useThemeProps,
7+
} from '@mui/material/styles';
38
import clsx from 'clsx';
49

510
import { UseMutationOptions } from '@tanstack/react-query';
@@ -20,8 +25,13 @@ import { Button, ButtonProps } from './Button';
2025
import { humanize, singularize } from 'inflection';
2126

2227
export const DeleteWithConfirmButton = <RecordType extends RaRecord = any>(
23-
props: DeleteWithConfirmButtonProps<RecordType>
28+
inProps: DeleteWithConfirmButtonProps<RecordType>
2429
) => {
30+
const props = useThemeProps({
31+
props: inProps,
32+
name: PREFIX,
33+
});
34+
2535
const {
2636
className,
2737
confirmTitle: confirmTitleProp,
@@ -80,7 +90,7 @@ export const DeleteWithConfirmButton = <RecordType extends RaRecord = any>(
8090

8191
return (
8292
<Fragment>
83-
<Button
93+
<StyledButton
8494
onClick={handleDialogOpen}
8595
label={label}
8696
className={clsx('ra-delete-button', className)}
@@ -89,7 +99,7 @@ export const DeleteWithConfirmButton = <RecordType extends RaRecord = any>(
8999
{...rest}
90100
>
91101
{icon}
92-
</Button>
102+
</StyledButton>
93103
<Confirm
94104
isOpen={open}
95105
loading={isPending}
@@ -158,3 +168,29 @@ export interface DeleteWithConfirmButtonProps<
158168
resource?: string;
159169
successMessage?: string;
160170
}
171+
172+
const PREFIX = 'RaDeleteWithConfirmButton';
173+
174+
const StyledButton = styled(Button, {
175+
name: PREFIX,
176+
overridesResolver: (props, styles) => styles.root,
177+
})({});
178+
179+
declare module '@mui/material/styles' {
180+
interface ComponentNameToClassKey {
181+
[PREFIX]: 'root';
182+
}
183+
184+
interface ComponentsPropsList {
185+
[PREFIX]: Partial<DeleteWithConfirmButtonProps>;
186+
}
187+
188+
interface Components {
189+
[PREFIX]?: {
190+
defaultProps?: ComponentsPropsList[typeof PREFIX];
191+
styleOverrides?: ComponentsOverrides<
192+
Omit<Theme, 'components'>
193+
>[typeof PREFIX];
194+
};
195+
}
196+
}

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

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import { Toolbar, SimpleForm } from '../form';
1313
import { Edit } from '../detail';
1414
import { TextInput } from '../input';
1515
import { DeleteWithUndoButton } from './DeleteWithUndoButton';
16+
import { Themed } from './DeleteWithUndoButton.stories';
1617

1718
const theme = createTheme();
1819

@@ -161,4 +162,10 @@ describe('<DeleteWithUndoButton />', () => {
161162
]);
162163
});
163164
});
165+
166+
it('should be customized by a theme', async () => {
167+
render(<Themed />);
168+
const buttons = await screen.findAllByTestId('themed');
169+
expect(buttons[0].classList).toContain('MuiButton-outlined');
170+
});
164171
});
Lines changed: 209 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,209 @@
1+
import { Resource, TestMemoryRouter } from 'ra-core';
2+
import { AdminContext } from '../AdminContext';
3+
import { deepmerge } from '@mui/utils';
4+
import { createTheme, ThemeOptions } from '@mui/material';
5+
import { AdminUI } from '../AdminUI';
6+
import { DeleteWithUndoButton } from './DeleteWithUndoButton';
7+
import * as React from 'react';
8+
import polyglotI18nProvider from 'ra-i18n-polyglot';
9+
import frenchMessages from 'ra-language-french';
10+
import englishMessages from 'ra-language-english';
11+
import fakeRestDataProvider from 'ra-data-fakerest';
12+
import { Datagrid, List } from '../list';
13+
import { TextField } from '../field';
14+
15+
export default { title: 'ra-ui-materialui/button/DeleteWithUndoButton' };
16+
17+
const i18nProvider = polyglotI18nProvider(
18+
locale =>
19+
locale === 'fr'
20+
? {
21+
...frenchMessages,
22+
resources: {
23+
books: {
24+
name: 'Livre |||| Livres',
25+
fields: {
26+
id: 'Id',
27+
title: 'Titre',
28+
author: 'Auteur',
29+
year: 'Année',
30+
},
31+
message: {
32+
delete_title:
33+
'Supprimer le livre "%{recordRepresentation}" ?',
34+
delete_content:
35+
'Souhaitez-vous vraiment supprimer le livre "%{recordRepresentation}" ?',
36+
},
37+
},
38+
},
39+
}
40+
: {
41+
...englishMessages,
42+
resources: {
43+
books: {
44+
message: {
45+
delete_title:
46+
'Delete the book "%{recordRepresentation}"?',
47+
delete_content:
48+
'Do you really want to delete the book "%{recordRepresentation}"?',
49+
},
50+
},
51+
},
52+
},
53+
// Default locale
54+
'en',
55+
[
56+
{ locale: 'en', name: 'English' },
57+
{ locale: 'fr', name: 'Français' },
58+
]
59+
);
60+
61+
const dataProvider = fakeRestDataProvider({
62+
books: [
63+
{
64+
id: 1,
65+
title: 'War and Peace',
66+
author: 'Leo Tolstoy',
67+
year: 1869,
68+
},
69+
{
70+
id: 2,
71+
title: 'Pride and Predjudice',
72+
author: 'Jane Austen',
73+
year: 1813,
74+
},
75+
{
76+
id: 3,
77+
title: 'The Picture of Dorian Gray',
78+
author: 'Oscar Wilde',
79+
year: 1890,
80+
},
81+
{
82+
id: 4,
83+
title: 'Le Petit Prince',
84+
author: 'Antoine de Saint-Exupéry',
85+
year: 1943,
86+
},
87+
{
88+
id: 5,
89+
title: "Alice's Adventures in Wonderland",
90+
author: 'Lewis Carroll',
91+
year: 1865,
92+
},
93+
{
94+
id: 6,
95+
title: 'Madame Bovary',
96+
author: 'Gustave Flaubert',
97+
year: 1856,
98+
},
99+
{
100+
id: 7,
101+
title: 'The Lord of the Rings',
102+
author: 'J. R. R. Tolkien',
103+
year: 1954,
104+
},
105+
{
106+
id: 8,
107+
title: "Harry Potter and the Philosopher's Stone",
108+
author: 'J. K. Rowling',
109+
year: 1997,
110+
},
111+
{
112+
id: 9,
113+
title: 'The Alchemist',
114+
author: 'Paulo Coelho',
115+
year: 1988,
116+
},
117+
{
118+
id: 10,
119+
title: 'A Catcher in the Rye',
120+
author: 'J. D. Salinger',
121+
year: 1951,
122+
},
123+
{
124+
id: 11,
125+
title: 'Ulysses',
126+
author: 'James Joyce',
127+
year: 1922,
128+
},
129+
],
130+
authors: [
131+
{ id: 1, fullName: 'Leo Tolstoy' },
132+
{ id: 2, fullName: 'Jane Austen' },
133+
{ id: 3, fullName: 'Oscar Wilde' },
134+
{ id: 4, fullName: 'Antoine de Saint-Exupéry' },
135+
{ id: 5, fullName: 'Lewis Carroll' },
136+
{ id: 6, fullName: 'Gustave Flaubert' },
137+
{ id: 7, fullName: 'J. R. R. Tolkien' },
138+
{ id: 8, fullName: 'J. K. Rowling' },
139+
{ id: 9, fullName: 'Paulo Coelho' },
140+
{ id: 10, fullName: 'J. D. Salinger' },
141+
{ id: 11, fullName: 'James Joyce' },
142+
],
143+
});
144+
145+
const BookList = ({ children }) => {
146+
return (
147+
<List>
148+
<Datagrid>
149+
<TextField source="id" />
150+
<TextField source="title" />
151+
<TextField source="author" />
152+
<TextField source="year" />
153+
{children}
154+
</Datagrid>
155+
</List>
156+
);
157+
};
158+
159+
export const Basic = () => (
160+
<TestMemoryRouter initialEntries={['/books']}>
161+
<AdminContext dataProvider={dataProvider} i18nProvider={i18nProvider}>
162+
<AdminUI>
163+
<Resource
164+
name="books"
165+
list={
166+
<BookList>
167+
<DeleteWithUndoButton />
168+
</BookList>
169+
}
170+
/>
171+
</AdminUI>
172+
</AdminContext>
173+
</TestMemoryRouter>
174+
);
175+
176+
export const Themed = () => (
177+
<TestMemoryRouter initialEntries={['/books']}>
178+
<AdminContext
179+
dataProvider={dataProvider}
180+
i18nProvider={i18nProvider}
181+
theme={deepmerge(createTheme(), {
182+
components: {
183+
RaDeleteWithUndoButton: {
184+
defaultProps: {
185+
variant: 'outlined',
186+
'data-testid': 'themed',
187+
},
188+
styleOverrides: {
189+
root: {
190+
color: 'hotpink',
191+
},
192+
},
193+
},
194+
},
195+
} as ThemeOptions)}
196+
>
197+
<AdminUI>
198+
<Resource
199+
name="books"
200+
list={
201+
<BookList>
202+
<DeleteWithUndoButton />
203+
</BookList>
204+
}
205+
/>
206+
</AdminUI>
207+
</AdminContext>
208+
</TestMemoryRouter>
209+
);

0 commit comments

Comments
 (0)