Files
siftlode/frontend/src/components/ChannelPicker.tsx
T
peter 17bb14fb18 fix(ui): a picker whose list failed says so, and takes no pick while one is being written
- Combobox gains `error` (said in the list, not "No matches") and a soft `disabled` (keeps the
  focus, announced, opens and changes nothing).
- UserPicker passes both: the share dialog's recipient picker is off while a share is pending, and
  the share and quota pickers say when the user list failed to load.
- ChannelPicker and the Plex playlist/collection pickers say a failed load too.
- The channel menu's Tags row says loading / failed instead of "no tags yet".
2026-09-27 14:47:46 +02:00

49 lines
2.0 KiB
TypeScript

import { useTranslation } from "react-i18next";
import { useQuery } from "@tanstack/react-query";
import { qk } from "../lib/queryKeys";
import { api, type FeedFilters } from "../lib/api";
import Combobox, { type ComboboxOption } from "./ui/Combobox";
// Pick channels to narrow the feed to — several OR together, so every other filter then applies
// across all of them. Until this existed the filter was settable only via a shared "?channel=" link:
// the panel had a remove button for something nothing could apply.
//
// It owns its channel query rather than letting the Sidebar hold it: the Sidebar is mounted for the
// whole feed page, but SidePanel only renders its children once the panel is open, so keeping the
// fetch here means a subscription list of a few hundred loads when the picker is actually looked at.
export default function ChannelPicker({
filters,
setFilters,
}: {
filters: FeedFilters;
setFilters: (f: FeedFilters) => void;
}) {
const { t } = useTranslation();
const channelsQuery = useQuery({
queryKey: qk.channels(),
queryFn: api.channels,
staleTime: 5 * 60_000,
});
const all = channelsQuery.data ?? [];
// The list order isn't alphabetical — it's the user's channel priority — and Combobox preserves the
// options order, so it stays meaningful. selectedLabels keeps a picked channel named even if a later
// narrowing ever drops it from the visible rows.
const options: ComboboxOption[] = all.map((c) => ({ value: c.id, label: c.title ?? c.id }));
const selectedLabels = Object.fromEntries(all.map((c) => [c.id, c.title ?? c.id]));
return (
<Combobox
label={t("sidebar.channelSearch")}
placeholder={t("sidebar.channelSearch")}
options={options}
selected={filters.channelIds}
onChange={(channelIds) => setFilters({ ...filters, channelIds })}
loading={channelsQuery.isLoading}
error={channelsQuery.isError && !channelsQuery.data ? t("common.loadError") : undefined}
emptyText={t("sidebar.noChannelMatch")}
selectedLabels={selectedLabels}
/>
);
}