Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feat: SimpleFormIterator clear item call action #8302

Merged
merged 5 commits into from
Nov 27, 2022
Merged
Show file tree
Hide file tree
Changes from all 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
14 changes: 14 additions & 0 deletions docs/SimpleFormIterator.md
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@ const OrderEdit = () => (
| `children` | Optional | `ReactElement` | - | List of inputs to display for each row |
| `className` | Optional | `string` | - | Applied to the root element (`<ul>`) |
| `disableAdd` | Optional | `boolean` | `false` | When true, the user cannot add new rows |
| `disableClear` | Optional | `boolean` | `false` | When true, the user cannot clear the array |
| `disableRemove` | Optional | `boolean` | `false` | When true, the user cannot remove rows |
| `disableReordering` | Optional | `boolean` | `false` | When true, the user cannot reorder rows |
| `fullWidth` | Optional | `boolean` | `false` | Set to true to push the actions to the right |
Expand Down Expand Up @@ -197,6 +198,19 @@ When true, the Add button isn't rendered, so users cannot add new rows.
</SimpleFormIterator>
```

## `disableClear`

When true, the array clear button isn't rendered, so the user cannot clear the array.

```jsx
<SimpleFormIterator disableClear>
<TextInput source="name" />
<NumberInput source="price" />
<NumberInput source="quantity" />
</SimpleFormIterator>
```


## `disableRemove`

When true, the Remove buttons aren't rendered, so users cannot remove existing rows.
Expand Down
40 changes: 20 additions & 20 deletions examples/tutorial/src/App.css
Original file line number Diff line number Diff line change
@@ -1,41 +1,41 @@
#root {
max-width: 1280px;
margin: 0 auto;
padding: 2rem;
text-align: center;
max-width: 1280px;
margin: 0 auto;
padding: 2rem;
text-align: center;
}

.logo {
height: 6em;
padding: 1.5em;
will-change: filter;
height: 6em;
padding: 1.5em;
will-change: filter;
}
.logo:hover {
filter: drop-shadow(0 0 2em #646cffaa);
filter: drop-shadow(0 0 2em #646cffaa);
}
.logo.react:hover {
filter: drop-shadow(0 0 2em #61dafbaa);
filter: drop-shadow(0 0 2em #61dafbaa);
}

@keyframes logo-spin {
from {
transform: rotate(0deg);
}
to {
transform: rotate(360deg);
}
from {
transform: rotate(0deg);
}
to {
transform: rotate(360deg);
}
}

@media (prefers-reduced-motion: no-preference) {
a:nth-of-type(2) .logo {
animation: logo-spin infinite 20s linear;
}
a:nth-of-type(2) .logo {
animation: logo-spin infinite 20s linear;
}
}

.card {
padding: 2em;
padding: 2em;
}

.read-the-docs {
color: #888;
color: #888;
}
2 changes: 2 additions & 0 deletions packages/ra-language-english/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ const englishMessages: TranslationMessages = {
back: 'Go Back',
bulk_actions: '1 item selected |||| %{smart_count} items selected',
cancel: 'Cancel',
clear_array_input: 'Clear the list',
clear_input_value: 'Clear value',
clone: 'Clone',
confirm: 'Confirm',
Expand Down Expand Up @@ -92,6 +93,7 @@ const englishMessages: TranslationMessages = {
'Are you sure you want to update this %{name}? |||| Are you sure you want to update these %{smart_count} items?',
bulk_update_title:
'Update %{name} |||| 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}',
details: 'Details',
Expand Down
3 changes: 3 additions & 0 deletions packages/ra-language-french/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ const frenchMessages: TranslationMessages = {
bulk_actions:
'%{smart_count} sélectionné |||| %{smart_count} sélectionnés',
cancel: 'Annuler',
clear_array_input: 'Vider la liste',
clear_input_value: 'Vider le champ',
clone: 'Dupliquer',
confirm: 'Confirmer',
Expand Down Expand Up @@ -94,6 +95,8 @@ const frenchMessages: TranslationMessages = {
'Êtes-vous sûr(e) de vouloir modifier cet élément ? |||| Êtes-vous sûr(e) de vouloir modifier ces %{smart_count} éléments ?',
bulk_update_title:
'Modifier %{name} |||| Modifier %{smart_count} %{name}',
clear_array_input:
'Ê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}',
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import * as React from 'react';
import { ButtonProps, IconButtonWithTooltip } from '../../button';
import ClearIcon from '@mui/icons-material/HighlightOff';

interface ClearArrayButtonProps extends ButtonProps {
className?: string;
}

export const ClearArrayButton: React.FC<ClearArrayButtonProps> = props => {
return (
<IconButtonWithTooltip
label="ra.action.clear_array_input"
size="small"
color="warning"
{...props}
>
<ClearIcon fontSize="small" />
</IconButtonWithTooltip>
);
};

export default null;
Original file line number Diff line number Diff line change
Expand Up @@ -226,6 +226,37 @@ describe('<SimpleFormIterator />', () => {
expect(screen.queryAllByLabelText('ra.action.remove').length).toBe(0);
});

it('should remove all children row on clear action button click', async () => {
render(
<Wrapper>
<SimpleForm
record={{
id: 'whatever',
emails: [{ email: '' }, { email: '' }],
}}
>
<ArrayInput source="emails">
<SimpleFormIterator>
<TextInput source="email" />
</SimpleFormIterator>
</ArrayInput>
</SimpleForm>
</Wrapper>
);
const clearActionElements = screen
.getByLabelText('ra.action.clear_array_input')
.closest('button') as HTMLButtonElement;

fireEvent.click(clearActionElements);
fireEvent.click(screen.getByText('ra.action.confirm'));
await waitFor(() => {
const inputElements = screen.queryAllByLabelText(
'resources.undefined.fields.emails.email'
);
expect(inputElements.length).toBe(0);
});
});

it('should add children row on add button click', async () => {
render(
<Wrapper>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,21 @@ export const DisableAdd = () => (
</AdminContext>
);

export const DisableClear = () => (
<AdminContext dataProvider={dataProvider}>
<Edit resource="books" id="1">
<SimpleForm>
<ArrayInput source="authors">
<SimpleFormIterator disableClear>
<TextInput source="name" />
<TextInput source="role" />
</SimpleFormIterator>
</ArrayInput>
</SimpleForm>
</Edit>
</AdminContext>
);

export const DisableRemove = () => (
<AdminContext dataProvider={dataProvider}>
<Edit resource="books" id="1">
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,18 @@ import {
useCallback,
useMemo,
useRef,
useState,
} from 'react';
import { styled, SxProps } from '@mui/material';
import clsx from 'clsx';
import get from 'lodash/get';
import PropTypes from 'prop-types';
import { FormDataConsumer, RaRecord, useRecordContext } from 'ra-core';
import {
FormDataConsumer,
RaRecord,
useRecordContext,
useTranslate,
} from 'ra-core';
import { UseFieldArrayReturn } from 'react-hook-form';

import { useArrayInput } from './useArrayInput';
Expand All @@ -30,6 +36,8 @@ import {
import { AddItemButton as DefaultAddItemButton } from './AddItemButton';
import { RemoveItemButton as DefaultRemoveItemButton } from './RemoveItemButton';
import { ReOrderButtons as DefaultReOrderButtons } from './ReOrderButtons';
import { ClearArrayButton } from './ClearArrayButton';
import { Confirm } from '../../layout';

export const SimpleFormIterator = (props: SimpleFormIteratorProps) => {
const {
Expand All @@ -42,14 +50,17 @@ export const SimpleFormIterator = (props: SimpleFormIteratorProps) => {
source,
disabled,
disableAdd,
disableClear,
disableRemove,
disableReordering,
inline,
getItemLabel = false,
fullWidth,
sx,
} = props;
const { append, fields, move, remove } = useArrayInput(props);
const [confirmIsOpen, setConfirmIsOpen] = useState<boolean>(false);
const { append, fields, move, remove, replace } = useArrayInput(props);
const translate = useTranslate();
const record = useRecordContext(props);
const initialDefaultValue = useRef({});

Expand Down Expand Up @@ -120,6 +131,11 @@ export const SimpleFormIterator = (props: SimpleFormIteratorProps) => {
[move]
);

const handleArrayClear = useCallback(() => {
replace([]);
setConfirmIsOpen(false);
}, [replace]);

const records = get(record, source);

const context = useMemo(
Expand Down Expand Up @@ -166,19 +182,56 @@ export const SimpleFormIterator = (props: SimpleFormIteratorProps) => {
</SimpleFormIteratorItem>
))}
</ul>
{!disabled && !disableAdd && (
<div className={SimpleFormIteratorClasses.add}>
{cloneElement(addButton, {
className: clsx(
'button-add',
`button-add-${source}`
),
onClick: handleAddButtonClick(
addButton.props.onClick
),
})}
</div>
)}
{!disabled &&
!Boolean(disableAdd && (disableClear || disableRemove)) && (
<div className={SimpleFormIteratorClasses.buttons}>
{!disableAdd && (
<div className={SimpleFormIteratorClasses.add}>
{cloneElement(addButton, {
className: clsx(
'button-add',
`button-add-${source}`
),
onClick: handleAddButtonClick(
addButton.props.onClick
),
})}
</div>
)}
{fields.length > 0 &&
!disableClear &&
!disableRemove && (
<div
className={
SimpleFormIteratorClasses.clear
}
>
<Confirm
isOpen={confirmIsOpen}
title={translate(
'ra.action.clear_array_input'
)}
content={translate(
'ra.message.clear_array_input'
)}
onConfirm={handleArrayClear}
onClose={() =>
setConfirmIsOpen(false)
}
/>
<ClearArrayButton
className={clsx(
'button-clear',
`button-clear-${source}`
)}
onClick={() =>
setConfirmIsOpen(true)
}
/>
</div>
)}
</div>
)}
</Root>
</SimpleFormIteratorContext.Provider>
) : null;
Expand Down Expand Up @@ -217,6 +270,7 @@ export interface SimpleFormIteratorProps extends Partial<UseFieldArrayReturn> {
className?: string;
disabled?: boolean;
disableAdd?: boolean;
disableClear?: boolean;
disableRemove?: boolean | DisableRemoveFunction;
disableReordering?: boolean;
fullWidth?: boolean;
Expand Down Expand Up @@ -281,9 +335,15 @@ const Root = styled('div', {
visibility: 'visible',
},
},
[`& .${SimpleFormIteratorClasses.buttons}`]: {
display: 'flex',
},
[`& .${SimpleFormIteratorClasses.add}`]: {
borderBottom: 'none',
},
[`& .${SimpleFormIteratorClasses.clear}`]: {
borderBottom: 'none',
},
[`& .${SimpleFormIteratorClasses.line}:hover > .${SimpleFormIteratorClasses.action}`]: {
visibility: 'visible',
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,11 @@ export const SimpleFormIteratorPrefix = 'RaSimpleFormIterator';
export const SimpleFormIteratorClasses = {
action: `${SimpleFormIteratorPrefix}-action`,
add: `${SimpleFormIteratorPrefix}-add`,
clear: `${SimpleFormIteratorPrefix}-clear`,
form: `${SimpleFormIteratorPrefix}-form`,
index: `${SimpleFormIteratorPrefix}-index`,
inline: `${SimpleFormIteratorPrefix}-inline`,
line: `${SimpleFormIteratorPrefix}-line`,
list: `${SimpleFormIteratorPrefix}-list`,
buttons: `${SimpleFormIteratorPrefix}-buttons`,
};