feat(player): the side panel's list controls and watch toggle are glyphs on one line
The queue row (previous, count, next, auto-advance, loop) wrapped onto three lines in the narrow side panel; glyph-only, it fits one. Mark watched joins the action icons as a toggle (check / double check). Every control keeps its full name and shortcut as its tooltip and accessible name; the docked bar keeps its labels. The related switch moves up to the toolbar rung (12px), and its second side reads just "Trending".
This commit is contained in:
@@ -4,6 +4,7 @@ import { Repeat, Repeat1, Shuffle, SkipBack, SkipForward } from "lucide-react";
|
||||
import type { VideoDetail } from "../../lib/api";
|
||||
import type { AutoMode, LoopMode } from "../../lib/playerModes";
|
||||
import { Button } from "../../components/ui/Button";
|
||||
import { IconButton } from "../../components/ui/IconButton";
|
||||
import { StateMessage } from "../../components/ui/QueryState";
|
||||
import { ClampedText } from "../../components/ui/ClampedText";
|
||||
import { renderDescription } from "./descriptionLinks";
|
||||
@@ -14,9 +15,11 @@ import { renderDescription } from "./descriptionLinks";
|
||||
|
||||
/** The queue transport cluster — prev / counter / next, then the auto-advance and loop mode toggles.
|
||||
* Rendered in two places with identical logic: the modal's queue bar (`variant:"modal"`, normal glass
|
||||
* theme, labels always shown) and the big-view docked control bar (`variant:"docked"`, white-on-dark,
|
||||
* labels hidden until a breakpoint). Only the theme + label-visibility differ; the behaviour is one,
|
||||
* so a mode/icon change lands once. The wrapper (fragment vs. bordered row) stays at the call site. */
|
||||
* theme, glyphs only so the whole cluster fits one line of the side panel) and the big-view docked
|
||||
* control bar (`variant:"docked"`, white-on-dark, labels shown from a breakpoint up). Only the theme +
|
||||
* label visibility differ; the behaviour is one, so a mode/icon change lands once. Every control keeps
|
||||
* its full name (with the shortcut) as its tooltip and accessible name, so a glyph-only one still says
|
||||
* what it does and which mode it is in. The wrapper (fragment vs. bordered row) stays at the call site. */
|
||||
export function PlayerTransportControls({
|
||||
variant,
|
||||
index,
|
||||
@@ -40,16 +43,61 @@ export function PlayerTransportControls({
|
||||
}) {
|
||||
const { t } = useTranslation();
|
||||
const docked = variant === "docked";
|
||||
// The docked bar sits on the video (always dark): the overlay variant; the modal's is a page surface.
|
||||
const chrome = docked ? "overlay" : "ghost";
|
||||
const navLabel = docked ? "hidden sm:inline" : "";
|
||||
const counter = docked ? "tabular-nums text-white/70" : "text-muted tabular-nums";
|
||||
const divider = docked ? "h-4 w-px bg-white/20" : "w-px h-4 bg-border";
|
||||
const toggleLabel = docked ? "hidden md:inline" : "";
|
||||
const autoName = `${t("player.autoAdvance.label")}: ${t(`player.autoAdvance.${autoMode}`)}`;
|
||||
const loopName = `${t("player.loop.label")}: ${t(`player.loop.${loopMode}`)}`;
|
||||
const autoIcon =
|
||||
autoMode === "prev" ? <SkipBack /> : autoMode === "random" ? <Shuffle /> : <SkipForward />;
|
||||
const loopIcon = loopMode === "one" ? <Repeat1 /> : <Repeat />;
|
||||
const count = (
|
||||
<span className={counter}>
|
||||
{index + 1} / {queueLength}
|
||||
</span>
|
||||
);
|
||||
if (!docked)
|
||||
return (
|
||||
<>
|
||||
<IconButton
|
||||
onClick={onPrev}
|
||||
disabled={index === 0}
|
||||
label={t("player.previous")}
|
||||
title={`${t("player.previous")} · Shift+←`}
|
||||
>
|
||||
<SkipBack />
|
||||
</IconButton>
|
||||
{count}
|
||||
<IconButton
|
||||
onClick={onNext}
|
||||
disabled={index === queueLength - 1}
|
||||
label={t("player.next")}
|
||||
title={`${t("player.next")} · Shift+→`}
|
||||
>
|
||||
<SkipForward />
|
||||
</IconButton>
|
||||
<span className={divider} />
|
||||
<IconButton
|
||||
pressed={autoMode !== "off"}
|
||||
onClick={onCycleAuto}
|
||||
label={autoName}
|
||||
title={`${autoName} · ${t("player.autoAdvance.hint")}`}
|
||||
>
|
||||
{autoIcon}
|
||||
</IconButton>
|
||||
<IconButton
|
||||
pressed={loopMode !== "off"}
|
||||
onClick={onCycleLoop}
|
||||
label={loopName}
|
||||
title={`${loopName} · ${t("player.loop.hint")}`}
|
||||
>
|
||||
{loopIcon}
|
||||
</IconButton>
|
||||
</>
|
||||
);
|
||||
return (
|
||||
<>
|
||||
<Button
|
||||
variant={chrome}
|
||||
variant="overlay"
|
||||
size="sm"
|
||||
icon={<SkipBack />}
|
||||
onClick={onPrev}
|
||||
@@ -57,51 +105,43 @@ export function PlayerTransportControls({
|
||||
label={t("player.previous")}
|
||||
title={`${t("player.previous")} · Shift+←`}
|
||||
>
|
||||
<span className={navLabel}>{t("player.previous")}</span>
|
||||
<span className="hidden sm:inline">{t("player.previous")}</span>
|
||||
</Button>
|
||||
<span className={counter}>
|
||||
{index + 1} / {queueLength}
|
||||
</span>
|
||||
{count}
|
||||
<Button
|
||||
variant={chrome}
|
||||
variant="overlay"
|
||||
size="sm"
|
||||
onClick={onNext}
|
||||
disabled={index === queueLength - 1}
|
||||
label={t("player.next")}
|
||||
title={`${t("player.next")} · Shift+→`}
|
||||
>
|
||||
<span className={navLabel}>{t("player.next")}</span>
|
||||
<span className="hidden sm:inline">{t("player.next")}</span>
|
||||
<SkipForward />
|
||||
</Button>
|
||||
<span className={divider} />
|
||||
{/* Persistent playback settings — cycle on click, saved to your account. */}
|
||||
<Button
|
||||
variant={chrome}
|
||||
variant="overlay"
|
||||
size="sm"
|
||||
pressed={autoMode !== "off"}
|
||||
icon={
|
||||
autoMode === "prev" ? <SkipBack /> : autoMode === "random" ? <Shuffle /> : <SkipForward />
|
||||
}
|
||||
icon={autoIcon}
|
||||
onClick={onCycleAuto}
|
||||
label={`${t("player.autoAdvance.label")}: ${t(`player.autoAdvance.${autoMode}`)}`}
|
||||
label={autoName}
|
||||
title={t("player.autoAdvance.hint")}
|
||||
>
|
||||
<span className={toggleLabel}>
|
||||
{t("player.autoAdvance.label")}: {t(`player.autoAdvance.${autoMode}`)}
|
||||
</span>
|
||||
<span className="hidden md:inline">{autoName}</span>
|
||||
</Button>
|
||||
<Button
|
||||
variant={chrome}
|
||||
variant="overlay"
|
||||
size="sm"
|
||||
pressed={loopMode !== "off"}
|
||||
icon={loopMode === "one" ? <Repeat1 /> : <Repeat />}
|
||||
icon={loopIcon}
|
||||
onClick={onCycleLoop}
|
||||
label={`${t("player.loop.label")}: ${t(`player.loop.${loopMode}`)}`}
|
||||
label={loopName}
|
||||
title={t("player.loop.hint")}
|
||||
>
|
||||
<span className={toggleLabel}>
|
||||
{t("player.loop.label")}: {t(`player.loop.${loopMode}`)}
|
||||
</span>
|
||||
<span className="hidden md:inline">{loopName}</span>
|
||||
</Button>
|
||||
</>
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user