Skip to content

Commit c795bf2

Browse files
authored
Merge pull request #10321 from marmelab/Autocomplete-createLabel-disabled
Fix `createLabel` option should not be clickable for `<AutocompleteInput>` and `<AutocompleteArrayInput>`
2 parents 19efa7e + 15d0ee7 commit c795bf2

13 files changed

+250
-146
lines changed

docs/AutocompleteArrayInput.md

Lines changed: 34 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -60,8 +60,8 @@ The form value for the source must be an array of the selected values, e.g.
6060
|----------------------------|----------|-----------------------|--------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
6161
| `choices` | Required | `Object[]` | - | List of choices |
6262
| `create` | Optional | `Element` | `-` | A React Element to render when users want to create a new choice |
63-
| `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 |
64-
| `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 |
63+
| `createLabel` | Optional | `string` | - | The label used as hint to let users know they can create a new choice. Displayed when the filter is empty. |
64+
| `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. |
6565
| `debounce` | Optional | `number` | `250` | The delay to wait before calling the setFilter function injected when used in a ReferenceArray Input. |
6666
| `emptyValue` | Optional | `any` | `''` | The value to use for the empty element |
6767
| `filterToQuery` | Optional | `string` => `Object` | `q => ({ q })` | How to transform the searchText into a parameter for the data provider |
@@ -159,7 +159,7 @@ const choices = [
159159
const UserCreate = () => (
160160
<Create>
161161
<SimpleForm>
162-
<SelectArrayInput
162+
<AutocompleteArrayInput
163163
source="roles"
164164
choices={choices}
165165
create={<CreateRole />}
@@ -216,6 +216,37 @@ If you just need to ask users for a single string to create the new option, you
216216

217217
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.
218218

219+
## `createLabel`
220+
221+
When you set the `create` or `onCreate` prop, `<AutocompleteArrayInput>` lets users create new options.
222+
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.
223+
224+
```jsx
225+
<AutocompleteArrayInput
226+
source="roles"
227+
choices={choices}
228+
create={<CreateRole />}
229+
createLabel="Start typing to create a new item"
230+
/>
231+
```
232+
233+
## `createItemLabel`
234+
235+
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.
236+
237+
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.
238+
239+
Or, if you want to customize it just for this `<AutocompleteArrayInput>`, use the `createItemLabel` prop:
240+
241+
```jsx
242+
<AutocompleteArrayInput
243+
source="roles"
244+
choices={choices}
245+
create={<CreateRole />}
246+
createItemLabel="Add a new role: %{item}"
247+
/>
248+
```
249+
219250
## `debounce`
220251

221252
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.

docs/AutocompleteInput.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -60,8 +60,8 @@ The form value for the source must be the selected value, e.g.
6060
|--------------------------- |----------|---------------------- |---------------------------------------------------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
6161
| `choices` | Optional | `Object[]` | `-` | List of items to autosuggest. Required if not inside a ReferenceInput. |
6262
| `create` | Optional | `Element` | `-` | A React Element to render when users want to create a new choice |
63-
| `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 |
64-
| `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 |
63+
| `createLabel` | Optional | `string` | - | The label used as hint to let users know they can create a new choice. Displayed when the filter is empty. |
64+
| `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. |
6565
| `debounce` | Optional | `number` | `250` | The delay to wait before calling the setFilter function injected when used in a ReferenceInput. |
6666
| `emptyText` | Optional | `string` | `''` | The text to use for the empty element |
6767
| `emptyValue` | Optional | `any` | `''` | The value to use for the empty element |
@@ -226,7 +226,7 @@ If you just need to ask users for a single string to create the new option, you
226226
## `createLabel`
227227

228228
When you set the `create` or `onCreate` prop, `<AutocompleteInput>` lets users create new options.
229-
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.
229+
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.
230230

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

docs/SelectArrayInput.md

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -223,6 +223,21 @@ If you just need to ask users for a single string to create the new option, you
223223

224224
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.
225225

226+
## `createLabel`
227+
228+
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.
229+
230+
Or, if you want to customize it just for this `<SelectArrayInput>`, use the `createLabel` prop:
231+
232+
```jsx
233+
<SelectArrayInput
234+
source="roles"
235+
choices={choices}
236+
create={<CreateRole />}
237+
createLabel="Add a new role"
238+
/>
239+
```
240+
226241
## `disableValue`
227242

