diff --git a/.changeset/plenty-snails-draw.md b/.changeset/plenty-snails-draw.md new file mode 100644 index 0000000000..cf206f8d61 --- /dev/null +++ b/.changeset/plenty-snails-draw.md @@ -0,0 +1,6 @@ +--- +'@commercetools-uikit/selectable-search-input': minor +--- + +We're introducing a **temporary** property to handle an internal case. +Please do not use it as it will be removed in the near future. diff --git a/packages/components/inputs/selectable-search-input/src/selectable-search-input.spec.tsx b/packages/components/inputs/selectable-search-input/src/selectable-search-input.spec.tsx index 2d75e66370..9befc2e331 100644 --- a/packages/components/inputs/selectable-search-input/src/selectable-search-input.spec.tsx +++ b/packages/components/inputs/selectable-search-input/src/selectable-search-input.spec.tsx @@ -134,6 +134,35 @@ describe('SelectableSearchInput', () => { ).toHaveTextContent('Bar'); }); + it('should show the passed experimental value when controlled externally', () => { + render( + + ); + expect(screen.getByLabelText('test-label')).toHaveAttribute( + 'value', + 'experimental' + ); + expect( + screen.getByTestId('selectable-search-input-container') + ).toHaveTextContent('Bar'); + }); + + it('should show an empty input when an empty string is passed externally as value', () => { + render( + + ); + expect(screen.getByLabelText('test-label')).toHaveAttribute('value', ''); + expect( + screen.getByTestId('selectable-search-input-container') + ).toHaveTextContent('Bar'); + }); + it('should call onFocus when the dropdown menu is focused', async () => { const onFocus = jest.fn(); render( diff --git a/packages/components/inputs/selectable-search-input/src/selectable-search-input.story.js b/packages/components/inputs/selectable-search-input/src/selectable-search-input.story.js index c088a4959f..d349c17290 100644 --- a/packages/components/inputs/selectable-search-input/src/selectable-search-input.story.js +++ b/packages/components/inputs/selectable-search-input/src/selectable-search-input.story.js @@ -1,4 +1,3 @@ -import { useState } from 'react'; import { storiesOf } from '@storybook/react'; import { action } from '@storybook/addon-actions'; import { withKnobs, boolean, text, select } from '@storybook/addon-knobs/react'; @@ -88,12 +87,10 @@ storiesOf('Components|Inputs', module) ]; const name = text('name', '') || 'default-name'; - const [dropdownValue, setDropdownValue] = useState(); - const [textInputValue, setTextInputValue] = useState(); const value = { - text: textInputValue, - option: dropdownValue, + text: '', + option: '', }; return ( @@ -114,12 +111,6 @@ storiesOf('Components|Inputs', module) value={value} onChange={(event) => { action('onChange')(event); - if (event.target.name.endsWith('.textInput')) { - setTextInputValue(event.target.value); - } - if (event.target.name.endsWith('.dropdown')) { - setDropdownValue(event.target.value); - } }} isAutofocussed={boolean('isAutofocussed', false)} isDisabled={boolean('isDisabled', false)} diff --git a/packages/components/inputs/selectable-search-input/src/selectable-search-input.tsx b/packages/components/inputs/selectable-search-input/src/selectable-search-input.tsx index cb99122b8d..8acf6de5eb 100644 --- a/packages/components/inputs/selectable-search-input/src/selectable-search-input.tsx +++ b/packages/components/inputs/selectable-search-input/src/selectable-search-input.tsx @@ -89,6 +89,7 @@ export type TSelectableSearchInputProps = { * Value of the input. Consists of text input and selected option. */ value: TValue; + _experimentalValue?: TValue; /** * Called with the event of the input or dropdown when either the selectable dropdown or the text input have changed. * The change event from the text input has a suffix of `.textInput` and the change event from the dropdown has a suffix of `.dropdown`. @@ -258,12 +259,15 @@ const transformDataProps = (dataProps?: Record) => const SelectableSearchInput = (props: TSelectableSearchInputProps) => { const [dropdownHasFocus, toggleDropdownHasFocus] = useToggleState(false); const [searchValue, setSearchValue] = useState(props.value.text || ''); + const [searchOption, setSearchOption] = useState(props.value.option || ''); const containerRef = useRef(null); const textInputRef = useRef(null); const legacyDataProps = filterDataAttributes(props); const transformedSelectDataProps = transformDataProps(props.selectDataProps); const transformedInputDataProps = transformDataProps(props.inputDataProps); + const searchInputValue = props._experimentalValue?.text ?? searchValue; + const searchInputOption = props._experimentalValue?.option ?? searchOption; const optionsWithoutGroups = props.options.flatMap((option) => { if (isOptionObject(option)) { @@ -273,7 +277,7 @@ const SelectableSearchInput = (props: TSelectableSearchInputProps) => { }); const selectedOption = optionsWithoutGroups.find( - (option) => option.value === props.value.option + (option) => option.value === searchInputOption ); const selectablSearchInputId = useFieldId( @@ -324,7 +328,7 @@ const SelectableSearchInput = (props: TSelectableSearchInputProps) => { } }; - const handleChange = (event: ChangeEvent) => { + const handleTextInputChange = (event: ChangeEvent) => { setSearchValue(event.target.value); if (props.onChange) { props.onChange({ @@ -346,7 +350,7 @@ const SelectableSearchInput = (props: TSelectableSearchInputProps) => { event.preventDefault(); if (props.onSubmit) { props.onSubmit({ - text: searchValue, + text: searchInputValue, option: selectedOption?.value ?? '', }); } @@ -406,6 +410,23 @@ const SelectableSearchInput = (props: TSelectableSearchInputProps) => { [onBlur, selectablSearchInputId, name] ); + const handleDropdownChange = useCallback( + (nextSelectedOptions) => { + setSearchOption(nextSelectedOptions.value); + if (props.onChange) { + props.onChange({ + target: { + id: SelectableSearchInput.getDropdownId(selectablSearchInputId), + name: getDropdownName(name), + value: nextSelectedOptions.value, + }, + }); + } + textInputRef.current?.focus(); + }, + [props.onChange, selectablSearchInputId, name] + ); + return ( { isCondensed={props.isCondensed ?? false} handleDropdownFocus={handleDropdownFocus} handleDropdownBlur={handleDropdownBlur} + handleDropdownChange={handleDropdownChange} textInputRef={textInputRef} selectedOption={selectedOption} dataProps={transformedSelectDataProps} @@ -445,8 +467,8 @@ const SelectableSearchInput = (props: TSelectableSearchInputProps) => { id={SelectableSearchInput.getTextInputId(selectablSearchInputId)} name={getTextInputName(props.name)} type="text" - value={searchValue} - onChange={handleChange} + value={searchInputValue} + onChange={handleTextInputChange} onBlur={handleTextInputBlur} onFocus={handleTextInputFocus} disabled={props.isDisabled} @@ -470,7 +492,7 @@ const SelectableSearchInput = (props: TSelectableSearchInputProps) => { }} /> {props.isClearable && - searchValue && + searchInputValue && !props.isDisabled && !props.isReadOnly && ( void; handleDropdownBlur: () => void; + handleDropdownChange: ReactSelectProps['onChange']; textInputRef: React.RefObject; selectedOption?: TOption; dataProps?: Record; @@ -50,23 +51,6 @@ const SelectableSelect = (props: TSelectableSelect) => { dropdownHasFocus: props.dropdownHasFocus, }) as ReactSelectProps['styles']; - const { onChange, name, id, textInputRef } = props; - const handleDropdownChange = useCallback( - (nextSelectedOptions) => { - if (onChange) { - onChange({ - target: { - id: id, - name: name, - value: (nextSelectedOptions as TOption).value, - }, - }); - } - textInputRef.current?.focus(); - }, - [onChange, id, name, textInputRef] - ); - return (