Skip to content

Commit

Permalink
Merge pull request #8124 from marmelab/simpleformiterator-ui
Browse files Browse the repository at this point in the history
Improve SimpleFormIterator UI
  • Loading branch information
slax57 authored Sep 2, 2022
2 parents 07ae128 + 714e784 commit 703db10
Show file tree
Hide file tree
Showing 23 changed files with 512 additions and 205 deletions.
5 changes: 4 additions & 1 deletion cypress/integration/create.js
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,10 @@ describe('Create Page', () => {
const backlinksContainer = cy
.get(CreatePage.elements.input('backlinks.0.date'))
.parents('.ra-input-backlinks');
backlinksContainer.contains('Remove').click();
// The button is visibility:hidden unless the user hovers on the row.
// It is not possible to simulate a CSS hover with cypress, so we use force: true
// see https://docs.cypress.io/api/commands/hover
backlinksContainer.get('.button-remove').click({ force: true });
CreatePage.setValues([
{
type: 'input',
Expand Down
8 changes: 4 additions & 4 deletions docs/ArrayInput.md
Original file line number Diff line number Diff line change
Expand Up @@ -57,10 +57,10 @@ const OrderEdit = () => (
<TextInput source="customer" />
<DateInput source="date" />
<ArrayInput source="items">
<SimpleFormIterator>
<TextInput source="name" />
<NumberInput source="price" />
<NumberInput source="quantity" />
<SimpleFormIterator inline>
<TextInput source="name" helperText={false} />
<NumberInput source="price" helperText={false} />
<NumberInput source="quantity" helperText={false} />
</SimpleFormIterator>
</ArrayInput>
</SimpleForm>
Expand Down
67 changes: 56 additions & 11 deletions docs/SimpleFormIterator.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ title: "SimpleFormIterator"

# `<SimpleFormIterator>`

This component provides a UI for editing arrays of objects, one row per object and one line per field.
This component provides a UI for editing arrays of objects, one row per object.

![ArrayInput](./img/array-input.gif)

Expand All @@ -26,6 +26,26 @@ import {
SimpleFormIterator
} from 'react-admin';

const OrderEdit = () => (
<Edit>
<SimpleForm>
<TextInput source="customer" />
<DateInput source="date" />
<ArrayInput source="items">
<SimpleFormIterator inline>
<TextInput source="name" helperText={false} />
<NumberInput source="price" helperText={false} />
<NumberInput source="quantity" helperText={false} />
</SimpleFormIterator>
</ArrayInput>
</SimpleForm>
</Edit>
);
```

In the example above, the inputs for each row appear inline, with no helper text. This dense layout is adapted to arrays with many items. If you need more room, omit the `inline` prop to use the default layout, where each input is displayed in a separate row.

```jsx
const OrderEdit = () => (
<Edit>
<SimpleForm>
Expand All @@ -43,6 +63,8 @@ const OrderEdit = () => (
);
```

![Simple form iterator block](./img/array-input-block.png)

## Props

| Prop | Required | Type | Default | Description |
Expand All @@ -53,6 +75,7 @@ const OrderEdit = () => (
| `disableAdd` | Optional | `boolean` | `false` | When true, the user cannot add new rows |
| `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 |
| `getItemLabel` | Optional | `function` | `x => x` | Callback to render the label displayed in each row |
| `inline` | Optional | `boolean` | `false` | When true, inputs are put on the same line |
| `removeButton` | Optional | `ReactElement` | - | Component to render for the remove button |
Expand Down Expand Up @@ -198,28 +221,38 @@ When true, the up and down buttons aren't rendered, so the user cannot reorder r
</SimpleFormIterator>
```

## `getItemLabel`
## `fullWidth`

Callback to render the label displayed in each row. `<SimpleFormIterator>` calls this function with the current row index as an argument.
When true, the row actions appear at the end of the row.

```jsx
<SimpleFormIterator getItemLabel={index => `item #${index}`}>
<SimpleFormIterator fullWidth>
<TextInput source="name" />
<NumberInput source="price" />
<NumberInput source="quantity" />
</SimpleFormIterator>
```

Use a function returning an empty string to disable the line labels:
![SimpleFormIterator full width](./img/simple-form-iterator-fullWidth.png)

This differs with the default behavior, where the row actions appear after the inputs.

![SimpleFormIterator default width](./img/simple-form-iterator-fullWidth-false.png)

## `getItemLabel`

`<SimpleFormIterator>` can add a label in front of each row, based on the row index. Set the `getItemLabel` prop with a callback to enable this feature.

```jsx
<SimpleFormIterator getItemLabel={() => ''}>
<SimpleFormIterator getItemLabel={index => `#${index + 1}`}>
<TextInput source="name" />
<NumberInput source="price" />
<NumberInput source="quantity" />
</SimpleFormIterator>
```

![SimpleFormIterator with iterm label](./img/array-input-item-label.png)

## `inline`

When true, inputs are put on the same line. Use this option to make the lines more compact, especially when the children are narrow inputs.
Expand All @@ -234,6 +267,18 @@ When true, inputs are put on the same line. Use this option to make the lines mo

![Inline form iterator](./img/simple-form-iterator-inline.png)

Without this prop, `<SimpleFormIterator>` will render one input per line.

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

![Not Inline form iterator](./img/simple-form-iterator-not-inline.png)

## `removeButton`

This prop lets you pass a custom element to replace the default Remove button.
Expand Down Expand Up @@ -293,16 +338,16 @@ const OrderEdit = () => (

## `sx`

You can override the style of the root element (a `<ul>` element) as well as those of the inner components thanks to the `sx` property. It relies on MUI System and supports CSS and shorthand properties (see [their documentation about it](https://mui.com/customization/how-to-customize/#overriding-nested-component-styles)).
You can override the style of the root element (a `<div>` element) as well as those of the inner components thanks to the `sx` property. It relies on MUI System and supports CSS and shorthand properties (see [their documentation about it](https://mui.com/customization/how-to-customize/#overriding-nested-component-styles)).

This property accepts the following subclasses:

| Rule name | Description |
|--------------------------|-----------------------------------------------------------|
| `RaSimpleFormIterator-action` | Applied to the action zone on each row (the one containing the Remove button) |
| `RaSimpleFormIterator-add` | Applied to the bottom line containing the Add button |
| `RaSimpleFormIterator-form` | Applied to the subform on each row |
| `RaSimpleFormIterator-index` | Applied to the index label |
| `RaSimpleFormIterator-indexContainer` | Applied to the container of the index label and reorder buttons |
| `RaSimpleFormIterator-index` | Applied to the row label when `getItemLabel` is set |
| `RaSimpleFormIterator-inline` | Applied to rows when `inline` is true |
| `RaSimpleFormIterator-leftIcon` | Applied to the left icon on each row |
| `RaSimpleFormIterator-line` | Applied to each row |
| `RaSimpleFormIterator-line` | Applied to each row |
| `RaSimpleFormIterator-list` | Applied to the `<ul>` element |
Binary file added docs/img/array-input-block.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/img/array-input-item-label.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified docs/img/array-input.gif
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/img/simple-form-iterator-fullWidth-false.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/img/simple-form-iterator-fullWidth.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified docs/img/simple-form-iterator-inline.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/img/simple-form-iterator-not-inline.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
5 changes: 3 additions & 2 deletions examples/simple/src/posts/PostEdit.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -148,12 +148,12 @@ const PostEdit = () => {
</ImageInput>
{permissions === 'admin' && (
<ArrayInput source="authors">
<SimpleFormIterator>
<SimpleFormIterator inline>
<ReferenceInput
source="user_id"
reference="users"
>
<AutocompleteInput />
<AutocompleteInput helperText={false} />
</ReferenceInput>
<FormDataConsumer>
{({
Expand All @@ -180,6 +180,7 @@ const PostEdit = () => {
name: 'Co-Writer',
},
]}
helperText={false}
{...rest}
/>
) : null
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,6 @@ export const IconButtonWithTooltip = ({
aria-label={translatedLabel}
onClick={handleClick}
{...props}
size="large"
/>
</Tooltip>
);
Expand Down
14 changes: 10 additions & 4 deletions packages/ra-ui-materialui/src/input/ArrayInput/AddItemButton.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,19 @@ import * as React from 'react';
import AddIcon from '@mui/icons-material/AddCircleOutline';
import { useSimpleFormIterator } from './useSimpleFormIterator';

import { Button, ButtonProps } from '../../button';
import { IconButtonWithTooltip, ButtonProps } from '../../button';

export const AddItemButton = (props: ButtonProps) => {
const { add } = useSimpleFormIterator();
return (
<Button label="ra.action.add" onClick={() => add()} {...props}>
<AddIcon />
</Button>
<IconButtonWithTooltip
label="ra.action.add"
size="small"
onClick={() => add()}
color="primary"
{...props}
>
<AddIcon fontSize="small" />
</IconButtonWithTooltip>
);
};
22 changes: 10 additions & 12 deletions packages/ra-ui-materialui/src/input/ArrayInput/ArrayInput.spec.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -162,12 +162,12 @@ describe('<ArrayInput />', () => {
</AdminContext>
);

fireEvent.click(screen.getByText('ra.action.add'));
fireEvent.click(screen.getByLabelText('ra.action.add'));
fireEvent.click(screen.getByText('ra.action.save'));
await waitFor(() => {
expect(screen.queryByText('array_min_length')).not.toBeNull();
});
fireEvent.click(screen.getByText('ra.action.add'));
fireEvent.click(screen.getByLabelText('ra.action.add'));
const firstId = screen.getAllByLabelText(
'resources.bar.fields.arr.id *'
)[0];
Expand Down Expand Up @@ -206,16 +206,14 @@ describe('<ArrayInput />', () => {

setArrayInputVisible = setVisible;

return (
visible && (
<ArrayInput resource="bar" source="arr">
<SimpleFormIterator>
<TextInput source="id" />
<TextInput source="foo" />
</SimpleFormIterator>
</ArrayInput>
)
);
return visible ? (
<ArrayInput resource="bar" source="arr">
<SimpleFormIterator>
<TextInput source="id" />
<TextInput source="foo" />
</SimpleFormIterator>
</ArrayInput>
) : null;
};

render(
Expand Down
Loading

0 comments on commit 703db10

Please sign in to comment.