228243
By default, `<SelectArrayInput>` renders the choices with the field `disabled: true` as disabled.
-48.3 KB
Loading

packages/ra-ui-materialui/src/input/AutocompleteArrayInput.spec.tsx

Lines changed: 47 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@ import { SimpleForm } from '../form';
1313
import { AutocompleteArrayInput } from './AutocompleteArrayInput';
1414
import { useCreateSuggestionContext } from './useSupportCreateSuggestion';
1515
import {
16+
CreateItemLabel,
17+
CreateLabel,
1618
InsideReferenceArrayInput,
1719
InsideReferenceArrayInputOnChange,
1820
OnChange,
@@ -868,6 +870,50 @@ describe('<AutocompleteArrayInput />', () => {
868870
expect(screen.queryByText('New Kid On The Block')).not.toBeNull();
869871
});
870872

873+
it('should support using a custom createLabel', async () => {
874+
render(<CreateLabel />);
875+
const input = (await screen.findByLabelText(
876+
'Roles'
877+
)) as HTMLInputElement;
878+
input.focus();
879+
880+
// Expect the custom create label to be present and disabled
881+
const customCreateLabel = await screen.findByText(
882+
'Start typing to create a new item'
883+
);
884+
expect(customCreateLabel.getAttribute('aria-disabled')).toEqual('true');
885+
886+
// Expect the creation workflow to still work
887+
fireEvent.change(input, { target: { value: 'new role' } });
888+
fireEvent.click(await screen.findByText('Create new role'));
889+
// Expect a dialog to have opened
890+
const dialogInput = (await screen.findByLabelText(
891+
'Role name'
892+
)) as HTMLInputElement;
893+
expect(dialogInput.value).toEqual('new role');
894+
});
895+
896+
it('should support using a custom createItemLabel', async () => {
897+
render(<CreateItemLabel />);
898+
const input = (await screen.findByLabelText(
899+
'Roles'
900+
)) as HTMLInputElement;
901+
input.focus();
902+
903+
// Expect the create label to be absent
904+
expect(screen.queryByText(/Create/)).toBeNull();
905+
906+
// Expect the creation workflow to still work
907+
fireEvent.change(input, { target: { value: 'new role' } });
908+
// Expect the custom create item label to be rendered
909+
fireEvent.click(await screen.findByText('Add a new role: new role'));
910+
// Expect a dialog to have opened
911+
const dialogInput = (await screen.findByLabelText(
912+
'Role name'
913+
)) as HTMLInputElement;
914+
expect(dialogInput.value).toEqual('new role');
915+
});
916+
871917
it('should use optionText with a function value as text identifier when a create element is passed', () => {
872918
const choices = [
873919
{ id: 't', foobar: 'Technical' },
@@ -905,7 +951,7 @@ describe('<AutocompleteArrayInput />', () => {
905951
selector: 'input',
906952
})
907953
);
908-
expect(screen.queryAllByRole('option')).toHaveLength(3);
954+
expect(screen.queryAllByRole('option')).toHaveLength(2);
909955
expect(screen.getByText('Technical')).not.toBeNull();
910956
expect(screen.getByText('Programming')).not.toBeNull();
911957
});

packages/ra-ui-materialui/src/input/AutocompleteArrayInput.stories.tsx

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -193,6 +193,30 @@ export const CreateProp = () => (
193193
</Wrapper>
194194
);
195195

196+
export const CreateLabel = () => (
197+
<Wrapper>
198+
<AutocompleteArrayInput
199+
source="roles"
200+
choices={choices}
201+
sx={{ width: 400 }}
202+
create={<CreateRole />}
203+
createLabel="Start typing to create a new item"
204+
/>
205+
</Wrapper>
206+
);
207+
208+
export const CreateItemLabel = () => (
209+
<Wrapper>
210+
<AutocompleteArrayInput
211+
source="roles"
212+
choices={choices}
213+
sx={{ width: 400 }}
214+
create={<CreateRole />}
215+
createItemLabel="Add a new role: %{item}"
216+
/>
217+
</Wrapper>
218+
);
219+
196220
const dataProvider = {
197221
getOne: () =>
198222
Promise.resolve({

0 commit comments

Comments
 (0)