diff --git a/Makefile b/Makefile index 2fdbfaf480a..2d90341142a 100644 --- a/Makefile +++ b/Makefile @@ -120,7 +120,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 diff --git a/docs/Inputs.md b/docs/Inputs.md index d83e2bf2c91..e40015ace00 100644 --- a/docs/Inputs.md +++ b/docs/Inputs.md @@ -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` | `false` | If `true`, the input will expand to fill the form width | | `helperText` | Optional | `string` | - | Text to be displayed under the input (cannot be used inside a filter) | @@ -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 + +``` + +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 ``` -**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](./EditTutorial.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 - -``` -{% 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` diff --git a/docs/SimpleFormIterator.md b/docs/SimpleFormIterator.md index 8fa004b39e1..95876339aa0 100644 --- a/docs/SimpleFormIterator.md +++ b/docs/SimpleFormIterator.md @@ -86,6 +86,8 @@ 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 | +| `readOnly` | Optional | `boolean` | `false` | If true, all inputs are in read-only mode. | +| `disabled` | Optional | `boolean` | `false` | If true, all inputs are disabled. | | `sx` | Optional | `SxProps` | - | Material UI shortcut for defining custom styles | ## `addButton` @@ -354,6 +356,34 @@ const OrderEdit = () => ( ); ``` +## `readOnly` + +The `readOnly` prop set to true makes the children input not mutable, meaning the user can not edit them. + +```jsx + + + + + +``` + +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 + + + + + +``` + +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 `
` element) as well as those of the inner components thanks to the `sx` property (see [the `sx` documentation](./SX.md) for syntax and examples). diff --git a/docs/TranslatableInputs.md b/docs/TranslatableInputs.md index c624872ac33..49a1ba96820 100644 --- a/docs/TranslatableInputs.md +++ b/docs/TranslatableInputs.md @@ -197,4 +197,4 @@ You can add validators to any of the inputs inside a `TranslatableInputs`. If an -``` \ No newline at end of file +``` diff --git a/packages/ra-core/src/form/useInput.ts b/packages/ra-core/src/form/useInput.ts index 85decdc4108..b78feff2edf 100644 --- a/packages/ra-core/src/form/useInput.ts +++ b/packages/ra-core/src/form/useInput.ts @@ -148,6 +148,8 @@ export type InputProps = Omit< resource?: string; source: string; validate?: Validator | Validator[]; + readOnly?: boolean; + disabled?: boolean; }; export type UseInputValue = { diff --git a/packages/ra-input-rich-text/src/RichTextInput.stories.tsx b/packages/ra-input-rich-text/src/RichTextInput.stories.tsx index 92ded1603de..6ff51f2b453 100644 --- a/packages/ra-input-rich-text/src/RichTextInput.stories.tsx +++ b/packages/ra-input-rich-text/src/RichTextInput.stories.tsx @@ -79,6 +79,19 @@ export const Disabled = (props: Partial) => ( ); +export const ReadOnly = (props: Partial) => ( + + {}} + {...props} + > + + + + +); + export const Small = (props: Partial) => ( ( + + + + + + ); + }} + /> + +); + +export const ReadOnly = () => ( + + { + return ( + { + console.log(data); + }, + }} + > + + + + + + + @@ -408,8 +440,8 @@ const BookEditGlobalValidation = () => { }} > - {/* - 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` */} @@ -438,8 +470,8 @@ const CreateGlobalValidationInFormTab = () => { }} > - {/* - 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` */} diff --git a/packages/ra-ui-materialui/src/input/ArrayInput/ArrayInput.tsx b/packages/ra-ui-materialui/src/input/ArrayInput/ArrayInput.tsx index 35e7af10547..196c1dbd55c 100644 --- a/packages/ra-ui-materialui/src/input/ArrayInput/ArrayInput.tsx +++ b/packages/ra-ui-materialui/src/input/ArrayInput/ArrayInput.tsx @@ -83,6 +83,7 @@ export const ArrayInput = (props: ArrayInputProps) => { validate, variant, disabled, + readOnly, margin = 'dense', ...rest } = props; @@ -185,7 +186,8 @@ export const ArrayInput = (props: ArrayInputProps) => { source, variant, margin, - disabled, + disabled: children.props.disabled || disabled, + readOnly: children.props.readOnly || readOnly, })} {renderHelperText ? ( diff --git a/packages/ra-ui-materialui/src/input/ArrayInput/SimpleFormIterator.spec.tsx b/packages/ra-ui-materialui/src/input/ArrayInput/SimpleFormIterator.spec.tsx index e3dac119c2c..701f67d9b0f 100644 --- a/packages/ra-ui-materialui/src/input/ArrayInput/SimpleFormIterator.spec.tsx +++ b/packages/ra-ui-materialui/src/input/ArrayInput/SimpleFormIterator.spec.tsx @@ -147,7 +147,23 @@ describe('', () => { render( - + + + + + + + + ); + + expect(screen.queryAllByLabelText('ra.action.add').length).toBe(0); + }); + + it('should not display add button if readOnly is truthy', () => { + render( + + + diff --git a/packages/ra-ui-materialui/src/input/ArrayInput/SimpleFormIterator.stories.tsx b/packages/ra-ui-materialui/src/input/ArrayInput/SimpleFormIterator.stories.tsx index 068402a70bd..04767455021 100644 --- a/packages/ra-ui-materialui/src/input/ArrayInput/SimpleFormIterator.stories.tsx +++ b/packages/ra-ui-materialui/src/input/ArrayInput/SimpleFormIterator.stories.tsx @@ -108,6 +108,38 @@ export const Inline = () => ( ); +export const ReadOnly = () => ( + + + + + + + + + + + + + +); + +export const Disabled = () => ( + + + + + + + + + + + + + +); + export const DisableAdd = () => ( diff --git a/packages/ra-ui-materialui/src/input/ArrayInput/SimpleFormIterator.tsx b/packages/ra-ui-materialui/src/input/ArrayInput/SimpleFormIterator.tsx index e2adf566de3..3fc87585479 100644 --- a/packages/ra-ui-materialui/src/input/ArrayInput/SimpleFormIterator.tsx +++ b/packages/ra-ui-materialui/src/input/ArrayInput/SimpleFormIterator.tsx @@ -48,6 +48,7 @@ export const SimpleFormIterator = (props: SimpleFormIteratorProps) => { className, resource, source, + readOnly, disabled, disableAdd = false, disableClear, @@ -164,7 +165,7 @@ export const SimpleFormIterator = (props: SimpleFormIteratorProps) => { className={clsx( className, fullWidth && 'fullwidth', - disabled && 'disabled' + (disabled || readOnly) && 'disabled' )} sx={sx} > @@ -172,7 +173,7 @@ export const SimpleFormIterator = (props: SimpleFormIteratorProps) => { {fields.map((member, index) => ( { ))} - {!disabled && !(disableAdd && (disableClear || disableRemove)) && ( -
- {!disableAdd && ( -
- {cloneElement(addButton, { - className: clsx( - 'button-add', - `button-add-${source}` - ), - onClick: handleAddButtonClick( - addButton.props.onClick - ), - })} -
- )} - {fields.length > 0 && !disableClear && !disableRemove && ( -
- setConfirmIsOpen(false)} - /> - setConfirmIsOpen(true)} - /> -
- )} -
- )} + {!(disabled || readOnly) && + !(disableAdd && (disableClear || disableRemove)) && ( +
+ {!disableAdd && ( +
+ {cloneElement(addButton, { + className: clsx( + 'button-add', + `button-add-${source}` + ), + onClick: handleAddButtonClick( + addButton.props.onClick + ), + })} +
+ )} + {fields.length > 0 && + !disableClear && + !disableRemove && ( +
+ + setConfirmIsOpen(false) + } + /> + + setConfirmIsOpen(true) + } + /> +
+ )} +
+ )} ) : null; @@ -247,6 +259,8 @@ SimpleFormIterator.propTypes = { source: PropTypes.string, resource: PropTypes.string, translate: PropTypes.func, + readOnly: PropTypes.bool, + disabled: PropTypes.bool, disableAdd: PropTypes.bool, disableRemove: PropTypes.oneOfType([PropTypes.func, PropTypes.bool]), TransitionProps: PropTypes.shape({}), @@ -258,6 +272,7 @@ export interface SimpleFormIteratorProps extends Partial { addButton?: ReactElement; children?: ReactNode; className?: string; + readOnly?: boolean; disabled?: boolean; disableAdd?: boolean; disableClear?: boolean; diff --git a/packages/ra-ui-materialui/src/input/AutocompleteArrayInput.stories.tsx b/packages/ra-ui-materialui/src/input/AutocompleteArrayInput.stories.tsx index 40ffeef7227..c9abdcb665c 100644 --- a/packages/ra-ui-materialui/src/input/AutocompleteArrayInput.stories.tsx +++ b/packages/ra-ui-materialui/src/input/AutocompleteArrayInput.stories.tsx @@ -52,6 +52,62 @@ export const Basic = () => (
); +export const ReadOnly = () => ( + + + + + + + + +); + +export const Disabled = () => ( + + + + + + + + +); + export const OnChange = ({ onChange = (value, records) => console.log({ value, records }), }: Pick) => ( diff --git a/packages/ra-ui-materialui/src/input/AutocompleteInput.stories.tsx b/packages/ra-ui-materialui/src/input/AutocompleteInput.stories.tsx index 404ecf5b97b..f1277cecd84 100644 --- a/packages/ra-ui-materialui/src/input/AutocompleteInput.stories.tsx +++ b/packages/ra-ui-materialui/src/input/AutocompleteInput.stories.tsx @@ -108,6 +108,40 @@ export const Basic = ({ onSuccess = console.log }) => ( ); +export const ReadOnly = () => ( + + + + +); + +export const Disabled = () => ( + + + + +); + export const Required = () => ( { const mergedTextFieldProps = { + readOnly, ...params.InputProps, ...TextFieldProps?.InputProps, }; diff --git a/packages/ra-ui-materialui/src/input/BooleanInput.stories.tsx b/packages/ra-ui-materialui/src/input/BooleanInput.stories.tsx index f1b2b74a781..7a3fb571782 100644 --- a/packages/ra-ui-materialui/src/input/BooleanInput.stories.tsx +++ b/packages/ra-ui-materialui/src/input/BooleanInput.stories.tsx @@ -24,6 +24,12 @@ export const Disabled = () => ( ); +export const ReadOnly = () => ( + + + +); + export const CustomIcon = () => ( } /> diff --git a/packages/ra-ui-materialui/src/input/BooleanInput.tsx b/packages/ra-ui-materialui/src/input/BooleanInput.tsx index db26240363b..7f9e8b70b63 100644 --- a/packages/ra-ui-materialui/src/input/BooleanInput.tsx +++ b/packages/ra-ui-materialui/src/input/BooleanInput.tsx @@ -25,6 +25,7 @@ export const BooleanInput = (props: BooleanInputProps) => { onBlur, onChange, onFocus, + readOnly, disabled, parse, resource, @@ -50,6 +51,8 @@ export const BooleanInput = (props: BooleanInputProps) => { onChange, type: 'checkbox', validate, + disabled, + readOnly, ...rest, }); @@ -82,7 +85,8 @@ export const BooleanInput = (props: BooleanInputProps) => { checked={Boolean(field.value)} {...sanitizeInputRestProps(rest)} {...options} - disabled={disabled} + disabled={disabled || readOnly} + readOnly={readOnly} /> } label={ @@ -112,6 +116,7 @@ BooleanInput.propTypes = { // @ts-ignore options: PropTypes.shape(Switch.propTypes), disabled: PropTypes.bool, + readOnly: PropTypes.bool, }; export type BooleanInputProps = CommonInputProps & diff --git a/packages/ra-ui-materialui/src/input/CheckboxGroupInput.stories.tsx b/packages/ra-ui-materialui/src/input/CheckboxGroupInput.stories.tsx index 4335fc3a541..1f1c5b51990 100644 --- a/packages/ra-ui-materialui/src/input/CheckboxGroupInput.stories.tsx +++ b/packages/ra-ui-materialui/src/input/CheckboxGroupInput.stories.tsx @@ -75,6 +75,24 @@ export const InsideReferenceArrayInput = () => ( ); +export const ReadOnly = () => ( + + + + + + + +); + export const Disabled = () => ( = pr source: sourceProp, translateChoice, validate, + disabled, + readOnly, ...rest } = props; @@ -151,6 +153,8 @@ export const CheckboxGroupInput: FunctionComponent = pr validate, onChange, onBlur, + disabled, + readOnly, ...rest, }); @@ -238,6 +242,8 @@ export const CheckboxGroupInput: FunctionComponent = pr value={value} labelPlacement={labelPlacement} inputRef={index === 0 ? ref : undefined} + disabled={disabled || readOnly} + readOnly={readOnly} {...sanitizeRestProps(rest)} /> ))} @@ -280,6 +286,8 @@ CheckboxGroupInput.propTypes = { PropTypes.element, ]), optionValue: PropTypes.string, + disabled: PropTypes.bool, + readOnly: PropTypes.bool, row: PropTypes.bool, resource: PropTypes.string, translateChoice: PropTypes.bool, diff --git a/packages/ra-ui-materialui/src/input/CommonInputProps.ts b/packages/ra-ui-materialui/src/input/CommonInputProps.ts index 86a79712451..e99dbdbf66d 100644 --- a/packages/ra-ui-materialui/src/input/CommonInputProps.ts +++ b/packages/ra-ui-materialui/src/input/CommonInputProps.ts @@ -6,6 +6,8 @@ export type CommonInputProps = InputProps & { * @deprecated this property is not used anymore */ formClassName?: string; + disabled?: boolean; + readOnly?: boolean; fullWidth?: boolean; headerCellClassName?: string; margin?: 'none' | 'dense' | 'normal'; diff --git a/packages/ra-ui-materialui/src/input/DatagridInput.tsx b/packages/ra-ui-materialui/src/input/DatagridInput.tsx index a1232431017..26a4dfd08f8 100644 --- a/packages/ra-ui-materialui/src/input/DatagridInput.tsx +++ b/packages/ra-ui-materialui/src/input/DatagridInput.tsx @@ -159,7 +159,10 @@ export const DatagridInput = (props: DatagridInputProps) => { ); }; -export type DatagridInputProps = Omit & +export type DatagridInputProps = Omit< + CommonInputProps, + 'source' | 'readOnly' | 'disabled' +> & ChoicesProps & Omit & DatagridProps & { diff --git a/packages/ra-ui-materialui/src/input/DateInput.stories.tsx b/packages/ra-ui-materialui/src/input/DateInput.stories.tsx index 70ddb236a37..a9c0b4f0821 100644 --- a/packages/ra-ui-materialui/src/input/DateInput.stories.tsx +++ b/packages/ra-ui-materialui/src/input/DateInput.stories.tsx @@ -26,6 +26,14 @@ export const FullWidth = () => ( export const Disabled = () => ( + + +); + +export const ReadOnly = () => ( + + + ); diff --git a/packages/ra-ui-materialui/src/input/DateInput.tsx b/packages/ra-ui-materialui/src/input/DateInput.tsx index 1dd3e738ffa..57d90c399e1 100644 --- a/packages/ra-ui-materialui/src/input/DateInput.tsx +++ b/packages/ra-ui-materialui/src/input/DateInput.tsx @@ -45,6 +45,8 @@ export const DateInput = ({ parse, validate, variant, + disabled, + readOnly, ...rest }: DateInputProps) => { const { field, fieldState, formState, id, isRequired } = useInput({ @@ -57,6 +59,8 @@ export const DateInput = ({ resource, source, validate, + disabled, + readOnly, ...rest, }); @@ -75,6 +79,8 @@ export const DateInput = ({ variant={variant} margin={margin} error={(isTouched || isSubmitted) && invalid} + disabled={disabled || readOnly} + readOnly={readOnly} helperText={ renderHelperText ? ( ( export const Disabled = () => ( + + +); + +export const ReadOnly = () => ( + + + ); diff --git a/packages/ra-ui-materialui/src/input/DateTimeInput.tsx b/packages/ra-ui-materialui/src/input/DateTimeInput.tsx index 6bf27aa5bf0..cfd68cf125c 100644 --- a/packages/ra-ui-materialui/src/input/DateTimeInput.tsx +++ b/packages/ra-ui-materialui/src/input/DateTimeInput.tsx @@ -35,6 +35,8 @@ export const DateTimeInput = ({ parse = parseDateTime, validate, variant, + disabled, + readOnly, ...rest }: DateTimeInputProps) => { const { field, fieldState, formState, id, isRequired } = useInput({ @@ -46,6 +48,8 @@ export const DateTimeInput = ({ resource, source, validate, + disabled, + readOnly, ...rest, }); @@ -63,6 +67,8 @@ export const DateTimeInput = ({ variant={variant} margin={margin} error={(isTouched || isSubmitted) && invalid} + disabled={disabled || readOnly} + readOnly={readOnly} helperText={ renderHelperText ? ( ( export const Disabled = () => ( - + + + + + +); + +export const ReadOnly = () => ( + + diff --git a/packages/ra-ui-materialui/src/input/FileInput.tsx b/packages/ra-ui-materialui/src/input/FileInput.tsx index 100c2be0bac..496fcb48d41 100644 --- a/packages/ra-ui-materialui/src/input/FileInput.tsx +++ b/packages/ra-ui-materialui/src/input/FileInput.tsx @@ -22,6 +22,7 @@ import { Labeled } from '../Labeled'; import { FileInputPreview } from './FileInputPreview'; import { sanitizeInputRestProps } from './sanitizeInputRestProps'; import { InputHelperText } from './InputHelperText'; +import { useTheme } from '@mui/material/styles'; import { SxProps } from '@mui/system'; import { SvgIconProps } from '@mui/material'; @@ -48,6 +49,8 @@ export const FileInput = (props: FileInputProps) => { source, validate, validateFileRemoval, + disabled, + readOnly, ...rest } = props; const { onDrop: onDropProp } = options; @@ -92,6 +95,8 @@ export const FileInput = (props: FileInputProps) => { parse: parse || transformFiles, source, validate, + disabled, + readOnly, ...rest, }); const { isTouched, error, invalid } = fieldState; @@ -147,6 +152,7 @@ export const FileInput = (props: FileInputProps) => { maxSize, minSize, multiple, + disabled: disabled || readOnly, ...options, onDrop, }); @@ -154,6 +160,8 @@ export const FileInput = (props: FileInputProps) => { const renderHelperText = helperText !== false || ((isTouched || isSubmitted) && invalid); + const theme = useTheme(); + return ( { resource={resource} isRequired={isRequired} color={(isTouched || isSubmitted) && invalid ? 'error' : undefined} + sx={{ + cursor: disabled || readOnly ? 'default' : 'pointer', + ...rest.sx, + }} {...sanitizeInputRestProps(rest)} > <> @@ -170,6 +182,17 @@ export const FileInput = (props: FileInputProps) => { {...getRootProps({ className: FileInputClasses.dropZone, 'data-testid': 'dropzone', + style: { + color: + disabled || readOnly + ? theme.palette.text.disabled + : inputPropsOptions?.color || + theme.palette.text.primary, + backgroundColor: + disabled || readOnly + ? theme.palette.action.disabledBackground + : inputPropsOptions?.backgroundColor, + }, })} > ( export const Disabled = () => ( - + + + + + +); + +export const ReadOnly = () => ( + + diff --git a/packages/ra-ui-materialui/src/input/ImageInput.tsx b/packages/ra-ui-materialui/src/input/ImageInput.tsx index c5e39d7d8ae..7030271c837 100644 --- a/packages/ra-ui-materialui/src/input/ImageInput.tsx +++ b/packages/ra-ui-materialui/src/input/ImageInput.tsx @@ -24,7 +24,6 @@ const StyledFileInput = styled(FileInput, { background: theme.palette.background.default, borderRadius: theme.shape.borderRadius, fontFamily: theme.typography.fontFamily, - cursor: 'pointer', padding: theme.spacing(1), textAlign: 'center', color: theme.palette.getContrastText(theme.palette.background.default), diff --git a/packages/ra-ui-materialui/src/input/NullableBooleanInput.stories.tsx b/packages/ra-ui-materialui/src/input/NullableBooleanInput.stories.tsx index 0e8f5b10af9..4e4b627d659 100644 --- a/packages/ra-ui-materialui/src/input/NullableBooleanInput.stories.tsx +++ b/packages/ra-ui-materialui/src/input/NullableBooleanInput.stories.tsx @@ -18,10 +18,18 @@ export const Basic = () => ( export const Disabled = () => ( + ); +export const ReadOnly = () => ( + + + + +); + const i18nProvider = polyglotI18nProvider(() => englishMessages); const Wrapper = ({ children }) => ( diff --git a/packages/ra-ui-materialui/src/input/NullableBooleanInput.tsx b/packages/ra-ui-materialui/src/input/NullableBooleanInput.tsx index e1efb25af8f..8dfb57db133 100644 --- a/packages/ra-ui-materialui/src/input/NullableBooleanInput.tsx +++ b/packages/ra-ui-materialui/src/input/NullableBooleanInput.tsx @@ -21,6 +21,8 @@ export const NullableBooleanInput = (props: NullableBooleanInputProps) => { onChange, parse = getBooleanFromString, resource, + disabled, + readOnly, source, validate, variant, @@ -46,6 +48,8 @@ export const NullableBooleanInput = (props: NullableBooleanInputProps) => { resource, source, validate, + disabled, + readOnly, ...rest, }); const renderHelperText = @@ -62,6 +66,8 @@ export const NullableBooleanInput = (props: NullableBooleanInputProps) => { className )} select + disabled={disabled || readOnly} + readOnly={readOnly} margin={margin} label={ { }; export type NullableBooleanInputProps = CommonInputProps & - Omit & { + Omit & { nullLabel?: string; falseLabel?: string; trueLabel?: string; diff --git a/packages/ra-ui-materialui/src/input/NumberInput.stories.tsx b/packages/ra-ui-materialui/src/input/NumberInput.stories.tsx index 3ff466adb06..29d17ca141e 100644 --- a/packages/ra-ui-materialui/src/input/NumberInput.stories.tsx +++ b/packages/ra-ui-materialui/src/input/NumberInput.stories.tsx @@ -26,6 +26,40 @@ export const Basic = () => ( ); +export const ReadOnly = () => ( + + + + + + + + + + +); + +export const Disabled = () => ( + + + + + + + + + + +); + export const Float = () => ( { const { @@ -52,6 +54,8 @@ export const NumberInput = ({ resource, source, validate, + disabled, + readOnly, ...rest, }); const { onBlur: onBlurFromField } = field; @@ -137,6 +141,8 @@ export const NumberInput = ({ size="small" variant={variant} error={(isTouched || isSubmitted) && invalid} + disabled={disabled || readOnly} + readOnly={readOnly} helperText={ renderHelperText ? ( } margin={margin} - inputProps={inputProps} + inputProps={{ ...inputProps, readOnly }} {...sanitizeInputRestProps(rest)} /> ); diff --git a/packages/ra-ui-materialui/src/input/PasswordInput.stories.tsx b/packages/ra-ui-materialui/src/input/PasswordInput.stories.tsx new file mode 100644 index 00000000000..ef998eb1557 --- /dev/null +++ b/packages/ra-ui-materialui/src/input/PasswordInput.stories.tsx @@ -0,0 +1,49 @@ +import * as React from 'react'; +import polyglotI18nProvider from 'ra-i18n-polyglot'; +import englishMessages from 'ra-language-english'; + +import { AdminContext } from '../AdminContext'; +import { Create } from '../detail'; +import { SimpleForm } from '../form'; +import { FormInspector } from './common'; +import { PasswordInput } from './PasswordInput'; + +export default { title: 'ra-ui-materialui/input/PasswordInput' }; + +export const Basic = () => ( + + + +); + +export const FullWidth = () => ( + + + +); + +export const Disabled = () => ( + + + + +); +export const ReadOnly = () => ( + + + + +); + +const i18nProvider = polyglotI18nProvider(() => englishMessages); + +const Wrapper = ({ children }) => ( + + + + {children} + + + + +); diff --git a/packages/ra-ui-materialui/src/input/RadioButtonGroupInput.stories.tsx b/packages/ra-ui-materialui/src/input/RadioButtonGroupInput.stories.tsx index ec41052c981..0f3d1aa2553 100644 --- a/packages/ra-ui-materialui/src/input/RadioButtonGroupInput.stories.tsx +++ b/packages/ra-ui-materialui/src/input/RadioButtonGroupInput.stories.tsx @@ -25,6 +25,18 @@ export const Basic = () => ( ); +export const Disabled = () => ( + + + +); + +export const ReadOnly = () => ( + + + +); + export const Row = () => ( { source: sourceProp, translateChoice, validate, + disabled, + readOnly, ...rest } = props; @@ -139,6 +141,8 @@ export const RadioButtonGroupInput = (props: RadioButtonGroupInputProps) => { resource, source, validate, + disabled, + readOnly, ...rest, }); @@ -171,6 +175,8 @@ export const RadioButtonGroupInput = (props: RadioButtonGroupInputProps) => { className={clsx('ra-input', `ra-input-${source}`, className)} margin={margin} error={fetchError || ((isTouched || isSubmitted) && invalid)} + disabled={disabled || readOnly} + readOnly={readOnly} {...sanitizeRestProps(rest)} > - // @ts-ignore Promise.resolve({ data: tags, total: tags.length, diff --git a/packages/ra-ui-materialui/src/input/ResettableTextField.tsx b/packages/ra-ui-materialui/src/input/ResettableTextField.tsx index 0680271da2c..c2da859b7af 100644 --- a/packages/ra-ui-materialui/src/input/ResettableTextField.tsx +++ b/packages/ra-ui-materialui/src/input/ResettableTextField.tsx @@ -23,6 +23,7 @@ export const ResettableTextField = forwardRef( value, resettable, disabled, + readOnly, variant, margin, className, @@ -136,7 +137,7 @@ export const ResettableTextField = forwardRef( title={translate('ra.action.clear_input_value')} onClick={handleClickClearButton} onMouseDown={handleMouseDownClearButton} - disabled={disabled} + disabled={disabled || readOnly} size="large" > { ResettableTextField.propTypes = { clearAlwaysVisible: PropTypes.bool, + readOnly: PropTypes.bool, disabled: PropTypes.bool, InputProps: PropTypes.object, onBlur: PropTypes.func, @@ -196,6 +199,7 @@ ResettableTextField.propTypes = { interface Props { clearAlwaysVisible?: boolean; resettable?: boolean; + readOnly?: boolean; } export type ResettableTextFieldProps = Props & diff --git a/packages/ra-ui-materialui/src/input/SearchInput.stories.tsx b/packages/ra-ui-materialui/src/input/SearchInput.stories.tsx new file mode 100644 index 00000000000..3188cebf455 --- /dev/null +++ b/packages/ra-ui-materialui/src/input/SearchInput.stories.tsx @@ -0,0 +1,164 @@ +import * as React from 'react'; +import { Resource } from 'ra-core'; +import fakeRestDataProvider from 'ra-data-fakerest'; +import polyglotI18nProvider from 'ra-i18n-polyglot'; +import englishMessages from 'ra-language-english'; +import frenchMessages from 'ra-language-french'; +import { createMemoryHistory } from 'history'; + +import { AdminContext } from '../AdminContext'; +import { AdminUI } from '../AdminUI'; +import { List, Datagrid } from '../list'; +import { TextField } from '../field'; +import { SearchInput } from './SearchInput'; + +export default { title: 'ra-ui-materialui/input/SearchInput' }; + +const dataProvider = fakeRestDataProvider({ + books: [ + { + id: 1, + title: 'War and Peace', + author: 'Leo Tolstoy', + year: 1869, + }, + { + id: 2, + title: 'Pride and Predjudice', + author: 'Jane Austen', + year: 1813, + }, + { + id: 3, + title: 'The Picture of Dorian Gray', + author: 'Oscar Wilde', + year: 1890, + }, + { + id: 4, + title: 'Le Petit Prince', + author: 'Antoine de Saint-Exupéry', + year: 1943, + }, + { + id: 5, + title: "Alice's Adventures in Wonderland", + author: 'Lewis Carroll', + year: 1865, + }, + { + id: 6, + title: 'Madame Bovary', + author: 'Gustave Flaubert', + year: 1856, + }, + { + id: 7, + title: 'The Lord of the Rings', + author: 'J. R. R. Tolkien', + year: 1954, + }, + { + id: 8, + title: "Harry Potter and the Philosopher's Stone", + author: 'J. K. Rowling', + year: 1997, + }, + { + id: 9, + title: 'The Alchemist', + author: 'Paulo Coelho', + year: 1988, + }, + { + id: 10, + title: 'A Catcher in the Rye', + author: 'J. D. Salinger', + year: 1951, + }, + { + id: 11, + title: 'Ulysses', + author: 'James Joyce', + year: 1922, + }, + ], + authors: [], +}); + +const i18nProvider = polyglotI18nProvider( + locale => + locale === 'fr' + ? { + ...frenchMessages, + resources: { + books: { + name: 'Livre |||| Livres', + fields: { + id: 'Id', + title: 'Titre', + author: 'Auteur', + year: 'Année', + }, + }, + }, + } + : englishMessages, + 'en' // Default locale +); + +const history = createMemoryHistory({ initialEntries: ['/books'] }); + +const postFilters = []; + +const BookList = () => { + return ( + + + + + + + + + ); +}; + +export const Basic = () => ( + + + + + +); + +const postFiltersReadOnly = []; + +const BookListReadOnly = () => { + return ( + + + + + + + + + ); +}; + +export const ReadOnly = () => ( + + + + + +); diff --git a/packages/ra-ui-materialui/src/input/SelectArrayInput.stories.tsx b/packages/ra-ui-materialui/src/input/SelectArrayInput.stories.tsx index 6ecd1def302..9182d5f1922 100644 --- a/packages/ra-ui-materialui/src/input/SelectArrayInput.stories.tsx +++ b/packages/ra-ui-materialui/src/input/SelectArrayInput.stories.tsx @@ -6,6 +6,8 @@ import { Dialog, DialogActions, DialogContent, + Stack, + Box, TextField, } from '@mui/material'; import fakeRestProvider from 'ra-data-fakerest'; @@ -20,7 +22,6 @@ import { TextInput } from './TextInput'; import { ArrayInput, SimpleFormIterator } from './ArrayInput'; import { FormDataConsumer } from 'ra-core'; import { useWatch } from 'react-hook-form'; - export default { title: 'ra-ui-materialui/input/SelectArrayInput' }; const i18nProvider = polyglotI18nProvider(() => englishMessages); @@ -82,6 +83,148 @@ export const Basic = () => ( ); +export const Disabled = () => ( + + + + + + + + + + + + + + + + + + +); + +export const ReadOnly = () => ( + + + + + + + + + + + + + + + + + + +); + export const DefaultValue = () => ( diff --git a/packages/ra-ui-materialui/src/input/SelectArrayInput.tsx b/packages/ra-ui-materialui/src/input/SelectArrayInput.tsx index 5b0c0f5cfbe..8989b8ae4c2 100644 --- a/packages/ra-ui-materialui/src/input/SelectArrayInput.tsx +++ b/packages/ra-ui-materialui/src/input/SelectArrayInput.tsx @@ -114,6 +114,8 @@ export const SelectArrayInput = (props: SelectArrayInputProps) => { translateChoice, validate, variant, + disabled, + readOnly, ...rest } = props; @@ -155,6 +157,8 @@ export const SelectArrayInput = (props: SelectArrayInputProps) => { resource, source, validate, + disabled, + readOnly, ...rest, }); @@ -338,6 +342,8 @@ export const SelectArrayInput = (props: SelectArrayInputProps) => { ))}
)} + disabled={disabled || readOnly} + readOnly={readOnly} data-testid="selectArray" size={size} {...field} diff --git a/packages/ra-ui-materialui/src/input/SelectInput.stories.tsx b/packages/ra-ui-materialui/src/input/SelectInput.stories.tsx index b0cccbbec0e..3a817c25730 100644 --- a/packages/ra-ui-materialui/src/input/SelectInput.stories.tsx +++ b/packages/ra-ui-materialui/src/input/SelectInput.stories.tsx @@ -75,6 +75,37 @@ export const Disabled = () => ( ]} disabled /> + + +); + +export const ReadOnly = () => ( + + + ); diff --git a/packages/ra-ui-materialui/src/input/TextInput.stories.tsx b/packages/ra-ui-materialui/src/input/TextInput.stories.tsx index 45b1925ac7d..29305938e71 100644 --- a/packages/ra-ui-materialui/src/input/TextInput.stories.tsx +++ b/packages/ra-ui-materialui/src/input/TextInput.stories.tsx @@ -27,6 +27,38 @@ export const Basic = () => ( ); +export const Disabled = () => ( + + + + + + + + + +); + +export const ReadOnly = () => ( + + + + + + + + + +); + export const DefaultValue = () => ( ( export const Disabled = () => ( + + +); +export const ReadOnly = () => ( + + + ); diff --git a/packages/ra-ui-materialui/src/input/TimeInput.tsx b/packages/ra-ui-materialui/src/input/TimeInput.tsx index d9aa4bbb85f..3a55a16afad 100644 --- a/packages/ra-ui-materialui/src/input/TimeInput.tsx +++ b/packages/ra-ui-materialui/src/input/TimeInput.tsx @@ -55,6 +55,8 @@ export const TimeInput = ({ onChange, source, resource, + disabled, + readOnly, parse = parseTime, validate, variant, @@ -69,6 +71,8 @@ export const TimeInput = ({ resource, source, validate, + readOnly, + disabled, ...rest, }); @@ -87,6 +91,8 @@ export const TimeInput = ({ size="small" variant={variant} margin={margin} + disabled={disabled || readOnly} + readOnly={readOnly} error={(isTouched || isSubmitted) && invalid} helperText={ renderHelperText ? (