From 31d454cdc52468276e4538b6ff33c1acc79d539d Mon Sep 17 00:00:00 2001 From: Antonella Sgarlatta Date: Thu, 3 Jun 2021 14:21:07 -0300 Subject: [PATCH] feat: add arrow key navigation for results dropdown --- .../components/AutocompleteTagInput.tsx | 45 +++++++++++++------ .../components/AutocompleteTagResult.tsx | 23 +++++++++- app/assets/javascripts/components/NoteTag.tsx | 13 ++---- .../javascripts/components/NotesOptions.tsx | 4 +- .../components/NotesOptionsPanel.tsx | 4 +- .../ui_models/app_state/note_tags_state.ts | 37 ++++++++++++++- 6 files changed, 97 insertions(+), 29 deletions(-) diff --git a/app/assets/javascripts/components/AutocompleteTagInput.tsx b/app/assets/javascripts/components/AutocompleteTagInput.tsx index f887d0d0a..247b8375d 100644 --- a/app/assets/javascripts/components/AutocompleteTagInput.tsx +++ b/app/assets/javascripts/components/AutocompleteTagInput.tsx @@ -15,6 +15,8 @@ export const AutocompleteTagInput = observer(({ appState }: Props) => { autocompleteSearchQuery, autocompleteTagHintVisible, autocompleteTagResults, + autocompleteTagResultElements, + autocompleteInputElement, tagElements, tags, } = appState.noteTags; @@ -23,7 +25,6 @@ export const AutocompleteTagInput = observer(({ appState }: Props) => { const [dropdownMaxHeight, setDropdownMaxHeight] = useState('auto'); - const inputRef = useRef(); const dropdownRef = useRef(); const [closeOnBlur] = useCloseOnBlur(dropdownRef, (visible: boolean) => { @@ -32,9 +33,11 @@ export const AutocompleteTagInput = observer(({ appState }: Props) => { }); const showDropdown = () => { - const { clientHeight } = document.documentElement; - const inputRect = inputRef.current.getBoundingClientRect(); - setDropdownMaxHeight(clientHeight - inputRect.bottom - 32 * 2); + if (autocompleteInputElement) { + const { clientHeight } = document.documentElement; + const inputRect = autocompleteInputElement.getBoundingClientRect(); + setDropdownMaxHeight(clientHeight - inputRect.bottom - 32 * 2); + } setDropdownVisible(true); }; @@ -49,6 +52,24 @@ export const AutocompleteTagInput = observer(({ appState }: Props) => { await appState.noteTags.createAndAddNewTag(); }; + const onKeyDown = (event: KeyboardEvent) => { + switch (event.key) { + case 'Backspace': + if (autocompleteSearchQuery === '' && tagElements.length > 0) { + tagElements[tagElements.length - 1]?.focus(); + } + break; + case 'ArrowDown': + event.preventDefault(); + if (autocompleteTagResultElements.length > 0) { + autocompleteTagResultElements[0]?.focus(); + } + break; + default: + return; + } + }; + useEffect(() => { appState.noteTags.searchActiveNoteAutocompleteTags(); }, [appState.noteTags]); @@ -60,7 +81,11 @@ export const AutocompleteTagInput = observer(({ appState }: Props) => { > { + if (element) { + appState.noteTags.setAutocompleteInputElement(element); + } + }} className="w-80 bg-default text-xs color-text no-border h-7 focus:outline-none focus:shadow-none focus:border-bottom" value={autocompleteSearchQuery} onChange={onSearchQueryChange} @@ -68,15 +93,7 @@ export const AutocompleteTagInput = observer(({ appState }: Props) => { placeholder="Add tag" onBlur={closeOnBlur} onFocus={showDropdown} - onKeyUp={(event) => { - if ( - event.key === 'Backspace' && - autocompleteSearchQuery === '' && - tagElements.length > 0 - ) { - tagElements[tagElements.length - 1]?.focus(); - } - }} + onKeyDown={onKeyDown} /> {dropdownVisible && ( { - const { autocompleteSearchQuery } = appState.noteTags; + const { autocompleteInputElement, autocompleteSearchQuery, autocompleteTagResults } = appState.noteTags; const onTagOptionClick = async (tag: SNTag) => { await appState.noteTags.addTagToActiveNote(tag); appState.noteTags.clearAutocompleteSearch(); }; + const onKeyDown = (event: KeyboardEvent) => { + const tagResultIndex = appState.noteTags.getTagIndex(tagResult, autocompleteTagResults); + switch (event.key) { + case 'ArrowUp': + event.preventDefault(); + if (tagResultIndex === 0) { + autocompleteInputElement?.focus(); + } else { + appState.noteTags.getPreviousAutocompleteTagResultElement(tagResult)?.focus(); + } + break; + case 'ArrowDown': + event.preventDefault(); + appState.noteTags.getNextAutocompleteTagResultElement(tagResult)?.focus(); + break; + default: + return; + } + }; + return (