Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 36 additions & 0 deletions docs/CheckboxGroupInput.md
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@ The form value for the source must be an array of the selected values, e.g.
| `optionValue` | Optional | `string` | `id` | Field name of record containing the value to use as input value |
| `row` | Optional | `boolean` | `true` | Display group of elements in a compact row. |
| `translateChoice` | Optional | `boolean` | `true` | Whether the choices should be translated |
| `disableValue` | Optional | `string` | `disabled` | The custom field name used in `choices` to disable some choices |

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

Expand Down Expand Up @@ -92,6 +93,17 @@ You can also use an array of objects with different properties for the label and
]} optionValue="_id" optionText="label" />
```

You can render some options as disabled by setting the `disabled` field in some choices:

```jsx
const choices = [
{ id: 'tech', name: 'Tech' },
{ id: 'lifestyle', name: 'Lifestyle' },
{ id: 'people', name: 'People', disabled: true },
];
<RadioButtonGroupInput source="category" choices={choices} />
```

The choices are translated by default, so you can use translation identifiers as choices:

```jsx
Expand Down Expand Up @@ -255,6 +267,30 @@ However, in some cases (e.g. inside a `<ReferenceArrayInput>`), you may not want
<CheckboxGroupInput source="roles" choices={choices} translateChoice={false}/>
```

## `disableValue`

By default, `<CheckboxGroupInput>` renders the choices with the field `disabled: true` as disabled.

```jsx
const choices = [
{ id: 'tech', name: 'Tech' },
{ id: 'lifestyle', name: 'Lifestyle' },
{ id: 'people', name: 'People', disabled: true },
];
<CheckboxGroupInput source="category" choices={choices} />
```

If you want to use another field to denote disabled options, set the `disableValue` prop.

```jsx
const choices = [
{ id: 'tech', name: 'Tech' },
{ id: 'lifestyle', name: 'Lifestyle' },
{ id: 'people', name: 'People', not_available: true },
];
<CheckboxGroupInput source="category" choices={choices} disableValue="not_available" />
```

## Fetching Choices

If you want to populate the `choices` attribute with a list of related records, you should decorate `<CheckboxGroupInput>` with [`<ReferenceArrayInput>`](./ReferenceArrayInput.md), and leave the `choices` empty:
Expand Down
38 changes: 37 additions & 1 deletion docs/RadioButtonGroupInput.md
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ The form value for the source must be the selected value, e.g.
| `optionValue` | Optional | `string` | `id` | Field name of record containing the value to use as input value |
| `row` | Optional | `boolean` | `true` | Display options in a compact row. |
| `translateChoice` | Optional | `boolean` | `true` | Whether the choices should be translated |
| `disableValue` | Optional | `string` | `disabled` | The custom field name used in `choices` to disable some choices |

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

Expand Down Expand Up @@ -94,6 +95,17 @@ const choices = [
/>
```

You can render some options as disabled by setting the `disabled` field in some choices:

```jsx
const choices = [
{ id: 'tech', name: 'Tech' },
{ id: 'lifestyle', name: 'Lifestyle' },
{ id: 'people', name: 'People', disabled: true },
];
<RadioButtonGroupInput source="category" choices={choices} />
```

Comment thread
WiXSL marked this conversation as resolved.
The choices are translated by default, so you can use translation identifiers as choices:

