Skip to content

Commit 4487b6e

Browse files
authored
Merge pull request #8302 from Seojun-Park/array-clear-button
Feat: `SimpleFormIterator` clear item call action
2 parents 1ea8028 + dd187c5 commit 4487b6e

File tree

9 files changed

+184
-35
lines changed

9 files changed

+184
-35
lines changed

docs/SimpleFormIterator.md

+14
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,7 @@ const OrderEdit = () => (
7373
| `children` | Optional | `ReactElement` | - | List of inputs to display for each row |
7474
| `className` | Optional | `string` | - | Applied to the root element (`<ul>`) |
7575
| `disableAdd` | Optional | `boolean` | `false` | When true, the user cannot add new rows |
76+
| `disableClear` | Optional | `boolean` | `false` | When true, the user cannot clear the array |
7677
| `disableRemove` | Optional | `boolean` | `false` | When true, the user cannot remove rows |
7778
| `disableReordering` | Optional | `boolean` | `false` | When true, the user cannot reorder rows |
7879
| `fullWidth` | Optional | `boolean` | `false` | Set to true to push the actions to the right |
@@ -197,6 +198,19 @@ When true, the Add button isn't rendered, so users cannot add new rows.
197198
</SimpleFormIterator>
198199
```
199200

201+
## `disableClear`
202+
203+
When true, the array clear button isn't rendered, so the user cannot clear the array.
204+
205+
```jsx
206+
<SimpleFormIterator disableClear>
207+
<TextInput source="name" />
208+
<NumberInput source="price" />
209+
<NumberInput source="quantity" />
210+
</SimpleFormIterator>
211+
```
212+
213+
200214
## `disableRemove`
201215

202216
When true, the Remove buttons aren't rendered, so users cannot remove existing rows.

examples/tutorial/src/App.css

+20-20
Original file line numberDiff line numberDiff line change
@@ -1,41 +1,41 @@
11
#root {
2-
max-width: 1280px;
3-
margin: 0 auto;
4-
padding: 2rem;
5-
text-align: center;
2+
max-width: 1280px;
3+
margin: 0 auto;
4+
padding: 2rem;
5+
text-align: center;
66
}
77

88
.logo {
9-
height: 6em;
10-
padding: 1.5em;
11-
will-change: filter;
9+
height: 6em;
10+
padding: 1.5em;
11+
will-change: filter;
1212
}
1313
.logo:hover {
14-
filter: drop-shadow(0 0 2em #646cffaa);
14+
filter: drop-shadow(0 0 2em #646cffaa);
1515
}
1616
.logo.react:hover {
17-
filter: drop-shadow(0 0 2em #61dafbaa);
17+
filter: drop-shadow(0 0 2em #61dafbaa);
1818
}
1919

2020
@keyframes logo-spin {
21-
from {
22-
transform: rotate(0deg);
23-
}
24-
to {
25-
transform: rotate(360deg);
26-
}
21+
from {
22+
transform: rotate(0deg);
23+
}
24+
to {
25+
transform: rotate(360deg);
26+
}
2727
}
2828

2929
@media (prefers-reduced-motion: no-preference) {
30-
a:nth-of-type(2) .logo {
31-
animation: logo-spin infinite 20s linear;
32-
}
30+
a:nth-of-type(2) .logo {
31+
animation: logo-spin infinite 20s linear;
32+
}
3333
}
3434

3535
.card {
36-
padding: 2em;
36+
padding: 2em;
3737
}
3838

3939
.read-the-docs {
40-
color: #888;
40+
color: #888;
4141
}

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

+2
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ const englishMessages: TranslationMessages = {
88
back: 'Go Back',
99
bulk_actions: '1 item selected |||| %{smart_count} items selected',
1010
cancel: 'Cancel',
11+
clear_array_input: 'Clear the list',
1112
clear_input_value: 'Clear value',
1213
clone: 'Clone',
1314
confirm: 'Confirm',
@@ -92,6 +93,7 @@ const englishMessages: TranslationMessages = {
9293
'Are you sure you want to update this %{name}? |||| Are you sure you want to update these %{smart_count} items?',
9394
bulk_update_title:
9495
'Update %{name} |||| Update %{smart_count} %{name}',
96+
clear_array_input: 'Are you sure you want to clear the whole list?',
9597
delete_content: 'Are you sure you want to delete this item?',
9698
delete_title: 'Delete %{name} #%{id}',
9799
details: 'Details',

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

+3
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ const frenchMessages: TranslationMessages = {
99
bulk_actions:
1010
'%{smart_count} sélectionné |||| %{smart_count} sélectionnés',
1111
cancel: 'Annuler',
12+
clear_array_input: 'Vider la liste',
1213
clear_input_value: 'Vider le champ',
1314
clone: 'Dupliquer',
1415
confirm: 'Confirmer',
@@ -94,6 +95,8 @@ const frenchMessages: TranslationMessages = {
9495
'Êtes-vous sûr(e) de vouloir modifier cet élément ? |||| Êtes-vous sûr(e) de vouloir modifier ces %{smart_count} éléments ?',
9596
bulk_update_title:
9697
'Modifier %{name} |||| Modifier %{smart_count} %{name}',
98+
clear_array_input:
99+
'Êtes-vous sûr(e) de vouloir supprimer tous les éléments de la liste ?',
97100
delete_content:
98101
'Êtes-vous sûr(e) de vouloir supprimer cet élément ?',
99102
delete_title: 'Supprimer %{name} #%{id}',
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
import * as React from 'react';
2+
import { ButtonProps, IconButtonWithTooltip } from '../../button';
3+
import ClearIcon from '@mui/icons-material/HighlightOff';
4+
5+
interface ClearArrayButtonProps extends ButtonProps {
6+
className?: string;
7+
}
8+
9+
export const ClearArrayButton: React.FC<ClearArrayButtonProps> = props => {
10+
return (
11+
<IconButtonWithTooltip
12+
label="ra.action.clear_array_input"
13+
size="small"
14+
color="warning"
15+
{...props}
16+
>
17+
<ClearIcon fontSize="small" />
18+
</IconButtonWithTooltip>
19+
);
20+
};
21+
22+
export default null;

packages/ra-ui-materialui/src/input/ArrayInput/SimpleFormIterator.spec.tsx

+31
Original file line numberDiff line numberDiff line change
@@ -226,6 +226,37 @@ describe('<SimpleFormIterator />', () => {
226226
expect(screen.queryAllByLabelText('ra.action.remove').length).toBe(0);
227227
});
228228

229+
it('should remove all children row on clear action button click', async () => {
230+
render(
231+
<Wrapper>
232+
<SimpleForm
233+
record={{
234+
id: 'whatever',
235+
emails: [{ email: '' }, { email: '' }],
236+
}}
237+
>
238+
<ArrayInput source="emails">
239+
<SimpleFormIterator>
240+
<TextInput source="email" />
241+
</SimpleFormIterator>
242+
</ArrayInput>
243+
</SimpleForm>
244+
</Wrapper>
245+
);
246+
const clearActionElements = screen
247+
.getByLabelText('ra.action.clear_array_input')
248+
.closest('button') as HTMLButtonElement;
249+
250+
fireEvent.click(clearActionElements);
251+
fireEvent.click(screen.getByText('ra.action.confirm'));
252+
await waitFor(() => {
253+
const inputElements = screen.queryAllByLabelText(
254+
'resources.undefined.fields.emails.email'
255+
);
256+
expect(inputElements.length).toBe(0);
257+
});
258+
});
259+
229260
it('should add children row on add button click', async () => {
230261
render(
231262
<Wrapper>

packages/ra-ui-materialui/src/input/ArrayInput/SimpleFormIterator.stories.tsx

+15
Original file line numberDiff line numberDiff line change
@@ -122,6 +122,21 @@ export const DisableAdd = () => (
122122
</AdminContext>
123123
);
124124

125+
export const DisableClear = () => (
126+
<AdminContext dataProvider={dataProvider}>
127+
<Edit resource="books" id="1">
128+
<SimpleForm>
129+
<ArrayInput source="authors">
130+
<SimpleFormIterator disableClear>
131+
<TextInput source="name" />
132+
<TextInput source="role" />
133+
</SimpleFormIterator>
134+
</ArrayInput>
135+
</SimpleForm>
136+
</Edit>
137+
</AdminContext>
138+
);
139+
125140
export const DisableRemove = () => (
126141
<AdminContext dataProvider={dataProvider}>
127142
<Edit resource="books" id="1">

packages/ra-ui-materialui/src/input/ArrayInput/SimpleFormIterator.tsx

+75-15
Original file line numberDiff line numberDiff line change
@@ -9,12 +9,18 @@ import {
99
useCallback,
1010
useMemo,
1111
useRef,
12+
useState,
1213
} from 'react';
1314
import { styled, SxProps } from '@mui/material';
1415
import clsx from 'clsx';
1516
import get from 'lodash/get';
1617
import PropTypes from 'prop-types';
17-
import { FormDataConsumer, RaRecord, useRecordContext } from 'ra-core';
18+
import {
19+
FormDataConsumer,
20+
RaRecord,
21+
useRecordContext,
22+
useTranslate,
23+
} from 'ra-core';
1824
import { UseFieldArrayReturn } from 'react-hook-form';
1925

2026
import { useArrayInput } from './useArrayInput';
@@ -30,6 +36,8 @@ import {
3036
import { AddItemButton as DefaultAddItemButton } from './AddItemButton';
3137
import { RemoveItemButton as DefaultRemoveItemButton } from './RemoveItemButton';
3238
import { ReOrderButtons as DefaultReOrderButtons } from './ReOrderButtons';
39+
import { ClearArrayButton } from './ClearArrayButton';
40+
import { Confirm } from '../../layout';
3341

3442
export const SimpleFormIterator = (props: SimpleFormIteratorProps) => {
3543
const {
@@ -42,14 +50,17 @@ export const SimpleFormIterator = (props: SimpleFormIteratorProps) => {
4250
source,
4351
disabled,
4452
disableAdd,
53+
disableClear,
4554
disableRemove,
4655
disableReordering,
4756
inline,
4857
getItemLabel = false,
4958
fullWidth,
5059
sx,
5160
} = props;
52-
const { append, fields, move, remove } = useArrayInput(props);
61+
const [confirmIsOpen, setConfirmIsOpen] = useState<boolean>(false);
62+
const { append, fields, move, remove, replace } = useArrayInput(props);
63+
const translate = useTranslate();
5364
const record = useRecordContext(props);
5465
const initialDefaultValue = useRef({});
5566

@@ -120,6 +131,11 @@ export const SimpleFormIterator = (props: SimpleFormIteratorProps) => {
120131
[move]
121132
);
122133

134+
const handleArrayClear = useCallback(() => {
135+
replace([]);
136+
setConfirmIsOpen(false);
137+
}, [replace]);
138+
123139
const records = get(record, source);
124140

125141
const context = useMemo(
@@ -166,19 +182,56 @@ export const SimpleFormIterator = (props: SimpleFormIteratorProps) => {
166182
</SimpleFormIteratorItem>
167183
))}
168184
</ul>
169-
{!disabled && !disableAdd && (
170-
<div className={SimpleFormIteratorClasses.add}>
171-
{cloneElement(addButton, {
172-
className: clsx(
173-
'button-add',
174-
`button-add-${source}`
175-
),
176-
onClick: handleAddButtonClick(
177-
addButton.props.onClick
178-
),
179-
})}
180-
</div>
181-
)}
185+
{!disabled &&
186+
!Boolean(disableAdd && (disableClear || disableRemove)) && (
187+
<div className={SimpleFormIteratorClasses.buttons}>
188+
{!disableAdd && (
189+
<div className={SimpleFormIteratorClasses.add}>
190+
{cloneElement(addButton, {
191+
className: clsx(
192+
'button-add',
193+
`button-add-${source}`
194+
),
195+
onClick: handleAddButtonClick(
196+
addButton.props.onClick
197+
),
198+
})}
199+
</div>
200+
)}
201+
{fields.length > 0 &&
202+
!disableClear &&
203+
!disableRemove && (
204+
<div
205+
className={
206+
SimpleFormIteratorClasses.clear
207+
}
208+
>
209+
<Confirm
210+
isOpen={confirmIsOpen}
211+
title={translate(
212+
'ra.action.clear_array_input'
213+
)}
214+
content={translate(
215+
'ra.message.clear_array_input'
216+
)}
217+
onConfirm={handleArrayClear}
218+
onClose={() =>
219+
setConfirmIsOpen(false)
220+
}
221+
/>
222+
<ClearArrayButton
223+
className={clsx(
224+
'button-clear',
225+
`button-clear-${source}`
226+
)}
227+
onClick={() =>
228+
setConfirmIsOpen(true)
229+
}
230+
/>
231+
</div>
232+
)}
233+
</div>
234+
)}
182235
</Root>
183236
</SimpleFormIteratorContext.Provider>
184237
) : null;
@@ -217,6 +270,7 @@ export interface SimpleFormIteratorProps extends Partial<UseFieldArrayReturn> {
217270
className?: string;
218271
disabled?: boolean;
219272
disableAdd?: boolean;
273+
disableClear?: boolean;
220274
disableRemove?: boolean | DisableRemoveFunction;
221275
disableReordering?: boolean;
222276
fullWidth?: boolean;
@@ -281,9 +335,15 @@ const Root = styled('div', {
281335
visibility: 'visible',
282336
},
283337
},
338+
[`& .${SimpleFormIteratorClasses.buttons}`]: {
339+
display: 'flex',
340+
},
284341
[`& .${SimpleFormIteratorClasses.add}`]: {
285342
borderBottom: 'none',
286343
},
344+
[`& .${SimpleFormIteratorClasses.clear}`]: {
345+
borderBottom: 'none',
346+
},
287347
[`& .${SimpleFormIteratorClasses.line}:hover > .${SimpleFormIteratorClasses.action}`]: {
288348
visibility: 'visible',
289349
},

packages/ra-ui-materialui/src/input/ArrayInput/useSimpleFormIteratorStyles.ts

+2
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,11 @@ export const SimpleFormIteratorPrefix = 'RaSimpleFormIterator';
33
export const SimpleFormIteratorClasses = {
44
action: `${SimpleFormIteratorPrefix}-action`,
55
add: `${SimpleFormIteratorPrefix}-add`,
6+
clear: `${SimpleFormIteratorPrefix}-clear`,
67
form: `${SimpleFormIteratorPrefix}-form`,
78
index: `${SimpleFormIteratorPrefix}-index`,
89
inline: `${SimpleFormIteratorPrefix}-inline`,
910
line: `${SimpleFormIteratorPrefix}-line`,
1011
list: `${SimpleFormIteratorPrefix}-list`,
12+
buttons: `${SimpleFormIteratorPrefix}-buttons`,
1113
};

0 commit comments

Comments
 (0)