From dcb91066f53c7a63313646d2f20ba7f561b874b2 Mon Sep 17 00:00:00 2001 From: npeter83 Date: Sun, 9 Aug 2026 21:06:58 +0200 Subject: [PATCH] =?UTF-8?q?refactor(ui):=20R10=20S4=20=E2=80=94=20unify=20?= =?UTF-8?q?selected-paint=20into=20.pill/.is-current/.chip=20recipes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The ~14 hand-rolled "this is the selected/current/active one" idioms each repeated the same `sel ? "bg-accent text-accent-fg border-accent" : "…"` inline ternary, which had drifted. Three attribute-driven recipes in @layer components now own that paint, keyed on the ARIA the control already carries: .pill (aria-pressed segments/toggles), .is-current (aria-current nav/list items), and .chip / .chip-solid (tag & filter tokens). The JSX just toggles the ARIA; the CSS paints, so one control's selected look can't diverge from the next's. No focus-visible in the recipes — the @layer base rule already rings every button/a. Migrated 15 sites (Feed show/content, Channels tag filter, Sidebar or-and + scope, PlexSidebar match, Tabs, SettingsPanel tab-rail, PlexPlaylistView layout [soft], NavSidebar nav + settings, ActiveFilterChips, PlexSidebar RemovableChip, ChannelTags, ChannelPicker). Deliberately left: PlexSidebar facet Chip and PlexPlaylistView already covered; SavedViews row (paint split across two elements), the scheme swatch (border-only on a coloured swatch), and ChannelPicker's option rows (text-only selection) — noted for a follow-up. --- frontend/src/components/ActiveFilterChips.tsx | 2 +- frontend/src/components/ChannelPicker.tsx | 2 +- frontend/src/components/ChannelTags.tsx | 3 +- frontend/src/components/Channels.tsx | 6 +- frontend/src/components/Feed.tsx | 12 +--- frontend/src/components/NavSidebar.tsx | 10 +-- frontend/src/components/PlexPlaylistView.tsx | 5 +- frontend/src/components/PlexSidebar.tsx | 6 +- frontend/src/components/SettingsPanel.tsx | 7 +- frontend/src/components/Sidebar.tsx | 10 +-- frontend/src/components/Tabs.tsx | 7 +- frontend/src/index.css | 64 +++++++++++++++++++ 12 files changed, 84 insertions(+), 50 deletions(-) diff --git a/frontend/src/components/ActiveFilterChips.tsx b/frontend/src/components/ActiveFilterChips.tsx index 625f4235..a6429473 100644 --- a/frontend/src/components/ActiveFilterChips.tsx +++ b/frontend/src/components/ActiveFilterChips.tsx @@ -32,7 +32,7 @@ export default function ActiveFilterChips({ onClick={f.remove} title={t("feed.chips.remove", { name: f.label })} aria-label={t("feed.chips.remove", { name: f.label })} - className="group inline-flex items-center gap-1 pl-2.5 pr-1.5 py-1 rounded-full border border-accent/40 bg-accent/10 text-accent text-xs hover:border-accent hover:bg-accent/20 transition" + className="chip group pl-2.5 pr-1.5 py-1 text-xs hover:border-accent hover:bg-accent/20 transition" > {f.label} diff --git a/frontend/src/components/ChannelPicker.tsx b/frontend/src/components/ChannelPicker.tsx index e2fd5225..e09f037b 100644 --- a/frontend/src/components/ChannelPicker.tsx +++ b/frontend/src/components/ChannelPicker.tsx @@ -52,7 +52,7 @@ export default function ChannelPicker({ key={id} onClick={() => toggle(id)} title={t("sidebar.channelRemove", { name: nameOf(id) })} - className="inline-flex items-center gap-1 max-w-full pl-2.5 pr-1.5 py-1 rounded-full bg-accent text-accent-fg text-xs shadow-xs hover:opacity-90 transition" + className="chip-solid inline-flex items-center gap-1 max-w-full pl-2.5 pr-1.5 py-1 rounded-full text-xs shadow-xs hover:opacity-90 transition" > {nameOf(id)} diff --git a/frontend/src/components/ChannelTags.tsx b/frontend/src/components/ChannelTags.tsx index ea6fa675..703b1990 100644 --- a/frontend/src/components/ChannelTags.tsx +++ b/frontend/src/components/ChannelTags.tsx @@ -22,8 +22,7 @@ interface TagChipProps { } function TagChip({ tag, onClick, title }: TagChipProps) { - const cls = - "shrink-0 text-3xs px-1.5 py-0.5 rounded-full bg-accent text-accent-fg border border-accent max-w-36 truncate"; + const cls = "chip-solid shrink-0 text-3xs px-1.5 py-0.5 rounded-full border max-w-36 truncate"; return onClick ? ( diff --git a/frontend/src/components/Feed.tsx b/frontend/src/components/Feed.tsx index c5dd0c26..5d49bcf6 100644 --- a/frontend/src/components/Feed.tsx +++ b/frontend/src/components/Feed.tsx @@ -557,11 +557,7 @@ export default function Feed({ data-testid={`feed-show-${id}`} onClick={() => setFilters({ ...filters, show: id })} aria-pressed={filters.show === id} - className={`text-xs px-3 py-1.5 rounded-full border transition ${ - filters.show === id - ? "bg-accent text-accent-fg border-accent" - : "border-border text-muted hover:text-fg hover:border-accent" - }`} + className="pill text-xs px-3 py-1.5 rounded-full border" > {t("sidebar.show." + id)} @@ -575,11 +571,7 @@ export default function Feed({ data-testid={`feed-content-${c.key}`} onClick={() => setFilters({ ...filters, [c.key]: !on })} aria-pressed={on} - className={`text-xs px-3 py-1.5 rounded-full border transition ${ - on - ? "bg-accent text-accent-fg border-accent" - : "border-border text-muted hover:text-fg hover:border-accent" - }`} + className="pill text-xs px-3 py-1.5 rounded-full border" > {t(c.label)} diff --git a/frontend/src/components/NavSidebar.tsx b/frontend/src/components/NavSidebar.tsx index 92b54547..0f9642b7 100644 --- a/frontend/src/components/NavSidebar.tsx +++ b/frontend/src/components/NavSidebar.tsx @@ -193,9 +193,7 @@ export default function NavSidebar({ onClick={() => setPage(p)} title={slim ? label : undefined} aria-current={active ? "page" : undefined} - className={`${rowBase} ${slim ? "justify-center px-0" : ""} relative ${ - active ? "bg-accent text-accent-fg" : "text-muted hover:text-fg hover:bg-card" - }`} + className={`${rowBase} ${slim ? "justify-center px-0" : ""} relative is-current`} > @@ -308,11 +306,7 @@ export default function NavSidebar({ onClick={() => setPage("settings")} title={slim ? t("header.account.settings") : undefined} aria-current={page === "settings" ? "page" : undefined} - className={`${rowBase} ${slim ? "justify-center px-0" : ""} ${ - page === "settings" - ? "bg-accent text-accent-fg" - : "text-muted hover:text-fg hover:bg-card" - }`} + className={`${rowBase} ${slim ? "justify-center px-0" : ""} is-current`} > {!slim && {t("header.account.settings")}} diff --git a/frontend/src/components/PlexPlaylistView.tsx b/frontend/src/components/PlexPlaylistView.tsx index d0daae57..60ca7dac 100644 --- a/frontend/src/components/PlexPlaylistView.tsx +++ b/frontend/src/components/PlexPlaylistView.tsx @@ -505,9 +505,8 @@ export default function PlexPlaylistView({ title={label} aria-label={label} aria-pressed={layout === v} - className={`rounded-lg p-2 text-sm transition ${ - layout === v ? "bg-accent/15 text-accent" : "glass-card glass-hover text-muted" - }`} + data-soft="true" + className={`pill rounded-lg p-2 text-sm ${layout === v ? "" : "glass-card glass-hover text-muted"}`} > {icon} diff --git a/frontend/src/components/PlexSidebar.tsx b/frontend/src/components/PlexSidebar.tsx index c332e729..4e882374 100644 --- a/frontend/src/components/PlexSidebar.tsx +++ b/frontend/src/components/PlexSidebar.tsx @@ -59,9 +59,7 @@ function MatchToggle({ key={m} onClick={() => onChange(m)} aria-pressed={mode === m} - className={`rounded-full px-2 py-0.5 transition ${ - mode === m ? "bg-accent text-accent-fg" : "text-muted hover:text-fg" - }`} + className="pill rounded-full px-2 py-0.5" > {t(`plex.filter.match.${m}`)} @@ -518,7 +516,7 @@ function Chip({ function RemovableChip({ label, onRemove }: { label: string; onRemove: () => void }) { return ( - + {label} @@ -392,9 +388,7 @@ export default function Sidebar({ onClick={() => setFilters({ ...filters, scope: s })} title={t("header.scope." + s + "Tip")} aria-pressed={filters.scope === s} - className={`flex-1 inline-flex items-center justify-center gap-1 px-2.5 py-1 rounded-full transition ${ - filters.scope === s ? "bg-accent text-accent-fg" : "text-muted hover:text-fg" - }`} + className="pill flex-1 inline-flex items-center justify-center gap-1 px-2.5 py-1 rounded-full" > {s === "my" ? : } {t("header.scope." + s)} diff --git a/frontend/src/components/Tabs.tsx b/frontend/src/components/Tabs.tsx index fbf25b84..3154827b 100644 --- a/frontend/src/components/Tabs.tsx +++ b/frontend/src/components/Tabs.tsx @@ -31,10 +31,9 @@ export default function Tabs({