fix(player): a chosen tab's underline and an open or pressed icon read the scope's chosen colour

The underline named `--color-chosen`, which is resolved once on :root, so it stayed the accent under
`media-marks` (seen in the browser: fuchsia on Slate light); it reads `--chosen` where it is used
now. An icon button pressed or with its menu open tinted its glyph with the accent; it takes the
chosen colour — the accent everywhere but the player chrome, where it is the media amber.
This commit is contained in:
2026-10-10 22:26:22 +02:00
parent 5b48018e92
commit 93192dadd8
5 changed files with 19 additions and 5 deletions
+2 -1
View File
@@ -20,7 +20,8 @@ export interface IconButtonProps
children: ReactNode;
variant?: IconButtonVariant;
size?: IconButtonSize;
/** A toggle: sets aria-pressed and tints the glyph with the accent. */
/** A toggle: sets aria-pressed and tints the glyph with the chosen colour (the accent; the media
* amber on the player chrome). */
pressed?: boolean;
/** Not accepted: a toggle is `pressed` (JSX lets any undeclared `aria-*` through, so it is `never`). */
"aria-pressed"?: never;
+4 -1
View File
@@ -68,6 +68,9 @@ describe("Tabs", () => {
const on = tab("Alpha").className;
expect(on).not.toMatch(/\btext-accent\b/);
expect(on).toMatch(/\btext-white\b/);
expect(on).toMatch(/--color-chosen/);
// The scope's own `--chosen`, read where it is used: `--color-chosen` is resolved once on :root
// and would stay the accent under `media-marks`.
expect(on).toMatch(/var\(--chosen,/);
expect(on).not.toMatch(/--color-chosen/);
});
});
+2 -2
View File
@@ -121,7 +121,7 @@ const ICON_VARIANTS: Record<IconButtonVariant, string> = {
// An icon toggle that is on shows it in the glyph (watched, saved, a selected track), not as a solid
// fill: a solid accent square in a row of quiet glyphs outshouts the row's primary action.
const ICON_PRESSED = "aria-pressed:text-accent";
const ICON_PRESSED = "aria-pressed:text-chosen";
const ICON_SIZES: Record<IconButtonSize, string> = {
sm: "size-7 [&>svg]:size-4",
@@ -172,7 +172,7 @@ export function iconButtonClass({
pressed?: boolean;
}) {
return clsx(
"inline-grid shrink-0 place-items-center transition touch-target aria-expanded:text-accent disabled:opacity-50 disabled:cursor-not-allowed aria-disabled:opacity-50 aria-disabled:cursor-not-allowed",
"inline-grid shrink-0 place-items-center transition touch-target aria-expanded:text-chosen disabled:opacity-50 disabled:cursor-not-allowed aria-disabled:opacity-50 aria-disabled:cursor-not-allowed",
ICON_VARIANTS[variant],
round || variant === "media" ? "rounded-full" : "rounded-lg",
pressed !== undefined && ICON_PRESSED,
+2 -1
View File
@@ -3,4 +3,5 @@
/** A choice chosen on the chrome (a settings tab, a date style): white on a veil, underlined in the
* chosen mark — `media-marks` points it at the media amber, as a light theme's accent is dark there. */
export const MEDIA_CHOSEN = "bg-white/15 text-white shadow-[inset_0_-2px_0_var(--color-chosen)]";
export const MEDIA_CHOSEN =
"bg-white/15 text-white shadow-[inset_0_-2px_0_var(--chosen,var(--accent))]";
+9
View File
@@ -93,6 +93,15 @@ describe("the media colours", () => {
// The tick of a chosen row reads the chosen colour, so a scope can name its own.
const check = readFileSync(new URL("../components/ui/MenuCheck.tsx", import.meta.url), "utf8");
expect(check).toMatch(/\btext-chosen\b/);
// So does an icon button that is on or has its menu open (the player's track menu button).
const buttons = readFileSync(
new URL("../components/ui/buttonStyles.ts", import.meta.url),
"utf8",
);
expect(buttons).toMatch(/aria-pressed:text-chosen/);
expect(buttons).toMatch(/aria-expanded:text-chosen/);
// (A pressed FILLED button keeps the accent fill under its own `text-accent-fg`.)
expect(buttons).not.toMatch(/aria-(pressed|expanded):text-accent(?!-)/);
// The player's own components are chrome throughout: nothing chosen there is the accent's text
// (a light theme's accent is dark on it — Slate 2.84:1).
for (const f of ["PlexPlayer.tsx", "PlexPlayerSettings.tsx", "UpNextCard.tsx"]) {