Skip to content
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
1 change: 1 addition & 0 deletions docs/ArrayInput.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ To edit arrays of data embedded inside a record, `<ArrayInput>` creates a list o
}
```

**Tip**: If you need to edit an array of *strings*, like a list of email addresses or a list of tags, you should use a [`<TextArrayInput>`](./TextArrayInput.md) instead.

`<ArrayInput>` expects a single child, which must be a *form iterator* component. A form iterator is a component rendering a field array (the object returned by react-hook-form's [`useFieldArray`](https://react-hook-form.com/docs/usefieldarray)). For instance, [the `<SimpleFormIterator>` component](./SimpleFormIterator.md) displays an array of react-admin Inputs in an unordered list (`<ul>`), one sub-form by list item (`<li>`). It also provides controls for adding and removing a sub-record.

Expand Down
2 changes: 1 addition & 1 deletion docs/AutocompleteArrayInput.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,11 @@ It renders using Material UI [Autocomplete](https://mui.com/material-ui/react-au
Your browser does not support the video tag.
</video>


This input allows editing values that are arrays of scalar values, e.g. `[123, 456]`.

**Tip**: React-admin includes other components allowing the edition of such values:

- [`<TextArrayInput>`](./TextArrayInput.md) lets you edit an array of strings
- [`<SelectArrayInput>`](./SelectArrayInput.md) renders a dropdown list of choices
- [`<CheckboxGroupInput>`](./CheckboxGroupInput.md) renders a list of checkbox options
- [`<DualListInput>`](./DualListInput.md) renders a list of choices that can be moved from one list to another
Expand Down
1 change: 0 additions & 1 deletion docs/AutocompleteInput.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@ It renders using [Material UI's `<Autocomplete>`](https://mui.com/material-ui/re
Your browser does not support the video tag.
</video>


This input allows editing record fields that are scalar values, e.g. `123`, `'admin'`, etc.

## Usage
Expand Down
1 change: 1 addition & 0 deletions docs/CheckboxGroupInput.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ This input allows editing values that are arrays of scalar values, e.g. `[123, 4

**Tip**: React-admin includes other components allowing the edition of such values:

- [`<TextArrayInput>`](./TextArrayInput.md) lets you edit an array of strings
- [`<SelectArrayInput>`](./SelectArrayInput.md) renders a dropdown list of choices
- [`<AutocompleteArrayInput>`](./AutocompleteArrayInput.md) renders an autocomplete input of choices
- [`<DualListInput>`](./DualListInput.md) renders a list of choices that can be moved from one list to another
Expand Down
1 change: 1 addition & 0 deletions docs/DualListInput.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ This input allows editing values that are arrays of scalar values, e.g. `[123, 4

**Tip**: React-admin includes other components allowing the edition of such values:

- [`<TextArrayInput>`](./TextArrayInput.md) lets you edit an array of strings
- [`<AutocompleteArrayInput>`](./AutocompleteArrayInput.md) renders an Autocomplete
- [`<SelectArrayInput>`](./SelectArrayInput.md) renders a dropdown list of choices
- [`<CheckboxGroupInput>`](./CheckboxGroupInput.md) renders a list of checkbox options
Expand Down
2 changes: 1 addition & 1 deletion docs/Inputs.md
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ React-admin provides a set of Input components, each one designed for a specific
| Tree node | `42` | [`<TreeInput>`](./TreeInput.md) |
| Foreign key | `42` | [`<ReferenceInput>`](./ReferenceInput.md) |
| Array of objects | `[{ item: 'jeans', qty: 3 }, { item: 'shirt', qty: 1 }]` | [`<ArrayInput>`](./ArrayInput.md) |
| Array of Enums | `['foo', 'bar']` | [`<SelectArrayInput>`](./SelectArrayInput.md), [`<AutocompleteArrayInput>`](./AutocompleteArrayInput.md), [`<CheckboxGroupInput>`](./CheckboxGroupInput.md), [`<DualListInput>`](./DualListInput.md) |
| Array of Enums | `['foo', 'bar']` | [`<TextArrayInput>`](./TextArrayinput.md), [`<SelectArrayInput>`](./SelectArrayInput.md), [`<AutocompleteArrayInput>`](./AutocompleteArrayInput.md), [`<CheckboxGroupInput>`](./CheckboxGroupInput.md), [`<DualListInput>`](./DualListInput.md) |
| Array of foreign keys | `[42, 43]` | [`<ReferenceArrayInput>`](./ReferenceArrayInput.md) |
| Translations | `{ en: 'Hello', fr: 'Bonjour' }` | [`<TranslatableInputs>`](./TranslatableInputs.md) |
| Related records | `[{ id: 42, title: 'Hello' }, { id: 43, title: 'World' }]` | [`<ReferenceManyInput>`](./ReferenceManyInput.md), [`<ReferenceManyToManyInput>`](./ReferenceManyToManyInput.md), [`<ReferenceNodeInput>`](./ReferenceNodeInput.md), [`<ReferenceOneInput>`](./ReferenceOneInput.md) |
Expand Down
1 change: 1 addition & 0 deletions docs/Reference.md
Original file line number Diff line number Diff line change
Expand Up @@ -188,6 +188,7 @@ title: "Index"
* [`<TabbedForm>`](./TabbedForm.md)
* [`<TabbedFormWithRevision>`](./TabbedForm.md#versioning)<img class="icon" src="./img/premium.svg" />
* [`<TabbedShowLayout>`](./TabbedShowLayout.md)
* [`<TextArrayInput>`](./TextArrayInput.md)
* [`<TextField>`](./TextField.md)
* [`<TextInput>`](./TextInput.md)
* [`<TimeInput>`](./TimeInput.md)
Expand Down
1 change: 1 addition & 0 deletions docs/SelectArrayInput.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ This input allows editing values that are arrays of scalar values, e.g. `[123, 4

**Tip**: React-admin includes other components allowing the edition of such values:

- [`<TextArrayInput>`](./TextArrayInput.md) lets you edit an array of strings
- [`<AutocompleteArrayInput>`](./AutocompleteArrayInput.md) renders an Autocomplete
- [`<CheckboxGroupInput>`](./CheckboxGroupInput.md) renders a list of checkbox options
- [`<DualListInput>`](./DualListInput.md) renders a list of choices that can be moved from one list to another
Expand Down
110 changes: 110 additions & 0 deletions docs/TextArrayInput.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
---
layout: default
title: "The TextArrayInput Component"
---

# `<TextArrayInput>`

`<TextArrayInput>` lets you edit an array of strings, like a list of email addresses or a list of tags. It renders as an input where the current values are represented as chips. Users can add or delete new values.

<video controls autoplay playsinline muted loop>
<source src="./img/TextArrayInput.mp4" type="video/mp4"/>
Your browser does not support the video tag.
</video>


## Usage

Use `<TextArrayInput>` to edit an array of strings:

```jsx
import { Create, SimpleForm, TextArrayInput, TextInput } from 'react-admin';

export const EmailCreate = () => (
<Create>
<SimpleForm>
<TextArrayInput source="to" />
<TextInput source="subject" />
<TextInput source="body" multiline minRows={5} />
</SimpleForm>
</Create>
);
```

This form will allow users to input multiple email addresses in the `to` field. The resulting email will look like this:

```jsx
{
"to": ["[email protected]", "[email protected]"],
"subject": "Request for a quote",
"body": "Hi,\n\nI would like to know if you can provide a quote for the following items:\n\n- 100 units of product A\n- 50 units of product B\n- 25 units of product C\n\nBest regards,\n\nJulie\n",
"id": 123,
"date": "2024-11-26T11:37:22.564Z",
"from": "[email protected]",
}
```

`<TextArrayInput>` is designed for simple string arrays. For more complex use cases, consider the following alternatives:

- [`<SelectArrayInput>`](./SelectArrayInput.md) or [`<AutocompleteArrayInput>`](./AutocompleteArrayInput.md) if the possible values are limited to a predefined list.
- [`<ReferenceArrayInput>`](./ReferenceArrayInput.md) if the possible values are stored in another resource.
- [`<ArrayInput>`](./ArrayInput.md) if the stored value is an array of *objects* instead of an array of strings.

## Props

| Prop | Required | Type | Default | Description |
| ------------ | -------- | --------- | ------- | -------------------------------------------------------------------- |
| `options` | Optional | `string[]` | | Optional list of possible values for the input. If provided, the input will suggest these values as the user types. |
| `renderTags` | Optional | `(value, getTagProps) => ReactNode` | | A function to render selected value. |

`<TextArrayInput>` also accepts the [common input props](./Inputs.md#common-input-props).

Additional props are passed down to the underlying Material UI [`<Autocomplete>`](https://mui.com/material-ui/react-autocomplete/) component.

## `options`

You can make show a list of suggestions to the user by setting the `options` prop:

```jsx
<TextArrayInput
source="to"
options={[
'[email protected]',
'[email protected]',
'[email protected]',
'[email protected]',
'[email protected]',
'[email protected]',
'[email protected]',
'[email protected]',
'[email protected]',
'[email protected]',
]}
/>
```

## `renderTags`

To customize the rendering of the chips, use the `renderTags` prop. This prop is a function that takes two arguments:

- `value`: The input value (an array of strings)
- `getTagProps`: A props getter for an individual tag.

```tsx
<TextArrayInput
source="to"
renderTags={(value: readonly string[], getTagProps) =>
value.map((option: string, index: number) => {
const { key, ...tagProps } = getTagProps({ index });
return (
<Chip
variant="outlined"
label={option}
key={key}
{...tagProps}
/>
);
})
}
/>
```
Binary file added docs/img/TextArrayInput.mp4
Binary file not shown.
1 change: 1 addition & 0 deletions docs/navigation.html
Original file line number Diff line number Diff line change
Expand Up @@ -212,6 +212,7 @@
<li {% if page.path == 'SelectArrayInput.md' %} class="active beginner" {% else %} class="beginner" {% endif %}><a class="nav-link" href="./SelectArrayInput.html"><code>&lt;SelectArrayInput&gt;</code></a></li>
<li {% if page.path == 'SimpleFormIterator.md' %} class="active beginner" {% else %} class="beginner" {% endif %}><a class="nav-link" href="./SimpleFormIterator.html"><code>&lt;SimpleFormIterator&gt;</code></a></li>
<li {% if page.path == 'SmartRichTextInput.md' %} class="active" {% endif %}><a class="nav-link" href="./SmartRichTextInput.html"><code>&lt;SmartRichTextInput&gt;</code><img class="premium" src="./img/premium.svg" /></a></li>
<li {% if page.path == 'TextArrayInput.md' %} class="active beginner" {% else %} class="beginner" {% endif %}><a class="nav-link" href="./TextArrayInput.html"><code>&lt;TextArrayInput&gt;</code></a></li>
<li {% if page.path == 'TextInput.md' %} class="active beginner" {% else %} class="beginner" {% endif %}><a class="nav-link" href="./TextInput.html"><code>&lt;TextInput&gt;</code></a></li>
<li {% if page.path == 'TimeInput.md' %} class="active" {% endif %}><a class="nav-link" href="./TimeInput.html"><code>&lt;TimeInput&gt;</code></a></li>
<li {% if page.path == 'TranslatableInputs.md' %} class="active" {% endif %}><a class="nav-link" href="./TranslatableInputs.html"><code>&lt;TranslatableInputs&gt;</code></a></li>
Expand Down
62 changes: 62 additions & 0 deletions packages/ra-ui-materialui/src/input/TextArrayInput.spec.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
import * as React from 'react';
import { render, fireEvent, screen } from '@testing-library/react';

import { Basic, HelperText, Label, Required } from './TextArrayInput.stories';

describe('<TextArrayInput />', () => {
it('should render the values as chips', () => {
render(<Basic />);
const chip1 = screen.getByText('[email protected]');
expect(chip1.classList.contains('MuiChip-label')).toBe(true);
const chip2 = screen.getByText('[email protected]');
expect(chip2.classList.contains('MuiChip-label')).toBe(true);
});
it('should allow to remove a value', async () => {
render(<Basic />);
await screen.findByText(
'["[email protected]","[email protected]"] (object)'
);
const deleteButtons = screen.getAllByTestId('CancelIcon');
fireEvent.click(deleteButtons[0]);
await screen.findByText('["[email protected]"] (object)');
});
it('should allow to remove all values one by one', async () => {
render(<Basic />);
await screen.findByText(
'["[email protected]","[email protected]"] (object)'
);
const deleteButtons = screen.getAllByTestId('CancelIcon');
fireEvent.click(deleteButtons[1]);
fireEvent.click(deleteButtons[0]);
await screen.findByText('[] (object)');
});
it('should allow to remove all values using the reset button', async () => {
render(<Basic />);
const input = screen.getByLabelText('resources.emails.fields.to');
fireEvent.click(input);
const clearButton = screen.getByLabelText('Clear');
fireEvent.click(clearButton);
await screen.findByText('[] (object)');
});
it('should allow to add a value', async () => {
render(<Basic />);
const input = screen.getByLabelText('resources.emails.fields.to');
fireEvent.change(input, { target: { value: '[email protected]' } });
fireEvent.keyDown(input, { key: 'Enter' });
await screen.findByText(
'["[email protected]","[email protected]","[email protected]"] (object)'
);
});
it('should render the helper text', () => {
render(<HelperText />);
screen.getByText('Email addresses of the recipients');
});
it('should render the custom label', () => {
render(<Label />);
screen.getByText('To');
});
it('should show required fields as required', () => {
render(<Required />);
expect(screen.getAllByText('*').length).toBe(2);
});
});
Loading
Loading