Skip to content

Commit

Permalink
Merge pull request #10321 from marmelab/Autocomplete-createLabel-disa…
Browse files Browse the repository at this point in the history
…bled

Fix `createLabel` option should not be clickable for `<AutocompleteInput>` and `<AutocompleteArrayInput>`
  • Loading branch information
djhi authored Nov 8, 2024
2 parents 19efa7e + 15d0ee7 commit c795bf2
Show file tree
Hide file tree
Showing 13 changed files with 250 additions and 146 deletions.
37 changes: 34 additions & 3 deletions docs/AutocompleteArrayInput.md
Original file line number Diff line number Diff line change
Expand Up @@ -60,8 +60,8 @@ The form value for the source must be an array of the selected values, e.g.
|----------------------------|----------|-----------------------|--------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| `choices` | Required | `Object[]` | - | List of choices |
| `create` | Optional | `Element` | `-` | A React Element to render when users want to create a new choice |
| `createLabel` | Optional | `string` | `ra.action. create` | The label for the menu item allowing users to create a new choice. Used when the filter is empty |
| `createItemLabel` | Optional | `string` | `ra.action .create_item` | The label for the menu item allowing users to create a new choice. Used when the filter is not empty |
| `createLabel` | Optional | `string` | - | The label used as hint to let users know they can create a new choice. Displayed when the filter is empty. |
| `createItemLabel` | Optional | `string` | `ra.action .create_item` | The label for the menu item allowing users to create a new choice. Used when the filter is not empty. |
| `debounce` | Optional | `number` | `250` | The delay to wait before calling the setFilter function injected when used in a ReferenceArray Input. |
| `emptyValue` | Optional | `any` | `''` | The value to use for the empty element |
| `filterToQuery` | Optional | `string` => `Object` | `q => ({ q })` | How to transform the searchText into a parameter for the data provider |
Expand Down Expand Up @@ -159,7 +159,7 @@ const choices = [
const UserCreate = () => (
<Create>
<SimpleForm>
<SelectArrayInput
<AutocompleteArrayInput
source="roles"
choices={choices}
create={<CreateRole />}
Expand Down Expand Up @@ -216,6 +216,37 @@ If you just need to ask users for a single string to create the new option, you

If you're in a `<ReferenceArrayInput>` or `<ReferenceManyToManyInput>`, the `handleSubmit` will need to create a new record in the related resource. Check the [Creating New Choices](#creating-new-choices) for an example.

## `createLabel`

When you set the `create` or `onCreate` prop, `<AutocompleteArrayInput>` lets users create new options.
You can use the `createLabel` prop to render an additional (disabled) menu item at the bottom of the list, that will only appear when the input is empty, inviting users to start typing to create a new option.

```jsx
<AutocompleteArrayInput
source="roles"
choices={choices}
create={<CreateRole />}
createLabel="Start typing to create a new item"
/>
```

## `createItemLabel`

If you set the `create` or `onCreate` prop, `<AutocompleteArrayInput>` lets users create new options. When the text entered by the user doesn't match any option, the input renders a "Create XXX" menu item at the bottom of the list.

You can customize the label of that menu item by setting a custom translation for the `ra.action.create_item` key in the translation files.

Or, if you want to customize it just for this `<AutocompleteArrayInput>`, use the `createItemLabel` prop:

```jsx
<AutocompleteArrayInput
source="roles"
choices={choices}
create={<CreateRole />}
createItemLabel="Add a new role: %{item}"
/>
```

## `debounce`

When used inside a [`<ReferenceArrayInput>`](./ReferenceArrayInput.md), `<AutocompleteArrayInput>` will call `dataProvider.getList()` with the current input value as filter after a delay of 250ms. This is to avoid calling the API too often while users are typing their query.
Expand Down
6 changes: 3 additions & 3 deletions docs/AutocompleteInput.md
Original file line number Diff line number Diff line change
Expand Up @@ -60,8 +60,8 @@ The form value for the source must be the selected value, e.g.
|--------------------------- |----------|---------------------- |---------------------------------------------------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| `choices` | Optional | `Object[]` | `-` | List of items to autosuggest. Required if not inside a ReferenceInput. |
| `create` | Optional | `Element` | `-` | A React Element to render when users want to create a new choice |
| `createLabel` | Optional | `string` | `ra.action .create` | The label for the menu item allowing users to create a new choice. Used when the filter is empty |
| `createItemLabel` | Optional | `string` | `ra.action .create_item` | The label for the menu item allowing users to create a new choice. Used when the filter is not empty |
| `createLabel` | Optional | `string` | - | The label used as hint to let users know they can create a new choice. Displayed when the filter is empty. |
| `createItemLabel` | Optional | `string` | `ra.action .create_item` | The label for the menu item allowing users to create a new choice. Used when the filter is not empty. |
| `debounce` | Optional | `number` | `250` | The delay to wait before calling the setFilter function injected when used in a ReferenceInput. |
| `emptyText` | Optional | `string` | `''` | The text to use for the empty element |
| `emptyValue` | Optional | `any` | `''` | The value to use for the empty element |
Expand Down Expand Up @@ -226,7 +226,7 @@ If you just need to ask users for a single string to create the new option, you
## `createLabel`

When you set the `create` or `onCreate` prop, `<AutocompleteInput>` lets users create new options.
You can use the `createLabel` prop to render an additional menu item at the bottom of the list, that will only appear when the input is empty, inviting users to start typing to create a new option.
You can use the `createLabel` prop to render an additional (disabled) menu item at the bottom of the list, that will only appear when the input is empty, inviting users to start typing to create a new option.

![Create Label](./img/AutocompleteInput-createLabel.png)

Expand Down
15 changes: 15 additions & 0 deletions docs/SelectArrayInput.md
Original file line number Diff line number Diff line change
Expand Up @@ -223,6 +223,21 @@ If you just need to ask users for a single string to create the new option, you

If you're in a `<ReferenceArrayInput>` or `<ReferenceManyToManyInput>`, the `handleSubmit` will need to create a new record in the related resource. Check the [Creating New Choices](#creating-new-choices) for an example.

## `createLabel`

When you set the `create` or `onCreate` prop to let users create new options, `<SelectArrayInput>` renders a "Create" menu item at the bottom of the list. You can customize the label of that menu item by setting a custom translation for the `ra.action.create` key in the translation files.

Or, if you want to customize it just for this `<SelectArrayInput>`, use the `createLabel` prop:

```jsx
<SelectArrayInput
source="roles"
choices={choices}
create={<CreateRole />}
createLabel="Add a new role"
/>
```

## `disableValue`

By default, `<SelectArrayInput>` renders the choices with the field `disabled: true` as disabled.
Expand Down
Binary file modified docs/img/AutocompleteInput-createLabel.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ import { SimpleForm } from '../form';
import { AutocompleteArrayInput } from './AutocompleteArrayInput';
import { useCreateSuggestionContext } from './useSupportCreateSuggestion';
import {
CreateItemLabel,
CreateLabel,
InsideReferenceArrayInput,
InsideReferenceArrayInputOnChange,
OnChange,
Expand Down Expand Up @@ -868,6 +870,50 @@ describe('<AutocompleteArrayInput />', () => {
expect(screen.queryByText('New Kid On The Block')).not.toBeNull();
});

it('should support using a custom createLabel', async () => {
render(<CreateLabel />);
const input = (await screen.findByLabelText(
'Roles'
)) as HTMLInputElement;
input.focus();

// Expect the custom create label to be present and disabled
const customCreateLabel = await screen.findByText(
'Start typing to create a new item'
);
expect(customCreateLabel.getAttribute('aria-disabled')).toEqual('true');

// Expect the creation workflow to still work
fireEvent.change(input, { target: { value: 'new role' } });
fireEvent.click(await screen.findByText('Create new role'));
// Expect a dialog to have opened
const dialogInput = (await screen.findByLabelText(
'Role name'
)) as HTMLInputElement;
expect(dialogInput.value).toEqual('new role');
});

it('should support using a custom createItemLabel', async () => {
render(<CreateItemLabel />);
const input = (await screen.findByLabelText(
'Roles'
)) as HTMLInputElement;
input.focus();

// Expect the create label to be absent
expect(screen.queryByText(/Create/)).toBeNull();

// Expect the creation workflow to still work
fireEvent.change(input, { target: { value: 'new role' } });
// Expect the custom create item label to be rendered
fireEvent.click(await screen.findByText('Add a new role: new role'));
// Expect a dialog to have opened
const dialogInput = (await screen.findByLabelText(
'Role name'
)) as HTMLInputElement;
expect(dialogInput.value).toEqual('new role');
});

it('should use optionText with a function value as text identifier when a create element is passed', () => {
const choices = [
{ id: 't', foobar: 'Technical' },
Expand Down Expand Up @@ -905,7 +951,7 @@ describe('<AutocompleteArrayInput />', () => {
selector: 'input',
})
);
expect(screen.queryAllByRole('option')).toHaveLength(3);
expect(screen.queryAllByRole('option')).toHaveLength(2);
expect(screen.getByText('Technical')).not.toBeNull();
expect(screen.getByText('Programming')).not.toBeNull();
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -193,6 +193,30 @@ export const CreateProp = () => (
</Wrapper>
);

export const CreateLabel = () => (
<Wrapper>
<AutocompleteArrayInput
source="roles"
choices={choices}
sx={{ width: 400 }}
create={<CreateRole />}
createLabel="Start typing to create a new item"
/>
</Wrapper>
);

export const CreateItemLabel = () => (
<Wrapper>
<AutocompleteArrayInput
source="roles"
choices={choices}
sx={{ width: 400 }}
create={<CreateRole />}
createItemLabel="Add a new role: %{item}"
/>
</Wrapper>
);

const dataProvider = {
getOne: () =>
Promise.resolve({
Expand Down
Loading

0 comments on commit c795bf2

Please sign in to comment.