Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix <CheckboxGroupInput options> prop is ignored #8291

Merged
merged 10 commits into from
Oct 24, 2022
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
224 changes: 181 additions & 43 deletions docs/CheckboxGroupInput.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,96 +7,215 @@ title: "The CheckboxGroupInput Component"

If you want to let the user choose multiple values among a list of possible values by showing them all, `<CheckboxGroupInput>` is the right component.

![CheckboxGroupInput](./img/checkbox-group-input.png)
![CheckboxGroupInput](./img/checkbox-group-input.gif)

Set the `choices` attribute to determine the options (with `id`, `name` tuples):
This input allows editing values that are arrays of scalar values, e.g. `[123, 456]`.

**Tip**: React-admin includes other components allowing the edition of such values:

- [`<SelectArrayInput>`](./SelectArrayInput.md) renders a dropdown list of choices
- [`<AutocompleteArrayInput>`](./AutocompleteArrayInput.md) renders an autocomplete input of choices

And if you are looking for a way to edit a list of embedded objects (e.g. `[{ id: 123, title: 'Hello' }, { id: 456, title: 'World' }]`), check the [`<ArrayInput>`](./ArrayInput.md) component.

## Usage

In addition to the `source`, `<CheckboxGroupInput>` requires one prop: the `choices` listing the possible values.

```jsx
import { CheckboxGroupInput } from 'react-admin';

<CheckboxGroupInput source="category" choices={[
{ id: 'programming', name: 'Programming' },
{ id: 'lifestyle', name: 'Lifestyle' },
{ id: 'photography', name: 'Photography' },
<CheckboxGroupInput source="roles" choices={[
{ id: 'admin', name: 'Admin' },
{ id: 'u001', name: 'Editor' },
{ id: 'u002', name: 'Moderator' },
{ id: 'u003', name: 'Reviewer' },
]} />
```

## Properties
By default, the possible choices are built from the `choices` prop, using:
- the `id` field as the option value,
- the `name` field as the option text

| Prop | Required | Type | Default | Description |
| ------------- | -------- | -------------------------- | ------- | ------------------------------------------------------------------------------------------------------------------------------------- |
| `choices` | Required | `Object[]` | - | List of choices |
| `optionText` | Optional | `string` &#124; `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 |
| `row` | Optional | `boolean` | `true` | Display group of elements in a compact row. |
| `labelPlacement` | Optional | `"bottom" `&#124;`"end"`&#124;`"start"`&#124;`"top" ` | `"end"` | The position of the checkbox label. |
The form value for the source must be an array of the selected values, e.g.

