fix(plex): address S2 /code-review findings (typeahead focus + a11y + test)
- Decouple the typeahead's dismiss from real focus: a separate `dismissed` flag (set on Escape/pick, cleared on typing/refocus) replaces manually clearing `focused`, so closing the panel no longer wedges it shut while the input keeps DOM focus — typing again re-opens it (verified in-browser). - Scroll the highlighted option into view during arrow-key nav (listRef + data-idx + scrollIntoView), instead of leaving listRef unused. - Wire the input as a combobox for screen readers: role=combobox, aria-autocomplete/expanded/controls/activedescendant + per-option ids. - Drop the redundant client-side .slice (the request already caps at limit=10). - SearchBar gains an `inputProps` passthrough for the combobox ARIA. - Add PlexSearchBox.dom.test.tsx (4 tests): per-scope pill options, suggestion render + keyboard pick→jumpTo, and the Escape→retype reopen regression. Reviewer's scrollbar-blur concern was a false positive — the listbox container is not focusable, so a mousedown on it never blurs the input.
This commit is contained in:
@@ -1,4 +1,9 @@
|
||||
import { type FocusEventHandler, type KeyboardEventHandler, type ReactNode } from "react";
|
||||
import {
|
||||
type FocusEventHandler,
|
||||
type InputHTMLAttributes,
|
||||
type KeyboardEventHandler,
|
||||
type ReactNode,
|
||||
} from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { Search, X } from "lucide-react";
|
||||
|
||||
@@ -26,6 +31,7 @@ export default function SearchBar({
|
||||
onFocus,
|
||||
onBlur,
|
||||
onKeyDown,
|
||||
inputProps,
|
||||
}: {
|
||||
value: string;
|
||||
onChange: (q: string) => void;
|
||||
@@ -41,6 +47,9 @@ export default function SearchBar({
|
||||
onFocus?: FocusEventHandler<HTMLInputElement>;
|
||||
onBlur?: FocusEventHandler<HTMLInputElement>;
|
||||
onKeyDown?: KeyboardEventHandler<HTMLInputElement>;
|
||||
// Extra attributes spread onto the <input> (e.g. Plex's combobox ARIA). Spread FIRST so the
|
||||
// component's own value/handlers always win.
|
||||
inputProps?: InputHTMLAttributes<HTMLInputElement>;
|
||||
}) {
|
||||
const { t } = useTranslation();
|
||||
const fire = () => {
|
||||
@@ -51,6 +60,7 @@ export default function SearchBar({
|
||||
<div className="h-11 pl-3 pr-2 rounded-2xl glass-menu flex items-center gap-2 focus-within:outline-2 focus-within:outline-accent/60 focus-within:outline-offset-2">
|
||||
{leading ?? <Search className="w-4 h-4 shrink-0 text-muted" />}
|
||||
<input
|
||||
{...inputProps}
|
||||
value={value}
|
||||
onChange={(e) => onChange(e.target.value)}
|
||||
onFocus={onFocus}
|
||||
|
||||
Reference in New Issue
Block a user