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:
2026-09-27 19:43:04 +02:00
parent cda626be10
commit e31a588f19
12 changed files with 266 additions and 116 deletions
+4 -3
View File
@@ -332,7 +332,7 @@ export default function PlexSidebar({ layout, setLayout, collapsed, onToggleColl
// you can still add one — the same reuse-the-Combobox idea as the feed's "Your tags" widget. `selected`
// stays empty: this picks an action (open / create), it doesn't hold a persistent selection.
const CREATE_PLAYLIST = "__create__";
function PlaylistPicker() {
export function PlaylistPicker() {
const { t } = useTranslation();
const { setPlaylistOpen: onOpenPlaylist } = usePlexActions();
const qc = useQueryClient();
@@ -346,8 +346,9 @@ function PlaylistPicker() {
const exact = playlists.some((p) => p.title.toLowerCase() === ql);
const options: ComboboxOption[] = [
...matches.map((p) => ({ value: `pl:${p.id}`, label: p.title, count: p.item_count })),
// Offer to create only when the typed name isn't already a playlist.
...(q && !exact ? [{ value: CREATE_PLAYLIST, label: q }] : []),
// Offer to create only when the typed name isn't already a playlist — which is unknown until the
// lists load: over a failed load the row pushed out the error and its Retry (round 2, P7).
...(q && !exact && playlistsQ.data ? [{ value: CREATE_PLAYLIST, label: q }] : []),
];
async function createPlaylist(title: string) {