fix(filters): the year dialog fits a tablet, walks by arrow keys, names one year once
- a decade cell is a 36-high box with its count under the year (owner A) - arrow keys walk the cells: along a decade, then to the nearest year below - clearing the kept older year hands the focus on instead of dropping it - More... shows while a year with titles is hidden, as a plain chip - the decade caption is not read twice; the Plex range fields have names - one Plex year is labelled 1999, not 1999-1999, via oneYear/singleYear - tests for the Plex wiring and the feed year clearing the upload date
This commit is contained in:
@@ -6,7 +6,14 @@ import { vocab } from "../../lib/vocab";
|
||||
import { plexLang } from "../../i18n";
|
||||
import { useQuery, useQueryClient } from "@tanstack/react-query";
|
||||
import { ListMusic, Plus, SlidersHorizontal } from "lucide-react";
|
||||
import { api, plexFilterCount, type PlexFilters } from "../../lib/api";
|
||||
import {
|
||||
api,
|
||||
oneYear,
|
||||
plexFilterCount,
|
||||
singleYear,
|
||||
type PlexFacets,
|
||||
type PlexFilters,
|
||||
} from "../../lib/api";
|
||||
import { notifyActionError } from "../../lib/actionErrors";
|
||||
import { useDebounced } from "../../lib/useDebounced";
|
||||
import FilterRail from "../../components/ui/FilterRail";
|
||||
@@ -173,36 +180,7 @@ export default function PlexSidebar() {
|
||||
{
|
||||
id: "year",
|
||||
title: t("plex.filter.year"),
|
||||
body: (
|
||||
<div className="flex flex-col gap-2">
|
||||
{/* One year is a range of one: picking it sets both ends, as the year on an info page does,
|
||||
so the range fields below show it and a decade stays typeable there. */}
|
||||
{facets && facets.years.length > 0 && (
|
||||
<YearPicker
|
||||
years={facets.years}
|
||||
selected={
|
||||
filters.yearMin != null && filters.yearMin === filters.yearMax
|
||||
? filters.yearMin
|
||||
: undefined
|
||||
}
|
||||
onPick={(year) => patch({ yearMin: year ?? null, yearMax: year ?? null })}
|
||||
/>
|
||||
)}
|
||||
<div className="flex items-center gap-2">
|
||||
<NumberInput
|
||||
value={filters.yearMin ?? null}
|
||||
placeholder={facets?.year_min ?? undefined}
|
||||
onChange={(v) => patch({ yearMin: v })}
|
||||
/>
|
||||
<span className="text-muted text-xs">–</span>
|
||||
<NumberInput
|
||||
value={filters.yearMax ?? null}
|
||||
placeholder={facets?.year_max ?? undefined}
|
||||
onChange={(v) => patch({ yearMax: v })}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
),
|
||||
body: <PlexYearFilter filters={filters} facets={facets} patch={patch} />,
|
||||
},
|
||||
...(scope === "movie"
|
||||
? [
|
||||
@@ -499,17 +477,62 @@ function RemovableChip({ label, onRemove }: { label: string; onRemove: () => voi
|
||||
);
|
||||
}
|
||||
|
||||
/** The rail's Year section: the year picker over the range fields. One year is a range of one:
|
||||
* picking it sets both ends, as the year on an info page does, so the range fields show it and a
|
||||
* decade stays typeable there. */
|
||||
export function PlexYearFilter({
|
||||
filters,
|
||||
facets,
|
||||
patch,
|
||||
}: {
|
||||
filters: PlexFilters;
|
||||
facets: PlexFacets | undefined;
|
||||
patch: (p: Partial<PlexFilters>) => void;
|
||||
}) {
|
||||
const { t } = useTranslation();
|
||||
return (
|
||||
<div className="flex flex-col gap-2">
|
||||
{facets && facets.years.length > 0 && (
|
||||
<YearPicker
|
||||
years={facets.years}
|
||||
selected={singleYear(filters)}
|
||||
onPick={(year) => patch(oneYear(year))}
|
||||
/>
|
||||
)}
|
||||
<div className="flex items-center gap-2">
|
||||
<NumberInput
|
||||
label={t("plex.filter.yearFrom")}
|
||||
value={filters.yearMin ?? null}
|
||||
placeholder={facets?.year_min ?? undefined}
|
||||
onChange={(v) => patch({ yearMin: v })}
|
||||
/>
|
||||
<span className="text-muted text-xs">–</span>
|
||||
<NumberInput
|
||||
label={t("plex.filter.yearTo")}
|
||||
value={filters.yearMax ?? null}
|
||||
placeholder={facets?.year_max ?? undefined}
|
||||
onChange={(v) => patch({ yearMax: v })}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function NumberInput({
|
||||
label,
|
||||
value,
|
||||
placeholder,
|
||||
onChange,
|
||||
}: {
|
||||
/** Its accessible name: the placeholder (a year) is not one. */
|
||||
label: string;
|
||||
value: number | null;
|
||||
placeholder?: number;
|
||||
onChange: (v: number | null) => void;
|
||||
}) {
|
||||
return (
|
||||
<Input
|
||||
aria-label={label}
|
||||
className="w-20 tabular-nums"
|
||||
size="sm"
|
||||
type="number"
|
||||
|
||||
Reference in New Issue
Block a user