diff --git a/packages/desktop-client/src/components/autocomplete/NewAutocomplete.tsx b/packages/desktop-client/src/components/autocomplete/NewAutocomplete.tsx index f01fc128ba1c8a0572e7edca916be0e1c155e0d7..9dc3d56ff25e5f625362f48722820241fc0d2c75 100644 --- a/packages/desktop-client/src/components/autocomplete/NewAutocomplete.tsx +++ b/packages/desktop-client/src/components/autocomplete/NewAutocomplete.tsx @@ -1,27 +1,48 @@ import React, { useState } from 'react'; -import Select, { +import Select from 'react-select'; +import type { + GroupBase, Props as SelectProps, PropsValue, SingleValue, SelectInstance, } from 'react-select'; +import type { CreatableProps } from 'react-select/creatable'; import CreatableSelect from 'react-select/creatable'; import { NullComponent } from '../common'; import styles from './autocomplete-styles'; -type OptionValue = string; - -interface AutocompleteProps extends SelectProps<OptionValue> { - focused: boolean; - embedded: boolean; - onSelect: (value: PropsValue<OptionValue>) => void; - onCreateOption: (value: SingleValue<OptionValue>) => void; - isCreatable: boolean; +type OptionValue = { + __isNew__?: boolean; + label: string; + value: string; +}; + +interface BaseAutocompleteProps { + focused?: boolean; + embedded?: boolean; + onSelect: (value: string | string[]) => void; + onCreateOption?: (value: string) => void; + isCreatable?: boolean; } +type SimpleAutocompleteProps = BaseAutocompleteProps & SelectProps<OptionValue>; +type CreatableAutocompleteProps = BaseAutocompleteProps & + CreatableProps<OptionValue, true, GroupBase<OptionValue>> & { + isCreatable: true; + }; + +type AutocompleteProps = SimpleAutocompleteProps | CreatableAutocompleteProps; + +const isSingleValue = ( + value: PropsValue<OptionValue>, +): value is SingleValue<OptionValue> => { + return !Array.isArray(value); +}; + const Autocomplete = React.forwardRef<SelectInstance, AutocompleteProps>( ( { @@ -38,16 +59,32 @@ const Autocomplete = React.forwardRef<SelectInstance, AutocompleteProps>( ref, ) => { const [initialValue] = useState(value); - const [isOpen, setIsOpen] = useState(focused); + const [isOpen, setIsOpen] = useState(focused || embedded); + + const [inputValue, setInputValue] = useState< + AutocompleteProps['inputValue'] + >(() => (isSingleValue(value) ? value?.label : undefined)); + const [isInitialInputValue, setInitialInputValue] = useState(true); + + const onInputChange: AutocompleteProps['onInputChange'] = value => { + setInputValue(value); + setInitialInputValue(false); + }; + + const filterOption: AutocompleteProps['filterOption'] = (option, input) => { + if (isInitialInputValue) { + return true; + } - const filterOption = (option, input) => { return ( option.data?.__isNew__ || option.label.toLowerCase().includes(input?.toLowerCase()) ); }; - const onChange = async selected => { + const onChange: AutocompleteProps['onChange'] = ( + selected: PropsValue<OptionValue>, + ) => { // Clear button clicked if (!selected) { onSelect(null); @@ -55,18 +92,18 @@ const Autocomplete = React.forwardRef<SelectInstance, AutocompleteProps>( } // Create a new option - if (selected.__isNew__) { + if (isSingleValue(selected) && selected.__isNew__) { onCreateOption(selected.value); return; } // Close the menu when making a successful selection - if (!Array.isArray(selected)) { + if (isSingleValue(selected)) { setIsOpen(false); } // Multi-select has multiple selections - if (Array.isArray(selected)) { + if (!isSingleValue(selected)) { onSelect(selected.map(option => option.value)); return; } @@ -74,9 +111,13 @@ const Autocomplete = React.forwardRef<SelectInstance, AutocompleteProps>( onSelect(selected.value); }; - const onKeyDown = event => { + const onKeyDown: AutocompleteProps['onKeyDown'] = event => { if (event.code === 'Escape') { - onSelect(initialValue); + onSelect( + isSingleValue(initialValue) + ? initialValue?.value + : initialValue.map(val => val.value), + ); setIsOpen(false); return; } @@ -92,7 +133,7 @@ const Autocomplete = React.forwardRef<SelectInstance, AutocompleteProps>( <Component ref={ref} value={value} - menuIsOpen={isOpen || embedded} + menuIsOpen={isOpen} autoFocus={embedded} options={options} placeholder="(none)" @@ -114,6 +155,8 @@ const Autocomplete = React.forwardRef<SelectInstance, AutocompleteProps>( data-embedded={embedded} menuPlacement="auto" menuPortalTarget={embedded ? undefined : document.body} + inputValue={inputValue} + onInputChange={onInputChange} {...props} /> ); diff --git a/packages/desktop-client/src/components/autocomplete/NewPayeeAutocomplete.js b/packages/desktop-client/src/components/autocomplete/NewPayeeAutocomplete.js index 01daed8a62540aa650b414a3ea7495bbfa1c873c..f381d2a365070a68f6e4621c3dcb9590c0ef4ed9 100644 --- a/packages/desktop-client/src/components/autocomplete/NewPayeeAutocomplete.js +++ b/packages/desktop-client/src/components/autocomplete/NewPayeeAutocomplete.js @@ -77,8 +77,6 @@ export default function PayeeAutocomplete({ const dispatch = useDispatch(); - const [inputValue, setInputValue] = useState(); - return ( <Autocomplete options={options} @@ -89,8 +87,6 @@ export default function PayeeAutocomplete({ } isValidNewOption={input => input && !focusTransferPayees} isMulti={multi} - inputValue={inputValue} - onInputChange={setInputValue} onSelect={onSelect} onCreateOption={async selectedValue => { const existingOption = allOptions.find(option => @@ -154,7 +150,6 @@ export default function PayeeAutocomplete({ } } onClick={() => { - setInputValue(''); setFocusTransferPayees(!focusTransferPayees); }} /> diff --git a/packages/desktop-client/src/components/autocomplete/autocomplete-styles.js b/packages/desktop-client/src/components/autocomplete/autocomplete-styles.js index 3c7acdcdb44cf92e34e19013ecf04ea3c51babd5..66eb88ebe6c0f959c6c60eab2620ba876598cdbd 100644 --- a/packages/desktop-client/src/components/autocomplete/autocomplete-styles.js +++ b/packages/desktop-client/src/components/autocomplete/autocomplete-styles.js @@ -17,6 +17,7 @@ const colourStyles = { ...styles, padding: '0 2px', margin: 0, + overflow: 'hidden', }), menuPortal: styles => ({ ...styles, diff --git a/upcoming-release-notes/856.md b/upcoming-release-notes/856.md new file mode 100644 index 0000000000000000000000000000000000000000..5d755ed845b069803292d206647cb5122fa0f4f8 --- /dev/null +++ b/upcoming-release-notes/856.md @@ -0,0 +1,6 @@ +--- +category: Bugfix +authors: [MatissJanis] +--- + +Autocomplete: allow editing previously selected payees