From c6fe23fb68c77c7071f211108f6fe6d4d34c29a2 Mon Sep 17 00:00:00 2001
From: Gildas Garcia <1122076+djhi@users.noreply.github.com>
Date: Tue, 13 Jul 2021 15:48:49 +0200
Subject: [PATCH 1/3] Add support for reordering items in SimpleFormIterator
---
docs/Inputs.md | 1119 +++++++++++------
.../ra-core/src/i18n/TranslationMessages.ts | 2 +
packages/ra-language-english/src/index.ts | 2 +
packages/ra-language-french/src/index.ts | 2 +
.../src/button/IconButtonWithTooltip.tsx | 51 +
packages/ra-ui-materialui/src/button/index.ts | 2 +
.../src/detail/editFieldTypes.tsx | 2 +-
.../src/form/SimpleFormIterator.spec.tsx | 137 +-
.../src/form/SimpleFormIterator.tsx | 270 ++--
packages/ra-ui-materialui/src/form/index.tsx | 6 +-
.../src/input/ArrayInput.spec.tsx | 2 +-
.../src/input/DateTimeInput.spec.tsx | 2 +-
12 files changed, 1047 insertions(+), 550 deletions(-)
create mode 100644 packages/ra-ui-materialui/src/button/IconButtonWithTooltip.tsx
diff --git a/docs/Inputs.md b/docs/Inputs.md
index e992937b7c5..15511bd757b 100644
--- a/docs/Inputs.md
+++ b/docs/Inputs.md
@@ -1,6 +1,6 @@
---
layout: default
-title: "Input Components"
+title: 'Input Components'
---
# Input Components
@@ -9,17 +9,33 @@ An `Input` component displays an input, or a dropdown list, a list of radio butt
```jsx
// in src/posts.js
-import * as React from "react";
-import { Edit, SimpleForm, ReferenceInput, SelectInput, TextInput, required } from 'react-admin';
+import * as React from 'react';
+import {
+ Edit,
+ SimpleForm,
+ ReferenceInput,
+ SelectInput,
+ TextInput,
+ required,
+} from 'react-admin';
-export const PostEdit = (props) => (
+export const PostEdit = props => (
} {...props}>
-
+
-
+
@@ -30,15 +46,15 @@ export const PostEdit = (props) => (
All input components accept the following props:
-| Prop | Required | Type | Default | Description |
-| --------------- | -------- | ------------------------- | ------- | ---------------------------------------------------------------------------------------------------------------------------- |
-| `source` | Required | `string` | - | Name of the entity property to use for the input value |
+| Prop | Required | Type | Default | Description |
+| --------------- | -------- | ------------------------- | ------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
+| `source` | Required | `string` | - | Name of the entity property to use for the input value |
| `label` | Optional | `string` | - | Input label. In i18n apps, the label is passed to the `translate` function. Defaults to the humanized `source` when omitted. Set `label={false}` to hide the label. |
-| `validate` | Optional | `Function` | `array` | - | Validation rules for the current property. See the [Validation Documentation](./CreateEdit.md#validation) for details. |
-| `helperText` | Optional | `string` | - | Text to be displayed under the input |
-| `fullWidth` | Optional | `boolean` | `false` | If `true`, the input will expand to fill the form width |
-| `className` | Optional | `string` | - | Class name (usually generated by JSS) to customize the look and feel of the field element itself |
-| `formClassName` | Optional | `string` | - | Class name to be applied to the container of the input (e.g. the `
` forming each row in `
`) |
+| `validate` | Optional | `Function` | `array` | - | Validation rules for the current property. See the [Validation Documentation](./CreateEdit.md#validation) for details. |
+| `helperText` | Optional | `string` | - | Text to be displayed under the input |
+| `fullWidth` | Optional | `boolean` | `false` | If `true`, the input will expand to fill the form width |
+| `className` | Optional | `string` | - | Class name (usually generated by JSS) to customize the look and feel of the field element itself |
+| `formClassName` | Optional | `string` | - | Class name to be applied to the container of the input (e.g. the `` forming each row in `
`) |
```jsx
@@ -86,7 +102,7 @@ Then you can display a text input to edit the author first name as follows:
```jsx
import { BooleanInput } from 'react-admin';
-
+ ;
```
![BooleanInput](./img/boolean-input.png)
@@ -96,6 +112,7 @@ This input does not handle `null` values. You would need the ` ,
}}
-/>
+/>;
```
+
{% endraw %}
![CustomBooleanInputCheckIcon](./img/custom-switch-icon.png)
@@ -118,7 +136,7 @@ Refer to [Material UI Switch documentation](https://material-ui.com/api/switch)
```jsx
import { NullableBooleanInput } from 'react-admin';
-
+ ;
```
![NullableBooleanInput](./img/nullable-boolean-input.gif)
@@ -134,7 +152,7 @@ englishMessages.ra.boolean.false = 'False label';
englishMessages.ra.boolean.true = 'True label';
const i18nProvider = polyglotI18nProvider(() => englishMessages, 'en');
-
+ ;
```
Additionally, individual instances of `NullableBooleanInput` may be customized by setting the `nullLabel`, `falseLabel` and `trueLabel` properties. Values specified for those properties will be translated by react-admin.
@@ -148,7 +166,7 @@ import { NullableBooleanInput } from 'react-admin';
nullLabel="Either"
falseLabel="No"
trueLabel="Yes"
-/>
+/>;
```
![NullableBooleanInput](./img/nullable-boolean-input-null-label.png)
@@ -174,7 +192,7 @@ The appearance of `` depends on the browser, and falls back to a text
```jsx
import { DateInput } from 'react-admin';
-
+ ;
```
`` also accepts the [common input props](./Inputs.md#common-input-props).
@@ -192,7 +210,7 @@ An input for editing dates with time. `` renders a standard brows
```jsx
import { DateTimeInput } from 'react-admin';
-
+ ;
```
`` also accepts the [common input props](./Inputs.md#common-input-props).
@@ -207,17 +225,17 @@ import { DateTimeInput } from 'react-admin';
#### Properties
-| Prop | Required | Type | Default | Description |
-| --------------- | -------- | --------------------------- | -------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
-| `accept` | Optional | `string | string[]` | - | Accepted file type(s), e. g. 'image/*,.pdf'. If left empty, all file types are accepted. Equivalent of the `accept` attribute of an ` `. See [MDN input docs](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input/file#accept) for syntax and examples. |
-| `children` | Optional | `ReactNode` | - | Element used to display the preview of an image (cloned several times if the select accepts multiple files). |
-| `minSize` | Optional | `number` | 0 | Minimum image size (in bytes), e.g. 5000 for 5KB |
-| `maxSize` | Optional | `number` | `Infinity` | Maximum image size (in bytes), e.g. 5000000 for 5MB |
-| `multiple` | Optional | `boolean` | `false` | Set to true if the input should accept a list of images, false if it should only accept one image |
-| `labelSingle` | Optional | `string` | 'ra.input.image. upload_single' | Invite displayed in the drop zone if the input accepts one image |
-| `labelMultiple` | Optional | `string` | 'ra.input.file. upload_multiple' | Invite displayed in the drop zone if the input accepts several images |
-| `placeholder` | Optional | `string` | `ReactNode` | - | Invite displayed in the drop zone, overrides `labelSingle` and `labelMultiple` |
-| `options` | Optional | `Object` | `{}` | Additional options passed to react-dropzone's `useDropzone()` hook. See [the react-dropzone source](https://github.com/react-dropzone/react-dropzone/blob/master/src/index.js) for details . |
+| Prop | Required | Type | Default | Description |
+| --------------- | -------- | --------------------------- | -------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
+| `accept` | Optional | `string | string[]` | - | Accepted file type(s), e. g. 'image/\*,.pdf'. If left empty, all file types are accepted. Equivalent of the `accept` attribute of an ` `. See [MDN input docs](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input/file#accept) for syntax and examples. |
+| `children` | Optional | `ReactNode` | - | Element used to display the preview of an image (cloned several times if the select accepts multiple files). |
+| `minSize` | Optional | `number` | 0 | Minimum image size (in bytes), e.g. 5000 for 5KB |
+| `maxSize` | Optional | `number` | `Infinity` | Maximum image size (in bytes), e.g. 5000000 for 5MB |
+| `multiple` | Optional | `boolean` | `false` | Set to true if the input should accept a list of images, false if it should only accept one image |
+| `labelSingle` | Optional | `string` | 'ra.input.image. upload_single' | Invite displayed in the drop zone if the input accepts one image |
+| `labelMultiple` | Optional | `string` | 'ra.input.file. upload_multiple' | Invite displayed in the drop zone if the input accepts several images |
+| `placeholder` | Optional | `string` | `ReactNode` | - | Invite displayed in the drop zone, overrides `labelSingle` and `labelMultiple` |
+| `options` | Optional | `Object` | `{}` | Additional options passed to react-dropzone's `useDropzone()` hook. See [the react-dropzone source](https://github.com/react-dropzone/react-dropzone/blob/master/src/index.js) for details . |
`` also accepts the [common input props](./Inputs.md#common-input-props).
@@ -242,7 +260,12 @@ The `ImageInput` component accepts an `options` prop, allowing to set the [react
If the default Dropzone label doesn't fit with your need, you can pass a `placeholder` prop to overwrite it. The value can be anything React can render (`PropTypes.node`):
```jsx
-Drop your file here}>
+Drop your file here}
+>
```
@@ -298,12 +321,17 @@ Writing a custom preview component is quite straightforward: it's a standard [fi
When receiving **new** files, `FileInput` will add a `rawFile` property to the object passed as the `record` prop of children. This `rawFile` is the [File](https://developer.mozilla.org/en-US/docs/Web/API/File) instance of the newly added file. This can be useful to display information about size or MIME type inside a custom field.
-The `FileInput` component accepts an `options` prop into which you can pass all the [react-dropzone properties](https://react-dropzone.netlify.com/#proptypes).
+The `FileInput` component accepts an `options` prop into which you can pass all the [react-dropzone properties](https://react-dropzone.netlify.com/#proptypes).
If the default Dropzone label doesn't fit with your need, you can pass a `placeholder` prop to overwrite it. The value can be anything React can render (`PropTypes.node`):
```jsx
-Drop your file here}>
+Drop your file here}
+>
```
@@ -354,7 +382,7 @@ It is necessary for numeric values because of a [known React bug](https://github
```jsx
import { NumberInput } from 'react-admin';
-
+ ;
```
#### Properties
@@ -381,7 +409,7 @@ You can customize the `step` props (which defaults to "any"). For instance, to r
```jsx
import { PasswordInput } from 'react-admin';
-
+ ;
```
![Password Input](./img/password-input.png)
@@ -390,7 +418,7 @@ It is possible to change the default behavior and display the value by default v
```jsx
import { PasswordInput } from 'react-admin';
-
+ ;
```
![Password Input (visible)](./img/password-input-visible.png)
@@ -398,9 +426,14 @@ import { PasswordInput } from 'react-admin';
**Tip**: It is possible to set the [`autocomplete` attribute](https://developer.mozilla.org/fr/docs/Web/HTML/Attributs/autocomplete) by injecting an input props:
{% raw %}
+
```jsx
-
+
```
+
{% endraw %}
### ``
@@ -421,50 +454,57 @@ Then use it as a normal input component:
```jsx
import RichTextInput from 'ra-input-rich-text';
-
+ ;
```
You can customize the rich text editor toolbar using the `toolbar` attribute, as described on the [Quill official toolbar documentation](https://quilljs.com/docs/modules/toolbar/).
```jsx
-
+
```
If you need to add Quill `modules` or `themes`, you can do so by passing them in the `options` prop.
{% raw %}
+
```jsx
```
+
{% endraw %}
If you need more customization, you can access the quill object through the `configureQuill` callback that will be called just after its initialization.
```jsx
-const configureQuill = quill => quill.getModule('toolbar').addHandler('bold', function (value) {
- this.quill.format('bold', value)
-});
+const configureQuill = quill =>
+ quill.getModule('toolbar').addHandler('bold', function (value) {
+ this.quill.format('bold', value);
+ });
// ...
-
+ ;
```
`` also accepts the [common input props](./Inputs.md#common-input-props).
-**Tip**: When used inside a material-ui `` (e.g in the default `` view), `` displays link tooltip as cut off when the user wants to add a hyperlink to a word located on the left side of the input. This is due to an incompatibility between material-ui's `` component and Quill's positioning algorithm for the link tooltip.
+**Tip**: When used inside a material-ui `` (e.g in the default `` view), `` displays link tooltip as cut off when the user wants to add a hyperlink to a word located on the left side of the input. This is due to an incompatibility between material-ui's `` component and Quill's positioning algorithm for the link tooltip.
To fix this problem, you should override the default card style, as follows:
@@ -492,7 +532,7 @@ import { Edit, SimpleForm, TextInput } from 'react-admin';
```jsx
import { TextInput } from 'react-admin';
-
+ ;
```
#### Properties
@@ -524,7 +564,7 @@ You can make the `` component resettable using the `resettable` prop.
```jsx
import { TextInput } from 'react-admin';
-
+ ;
```
![resettable TextInput](./img/resettable-text-input.gif)
@@ -545,34 +585,37 @@ Set the `choices` attribute to determine the options list (with `id`, `name` tup
```jsx
import { AutocompleteInput } from 'react-admin';
-
+ ;
```
#### Properties
-| Prop | Required | Type | Default | Description |
-| ------------------------- | -------- | -------------- | ------------ | ------------------------------------ |
-| `allowEmpty` | Optional | `boolean` | `false` | If `false` and the `searchText` typed did not match any suggestion, the `searchText` will revert to the current value when the field is blurred. If `true` and the `searchText` is set to `''` then the field will set the input value to `null`. |
-| `clearAlwaysVisible` | Optional | `boolean` | `false` | When `resettable` is true, set this prop to `true` to have the Reset button visible even when the field is empty |
-| `choices` | Required | `Object[]` | `-` | List of items to autosuggest |
-| `create` | Optional | `Element` | `-` | A React Element to render when users want to create a new choice |
-| `createLabel` | Optional | `string` | `ra.action.create` | The label for the menu item allowing users to create a new choice. Used when the filter is empty |
-| `createItemLabel` | Optional | `string` | `ra.action.create_item` | The label for the menu item allowing users to create a new choice. Used when the filter is not empty |
-| `emptyValue` | Optional | `any` | `''` | The value to use for the empty element |
-| `emptyText` | Optional | `string` | `''` | The text to use for the empty element |
-| `matchSuggestion` | Optional | `Function` | `-` | Required if `optionText` is a React element. Function returning a boolean indicating whether a choice matches the filter. `(filter, choice) => boolean` |
-| `onCreate` | Optional | `Function` | `-` | A function called with the current filter value when users choose to create a new choice. |
-| `optionText` | Optional | `string` | `Function` | `Component` | `name` | Field name of record to display in the suggestion item or function which accepts the correct record as argument (`(record)=> {string}`) |
-| `optionValue` | Optional | `string` | `id` | Field name of record containing the value to use as input value |
-| `inputText` | Optional | `Function` | `-` | If `optionText` is a custom Component, this function is needed to determine the text displayed for the current selection. |
-| `resettable` | Optional | `boolean` | `false` | Display a button to reset the text filter. Useful when using `` inside the list filters |
-| `setFilter` | Optional | `Function` | `null` | A callback to inform the `searchText` has changed and new `choices` can be retrieved based on this `searchText`. Signature `searchText => void`. This function is automatically setup when using `ReferenceInput`. |
-| `shouldRenderSuggestions` | Optional | `Function` | `() => true` | A function that returns a `boolean` to determine whether or not suggestions are rendered. Use this when working with large collections of data to improve performance and user experience. This function is passed into the underlying react-autosuggest component. Ex.`(value) => value.trim() > 2` |
-| `suggestionLimit` | Optional | `number` | `null` | Limits the numbers of suggestions that are shown in the dropdown list |
+| Prop | Required | Type | Default | Description |
+| ------------------------- | -------- | --------------------------------------------- | ----------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
+| `allowEmpty` | Optional | `boolean` | `false` | If `false` and the `searchText` typed did not match any suggestion, the `searchText` will revert to the current value when the field is blurred. If `true` and the `searchText` is set to `''` then the field will set the input value to `null`. |
+| `clearAlwaysVisible` | Optional | `boolean` | `false` | When `resettable` is true, set this prop to `true` to have the Reset button visible even when the field is empty |
+| `choices` | Required | `Object[]` | `-` | List of items to autosuggest |
+| `create` | Optional | `Element` | `-` | A React Element to render when users want to create a new choice |
+| `createLabel` | Optional | `string` | `ra.action.create` | The label for the menu item allowing users to create a new choice. Used when the filter is empty |
+| `createItemLabel` | Optional | `string` | `ra.action.create_item` | The label for the menu item allowing users to create a new choice. Used when the filter is not empty |
+| `emptyValue` | Optional | `any` | `''` | The value to use for the empty element |
+| `emptyText` | Optional | `string` | `''` | The text to use for the empty element |
+| `matchSuggestion` | Optional | `Function` | `-` | Required if `optionText` is a React element. Function returning a boolean indicating whether a choice matches the filter. `(filter, choice) => boolean` |
+| `onCreate` | Optional | `Function` | `-` | A function called with the current filter value when users choose to create a new choice. |
+| `optionText` | Optional | `string` | `Function` | `Component` | `name` | Field name of record to display in the suggestion item or function which accepts the correct record as argument (`(record)=> {string}`) |
+| `optionValue` | Optional | `string` | `id` | Field name of record containing the value to use as input value |
+| `inputText` | Optional | `Function` | `-` | If `optionText` is a custom Component, this function is needed to determine the text displayed for the current selection. |
+| `resettable` | Optional | `boolean` | `false` | Display a button to reset the text filter. Useful when using `` inside the list filters |
+| `setFilter` | Optional | `Function` | `null` | A callback to inform the `searchText` has changed and new `choices` can be retrieved based on this `searchText`. Signature `searchText => void`. This function is automatically setup when using `ReferenceInput`. |
+| `shouldRenderSuggestions` | Optional | `Function` | `() => true` | A function that returns a `boolean` to determine whether or not suggestions are rendered. Use this when working with large collections of data to improve performance and user experience. This function is passed into the underlying react-autosuggest component. Ex.`(value) => value.trim() > 2` |
+| `suggestionLimit` | Optional | `number` | `null` | Limits the numbers of suggestions that are shown in the dropdown list |
`` also accepts the [common input props](./Inputs.md#common-input-props).
@@ -585,26 +628,35 @@ const choices = [
{ _id: 123, full_name: 'Leo Tolstoi', sex: 'M' },
{ _id: 456, full_name: 'Jane Austen', sex: 'F' },
];
-
+ ;
```
`optionText` also accepts a function, so you can shape the option text at will:
```jsx
const choices = [
- { id: 123, first_name: 'Leo', last_name: 'Tolstoi' },
- { id: 456, first_name: 'Jane', last_name: 'Austen' },
+ { id: 123, first_name: 'Leo', last_name: 'Tolstoi' },
+ { id: 456, first_name: 'Jane', last_name: 'Austen' },
];
const optionRenderer = choice => `${choice.first_name} ${choice.last_name}`;
-
+ ;
```
`optionText` also accepts a custom Component. However, as the underlying Autocomplete component requires that the current selection is a string, if you opt for a Component, you must pass a function as the `inputText` prop. This function should return text representation of the current selection:
```jsx
const choices = [
- { id: 123, first_name: 'Leo', last_name: 'Tolstoi', avatar:'/pengouin' },
- { id: 456, first_name: 'Jane', last_name: 'Austen', avatar:'/panda' },
+ { id: 123, first_name: 'Leo', last_name: 'Tolstoi', avatar: '/pengouin' },
+ { id: 456, first_name: 'Jane', last_name: 'Austen', avatar: '/panda' },
];
const OptionRenderer = choice => (
@@ -618,15 +670,15 @@ const inputText = choice => `${choice.first_name} ${choice.last_name}`;
choices={choices}
optionText={ }
inputText={inputText}
-/>
+/>;
```
The choices are translated by default, so you can use translation identifiers as choices:
```jsx
const choices = [
- { id: 'M', name: 'myroot.gender.male' },
- { id: 'F', name: 'myroot.gender.female' },
+ { id: 'M', name: 'myroot.gender.male' },
+ { id: 'F', name: 'myroot.gender.female' },
];
```
@@ -634,7 +686,7 @@ However, in some cases (e.g. inside a ``), you may not want the
In that case, set the `translateChoice` prop to `false`.
```jsx
-
+
```
If you want to limit the initial choices shown to the current value only, you can set the `limitChoicesToValue` prop.
@@ -645,11 +697,16 @@ Ex. ` { return val.trim().le
`` renders a [material-ui `` component](https://material-ui.com/api/text-field/). Use the `options` attribute to override any of the `` attributes:
{% raw %}
+
```jsx
-
+
```
+
{% endraw %}
**Tip**: If you want to populate the `choices` attribute with a list of related records, you should decorate `` with [``](#referenceinput), and leave the `choices` empty:
@@ -659,21 +716,27 @@ import { AutocompleteInput, ReferenceInput } from 'react-admin';
-
+ ;
```
Lastly, would you need to override the props of the suggestion's container (a `Popper` element), you can specify them using the `options.suggestionsContainerProps`. For example:
{% raw %}
+
```jsx
-
+
```
+
{% endraw %}
-**Tip**: `` is a stateless component, so it only allows to *filter* the list of choices, not to *extend* it. If you need to populate the list of choices based on the result from a `fetch` call (and if [``](#referenceinput) doesn't cover your need), you'll have to [write your own Input component](#writing-your-own-input-component) based on material-ui `` component.
+**Tip**: `` is a stateless component, so it only allows to _filter_ the list of choices, not to _extend_ it. If you need to populate the list of choices based on the result from a `fetch` call (and if [``](#referenceinput) doesn't cover your need), you'll have to [write your own Input component](#writing-your-own-input-component) based on material-ui `` component.
#### Creating New Choices
@@ -682,10 +745,11 @@ The `` can allow users to create a new choice if either the `
Use the `onCreate` prop when you only require users to provide a simple string and a `prompt` is enough. You can return either the new choice directly or a Promise resolving to the new choice.
{% raw %}
+
```js
import { AutocompleteInput, Create, SimpleForm, TextInput } from 'react-admin';
-const PostCreate = (props) => {
+const PostCreate = props => {
const categories = [
{ name: 'Tech', id: 'tech' },
{ name: 'Lifestyle', id: 'lifestyle' },
@@ -697,7 +761,10 @@ const PostCreate = (props) => {
{
const newCategoryName = prompt('Enter a new category');
- const newCategory = { id: newCategoryName.toLowerCase(), name: newCategoryName };
+ const newCategory = {
+ id: newCategoryName.toLowerCase(),
+ name: newCategoryName,
+ };
categories.push(newCategory);
return newCategory;
}}
@@ -707,13 +774,15 @@ const PostCreate = (props) => {
);
-}
+};
```
+
{% endraw %}
Use the `create` prop when you want a more polished or complex UI. For example a Material UI `
` asking for multiple fields because the choices are from a referenced resource.
{% raw %}
+
```js
import {
AutocompleteInput,
@@ -721,7 +790,7 @@ import {
ReferenceInput,
SimpleForm,
TextInput,
- useCreateSuggestionContext
+ useCreateSuggestionContext,
} from 'react-admin';
import {
@@ -734,7 +803,7 @@ import {
TextField,
} from '@material-ui/core';
-const PostCreate = (props) => {
+const PostCreate = props => {
return (
@@ -745,14 +814,14 @@ const PostCreate = (props) => {
);
-}
+};
const CreateCategory = () => {
const { filter, onCancel, onCreate } = useCreateSuggestionContext();
const [value, setValue] = React.useState(filter || '');
const [create] = useCreate('categories');
- const handleSubmit = (event) => {
+ const handleSubmit = event => {
event.preventDefault();
create(
{
@@ -791,6 +860,7 @@ const CreateCategory = () => {
);
};
```
+
{% endraw %}
#### CSS API
@@ -814,11 +884,14 @@ Set the `choices` attribute to determine the options (with `id`, `name` tuples):
```jsx
import { RadioButtonGroupInput } from 'react-admin';
-
+ ;
```
#### Properties
@@ -843,54 +916,80 @@ const choices = [
{ _id: 123, full_name: 'Leo Tolstoi', sex: 'M' },
{ _id: 456, full_name: 'Jane Austen', sex: 'F' },
];
-
+ ;
```
`optionText` also accepts a function, so you can shape the option text at will:
```jsx
const choices = [
- { id: 123, first_name: 'Leo', last_name: 'Tolstoi' },
- { id: 456, first_name: 'Jane', last_name: 'Austen' },
+ { id: 123, first_name: 'Leo', last_name: 'Tolstoi' },
+ { id: 456, first_name: 'Jane', last_name: 'Austen' },
];
const optionRenderer = choice => `${choice.first_name} ${choice.last_name}`;
-
+ ;
```
`optionText` also accepts a React Element, that will be cloned and receive the related choice as the `record` prop. You can use Field components there.
```jsx
const choices = [
- { id: 123, first_name: 'Leo', last_name: 'Tolstoi' },
- { id: 456, first_name: 'Jane', last_name: 'Austen' },
+ { id: 123, first_name: 'Leo', last_name: 'Tolstoi' },
+ { id: 456, first_name: 'Jane', last_name: 'Austen' },
];
-const FullNameField = ({ record }) => {record.first_name} {record.last_name} ;
- }/>
+const FullNameField = ({ record }) => (
+
+ {record.first_name} {record.last_name}
+
+);
+ }
+/>;
```
The choices are translated by default, so you can use translation identifiers as choices:
```jsx
const choices = [
- { id: 'M', name: 'myroot.gender.male' },
- { id: 'F', name: 'myroot.gender.female' },
+ { id: 'M', name: 'myroot.gender.male' },
+ { id: 'F', name: 'myroot.gender.female' },
];
```
However, in some cases (e.g. inside a ``), you may not want the choice to be translated. In that case, set the `translateChoice` prop to `false`.
```jsx
-
+
```
Lastly, use the `options` attribute if you want to override any of Material UI's `` attributes:
{% raw %}
+
```jsx
-
+
```
+
{% endraw %}
Refer to [Material UI RadioGroup documentation](https://material-ui.com/api/radio-group) for more details.
@@ -902,7 +1001,7 @@ import { RadioButtonGroupInput, ReferenceInput } from 'react-admin';
-
+ ;
```
#### CSS API
@@ -925,28 +1024,31 @@ Set the `choices` attribute to determine the options (with `id`, `name` tuples):
```jsx
import { SelectInput } from 'react-admin';
-
+ ;
```
#### Properties
-| Prop | Required | Type | Default | Description |
-| ----------------- | -------- | -------------------------- | ------- | -------------------------------------------------------------------------------------------------------------------------------------- |
-| `allowEmpty` | Optional | `boolean` | `false` | If true, the first option is an empty one |
-| `choices` | Required | `Object[]` | - | List of items to show as options |
-| `create` | Optional | `Element` | `-` | A React Element to render when users want to create a new choice |
-| `createLabel` | Optional | `string` | `ra.action.create` | The label for the menu item allowing users to create a new choice. Used when the filter is empty |
-| `emptyText` | Optional | `string` | '' | The text to display for the empty option |
-| `onCreate` | Optional | `Function` | `-` | A function called with the current filter value when users choose to create a new choice. |
-| `options` | Optional | `Object` | - | Props to pass to the underlying `` element |
-| `optionText` | Optional | `string` | `Function` | `name` | Field name of record to display in the suggestion item or function which accepts the current record as argument (`record => {string}`) |
-| `optionValue` | Optional | `string` | `id` | Field name of record containing the value to use as input value |
-| `resettable` | Optional | `boolean` | `false` | If `true`, display a button to reset the changes in this input value |
-| `translateChoice` | Optional | `boolean` | `true` | Whether the choices should be translated |
+| Prop | Required | Type | Default | Description |
+| ----------------- | -------- | -------------------------- | ------------------ | -------------------------------------------------------------------------------------------------------------------------------------- |
+| `allowEmpty` | Optional | `boolean` | `false` | If true, the first option is an empty one |
+| `choices` | Required | `Object[]` | - | List of items to show as options |
+| `create` | Optional | `Element` | `-` | A React Element to render when users want to create a new choice |
+| `createLabel` | Optional | `string` | `ra.action.create` | The label for the menu item allowing users to create a new choice. Used when the filter is empty |
+| `emptyText` | Optional | `string` | '' | The text to display for the empty option |
+| `onCreate` | Optional | `Function` | `-` | A function called with the current filter value when users choose to create a new choice. |
+| `options` | Optional | `Object` | - | Props to pass to the underlying `` element |
+| `optionText` | Optional | `string` | `Function` | `name` | Field name of record to display in the suggestion item or function which accepts the current record as argument (`record => {string}`) |
+| `optionValue` | Optional | `string` | `id` | Field name of record containing the value to use as input value |
+| `resettable` | Optional | `boolean` | `false` | If `true`, display a button to reset the changes in this input value |
+| `translateChoice` | Optional | `boolean` | `true` | Whether the choices should be translated |
`` also accepts the [common input props](./Inputs.md#common-input-props).
@@ -959,54 +1061,76 @@ const choices = [
{ _id: 123, full_name: 'Leo Tolstoi', sex: 'M' },
{ _id: 456, full_name: 'Jane Austen', sex: 'F' },
];
-
+ ;
```
`optionText` also accepts a function, so you can shape the option text at will:
```jsx
const choices = [
- { id: 123, first_name: 'Leo', last_name: 'Tolstoi' },
- { id: 456, first_name: 'Jane', last_name: 'Austen' },
+ { id: 123, first_name: 'Leo', last_name: 'Tolstoi' },
+ { id: 456, first_name: 'Jane', last_name: 'Austen' },
];
const optionRenderer = choice => `${choice.first_name} ${choice.last_name}`;
-
+ ;
```
`optionText` also accepts a React Element, that will be cloned and receive the related choice as the `record` prop. You can use Field components there.
```jsx
const choices = [
- { id: 123, first_name: 'Leo', last_name: 'Tolstoi' },
- { id: 456, first_name: 'Jane', last_name: 'Austen' },
+ { id: 123, first_name: 'Leo', last_name: 'Tolstoi' },
+ { id: 456, first_name: 'Jane', last_name: 'Austen' },
];
-const FullNameField = ({ record }) => {record.first_name} {record.last_name} ;
- }/>
+const FullNameField = ({ record }) => (
+
+ {record.first_name} {record.last_name}
+
+);
+ }
+/>;
```
Enabling the `allowEmpty` props adds an empty choice (with a default `''` value, which you can overwrite with the `emptyValue` prop) on top of the options. You can furthermore customize the `MenuItem` for the empty choice by using the `emptyText` prop, which can receive either a string or a React Element, which doesn't receive any props.
```jsx
-
+
```
The choices are translated by default, so you can use translation identifiers as choices:
```jsx
const choices = [
- { id: 'M', name: 'myroot.gender.male' },
- { id: 'F', name: 'myroot.gender.female' },
+ { id: 'M', name: 'myroot.gender.male' },
+ { id: 'F', name: 'myroot.gender.female' },
];
```
However, in some cases, you may not want the choice to be translated. In that case, set the `translateChoice` prop to `false`.
```jsx
-
+
```
Note that `translateChoice` is set to `false` when `` is a child of ``.
@@ -1014,11 +1138,16 @@ Note that `translateChoice` is set to `false` when `` is a child of
Lastly, use the `options` attribute if you want to override any of Material UI's `` attributes:
{% raw %}
+
```jsx
-
+
```
+
{% endraw %}
Refer to [Material UI Select documentation](https://material-ui.com/api/select) for more details.
@@ -1030,7 +1159,7 @@ import { SelectInput, ReferenceInput } from 'react-admin';
-
+ ;
```
If, instead of showing choices as a dropdown list, you prefer to display them as a list of radio buttons, try the [``](#radiobuttongroupinput). And if the list is too big, prefer the [``](#autocompleteinput).
@@ -1047,7 +1176,12 @@ const choices = [
{ _id: 456, full_name: 'Jane Austen', sex: 'F' },
{ _id: 1, full_name: 'System Administrator', sex: 'F', disabled: true },
];
-
+ ;
```
You can use a custom field name by setting `disableValue` prop:
@@ -1058,7 +1192,13 @@ const choices = [
{ _id: 456, full_name: 'Jane Austen', sex: 'F' },
{ _id: 987, full_name: 'Jack Harden', sex: 'M', not_available: true },
];
-
+ ;
```
#### Creating New Choices
@@ -1068,10 +1208,11 @@ The `` can allow users to create a new choice if either the `create
Use the `onCreate` prop when you only require users to provide a simple string and a `prompt` is enough. You can return either the new choice directly or a Promise resolving to the new choice.
{% raw %}
+
```js
import { SelectInput, Create, SimpleForm, TextInput } from 'react-admin';
-const PostCreate = (props) => {
+const PostCreate = props => {
const categories = [
{ name: 'Tech', id: 'tech' },
{ name: 'Lifestyle', id: 'lifestyle' },
@@ -1083,7 +1224,10 @@ const PostCreate = (props) => {
{
const newCategoryName = prompt('Enter a new category');
- const newCategory = { id: newCategoryName.toLowerCase(), name: newCategoryName };
+ const newCategory = {
+ id: newCategoryName.toLowerCase(),
+ name: newCategoryName,
+ };
categories.push(newCategory);
return newCategory;
}}
@@ -1093,13 +1237,15 @@ const PostCreate = (props) => {
);
-}
+};
```
+
{% endraw %}
Use the `create` prop when you want a more polished or complex UI. For example a Material UI `` asking for multiple fields because the choices are from a referenced resource.
{% raw %}
+
```js
import {
SelectInput,
@@ -1107,7 +1253,7 @@ import {
ReferenceInput,
SimpleForm,
TextInput,
- useCreateSuggestionContext
+ useCreateSuggestionContext,
} from 'react-admin';
import {
@@ -1120,7 +1266,7 @@ import {
TextField,
} from '@material-ui/core';
-const PostCreate = (props) => {
+const PostCreate = props => {
return (
@@ -1131,14 +1277,14 @@ const PostCreate = (props) => {
);
-}
+};
const CreateCategory = () => {
const { filter, onCancel, onCreate } = useCreateSuggestionContext();
const [value, setValue] = React.useState(filter || '');
const [create] = useCreate('categories');
- const handleSubmit = (event) => {
+ const handleSubmit = event => {
event.preventDefault();
create(
{
@@ -1177,6 +1323,7 @@ const CreateCategory = () => {
);
};
```
+
{% endraw %}
#### CSS API
@@ -1196,14 +1343,19 @@ To edit arrays of data embedded inside a record, `` creates a list o
![ArrayInput](./img/array-input.gif)
```jsx
-import { ArrayInput, SimpleFormIterator, DateInput, TextInput } from 'react-admin';
+import {
+ ArrayInput,
+ SimpleFormIterator,
+ DateInput,
+ TextInput,
+} from 'react-admin';
-
+ ;
```
`` allows editing of embedded arrays, like the `authors` field in the following `post` record:
@@ -1220,7 +1372,7 @@ import { ArrayInput, SimpleFormIterator, DateInput, TextInput } from 'react-admi
"user_id": 456,
"url": "co_writer",
}
- ]
+ ]
}
```
@@ -1228,52 +1380,77 @@ import { ArrayInput, SimpleFormIterator, DateInput, TextInput } from 'react-admi
`` expects a single child, which must be a *form iterator* component. A form iterator is a component accepting a `fields` object as passed by [react-final-form-array](https://github.com/final-form/react-final-form-arrays#fieldarrayrenderprops), and defining a layout for an array of fields. For instance, the `` component displays an array of react-admin Inputs in an unordered list (``), one sub-form by list item (``). It also provides controls for adding and removing a sub-record (a backlink in this example).
-You can pass `disableAdd` and `disableRemove` as props of `SimpleFormIterator`, to disable `ADD` and `REMOVE` button respectively. Default value of both is `false`.
+You can pass `disableAdd`, `disableRemove` and `disableReordering` as props of `SimpleFormIterator`, to disable `ADD`, `REMOVE` and reordering buttons respectively. They are all enabled by default.
```jsx
-import { ArrayInput, SimpleFormIterator, DateInput, TextInput } from 'react-admin';
+import {
+ ArrayInput,
+ SimpleFormIterator,
+ DateInput,
+ TextInput,
+} from 'react-admin';
-
+
-
+ ;
```
-You can also use `addButton` and `removeButton` props to pass your custom add and remove buttons to `SimpleFormIterator`.
+You can also use `addButton`, `removeButton` or `reOrderButtons` props to pass your custom add and remove buttons to `SimpleFormIterator`.
```jsx
-import { ArrayInput, SimpleFormIterator, DateInput, TextInput } from 'react-admin';
+import {
+ ArrayInput,
+ SimpleFormIterator,
+ DateInput,
+ TextInput,
+} from 'react-admin';
- } removeButton={ }>
+ }
+ removeButton={ }
+ reOrderButtons={ }
+ >
-
+ ;
```
Furthermore, if you want to customize the label displayed for each item, you can pass a function to `` via the `getItemLabel` prop.
```jsx
-import { ArrayInput, SimpleFormIterator, DateInput, TextInput } from 'react-admin';
+import {
+ ArrayInput,
+ SimpleFormIterator,
+ DateInput,
+ TextInput,
+} from 'react-admin';
- `${index + 1}. link`}>
+ `${index + 1}. link`}>
-
+;
```
**Note**: `` only accepts `Input` components as children. If you want to use some `Fields` instead, you have to use a `` to get the correct source, as follows:
```jsx
-import { ArrayInput, SimpleFormIterator, DateInput, TextInput, FormDataConsumer } from 'react-admin';
+import {
+ ArrayInput,
+ SimpleFormIterator,
+ DateInput,
+ TextInput,
+ FormDataConsumer,
+} from 'react-admin';
-
+
{({ getSource, scopedFormData }) => {
@@ -1286,10 +1463,10 @@ import { ArrayInput, SimpleFormIterator, DateInput, TextInput, FormDataConsumer
}}
-
+;
```
-`` also accepts the [common input props](./Inputs.md#common-input-props) (except `format` and `parse`).
+`` also accepts the [common input props](./Inputs.md#common-input-props) (except `format` and `parse`).
**Important**: Note that asynchronous validators are not supported on the `` component due to a limitation of [react-final-form-arrays](https://github.com/final-form/react-final-form-arrays).
@@ -1305,32 +1482,35 @@ Set the `choices` attribute to determine the options list (with `id`, `name` tup
```jsx
import { AutocompleteArrayInput } from 'react-admin';
-
+ ;
```
#### Properties
-| Prop | Required | Type | Default | Description |
-| ------------------------- | -------- | -------------------------- | ------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
-| `allowEmpty` | Optional | `boolean` | `false` | If `true`, the first option is an empty one |
-| `allowDuplicates` | Optional | `boolean` | `false` | If `true`, the options can be selected several times |
-| `create` | Optional | `Element` | `-` | A React Element to render when users want to create a new choice |
-| `createLabel` | Optional | `string` | `ra.action.create` | The label for the menu item allowing users to create a new choice. Used when the filter is empty |
-| `createItemLabel` | Optional | `string` | `ra.action.create_item` | The label for the menu item allowing users to create a new choice. Used when the filter is not empty |
-| `debounce` | Optional | `number` | `250` | The delay to wait before calling the setFilter function injected when used in a ReferenceInput. |
-| `choices` | Required | `Object[]` | - | List of items to autosuggest |
-| `matchSuggestion` | Optional | `Function` | - | Required if `optionText` is a React element. Function returning a boolean indicating whether a choice matches the filter. `(filter, choice) => boolean` |
-| `onCreate` | Optional | `Function` | `-` | A function called with the current filter value when users choose to create a new choice. |
-| `optionValue` | Optional | `string` | `id` | Field name of record containing the value to use as input value |
-| `optionText` | Optional | `string` | `Function` | `name` | Field name of record to display in the suggestion item or function which accepts the current record as argument (`record => {string}`) |
-| `setFilter` | Optional | `Function` | `null` | A callback to inform the `searchText` has changed and new `choices` can be retrieved based on this `searchText`. Signature `searchText => void`. This function is automatically setup when using `ReferenceInput`. |
-| `shouldRenderSuggestions` | Optional | `Function` | `() => true` | A function that returns a `boolean` to determine whether or not suggestions are rendered. Use this when working with large collections of data to improve performance and user experience. This function is passed into the underlying react-autosuggest component. Ex.`(value) => value.trim() > 2` |
-| `source` | Required | `string` | - | Name of field to edit, its type should match the type retrieved from `optionValue` |
-| `suggestionLimit` | Optional | `number` | `null` | Limits the numbers of suggestions that are shown in the dropdown list |
+| Prop | Required | Type | Default | Description |
+| ------------------------- | -------- | -------------------------- | ----------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
+| `allowEmpty` | Optional | `boolean` | `false` | If `true`, the first option is an empty one |
+| `allowDuplicates` | Optional | `boolean` | `false` | If `true`, the options can be selected several times |
+| `create` | Optional | `Element` | `-` | A React Element to render when users want to create a new choice |
+| `createLabel` | Optional | `string` | `ra.action.create` | The label for the menu item allowing users to create a new choice. Used when the filter is empty |
+| `createItemLabel` | Optional | `string` | `ra.action.create_item` | The label for the menu item allowing users to create a new choice. Used when the filter is not empty |
+| `debounce` | Optional | `number` | `250` | The delay to wait before calling the setFilter function injected when used in a ReferenceInput. |
+| `choices` | Required | `Object[]` | - | List of items to autosuggest |
+| `matchSuggestion` | Optional | `Function` | - | Required if `optionText` is a React element. Function returning a boolean indicating whether a choice matches the filter. `(filter, choice) => boolean` |
+| `onCreate` | Optional | `Function` | `-` | A function called with the current filter value when users choose to create a new choice. |
+| `optionValue` | Optional | `string` | `id` | Field name of record containing the value to use as input value |
+| `optionText` | Optional | `string` | `Function` | `name` | Field name of record to display in the suggestion item or function which accepts the current record as argument (`record => {string}`) |
+| `setFilter` | Optional | `Function` | `null` | A callback to inform the `searchText` has changed and new `choices` can be retrieved based on this `searchText`. Signature `searchText => void`. This function is automatically setup when using `ReferenceInput`. |
+| `shouldRenderSuggestions` | Optional | `Function` | `() => true` | A function that returns a `boolean` to determine whether or not suggestions are rendered. Use this when working with large collections of data to improve performance and user experience. This function is passed into the underlying react-autosuggest component. Ex.`(value) => value.trim() > 2` |
+| `source` | Required | `string` | - | Name of field to edit, its type should match the type retrieved from `optionValue` |
+| `suggestionLimit` | Optional | `number` | `null` | Limits the numbers of suggestions that are shown in the dropdown list |
`` also accepts the [common input props](./Inputs.md#common-input-props).
@@ -1343,33 +1523,46 @@ const choices = [
{ _id: 123, full_name: 'Leo Tolstoi', sex: 'M' },
{ _id: 456, full_name: 'Jane Austen', sex: 'F' },
];
-
+ ;
```
`optionText` also accepts a function, so you can shape the option text at will:
```jsx
const choices = [
- { id: 123, first_name: 'Leo', last_name: 'Tolstoi' },
- { id: 456, first_name: 'Jane', last_name: 'Austen' },
+ { id: 123, first_name: 'Leo', last_name: 'Tolstoi' },
+ { id: 456, first_name: 'Jane', last_name: 'Austen' },
];
const optionRenderer = choice => `${choice.first_name} ${choice.last_name}`;
-
+ ;
```
The choices are translated by default, so you can use translation identifiers as choices:
```jsx
const choices = [
- { id: 'M', name: 'myroot.gender.male' },
- { id: 'F', name: 'myroot.gender.female' },
+ { id: 'M', name: 'myroot.gender.male' },
+ { id: 'F', name: 'myroot.gender.female' },
];
```
However, in some cases (e.g. inside a ``), you may not want the choice to be translated. In that case, set the `translateChoice` prop to `false`.
```jsx
-
+
```
When dealing with a large amount of `choices` you may need to limit the number of suggestions that are rendered in order to maintain usable performance. The `shouldRenderSuggestions` is an optional prop that allows you to set conditions on when to render suggestions. An easy way to improve performance would be to skip rendering until the user has entered 2 or 3 characters in the search box. This lowers the result set significantly, and might be all you need (depending on your data set).
@@ -1378,11 +1571,16 @@ Ex. ` { return val.trim
Lastly, `` renders a [material-ui `` component](https://material-ui.com/api/text-field/). Use the `options` attribute to override any of the `` attributes:
{% raw %}
+
```jsx
-
+
```
+
{% endraw %}
**Tip**: Like many other inputs, `` accept a `fullWidth` prop.
@@ -1394,21 +1592,27 @@ import { AutocompleteArrayInput, ReferenceArrayInput } from 'react-admin';
-
+;
```
If you need to override the props of the suggestion's container (a `Popper` element), you can specify them using the `options.suggestionsContainerProps`. For example:
{% raw %}
+
```jsx
-
+
```
+
{% endraw %}
-**Tip**: `` is a stateless component, so it only allows to *filter* the list of choices, not to *extend* it. If you need to populate the list of choices based on the result from a `fetch` call (and if [``](#referencearrayinput) doesn't cover your need), you'll have to [write your own Input component](#writing-your-own-input-component) based on [material-ui-chip-input](https://github.com/TeamWertarbyte/material-ui-chip-input).
+**Tip**: `` is a stateless component, so it only allows to _filter_ the list of choices, not to _extend_ it. If you need to populate the list of choices based on the result from a `fetch` call (and if [``](#referencearrayinput) doesn't cover your need), you'll have to [write your own Input component](#writing-your-own-input-component) based on [material-ui-chip-input](https://github.com/TeamWertarbyte/material-ui-chip-input).
**Tip**: React-admin's `` has only a capital A, while material-ui's `` has a capital A and a capital C. Don't mix up the components!
@@ -1419,10 +1623,16 @@ The `` can allow users to create a new choice if either
Use the `onCreate` prop when you only require users to provide a simple string and a `prompt` is enough. You can return either the new choice directly or a Promise resolving to the new choice.
{% raw %}
+
```js
-import { AutocompleteArrayInput, Create, SimpleForm, TextInput } from 'react-admin';
+import {
+ AutocompleteArrayInput,
+ Create,
+ SimpleForm,
+ TextInput,
+} from 'react-admin';
-const PostCreate = (props) => {
+const PostCreate = props => {
const tags = [
{ name: 'Tech', id: 'tech' },
{ name: 'Lifestyle', id: 'lifestyle' },
@@ -1434,7 +1644,10 @@ const PostCreate = (props) => {
{
const newTagName = prompt('Enter a new tag');
- const newTag = { id: newTagName.toLowerCase(), name: newTagName };
+ const newTag = {
+ id: newTagName.toLowerCase(),
+ name: newTagName,
+ };
categories.push(newTag);
return newTag;
}}
@@ -1444,13 +1657,15 @@ const PostCreate = (props) => {
);
-}
+};
```
+
{% endraw %}
Use the `create` prop when you want a more polished or complex UI. For example a Material UI `` asking for multiple fields because the choices are from a referenced resource.
{% raw %}
+
```js
import {
AutocompleteArrayInput,
@@ -1458,7 +1673,7 @@ import {
ReferenceArrayInput,
SimpleForm,
TextInput,
- useCreateSuggestionContext
+ useCreateSuggestionContext,
} from 'react-admin';
import {
@@ -1471,7 +1686,7 @@ import {
TextField,
} from '@material-ui/core';
-const PostCreate = (props) => {
+const PostCreate = props => {
return (
@@ -1482,14 +1697,14 @@ const PostCreate = (props) => {
);
-}
+};
const CreateTag = () => {
const { filter, onCancel, onCreate } = useCreateSuggestionContext();
const [value, setValue] = React.useState(filter || '');
const [create] = useCreate('tags');
- const handleSubmit = (event) => {
+ const handleSubmit = event => {
event.preventDefault();
create(
{
@@ -1528,6 +1743,7 @@ const CreateTag = () => {
);
};
```
+
{% endraw %}
#### CSS API
@@ -1556,17 +1772,20 @@ Set the `choices` attribute to determine the options (with `id`, `name` tuples):
```jsx
import { CheckboxGroupInput } from 'react-admin';
-
+ ;
```
#### Properties
| Prop | Required | Type | Default | Description |
-| ------------- | -------- | -------------------------- | ------- | ------------------------------------------------------------------------------------------------------------------------------------- |
+| ------------- | -------- | -------------------------- | ------- | -------------------------------------------------------------------------------------------------------------------------------------- |
| `choices` | Required | `Object[]` | - | List of choices |
| `optionText` | Optional | `string` | `Function` | `name` | Field name of record to display in the suggestion item or function which accepts the correct record as argument (`record => {string}`) |
| `optionValue` | Optional | `string` | `id` | Field name of record containing the value to use as input value |
@@ -1585,29 +1804,46 @@ const choices = [
{ _id: 123, full_name: 'Leo Tolstoi', sex: 'M' },
{ _id: 456, full_name: 'Jane Austen', sex: 'F' },
];
-
+ ;
```
`optionText` also accepts a function, so you can shape the option text at will:
```jsx
const choices = [
- { id: 123, first_name: 'Leo', last_name: 'Tolstoi' },
- { id: 456, first_name: 'Jane', last_name: 'Austen' },
+ { id: 123, first_name: 'Leo', last_name: 'Tolstoi' },
+ { id: 456, first_name: 'Jane', last_name: 'Austen' },
];
const optionRenderer = choice => `${choice.first_name} ${choice.last_name}`;
-
+ ;
```
`optionText` also accepts a React Element, that will be cloned and receive the related choice as the `record` prop. You can use Field components there.
```jsx
const choices = [
- { id: 123, first_name: 'Leo', last_name: 'Tolstoi' },
- { id: 456, first_name: 'Jane', last_name: 'Austen' },
+ { id: 123, first_name: 'Leo', last_name: 'Tolstoi' },
+ { id: 456, first_name: 'Jane', last_name: 'Austen' },
];
-const FullNameField = ({ record }) => {record.first_name} {record.last_name} ;
- }/>
+const FullNameField = ({ record }) => (
+
+ {record.first_name} {record.last_name}
+
+);
+ }
+/>;
```
The choices are translated by default, so you can use translation identifiers as choices:
@@ -1623,20 +1859,25 @@ const choices = [
However, in some cases (e.g. inside a ``), you may not want the choice to be translated. In that case, set the `translateChoice` prop to `false`.
```jsx
-
+
```
Lastly, use the `options` attribute if you want to override any of Material UI's `` attributes:
{% raw %}
+
```jsx
import { FavoriteBorder, Favorite } from '@material-ui/icons';
- ,
- checkedIcon:
-}} />
+ ,
+ checkedIcon: ,
+ }}
+/>;
```
+
{% endraw %}
#### CSS API
@@ -1660,7 +1901,7 @@ import { DualListInput } from '@react-admin/ra-relationships';
-
+ ;
```
Check [the `ra-relationships` documentation](https://marmelab.com/ra-enterprise/modules/ra-relationships) for more details.
@@ -1673,19 +1914,19 @@ To let users choose several values in a list using a dropdown, use `` element |
-| `optionText` | Optional | `string` | `Function` | `name` | Field name of record to display in the suggestion item or function which accepts the current record as argument (`record => {string}`) |
-| `optionValue` | Optional | `string` | `id` | Field name of record containing the value to use as input value |
-| `resettable` | Optional | `boolean` | `false` | If `true`, display a button to reset the changes in this input value |
-| `translateChoice` | Optional | `boolean` | `true` | Whether the choices should be translated |
+| Prop | Required | Type | Default | Description |
+| ----------------- | -------- | -------------------------- | ------------------ | -------------------------------------------------------------------------------------------------------------------------------------- |
+| `allowEmpty` | Optional | `boolean` | `false` | If true, the first option is an empty one |
+| `choices` | Required | `Object[]` | - | List of items to show as options |
+| `create` | Optional | `Element` | `-` | A React Element to render when users want to create a new choice |
+| `createLabel` | Optional | `string` | `ra.action.create` | The label for the menu item allowing users to create a new choice. Used when the filter is empty |
+| `emptyText` | Optional | `string` | '' | The text to display for the empty option |
+| `onCreate` | Optional | `Function` | `-` | A function called with the current filter value when users choose to create a new choice. |
+| `options` | Optional | `Object` | - | Props to pass to the underlying `` element |
+| `optionText` | Optional | `string` | `Function` | `name` | Field name of record to display in the suggestion item or function which accepts the current record as argument (`record => {string}`) |
+| `optionValue` | Optional | `string` | `id` | Field name of record containing the value to use as input value |
+| `resettable` | Optional | `boolean` | `false` | If `true`, display a button to reset the changes in this input value |
+| `translateChoice` | Optional | `boolean` | `true` | Whether the choices should be translated |
`` also accepts the [common input props](./Inputs.md#common-input-props).
@@ -1696,13 +1937,17 @@ Set the `choices` attribute to determine the options (with `id`, `name` tuples):
```jsx
import { SelectArrayInput } from 'react-admin';
-
+ ;
```
You can also customize the properties to use for the option name and value,
@@ -1710,35 +1955,44 @@ thanks to the `optionText` and `optionValue` attributes.
```jsx
const choices = [
- { _id: '1', name: 'Book', plural_name: 'Books' },
- { _id: '2', name: 'Video', plural_name: 'Videos' },
- { _id: '3', name: 'Audio', plural_name: 'Audios' },
+ { _id: '1', name: 'Book', plural_name: 'Books' },
+ { _id: '2', name: 'Video', plural_name: 'Videos' },
+ { _id: '3', name: 'Audio', plural_name: 'Audios' },
];
-
+ ;
```
`optionText` also accepts a function, so you can shape the option text at will:
```jsx
const choices = [
- { id: '1', name: 'Book', quantity: 23 },
- { id: '2', name: 'Video', quantity: 56 },
- { id: '3', name: 'Audio', quantity: 12 },
+ { id: '1', name: 'Book', quantity: 23 },
+ { id: '2', name: 'Video', quantity: 56 },
+ { id: '3', name: 'Audio', quantity: 12 },
];
const optionRenderer = choice => `${choice.name} (${choice.quantity})`;
-
+ ;
```
The choices are translated by default, so you can use translation identifiers as choices:
```js
const choices = [
- { id: 'books', name: 'myroot.category.books' },
- { id: 'sport', name: 'myroot.category.sport' },
+ { id: 'books', name: 'myroot.category.books' },
+ { id: 'sport', name: 'myroot.category.sport' },
];
```
-You can render any item as disabled by setting its `disabled` property to `true`:
+You can render any item as disabled by setting its `disabled` property to `true`:
```jsx
const choices = [
@@ -1746,10 +2000,15 @@ const choices = [
{ _id: 456, full_name: 'Jane Austen', sex: 'F' },
{ _id: 1, full_name: 'System Administrator', sex: 'F', disabled: true },
];
-
+ ;
```
-You can use a custom field name by setting the `disableValue` prop:
+You can use a custom field name by setting the `disableValue` prop:
```jsx
const choices = [
@@ -1757,15 +2016,23 @@ const choices = [
{ _id: 456, full_name: 'Jane Austen', sex: 'F' },
{ _id: 987, full_name: 'Jack Harden', sex: 'M', not_available: true },
];
-
+ ;
```
Lastly, use the `options` attribute if you want to override any of the `` attributes:
{% raw %}
+
```jsx
```
+
{% endraw %}
Refer to [the Select documentation](https://material-ui.com/api/select) for more details.
@@ -1773,7 +2040,7 @@ Refer to [the Select documentation](https://material-ui.com/api/select) for more
The `SelectArrayInput` component **cannot** be used inside a `ReferenceInput` but can be used inside a `ReferenceArrayInput`.
```jsx
-import * as React from "react";
+import * as React from 'react';
import {
ChipField,
Create,
@@ -1811,10 +2078,11 @@ The `` can allow users to create a new choice if either the `c
Use the `onCreate` prop when you only require users to provide a simple string and a `prompt` is enough. You can return either the new choice directly or a Promise resolving to the new choice.
{% raw %}
+
```js
import { SelectArrayInput, Create, SimpleForm, TextInput } from 'react-admin';
-const PostCreate = (props) => {
+const PostCreate = props => {
const tags = [
{ name: 'Tech', id: 'tech' },
{ name: 'Lifestyle', id: 'lifestyle' },
@@ -1826,7 +2094,10 @@ const PostCreate = (props) => {
{
const newTagName = prompt('Enter a new tag');
- const newTag = { id: newTagName.toLowerCase(), name: newTagName };
+ const newTag = {
+ id: newTagName.toLowerCase(),
+ name: newTagName,
+ };
categories.push(newTag);
return newTag;
}}
@@ -1836,13 +2107,15 @@ const PostCreate = (props) => {
);
-}
+};
```
+
{% endraw %}
Use the `create` prop when you want a more polished or complex UI. For example a Material UI `` asking for multiple fields because the choices are from a referenced resource.
{% raw %}
+
```js
import {
SelectArrayInput,
@@ -1850,7 +2123,7 @@ import {
ReferenceArrayInput,
SimpleForm,
TextInput,
- useCreateSuggestionContext
+ useCreateSuggestionContext,
} from 'react-admin';
import {
@@ -1863,7 +2136,7 @@ import {
TextField,
} from '@material-ui/core';
-const PostCreate = (props) => {
+const PostCreate = props => {
return (
@@ -1874,14 +2147,14 @@ const PostCreate = (props) => {
);
-}
+};
const CreateTag = () => {
const { filter, onCancel, onCreate } = useCreateSuggestionContext();
const [value, setValue] = React.useState(filter || '');
const [create] = useCreate('tags');
- const handleSubmit = (event) => {
+ const handleSubmit = event => {
event.preventDefault();
create(
{
@@ -1920,6 +2193,7 @@ const CreateTag = () => {
);
};
```
+
{% endraw %}
#### CSS API
@@ -1968,7 +2242,7 @@ import { ReferenceArrayInput, SelectArrayInput } from 'react-admin';
-
+ ;
```
![SelectArrayInput](./img/select-array-input.gif)
@@ -1990,7 +2264,7 @@ import { ReferenceArrayInput, SelectArrayInput } from 'react-admin';
-
+ ;
```
**Tip**: `allowEmpty` is set by default for all Input components passed as ``.
@@ -1998,6 +2272,7 @@ import { ReferenceArrayInput, SelectArrayInput } from 'react-admin';
You can tweak how this component fetches the possible values using the `perPage`, `sort`, and `filter` props.
{% raw %}
+
```jsx
// by default, fetches only the first 25 values. You can extend this limit
// by setting the `perPage` prop.
@@ -2029,6 +2304,7 @@ You can tweak how this component fetches the possible values using the `perPage`
```
+
{% endraw %}
In addition to the `ReferenceArrayInputContext`, `` also sets up a `ListContext` providing access to the records from the reference resource in a similar fashion to that of the `` component. This `ListContext` value is accessible with the [`useListContext`](./List.md#uselistcontext) hook.
@@ -2045,7 +2321,7 @@ Any component descendant of `` can grab information from the
const {
choices, // An array of records matching both the current input value and the filters
error, // A potential error that may have occured while fetching the data
- warning, // A potential warning regarding missing references
+ warning, // A potential warning regarding missing references
loaded, // boolean that is false until the data is available
loading, // boolean that is true on mount, and false once the data was fetched
setFilter, // a callback to update the filters, e.g. setFilters({ q: 'query' })
@@ -2068,12 +2344,12 @@ import { ReferenceInput, SelectInput } from 'react-admin';
-
+
;
```
![ReferenceInput](./img/reference-input.gif)
-**Note**: You **must** add a `` for the reference resource - react-admin needs it to fetch the reference data. You *can* omit the `list` prop in this reference if you want to hide it in the sidebar menu.
+**Note**: You **must** add a `` for the reference resource - react-admin needs it to fetch the reference data. You _can_ omit the `list` prop in this reference if you want to hide it in the sidebar menu.
```jsx
@@ -2105,22 +2381,25 @@ import { ReferenceInput, SelectInput } from 'react-admin';
-
+;
```
**Tip**: `allowEmpty` is set by default for all Input components passed as ``:
```jsx
const commentFilters = [
- // no need for allowEmpty
+
+ {' '}
+ // no need for allowEmpty
-
+ ,
];
```
You can tweak how this component fetches the possible values using the `perPage`, `sort`, and `filter` props.
{% raw %}
+
```jsx
// by default, fetches only the first 25 values. You can extend this limit
// by setting the `perPage` prop.
@@ -2152,6 +2431,7 @@ You can tweak how this component fetches the possible values using the `perPage`
```
+
{% endraw %}
The child component may further filter results (that's the case, for instance, for ``). ReferenceInput passes a `setFilter` function as prop to its child component. It uses the value to create a filter for the query - by default `{ q: [searchText] }`. You can customize the mapping
@@ -2169,18 +2449,18 @@ The child component may further filter results (that's the case, for instance, f
The child component receives the following props from ``:
-- `loading`: whether the request for possible values is loading or not
-- `filter`: the current filter of the request for possible values. Defaults to `{}`.
-- `pagination`: the current pagination of the request for possible values. Defaults to `{ page: 1, perPage: 25 }`.
-- `sort`: the current sorting of the request for possible values. Defaults to `{ field: 'id', order: 'DESC' }`.
-- `error`: the error message if the form validation failed for that input
-- `warning`: the warning message if the form validation failed for that input
-- `onChange`: function to call when the value changes
-- `setFilter`: function to call to update the filter of the request for possible values
-- `setPagination`: : function to call to update the pagination of the request for possible values
-- `setSort`: function to call to update the sorting of the request for possible values
+- `loading`: whether the request for possible values is loading or not
+- `filter`: the current filter of the request for possible values. Defaults to `{}`.
+- `pagination`: the current pagination of the request for possible values. Defaults to `{ page: 1, perPage: 25 }`.
+- `sort`: the current sorting of the request for possible values. Defaults to `{ field: 'id', order: 'DESC' }`.
+- `error`: the error message if the form validation failed for that input
+- `warning`: the warning message if the form validation failed for that input
+- `onChange`: function to call when the value changes
+- `setFilter`: function to call to update the filter of the request for possible values
+- `setPagination`: : function to call to update the pagination of the request for possible values
+- `setSort`: function to call to update the sorting of the request for possible values
-**Tip**: Why does `` use the `dataProvider.getMany()` method with a single value `[id]` instead of `dataProvider.getOne()` to fetch the record for the current value? Because when there are many `` for the same resource in a form (for instance when inside an ``), react-admin *aggregates* the calls to `dataProvider.getMany()` into a single one with `[id1, id2, ...]`. This speeds up the UI and avoids hitting the API too much.
+**Tip**: Why does `` use the `dataProvider.getMany()` method with a single value `[id]` instead of `dataProvider.getOne()` to fetch the record for the current value? Because when there are many `` for the same resource in a form (for instance when inside an ``), react-admin _aggregates_ the calls to `dataProvider.getMany()` into a single one with `[id1, id2, ...]`. This speeds up the UI and avoids hitting the API too much.
### ``
@@ -2203,7 +2483,10 @@ The form displays the events name in a ``:
```jsx
import * as React from 'react';
import { Edit, SelectArrayInput, SimpleForm, TextInput } from 'react-admin';
-import { ReferenceManyToManyInput, useReferenceManyToManyUpdate } from '@react-admin/ra-many-to-many';
+import {
+ ReferenceManyToManyInput,
+ useReferenceManyToManyUpdate,
+} from '@react-admin/ra-many-to-many';
/**
* Decorate to override the default save function.
@@ -2300,11 +2583,7 @@ You may override the language selector using the `selector` prop, which accepts
```jsx
const Selector = () => {
- const {
- locales,
- selectLocale,
- selectedLocale,
- } = useTranslatableContext();
+ const { locales, selectLocale, selectedLocale } = useTranslatableContext();
const handleChange = event => {
selectLocale(event.target.value);
@@ -2339,7 +2618,7 @@ const Selector = () => {
>
-
+;
```
If you have multiple `TranslatableInputs` on the same page, you should specify a `groupKey` so that react-admin can create unique identifiers for accessibility.
@@ -2360,7 +2639,7 @@ You can add validators to any of the inputs inside a `TranslatableInputs`. If an
```
-## Recipes
+## Recipes
### Transforming Input Value to/from Record
@@ -2368,45 +2647,50 @@ The data format returned by the input component may not be what your API desires
Mnemonic for the two functions:
-- `parse()`: input -> record
-- `format()`: record -> input
+- `parse()`: input -> record
+- `format()`: record -> input
Say the user would like to input values of 0-100 to a percentage field but your API (hence record) expects 0-1.0. You can use simple `parse()` and `format()` functions to archive the transform:
```jsx
- v * 100} parse={v => parseFloat(v) / 100} label="Formatted number" />
+ v * 100}
+ parse={v => parseFloat(v) / 100}
+ label="Formatted number"
+/>
```
`` stores and returns a string. If you would like to store a JavaScript Date object in your record instead:
```jsx
const dateFormatter = v => {
- // v is a `Date` object
- if (!(v instanceof Date) || isNaN(v)) return;
- const pad = '00';
- const yy = v.getFullYear().toString();
- const mm = (v.getMonth() + 1).toString();
- const dd = v.getDate().toString();
- return `${yy}-${(pad + mm).slice(-2)}-${(pad + dd).slice(-2)}`;
+ // v is a `Date` object
+ if (!(v instanceof Date) || isNaN(v)) return;
+ const pad = '00';
+ const yy = v.getFullYear().toString();
+ const mm = (v.getMonth() + 1).toString();
+ const dd = v.getDate().toString();
+ return `${yy}-${(pad + mm).slice(-2)}-${(pad + dd).slice(-2)}`;
};
const dateParser = v => {
- // v is a string of "YYYY-MM-DD" format
- const match = /(\d{4})-(\d{2})-(\d{2})/.exec(v);
- if (match === null) return;
- const d = new Date(match[1], parseInt(match[2], 10) - 1, match[3]);
- if (isNaN(d)) return;
- return d;
+ // v is a string of "YYYY-MM-DD" format
+ const match = /(\d{4})-(\d{2})-(\d{2})/.exec(v);
+ if (match === null) return;
+ const d = new Date(match[1], parseInt(match[2], 10) - 1, match[3]);
+ if (isNaN(d)) return;
+ return d;
};
-
+ ;
```
### Linking Two Inputs
Edition forms often contain linked inputs, e.g. country and city (the choices of the latter depending on the value of the former).
-React-admin relies on [react-final-form](https://final-form.org/docs/react-final-form/getting-started) for form handling. You can grab the current form values using react-final-form [useFormState](https://final-form.org/docs/react-final-form/api/useFormState) hook.
+React-admin relies on [react-final-form](https://final-form.org/docs/react-final-form/getting-started) for form handling. You can grab the current form values using react-final-form [useFormState](https://final-form.org/docs/react-final-form/api/useFormState) hook.
```jsx
import * as React from 'react';
@@ -2473,15 +2757,15 @@ const OrderEdit = props => (
**Tip**: When using a `FormDataConsumer` inside an `ArrayInput`, the `FormDataConsumer` will provide three additional properties to its children function:
-- `scopedFormData`: an object containing the current values of the currently rendered item from the `ArrayInput`
-- `getSource`: a function which will translate the source into a valid one for the `ArrayInput`
+- `scopedFormData`: an object containing the current values of the currently rendered item from the `ArrayInput`
+- `getSource`: a function which will translate the source into a valid one for the `ArrayInput`
And here is an example usage for `getSource` inside ``:
```jsx
import { FormDataConsumer } from 'react-admin';
-const PostEdit = (props) => (
+const PostEdit = props => (
@@ -2519,39 +2803,41 @@ For such cases, you can use the approach described above, using the ` (
-
-
-
-
- {({ formData, ...rest }) => formData.hasEmail &&
-
- }
-
-
-
- );
+const PostEdit = props => (
+
+
+
+
+ {({ formData, ...rest }) =>
+ formData.hasEmail &&
+ }
+
+
+
+);
```
**Tip**: When using a `FormDataConsumer` you can define [subscription](https://final-form.org/docs/react-final-form/types/FormProps#subscription) prop to pass to the `react-final-form`
{% raw %}
+
```jsx
import { FormDataConsumer } from 'react-admin';
- const PostEdit = (props) => (
-
-
-
-
- {({ formData, ...rest }) => formData.hasEmail &&
-
- }
-
-
-
- );
+const PostEdit = props => (
+
+
+
+
+ {({ formData, ...rest }) =>
+ formData.hasEmail &&
+ }
+
+
+
+);
```
+
{% endraw %}
## Writing Your Own Input Component
@@ -2567,15 +2853,25 @@ For instance, let's write a component to edit the latitude and longitude of the
import { Field } from 'react-final-form';
const LatLngInput = () => (
-
+
-
+
);
export default LatLngInput;
// in ItemEdit.js
-const ItemEdit = (props) => (
+const ItemEdit = props => (
@@ -2588,8 +2884,8 @@ const ItemEdit = (props) => (
```html
-
-
+
+
```
@@ -2598,9 +2894,19 @@ const ItemEdit = (props) => (
```jsx
const LatLongInput = () => (
-
+
-
+
);
```
@@ -2617,9 +2923,19 @@ import { Labeled } from 'react-admin';
const LatLngInput = () => (
-
+
-
+
);
@@ -2631,8 +2947,8 @@ Now the component will render with a label:
```html
Position
-
-
+
+
```
@@ -2648,7 +2964,7 @@ import { useField } from 'react-final-form';
const BoundedTextField = ({ name, label }) => {
const {
input: { onChange },
- meta: { touched, error }
+ meta: { touched, error },
} = useField(name);
return (
{
const {
input: { name, onChange, ...rest },
meta: { touched, error },
- isRequired
+ isRequired,
} = useInput(props);
return (
@@ -2723,13 +3039,23 @@ const BoundedTextField = props => {
);
};
const LatLngInput = props => {
- const {source, ...rest} = props;
+ const { source, ...rest } = props;
return (
-
+
-
+
);
};
@@ -2746,14 +3072,11 @@ import { useInput } from 'react-admin';
const SexInput = props => {
const {
input,
- meta: { touched, error }
+ meta: { touched, error },
} = useInput(props);
return (
-
+
Male
Female
@@ -2765,16 +3088,20 @@ export default SexInput;
**Tip**: `useInput` accepts all arguments that you can pass to `useField`. That means that components using `useInput` accept props like [format](https://final-form.org/docs/react-final-form/types/FieldProps#format) and [parse](https://final-form.org/docs/react-final-form/types/FieldProps#parse), to convert values from the form to the input, and vice-versa:
```jsx
-const parse = value => {/* ... */};
-const format = value => {/* ... */};
+const parse = value => {
+ /* ... */
+};
+const format = value => {
+ /* ... */
+};
const PersonEdit = props => (
formValue === 0 ? 'M' : 'F'}
- parse={inputValue => inputValue === 'M' ? 0 : 1}
+ format={formValue => (formValue === 0 ? 'M' : 'F')}
+ parse={inputValue => (inputValue === 'M' ? 0 : 1)}
/>
@@ -2791,4 +3118,4 @@ You can find components for react-admin in third-party repositories.
- [@bb-tech/ra-components](https://github.com/bigbasket/ra-components): `JsonInput` which allows only valid JSON as input, `JsonField` to view JSON properly on show card and `TrimField` to trim the fields while showing in `Datagrid` in `List` component.
- [@react-page/react-admin](https://react-page.github.io/docs/#/integration-react-admin): ReactPage is a rich content editor and can comes with a ready-to-use React-admin input component. [check out the demo](https://react-page.github.io/examples/reactadmin)
-- **DEPRECATED V3** [LoicMahieu/aor-tinymce-input](https://github.com/LoicMahieu/aor-tinymce-input): a TinyMCE component, useful for editing HTML
+- **DEPRECATED V3** [LoicMahieu/aor-tinymce-input](https://github.com/LoicMahieu/aor-tinymce-input): a TinyMCE component, useful for editing HTML
diff --git a/packages/ra-core/src/i18n/TranslationMessages.ts b/packages/ra-core/src/i18n/TranslationMessages.ts
index 52808d687a8..8c41c786228 100644
--- a/packages/ra-core/src/i18n/TranslationMessages.ts
+++ b/packages/ra-core/src/i18n/TranslationMessages.ts
@@ -35,6 +35,8 @@ export interface TranslationMessages extends StringMap {
open_menu: string;
close_menu: string;
update: string;
+ move_up: string;
+ move_down: string;
};
boolean: {
[key: string]: StringMap | string;
diff --git a/packages/ra-language-english/src/index.ts b/packages/ra-language-english/src/index.ts
index 62b3003e4c1..f1a526dfaaa 100644
--- a/packages/ra-language-english/src/index.ts
+++ b/packages/ra-language-english/src/index.ts
@@ -31,6 +31,8 @@ const englishMessages: TranslationMessages = {
open_menu: 'Open menu',
close_menu: 'Close menu',
update: 'Update',
+ move_up: 'Move up',
+ move_down: 'Move down',
},
boolean: {
true: 'Yes',
diff --git a/packages/ra-language-french/src/index.ts b/packages/ra-language-french/src/index.ts
index ec1c8a15c87..c8774214479 100644
--- a/packages/ra-language-french/src/index.ts
+++ b/packages/ra-language-french/src/index.ts
@@ -32,6 +32,8 @@ const frenchMessages: TranslationMessages = {
open_menu: 'Ouvrir le menu',
close_menu: 'Fermer le menu',
update: 'Modifier',
+ move_up: 'Déplacer vers le haut',
+ move_down: 'Déplacer vers le bas',
},
boolean: {
true: 'Oui',
diff --git a/packages/ra-ui-materialui/src/button/IconButtonWithTooltip.tsx b/packages/ra-ui-materialui/src/button/IconButtonWithTooltip.tsx
new file mode 100644
index 00000000000..9629da1a973
--- /dev/null
+++ b/packages/ra-ui-materialui/src/button/IconButtonWithTooltip.tsx
@@ -0,0 +1,51 @@
+import * as React from 'react';
+import { MouseEvent } from 'react';
+import { IconButton, IconButtonProps, Tooltip } from '@material-ui/core';
+import { useTranslate } from 'ra-core';
+
+/**
+ * An IconButton with a tooltip which ensures the tooltip is closed on click to avoid ghost tooltips
+ * when the button position changes.
+ */
+export const IconButtonWithTooltip = ({
+ label,
+ onClick,
+ ...props
+}: IconButtonWithTooltipProps) => {
+ const translate = useTranslate();
+ const [open, setOpen] = React.useState(false);
+
+ const handleClose = () => {
+ setOpen(false);
+ };
+
+ const handleOpen = () => {
+ setOpen(true);
+ };
+
+ const translatedLabel = translate(label, { _: label });
+
+ const handleClick = (event: MouseEvent) => {
+ handleClose();
+ onClick(event);
+ };
+
+ return (
+
+
+
+ );
+};
+
+export interface IconButtonWithTooltipProps extends IconButtonProps {
+ label: string;
+}
diff --git a/packages/ra-ui-materialui/src/button/index.ts b/packages/ra-ui-materialui/src/button/index.ts
index 93cc58b369f..1489acf5bda 100644
--- a/packages/ra-ui-materialui/src/button/index.ts
+++ b/packages/ra-ui-materialui/src/button/index.ts
@@ -26,6 +26,8 @@ export * from './DeleteButton';
export * from './DeleteWithConfirmButton';
export * from './DeleteWithUndoButton';
+export * from './IconButtonWithTooltip';
+
export type {
BulkDeleteButtonProps,
BulkDeleteWithConfirmButtonProps,
diff --git a/packages/ra-ui-materialui/src/detail/editFieldTypes.tsx b/packages/ra-ui-materialui/src/detail/editFieldTypes.tsx
index 9af5b891529..5fb4fc2a034 100644
--- a/packages/ra-ui-materialui/src/detail/editFieldTypes.tsx
+++ b/packages/ra-ui-materialui/src/detail/editFieldTypes.tsx
@@ -1,7 +1,7 @@
import * as React from 'react';
import { ReactNode, ReactElement } from 'react';
import SimpleForm from '../form/SimpleForm';
-import SimpleFormIterator from '../form/SimpleFormIterator';
+import { SimpleFormIterator } from '../form/SimpleFormIterator';
import ArrayInput from '../input/ArrayInput';
import BooleanInput from '../input/BooleanInput';
import DateInput from '../input/DateInput';
diff --git a/packages/ra-ui-materialui/src/form/SimpleFormIterator.spec.tsx b/packages/ra-ui-materialui/src/form/SimpleFormIterator.spec.tsx
index 0b66aad6797..415fe0d1db4 100644
--- a/packages/ra-ui-materialui/src/form/SimpleFormIterator.spec.tsx
+++ b/packages/ra-ui-materialui/src/form/SimpleFormIterator.spec.tsx
@@ -8,7 +8,7 @@ import * as React from 'react';
import { ArrayInput } from '../input';
import TextInput from '../input/TextInput';
import SimpleForm from './SimpleForm';
-import SimpleFormIterator from './SimpleFormIterator';
+import { SimpleFormIterator } from './SimpleFormIterator';
const theme = createMuiTheme();
@@ -266,10 +266,10 @@ describe(' ', () => {
const inputElements = queryAllByLabelText(
'resources.undefined.fields.email'
- );
+ ) as HTMLInputElement[];
expect(
- inputElements.map((inputElement: HTMLInputElement) => ({
+ inputElements.map(inputElement => ({
email: inputElement.value,
}))
).toEqual([{ email: '' }, { email: '' }]);
@@ -305,13 +305,13 @@ describe(' ', () => {
expect(inputElements.length).toBe(1);
});
- const inputElements = queryAllByLabelText('CustomLabel');
+ const inputElements = queryAllByLabelText(
+ 'CustomLabel'
+ ) as HTMLInputElement[];
- expect(
- inputElements.map(
- (inputElement: HTMLInputElement) => inputElement.value
- )
- ).toEqual(['']);
+ expect(inputElements.map(inputElement => inputElement.value)).toEqual([
+ '',
+ ]);
expect(queryAllByText('ra.action.remove').length).toBe(1);
});
@@ -348,13 +348,13 @@ describe(' ', () => {
expect(inputElements.length).toBe(1);
});
- const inputElements = queryAllByLabelText('CustomLabel');
+ const inputElements = queryAllByLabelText(
+ 'CustomLabel'
+ ) as HTMLInputElement[];
- expect(
- inputElements.map(
- (inputElement: HTMLInputElement) => inputElement.value
- )
- ).toEqual(['5']);
+ expect(inputElements.map(inputElement => inputElement.value)).toEqual([
+ '5',
+ ]);
expect(queryAllByText('ra.action.remove').length).toBe(1);
});
@@ -380,10 +380,10 @@ describe(' ', () => {
const inputElements = queryAllByLabelText(
'resources.undefined.fields.email'
- );
+ ) as HTMLInputElement[];
expect(
- inputElements.map((inputElement: HTMLInputElement) => ({
+ inputElements.map(inputElement => ({
email: inputElement.value,
}))
).toEqual(emails);
@@ -397,18 +397,78 @@ describe(' ', () => {
await waitFor(() => {
const inputElements = queryAllByLabelText(
'resources.undefined.fields.email'
- );
+ ) as HTMLInputElement[];
expect(
- inputElements.map((inputElement: HTMLInputElement) => ({
+ inputElements.map(inputElement => ({
email: inputElement.value,
}))
).toEqual([{ email: 'bar@foo.com' }]);
});
});
+ it('should reorder children on reorder buttons click', async () => {
+ const emails = [{ email: 'foo@bar.com' }, { email: 'bar@foo.com' }];
+
+ const { queryAllByLabelText } = renderWithRedux(
+
+
+
+
+
+
+
+
+
+
+
+
+
+ );
+
+ const inputElements = queryAllByLabelText(
+ 'resources.undefined.fields.email'
+ ) as HTMLInputElement[];
+
+ expect(
+ inputElements.map(inputElement => ({
+ email: inputElement.value,
+ }))
+ ).toEqual(emails);
+
+ const moveDownFirstButton = queryAllByLabelText('ra.action.move_down');
+
+ fireEvent.click(moveDownFirstButton[0]);
+ await waitFor(() => {
+ const inputElements = queryAllByLabelText(
+ 'resources.undefined.fields.email'
+ ) as HTMLInputElement[];
+
+ expect(
+ inputElements.map(inputElement => ({
+ email: inputElement.value,
+ }))
+ ).toEqual([{ email: 'bar@foo.com' }, { email: 'foo@bar.com' }]);
+ });
+
+ const moveUpButton = queryAllByLabelText('ra.action.move_up');
+
+ fireEvent.click(moveUpButton[1]);
+ await waitFor(() => {
+ const inputElements = queryAllByLabelText(
+ 'resources.undefined.fields.email'
+ ) as HTMLInputElement[];
+
+ expect(
+ inputElements.map(inputElement => ({
+ email: inputElement.value,
+ }))
+ ).toEqual([{ email: 'foo@bar.com' }, { email: 'bar@foo.com' }]);
+ });
+ });
+
it('should not display the default add button if a custom add button is passed', () => {
- const { queryAllByText } = renderWithRedux(
+ const { getByText, queryAllByText } = renderWithRedux(
@@ -423,11 +483,13 @@ describe(' ', () => {
);
+
expect(queryAllByText('ra.action.add').length).toBe(0);
+ expect(getByText('Custom Add Button')).not.toBeNull();
});
it('should not display the default remove button if a custom remove button is passed', () => {
- const { queryAllByText } = renderWithRedux(
+ const { getByText, queryAllByText } = renderWithRedux(
@@ -450,30 +512,11 @@ describe(' ', () => {
);
expect(queryAllByText('ra.action.remove').length).toBe(0);
+ expect(getByText('Custom Remove Button')).not.toBeNull();
});
- it('should display the custom add button', () => {
- const { getByText } = renderWithRedux(
-
-
-
-
- Custom Add Button}
- >
-
-
-
-
-
-
- );
-
- expect(getByText('Custom Add Button')).not.toBeNull();
- });
-
- it('should display the custom remove button', () => {
- const { getByText } = renderWithRedux(
+ it('should not display the default reorder element if a custom reorder element is passed', () => {
+ const { getByText, queryAllByLabelText } = renderWithRedux(
@@ -482,8 +525,8 @@ describe(' ', () => {
>
Custom Remove Button
+ reOrderButtons={
+ Custom reorder Button
}
>
@@ -495,7 +538,9 @@ describe(' ', () => {
);
- expect(getByText('Custom Remove Button')).not.toBeNull();
+ expect(queryAllByLabelText('ra.action.move_up').length).toBe(0);
+ expect(queryAllByLabelText('ra.action.move_down').length).toBe(0);
+ expect(getByText('Custom reorder Button')).not.toBeNull();
});
it('should display custom row label', () => {
diff --git a/packages/ra-ui-materialui/src/form/SimpleFormIterator.tsx b/packages/ra-ui-materialui/src/form/SimpleFormIterator.tsx
index f6f9a8a5051..f699719306e 100644
--- a/packages/ra-ui-materialui/src/form/SimpleFormIterator.tsx
+++ b/packages/ra-ui-materialui/src/form/SimpleFormIterator.tsx
@@ -1,104 +1,36 @@
-import Button from '@material-ui/core/Button';
-import FormHelperText from '@material-ui/core/FormHelperText';
-import { makeStyles } from '@material-ui/core/styles';
-import Typography from '@material-ui/core/Typography';
-import AddIcon from '@material-ui/icons/AddCircleOutline';
-import CloseIcon from '@material-ui/icons/RemoveCircleOutline';
-import classNames from 'classnames';
-import get from 'lodash/get';
-import PropTypes from 'prop-types';
-import { Record, useTranslate, ValidationError } from 'ra-core';
import * as React from 'react';
import {
Children,
cloneElement,
- FC,
+ MouseEvent,
+ MouseEventHandler,
isValidElement,
ReactElement,
+ ReactNode,
useRef,
} from 'react';
+import { Button, FormHelperText, Typography } from '@material-ui/core';
+import { makeStyles } from '@material-ui/core/styles';
+import AddIcon from '@material-ui/icons/AddCircleOutline';
+import CloseIcon from '@material-ui/icons/RemoveCircleOutline';
+import ArrowUpwardIcon from '@material-ui/icons/ArrowUpward';
+import ArrowDownwardIcon from '@material-ui/icons/ArrowDownward';
+import classNames from 'classnames';
+import get from 'lodash/get';
+import PropTypes from 'prop-types';
+import { Record, useTranslate, ValidationError } from 'ra-core';
import { FieldArrayRenderProps } from 'react-final-form-arrays';
import { CSSTransition, TransitionGroup } from 'react-transition-group';
+
import { ClassesOverride } from '../types';
+import { IconButtonWithTooltip } from '../button';
import FormInput from './FormInput';
-const useStyles = makeStyles(
- theme => ({
- root: {
- padding: 0,
- marginBottom: 0,
- '& > li:last-child': {
- borderBottom: 'none',
- },
- },
- line: {
- display: 'flex',
- listStyleType: 'none',
- borderBottom: `solid 1px ${theme.palette.divider}`,
- [theme.breakpoints.down('xs')]: { display: 'block' },
- '&.fade-enter': {
- opacity: 0.01,
- transform: 'translateX(100vw)',
- },
- '&.fade-enter-active': {
- opacity: 1,
- transform: 'translateX(0)',
- transition: 'all 500ms ease-in',
- },
- '&.fade-exit': {
- opacity: 1,
- transform: 'translateX(0)',
- },
- '&.fade-exit-active': {
- opacity: 0.01,
- transform: 'translateX(100vw)',
- transition: 'all 500ms ease-in',
- },
- },
- index: {
- width: '3em',
- paddingTop: '1em',
- [theme.breakpoints.down('sm')]: { display: 'none' },
- },
- form: { flex: 2 },
- action: {
- paddingTop: '0.5em',
- },
- leftIcon: {
- marginRight: theme.spacing(1),
- },
- }),
- { name: 'RaSimpleFormIterator' }
-);
-
-const DefaultAddButton = props => {
- const classes = useStyles(props);
- const translate = useTranslate();
- return (
-
-
- {translate('ra.action.add')}
-
- );
-};
-
-const DefaultLabelFn = index => index + 1;
-
-const DefaultRemoveButton = props => {
- const classes = useStyles(props);
- const translate = useTranslate();
- return (
-
-
- {translate('ra.action.remove')}
-
- );
-};
-
-const SimpleFormIterator: FC = props => {
+export const SimpleFormIterator = (props: SimpleFormIteratorProps) => {
const {
addButton = ,
removeButton = ,
+ reOrderButtons = ,
basePath,
children,
className,
@@ -110,6 +42,7 @@ const SimpleFormIterator: FC = props => {
disabled,
disableAdd,
disableRemove,
+ disableReordering,
variant,
margin,
TransitionProps,
@@ -137,16 +70,19 @@ const SimpleFormIterator: FC = props => {
nextId.current > 0 ? Array.from(Array(nextId.current).keys()) : []
);
- const removeField = index => () => {
+ const removeField = (index: number) => () => {
ids.current.splice(index, 1);
- fields.remove(index);
+ fields?.remove(index);
};
// Returns a boolean to indicate whether to disable the remove button for certain fields.
// If disableRemove is a function, then call the function with the current record to
// determining if the button should be disabled. Otherwise, use a boolean property that
// enables or disables the button for all of the fields.
- const disableRemoveField = (record, disableRemove) => {
+ const disableRemoveField = (
+ record: Record,
+ disableRemove: boolean | DisableRemoveFunction
+ ) => {
if (typeof disableRemove === 'boolean') {
return disableRemove;
}
@@ -155,11 +91,13 @@ const SimpleFormIterator: FC = props => {
const addField = () => {
ids.current.push(nextId.current++);
- fields.push(undefined);
+ fields?.push(undefined);
};
// add field and call the onClick event of the button passed as addButton prop
- const handleAddButtonClick = originalOnClickHandler => event => {
+ const handleAddButtonClick = (
+ originalOnClickHandler: MouseEventHandler
+ ) => (event: MouseEvent) => {
addField();
if (originalOnClickHandler) {
originalOnClickHandler(event);
@@ -168,15 +106,22 @@ const SimpleFormIterator: FC = props => {
// remove field and call the onClick event of the button passed as removeButton prop
const handleRemoveButtonClick = (
- originalOnClickHandler,
- index
- ) => event => {
+ originalOnClickHandler: MouseEventHandler,
+ index: number
+ ) => (event: MouseEvent) => {
removeField(index)();
if (originalOnClickHandler) {
originalOnClickHandler(event);
}
};
+ const handleReorder = (origin: number, destination: number) => {
+ const item = ids.current[origin];
+ ids.current[origin] = ids.current[destination];
+ ids.current[destination] = item;
+ fields?.move(origin, destination);
+ };
+
const records = get(record, source);
return fields ? (
@@ -195,12 +140,27 @@ const SimpleFormIterator: FC = props => {
{...TransitionProps}
>
-
- {getItemLabel(index)}
-
+
+
+
+ {getItemLabel(index)}
+
+ {!disabled &&
+ !disableReordering &&
+ cloneElement(reOrderButtons, {
+ index,
+ max: fields.length,
+ onReorder: handleReorder,
+ className: classNames(
+ 'button-reorder',
+ `button-reorder-${source}-${index}`
+ ),
+ })}
+
+
{Children.map(
children,
@@ -322,12 +282,14 @@ export interface SimpleFormIteratorProps
extends Partial, 'meta'>> {
addButton?: ReactElement;
basePath?: string;
+ children?: ReactNode;
classes?: ClassesOverride;
className?: string;
defaultValue?: any;
disabled?: boolean;
disableAdd?: boolean;
disableRemove?: boolean | DisableRemoveFunction;
+ disableReordering?: boolean;
getItemLabel?: (index: number) => string;
margin?: 'none' | 'normal' | 'dense';
meta?: {
@@ -337,10 +299,118 @@ export interface SimpleFormIteratorProps
};
record?: Record;
removeButton?: ReactElement;
+ reOrderButtons?: ReactElement;
resource?: string;
source?: string;
TransitionProps?: any;
variant?: 'standard' | 'outlined' | 'filled';
}
-export default SimpleFormIterator;
+const useStyles = makeStyles(
+ theme => ({
+ root: {
+ padding: 0,
+ marginBottom: 0,
+ '& > li:last-child': {
+ borderBottom: 'none',
+ },
+ },
+ line: {
+ display: 'flex',
+ listStyleType: 'none',
+ borderBottom: `solid 1px ${theme.palette.divider}`,
+ [theme.breakpoints.down('xs')]: { display: 'block' },
+ '&.fade-enter': {
+ opacity: 0.01,
+ transform: 'translateX(100vw)',
+ },
+ '&.fade-enter-active': {
+ opacity: 1,
+ transform: 'translateX(0)',
+ transition: 'all 500ms ease-in',
+ },
+ '&.fade-exit': {
+ opacity: 1,
+ transform: 'translateX(0)',
+ },
+ '&.fade-exit-active': {
+ opacity: 0.01,
+ transform: 'translateX(100vw)',
+ transition: 'all 500ms ease-in',
+ },
+ },
+ index: {
+ [theme.breakpoints.down('sm')]: { display: 'none' },
+ marginRight: theme.spacing(1),
+ },
+ indexContainer: {
+ display: 'flex',
+ paddingTop: '1em',
+ marginRight: theme.spacing(1),
+ alignItems: 'center',
+ },
+ form: { flex: 2 },
+ action: {
+ paddingTop: '0.5em',
+ },
+ leftIcon: {
+ marginRight: theme.spacing(1),
+ },
+ }),
+ { name: 'RaSimpleFormIterator' }
+);
+
+const DefaultAddButton = props => {
+ const classes = useStyles(props);
+ const translate = useTranslate();
+ return (
+
+
+ {translate('ra.action.add')}
+
+ );
+};
+
+const DefaultLabelFn = index => index + 1;
+
+const DefaultRemoveButton = props => {
+ const classes = useStyles(props);
+ const translate = useTranslate();
+ return (
+
+
+ {translate('ra.action.remove')}
+
+ );
+};
+
+const DefaultReOrderButtons = ({
+ className,
+ index,
+ max,
+ onReorder,
+}: {
+ className?: string;
+ index?: number;
+ max?: number;
+ onReorder?: (origin: number, destination: number) => void;
+}) => (
+
+
onReorder(index, index - 1)}
+ disabled={index <= 0}
+ >
+
+
+
onReorder(index, index + 1)}
+ disabled={max == null || index >= max - 1}
+ >
+
+
+
+);
diff --git a/packages/ra-ui-materialui/src/form/index.tsx b/packages/ra-ui-materialui/src/form/index.tsx
index 1c414079041..e8cee28545a 100644
--- a/packages/ra-ui-materialui/src/form/index.tsx
+++ b/packages/ra-ui-materialui/src/form/index.tsx
@@ -1,14 +1,12 @@
import FormInput, { FormInputProps } from './FormInput';
import SimpleForm, { SimpleFormProps } from './SimpleForm';
-import SimpleFormIterator, {
- SimpleFormIteratorProps,
-} from './SimpleFormIterator';
import TabbedFormTabs, { TabbedFormTabsProps } from './TabbedFormTabs';
import Toolbar, { ToolbarProps } from './Toolbar';
import getFormInitialValues from './getFormInitialValues';
import { SimpleFormView, SimpleFormViewProps } from './SimpleFormView';
import { TabbedFormView, TabbedFormViewProps } from './TabbedFormView';
+export * from './SimpleFormIterator';
export * from './TabbedForm';
export * from './FormTab';
export * from './FormTabHeader';
@@ -16,7 +14,6 @@ export * from './FormTabHeader';
export type {
FormInputProps,
SimpleFormProps,
- SimpleFormIteratorProps,
TabbedFormTabsProps,
SimpleFormViewProps,
TabbedFormViewProps,
@@ -27,7 +24,6 @@ export {
FormInput,
SimpleForm,
SimpleFormView,
- SimpleFormIterator,
TabbedFormTabs,
TabbedFormView,
Toolbar,
diff --git a/packages/ra-ui-materialui/src/input/ArrayInput.spec.tsx b/packages/ra-ui-materialui/src/input/ArrayInput.spec.tsx
index 9258eb7ab5a..2949002322f 100644
--- a/packages/ra-ui-materialui/src/input/ArrayInput.spec.tsx
+++ b/packages/ra-ui-materialui/src/input/ArrayInput.spec.tsx
@@ -6,7 +6,7 @@ import arrayMutators from 'final-form-arrays';
import ArrayInput from './ArrayInput';
import NumberInput from './NumberInput';
import TextInput from './TextInput';
-import SimpleFormIterator from '../form/SimpleFormIterator';
+import { SimpleFormIterator } from '../form/SimpleFormIterator';
import { minLength, required } from 'ra-core';
describe(' ', () => {
diff --git a/packages/ra-ui-materialui/src/input/DateTimeInput.spec.tsx b/packages/ra-ui-materialui/src/input/DateTimeInput.spec.tsx
index 424cdd65cbd..f7538bb2faf 100644
--- a/packages/ra-ui-materialui/src/input/DateTimeInput.spec.tsx
+++ b/packages/ra-ui-materialui/src/input/DateTimeInput.spec.tsx
@@ -8,7 +8,7 @@ import format from 'date-fns/format';
import DateTimeInput from './DateTimeInput';
import ArrayInput from './ArrayInput';
-import SimpleFormIterator from '../form/SimpleFormIterator';
+import { SimpleFormIterator } from '../form/SimpleFormIterator';
import { FormApi } from 'final-form';
describe(' ', () => {
From ae466087ffddc621f7ffa6940b5bc10f9b7a1819 Mon Sep 17 00:00:00 2001
From: Gildas Garcia <1122076+djhi@users.noreply.github.com>
Date: Wed, 1 Sep 2021 16:18:23 +0200
Subject: [PATCH 2/3] Apply review
---
.../src/form/SimpleFormIterator.spec.tsx | 46 +++++++++++++++++++
1 file changed, 46 insertions(+)
diff --git a/packages/ra-ui-materialui/src/form/SimpleFormIterator.spec.tsx b/packages/ra-ui-materialui/src/form/SimpleFormIterator.spec.tsx
index 415fe0d1db4..f21e5da66af 100644
--- a/packages/ra-ui-materialui/src/form/SimpleFormIterator.spec.tsx
+++ b/packages/ra-ui-materialui/src/form/SimpleFormIterator.spec.tsx
@@ -626,4 +626,50 @@ describe(' ', () => {
fireEvent.click(getByText('Custom Remove Button'));
expect(onClick).toHaveBeenCalled();
});
+
+ it('should display the custom add button', () => {
+ const { getByText } = renderWithRedux(
+
+
+
+
+ Custom Add Button}
+ >
+
+
+
+
+
+
+ );
+
+ expect(getByText('Custom Add Button')).not.toBeNull();
+ });
+
+ it('should display the custom remove button', () => {
+ const { getByText } = renderWithRedux(
+
+
+
+
+
+ Custom Remove Button
+ }
+ >
+
+
+
+
+
+
+
+ );
+
+ expect(getByText('Custom Remove Button')).not.toBeNull();
+ });
});
From 2601ea0ce423ad3a0c48d55489726ce0dfcabd04 Mon Sep 17 00:00:00 2001
From: Gildas Garcia <1122076+djhi@users.noreply.github.com>
Date: Wed, 1 Sep 2021 16:20:45 +0200
Subject: [PATCH 3/3] Revert Input documentation formatting changes
---
docs/Inputs.md | 1119 +++++++++++++++++-------------------------------
1 file changed, 396 insertions(+), 723 deletions(-)
diff --git a/docs/Inputs.md b/docs/Inputs.md
index 15511bd757b..e992937b7c5 100644
--- a/docs/Inputs.md
+++ b/docs/Inputs.md
@@ -1,6 +1,6 @@
---
layout: default
-title: 'Input Components'
+title: "Input Components"
---
# Input Components
@@ -9,33 +9,17 @@ An `Input` component displays an input, or a dropdown list, a list of radio butt
```jsx
// in src/posts.js
-import * as React from 'react';
-import {
- Edit,
- SimpleForm,
- ReferenceInput,
- SelectInput,
- TextInput,
- required,
-} from 'react-admin';
+import * as React from "react";
+import { Edit, SimpleForm, ReferenceInput, SelectInput, TextInput, required } from 'react-admin';
-export const PostEdit = props => (
+export const PostEdit = (props) => (
} {...props}>
-
+
-
+
@@ -46,15 +30,15 @@ export const PostEdit = props => (
All input components accept the following props:
-| Prop | Required | Type | Default | Description |
-| --------------- | -------- | ------------------------- | ------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
-| `source` | Required | `string` | - | Name of the entity property to use for the input value |
+| Prop | Required | Type | Default | Description |
+| --------------- | -------- | ------------------------- | ------- | ---------------------------------------------------------------------------------------------------------------------------- |
+| `source` | Required | `string` | - | Name of the entity property to use for the input value |
| `label` | Optional | `string` | - | Input label. In i18n apps, the label is passed to the `translate` function. Defaults to the humanized `source` when omitted. Set `label={false}` to hide the label. |
-| `validate` | Optional | `Function` | `array` | - | Validation rules for the current property. See the [Validation Documentation](./CreateEdit.md#validation) for details. |
-| `helperText` | Optional | `string` | - | Text to be displayed under the input |
-| `fullWidth` | Optional | `boolean` | `false` | If `true`, the input will expand to fill the form width |
-| `className` | Optional | `string` | - | Class name (usually generated by JSS) to customize the look and feel of the field element itself |
-| `formClassName` | Optional | `string` | - | Class name to be applied to the container of the input (e.g. the `` forming each row in `
`) |
+| `validate` | Optional | `Function` | `array` | - | Validation rules for the current property. See the [Validation Documentation](./CreateEdit.md#validation) for details. |
+| `helperText` | Optional | `string` | - | Text to be displayed under the input |
+| `fullWidth` | Optional | `boolean` | `false` | If `true`, the input will expand to fill the form width |
+| `className` | Optional | `string` | - | Class name (usually generated by JSS) to customize the look and feel of the field element itself |
+| `formClassName` | Optional | `string` | - | Class name to be applied to the container of the input (e.g. the `` forming each row in `
`) |
```jsx
@@ -102,7 +86,7 @@ Then you can display a text input to edit the author first name as follows:
```jsx
import { BooleanInput } from 'react-admin';
- ;
+
```
![BooleanInput](./img/boolean-input.png)
@@ -112,7 +96,6 @@ This input does not handle `null` values. You would need the ` ,
}}
-/>;
+/>
```
-
{% endraw %}
![CustomBooleanInputCheckIcon](./img/custom-switch-icon.png)
@@ -136,7 +118,7 @@ Refer to [Material UI Switch documentation](https://material-ui.com/api/switch)
```jsx
import { NullableBooleanInput } from 'react-admin';
- ;
+
```
![NullableBooleanInput](./img/nullable-boolean-input.gif)
@@ -152,7 +134,7 @@ englishMessages.ra.boolean.false = 'False label';
englishMessages.ra.boolean.true = 'True label';
const i18nProvider = polyglotI18nProvider(() => englishMessages, 'en');
- ;
+
```
Additionally, individual instances of `NullableBooleanInput` may be customized by setting the `nullLabel`, `falseLabel` and `trueLabel` properties. Values specified for those properties will be translated by react-admin.
@@ -166,7 +148,7 @@ import { NullableBooleanInput } from 'react-admin';
nullLabel="Either"
falseLabel="No"
trueLabel="Yes"
-/>;
+/>
```
![NullableBooleanInput](./img/nullable-boolean-input-null-label.png)
@@ -192,7 +174,7 @@ The appearance of `` depends on the browser, and falls back to a text
```jsx
import { DateInput } from 'react-admin';
- ;
+
```
`` also accepts the [common input props](./Inputs.md#common-input-props).
@@ -210,7 +192,7 @@ An input for editing dates with time. `` renders a standard brows
```jsx
import { DateTimeInput } from 'react-admin';
- ;
+
```
`` also accepts the [common input props](./Inputs.md#common-input-props).
@@ -225,17 +207,17 @@ import { DateTimeInput } from 'react-admin';
#### Properties
-| Prop | Required | Type | Default | Description |
-| --------------- | -------- | --------------------------- | -------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
-| `accept` | Optional | `string | string[]` | - | Accepted file type(s), e. g. 'image/\*,.pdf'. If left empty, all file types are accepted. Equivalent of the `accept` attribute of an ` `. See [MDN input docs](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input/file#accept) for syntax and examples. |
-| `children` | Optional | `ReactNode` | - | Element used to display the preview of an image (cloned several times if the select accepts multiple files). |
-| `minSize` | Optional | `number` | 0 | Minimum image size (in bytes), e.g. 5000 for 5KB |
-| `maxSize` | Optional | `number` | `Infinity` | Maximum image size (in bytes), e.g. 5000000 for 5MB |
-| `multiple` | Optional | `boolean` | `false` | Set to true if the input should accept a list of images, false if it should only accept one image |
-| `labelSingle` | Optional | `string` | 'ra.input.image. upload_single' | Invite displayed in the drop zone if the input accepts one image |
-| `labelMultiple` | Optional | `string` | 'ra.input.file. upload_multiple' | Invite displayed in the drop zone if the input accepts several images |
-| `placeholder` | Optional | `string` | `ReactNode` | - | Invite displayed in the drop zone, overrides `labelSingle` and `labelMultiple` |
-| `options` | Optional | `Object` | `{}` | Additional options passed to react-dropzone's `useDropzone()` hook. See [the react-dropzone source](https://github.com/react-dropzone/react-dropzone/blob/master/src/index.js) for details . |
+| Prop | Required | Type | Default | Description |
+| --------------- | -------- | --------------------------- | -------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
+| `accept` | Optional | `string | string[]` | - | Accepted file type(s), e. g. 'image/*,.pdf'. If left empty, all file types are accepted. Equivalent of the `accept` attribute of an ` `. See [MDN input docs](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input/file#accept) for syntax and examples. |
+| `children` | Optional | `ReactNode` | - | Element used to display the preview of an image (cloned several times if the select accepts multiple files). |
+| `minSize` | Optional | `number` | 0 | Minimum image size (in bytes), e.g. 5000 for 5KB |
+| `maxSize` | Optional | `number` | `Infinity` | Maximum image size (in bytes), e.g. 5000000 for 5MB |
+| `multiple` | Optional | `boolean` | `false` | Set to true if the input should accept a list of images, false if it should only accept one image |
+| `labelSingle` | Optional | `string` | 'ra.input.image. upload_single' | Invite displayed in the drop zone if the input accepts one image |
+| `labelMultiple` | Optional | `string` | 'ra.input.file. upload_multiple' | Invite displayed in the drop zone if the input accepts several images |
+| `placeholder` | Optional | `string` | `ReactNode` | - | Invite displayed in the drop zone, overrides `labelSingle` and `labelMultiple` |
+| `options` | Optional | `Object` | `{}` | Additional options passed to react-dropzone's `useDropzone()` hook. See [the react-dropzone source](https://github.com/react-dropzone/react-dropzone/blob/master/src/index.js) for details . |
`` also accepts the [common input props](./Inputs.md#common-input-props).
@@ -260,12 +242,7 @@ The `ImageInput` component accepts an `options` prop, allowing to set the [react
If the default Dropzone label doesn't fit with your need, you can pass a `placeholder` prop to overwrite it. The value can be anything React can render (`PropTypes.node`):
```jsx
-Drop your file here}
->
+Drop your file here}>
```
@@ -321,17 +298,12 @@ Writing a custom preview component is quite straightforward: it's a standard [fi
When receiving **new** files, `FileInput` will add a `rawFile` property to the object passed as the `record` prop of children. This `rawFile` is the [File](https://developer.mozilla.org/en-US/docs/Web/API/File) instance of the newly added file. This can be useful to display information about size or MIME type inside a custom field.
-The `FileInput` component accepts an `options` prop into which you can pass all the [react-dropzone properties](https://react-dropzone.netlify.com/#proptypes).
+The `FileInput` component accepts an `options` prop into which you can pass all the [react-dropzone properties](https://react-dropzone.netlify.com/#proptypes).
If the default Dropzone label doesn't fit with your need, you can pass a `placeholder` prop to overwrite it. The value can be anything React can render (`PropTypes.node`):
```jsx
-Drop your file here}
->
+Drop your file here}>
```
@@ -382,7 +354,7 @@ It is necessary for numeric values because of a [known React bug](https://github
```jsx
import { NumberInput } from 'react-admin';
- ;
+
```
#### Properties
@@ -409,7 +381,7 @@ You can customize the `step` props (which defaults to "any"). For instance, to r
```jsx
import { PasswordInput } from 'react-admin';
- ;
+
```
![Password Input](./img/password-input.png)
@@ -418,7 +390,7 @@ It is possible to change the default behavior and display the value by default v
```jsx
import { PasswordInput } from 'react-admin';
- ;
+
```
![Password Input (visible)](./img/password-input-visible.png)
@@ -426,14 +398,9 @@ import { PasswordInput } from 'react-admin';
**Tip**: It is possible to set the [`autocomplete` attribute](https://developer.mozilla.org/fr/docs/Web/HTML/Attributs/autocomplete) by injecting an input props:
{% raw %}
-
```jsx
-
+
```
-
{% endraw %}
### ``
@@ -454,57 +421,50 @@ Then use it as a normal input component:
```jsx
import RichTextInput from 'ra-input-rich-text';
- ;
+
```
You can customize the rich text editor toolbar using the `toolbar` attribute, as described on the [Quill official toolbar documentation](https://quilljs.com/docs/modules/toolbar/).
```jsx
-
+
```
If you need to add Quill `modules` or `themes`, you can do so by passing them in the `options` prop.
{% raw %}
-
```jsx
```
-
{% endraw %}
If you need more customization, you can access the quill object through the `configureQuill` callback that will be called just after its initialization.
```jsx
-const configureQuill = quill =>
- quill.getModule('toolbar').addHandler('bold', function (value) {
- this.quill.format('bold', value);
- });
+const configureQuill = quill => quill.getModule('toolbar').addHandler('bold', function (value) {
+ this.quill.format('bold', value)
+});
// ...
- ;
+
```
`` also accepts the [common input props](./Inputs.md#common-input-props).
-**Tip**: When used inside a material-ui `` (e.g in the default `` view), `` displays link tooltip as cut off when the user wants to add a hyperlink to a word located on the left side of the input. This is due to an incompatibility between material-ui's `` component and Quill's positioning algorithm for the link tooltip.
+**Tip**: When used inside a material-ui `` (e.g in the default `` view), `` displays link tooltip as cut off when the user wants to add a hyperlink to a word located on the left side of the input. This is due to an incompatibility between material-ui's `` component and Quill's positioning algorithm for the link tooltip.
To fix this problem, you should override the default card style, as follows:
@@ -532,7 +492,7 @@ import { Edit, SimpleForm, TextInput } from 'react-admin';
```jsx
import { TextInput } from 'react-admin';
- ;
+
```
#### Properties
@@ -564,7 +524,7 @@ You can make the `` component resettable using the `resettable` prop.
```jsx
import { TextInput } from 'react-admin';
- ;
+
```
![resettable TextInput](./img/resettable-text-input.gif)
@@ -585,37 +545,34 @@ Set the `choices` attribute to determine the options list (with `id`, `name` tup
```jsx
import { AutocompleteInput } from 'react-admin';
- ;
+
```
#### Properties
-| Prop | Required | Type | Default | Description |
-| ------------------------- | -------- | --------------------------------------------- | ----------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
-| `allowEmpty` | Optional | `boolean` | `false` | If `false` and the `searchText` typed did not match any suggestion, the `searchText` will revert to the current value when the field is blurred. If `true` and the `searchText` is set to `''` then the field will set the input value to `null`. |
-| `clearAlwaysVisible` | Optional | `boolean` | `false` | When `resettable` is true, set this prop to `true` to have the Reset button visible even when the field is empty |
-| `choices` | Required | `Object[]` | `-` | List of items to autosuggest |
-| `create` | Optional | `Element` | `-` | A React Element to render when users want to create a new choice |
-| `createLabel` | Optional | `string` | `ra.action.create` | The label for the menu item allowing users to create a new choice. Used when the filter is empty |
-| `createItemLabel` | Optional | `string` | `ra.action.create_item` | The label for the menu item allowing users to create a new choice. Used when the filter is not empty |
-| `emptyValue` | Optional | `any` | `''` | The value to use for the empty element |
-| `emptyText` | Optional | `string` | `''` | The text to use for the empty element |
-| `matchSuggestion` | Optional | `Function` | `-` | Required if `optionText` is a React element. Function returning a boolean indicating whether a choice matches the filter. `(filter, choice) => boolean` |
-| `onCreate` | Optional | `Function` | `-` | A function called with the current filter value when users choose to create a new choice. |
-| `optionText` | Optional | `string` | `Function` | `Component` | `name` | Field name of record to display in the suggestion item or function which accepts the correct record as argument (`(record)=> {string}`) |
-| `optionValue` | Optional | `string` | `id` | Field name of record containing the value to use as input value |
-| `inputText` | Optional | `Function` | `-` | If `optionText` is a custom Component, this function is needed to determine the text displayed for the current selection. |
-| `resettable` | Optional | `boolean` | `false` | Display a button to reset the text filter. Useful when using `` inside the list filters |
-| `setFilter` | Optional | `Function` | `null` | A callback to inform the `searchText` has changed and new `choices` can be retrieved based on this `searchText`. Signature `searchText => void`. This function is automatically setup when using `ReferenceInput`. |
-| `shouldRenderSuggestions` | Optional | `Function` | `() => true` | A function that returns a `boolean` to determine whether or not suggestions are rendered. Use this when working with large collections of data to improve performance and user experience. This function is passed into the underlying react-autosuggest component. Ex.`(value) => value.trim() > 2` |
-| `suggestionLimit` | Optional | `number` | `null` | Limits the numbers of suggestions that are shown in the dropdown list |
+| Prop | Required | Type | Default | Description |
+| ------------------------- | -------- | -------------- | ------------ | ------------------------------------ |
+| `allowEmpty` | Optional | `boolean` | `false` | If `false` and the `searchText` typed did not match any suggestion, the `searchText` will revert to the current value when the field is blurred. If `true` and the `searchText` is set to `''` then the field will set the input value to `null`. |
+| `clearAlwaysVisible` | Optional | `boolean` | `false` | When `resettable` is true, set this prop to `true` to have the Reset button visible even when the field is empty |
+| `choices` | Required | `Object[]` | `-` | List of items to autosuggest |
+| `create` | Optional | `Element` | `-` | A React Element to render when users want to create a new choice |
+| `createLabel` | Optional | `string` | `ra.action.create` | The label for the menu item allowing users to create a new choice. Used when the filter is empty |
+| `createItemLabel` | Optional | `string` | `ra.action.create_item` | The label for the menu item allowing users to create a new choice. Used when the filter is not empty |
+| `emptyValue` | Optional | `any` | `''` | The value to use for the empty element |
+| `emptyText` | Optional | `string` | `''` | The text to use for the empty element |
+| `matchSuggestion` | Optional | `Function` | `-` | Required if `optionText` is a React element. Function returning a boolean indicating whether a choice matches the filter. `(filter, choice) => boolean` |
+| `onCreate` | Optional | `Function` | `-` | A function called with the current filter value when users choose to create a new choice. |
+| `optionText` | Optional | `string` | `Function` | `Component` | `name` | Field name of record to display in the suggestion item or function which accepts the correct record as argument (`(record)=> {string}`) |
+| `optionValue` | Optional | `string` | `id` | Field name of record containing the value to use as input value |
+| `inputText` | Optional | `Function` | `-` | If `optionText` is a custom Component, this function is needed to determine the text displayed for the current selection. |
+| `resettable` | Optional | `boolean` | `false` | Display a button to reset the text filter. Useful when using `` inside the list filters |
+| `setFilter` | Optional | `Function` | `null` | A callback to inform the `searchText` has changed and new `choices` can be retrieved based on this `searchText`. Signature `searchText => void`. This function is automatically setup when using `ReferenceInput`. |
+| `shouldRenderSuggestions` | Optional | `Function` | `() => true` | A function that returns a `boolean` to determine whether or not suggestions are rendered. Use this when working with large collections of data to improve performance and user experience. This function is passed into the underlying react-autosuggest component. Ex.`(value) => value.trim() > 2` |
+| `suggestionLimit` | Optional | `number` | `null` | Limits the numbers of suggestions that are shown in the dropdown list |
`` also accepts the [common input props](./Inputs.md#common-input-props).
@@ -628,35 +585,26 @@ const choices = [
{ _id: 123, full_name: 'Leo Tolstoi', sex: 'M' },
{ _id: 456, full_name: 'Jane Austen', sex: 'F' },
];
- ;
+
```
`optionText` also accepts a function, so you can shape the option text at will:
```jsx
const choices = [
- { id: 123, first_name: 'Leo', last_name: 'Tolstoi' },
- { id: 456, first_name: 'Jane', last_name: 'Austen' },
+ { id: 123, first_name: 'Leo', last_name: 'Tolstoi' },
+ { id: 456, first_name: 'Jane', last_name: 'Austen' },
];
const optionRenderer = choice => `${choice.first_name} ${choice.last_name}`;
- ;
+
```
`optionText` also accepts a custom Component. However, as the underlying Autocomplete component requires that the current selection is a string, if you opt for a Component, you must pass a function as the `inputText` prop. This function should return text representation of the current selection:
```jsx
const choices = [
- { id: 123, first_name: 'Leo', last_name: 'Tolstoi', avatar: '/pengouin' },
- { id: 456, first_name: 'Jane', last_name: 'Austen', avatar: '/panda' },
+ { id: 123, first_name: 'Leo', last_name: 'Tolstoi', avatar:'/pengouin' },
+ { id: 456, first_name: 'Jane', last_name: 'Austen', avatar:'/panda' },
];
const OptionRenderer = choice => (
@@ -670,15 +618,15 @@ const inputText = choice => `${choice.first_name} ${choice.last_name}`;
choices={choices}
optionText={ }
inputText={inputText}
-/>;
+/>
```
The choices are translated by default, so you can use translation identifiers as choices:
```jsx
const choices = [
- { id: 'M', name: 'myroot.gender.male' },
- { id: 'F', name: 'myroot.gender.female' },
+ { id: 'M', name: 'myroot.gender.male' },
+ { id: 'F', name: 'myroot.gender.female' },
];
```
@@ -686,7 +634,7 @@ However, in some cases (e.g. inside a ``), you may not want the
In that case, set the `translateChoice` prop to `false`.
```jsx
-
+
```
If you want to limit the initial choices shown to the current value only, you can set the `limitChoicesToValue` prop.
@@ -697,16 +645,11 @@ Ex. ` { return val.trim().le
`` renders a [material-ui `` component](https://material-ui.com/api/text-field/). Use the `options` attribute to override any of the `` attributes:
{% raw %}
-
```jsx
-
+
```
-
{% endraw %}
**Tip**: If you want to populate the `choices` attribute with a list of related records, you should decorate `` with [``](#referenceinput), and leave the `choices` empty:
@@ -716,27 +659,21 @@ import { AutocompleteInput, ReferenceInput } from 'react-admin';
- ;
+
```
Lastly, would you need to override the props of the suggestion's container (a `Popper` element), you can specify them using the `options.suggestionsContainerProps`. For example:
{% raw %}
-
```jsx
-
+
```
-
{% endraw %}
-**Tip**: `` is a stateless component, so it only allows to _filter_ the list of choices, not to _extend_ it. If you need to populate the list of choices based on the result from a `fetch` call (and if [``](#referenceinput) doesn't cover your need), you'll have to [write your own Input component](#writing-your-own-input-component) based on material-ui `` component.
+**Tip**: `` is a stateless component, so it only allows to *filter* the list of choices, not to *extend* it. If you need to populate the list of choices based on the result from a `fetch` call (and if [``](#referenceinput) doesn't cover your need), you'll have to [write your own Input component](#writing-your-own-input-component) based on material-ui `` component.
#### Creating New Choices
@@ -745,11 +682,10 @@ The `` can allow users to create a new choice if either the `
Use the `onCreate` prop when you only require users to provide a simple string and a `prompt` is enough. You can return either the new choice directly or a Promise resolving to the new choice.
{% raw %}
-
```js
import { AutocompleteInput, Create, SimpleForm, TextInput } from 'react-admin';
-const PostCreate = props => {
+const PostCreate = (props) => {
const categories = [
{ name: 'Tech', id: 'tech' },
{ name: 'Lifestyle', id: 'lifestyle' },
@@ -761,10 +697,7 @@ const PostCreate = props => {
{
const newCategoryName = prompt('Enter a new category');
- const newCategory = {
- id: newCategoryName.toLowerCase(),
- name: newCategoryName,
- };
+ const newCategory = { id: newCategoryName.toLowerCase(), name: newCategoryName };
categories.push(newCategory);
return newCategory;
}}
@@ -774,15 +707,13 @@ const PostCreate = props => {
);
-};
+}
```
-
{% endraw %}
Use the `create` prop when you want a more polished or complex UI. For example a Material UI `
` asking for multiple fields because the choices are from a referenced resource.
{% raw %}
-
```js
import {
AutocompleteInput,
@@ -790,7 +721,7 @@ import {
ReferenceInput,
SimpleForm,
TextInput,
- useCreateSuggestionContext,
+ useCreateSuggestionContext
} from 'react-admin';
import {
@@ -803,7 +734,7 @@ import {
TextField,
} from '@material-ui/core';
-const PostCreate = props => {
+const PostCreate = (props) => {
return (
@@ -814,14 +745,14 @@ const PostCreate = props => {
);
-};
+}
const CreateCategory = () => {
const { filter, onCancel, onCreate } = useCreateSuggestionContext();
const [value, setValue] = React.useState(filter || '');
const [create] = useCreate('categories');
- const handleSubmit = event => {
+ const handleSubmit = (event) => {
event.preventDefault();
create(
{
@@ -860,7 +791,6 @@ const CreateCategory = () => {
);
};
```
-
{% endraw %}
#### CSS API
@@ -884,14 +814,11 @@ Set the `choices` attribute to determine the options (with `id`, `name` tuples):
```jsx
import { RadioButtonGroupInput } from 'react-admin';
- ;
+
```
#### Properties
@@ -916,80 +843,54 @@ const choices = [
{ _id: 123, full_name: 'Leo Tolstoi', sex: 'M' },
{ _id: 456, full_name: 'Jane Austen', sex: 'F' },
];
- ;
+
```
`optionText` also accepts a function, so you can shape the option text at will:
```jsx
const choices = [
- { id: 123, first_name: 'Leo', last_name: 'Tolstoi' },
- { id: 456, first_name: 'Jane', last_name: 'Austen' },
+ { id: 123, first_name: 'Leo', last_name: 'Tolstoi' },
+ { id: 456, first_name: 'Jane', last_name: 'Austen' },
];
const optionRenderer = choice => `${choice.first_name} ${choice.last_name}`;
- ;
+
```
`optionText` also accepts a React Element, that will be cloned and receive the related choice as the `record` prop. You can use Field components there.
```jsx
const choices = [
- { id: 123, first_name: 'Leo', last_name: 'Tolstoi' },
- { id: 456, first_name: 'Jane', last_name: 'Austen' },
+ { id: 123, first_name: 'Leo', last_name: 'Tolstoi' },
+ { id: 456, first_name: 'Jane', last_name: 'Austen' },
];
-const FullNameField = ({ record }) => (
-
- {record.first_name} {record.last_name}
-
-);
- }
-/>;
+const FullNameField = ({ record }) => {record.first_name} {record.last_name} ;
+ }/>
```
The choices are translated by default, so you can use translation identifiers as choices:
```jsx
const choices = [
- { id: 'M', name: 'myroot.gender.male' },
- { id: 'F', name: 'myroot.gender.female' },
+ { id: 'M', name: 'myroot.gender.male' },
+ { id: 'F', name: 'myroot.gender.female' },
];
```
However, in some cases (e.g. inside a ``), you may not want the choice to be translated. In that case, set the `translateChoice` prop to `false`.
```jsx
-
+
```
Lastly, use the `options` attribute if you want to override any of Material UI's `` attributes:
{% raw %}
-
```jsx
-
+
```
-
{% endraw %}
Refer to [Material UI RadioGroup documentation](https://material-ui.com/api/radio-group) for more details.
@@ -1001,7 +902,7 @@ import { RadioButtonGroupInput, ReferenceInput } from 'react-admin';
- ;
+
```
#### CSS API
@@ -1024,31 +925,28 @@ Set the `choices` attribute to determine the options (with `id`, `name` tuples):
```jsx
import { SelectInput } from 'react-admin';
- ;
+
```
#### Properties
-| Prop | Required | Type | Default | Description |
-| ----------------- | -------- | -------------------------- | ------------------ | -------------------------------------------------------------------------------------------------------------------------------------- |
-| `allowEmpty` | Optional | `boolean` | `false` | If true, the first option is an empty one |
-| `choices` | Required | `Object[]` | - | List of items to show as options |
-| `create` | Optional | `Element` | `-` | A React Element to render when users want to create a new choice |
-| `createLabel` | Optional | `string` | `ra.action.create` | The label for the menu item allowing users to create a new choice. Used when the filter is empty |
-| `emptyText` | Optional | `string` | '' | The text to display for the empty option |
-| `onCreate` | Optional | `Function` | `-` | A function called with the current filter value when users choose to create a new choice. |
-| `options` | Optional | `Object` | - | Props to pass to the underlying `` element |
-| `optionText` | Optional | `string` | `Function` | `name` | Field name of record to display in the suggestion item or function which accepts the current record as argument (`record => {string}`) |
-| `optionValue` | Optional | `string` | `id` | Field name of record containing the value to use as input value |
-| `resettable` | Optional | `boolean` | `false` | If `true`, display a button to reset the changes in this input value |
-| `translateChoice` | Optional | `boolean` | `true` | Whether the choices should be translated |
+| Prop | Required | Type | Default | Description |
+| ----------------- | -------- | -------------------------- | ------- | -------------------------------------------------------------------------------------------------------------------------------------- |
+| `allowEmpty` | Optional | `boolean` | `false` | If true, the first option is an empty one |
+| `choices` | Required | `Object[]` | - | List of items to show as options |
+| `create` | Optional | `Element` | `-` | A React Element to render when users want to create a new choice |
+| `createLabel` | Optional | `string` | `ra.action.create` | The label for the menu item allowing users to create a new choice. Used when the filter is empty |
+| `emptyText` | Optional | `string` | '' | The text to display for the empty option |
+| `onCreate` | Optional | `Function` | `-` | A function called with the current filter value when users choose to create a new choice. |
+| `options` | Optional | `Object` | - | Props to pass to the underlying `` element |
+| `optionText` | Optional | `string` | `Function` | `name` | Field name of record to display in the suggestion item or function which accepts the current record as argument (`record => {string}`) |
+| `optionValue` | Optional | `string` | `id` | Field name of record containing the value to use as input value |
+| `resettable` | Optional | `boolean` | `false` | If `true`, display a button to reset the changes in this input value |
+| `translateChoice` | Optional | `boolean` | `true` | Whether the choices should be translated |
`` also accepts the [common input props](./Inputs.md#common-input-props).
@@ -1061,76 +959,54 @@ const choices = [
{ _id: 123, full_name: 'Leo Tolstoi', sex: 'M' },
{ _id: 456, full_name: 'Jane Austen', sex: 'F' },
];
- ;
+
```
`optionText` also accepts a function, so you can shape the option text at will:
```jsx
const choices = [
- { id: 123, first_name: 'Leo', last_name: 'Tolstoi' },
- { id: 456, first_name: 'Jane', last_name: 'Austen' },
+ { id: 123, first_name: 'Leo', last_name: 'Tolstoi' },
+ { id: 456, first_name: 'Jane', last_name: 'Austen' },
];
const optionRenderer = choice => `${choice.first_name} ${choice.last_name}`;
- ;
+
```
`optionText` also accepts a React Element, that will be cloned and receive the related choice as the `record` prop. You can use Field components there.
```jsx
const choices = [
- { id: 123, first_name: 'Leo', last_name: 'Tolstoi' },
- { id: 456, first_name: 'Jane', last_name: 'Austen' },
+ { id: 123, first_name: 'Leo', last_name: 'Tolstoi' },
+ { id: 456, first_name: 'Jane', last_name: 'Austen' },
];
-const FullNameField = ({ record }) => (
-
- {record.first_name} {record.last_name}
-
-);
- }
-/>;
+const FullNameField = ({ record }) => {record.first_name} {record.last_name} ;
+ }/>
```
Enabling the `allowEmpty` props adds an empty choice (with a default `''` value, which you can overwrite with the `emptyValue` prop) on top of the options. You can furthermore customize the `MenuItem` for the empty choice by using the `emptyText` prop, which can receive either a string or a React Element, which doesn't receive any props.
```jsx
-
+
```
The choices are translated by default, so you can use translation identifiers as choices:
```jsx
const choices = [
- { id: 'M', name: 'myroot.gender.male' },
- { id: 'F', name: 'myroot.gender.female' },
+ { id: 'M', name: 'myroot.gender.male' },
+ { id: 'F', name: 'myroot.gender.female' },
];
```
However, in some cases, you may not want the choice to be translated. In that case, set the `translateChoice` prop to `false`.
```jsx
-
+
```
Note that `translateChoice` is set to `false` when `` is a child of ``.
@@ -1138,16 +1014,11 @@ Note that `translateChoice` is set to `false` when `` is a child of
Lastly, use the `options` attribute if you want to override any of Material UI's `` attributes:
{% raw %}
-
```jsx
-
+
```
-
{% endraw %}
Refer to [Material UI Select documentation](https://material-ui.com/api/select) for more details.
@@ -1159,7 +1030,7 @@ import { SelectInput, ReferenceInput } from 'react-admin';
- ;
+
```
If, instead of showing choices as a dropdown list, you prefer to display them as a list of radio buttons, try the [``](#radiobuttongroupinput). And if the list is too big, prefer the [``](#autocompleteinput).
@@ -1176,12 +1047,7 @@ const choices = [
{ _id: 456, full_name: 'Jane Austen', sex: 'F' },
{ _id: 1, full_name: 'System Administrator', sex: 'F', disabled: true },
];
- ;
+
```
You can use a custom field name by setting `disableValue` prop:
@@ -1192,13 +1058,7 @@ const choices = [
{ _id: 456, full_name: 'Jane Austen', sex: 'F' },
{ _id: 987, full_name: 'Jack Harden', sex: 'M', not_available: true },
];
- ;
+
```
#### Creating New Choices
@@ -1208,11 +1068,10 @@ The `` can allow users to create a new choice if either the `create
Use the `onCreate` prop when you only require users to provide a simple string and a `prompt` is enough. You can return either the new choice directly or a Promise resolving to the new choice.
{% raw %}
-
```js
import { SelectInput, Create, SimpleForm, TextInput } from 'react-admin';
-const PostCreate = props => {
+const PostCreate = (props) => {
const categories = [
{ name: 'Tech', id: 'tech' },
{ name: 'Lifestyle', id: 'lifestyle' },
@@ -1224,10 +1083,7 @@ const PostCreate = props => {
{
const newCategoryName = prompt('Enter a new category');
- const newCategory = {
- id: newCategoryName.toLowerCase(),
- name: newCategoryName,
- };
+ const newCategory = { id: newCategoryName.toLowerCase(), name: newCategoryName };
categories.push(newCategory);
return newCategory;
}}
@@ -1237,15 +1093,13 @@ const PostCreate = props => {
);
-};
+}
```
-
{% endraw %}
Use the `create` prop when you want a more polished or complex UI. For example a Material UI `` asking for multiple fields because the choices are from a referenced resource.
{% raw %}
-
```js
import {
SelectInput,
@@ -1253,7 +1107,7 @@ import {
ReferenceInput,
SimpleForm,
TextInput,
- useCreateSuggestionContext,
+ useCreateSuggestionContext
} from 'react-admin';
import {
@@ -1266,7 +1120,7 @@ import {
TextField,
} from '@material-ui/core';
-const PostCreate = props => {
+const PostCreate = (props) => {
return (
@@ -1277,14 +1131,14 @@ const PostCreate = props => {
);
-};
+}
const CreateCategory = () => {
const { filter, onCancel, onCreate } = useCreateSuggestionContext();
const [value, setValue] = React.useState(filter || '');
const [create] = useCreate('categories');
- const handleSubmit = event => {
+ const handleSubmit = (event) => {
event.preventDefault();
create(
{
@@ -1323,7 +1177,6 @@ const CreateCategory = () => {
);
};
```
-
{% endraw %}
#### CSS API
@@ -1343,19 +1196,14 @@ To edit arrays of data embedded inside a record, `` creates a list o
![ArrayInput](./img/array-input.gif)
```jsx
-import {
- ArrayInput,
- SimpleFormIterator,
- DateInput,
- TextInput,
-} from 'react-admin';
+import { ArrayInput, SimpleFormIterator, DateInput, TextInput } from 'react-admin';
- ;
+
```
`` allows editing of embedded arrays, like the `authors` field in the following `post` record:
@@ -1372,7 +1220,7 @@ import {
"user_id": 456,
"url": "co_writer",
}
- ]
+ ]
}
```
@@ -1380,77 +1228,52 @@ import {
`` expects a single child, which must be a *form iterator* component. A form iterator is a component accepting a `fields` object as passed by [react-final-form-array](https://github.com/final-form/react-final-form-arrays#fieldarrayrenderprops), and defining a layout for an array of fields. For instance, the `` component displays an array of react-admin Inputs in an unordered list (``), one sub-form by list item (``). It also provides controls for adding and removing a sub-record (a backlink in this example).
-You can pass `disableAdd`, `disableRemove` and `disableReordering` as props of `SimpleFormIterator`, to disable `ADD`, `REMOVE` and reordering buttons respectively. They are all enabled by default.
+You can pass `disableAdd` and `disableRemove` as props of `SimpleFormIterator`, to disable `ADD` and `REMOVE` button respectively. Default value of both is `false`.
```jsx
-import {
- ArrayInput,
- SimpleFormIterator,
- DateInput,
- TextInput,
-} from 'react-admin';
+import { ArrayInput, SimpleFormIterator, DateInput, TextInput } from 'react-admin';
-
+
- ;
+
```
-You can also use `addButton`, `removeButton` or `reOrderButtons` props to pass your custom add and remove buttons to `SimpleFormIterator`.
+You can also use `addButton` and `removeButton` props to pass your custom add and remove buttons to `SimpleFormIterator`.
```jsx
-import {
- ArrayInput,
- SimpleFormIterator,
- DateInput,
- TextInput,
-} from 'react-admin';
+import { ArrayInput, SimpleFormIterator, DateInput, TextInput } from 'react-admin';
- }
- removeButton={ }
- reOrderButtons={ }
- >
+ } removeButton={ }>
- ;
+
```
Furthermore, if you want to customize the label displayed for each item, you can pass a function to `` via the `getItemLabel` prop.
```jsx
-import {
- ArrayInput,
- SimpleFormIterator,
- DateInput,
- TextInput,
-} from 'react-admin';
+import { ArrayInput, SimpleFormIterator, DateInput, TextInput } from 'react-admin';
- `${index + 1}. link`}>
+ `${index + 1}. link`}>
- ;
+
```
**Note**: `` only accepts `Input` components as children. If you want to use some `Fields` instead, you have to use a `` to get the correct source, as follows:
```jsx
-import {
- ArrayInput,
- SimpleFormIterator,
- DateInput,
- TextInput,
- FormDataConsumer,
-} from 'react-admin';
+import { ArrayInput, SimpleFormIterator, DateInput, TextInput, FormDataConsumer } from 'react-admin';
-
+
{({ getSource, scopedFormData }) => {
@@ -1463,10 +1286,10 @@ import {
}}
- ;
+
```
-`` also accepts the [common input props](./Inputs.md#common-input-props) (except `format` and `parse`).
+`` also accepts the [common input props](./Inputs.md#common-input-props) (except `format` and `parse`).
**Important**: Note that asynchronous validators are not supported on the `` component due to a limitation of [react-final-form-arrays](https://github.com/final-form/react-final-form-arrays).
@@ -1482,35 +1305,32 @@ Set the `choices` attribute to determine the options list (with `id`, `name` tup
```jsx
import { AutocompleteArrayInput } from 'react-admin';
- ;
+
```
#### Properties
-| Prop | Required | Type | Default | Description |
-| ------------------------- | -------- | -------------------------- | ----------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
-| `allowEmpty` | Optional | `boolean` | `false` | If `true`, the first option is an empty one |
-| `allowDuplicates` | Optional | `boolean` | `false` | If `true`, the options can be selected several times |
-| `create` | Optional | `Element` | `-` | A React Element to render when users want to create a new choice |
-| `createLabel` | Optional | `string` | `ra.action.create` | The label for the menu item allowing users to create a new choice. Used when the filter is empty |
-| `createItemLabel` | Optional | `string` | `ra.action.create_item` | The label for the menu item allowing users to create a new choice. Used when the filter is not empty |
-| `debounce` | Optional | `number` | `250` | The delay to wait before calling the setFilter function injected when used in a ReferenceInput. |
-| `choices` | Required | `Object[]` | - | List of items to autosuggest |
-| `matchSuggestion` | Optional | `Function` | - | Required if `optionText` is a React element. Function returning a boolean indicating whether a choice matches the filter. `(filter, choice) => boolean` |
-| `onCreate` | Optional | `Function` | `-` | A function called with the current filter value when users choose to create a new choice. |
-| `optionValue` | Optional | `string` | `id` | Field name of record containing the value to use as input value |
-| `optionText` | Optional | `string` | `Function` | `name` | Field name of record to display in the suggestion item or function which accepts the current record as argument (`record => {string}`) |
-| `setFilter` | Optional | `Function` | `null` | A callback to inform the `searchText` has changed and new `choices` can be retrieved based on this `searchText`. Signature `searchText => void`. This function is automatically setup when using `ReferenceInput`. |
-| `shouldRenderSuggestions` | Optional | `Function` | `() => true` | A function that returns a `boolean` to determine whether or not suggestions are rendered. Use this when working with large collections of data to improve performance and user experience. This function is passed into the underlying react-autosuggest component. Ex.`(value) => value.trim() > 2` |
-| `source` | Required | `string` | - | Name of field to edit, its type should match the type retrieved from `optionValue` |
-| `suggestionLimit` | Optional | `number` | `null` | Limits the numbers of suggestions that are shown in the dropdown list |
+| Prop | Required | Type | Default | Description |
+| ------------------------- | -------- | -------------------------- | ------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
+| `allowEmpty` | Optional | `boolean` | `false` | If `true`, the first option is an empty one |
+| `allowDuplicates` | Optional | `boolean` | `false` | If `true`, the options can be selected several times |
+| `create` | Optional | `Element` | `-` | A React Element to render when users want to create a new choice |
+| `createLabel` | Optional | `string` | `ra.action.create` | The label for the menu item allowing users to create a new choice. Used when the filter is empty |
+| `createItemLabel` | Optional | `string` | `ra.action.create_item` | The label for the menu item allowing users to create a new choice. Used when the filter is not empty |
+| `debounce` | Optional | `number` | `250` | The delay to wait before calling the setFilter function injected when used in a ReferenceInput. |
+| `choices` | Required | `Object[]` | - | List of items to autosuggest |
+| `matchSuggestion` | Optional | `Function` | - | Required if `optionText` is a React element. Function returning a boolean indicating whether a choice matches the filter. `(filter, choice) => boolean` |
+| `onCreate` | Optional | `Function` | `-` | A function called with the current filter value when users choose to create a new choice. |
+| `optionValue` | Optional | `string` | `id` | Field name of record containing the value to use as input value |
+| `optionText` | Optional | `string` | `Function` | `name` | Field name of record to display in the suggestion item or function which accepts the current record as argument (`record => {string}`) |
+| `setFilter` | Optional | `Function` | `null` | A callback to inform the `searchText` has changed and new `choices` can be retrieved based on this `searchText`. Signature `searchText => void`. This function is automatically setup when using `ReferenceInput`. |
+| `shouldRenderSuggestions` | Optional | `Function` | `() => true` | A function that returns a `boolean` to determine whether or not suggestions are rendered. Use this when working with large collections of data to improve performance and user experience. This function is passed into the underlying react-autosuggest component. Ex.`(value) => value.trim() > 2` |
+| `source` | Required | `string` | - | Name of field to edit, its type should match the type retrieved from `optionValue` |
+| `suggestionLimit` | Optional | `number` | `null` | Limits the numbers of suggestions that are shown in the dropdown list |
`` also accepts the [common input props](./Inputs.md#common-input-props).
@@ -1523,46 +1343,33 @@ const choices = [
{ _id: 123, full_name: 'Leo Tolstoi', sex: 'M' },
{ _id: 456, full_name: 'Jane Austen', sex: 'F' },
];
- ;
+
```
`optionText` also accepts a function, so you can shape the option text at will:
```jsx
const choices = [
- { id: 123, first_name: 'Leo', last_name: 'Tolstoi' },
- { id: 456, first_name: 'Jane', last_name: 'Austen' },
+ { id: 123, first_name: 'Leo', last_name: 'Tolstoi' },
+ { id: 456, first_name: 'Jane', last_name: 'Austen' },
];
const optionRenderer = choice => `${choice.first_name} ${choice.last_name}`;
- ;
+
```
The choices are translated by default, so you can use translation identifiers as choices:
```jsx
const choices = [
- { id: 'M', name: 'myroot.gender.male' },
- { id: 'F', name: 'myroot.gender.female' },
+ { id: 'M', name: 'myroot.gender.male' },
+ { id: 'F', name: 'myroot.gender.female' },
];
```
However, in some cases (e.g. inside a ``), you may not want the choice to be translated. In that case, set the `translateChoice` prop to `false`.
```jsx
-
+
```
When dealing with a large amount of `choices` you may need to limit the number of suggestions that are rendered in order to maintain usable performance. The `shouldRenderSuggestions` is an optional prop that allows you to set conditions on when to render suggestions. An easy way to improve performance would be to skip rendering until the user has entered 2 or 3 characters in the search box. This lowers the result set significantly, and might be all you need (depending on your data set).
@@ -1571,16 +1378,11 @@ Ex. ` { return val.trim
Lastly, `` renders a [material-ui `` component](https://material-ui.com/api/text-field/). Use the `options` attribute to override any of the `` attributes:
{% raw %}
-
```jsx
-
+
```
-
{% endraw %}
**Tip**: Like many other inputs, `` accept a `fullWidth` prop.
@@ -1592,27 +1394,21 @@ import { AutocompleteArrayInput, ReferenceArrayInput } from 'react-admin';
- ;
+
```
If you need to override the props of the suggestion's container (a `Popper` element), you can specify them using the `options.suggestionsContainerProps`. For example:
{% raw %}
-
```jsx
-
+
```
-
{% endraw %}
-**Tip**: `` is a stateless component, so it only allows to _filter_ the list of choices, not to _extend_ it. If you need to populate the list of choices based on the result from a `fetch` call (and if [``](#referencearrayinput) doesn't cover your need), you'll have to [write your own Input component](#writing-your-own-input-component) based on [material-ui-chip-input](https://github.com/TeamWertarbyte/material-ui-chip-input).
+**Tip**: `` is a stateless component, so it only allows to *filter* the list of choices, not to *extend* it. If you need to populate the list of choices based on the result from a `fetch` call (and if [``](#referencearrayinput) doesn't cover your need), you'll have to [write your own Input component](#writing-your-own-input-component) based on [material-ui-chip-input](https://github.com/TeamWertarbyte/material-ui-chip-input).
**Tip**: React-admin's `` has only a capital A, while material-ui's `` has a capital A and a capital C. Don't mix up the components!
@@ -1623,16 +1419,10 @@ The `` can allow users to create a new choice if either
Use the `onCreate` prop when you only require users to provide a simple string and a `prompt` is enough. You can return either the new choice directly or a Promise resolving to the new choice.
{% raw %}
-
```js
-import {
- AutocompleteArrayInput,
- Create,
- SimpleForm,
- TextInput,
-} from 'react-admin';
+import { AutocompleteArrayInput, Create, SimpleForm, TextInput } from 'react-admin';
-const PostCreate = props => {
+const PostCreate = (props) => {
const tags = [
{ name: 'Tech', id: 'tech' },
{ name: 'Lifestyle', id: 'lifestyle' },
@@ -1644,10 +1434,7 @@ const PostCreate = props => {
{
const newTagName = prompt('Enter a new tag');
- const newTag = {
- id: newTagName.toLowerCase(),
- name: newTagName,
- };
+ const newTag = { id: newTagName.toLowerCase(), name: newTagName };
categories.push(newTag);
return newTag;
}}
@@ -1657,15 +1444,13 @@ const PostCreate = props => {
);
-};
+}
```
-
{% endraw %}
Use the `create` prop when you want a more polished or complex UI. For example a Material UI `` asking for multiple fields because the choices are from a referenced resource.
{% raw %}
-
```js
import {
AutocompleteArrayInput,
@@ -1673,7 +1458,7 @@ import {
ReferenceArrayInput,
SimpleForm,
TextInput,
- useCreateSuggestionContext,
+ useCreateSuggestionContext
} from 'react-admin';
import {
@@ -1686,7 +1471,7 @@ import {
TextField,
} from '@material-ui/core';
-const PostCreate = props => {
+const PostCreate = (props) => {
return (
@@ -1697,14 +1482,14 @@ const PostCreate = props => {
);
-};
+}
const CreateTag = () => {
const { filter, onCancel, onCreate } = useCreateSuggestionContext();
const [value, setValue] = React.useState(filter || '');
const [create] = useCreate('tags');
- const handleSubmit = event => {
+ const handleSubmit = (event) => {
event.preventDefault();
create(
{
@@ -1743,7 +1528,6 @@ const CreateTag = () => {
);
};
```
-
{% endraw %}
#### CSS API
@@ -1772,20 +1556,17 @@ Set the `choices` attribute to determine the options (with `id`, `name` tuples):
```jsx
import { CheckboxGroupInput } from 'react-admin';
- ;
+
```
#### Properties
| Prop | Required | Type | Default | Description |
-| ------------- | -------- | -------------------------- | ------- | -------------------------------------------------------------------------------------------------------------------------------------- |
+| ------------- | -------- | -------------------------- | ------- | ------------------------------------------------------------------------------------------------------------------------------------- |
| `choices` | Required | `Object[]` | - | List of choices |
| `optionText` | Optional | `string` | `Function` | `name` | Field name of record to display in the suggestion item or function which accepts the correct record as argument (`record => {string}`) |
| `optionValue` | Optional | `string` | `id` | Field name of record containing the value to use as input value |
@@ -1804,46 +1585,29 @@ const choices = [
{ _id: 123, full_name: 'Leo Tolstoi', sex: 'M' },
{ _id: 456, full_name: 'Jane Austen', sex: 'F' },
];
- ;
+
```
`optionText` also accepts a function, so you can shape the option text at will:
```jsx
const choices = [
- { id: 123, first_name: 'Leo', last_name: 'Tolstoi' },
- { id: 456, first_name: 'Jane', last_name: 'Austen' },
+ { id: 123, first_name: 'Leo', last_name: 'Tolstoi' },
+ { id: 456, first_name: 'Jane', last_name: 'Austen' },
];
const optionRenderer = choice => `${choice.first_name} ${choice.last_name}`;
- ;
+
```
`optionText` also accepts a React Element, that will be cloned and receive the related choice as the `record` prop. You can use Field components there.
```jsx
const choices = [
- { id: 123, first_name: 'Leo', last_name: 'Tolstoi' },
- { id: 456, first_name: 'Jane', last_name: 'Austen' },
+ { id: 123, first_name: 'Leo', last_name: 'Tolstoi' },
+ { id: 456, first_name: 'Jane', last_name: 'Austen' },
];
-const FullNameField = ({ record }) => (
-
- {record.first_name} {record.last_name}
-
-);
- }
-/>;
+const FullNameField = ({ record }) => {record.first_name} {record.last_name} ;
+ }/>
```
The choices are translated by default, so you can use translation identifiers as choices:
@@ -1859,25 +1623,20 @@ const choices = [
However, in some cases (e.g. inside a ``), you may not want the choice to be translated. In that case, set the `translateChoice` prop to `false`.
```jsx
-
+
```
Lastly, use the `options` attribute if you want to override any of Material UI's `` attributes:
{% raw %}
-
```jsx
import { FavoriteBorder, Favorite } from '@material-ui/icons';
- ,
- checkedIcon: ,
- }}
-/>;
+ ,
+ checkedIcon:
+}} />
```
-
{% endraw %}
#### CSS API
@@ -1901,7 +1660,7 @@ import { DualListInput } from '@react-admin/ra-relationships';
- ;
+
```
Check [the `ra-relationships` documentation](https://marmelab.com/ra-enterprise/modules/ra-relationships) for more details.
@@ -1914,19 +1673,19 @@ To let users choose several values in a list using a dropdown, use `` element |
-| `optionText` | Optional | `string` | `Function` | `name` | Field name of record to display in the suggestion item or function which accepts the current record as argument (`record => {string}`) |
-| `optionValue` | Optional | `string` | `id` | Field name of record containing the value to use as input value |
-| `resettable` | Optional | `boolean` | `false` | If `true`, display a button to reset the changes in this input value |
-| `translateChoice` | Optional | `boolean` | `true` | Whether the choices should be translated |
+| Prop | Required | Type | Default | Description |
+| ----------------- | -------- | -------------------------- | ------- | -------------------------------------------------------------------------------------------------------------------------------------- |
+| `allowEmpty` | Optional | `boolean` | `false` | If true, the first option is an empty one |
+| `choices` | Required | `Object[]` | - | List of items to show as options |
+| `create` | Optional | `Element` | `-` | A React Element to render when users want to create a new choice |
+| `createLabel` | Optional | `string` | `ra.action.create` | The label for the menu item allowing users to create a new choice. Used when the filter is empty |
+| `emptyText` | Optional | `string` | '' | The text to display for the empty option |
+| `onCreate` | Optional | `Function` | `-` | A function called with the current filter value when users choose to create a new choice. |
+| `options` | Optional | `Object` | - | Props to pass to the underlying `` element |
+| `optionText` | Optional | `string` | `Function` | `name` | Field name of record to display in the suggestion item or function which accepts the current record as argument (`record => {string}`) |
+| `optionValue` | Optional | `string` | `id` | Field name of record containing the value to use as input value |
+| `resettable` | Optional | `boolean` | `false` | If `true`, display a button to reset the changes in this input value |
+| `translateChoice` | Optional | `boolean` | `true` | Whether the choices should be translated |
`` also accepts the [common input props](./Inputs.md#common-input-props).
@@ -1937,17 +1696,13 @@ Set the `choices` attribute to determine the options (with `id`, `name` tuples):
```jsx
import { SelectArrayInput } from 'react-admin';
- ;
+
```
You can also customize the properties to use for the option name and value,
@@ -1955,44 +1710,35 @@ thanks to the `optionText` and `optionValue` attributes.
```jsx
const choices = [
- { _id: '1', name: 'Book', plural_name: 'Books' },
- { _id: '2', name: 'Video', plural_name: 'Videos' },
- { _id: '3', name: 'Audio', plural_name: 'Audios' },
+ { _id: '1', name: 'Book', plural_name: 'Books' },
+ { _id: '2', name: 'Video', plural_name: 'Videos' },
+ { _id: '3', name: 'Audio', plural_name: 'Audios' },
];
- ;
+
```
`optionText` also accepts a function, so you can shape the option text at will:
```jsx
const choices = [
- { id: '1', name: 'Book', quantity: 23 },
- { id: '2', name: 'Video', quantity: 56 },
- { id: '3', name: 'Audio', quantity: 12 },
+ { id: '1', name: 'Book', quantity: 23 },
+ { id: '2', name: 'Video', quantity: 56 },
+ { id: '3', name: 'Audio', quantity: 12 },
];
const optionRenderer = choice => `${choice.name} (${choice.quantity})`;
- ;
+
```
The choices are translated by default, so you can use translation identifiers as choices:
```js
const choices = [
- { id: 'books', name: 'myroot.category.books' },
- { id: 'sport', name: 'myroot.category.sport' },
+ { id: 'books', name: 'myroot.category.books' },
+ { id: 'sport', name: 'myroot.category.sport' },
];
```
-You can render any item as disabled by setting its `disabled` property to `true`:
+You can render any item as disabled by setting its `disabled` property to `true`:
```jsx
const choices = [
@@ -2000,15 +1746,10 @@ const choices = [
{ _id: 456, full_name: 'Jane Austen', sex: 'F' },
{ _id: 1, full_name: 'System Administrator', sex: 'F', disabled: true },
];
- ;
+
```
-You can use a custom field name by setting the `disableValue` prop:
+You can use a custom field name by setting the `disableValue` prop:
```jsx
const choices = [
@@ -2016,23 +1757,15 @@ const choices = [
{ _id: 456, full_name: 'Jane Austen', sex: 'F' },
{ _id: 987, full_name: 'Jack Harden', sex: 'M', not_available: true },
];
- ;
+
```
Lastly, use the `options` attribute if you want to override any of the `` attributes:
{% raw %}
-
```jsx
```
-
{% endraw %}
Refer to [the Select documentation](https://material-ui.com/api/select) for more details.
@@ -2040,7 +1773,7 @@ Refer to [the Select documentation](https://material-ui.com/api/select) for more
The `SelectArrayInput` component **cannot** be used inside a `ReferenceInput` but can be used inside a `ReferenceArrayInput`.
```jsx
-import * as React from 'react';
+import * as React from "react";
import {
ChipField,
Create,
@@ -2078,11 +1811,10 @@ The `` can allow users to create a new choice if either the `c
Use the `onCreate` prop when you only require users to provide a simple string and a `prompt` is enough. You can return either the new choice directly or a Promise resolving to the new choice.
{% raw %}
-
```js
import { SelectArrayInput, Create, SimpleForm, TextInput } from 'react-admin';
-const PostCreate = props => {
+const PostCreate = (props) => {
const tags = [
{ name: 'Tech', id: 'tech' },
{ name: 'Lifestyle', id: 'lifestyle' },
@@ -2094,10 +1826,7 @@ const PostCreate = props => {
{
const newTagName = prompt('Enter a new tag');
- const newTag = {
- id: newTagName.toLowerCase(),
- name: newTagName,
- };
+ const newTag = { id: newTagName.toLowerCase(), name: newTagName };
categories.push(newTag);
return newTag;
}}
@@ -2107,15 +1836,13 @@ const PostCreate = props => {
);
-};
+}
```
-
{% endraw %}
Use the `create` prop when you want a more polished or complex UI. For example a Material UI `` asking for multiple fields because the choices are from a referenced resource.
{% raw %}
-
```js
import {
SelectArrayInput,
@@ -2123,7 +1850,7 @@ import {
ReferenceArrayInput,
SimpleForm,
TextInput,
- useCreateSuggestionContext,
+ useCreateSuggestionContext
} from 'react-admin';
import {
@@ -2136,7 +1863,7 @@ import {
TextField,
} from '@material-ui/core';
-const PostCreate = props => {
+const PostCreate = (props) => {
return (
@@ -2147,14 +1874,14 @@ const PostCreate = props => {
);
-};
+}
const CreateTag = () => {
const { filter, onCancel, onCreate } = useCreateSuggestionContext();
const [value, setValue] = React.useState(filter || '');
const [create] = useCreate('tags');
- const handleSubmit = event => {
+ const handleSubmit = (event) => {
event.preventDefault();
create(
{
@@ -2193,7 +1920,6 @@ const CreateTag = () => {
);
};
```
-
{% endraw %}
#### CSS API
@@ -2242,7 +1968,7 @@ import { ReferenceArrayInput, SelectArrayInput } from 'react-admin';
- ;
+
```
![SelectArrayInput](./img/select-array-input.gif)
@@ -2264,7 +1990,7 @@ import { ReferenceArrayInput, SelectArrayInput } from 'react-admin';
- ;
+
```
**Tip**: `allowEmpty` is set by default for all Input components passed as ``.
@@ -2272,7 +1998,6 @@ import { ReferenceArrayInput, SelectArrayInput } from 'react-admin';
You can tweak how this component fetches the possible values using the `perPage`, `sort`, and `filter` props.
{% raw %}
-
```jsx
// by default, fetches only the first 25 values. You can extend this limit
// by setting the `perPage` prop.
@@ -2304,7 +2029,6 @@ You can tweak how this component fetches the possible values using the `perPage`
```
-
{% endraw %}
In addition to the `ReferenceArrayInputContext`, `` also sets up a `ListContext` providing access to the records from the reference resource in a similar fashion to that of the `` component. This `ListContext` value is accessible with the [`useListContext`](./List.md#uselistcontext) hook.
@@ -2321,7 +2045,7 @@ Any component descendant of `` can grab information from the
const {
choices, // An array of records matching both the current input value and the filters
error, // A potential error that may have occured while fetching the data
- warning, // A potential warning regarding missing references
+ warning, // A potential warning regarding missing references
loaded, // boolean that is false until the data is available
loading, // boolean that is true on mount, and false once the data was fetched
setFilter, // a callback to update the filters, e.g. setFilters({ q: 'query' })
@@ -2344,12 +2068,12 @@ import { ReferenceInput, SelectInput } from 'react-admin';
- ;
+
```
![ReferenceInput](./img/reference-input.gif)
-**Note**: You **must** add a `` for the reference resource - react-admin needs it to fetch the reference data. You _can_ omit the `list` prop in this reference if you want to hide it in the sidebar menu.
+**Note**: You **must** add a `` for the reference resource - react-admin needs it to fetch the reference data. You *can* omit the `list` prop in this reference if you want to hide it in the sidebar menu.
```jsx
@@ -2381,25 +2105,22 @@ import { ReferenceInput, SelectInput } from 'react-admin';
- ;
+
```
**Tip**: `allowEmpty` is set by default for all Input components passed as ``:
```jsx
const commentFilters = [
-
- {' '}
- // no need for allowEmpty
+ // no need for allowEmpty
- ,
+
];
```
You can tweak how this component fetches the possible values using the `perPage`, `sort`, and `filter` props.
{% raw %}
-
```jsx
// by default, fetches only the first 25 values. You can extend this limit
// by setting the `perPage` prop.
@@ -2431,7 +2152,6 @@ You can tweak how this component fetches the possible values using the `perPage`
```
-
{% endraw %}
The child component may further filter results (that's the case, for instance, for ``). ReferenceInput passes a `setFilter` function as prop to its child component. It uses the value to create a filter for the query - by default `{ q: [searchText] }`. You can customize the mapping
@@ -2449,18 +2169,18 @@ The child component may further filter results (that's the case, for instance, f
The child component receives the following props from ``:
-- `loading`: whether the request for possible values is loading or not
-- `filter`: the current filter of the request for possible values. Defaults to `{}`.
-- `pagination`: the current pagination of the request for possible values. Defaults to `{ page: 1, perPage: 25 }`.
-- `sort`: the current sorting of the request for possible values. Defaults to `{ field: 'id', order: 'DESC' }`.
-- `error`: the error message if the form validation failed for that input
-- `warning`: the warning message if the form validation failed for that input
-- `onChange`: function to call when the value changes
-- `setFilter`: function to call to update the filter of the request for possible values
-- `setPagination`: : function to call to update the pagination of the request for possible values
-- `setSort`: function to call to update the sorting of the request for possible values
+- `loading`: whether the request for possible values is loading or not
+- `filter`: the current filter of the request for possible values. Defaults to `{}`.
+- `pagination`: the current pagination of the request for possible values. Defaults to `{ page: 1, perPage: 25 }`.
+- `sort`: the current sorting of the request for possible values. Defaults to `{ field: 'id', order: 'DESC' }`.
+- `error`: the error message if the form validation failed for that input
+- `warning`: the warning message if the form validation failed for that input
+- `onChange`: function to call when the value changes
+- `setFilter`: function to call to update the filter of the request for possible values
+- `setPagination`: : function to call to update the pagination of the request for possible values
+- `setSort`: function to call to update the sorting of the request for possible values
-**Tip**: Why does `` use the `dataProvider.getMany()` method with a single value `[id]` instead of `dataProvider.getOne()` to fetch the record for the current value? Because when there are many `` for the same resource in a form (for instance when inside an ``), react-admin _aggregates_ the calls to `dataProvider.getMany()` into a single one with `[id1, id2, ...]`. This speeds up the UI and avoids hitting the API too much.
+**Tip**: Why does `` use the `dataProvider.getMany()` method with a single value `[id]` instead of `dataProvider.getOne()` to fetch the record for the current value? Because when there are many `` for the same resource in a form (for instance when inside an ``), react-admin *aggregates* the calls to `dataProvider.getMany()` into a single one with `[id1, id2, ...]`. This speeds up the UI and avoids hitting the API too much.
### ``
@@ -2483,10 +2203,7 @@ The form displays the events name in a ``:
```jsx
import * as React from 'react';
import { Edit, SelectArrayInput, SimpleForm, TextInput } from 'react-admin';
-import {
- ReferenceManyToManyInput,
- useReferenceManyToManyUpdate,
-} from '@react-admin/ra-many-to-many';
+import { ReferenceManyToManyInput, useReferenceManyToManyUpdate } from '@react-admin/ra-many-to-many';
/**
* Decorate to override the default save function.
@@ -2583,7 +2300,11 @@ You may override the language selector using the `selector` prop, which accepts
```jsx
const Selector = () => {
- const { locales, selectLocale, selectedLocale } = useTranslatableContext();
+ const {
+ locales,
+ selectLocale,
+ selectedLocale,
+ } = useTranslatableContext();
const handleChange = event => {
selectLocale(event.target.value);
@@ -2618,7 +2339,7 @@ const Selector = () => {
>
-;
+
```
If you have multiple `TranslatableInputs` on the same page, you should specify a `groupKey` so that react-admin can create unique identifiers for accessibility.
@@ -2639,7 +2360,7 @@ You can add validators to any of the inputs inside a `TranslatableInputs`. If an
```
-## Recipes
+## Recipes
### Transforming Input Value to/from Record
@@ -2647,50 +2368,45 @@ The data format returned by the input component may not be what your API desires
Mnemonic for the two functions:
-- `parse()`: input -> record
-- `format()`: record -> input
+- `parse()`: input -> record
+- `format()`: record -> input
Say the user would like to input values of 0-100 to a percentage field but your API (hence record) expects 0-1.0. You can use simple `parse()` and `format()` functions to archive the transform:
```jsx
- v * 100}
- parse={v => parseFloat(v) / 100}
- label="Formatted number"
-/>
+ v * 100} parse={v => parseFloat(v) / 100} label="Formatted number" />
```
`` stores and returns a string. If you would like to store a JavaScript Date object in your record instead:
```jsx
const dateFormatter = v => {
- // v is a `Date` object
- if (!(v instanceof Date) || isNaN(v)) return;
- const pad = '00';
- const yy = v.getFullYear().toString();
- const mm = (v.getMonth() + 1).toString();
- const dd = v.getDate().toString();
- return `${yy}-${(pad + mm).slice(-2)}-${(pad + dd).slice(-2)}`;
+ // v is a `Date` object
+ if (!(v instanceof Date) || isNaN(v)) return;
+ const pad = '00';
+ const yy = v.getFullYear().toString();
+ const mm = (v.getMonth() + 1).toString();
+ const dd = v.getDate().toString();
+ return `${yy}-${(pad + mm).slice(-2)}-${(pad + dd).slice(-2)}`;
};
const dateParser = v => {
- // v is a string of "YYYY-MM-DD" format
- const match = /(\d{4})-(\d{2})-(\d{2})/.exec(v);
- if (match === null) return;
- const d = new Date(match[1], parseInt(match[2], 10) - 1, match[3]);
- if (isNaN(d)) return;
- return d;
+ // v is a string of "YYYY-MM-DD" format
+ const match = /(\d{4})-(\d{2})-(\d{2})/.exec(v);
+ if (match === null) return;
+ const d = new Date(match[1], parseInt(match[2], 10) - 1, match[3]);
+ if (isNaN(d)) return;
+ return d;
};
- ;
+
```
### Linking Two Inputs
Edition forms often contain linked inputs, e.g. country and city (the choices of the latter depending on the value of the former).
-React-admin relies on [react-final-form](https://final-form.org/docs/react-final-form/getting-started) for form handling. You can grab the current form values using react-final-form [useFormState](https://final-form.org/docs/react-final-form/api/useFormState) hook.
+React-admin relies on [react-final-form](https://final-form.org/docs/react-final-form/getting-started) for form handling. You can grab the current form values using react-final-form [useFormState](https://final-form.org/docs/react-final-form/api/useFormState) hook.
```jsx
import * as React from 'react';
@@ -2757,15 +2473,15 @@ const OrderEdit = props => (
**Tip**: When using a `FormDataConsumer` inside an `ArrayInput`, the `FormDataConsumer` will provide three additional properties to its children function:
-- `scopedFormData`: an object containing the current values of the currently rendered item from the `ArrayInput`
-- `getSource`: a function which will translate the source into a valid one for the `ArrayInput`
+- `scopedFormData`: an object containing the current values of the currently rendered item from the `ArrayInput`
+- `getSource`: a function which will translate the source into a valid one for the `ArrayInput`
And here is an example usage for `getSource` inside ``:
```jsx
import { FormDataConsumer } from 'react-admin';
-const PostEdit = props => (
+const PostEdit = (props) => (
@@ -2803,41 +2519,39 @@ For such cases, you can use the approach described above, using the ` (
-
-
-
-
- {({ formData, ...rest }) =>
- formData.hasEmail &&
- }
-
-
-
-);
+ const PostEdit = (props) => (
+
+
+
+
+ {({ formData, ...rest }) => formData.hasEmail &&
+
+ }
+
+
+
+ );
```
**Tip**: When using a `FormDataConsumer` you can define [subscription](https://final-form.org/docs/react-final-form/types/FormProps#subscription) prop to pass to the `react-final-form`
{% raw %}
-
```jsx
import { FormDataConsumer } from 'react-admin';
-const PostEdit = props => (
-
-
-
-
- {({ formData, ...rest }) =>
- formData.hasEmail &&
- }
-
-
-
-);
+ const PostEdit = (props) => (
+
+
+
+
+ {({ formData, ...rest }) => formData.hasEmail &&
+
+ }
+
+
+
+ );
```
-
{% endraw %}
## Writing Your Own Input Component
@@ -2853,25 +2567,15 @@ For instance, let's write a component to edit the latitude and longitude of the
import { Field } from 'react-final-form';
const LatLngInput = () => (
-
+
-
+
);
export default LatLngInput;
// in ItemEdit.js
-const ItemEdit = props => (
+const ItemEdit = (props) => (
@@ -2884,8 +2588,8 @@ const ItemEdit = props => (
```html
-
-
+
+
```
@@ -2894,19 +2598,9 @@ const ItemEdit = props => (
```jsx
const LatLongInput = () => (
-
+
-
+
);
```
@@ -2923,19 +2617,9 @@ import { Labeled } from 'react-admin';
const LatLngInput = () => (
-
+
-
+
);
@@ -2947,8 +2631,8 @@ Now the component will render with a label:
```html
Position
-
-
+
+
```
@@ -2964,7 +2648,7 @@ import { useField } from 'react-final-form';
const BoundedTextField = ({ name, label }) => {
const {
input: { onChange },
- meta: { touched, error },
+ meta: { touched, error }
} = useField(name);
return (
{
const {
input: { name, onChange, ...rest },
meta: { touched, error },
- isRequired,
+ isRequired
} = useInput(props);
return (
@@ -3039,23 +2723,13 @@ const BoundedTextField = props => {
);
};
const LatLngInput = props => {
- const { source, ...rest } = props;
+ const {source, ...rest} = props;
return (
-
+
-
+
);
};
@@ -3072,11 +2746,14 @@ import { useInput } from 'react-admin';
const SexInput = props => {
const {
input,
- meta: { touched, error },
+ meta: { touched, error }
} = useInput(props);
return (
-
+
Male
Female
@@ -3088,20 +2765,16 @@ export default SexInput;
**Tip**: `useInput` accepts all arguments that you can pass to `useField`. That means that components using `useInput` accept props like [format](https://final-form.org/docs/react-final-form/types/FieldProps#format) and [parse](https://final-form.org/docs/react-final-form/types/FieldProps#parse), to convert values from the form to the input, and vice-versa:
```jsx
-const parse = value => {
- /* ... */
-};
-const format = value => {
- /* ... */
-};
+const parse = value => {/* ... */};
+const format = value => {/* ... */};
const PersonEdit = props => (
(formValue === 0 ? 'M' : 'F')}
- parse={inputValue => (inputValue === 'M' ? 0 : 1)}
+ format={formValue => formValue === 0 ? 'M' : 'F'}
+ parse={inputValue => inputValue === 'M' ? 0 : 1}
/>
@@ -3118,4 +2791,4 @@ You can find components for react-admin in third-party repositories.
- [@bb-tech/ra-components](https://github.com/bigbasket/ra-components): `JsonInput` which allows only valid JSON as input, `JsonField` to view JSON properly on show card and `TrimField` to trim the fields while showing in `Datagrid` in `List` component.
- [@react-page/react-admin](https://react-page.github.io/docs/#/integration-react-admin): ReactPage is a rich content editor and can comes with a ready-to-use React-admin input component. [check out the demo](https://react-page.github.io/examples/reactadmin)
-- **DEPRECATED V3** [LoicMahieu/aor-tinymce-input](https://github.com/LoicMahieu/aor-tinymce-input): a TinyMCE component, useful for editing HTML
+- **DEPRECATED V3** [LoicMahieu/aor-tinymce-input](https://github.com/LoicMahieu/aor-tinymce-input): a TinyMCE component, useful for editing HTML