Add keyboard navigation to generator switcher
Some checks are pending
Deploy to GitHub Pages / build (push) Waiting to run
Deploy to GitHub Pages / deploy (push) Blocked by required conditions

This commit is contained in:
Misode
2025-01-27 23:13:10 +01:00
parent 953425b800
commit c4a9bc06fa
2 changed files with 37 additions and 3 deletions

View File

@@ -66,6 +66,7 @@ function GeneratorTitle({ title, gen }: GeneratorTitleProps) {
const [active, setActive] = useFocus()
const [search, setSearch] = useState('')
const inputRef = useRef<HTMLInputElement>(null)
const resultsRef = useRef<HTMLDivElement>(null)
const icon = Object.keys(Icons).includes(gen.id) ? gen.id as keyof typeof Icons : undefined
@@ -92,14 +93,41 @@ function GeneratorTitle({ title, gen }: GeneratorTitleProps) {
})
}, [setActive, inputRef])
const handleKeyDown = useCallback((e: KeyboardEvent) => {
if (e.key == 'Enter') {
if (document.activeElement == inputRef.current) {
const firstResult = resultsRef.current?.firstElementChild
if (firstResult instanceof HTMLElement) {
firstResult.click()
}
}
} else if (e.key == 'ArrowDown') {
const nextElement = document.activeElement == inputRef.current
? resultsRef.current?.firstElementChild
: document.activeElement?.nextElementSibling
if (nextElement instanceof HTMLElement) {
nextElement.focus()
}
e.preventDefault()
} else if (e.key == 'ArrowUp') {
const prevElement = document.activeElement?.previousElementSibling
if (prevElement instanceof HTMLElement) {
prevElement.focus()
}
e.preventDefault()
} else if (e.key == 'Escape') {
setActive(false)
}
}, [setActive, inputRef])
return <div class="px-1 relative">
<h1 class="font-bold flex items-center cursor-pointer text-lg sm:text-2xl" onClick={open}>
{title}
{icon && Icons[icon]}
</h1>
<div class={`gen-menu absolute flex flex-col gap-2 p-2 rounded-lg drop-shadow-xl ${active ? '' : 'hidden'}`}>
<input ref={inputRef} type="text" class="py-1 px-2 w-full rounded" value={search} placeholder={locale('generators.search')} onInput={(e) => setSearch((e.target as HTMLInputElement).value)} onClick={e => e.stopPropagation()} />
{active && <div class="gen-results overflow-y-auto overscroll-none flex flex-col pr-2 h-96 max-h-max min-w-max">
<div class={`gen-menu absolute flex flex-col gap-2 p-2 rounded-lg drop-shadow-xl ${active ? '' : 'hidden'}`} onKeyDown={handleKeyDown}>
<input ref={inputRef} type="text" class="py-1 px-2 w-full rounded" value={search} placeholder={locale('generators.search')} onInput={(e) => setSearch((e.target as HTMLInputElement).value)} onClick={(e) => e.stopPropagation()} />
{active && <div ref={resultsRef} class="gen-results overflow-y-auto overscroll-none flex flex-col pr-2 h-96 max-h-max min-w-max">
{generators.length === 0 && <span class="note">{locale('generators.no_results')}</span>}
{generators.map(g =>
<Link class="flex items-center cursor-pointer no-underline rounded p-1" href={cleanUrl(g.url)} onClick={() => setActive(false)}>

View File

@@ -234,6 +234,10 @@ nav li .btn svg {
background-color: var(--background-1);
}
.gen-results > a {
outline-offset: -2px;
}
.gen-results > a svg {
width: 16px;
height: 16px;
@@ -243,10 +247,12 @@ nav li .btn svg {
transition: margin 0.2s;
}
.gen-results > a:focus-visible,
.gen-results > a:hover {
background-color: var(--background-3);
}
.gen-results > a:focus-visible svg,
.gen-results > a:hover svg {
margin-left: 14px;
margin-right: 0px;