[Glitch] Improve search menu keyboard navigation
Port c4a760d6ba4394f07d1147259ebf280b0bade33f to glitch-soc Signed-off-by: Claire <claire.github-309c@sitedethib.com>
This commit is contained in:
parent
34dbea26a7
commit
392a241637
@ -1,4 +1,11 @@
|
|||||||
import { useCallback, useState, useRef, useEffect, useMemo } from 'react';
|
import {
|
||||||
|
useCallback,
|
||||||
|
useState,
|
||||||
|
useRef,
|
||||||
|
useEffect,
|
||||||
|
useMemo,
|
||||||
|
useId,
|
||||||
|
} from 'react';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
defineMessages,
|
defineMessages,
|
||||||
@ -432,12 +439,17 @@ export const Search: React.FC<{
|
|||||||
switch (e.key) {
|
switch (e.key) {
|
||||||
case 'Escape':
|
case 'Escape':
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
unfocus();
|
searchInputRef.current?.focus();
|
||||||
|
setExpanded(false);
|
||||||
|
|
||||||
break;
|
break;
|
||||||
case 'ArrowDown':
|
case 'ArrowDown':
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
|
|
||||||
|
if (!expanded) {
|
||||||
|
setExpanded(true);
|
||||||
|
}
|
||||||
|
|
||||||
if (navigableOptions.length > 0) {
|
if (navigableOptions.length > 0) {
|
||||||
setSelectedOption(
|
setSelectedOption(
|
||||||
Math.min(selectedOption + 1, navigableOptions.length - 1),
|
Math.min(selectedOption + 1, navigableOptions.length - 1),
|
||||||
@ -476,10 +488,10 @@ export const Search: React.FC<{
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
[unfocus, navigableOptions, selectedOption, submit, value],
|
[expanded, navigableOptions, selectedOption, submit, value],
|
||||||
);
|
);
|
||||||
|
|
||||||
const handleFocus = useCallback(() => {
|
const handleInputFocus = useCallback(() => {
|
||||||
setExpanded(true);
|
setExpanded(true);
|
||||||
setSelectedOption(-1);
|
setSelectedOption(-1);
|
||||||
|
|
||||||
@ -495,10 +507,16 @@ export const Search: React.FC<{
|
|||||||
}
|
}
|
||||||
}, [setExpanded, setSelectedOption, singleColumn]);
|
}, [setExpanded, setSelectedOption, singleColumn]);
|
||||||
|
|
||||||
const handleBlur = useCallback(() => {
|
const handleInputBlur = useCallback(() => {
|
||||||
setSelectedOption(-1);
|
setSelectedOption(-1);
|
||||||
}, [setSelectedOption]);
|
}, [setSelectedOption]);
|
||||||
|
|
||||||
|
const getOptionFocusHandler = useCallback((index: number) => {
|
||||||
|
return () => {
|
||||||
|
setSelectedOption(index);
|
||||||
|
};
|
||||||
|
}, []);
|
||||||
|
|
||||||
const formRef = useRef<HTMLFormElement>(null);
|
const formRef = useRef<HTMLFormElement>(null);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@ -526,6 +544,8 @@ export const Search: React.FC<{
|
|||||||
return () => null;
|
return () => null;
|
||||||
}, [expanded]);
|
}, [expanded]);
|
||||||
|
|
||||||
|
const searchOptionsHeading = useId();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<form ref={formRef} className={classNames('search', { active: expanded })}>
|
<form ref={formRef} className={classNames('search', { active: expanded })}>
|
||||||
<input
|
<input
|
||||||
@ -541,13 +561,20 @@ export const Search: React.FC<{
|
|||||||
value={value}
|
value={value}
|
||||||
onChange={handleChange}
|
onChange={handleChange}
|
||||||
onKeyDown={handleKeyDown}
|
onKeyDown={handleKeyDown}
|
||||||
onFocus={handleFocus}
|
onFocus={handleInputFocus}
|
||||||
onBlur={handleBlur}
|
onBlur={handleInputBlur}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<ClearButton hasValue={hasValue} onClick={handleClear} />
|
<ClearButton hasValue={hasValue} onClick={handleClear} />
|
||||||
|
|
||||||
<div className='search__popout' tabIndex={-1}>
|
{/* eslint-disable-next-line jsx-a11y/no-noninteractive-element-interactions */}
|
||||||
|
<div
|
||||||
|
className='search__popout'
|
||||||
|
role='dialog'
|
||||||
|
tabIndex={-1}
|
||||||
|
aria-labelledby={searchOptionsHeading}
|
||||||
|
onKeyDown={handleKeyDown}
|
||||||
|
>
|
||||||
{!hasValue && (
|
{!hasValue && (
|
||||||
<>
|
<>
|
||||||
<h4>
|
<h4>
|
||||||
@ -565,6 +592,7 @@ export const Search: React.FC<{
|
|||||||
tabIndex={0}
|
tabIndex={0}
|
||||||
role='button'
|
role='button'
|
||||||
onMouseDown={action}
|
onMouseDown={action}
|
||||||
|
onFocus={getOptionFocusHandler(i)}
|
||||||
className={classNames(
|
className={classNames(
|
||||||
'search__popout__menu__item search__popout__menu__item--flex',
|
'search__popout__menu__item search__popout__menu__item--flex',
|
||||||
{ selected: selectedOption === i },
|
{ selected: selectedOption === i },
|
||||||
@ -606,6 +634,7 @@ export const Search: React.FC<{
|
|||||||
<button
|
<button
|
||||||
key={key}
|
key={key}
|
||||||
onMouseDown={action}
|
onMouseDown={action}
|
||||||
|
onFocus={getOptionFocusHandler(i)}
|
||||||
className={classNames('search__popout__menu__item', {
|
className={classNames('search__popout__menu__item', {
|
||||||
selected: selectedOption === i,
|
selected: selectedOption === i,
|
||||||
})}
|
})}
|
||||||
@ -618,7 +647,7 @@ export const Search: React.FC<{
|
|||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<h4>
|
<h4 id={searchOptionsHeading}>
|
||||||
<FormattedMessage
|
<FormattedMessage
|
||||||
id='search_popout.options'
|
id='search_popout.options'
|
||||||
defaultMessage='Search options'
|
defaultMessage='Search options'
|
||||||
@ -627,20 +656,22 @@ export const Search: React.FC<{
|
|||||||
|
|
||||||
{searchEnabled && signedIn ? (
|
{searchEnabled && signedIn ? (
|
||||||
<div className='search__popout__menu'>
|
<div className='search__popout__menu'>
|
||||||
{searchOptions.map(({ key, label, action }, i) => (
|
{searchOptions.map(({ key, label, action }, i) => {
|
||||||
<button
|
const currentIndex = (quickActions.length || recent.length) + i;
|
||||||
key={key}
|
return (
|
||||||
onMouseDown={action}
|
<button
|
||||||
className={classNames('search__popout__menu__item', {
|
key={key}
|
||||||
selected:
|
onMouseDown={action}
|
||||||
selectedOption ===
|
onFocus={getOptionFocusHandler(currentIndex)}
|
||||||
(quickActions.length || recent.length) + i,
|
className={classNames('search__popout__menu__item', {
|
||||||
})}
|
selected: selectedOption === currentIndex,
|
||||||
type='button'
|
})}
|
||||||
>
|
type='button'
|
||||||
{label}
|
>
|
||||||
</button>
|
{label}
|
||||||
))}
|
</button>
|
||||||
|
);
|
||||||
|
})}
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<div className='search__popout__menu__message'>
|
<div className='search__popout__menu__message'>
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user