diff --git a/docs/Inputs.md b/docs/Inputs.md index c3c16891caf..47b4fbc9f48 100644 --- a/docs/Inputs.md +++ b/docs/Inputs.md @@ -5,22 +5,30 @@ title: "Input Components" # Input Components -An `Input` component displays an input, or a dropdown list, a list of radio buttons, etc. Such components allow to edit a record property, and are common in the `` and `` components, and in the List Filters. +An `Input` component displays an input, or a dropdown list, a list of radio buttons, etc. Such components allow to update a record field and are common in the `` and `` components, and in the List Filters. + +![Inputs](./img/inputs.webp) + +Input components are usually wrappers around MUI form components, bound to the current react-hook-form context. + +## Usage + +Input components must be used inside a Form element (e.g. [`
`](./Form.md), [``](./SimpleForm.md), [``](./TabbedForm.md)). These components create a [`react-hook-form`](https://react-hook-form.com/) form and context. + +They require a `source` property. ```jsx -// in src/posts.js -import * as React from "react"; import { Edit, SimpleForm, ReferenceInput, SelectInput, TextInput, required } from 'react-admin'; export const PostEdit = () => ( - }> + - + ); @@ -33,30 +41,193 @@ 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 | -| `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. | -| `defaultValue` | Optional | `any` | - | Default value of the input. | -| `validate` | Optional | `Function` | `array` | - | Validation rules for the current property. See the [Validation Documentation](./Validation.md#per-input-validation-built-in-field-validators) 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 ``) | -| [`sx`](https://mui.com/system/the-sx-prop/#main-content) | Optional | `SxProps` | '' | MUI shortcut for defining custom styles with access to the theme | +| `defaultValue` | Optional | `any` | - | Default value of the input. | +| `disabled` | Optional | `boolean` | - | If true, the input is disabled. | +| `format` | Optional | `Function` | - | Callback taking the value from the form state, and returning the input value. | +| `fullWidth` | Optional | `boolean` | `false` | If `true`, the input will expand to fill the form width | +| `helperText` | Optional | `string` | - | Text to be displayed under the input | +| `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. | +| `parse` | Optional | `Function` | - | Callback taking the input value, and returning the value you want stored in the form state. | +| `sx` | Optional | `SxProps` | - | MUI shortcut for defining custom styles | +| `validate` | Optional | `Function` | `array` | - | Validation rules for the current property. See the [Validation Documentation](./Validation.md#per-input-validation-built-in-field-validators) for details. | + +React-admin uses [react-hook-form](https://react-hook-form.com/) to control form inputs. Each input component also accepts all react-hook-form [useController](https://react-hook-form.com/api/usecontroller) hook options. + +Additional props are passed down to the underlying component (usually an MUI component). For instance, when setting the `variant` prop on a `TextInput` component, the underlying MUI `` receives it, and renders it with a different variant. Refer to the documentation of each Input component to see the underlying MUI component and its props. + +## `className` + +The `className` prop is passed to the root element. + +```jsx + +``` + +**Tip**: Use [the `sx` prop](#sx) rather than `className` to style the component. + +## `defaultValue` + +Value of the input if the record has no value for the `source`. + +{% raw %} +```jsx + + {/* input initially renders with value 18 */} + {/* input initially renders with value "Lorem ipsum" */} + +``` +{% endraw %} + +React-admin will ignore these default values if the Form already defines [a form-wide `defaultValues`](./Form.md#defaultvalues): + +{% raw %} +```jsx +export const PostCreate = () => ( + + + + + {/* input initially renders with value 123 (form > input) */} + + + +); +``` +{% endraw %} + +**Tip**: `defaultValue` cannot use a function as value. For default values computed at render time, set the `defaultValues` at the form level. + +```jsx +const postDefaultValue = () => ({ id: uuid(), created_at: new Date(), nb_views: 0 }); + +export const PostCreate = () => ( + + + + + + + +); +``` + +## `disabled` + +If `true`, the input is disabled and the user can't change the value. + +```jsx + +``` + +## `format` + +The `format` prop accepts a callback taking the value from the form state, and returning the input value (which should be a string). + +``` +form state value --> format --> form input value (string) +``` + +```jsx +{/* Unit Price is stored in cents, i.e. 123 means 1.23 */} + String(v * 100)} + parse={v => parseFloat(v) / 100} +``` + +`format` often comes in pair with [`parse`](#parse) to transform the input value before storing it in the form state. See the [Transforming Input Value](#transforming-input-value-tofrom-record) section for more details. + +## `fullWidth` + +If `true`, the input will expand to fill the form width. + +![input full width](./img/input-full-width.png) + +```jsx + + +``` + +## `helperText` + +Most inputs accept a `helperText` prop to display a text below the input. + +![input helper text](./img/input-helper-text.png) ```jsx - + ``` -React-admin uses [react-hook-form](https://react-hook-form.com/) to control form inputs. Each input component also accepts all react-hook-form [useController](https://react-hook-form.com/api/usecontroller) hook options, with the addition of: +Set `helperText` to `false` to remove the empty line below the input. Beware that the form may "jump" visually when the input contains an error, as the error message will appear below the input. -| Prop | Required | Type | Default | Description | -| -------------- | -------- | ---------- | ------- |------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| -| `defaultValue` | Optional | `mixed` | - | Value to be set when the property is `undefined` | -| `format` | Optional | `Function` | - | Callback taking the value from the form state and the name of the field, and returns the input value. See the [Transforming Input Value](./Inputs.md#transforming-input-value-tofrom-record) section. | -| `parse` | Optional | `Function` | - | Callback taking the input value and name of the field, and returns the value you want stored in the form state. See the [Transforming Input Value](./Inputs.md#transforming-input-value-tofrom-record) section. | +## `label` -Additional props are passed down to the underlying component (usually a MUI component). For instance, when setting the `className` prop on a `TextInput` component, the underlying MUI `` receives it, and renders with custom styles. You can also set the underlying component `variant` and `margin` that way. +Input label. Defaults to the humanized `source` when omitted. Set `label={false}` to hide the label. -**Tip**: If you edit a record with a complex structure, you can use a path as the `source` parameter. For instance, if the API returns the following 'book' record: +```jsx + {/* input label is "Title" */} + {/* input label is "Post title" */} + {/* input has no label */} +``` + +**Tip**: If your interface has to support multiple languages, don't use the `label` prop. Provide one label per locale based on the default label (which is `resources.${resourceName}.fields.${fieldName}`) instead. + +```jsx +const frenchMessages = { + resources: { + posts: { + fields: { + title: 'Titre', + // ... + }, + }, + }, +}; + + {/* input label is "Titre" */} +``` + +See the [Translation documentation](./TranslationTranslating.md#translating-resource-and-field-names) for details. + +## `parse` + +The `parse` prop accepts a callback taking the value from the input (which is a string), and returning the value to put in the form state. + +``` +form input value (string) ---> parse ---> form state value +``` + +```jsx +{/* Unit Price is stored in cents, i.e. 123 means 1.23 */} + String(v * 100)} + parse={v => parseFloat(v) / 100} +``` + +`parse` often comes in pair with [`format`](#format) to transform the form value value before passing it to the input. See the [Transforming Input Value](#transforming-input-value-tofrom-record) section for more details. + +## `source` + +Specifies the field of the record that the input should edit. + +{% raw %} +```jsx +
+ {/* default value is "Hello, world!" */} + +``` +{% endraw %} + +If you edit a record with a complex structure, you can use a path as the `source` parameter. For instance, if the API returns the following 'book' record: ```json { @@ -69,19 +240,89 @@ Additional props are passed down to the underlying component (usually a MUI comp } ``` -Then you can display a text input to edit the author first name as follows: +Then you can display a text input to edit the author's first name as follows: ```jsx ``` -**Tip**: If your interface has to support multiple languages, don't use the `label` prop, and put the localized labels in a dictionary instead. See the [Translation documentation](./TranslationTranslating.md#translating-resource-and-field-names) for details. +## `sx` + +Each individual input supports an `sx` prop to pass custom styles to the underlying component, relying on [MUI system](https://mui.com/system/basics/#the-sx-prop). + +{% raw %} +```jsx + +``` +{% endraw %} + +Refer to the documentation of each input component to see what inner classes you can override. + +## `validate` + +A function or an array of functions to validate the input value. + +Validator functions should return `undefined` if the value is valid, or a string describing the error if it's invalid. + +```jsx +const validateAge = (value) => { + if (value < 18) { + return 'Must be over 18'; + } + return undefined; +} + + +``` + +**Tip**: If your admin has [multi-language support](./Translation.md), validator functions should return message *identifiers* rather than messages themselves. React-admin automatically passes these identifiers to the translation function: + +```jsx +// in validators/required.js +const required = () => (value) => + value + ? undefined + : 'myroot.validation.required'; +``` + +React-admin comes with a set of built-in validators: + +* `required(message)` if the field is mandatory, +* `minValue(min, message)` to specify a minimum value for integers, +* `maxValue(max, message)` to specify a maximum value for integers, +* `minLength(min, message)` to specify a minimum length for strings, +* `maxLength(max, message)` to specify a maximum length for strings, +* `number(message)` to check that the input is a valid number, +* `email(message)` to check that the input is a valid email address, +* `regex(pattern, message)` to validate that the input matches a regex, +* `choices(list, message)` to validate that the input is within a given list, + +These are validator factories, so you need to call the function to get the validator. + +```jsx + +``` -**Tip**: For compatibility reasons, input components also accept the `defaultValue` prop - which is simply copied as the `initialValue` prop. +You can use an array of validators to apply different validation rules to the same input. -## Recipes +```jsx + +``` -### Transforming Input Value to/from Record +**Note**: You can’t use both input-level validation and [form-level validation](./Form.md#validate) - this is a `react-hook-form` limitation. + +Check [the Validation chapter for details](./Validation.md). + +## Transforming Input Value to/from Record The data format returned by the input component may not be what your API desires. You can use the `parse` and `format` functions to transform the input value when saving to and loading from the record. @@ -135,7 +376,7 @@ const dateParser = value => { ``` -### Linking Two Inputs +## 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). @@ -209,7 +450,7 @@ const OrderEdit = () => ( **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` +- `getSource`: a function that translates the source into a valid one for the `ArrayInput` And here is an example usage for `getSource` inside ``: @@ -245,9 +486,9 @@ const PostEdit = () => ( ); ``` -### Hiding Inputs Based On Other Inputs +## Hiding Inputs Based On Other Inputs -You may want to display or hide inputs base on the value of another input - for instance, show an `email` input only if the `hasEmail` boolean input has been ticked to `true`. +You may want to display or hide inputs based on the value of another input - for instance, show an `email` input only if the `hasEmail` boolean input has been ticked to `true`. For such cases, you can use the approach described above, using the `` component. @@ -268,9 +509,17 @@ import { FormDataConsumer } from 'react-admin'; ); ``` -### Overriding The Input Variant +## Overriding The Input Variant + +MUI offers [3 variants for text fields](https://mui.com/material-ui/react-text-field/#basic-textfield): `outlined`, `filled`, and `standard`. The default react-admin theme uses the `filled` variant. + +Most Input components pass their additional props down to the root component, which is often an MUI Field component. This means you can pass a `variant` prop to override the varaint of a single input: + +```jsx + +``` -MUI offers [3 variants for text fields](https://mui.com/material-ui/react-text-field/#basic-textfield): `outlined`, `filled`, and `standard`. The default react-admin theme uses the `filled` variant. If you want your application to use another variant, override the `` prop with a [custom theme](./Theming.md#global-theme-overrides), as follows: +If you want to use another variant in all the Inputs of your application, override the `` prop with a [custom theme](./Theming.md#global-theme-overrides), as follows: ```jsx import { defaultTheme } from 'react-admin'; @@ -402,7 +651,7 @@ Now the component will render with a label: ### Using MUI Field Components -Instead of HTML `input` elements, you can use a MUI component like `TextField`. To bind MUI components to the form values, use the `useController()` hook: +Instead of HTML `input` elements, you can use an MUI component like `TextField`. To bind MUI components to the form values, use the `useController()` hook: ```jsx // in LatLongInput.js @@ -507,7 +756,7 @@ const LatLngInput = props => { }; ``` -Here is another example, this time using a MUI `Select` component: +Here is another example, this time using an MUI `Select` component: ```jsx // in SexInput.js diff --git a/docs/TextInput.md b/docs/TextInput.md index 354c0fcba9d..728fa50711d 100644 --- a/docs/TextInput.md +++ b/docs/TextInput.md @@ -5,17 +5,26 @@ title: "The TextInput Component" # `` -`` is the most common input. It is used for texts, emails, URL or passwords. In translates to an HTML `` tag. +`` is the most common input. It is used for texts, emails, URL or passwords. In translates into [an MUI ``](https://mui.com/material-ui/react-text-field/), and renders as `` in HTML. ![TextInput](./img/text-input.gif) -```jsx -import { TextInput } from 'react-admin'; +## Usage - +```jsx +import { Edit, SimpleForm, TextInput, required } from 'react-admin'; + +export const PostEdit = () => ( + }> + + + + + +); ``` -## Properties +## Props | Prop | Required | Type | Default | Description | | ------------ | -------- | --------- | ------- | -------------------------------------------------------------------- | @@ -25,13 +34,9 @@ import { TextInput } from 'react-admin'; `` also accepts the [common input props](./Inputs.md#common-input-props). -## Usage +Additional props are passed down to the underlying MUI [``](https://mui.com/material-ui/react-text-field/) component. -You can choose a specific input type using the `type` attribute, for instance `text` (the default), `email`, `url`, or `password`: - -```jsx - -``` +## `multiline` You can make the `` expandable using the `multiline` prop for multiline text values. It renders as an auto expandable textarea. @@ -39,6 +44,8 @@ You can make the `` expandable using the `multiline` prop for multili ``` +## `resettable` + You can make the `` component resettable using the `resettable` prop. This will add a reset button which will be displayed only when the field has a value and is focused. ```jsx @@ -49,4 +56,12 @@ import { TextInput } from 'react-admin'; ![resettable TextInput](./img/resettable-text-input.gif) +## `type` + +You can choose a specific input type using the `type` attribute, for instance `text` (the default), `email`, `url`, or `password`: + +```jsx + +``` + **Warning**: Do not use `type="number"`, or you'll receive a string as value (this is a [known React bug](https://github.com/facebook/react/issues/1425)). Instead, use [``](./NumberInput.md). diff --git a/docs/img/input-full-width.png b/docs/img/input-full-width.png new file mode 100644 index 00000000000..365d4cb0e84 Binary files /dev/null and b/docs/img/input-full-width.png differ diff --git a/docs/img/input-helper-text.png b/docs/img/input-helper-text.png new file mode 100644 index 00000000000..e326789117d Binary files /dev/null and b/docs/img/input-helper-text.png differ diff --git a/docs/img/inputs.webp b/docs/img/inputs.webp new file mode 100644 index 00000000000..02ad1b18be3 Binary files /dev/null and b/docs/img/inputs.webp differ diff --git a/packages/ra-ui-materialui/src/field/types.ts b/packages/ra-ui-materialui/src/field/types.ts index 1aaeb2417e1..071be799c79 100644 --- a/packages/ra-ui-materialui/src/field/types.ts +++ b/packages/ra-ui-materialui/src/field/types.ts @@ -19,6 +19,9 @@ export interface PublicFieldProps { className?: string; cellClassName?: string; headerClassName?: string; + /* + * @deprecated this property is not used anymore + */ formClassName?: string; textAlign?: TextAlign; emptyText?: string; diff --git a/packages/ra-ui-materialui/src/input/CommonInputProps.ts b/packages/ra-ui-materialui/src/input/CommonInputProps.ts index 0a6fd19d063..86a79712451 100644 --- a/packages/ra-ui-materialui/src/input/CommonInputProps.ts +++ b/packages/ra-ui-materialui/src/input/CommonInputProps.ts @@ -2,6 +2,9 @@ import { InputProps } from 'ra-core'; export type CommonInputProps = InputProps & { cellClassName?: string; + /* + * @deprecated this property is not used anymore + */ formClassName?: string; fullWidth?: boolean; headerCellClassName?: string;