refactor(ui): R10 S4 — complete the selection-paint sweep (all pill idioms)
The first S4 pass migrated a subset, which left same-idiom selection pills hand-rolled and made adjacent controls inconsistent (a /code-review finding). This completes the sweep so every selected-among-options control shares the recipe: - plain pills → .pill (+ aria-pressed): Channels status filters + view tabs, Sidebar filter-tag chips + date presets + custom-dates, Stats tabs + ranges. - glass-card-resting pills → .pill with the resting `glass-card glass-hover` kept in the non-pressed branch (solid accent when pressed): PlexSidebar library scope + the facet Chip (retired the round-1 "deferred" note — the recipe already expresses this via the resting branch, no new variant needed). - custom-dates aria-pressed is coerced with !! so a date string can't leak into the attribute and miss the [aria-pressed="true"] paint. - drop the dead `border` on ChannelTags' chip-solid (accent border on accent bg). Out of scope (a separate concern, NOT selection-among-options): primary/action buttons that share the accent fill — Banner action, ConfirmProvider primary, PlexBrowse primary, PlayerModal watched-toggle — these want a future .btn-primary recipe. Left by-design: tableHeader filter options (PopoverItem + keepSelectedPaint, owned by the popover platform).
This commit is contained in:
@@ -22,7 +22,7 @@ interface TagChipProps {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function TagChip({ tag, onClick, title }: TagChipProps) {
|
function TagChip({ tag, onClick, title }: TagChipProps) {
|
||||||
const cls = "chip-solid shrink-0 text-3xs px-1.5 py-0.5 rounded-full border max-w-36 truncate";
|
const cls = "chip-solid shrink-0 text-3xs px-1.5 py-0.5 rounded-full max-w-36 truncate";
|
||||||
return onClick ? (
|
return onClick ? (
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
|
|||||||
@@ -483,11 +483,8 @@ export default function Channels() {
|
|||||||
<button
|
<button
|
||||||
key={f.id}
|
key={f.id}
|
||||||
onClick={() => setStatusFilter(f.id)}
|
onClick={() => setStatusFilter(f.id)}
|
||||||
className={`text-xs px-2.5 py-1 rounded-full border transition ${
|
aria-pressed={statusFilter === f.id}
|
||||||
statusFilter === f.id
|
className="pill text-xs px-2.5 py-1 rounded-full border"
|
||||||
? "bg-accent text-accent-fg border-accent"
|
|
||||||
: "bg-card border-border text-muted hover:border-accent"
|
|
||||||
}`}
|
|
||||||
>
|
>
|
||||||
{t(f.labelKey)}
|
{t(f.labelKey)}
|
||||||
</button>
|
</button>
|
||||||
@@ -622,11 +619,8 @@ export default function Channels() {
|
|||||||
<button
|
<button
|
||||||
key={v}
|
key={v}
|
||||||
onClick={() => setView(v)}
|
onClick={() => setView(v)}
|
||||||
className={`text-sm px-3 py-1.5 rounded-full border transition ${
|
aria-pressed={view === v}
|
||||||
view === v
|
className="pill text-sm px-3 py-1.5 rounded-full border"
|
||||||
? "bg-accent text-accent-fg border-accent"
|
|
||||||
: "bg-card border-border text-muted hover:border-accent"
|
|
||||||
}`}
|
|
||||||
>
|
>
|
||||||
{t(`channels.tabs.${v}`)}
|
{t(`channels.tabs.${v}`)}
|
||||||
</button>
|
</button>
|
||||||
|
|||||||
@@ -148,8 +148,9 @@ export default function PlexSidebar({ layout, setLayout, collapsed, onToggleColl
|
|||||||
<button
|
<button
|
||||||
key={s}
|
key={s}
|
||||||
onClick={() => setScope(s)}
|
onClick={() => setScope(s)}
|
||||||
className={`min-w-0 px-1.5 py-1.5 rounded-lg text-xs font-medium truncate transition ${
|
aria-pressed={s === scope}
|
||||||
s === scope ? "bg-accent text-accent-fg" : "glass-card glass-hover"
|
className={`pill min-w-0 px-1.5 py-1.5 rounded-lg text-xs font-medium truncate ${
|
||||||
|
s === scope ? "" : "glass-card glass-hover"
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
{t(`plex.filter.scopeOpt.${s}`)}
|
{t(`plex.filter.scopeOpt.${s}`)}
|
||||||
@@ -505,9 +506,8 @@ function Chip({
|
|||||||
return (
|
return (
|
||||||
<button
|
<button
|
||||||
onClick={onClick}
|
onClick={onClick}
|
||||||
className={`px-2.5 py-1 rounded-full text-xs transition ${
|
aria-pressed={active}
|
||||||
active ? "bg-accent text-accent-fg" : "glass-card glass-hover"
|
className={`pill px-2.5 py-1 rounded-full text-xs ${active ? "" : "glass-card glass-hover"}`}
|
||||||
}`}
|
|
||||||
>
|
>
|
||||||
{children}
|
{children}
|
||||||
</button>
|
</button>
|
||||||
|
|||||||
@@ -46,11 +46,8 @@ function TagChip({
|
|||||||
data-tag={tag.name}
|
data-tag={tag.name}
|
||||||
onClick={onClick}
|
onClick={onClick}
|
||||||
title={t("sidebar.channelCount", { count })}
|
title={t("sidebar.channelCount", { count })}
|
||||||
className={`inline-flex items-center gap-1.5 text-xs px-2.5 py-1 rounded-full border shadow-xs hover:shadow-sm active:translate-y-px transition ${
|
aria-pressed={active}
|
||||||
active
|
className="pill inline-flex items-center gap-1.5 text-xs px-2.5 py-1 rounded-full border shadow-xs hover:shadow-sm active:translate-y-px"
|
||||||
? "bg-accent text-accent-fg border-accent"
|
|
||||||
: "bg-card border-border text-fg hover:border-accent"
|
|
||||||
}`}
|
|
||||||
>
|
>
|
||||||
<span>{tag.name}</span>
|
<span>{tag.name}</span>
|
||||||
<span
|
<span
|
||||||
@@ -169,11 +166,8 @@ export default function Sidebar({
|
|||||||
dateTo: undefined,
|
dateTo: undefined,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
className={`text-xs px-2.5 py-1 rounded-full border shadow-xs active:translate-y-px transition ${
|
aria-pressed={activePreset}
|
||||||
activePreset
|
className="pill text-xs px-2.5 py-1 rounded-full border shadow-xs active:translate-y-px"
|
||||||
? "bg-accent text-accent-fg border-accent"
|
|
||||||
: "bg-card border-border hover:border-accent"
|
|
||||||
}`}
|
|
||||||
>
|
>
|
||||||
{t("sidebar.datePreset." + p.key)}
|
{t("sidebar.datePreset." + p.key)}
|
||||||
</button>
|
</button>
|
||||||
@@ -185,11 +179,8 @@ export default function Sidebar({
|
|||||||
setCustomDates(next);
|
setCustomDates(next);
|
||||||
if (next) setFilters({ ...filters, maxAgeDays: undefined });
|
if (next) setFilters({ ...filters, maxAgeDays: undefined });
|
||||||
}}
|
}}
|
||||||
className={`text-xs px-2.5 py-1 rounded-full border shadow-xs active:translate-y-px transition ${
|
aria-pressed={!!(customDates || filters.dateFrom || filters.dateTo)}
|
||||||
customDates || filters.dateFrom || filters.dateTo
|
className="pill text-xs px-2.5 py-1 rounded-full border shadow-xs active:translate-y-px"
|
||||||
? "bg-accent text-accent-fg border-accent"
|
|
||||||
: "bg-card border-border hover:border-accent"
|
|
||||||
}`}
|
|
||||||
>
|
>
|
||||||
{t("sidebar.custom")}
|
{t("sidebar.custom")}
|
||||||
</button>
|
</button>
|
||||||
|
|||||||
@@ -35,11 +35,8 @@ export default function Stats() {
|
|||||||
<button
|
<button
|
||||||
key={id}
|
key={id}
|
||||||
onClick={() => setTab(id)}
|
onClick={() => setTab(id)}
|
||||||
className={`px-3 py-1.5 rounded-full text-sm border transition ${
|
aria-pressed={tab === id}
|
||||||
tab === id
|
className="pill px-3 py-1.5 rounded-full text-sm border"
|
||||||
? "bg-accent text-accent-fg border-accent"
|
|
||||||
: "bg-card border-border text-muted hover:border-accent"
|
|
||||||
}`}
|
|
||||||
>
|
>
|
||||||
{t(`stats.tabs.${id}`)}
|
{t(`stats.tabs.${id}`)}
|
||||||
</button>
|
</button>
|
||||||
@@ -236,11 +233,8 @@ function SystemStats() {
|
|||||||
<button
|
<button
|
||||||
key={d}
|
key={d}
|
||||||
onClick={() => setDays(d)}
|
onClick={() => setDays(d)}
|
||||||
className={`px-3 py-1.5 rounded-full text-sm border transition ${
|
aria-pressed={days === d}
|
||||||
days === d
|
className="pill px-3 py-1.5 rounded-full text-sm border"
|
||||||
? "bg-accent text-accent-fg border-accent"
|
|
||||||
: "bg-card border-border text-muted hover:border-accent"
|
|
||||||
}`}
|
|
||||||
>
|
>
|
||||||
{t("stats.rangeDays", { count: d })}
|
{t("stats.rangeDays", { count: d })}
|
||||||
</button>
|
</button>
|
||||||
|
|||||||
@@ -474,7 +474,7 @@ body {
|
|||||||
.chip-solid is intentionally a PAINT-ONLY modifier (bg/text/border colour, no display or
|
.chip-solid is intentionally a PAINT-ONLY modifier (bg/text/border colour, no display or
|
||||||
geometry): it sits on a site's OWN chip geometry so a truncating single-element chip
|
geometry): it sits on a site's OWN chip geometry so a truncating single-element chip
|
||||||
(ChannelTags) isn't forced into inline-flex, which would break its text-overflow. Use it WITH a
|
(ChannelTags) isn't forced into inline-flex, which would break its text-overflow. Use it WITH a
|
||||||
site's `rounded-full`/`border` utilities (or on top of `.chip`), never bare. */
|
site's own geometry (`rounded-full`, optional `border`) or on top of `.chip`, never bare. */
|
||||||
.chip {
|
.chip {
|
||||||
display: inline-flex;
|
display: inline-flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
|||||||
Reference in New Issue
Block a user