feat: add arrow key navigation for results dropdown

This commit is contained in:
Antonella Sgarlatta
2021-06-03 14:21:07 -03:00
parent 386ca34178
commit 31d454cdc5
6 changed files with 97 additions and 29 deletions

View File

@@ -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<number | 'auto'>('auto');
const inputRef = useRef<HTMLInputElement>();
const dropdownRef = useRef<HTMLDivElement>();
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) => {
>
<Disclosure open={dropdownVisible} onChange={showDropdown}>
<input
ref={inputRef}
ref={(element) => {
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 && (
<DisclosurePanel

View File

@@ -11,13 +11,33 @@ type Props = {
export const AutocompleteTagResult = observer(
({ appState, tagResult, closeOnBlur }: Props) => {
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 (
<button
ref={(element) => {
@@ -32,6 +52,7 @@ export const AutocompleteTagResult = observer(
className="sn-dropdown-item"
onClick={() => onTagOptionClick(tagResult)}
onBlur={closeOnBlur}
onKeyDown={onKeyDown}
>
<Icon type="hashtag" className="color-neutral mr-2 min-h-5 min-w-5" />
<span className="whitespace-nowrap overflow-hidden overflow-ellipsis">

View File

@@ -34,21 +34,16 @@ export const NoteTag = observer(({ appState, tag }: Props) => {
}
};
const onKeyUp = (event: KeyboardEvent) => {
let previousTagElement;
let nextTagElement;
const onKeyDown = (event: KeyboardEvent) => {
switch (event.key) {
case 'Backspace':
deleteTag();
break;
case 'ArrowLeft':
previousTagElement = appState.noteTags.getPreviousTagElement(tag);
previousTagElement?.focus();
appState.noteTags.getPreviousTagElement(tag)?.focus();
break;
case 'ArrowRight':
nextTagElement = appState.noteTags.getNextTagElement(tag);
nextTagElement?.focus();
appState.noteTags.getNextTagElement(tag)?.focus();
break;
default:
return;
@@ -65,7 +60,7 @@ export const NoteTag = observer(({ appState, tag }: Props) => {
className="sn-tag pl-1 pr-2 mr-2"
style={{ maxWidth: tagsContainerMaxWidth }}
onClick={onTagClick}
onKeyUp={onKeyUp}
onKeyDown={onKeyDown}
onFocus={onFocus}
onBlur={onBlur}
>

View File

@@ -133,7 +133,7 @@ export const NotesOptions = observer(
{appState.tags.tagsCount > 0 && (
<Disclosure open={tagsMenuOpen} onChange={openTagsMenu}>
<DisclosureButton
onKeyUp={(event) => {
onKeyDown={(event) => {
if (event.key === 'Escape') {
setTagsMenuOpen(false);
}
@@ -149,7 +149,7 @@ export const NotesOptions = observer(
<Icon type="chevron-right" className="color-neutral" />
</DisclosureButton>
<DisclosurePanel
onKeyUp={(event) => {
onKeyDown={(event) => {
if (event.key === 'Escape') {
setTagsMenuOpen(false);
tagsButtonRef.current.focus();

View File

@@ -47,7 +47,7 @@ export const NotesOptionsPanel = observer(({ appState }: Props) => {
}}
>
<DisclosureButton
onKeyUp={(event) => {
onKeyDown={(event) => {
if (event.key === 'Escape' && !submenuOpen) {
setOpen(false);
}
@@ -60,7 +60,7 @@ export const NotesOptionsPanel = observer(({ appState }: Props) => {
<Icon type="more" className="block" />
</DisclosureButton>
<DisclosurePanel
onKeyUp={(event) => {
onKeyDown={(event) => {
if (event.key === 'Escape' && !submenuOpen) {
setOpen(false);
buttonRef.current.focus();