fix(player): the channel line wraps instead of squeezing the meta into a column

On a narrow card (a tablet, a zoomed window) the unwrapped row of name, meta and
actions left the meta a one-word column. The row now wraps: the meta keeps at
least 12rem, and the actions move down together.
This commit is contained in:
2026-10-08 02:25:58 +02:00
parent 49a9319f84
commit ca949d22de
+53 -50
View File
@@ -145,6 +145,9 @@ const metaSep = (
</span> </span>
); );
const META_OWN_LINE = "w-full [&>span:first-child>[data-meta-sep]]:hidden"; const META_OWN_LINE = "w-full [&>span:first-child>[data-meta-sep]]:hidden";
// Beside the channel name the meta takes what is left of the line, but never less than this: under it
// the line wraps and the meta, or the actions after it, move down instead of squeezing.
const META_MIN = "min-w-48 flex-1";
export default function PlayerModal({ export default function PlayerModal({
video, video,
@@ -961,10 +964,13 @@ export default function PlayerModal({
</Button> </Button>
</div> </div>
{/* Channel + meta on one line, with the watched toggle pushed to the right. {/* Channel + meta on one line, with the actions pushed to the right.
When navigated to a linked video we only have its author (from the player), When navigated to a linked video we only have its author (from the player),
so we show that as plain text and hide feed-video-specific bits. */} so we show that as plain text and hide feed-video-specific bits.
<div className={`flex items-center gap-3 mt-3 ${wide ? "flex-wrap" : ""}`}> It wraps: the meta never gets narrower than META_MIN, and the actions move down as one
group. Unwrapped, a narrow card (a tablet, a zoomed window) squeezed the meta into a
one-word column between the name and the buttons. */}
<div className="flex flex-wrap items-center gap-x-3 gap-y-2 mt-3">
{!navigated && ( {!navigated && (
<Avatar <Avatar
src={active.channel_thumbnail} src={active.channel_thumbnail}
@@ -1012,7 +1018,7 @@ export default function PlayerModal({
)} )}
{!navigated ? ( {!navigated ? (
<div <div
className={`flex flex-wrap items-center gap-x-2 gap-y-0.5 text-sm text-muted min-w-0 ${wide ? META_OWN_LINE : ""}`} className={`flex flex-wrap items-center gap-x-2 gap-y-0.5 text-sm text-muted ${wide ? META_OWN_LINE : META_MIN}`}
> >
{active.view_count != null && ( {active.view_count != null && (
<span> <span>
@@ -1043,7 +1049,7 @@ export default function PlayerModal({
) : ( ) : (
// Linked video's stats come from the (already-fetched) video detail. // Linked video's stats come from the (already-fetched) video detail.
<div <div
className={`flex flex-wrap items-center gap-x-2 gap-y-0.5 text-sm text-muted min-w-0 ${wide ? META_OWN_LINE : ""}`} className={`flex flex-wrap items-center gap-x-2 gap-y-0.5 text-sm text-muted ${wide ? META_OWN_LINE : META_MIN}`}
> >
{detail.data?.view_count != null && ( {detail.data?.view_count != null && (
<span> <span>
@@ -1076,51 +1082,48 @@ export default function PlayerModal({
own (which hides our chrome). Mirrors the F key (maximise, a CSS big view); from there own (which hides our chrome). Mirrors the F key (maximise, a CSS big view); from there
the docked bar offers OUR true fullscreen. */} the docked bar offers OUR true fullscreen. */}
{!navigated && ( {!navigated && (
<IconButton // Narrow: the actions end the channel line. Wide: their own line in the panel.
variant="outline" <div className={`flex items-center gap-3 ${wide ? "flex-wrap" : "ml-auto"}`}>
size="md" <IconButton
onClick={toggleFullscreen} variant="outline"
label={t("player.fullscreen")} size="md"
title={`${t("player.fullscreen")} · F`} onClick={toggleFullscreen}
// Narrow: the actions end the channel line. Wide: their own line in the panel. label={t("player.fullscreen")}
className={wide ? undefined : "ml-auto"} title={`${t("player.fullscreen")} · F`}
> >
<Maximize /> <Maximize />
</IconButton> </IconButton>
)} <AddToPlaylist videoId={active.id} variant="outline" size="md" />
{!navigated && <AddToPlaylist videoId={active.id} variant="outline" size="md" />} <DownloadButton
{!navigated && ( videoId={active.id}
<DownloadButton title={active.title}
videoId={active.id} variant="outline"
title={active.title} size="md"
variant="outline" />
size="md" {onRemoveFromList && (
/> <IconButton
)} variant="outline-danger"
{!navigated && onRemoveFromList && ( size="md"
<IconButton onClick={() => void removeFromList()}
variant="outline-danger" // Named with its object, like the list's menu row: a bare "Remove" among Fullscreen /
size="md" // Download / Mark watched reads as "delete this video".
onClick={() => void removeFromList()} label={t("playlists.removeItemOf", { title: active.title })}
// Named with its object, like the list's menu row: a bare "Remove" among Fullscreen / title={t("playlists.removeItem")}
// Download / Mark watched reads as "delete this video". >
label={t("playlists.removeItemOf", { title: active.title })} <Trash2 />
title={t("playlists.removeItem")} </IconButton>
> )}
<Trash2 /> <Button
</IconButton> variant="outline"
)} size="sm"
{!navigated && ( pressed={watched}
<Button onClick={() => setWatched(!watched)}
variant="outline" title={watched ? t("player.watchedUnmark") : t("player.markWatched")}
size="sm" icon={watched ? <CheckCheck /> : <Check />}
pressed={watched} >
onClick={() => setWatched(!watched)} {watched ? t("player.watched") : t("player.markWatched")}
title={watched ? t("player.watchedUnmark") : t("player.markWatched")} </Button>
icon={watched ? <CheckCheck /> : <Check />} </div>
>
{watched ? t("player.watched") : t("player.markWatched")}
</Button>
)} )}
</div> </div>
{wide && ( {wide && (