diff --git a/.changeset/sour-weeks-camp.md b/.changeset/sour-weeks-camp.md
new file mode 100644
index 0000000000..6d26459a93
--- /dev/null
+++ b/.changeset/sour-weeks-camp.md
@@ -0,0 +1,6 @@
+---
+'@commercetools-uikit/async-select-input': patch
+'@commercetools-uikit/select-input': patch
+---
+
+feat(select input props): add hideSelectedOptions prop from 'react-select' to SelectInput and AsyncSelectInput
diff --git a/packages/components/filters/README.md b/packages/components/filters/README.md
index 4ad4aafb61..c69b57da46 100644
--- a/packages/components/filters/README.md
+++ b/packages/components/filters/README.md
@@ -42,6 +42,6 @@ export default Example;
## Properties
-| Props | Type | Required | Default | Description |
-| ------- | -------- | :------: | ------- | ------------------- |
-| `label` | `string` | | | This is a stub prop |
+| Props | Type | Required | Default | Description |
+| ------- | -------- | :------: | ------- | -------------------- |
+| `label` | `string` | ✅ | | This is a stub prop! |
diff --git a/packages/components/inputs/async-select-input/README.md b/packages/components/inputs/async-select-input/README.md
index 45795afa45..a27f59b1ce 100644
--- a/packages/components/inputs/async-select-input/README.md
+++ b/packages/components/inputs/async-select-input/README.md
@@ -64,6 +64,7 @@ export default Example;
| `components` | `AsyncProps['components']` | | | Map of components to overwrite the default ones, see [what components you can override](https://react-select.com/components)
[Props from React select was used](https://react-select.com/props) |
| `controlShouldRenderValue` | `AsyncProps['controlShouldRenderValue']` | | `true` | Control whether the selected values should be rendered in the control
[Props from React select was used](https://react-select.com/props) |
| `filterOption` | `AsyncProps['filterOption']` | | | Custom method to filter whether an option should be displayed in the menu
[Props from React select was used](https://react-select.com/props) |
+| `hideSelectedOptions` | `AsyncProps['hideSelectedOptions']` | | | Custom method to determine whether selected options should be displayed in the menu
[Props from React select was used](https://react-select.com/props) |
| `id` | `AsyncProps['inputId']` | | | The id of the search input
[Props from React select was used](https://react-select.com/props) |
| `inputValue` | `AsyncProps['inputValue']` | | | The value of the search input
[Props from React select was used](https://react-select.com/props) |
| `containerId` | `AsyncProps['id']` | | | The id to set on the SelectContainer component
[Props from React select was used](https://react-select.com/props) |
diff --git a/packages/components/inputs/async-select-input/src/async-select-input.spec.js b/packages/components/inputs/async-select-input/src/async-select-input.spec.js
index 76a148c2c2..cfd0110a84 100644
--- a/packages/components/inputs/async-select-input/src/async-select-input.spec.js
+++ b/packages/components/inputs/async-select-input/src/async-select-input.spec.js
@@ -1,6 +1,11 @@
import { Component } from 'react';
import PropTypes from 'prop-types';
-import { render, fireEvent, waitFor } from '../../../../../test/test-utils';
+import {
+ render,
+ fireEvent,
+ waitFor,
+ within,
+} from '../../../../../test/test-utils';
import AsyncSelectInput from './async-select-input';
// We use this component to simulate the whole flow of
@@ -83,7 +88,7 @@ it('should have an open menu if menuIsOpen is true', async () => {
const { findByLabelText, getByText } = renderInput({
menuIsOpen: true,
});
- const input = await findByLabelText('Fruit');
+ await findByLabelText('Fruit');
expect(getByText('Mango')).toBeInTheDocument();
});
@@ -94,7 +99,7 @@ it('should not have an open menu if menuIsOpen is true and isReadOnly is true',
isReadOnly: true,
});
- const input = await findByLabelText('Fruit');
+ await findByLabelText('Fruit');
expect(queryByText('Mango')).not.toBeInTheDocument();
});
@@ -119,6 +124,34 @@ it('should call onBlur when input loses focus', async () => {
expect(onBlur).toHaveBeenCalled();
});
+it('should not display selected options in menu when hideSelectedOptions is true', async () => {
+ const { findByLabelText, findByRole } = renderInput({
+ hideSelectedOptions: true,
+ });
+ const input = await findByLabelText('Fruit', { text: 'Banana' });
+
+ fireEvent.keyDown(input, {
+ key: 'ArrowDown',
+ });
+
+ const menu = await findByRole('listbox');
+ expect(within(menu).queryByText('Banana')).not.toBeInTheDocument();
+});
+
+it('should display selected options in menu when hideSelectedOptions is false', async () => {
+ const { findByLabelText, findByRole } = renderInput({
+ hideSelectedOptions: false,
+ });
+ const input = await findByLabelText('Fruit', { text: 'Banana' });
+
+ fireEvent.keyDown(input, {
+ key: 'ArrowDown',
+ });
+
+ const menu = await findByRole('listbox');
+ expect(within(menu).getByText('Banana')).toBeInTheDocument();
+});
+
describe('in single mode', () => {
describe('when no value is specified', () => {
it('should render a select input', async () => {
@@ -136,6 +169,18 @@ describe('in single mode', () => {
expect(input).toBeInTheDocument();
expect(getByText('Banana')).toBeInTheDocument();
});
+ it('should display selected option in menu when hideSelectedOptions is undefined', async () => {
+ const { findByLabelText, findByRole } = renderInput();
+ const input = await findByLabelText('Fruit', { text: 'Banana' });
+ expect(input).toBeInTheDocument();
+
+ fireEvent.keyDown(input, {
+ key: 'ArrowDown',
+ });
+
+ const menu = await findByRole('listbox');
+ expect(within(menu).getByText('Banana')).toBeInTheDocument();
+ });
});
describe('interacting', () => {
it('should open the list and all options should be visible', async () => {
@@ -209,6 +254,23 @@ describe('in multi mode', () => {
expect(getByText('Mango')).toBeInTheDocument();
expect(getByText('Raspberry')).toBeInTheDocument();
});
+ it('should not display selected options in menu when hideSelectedOptions is undefined', async () => {
+ const { findByLabelText, findByRole } = renderInput({
+ isMulti: true,
+ value: [
+ { value: 'mango', label: 'Mango' },
+ { value: 'raspberry', label: 'Raspberry' },
+ ],
+ });
+ const input = await findByLabelText('Fruit');
+ fireEvent.keyDown(input, {
+ key: 'ArrowDown',
+ });
+
+ const menu = await findByRole('listbox');
+ expect(within(menu).queryByText('Mango')).not.toBeInTheDocument();
+ expect(within(menu).queryByText('Raspberry')).not.toBeInTheDocument();
+ });
});
});
describe('interacting', () => {
diff --git a/packages/components/inputs/async-select-input/src/async-select-input.stories.tsx b/packages/components/inputs/async-select-input/src/async-select-input.stories.tsx
index 4da00cb5d2..8bd03bb65b 100644
--- a/packages/components/inputs/async-select-input/src/async-select-input.stories.tsx
+++ b/packages/components/inputs/async-select-input/src/async-select-input.stories.tsx
@@ -15,6 +15,7 @@ const meta: Meta = {
'aria-errormessage': { control: 'text' },
backspaceRemovesValue: { control: 'boolean' },
controlShouldRenderValue: { control: 'boolean' },
+ hideSelectedOptions: { control: 'boolean' },
id: { control: 'text' },
containerId: { control: 'text' },
isClearable: { control: 'boolean' },
diff --git a/packages/components/inputs/async-select-input/src/async-select-input.tsx b/packages/components/inputs/async-select-input/src/async-select-input.tsx
index 69bc3371d3..c10c796222 100644
--- a/packages/components/inputs/async-select-input/src/async-select-input.tsx
+++ b/packages/components/inputs/async-select-input/src/async-select-input.tsx
@@ -137,6 +137,12 @@ export type TAsyncSelectInputProps = {
* [Props from React select was used](https://react-select.com/props)
*/
filterOption?: ReactSelectAsyncProps['filterOption'];
+ /**
+ * Custom method to determine whether selected options should be displayed in the menu
+ *
+ * [Props from React select was used](https://react-select.com/props)
+ */
+ hideSelectedOptions?: ReactSelectAsyncProps['hideSelectedOptions'];
// This forwarded as react-select's "inputId"
/**
* The id of the search input
@@ -398,6 +404,7 @@ const AsyncSelectInput = (props: TAsyncSelectInputProps) => {
}) as ReactSelectAsyncProps['styles']
}
filterOption={props.filterOption}
+ hideSelectedOptions={props.hideSelectedOptions}
// react-select uses "id" (for the container) and "inputId" (for the input),
// but we use "id" (for the input) and "containerId" (for the container)
// instead.
diff --git a/packages/components/inputs/select-input/README.md b/packages/components/inputs/select-input/README.md
index 96c9a2d131..8c285f0454 100644
--- a/packages/components/inputs/select-input/README.md
+++ b/packages/components/inputs/select-input/README.md
@@ -76,6 +76,7 @@ export default Example;
| `isCondensed` | `boolean` | | | Whether the input and options are rendered with condensed paddings |
| `controlShouldRenderValue` | `ReactSelectProps['controlShouldRenderValue']` | | | Control whether the selected values should be rendered in the control
[Props from React select was used](https://react-select.com/props) |
| `filterOption` | `ReactSelectProps['filterOption']` | | | Custom method to filter whether an option should be displayed in the menu
[Props from React select was used](https://react-select.com/props) |
+| `hideSelectedOptions` | `ReactSelectProps['hideSelectedOptions']` | | | Custom method to determine whether selected options should be displayed in the menu
[Props from React select was used](https://react-select.com/props) |
| `id` | `ReactSelectProps['inputId']` | | | Used as HTML id property. An id is generated automatically when not provided.
This forwarded as react-select's "inputId"
[Props from React select was used](https://react-select.com/props) |
| `inputValue` | `ReactSelectProps['inputValue']` | | | The value of the search input
[Props from React select was used](https://react-select.com/props) |
| `containerId` | `ReactSelectProps['id']` | | | The id to set on the SelectContainer component
This is forwarded as react-select's "id"
[Props from React select was used](https://react-select.com/props) |
diff --git a/packages/components/inputs/select-input/src/select-input.spec.js b/packages/components/inputs/select-input/src/select-input.spec.js
index f3a01c069c..4625804143 100644
--- a/packages/components/inputs/select-input/src/select-input.spec.js
+++ b/packages/components/inputs/select-input/src/select-input.spec.js
@@ -1,6 +1,6 @@
import { Component } from 'react';
import PropTypes from 'prop-types';
-import { render, fireEvent } from '../../../../../test/test-utils';
+import { render, fireEvent, within } from '../../../../../test/test-utils';
import SelectInput from './select-input';
// We use this component to simulate the whole flow of
@@ -78,22 +78,19 @@ it('should have focus automatically when isAutofocussed is passed', () => {
});
it('should have an open menu if menuIsOpen is true', () => {
- const { getByLabelText, getByText } = renderInput({
+ const { getByText } = renderInput({
menuIsOpen: true,
});
- const input = getByLabelText('Fruit');
expect(getByText('Mango')).toBeInTheDocument();
});
it('should not have an open menu if menuIsOpen is true and isReadOnly is true', () => {
- const { getByLabelText, queryByText } = renderInput({
+ const { queryByText } = renderInput({
menuIsOpen: true,
isReadOnly: true,
});
- const input = getByLabelText('Fruit');
-
expect(queryByText('Mango')).not.toBeInTheDocument();
});
@@ -117,6 +114,36 @@ it('should call onBlur when input loses focus', () => {
expect(onBlur).toHaveBeenCalled();
});
+it('should not display selected options in menu when hideSelectedOptions is true', () => {
+ const { getByLabelText, getByRole } = renderInput({
+ hideSelectedOptions: true,
+ });
+ const input = getByLabelText('Fruit', { text: 'Banana' });
+ expect(input).toBeInTheDocument();
+
+ fireEvent.keyDown(input, {
+ key: 'ArrowDown',
+ });
+
+ const menu = getByRole('listbox');
+ expect(within(menu).queryByText('Banana')).not.toBeInTheDocument();
+});
+
+it('should display selected options in menu when hideSelectedOptions is false', () => {
+ const { getByLabelText, getByRole } = renderInput({
+ hideSelectedOptions: false,
+ });
+ const input = getByLabelText('Fruit', { text: 'Banana' });
+ expect(input).toBeInTheDocument();
+
+ fireEvent.keyDown(input, {
+ key: 'ArrowDown',
+ });
+
+ const menu = getByRole('listbox');
+ expect(within(menu).getByText('Banana')).toBeInTheDocument();
+});
+
describe('in single mode', () => {
describe('when no value is specified', () => {
it('should render a select input', () => {
@@ -134,6 +161,18 @@ describe('in single mode', () => {
expect(input).toBeInTheDocument();
expect(getByText('Banana')).toBeInTheDocument();
});
+ it('should display selected option in menu when hideSelectedOptions is undefined', () => {
+ const { getByLabelText, getByRole } = renderInput();
+ const input = getByLabelText('Fruit', { text: 'Banana' });
+ expect(input).toBeInTheDocument();
+
+ fireEvent.keyDown(input, {
+ key: 'ArrowDown',
+ });
+
+ const menu = getByRole('listbox');
+ expect(within(menu).getByText('Banana')).toBeInTheDocument();
+ });
});
describe('interacting', () => {
describe('when isAutofocussed is `true`', () => {
@@ -218,8 +257,23 @@ describe('in multi mode', () => {
expect(getByText('Mango')).toBeInTheDocument();
expect(getByText('Raspberry')).toBeInTheDocument();
});
+ it('should not display selected options in menu when hideSelectedOptions is undefined', () => {
+ const { getByLabelText, getByRole } = renderInput({
+ isMulti: true,
+ value: ['mango', 'raspberry'],
+ });
+ const input = getByLabelText('Fruit');
+ fireEvent.keyDown(input, {
+ key: 'ArrowDown',
+ });
+
+ const menu = getByRole('listbox');
+ expect(within(menu).queryByText('Mango')).not.toBeInTheDocument();
+ expect(within(menu).queryByText('Raspberry')).not.toBeInTheDocument();
+ });
});
});
+
describe('interacting', () => {
describe('when disabled', () => {
it('should not call onChange when value is cleared', () => {
diff --git a/packages/components/inputs/select-input/src/select-input.stories.tsx b/packages/components/inputs/select-input/src/select-input.stories.tsx
index 2a8346b25f..ad521fc320 100644
--- a/packages/components/inputs/select-input/src/select-input.stories.tsx
+++ b/packages/components/inputs/select-input/src/select-input.stories.tsx
@@ -15,6 +15,7 @@ const meta: Meta = {
backspaceRemovesValue: { control: { type: 'boolean' } },
controlShouldRenderValue: { control: { type: 'boolean' } },
filterOption: { type: 'function' },
+ hideSelectedOptions: { type: 'boolean' },
id: { control: { type: 'text' } },
inputValue: { control: { type: 'text' } },
containerId: { control: { type: 'text' } },
diff --git a/packages/components/inputs/select-input/src/select-input.tsx b/packages/components/inputs/select-input/src/select-input.tsx
index 94919565c2..4b5cfd6d98 100644
--- a/packages/components/inputs/select-input/src/select-input.tsx
+++ b/packages/components/inputs/select-input/src/select-input.tsx
@@ -161,7 +161,12 @@ export type TSelectInputProps = {
// formatOptionLabel: PropTypes.func,
// getOptionLabel: PropTypes.func,
// getOptionValue: PropTypes.func,
- // hideSelectedOptions: PropTypes.bool,
+ /**
+ * Custom method to determine whether selected options should be displayed in the menu
+ *
+ * [Props from React select was used](https://react-select.com/props)
+ */
+ hideSelectedOptions?: ReactSelectProps['hideSelectedOptions'];
/**
* Used as HTML id property. An id is generated automatically when not provided.
* This forwarded as react-select's "inputId"
@@ -491,6 +496,7 @@ const SelectInput = (props: TSelectInputProps) => {
isClearable={props.isReadOnly ? false : props.isClearable}
isDisabled={props.isDisabled}
isOptionDisabled={props.isOptionDisabled}
+ hideSelectedOptions={props.hideSelectedOptions}
// @ts-ignore
isReadOnly={props.isReadOnly}
isMulti={props.isMulti}
diff --git a/tsconfig.json b/tsconfig.json
index 43c82a0814..7e898117ed 100644
--- a/tsconfig.json
+++ b/tsconfig.json
@@ -1,4 +1,5 @@
{
+ "exclude": ["**/node_modules/**", "**/dist/**"],
"compilerOptions": {
"allowSyntheticDefaultImports": true,
"emitDecoratorMetadata": false,
@@ -28,6 +29,7 @@
"stripInternal": true,
"target": "ES2019",
"allowJs": true,
+ "noEmit": true,
"typeRoots": [
"@types",
// "@types-extensions",