```jsx
Expand Down Expand Up @@ -282,6 +294,30 @@ However, in some cases, you may not want the choice to be translated. In that ca

Note that `translateChoice` is set to `false` when `<RadioButtonGroupInput>` is a child of `<ReferenceInput>`.

## `disableValue`

By default, `<RadioButtonGroupInput>` renders the choices with the field `disabled: true` as disabled.

```jsx
const choices = [
{ id: 'tech', name: 'Tech' },
{ id: 'lifestyle', name: 'Lifestyle' },
{ id: 'people', name: 'People', disabled: true },
];
<RadioButtonGroupInput source="category" choices={choices} />
```

If you want to use another field to denote disabled options, set the `disableValue` prop.

```jsx
const choices = [
{ id: 'tech', name: 'Tech' },
{ id: 'lifestyle', name: 'Lifestyle' },
{ id: 'people', name: 'People', not_available: true },
];
<RadioButtonGroupInput source="category" choices={choices} disableValue="not_available" />
```

## Fetching Choices

You can use [`useGetList`](./useGetList.md) to fetch choices. For example, to fetch a list of countries for a user profile:
Expand Down Expand Up @@ -390,4 +426,4 @@ const CompanyInput = () => (

This is the recommended approach for using `<RadioButtonGroupInput>` to select a foreign key. This not only signifies that the input is a `<RadioButtonGroupInput>` but also highlights its function in fetching choices from another resource, ultimately enhancing the code's readability.

**Tip**: `<ReferenceInput>` is much more powerful than the initial snippet. It optimizes and caches API calls, enables refetching of both API calls with a single command, and stores supplementary data in the `<ChoicesContext>`. `<ReferenceInput>` can provide choices to `<RadioButtonGroupInput>`, but also to [`<AutocompleteInput>`](./AutocompleteInput.md) and [`<SelectInput>`](./SelectInput.md). For further information, refer to [the `<ReferenceInput>` documentation](./ReferenceInput.md).
**Tip**: `<ReferenceInput>` is much more powerful than the initial snippet. It optimizes and caches API calls, enables refetching of both API calls with a single command, and stores supplementary data in the `<ChoicesContext>`. `<ReferenceInput>` can provide choices to `<RadioButtonGroupInput>`, but also to [`<AutocompleteInput>`](./AutocompleteInput.md) and [`<SelectInput>`](./SelectInput.md). For further information, refer to [the `<ReferenceInput>` documentation](./ReferenceInput.md).
1 change: 1 addition & 0 deletions packages/ra-core/src/form/choices/useChoices.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ export interface ChoicesProps {
optionValue?: string;
optionText?: OptionText;
translateChoice?: boolean;
disableValue?: string;
}

export interface UseChoicesOptions {
Expand Down
2 changes: 1 addition & 1 deletion packages/ra-ui-materialui/src/field/SelectField.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -138,7 +138,7 @@ export const SelectField = genericMemo(SelectFieldImpl);

export interface SelectFieldProps<
RecordType extends Record<string, any> = Record<string, any>,
> extends ChoicesProps,
> extends Omit<ChoicesProps, 'disableValue'>,
FieldProps<RecordType>,
Omit<TypographyProps, 'textAlign'> {}

Expand Down
2 changes: 1 addition & 1 deletion packages/ra-ui-materialui/src/input/AutocompleteInput.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -780,7 +780,7 @@ export interface AutocompleteInputProps<
DisableClearable extends boolean | undefined = false,
SupportCreate extends boolean | undefined = false,
> extends Omit<CommonInputProps, 'source' | 'onChange'>,
ChoicesProps,
Omit<ChoicesProps, 'disableValue'>,
UseSuggestionsOptions,
Omit<SupportCreateSuggestionOptions, 'handleChange' | 'optionText'>,
Omit<
Expand Down
61 changes: 61 additions & 0 deletions packages/ra-ui-materialui/src/input/CheckboxGroupInput.spec.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -426,4 +426,65 @@ describe('<CheckboxGroupInput />', () => {
await screen.findByText('Option 1 (This is option 1)');
});
});

it('should render disabled choices marked as so', () => {
const choices = [
{ id: 'ang', name: 'Angular' },
{ id: 'rct', name: 'React', disabled: true },
];
render(
<AdminContext dataProvider={testDataProvider()}>
<ResourceContextProvider value="posts">
<SimpleForm onSubmit={jest.fn()}>
<CheckboxGroupInput
{...defaultProps}
choices={choices}
/>
</SimpleForm>
</ResourceContextProvider>
</AdminContext>
);
fireEvent.mouseDown(screen.getByLabelText('React'));

const enabledInput = screen.getByLabelText(
'Angular'
) as HTMLInputElement;
expect(enabledInput.disabled).toBe(false);

const disabledInput = screen.getByLabelText(
'React'
) as HTMLInputElement;
expect(disabledInput.disabled).toBe(true);
});

it('should render disabled choices marked as so by disableValue prop', () => {
const choices = [
{ id: 'ang', name: 'Angular' },
{ id: 'rct', name: 'React', not_available: true },
];
render(
<AdminContext dataProvider={testDataProvider()}>
<ResourceContextProvider value="posts">
<SimpleForm onSubmit={jest.fn()}>
<CheckboxGroupInput
{...defaultProps}
choices={choices}
disableValue="not_available"
/>
</SimpleForm>
</ResourceContextProvider>
</AdminContext>
);
fireEvent.mouseDown(screen.getByLabelText('React'));

const enabledInput = screen.getByLabelText(
'Angular'
) as HTMLInputElement;
expect(enabledInput.disabled).toBe(false);

const disabledInput = screen.getByLabelText(
'React'
) as HTMLInputElement;
expect(disabledInput.disabled).toBe(true);
});
});
14 changes: 14 additions & 0 deletions packages/ra-ui-materialui/src/input/CheckboxGroupInput.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -322,3 +322,17 @@ export const SetFocus = () => (
<SetFocusButton source="roles" />
</Wrapper>
);

export const DisabledChoice = () => (
<Wrapper>
<CheckboxGroupInput
source="roles"
choices={[
{ id: 'admin', name: 'Admin' },
{ id: 'u001', name: 'Editor' },
{ id: 'u002', name: 'Moderator' },
{ id: 'u003', name: 'Reviewer', disabled: true },
]}
/>
</Wrapper>
);
19 changes: 19 additions & 0 deletions packages/ra-ui-materialui/src/input/CheckboxGroupInput.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,23 @@ import { LinearProgress } from '../layout';
* <CheckboxGroupInput source="tags" choices={choices} translateChoice={false}/>
*
* The object passed as `options` props is passed to the Material UI <Checkbox> components
*
* You can disable some choices by providing a `disableValue` field which name is `disabled` by default
* @example
* const choices = [
* { id: 'programming', name: 'myroot.category.programming' },
* { id: 'lifestyle', name: 'myroot.category.lifestyle' },
* { id: 'photography', name: 'myroot.category.photography', disabled: true },
* ];
*
* @example
* const choices = [
* { id: 'programming', name: 'myroot.category.programming' },
* { id: 'lifestyle', name: 'myroot.category.lifestyle' },
* { id: 'photography', name: 'myroot.category.photography', not_available: true },
* ];
* <CheckboxGroupInput source="tags" choices={choices} disableValue="not_available" />
*
*/
export const CheckboxGroupInput = (inProps: CheckboxGroupInputProps) => {
const props = useThemeProps({
Expand Down Expand Up @@ -124,6 +141,7 @@ export const CheckboxGroupInput = (inProps: CheckboxGroupInputProps) => {
source: sourceProp,
translateChoice,
validate,
disableValue = 'disabled',
disabled,
readOnly,
...rest
Expand Down Expand Up @@ -262,6 +280,7 @@ export const CheckboxGroupInput = (inProps: CheckboxGroupInputProps) => {
value={value}
labelPlacement={labelPlacement}
inputRef={index === 0 ? ref : undefined}
disableValue={disableValue}
disabled={disabled || readOnly}
readOnly={readOnly}
{...sanitizeRestProps(rest)}
Expand Down
11 changes: 9 additions & 2 deletions packages/ra-ui-materialui/src/input/CheckboxGroupInputItem.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -32,16 +32,19 @@ export const CheckboxGroupInputItem = (
value,
labelPlacement,
inputRef,
disableValue = 'disabled',
...rest
} = props;

const { getChoiceText, getChoiceValue } = useChoices({
const { getChoiceText, getChoiceValue, getDisableValue } = useChoices({
optionText,
optionValue,
translateChoice,
disableValue,
});

const choiceName = getChoiceText(choice);
const disabled = getDisableValue(choice);

return (
<StyledFormControlLabel
Expand All @@ -62,6 +65,7 @@ export const CheckboxGroupInputItem = (
: false
}
value={String(getChoiceValue(choice))}
disabled={disabled}
{...options}
/>
}
Expand All @@ -74,7 +78,10 @@ export const CheckboxGroupInputItem = (

export interface CheckboxGroupInputItemProps
extends Omit<FormControlLabelProps, 'control' | 'label'>,
Pick<ChoicesProps, 'optionValue' | 'optionText' | 'translateChoice'> {
Pick<
ChoicesProps,
'optionValue' | 'optionText' | 'translateChoice' | 'disableValue'
> {
choice: any;
value: any;
fullWidth?: boolean;
Expand Down
2 changes: 1 addition & 1 deletion packages/ra-ui-materialui/src/input/DatagridInput.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -177,7 +177,7 @@ export type DatagridInputProps = Omit<
CommonInputProps,
'source' | 'readOnly' | 'disabled'
> &
ChoicesProps &
Omit<ChoicesProps, 'disableValue'> &
Omit<SupportCreateSuggestionOptions, 'handleChange'> &
DatagridProps & {
children?: ReactNode;
Expand Down
57 changes: 57 additions & 0 deletions packages/ra-ui-materialui/src/input/RadioButtonGroupInput.spec.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -541,6 +541,63 @@ describe('<RadioButtonGroupInput />', () => {
expect(screen.queryByRole('progressbar')).toBeNull();
});

it('should render disabled choices marked as so', () => {
const choices = [
{ id: 1, name: 'VISA' },
{ id: 2, name: 'Mastercard', disabled: true },
];
render(
<AdminContext dataProvider={testDataProvider()}>
<ResourceContextProvider value="creditcards">
<SimpleForm onSubmit={jest.fn()}>
<RadioButtonGroupInput
{...defaultProps}
choices={choices}
/>
</SimpleForm>
</ResourceContextProvider>
</AdminContext>
);
fireEvent.mouseDown(screen.getByLabelText('Mastercard'));

const enabledInput = screen.getByLabelText('VISA') as HTMLInputElement;
expect(enabledInput.disabled).toBe(false);

const disabledInput = screen.getByLabelText(
'Mastercard'
) as HTMLInputElement;
expect(disabledInput.disabled).toBe(true);
});

it('should render disabled choices marked as so by disableValue prop', () => {
const choices = [
{ id: 1, name: 'VISA' },
{ id: 2, name: 'Mastercard', not_available: true },
];
render(
<AdminContext dataProvider={testDataProvider()}>
<ResourceContextProvider value="creditcards">
<SimpleForm onSubmit={jest.fn()}>
<RadioButtonGroupInput
{...defaultProps}
choices={choices}
disableValue="not_available"
/>
</SimpleForm>
</ResourceContextProvider>
</AdminContext>
);
fireEvent.mouseDown(screen.getByLabelText('Mastercard'));

const enabledInput = screen.getByLabelText('VISA') as HTMLInputElement;
expect(enabledInput.disabled).toBe(false);

const disabledInput = screen.getByLabelText(
'Mastercard'
) as HTMLInputElement;
expect(disabledInput.disabled).toBe(true);
});

describe('inside ReferenceArrayInput', () => {
it('should use the recordRepresentation as optionText', async () => {
render(<InsideReferenceArrayInput />);
Expand Down
Loading
Loading