Skip to content

Commit

Permalink
Backport #9656
Browse files Browse the repository at this point in the history
  • Loading branch information
djhi committed Jun 24, 2024
1 parent 0b3f840 commit 6118c6a
Show file tree
Hide file tree
Showing 41 changed files with 657 additions and 25 deletions.
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -114,7 +114,7 @@ build-create-react-admin:
@echo "Transpiling create-react-admin files...";
@cd ./packages/create-react-admin && yarn build

build: build-ra-core build-ra-ui-materialui build-ra-data-fakerest build-ra-data-json-server build-ra-data-localforage build-ra-data-localstorage build-ra-data-simple-rest build-ra-data-graphql build-ra-data-graphql-simple build-ra-i18n-polyglot build-ra-input-rich-text build-data-generator build-ra-language-english build-ra-language-french build-ra-i18n-i18next build-react-admin build-ra-no-code build-create-react-admin ## compile ES6 files to JS
build: build-ra-core build-ra-data-fakerest build-ra-ui-materialui build-ra-data-json-server build-ra-data-localforage build-ra-data-localstorage build-ra-data-simple-rest build-ra-data-graphql build-ra-data-graphql-simple build-ra-i18n-polyglot build-ra-input-rich-text build-data-generator build-ra-language-english build-ra-language-french build-ra-i18n-i18next build-react-admin build-ra-no-code build-create-react-admin ## compile ES6 files to JS

doc: ## compile doc as html and launch doc web server
@yarn doc
Expand Down
25 changes: 17 additions & 8 deletions docs/Inputs.md
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,8 @@ All input components accept the following props:
| `source` | Required | `string` | - | Name of the entity property to use for the input value |
| `className` | Optional | `string` | - | Class name (usually generated by JSS) to customize the look and feel of the field element itself |
| `defaultValue` | Optional | `any` | - | Default value of the input. |
| `disabled` | Optional | `boolean` | - | If true, the input is disabled. |
| `readOnly` | Optional | `boolean` | `false` | If true, the input is in read-only mode. |
| `disabled` | Optional | `boolean` | `false` | If true, the input is disabled. |
| `format` | Optional | `Function` | `value => value == null ? '' : value` | Callback taking the value from the form state, and returning the input value. |
| `fullWidth` | Optional | `boolean` | `true` | If `false`, the input will not expand to fill the form width |
| `helperText` | Optional | `string` | - | Text to be displayed under the input (cannot be used inside a filter) |
Expand Down Expand Up @@ -150,21 +151,29 @@ export const PostCreate = () => (
);
```

## `readOnly`

The `readOnly` prop set to true makes the element not mutable, meaning the user can not edit the control.

```tsx
<TextInput source="title" readOnly />
```

Contrary to disabled controls, read-only controls are still focusable and are submitted with the form.

## `disabled`

If `true`, the input is disabled and the user can't change the value.
The `disabled` prop set to true makes the element not mutable, focusable, or even submitted with the form.

```tsx
<TextInput source="title" disabled />
```

**Tip**: The form framework used by react-admin, react-hook-form, [considers](https://github.com/react-hook-form/react-hook-form/pull/10805) that a `disabled` input shouldn't submit any value. So react-hook-form sets the value of all `disabled` inputs to `undefined`. As a consequence, a form with a `disabled` input is always considered `dirty` (i.e. react-hook-form considers that the form values and the initial record values are different), and it triggers [the `warnWhenUnsavedChanges` feature](./Forms.md#warning-about-unsaved-changes) when leaving the form, even though the user changed nothing. The workaround is to set the `disabled` prop on the underlying input component, as follows:
Contrary to read-only controls, disabled controls can not receive focus and are not submitted with the form.

{% raw %}
```jsx
<TextInput source="title" InputProps={{ disabled: true }} />
```
{% endraw %}
**Warning:** Note that `disabled` inputs are **not** included in the form values, and hence may trigger `warnWhenUnsavedChanges` if the input previously had a value in the record.

**Tip:** To include the input in the form values, you can use `readOnly` instead of `disabled`.

## `format`

Expand Down
29 changes: 29 additions & 0 deletions docs/SimpleFormIterator.md
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,7 @@ const OrderEdit = () => (
| `inline` | Optional | `boolean` | `false` | When true, inputs are put on the same line |
| `removeButton` | Optional | `ReactElement` | - | Component to render for the remove button |
| `reOrderButtons` | Optional | `ReactElement` | - | Component to render for the up / down button |
| `disabled` | Optional | `boolean` | `false` | If true, all buttons are disabled. |
| `sx` | Optional | `SxProps` | - | Material UI shortcut for defining custom styles |

## `addButton`
Expand Down Expand Up @@ -345,6 +346,34 @@ const OrderEdit = () => (
);
```

