fix(ui): address self-review findings (R13 S2c)
Three low-severity findings from the /code-review high self-pass: - Plex duration: the "Any" chip no longer false-highlights when durationMin/Max hold a set-but-off-bucket value (stale/version-skew state) — it selects nothing then, matching the pre-sweep behaviour, instead of colliding on the empty-string sentinel. - FilterChipGroup: count badge now guards on count > 0 (was `!= null`), matching the Tabs badge so a future count:0 can't render a stray "0" pill. - Extract the shared CountBadge component; Tabs and FilterChipGroup now render the count-badge token from one source (was two near-identical inline spans). gate green (tsc/eslint-0/vitest 136/knip/prettier). Duration self-E2E'd: bucket select + Any toggle intact. Branch not merged.
This commit is contained in:
@@ -0,0 +1,15 @@
|
|||||||
|
// A small count pill shown beside a tab label or filter chip (pending requests, item counts). One
|
||||||
|
// source for the count-badge token so Tabs and FilterChipGroup can't drift. The `active` variant sits
|
||||||
|
// on an accent-filled parent (a pressed tab/chip), so it tints against accent-fg; the resting variant
|
||||||
|
// carries its own accent tint. Callers guard on `count > 0` — this just renders the pill.
|
||||||
|
export default function CountBadge({ count, active }: { count: number; active?: boolean }) {
|
||||||
|
return (
|
||||||
|
<span
|
||||||
|
className={`tabular-nums text-2xs leading-none px-1.5 py-0.5 rounded-full ${
|
||||||
|
active ? "bg-accent-fg/20" : "bg-accent/20 text-accent"
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
{count}
|
||||||
|
</span>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -1,4 +1,5 @@
|
|||||||
import { type ReactNode } from "react";
|
import { type ReactNode } from "react";
|
||||||
|
import CountBadge from "./CountBadge";
|
||||||
|
|
||||||
// Canonical filter-chip geometry — the rounded-full "Filters bar" look used app-wide for a selectable
|
// Canonical filter-chip geometry — the rounded-full "Filters bar" look used app-wide for a selectable
|
||||||
// filter chip. Exported so single-select groups (this component) AND standalone/multi-select filter
|
// filter chip. Exported so single-select groups (this component) AND standalone/multi-select filter
|
||||||
@@ -48,15 +49,7 @@ export default function FilterChipGroup<T extends string>({
|
|||||||
>
|
>
|
||||||
{o.icon}
|
{o.icon}
|
||||||
<span>{o.label}</span>
|
<span>{o.label}</span>
|
||||||
{o.count != null && (
|
{o.count != null && o.count > 0 && <CountBadge count={o.count} active={on} />}
|
||||||
<span
|
|
||||||
className={`tabular-nums text-3xs leading-none px-1 py-0.5 rounded-full ${
|
|
||||||
on ? "bg-accent-fg/20 text-accent-fg" : "bg-muted/15 text-muted"
|
|
||||||
}`}
|
|
||||||
>
|
|
||||||
{o.count}
|
|
||||||
</span>
|
|
||||||
)}
|
|
||||||
</button>
|
</button>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
|
|||||||
@@ -373,7 +373,12 @@ export default function PlexSidebar({ layout, setLayout, collapsed, onToggleColl
|
|||||||
body: (
|
body: (
|
||||||
<FilterChipGroup
|
<FilterChipGroup
|
||||||
ariaLabel={t("plex.filter.duration")}
|
ariaLabel={t("plex.filter.duration")}
|
||||||
value={durBucketKey ?? ""}
|
// "Any" is selected only when there is genuinely no duration filter; a set-but-off-bucket
|
||||||
|
// duration (e.g. stale state after a bucket-boundary change) selects nothing, not "Any".
|
||||||
|
value={
|
||||||
|
durBucketKey ??
|
||||||
|
(filters.durationMin == null && filters.durationMax == null ? "" : " | ||||||