Refer to [MUI Checkbox documentation](https://mui.com/api/checkbox/) for more details.
```js
{
id: 123,
name: 'John Doe',
roles: ['u001', 'u003'],
}
```

## Props

| Prop | Required | Type | Default | Description |
| ----------------- | -------- | -------------------------- | ------- | ----------------------------------------------------------------- |
| `choices` | Required | `Object[]` | - | List of choices |
| `labelPlacement` | Optional | `"bottom" `&#124;`"end"`&#124;`"start"`&#124;`"top" ` | `"end"` | The position of the checkbox label. |
| `options` | Optional | `Object` | - | Props to pass to the MUI `<CheckboxGroup>` component. |
| `optionText` | Optional | `string` &#124; `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 |
| `row` | Optional | `boolean` | `true` | Display group of elements in a compact row. |
| `translateChoice` | Optional | `boolean` | `true` | Whether the choices should be translated |

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

## Usage
## `choices`

The list of choices must be an array of objects - one object for each possible choice. In each object, `id` is the value, and the `name` is the label displayed to the user.

```jsx
<CheckboxGroupInput source="roles" choices={[
{ id: 'admin', name: 'Admin' },
{ id: 'u001', name: 'Editor' },
{ id: 'u002', name: 'Moderator' },
{ id: 'u003', name: 'Reviewer' },
]} />
```

You can also use an array of objects with different properties for the label and value, given you specify the `optionText` and `optionValue` props:

```jsx
<CheckboxGroupInput source="roles" choices={[
{ _id: 'admin', label: 'Admin' },
{ _id: 'u001', label: 'Editor' },
{ _id: 'u002', label: 'Moderator' },
{ _id: 'u003', label: 'Reviewer' },
]} optionValue="_id" optionText="label" />
```

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

```jsx
const choices = [
{ id: 'admin', label: 'myroot.roles.admin' },
{ id: 'u001', label: 'myroot.roles.u001' },
{ id: 'u002', label: 'myroot.roles.u002' },
{ id: 'u003', label: 'myroot.roles.u003' },
];
```

You can opt-out of this translation by setting [the `translateChoice` prop](#translatechoice) to `false`.

If you need to *fetch* the options from another resource, you're actually editing a one-to-many or a many-to-many relationship. In this case, wrap the `<CheckboxGroupInput>` in a [`<ReferenceArrayInput>`](./ReferenceArrayInput.md) or a [`<ReferenceManyToManyInput>`](./ReferenceManyToManyInput.md) component. You don't need to specify the `choices` prop - the parent component injects it based on the possible values of the related resource.

```jsx
<ReferenceArrayInput source="tag_ids" reference="tags">
<CheckboxGroupInput />
</ReferenceArrayInput>
```

If you have an *array of values* for the options, turn it into an array of objects with the `id` and `name` properties:

```jsx
const possibleValues = ['programming', 'lifestyle', 'photography'];
const ucfirst = name => name.charAt(0).toUpperCase() + name.slice(1);
const choices = possibleValues.map(value => ({ id: value, name: ucfirst(value) }));

<CheckboxGroupInput source="roles" choices={choices} />
```

## `labelPlacement`

By default, this inputs renders a checkbox and a label for each choice, with the label on the right of the checkbox. You can change this behavior with the `labelPlacement` prop:

```jsx
<CheckboxGroupInput source="options" choices={choices} labelPlacement="bottom" />
```

![labelPlacement bottom](./img/CheckboxGroupInput-labelPlacement.png)

## `options`

Use the `options` attribute if you want to override any of MUI's [MUI Checkbox documentation](https://mui.com/api/checkbox/) attributes:

You can customize the properties to use for the option name and value, thanks to the `optionText` and `optionValue` attributes:
{% raw %}
```jsx
import { FavoriteBorder, Favorite } from '@mui/icons-material';

<CheckboxGroupInput source="options" options={{
icon: <FavoriteBorder />,
checkedIcon: <Favorite />
}} />
```
{% endraw %}

![row bottom](./img/CheckboxGroupInput-options.png)

## `optionText`

You can customize the properties to use for the option name (instead of the default `name`) thanks to the `optionText` prop:

```jsx
const choices = [
{ _id: 123, full_name: 'Leo Tolstoi', sex: 'M' },
{ _id: 456, full_name: 'Jane Austen', sex: 'F' },
{ id: 'admin', label: 'Admin' },
{ id: 'u001', label: 'Editor' },
{ id: 'u002', label: 'Moderator' },
{ id: 'u003', label: 'Reviewer' },
];
<CheckboxGroupInput source="author_id" choices={choices} optionText="full_name" optionValue="_id" />
<CheckboxGroupInput source="roles" choices={choices} optionText="label" />
```

`optionText` is especially useful when the choices are records coming from a `<ReferenceArrayInput>` or a `<ReferenceManyToManyInput>`. By default, react-admin uses the [`recordRepresentation`](./Resource.md#recordrepresentation) function to display the record label. But if you set the `optionText` prop, react-admin will use it instead.

```jsx
<ReferenceArrayInput source="tag_ids" reference="tags">
<CheckboxGroupInput optionText="tag" />
</ReferenceArrayInput>
```

`optionText` also accepts a function, so you can shape the option text at will:
`optionText` also accepts a function, so you can shape the option text based on the entire choice object:

```jsx
const choices = [
{ 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}`;
<CheckboxGroupInput source="author_id" choices={choices} optionText={optionRenderer} />

<CheckboxGroupInput source="authors" choices={choices} optionText={optionRenderer} />
```

`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.
`optionText` also accepts a React Element, that will be rendered inside a [`<RecordContext>`](./useRecordContext.md) using 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' },
];
const FullNameField = ({ record }) => <span>{record.first_name} {record.last_name}</span>;
<CheckboxGroupInput source="gender" choices={choices} optionText={<FullNameField />}/>

const FullNameField = () => {
const record = useRecordContext();
return <span>{record.first_name} {record.last_name}</span>;
}

<CheckboxGroupInput source="authors" choices={choices} optionText={<FullNameField />}/>
```

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

You can customize the properties to use for the option value (instead of the default `id`) thanks to the `optionValue` prop:

```jsx
const choices = [
{ id: 'programming', name: 'myroot.category.programming' },
{ id: 'lifestyle', name: 'myroot.category.lifestyle' },
{ id: 'photography', name: 'myroot.category.photography' },
{ _id: 'admin', name: 'Admin' },
{ _id: 'u001', name: 'Editor' },
{ _id: 'u002', name: 'Moderator' },
{ _id: 'u003', name: 'Reviewer' },
];
<CheckboxGroupInput source="roles" choices={choices} optionValue="_id" />
```

However, in some cases (e.g. inside a `<ReferenceInput>`), you may not want the choice to be translated. In that case, set the `translateChoice` prop to `false`.

```jsx
<CheckboxGroupInput source="gender" choices={choices} translateChoice={false}/>
```
## `row`

Lastly, use the `options` attribute if you want to override any of MUI's `<Checkbox>` attributes:
By default, the checkboxes are displayed in a row. You can change that and let react-admin render one choice per row by setting the `row` prop to `false`:

{% raw %}
```jsx
import { FavoriteBorder, Favorite } from '@mui/icons-material';

<CheckboxGroupInput source="category" options={{
icon: <FavoriteBorder />,
checkedIcon: <Favorite />
}} />
<CheckboxGroupInput source="options" choices={choices} row={false} />
```
{% endraw %}

![row bottom](./img/CheckboxGroupInput-row.png)

## `sx`: CSS API

Expand All @@ -107,3 +226,22 @@ The `<CheckboxGroupInput>` component accepts the usual `className` prop. You can
| `& .RaCheckboxGroupInput-label` | Applied to the underlying MUI's `FormLabel` component |

To override the style of all instances of `<CheckboxGroupInput>` using the [MUI style overrides](https://mui.com/customization/globals/#css), use the `RaCheckboxGroupInput` key.

## `translateChoice`

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

```jsx
const choices = [
{ id: 'admin', label: 'myroot.roles.admin' },
{ id: 'u001', label: 'myroot.roles.u001' },
{ id: 'u002', label: 'myroot.roles.u002' },
{ id: 'u003', label: 'myroot.roles.u003' },
];
```

However, in some cases (e.g. inside a `<ReferenceArrayInput>`), you may not want the choice to be translated. In that case, set the `translateChoice` prop to `false`.

```jsx
<CheckboxGroupInput source="roles" choices={choices} translateChoice={false}/>
```
Binary file added docs/img/CheckboxGroupInput-labelPlacement.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/img/CheckboxGroupInput-options.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/img/CheckboxGroupInput-row.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/img/checkbox-group-input.gif
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
56 changes: 52 additions & 4 deletions packages/ra-ui-materialui/src/input/CheckboxGroupInput.stories.tsx
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
import * as React from 'react';
import polyglotI18nProvider from 'ra-i18n-polyglot';
import englishMessages from 'ra-language-english';
import { Typography } from '@mui/material';
import { FavoriteBorder, Favorite } from '@mui/icons-material';
import { testDataProvider, useRecordContext } from 'ra-core';

import { AdminContext } from '../AdminContext';
import { Create } from '../detail';
import { SimpleForm } from '../form';
import { CheckboxGroupInput } from './CheckboxGroupInput';
import { Typography } from '@mui/material';
import { testDataProvider, useRecordContext } from 'ra-core';
import { ReferenceArrayInput } from './ReferenceArrayInput';

export default { title: 'ra-ui-materialui/input/CheckboxGroupInput' };
Expand All @@ -27,11 +28,19 @@ export const Basic = () => (
<AdminContext i18nProvider={i18nProvider}>
<Create
resource="posts"
record={{ options: [1, 2] }}
record={{ roles: ['u001', 'u003'] }}
sx={{ width: 600 }}
>
<SimpleForm>
<CheckboxGroupInput source="options" choices={choices} />
<CheckboxGroupInput
source="roles"
choices={[
{ id: 'admin', name: 'Admin' },
{ id: 'u001', name: 'Editor' },
{ id: 'u002', name: 'Moderator' },
{ id: 'u003', name: 'Reviewer' },
]}
/>
</SimpleForm>
</Create>
</AdminContext>
Expand Down Expand Up @@ -80,6 +89,24 @@ export const Disabled = () => (
</AdminContext>
);

export const LabelPlacement = () => (
<AdminContext i18nProvider={i18nProvider}>
<Create
resource="posts"
record={{ options: [1, 2] }}
sx={{ width: 600 }}
>
<SimpleForm>
<CheckboxGroupInput
source="options"
choices={choices}
labelPlacement="bottom"
/>
</SimpleForm>
</Create>
</AdminContext>
);

export const Column = () => (
<AdminContext i18nProvider={i18nProvider}>
<Create
Expand All @@ -98,6 +125,27 @@ export const Column = () => (
</AdminContext>
);

export const Options = () => (
<AdminContext i18nProvider={i18nProvider}>
<Create
resource="posts"
record={{ options: [1, 2] }}
sx={{ width: 600 }}
>
<SimpleForm>
<CheckboxGroupInput
source="options"
choices={choices}
options={{
icon: <FavoriteBorder />,
checkedIcon: <Favorite />,
}}
/>
</SimpleForm>
</Create>
</AdminContext>
);

export const CustomOptionText = () => (
<AdminContext i18nProvider={i18nProvider}>
<Create
Expand Down
Loading