refactor(ui): unify Channels + Stats filter chips (R13 S2c step 4)

- channel-manager sync-status chips (F2) -> FilterChipGroup (rounded-lg -> the
  canonical rounded-full), controls-row alignment preserved via className
- channel-manager user-tag chips (multi-select) restyled to filterChipClass
  (rounded-lg -> rounded-full) so the whole manager chip set is one look
- stats system time-range chips (F4) -> FilterChipGroup (text-sm -> canonical)

Adds aria-label i18n (channels.filters.syncLabel, stats.rangeLabel; HU+EN).
Self-E2E'd: sync chips + tag chips rounded-full, view TABS stay rounded-lg
(their own canonical), stats 7/30/90d toggles. gate green (tsc/eslint-0/
vitest 136/knip/prettier). Branch not merged.
This commit is contained in:
2026-08-15 00:53:12 +02:00
parent f26e329ecf
commit 42d0173095
6 changed files with 24 additions and 25 deletions
+9 -13
View File
@@ -19,6 +19,7 @@ import { useChannelTagActions } from "../lib/useChannelTagActions";
import { useChannelPriority } from "../lib/useChannelPriority";
import ViewSwitcher, { type ViewOption } from "./ViewSwitcher";
import ColumnSortControl from "./ColumnSortControl";
import FilterChipGroup, { filterChipClass } from "./FilterChipGroup";
import Pager from "./Pager";
import { CHANNEL_LAYOUTS, type ChannelLayout } from "../lib/channelLayout";
import { sortRows } from "../lib/columnSort";
@@ -294,18 +295,13 @@ export default function Channels() {
// where they act.
const syncFilter = typeof filters.sync === "string" ? filters.sync : "";
const statusChips = (
<div className="flex flex-wrap items-center gap-1.5">
{SYNC_CHIPS.map((c) => (
<button
key={c.value || "all"}
onClick={() => setFilters({ ...filters, sync: c.value })}
aria-pressed={syncFilter === c.value}
className="pill text-xs px-2.5 py-1 rounded-lg border"
>
{t(c.labelKey)}
</button>
))}
</div>
<FilterChipGroup
ariaLabel={t("channels.filters.syncLabel")}
className="flex flex-wrap items-center gap-1.5"
options={SYNC_CHIPS.map((c) => ({ value: c.value, label: t(c.labelKey) }))}
value={syncFilter}
onChange={(sync) => setFilters({ ...filters, sync })}
/>
);
// The catalog-wide stats ride on the right of the tab row (subscribed only), leaving room for
@@ -479,7 +475,7 @@ export default function Channels() {
key={tg.id}
onClick={() => toggleTagFilter(tg.id)}
aria-pressed={tagFilter.includes(String(tg.id))}
className="pill text-xs px-2 py-1 rounded-lg border"
className={filterChipClass}
>
{tg.name}
</button>
+11 -12
View File
@@ -9,6 +9,7 @@ import { notify } from "../lib/notifications";
import { LS, useAccountPersistedState } from "../lib/storage";
import Tooltip from "./Tooltip";
import { LoadingState, StateMessage } from "./QueryState";
import FilterChipGroup from "./FilterChipGroup";
import { Section, SettingRow } from "./ui/form";
import { useMe } from "../lib/useMe";
@@ -228,18 +229,16 @@ function SystemStats() {
<div className="flex items-center justify-between gap-3 mb-4">
<h2 className="text-lg font-semibold">{t("stats.title")}</h2>
<div className="flex items-center gap-1">
{RANGES.map((d) => (
<button
key={d}
onClick={() => setDays(d)}
aria-pressed={days === d}
className="pill px-3 py-1.5 rounded-full text-sm border"
>
{t("stats.rangeDays", { count: d })}
</button>
))}
</div>
<FilterChipGroup
ariaLabel={t("stats.rangeLabel")}
className="flex items-center gap-1.5"
options={RANGES.map((d) => ({
value: String(d),
label: t("stats.rangeDays", { count: d }),
}))}
value={String(days)}
onChange={(v) => setDays(Number(v))}
/>
</div>
<p className="text-xs text-muted mb-4 leading-relaxed">
@@ -39,6 +39,7 @@
}
},
"filters": {
"syncLabel": "Sync status",
"all": "All",
"needsFull": "Needs full history",
"fullySynced": "Fully synced"
+1
View File
@@ -5,6 +5,7 @@
},
"title": "API quota usage",
"rangeDays": "{{count}}d",
"rangeLabel": "Time range",
"introBefore": "YouTube Data API units attributed by who triggered the spend. ",
"introSystem": "System",
"introAfter": " is shared background work (scheduled backfill, enrichment, resync) that isn't tied to one person.",
@@ -39,6 +39,7 @@
}
},
"filters": {
"syncLabel": "Szinkron állapot",
"all": "Mind",
"needsFull": "Hiányos előzmény",
"fullySynced": "Teljesen szinkronizálva"
+1
View File
@@ -5,6 +5,7 @@
},
"title": "API-kvóta használat",
"rangeDays": "{{count}} nap",
"rangeLabel": "Időtartomány",
"introBefore": "A YouTube Data API-egységek aszerint, hogy ki váltotta ki a felhasználást. A ",
"introSystem": "Rendszer",
"introAfter": " a megosztott háttérmunka (ütemezett letöltés, gazdagítás, újraszinkronizálás), amely nem köthető egy személyhez.",