## `readOnly`

The `readOnly` prop set to true makes the children input not mutable, meaning the user can not edit them.

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

Contrary to disabled controls, read-only controls are still focusable and are submitted with the form.

## `disabled`

The `disabled` prop set to true makes the children input not mutable, focusable, or even submitted with the form.

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

Contrary to read-only controls, disabled controls can not receive focus and are not submitted with the form.

## `sx`

You can override the style of the root element (a `<div>` element) as well as those of the inner components thanks to the `sx` property (see [the `sx` documentation](./SX.md) for syntax and examples).
Expand Down
2 changes: 1 addition & 1 deletion docs/TranslatableInputs.md
Original file line number Diff line number Diff line change
Expand Up @@ -197,4 +197,4 @@ You can add validators to any of the inputs inside a `TranslatableInputs`. If an
<TextInput source="name" validate={[required()]} />
<RichTextInput source="description" validate={[maxLength(100)]} />
</TranslatableInputs>
```
```
2 changes: 2 additions & 0 deletions packages/ra-core/src/form/useInput.ts
Original file line number Diff line number Diff line change
Expand Up @@ -168,6 +168,8 @@ export type InputProps<ValueType = any> = Omit<
resource?: string;
source: string;
validate?: Validator | Validator[];
readOnly?: boolean;
disabled?: boolean;
};

export type UseInputValue = {
Expand Down
13 changes: 13 additions & 0 deletions packages/ra-input-rich-text/src/RichTextInput.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,19 @@ export const Disabled = (props: Partial<SimpleFormProps>) => (
</AdminContext>
);

export const ReadOnly = (props: Partial<SimpleFormProps>) => (
<AdminContext i18nProvider={i18nProvider}>
<SimpleForm
defaultValues={{ body: 'Hello World' }}
onSubmit={() => {}}
{...props}
>
<RichTextInput source="body" readOnly />
<FormInspector />
</SimpleForm>
</AdminContext>
);

export const Small = (props: Partial<SimpleFormProps>) => (
<AdminContext i18nProvider={i18nProvider}>
<SimpleForm
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,40 @@ export const Disabled = () => (
<SimpleFormIterator>
<TextInput source="name" />
<TextInput source="role" />
<TextInput source="surname" />
</SimpleFormIterator>
</ArrayInput>
</SimpleForm>
</Edit>
);
}}
/>
</Admin>
</TestMemoryRouter>
);

