fix(ui): focus, paging and state that a key or a finger can reach

- A key landing on a side panel's tab steps into the card it peeks open, not onto the hidden tab.
- The Channels cards keep their status / pager row at the list's end while the band is folded.
- A menu row's note shows on an enabled row too: the download state under 'Download…', not a title.
- A removal chip hands the focus to its neighbour before it goes, for every caller (the feed's and
  Plex's active filters); the hidden-filter cue's own copy is gone.
- Back to a spot within a page's end spacer lands at the page's end, not its top.
- A running push / revert is tracked per list: another list's push is no longer refused unseen.
This commit is contained in:
2026-09-27 19:43:04 +02:00
parent e31a588f19
commit 740602b9bf
15 changed files with 231 additions and 48 deletions
+11 -1
View File
@@ -27,7 +27,8 @@ type ChipProps = ButtonHTMLAttributes<HTMLButtonElement> & {
pressed?: boolean;
/** Leading glyph (a lucide element). */
icon?: ReactNode;
/** The whole chip removes what it stands for: a trailing × and the removal as its name. */
/** The whole chip removes what it stands for: a trailing × and the removal as its name. A press
* that holds the focus hands it to the chip's neighbour before the removal, not to <body>. */
removeLabel?: string;
};
@@ -42,6 +43,7 @@ export const Chip = forwardRef<HTMLButtonElement, ChipProps>(function Chip(
type,
className,
children,
onClick,
...rest
},
ref,
@@ -54,6 +56,14 @@ export const Chip = forwardRef<HTMLButtonElement, ChipProps>(function Chip(
aria-label={removeLabel}
title={removeLabel}
className={clsx(chipClass(variant, size), removeLabel && "pr-1.5", className)}
onClick={(e) => {
// A removal chip unmounts with what it removes: from the keyboard it holds the focus, which
// went to <body> (round 2, F6 — the active-filter chips of the feed and Plex). Every
// removal chip gets it here, once, instead of each caller remembering.
if (removeLabel && e.currentTarget === document.activeElement)
focusNeighbour(e.currentTarget);
onClick?.(e);
}}
{...rest}
>
{icon}