fix(menus): one tick for every choice; a listbox holds its options only
- The saved views, the view switcher, the year filter and the table's single-choice filter mark the chosen row with MenuCheck, as DESIGN asks, not a trailing tick, a dot or a filled row; a contract test holds every checkable menu row to it. keepSelectedPaint had no caller left and is gone. - A combobox's panel carries its listbox role on an inner PopoverList (listInside), so the failed load, Loading, No matches and the 'n more' line sit beside the options, not inside the listbox. The Plex search box's Loading line likewise. - The Plex sidebar's playlist picker offers Create only once the lists have loaded: over a failed load it pushed the error and its Retry out at the first keystroke.
This commit is contained in:
@@ -137,47 +137,46 @@ export default function PlexSearchBox() {
|
||||
below={
|
||||
open ? (
|
||||
<div
|
||||
ref={listRef}
|
||||
id={listId}
|
||||
role="listbox"
|
||||
aria-label={t("plex.suggest.aria")}
|
||||
className={`${SURFACE.menu} absolute left-0 right-0 top-full z-menu mt-2 max-h-[70dvh] overflow-y-auto animate-[popIn_0.16s_ease]`}
|
||||
>
|
||||
{/* Beside the listbox, not in it: a listbox may own options only (round 2, F4). */}
|
||||
{suggestions.length === 0 && suggestQ.isFetching && (
|
||||
<div className="px-3 py-2 text-sm text-muted">{t("plex.loading")}</div>
|
||||
)}
|
||||
{suggestions.map((c, i) => (
|
||||
<Pressable
|
||||
key={c.id}
|
||||
id={`${listId}-opt-${i}`}
|
||||
data-idx={i}
|
||||
role="option"
|
||||
aria-selected={hi === i}
|
||||
onMouseDown={(e) => e.preventDefault()} // keep input focus through the click
|
||||
onMouseEnter={() => setHi(i)}
|
||||
onClick={() => pick(i)}
|
||||
// The pointer moves `hi` too, so the highlight IS the hover: the menus' roving
|
||||
// recipe (AnchoredPopover's ROVING_HIGHLIGHT), one look for the mouse and the keys.
|
||||
data-active={hi === i ? "true" : undefined}
|
||||
className={`flex w-full items-center gap-3 rounded-lg p-1.5 transition ${ROVING_HIGHLIGHT}`}
|
||||
>
|
||||
<img
|
||||
src={c.thumb}
|
||||
alt=""
|
||||
loading="lazy"
|
||||
className="h-12 w-8 shrink-0 rounded-md object-cover bg-border/40"
|
||||
/>
|
||||
<span className="min-w-0 flex-1 truncate text-sm">{c.title}</span>
|
||||
{c.year != null && (
|
||||
<span className="shrink-0 text-xs text-muted tabular-nums">{c.year}</span>
|
||||
)}
|
||||
{c.type === "show" ? (
|
||||
<Tv className="w-3.5 h-3.5 shrink-0 text-muted" aria-hidden="true" />
|
||||
) : (
|
||||
<Film className="w-3.5 h-3.5 shrink-0 text-muted" aria-hidden="true" />
|
||||
)}
|
||||
</Pressable>
|
||||
))}
|
||||
<div ref={listRef} id={listId} role="listbox" aria-label={t("plex.suggest.aria")}>
|
||||
{suggestions.map((c, i) => (
|
||||
<Pressable
|
||||
key={c.id}
|
||||
id={`${listId}-opt-${i}`}
|
||||
data-idx={i}
|
||||
role="option"
|
||||
aria-selected={hi === i}
|
||||
onMouseDown={(e) => e.preventDefault()} // keep input focus through the click
|
||||
onMouseEnter={() => setHi(i)}
|
||||
onClick={() => pick(i)}
|
||||
// The pointer moves `hi` too, so the highlight IS the hover: the menus' roving
|
||||
// recipe (AnchoredPopover's ROVING_HIGHLIGHT), one look for the mouse and the keys.
|
||||
data-active={hi === i ? "true" : undefined}
|
||||
className={`flex w-full items-center gap-3 rounded-lg p-1.5 transition ${ROVING_HIGHLIGHT}`}
|
||||
>
|
||||
<img
|
||||
src={c.thumb}
|
||||
alt=""
|
||||
loading="lazy"
|
||||
className="h-12 w-8 shrink-0 rounded-md object-cover bg-border/40"
|
||||
/>
|
||||
<span className="min-w-0 flex-1 truncate text-sm">{c.title}</span>
|
||||
{c.year != null && (
|
||||
<span className="shrink-0 text-xs text-muted tabular-nums">{c.year}</span>
|
||||
)}
|
||||
{c.type === "show" ? (
|
||||
<Tv className="w-3.5 h-3.5 shrink-0 text-muted" aria-hidden="true" />
|
||||
) : (
|
||||
<Film className="w-3.5 h-3.5 shrink-0 text-muted" aria-hidden="true" />
|
||||
)}
|
||||
</Pressable>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
) : null
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user