export const ReadOnly = () => (
<TestMemoryRouter initialEntries={['/books/1']}>
<Admin dataProvider={dataProvider}>
<Resource
name="books"
edit={() => {
return (
<Edit
mutationMode="pessimistic"
mutationOptions={{
onSuccess: data => {
console.log(data);
},
}}
>
<SimpleForm>
<TextInput source="title" />
<ArrayInput source="authors" readOnly>
<SimpleFormIterator>
<TextInput source="name" />
<TextInput source="role" />
<TextInput source="surname" />
</SimpleFormIterator>
</ArrayInput>
</SimpleForm>
Expand Down Expand Up @@ -664,8 +698,8 @@ const BookEditGlobalValidation = () => {
}}
>
<SimpleForm validate={globalValidator}>
{/*
We still need `validate={required()}` to indicate fields are required
{/*
We still need `validate={required()}` to indicate fields are required
with a '*' symbol after the label, but the real validation happens in `globalValidator`
*/}
<ArrayInput source="authors" fullWidth validate={required()}>
Expand Down Expand Up @@ -696,8 +730,8 @@ const CreateGlobalValidationInFormTab = () => {
}}
>
<TabbedForm validate={globalValidator}>
{/*
We still need `validate={required()}` to indicate fields are required
{/*
We still need `validate={required()}` to indicate fields are required
with a '*' symbol after the label, but the real validation happens in `globalValidator`
*/}
<TabbedForm.Tab label="Main">
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,7 @@ export const ArrayInput = (props: ArrayInputProps) => {
validate,
variant,
disabled,
readOnly,
margin = 'dense',
...rest
} = props;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,38 @@ export const Inline = () => (
</Wrapper>
);

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

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

export const DisableAdd = () => (
<Wrapper>
<SimpleFormIterator disableAdd>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -239,6 +239,7 @@ export interface SimpleFormIteratorProps extends Partial<UseFieldArrayReturn> {
addButton?: ReactElement;
children?: ReactNode;
className?: string;
readOnly?: boolean;
disabled?: boolean;
disableAdd?: boolean;
disableClear?: boolean;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,62 @@ export const Basic = () => (
</Wrapper>
);

export const ReadOnly = () => (
<AdminContext i18nProvider={i18nProvider}>
<Create
resource="posts"
record={{ roles: ['u001', 'u003'] }}
sx={{ width: 600 }}
>
<SimpleForm>
<AutocompleteArrayInput
source="roles"
choices={[
{ id: 'admin', name: 'Admin' },
{ id: 'u001', name: 'Editor' },
{ id: 'u002', name: 'Moderator' },
{ id: 'u003', name: 'Reviewer' },
]}
readOnly
/>
<AutocompleteArrayInput
source="authors"
choices={[]}
readOnly
/>
</SimpleForm>
</Create>
</AdminContext>
);

export const Disabled = () => (
<AdminContext i18nProvider={i18nProvider}>
<Create
resource="posts"
record={{ roles: ['u001', 'u003'] }}
sx={{ width: 600 }}
>
<SimpleForm>
<AutocompleteArrayInput
source="roles"
choices={[
{ id: 'admin', name: 'Admin' },
{ id: 'u001', name: 'Editor' },
{ id: 'u002', name: 'Moderator' },
{ id: 'u003', name: 'Reviewer' },
]}
disabled
/>
<AutocompleteArrayInput
source="authors"
choices={[]}
disabled
/>
</SimpleForm>
</Create>
</AdminContext>
);

export const OnChange = ({
onChange = (value, records) => console.log({ value, records }),
}: Pick<AutocompleteArrayInputProps, 'onChange'>) => (
Expand Down
34 changes: 34 additions & 0 deletions packages/ra-ui-materialui/src/input/AutocompleteInput.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,40 @@ export const Basic = ({ onSuccess = console.log }) => (
</Wrapper>
);

export const ReadOnly = () => (
<Wrapper>
<AutocompleteInput
source="author"
choices={defaultChoices}
fullWidth
readOnly
/>
<AutocompleteInput
source="genre"
choices={defaultChoices}
fullWidth
readOnly
/>
</Wrapper>
);

export const Disabled = () => (
<Wrapper>
<AutocompleteInput
source="author"
choices={defaultChoices}
fullWidth
disabled
/>
<AutocompleteInput
source="genre"
choices={defaultChoices}
fullWidth
disabled
/>
</Wrapper>
);

export const Required = () => (
<Wrapper>
<AutocompleteInput
Expand Down
6 changes: 6 additions & 0 deletions packages/ra-ui-materialui/src/input/AutocompleteInput.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -175,6 +175,8 @@ export const AutocompleteInput = <
validate,
variant,
onInputChange,
disabled,
readOnly,
...rest
} = props;

Expand Down Expand Up @@ -218,6 +220,8 @@ export const AutocompleteInput = <
resource,
source,
validate,
disabled,
readOnly,
...rest,
});

Expand Down Expand Up @@ -559,8 +563,10 @@ If you provided a React element for the optionText prop, you must also provide t
id={id}
isOptionEqualToValue={isOptionEqualToValue}
filterSelectedOptions
disabled={disabled || readOnly}
renderInput={params => {
const mergedTextFieldProps = {
readOnly,
...params.InputProps,
...TextFieldProps?.InputProps,
};
Expand Down
6 changes: 6 additions & 0 deletions packages/ra-ui-materialui/src/input/BooleanInput.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,12 @@ export const Disabled = () => (
</Wrapper>
);

export const ReadOnly = () => (
<Wrapper>
<BooleanInput source="published" readOnly />
</Wrapper>
);

export const CustomIcon = () => (
<Wrapper>
<BooleanInput source="published" checkedIcon={<FavoriteIcon />} />
Expand Down
Loading

0 comments on commit 6118c6a

Please sign in to comment.