fix(feed): size the badge column against the library's worst case
A 60-video sample topped out at 2:00:58, so the column was 8px short of an 11-hour stream that is also saved — and the library holds one. That row would wrap to two lines in the one layout that promises single ones. The stacked layouts' textBlock now builds after the one-line row returns, since only they use it.
This commit is contained in:
@@ -329,13 +329,6 @@ function VideoCard({
|
|||||||
{video.published_at && <>{" · "}{formatDate(video.published_at, i18n.language)}</>}
|
{video.published_at && <>{" · "}{formatDate(video.published_at, i18n.language)}</>}
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
const meta = (
|
|
||||||
<>
|
|
||||||
{views}
|
|
||||||
{views && " · "}
|
|
||||||
{published}
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
// Show the full title in a native tooltip only when it's actually cut off — vertically where it
|
// Show the full title in a native tooltip only when it's actually cut off — vertically where it
|
||||||
// clamps to 2 lines, horizontally where the one-line row truncates it.
|
// clamps to 2 lines, horizontally where the one-line row truncates it.
|
||||||
const titleRef = useRef<HTMLAnchorElement>(null);
|
const titleRef = useRef<HTMLAnchorElement>(null);
|
||||||
@@ -377,16 +370,6 @@ function VideoCard({
|
|||||||
{video.channel_title}
|
{video.channel_title}
|
||||||
</button>
|
</button>
|
||||||
);
|
);
|
||||||
// Title + channel + meta stacked — the card and the thumbnailed row share this; only their
|
|
||||||
// wrapper / Actions placement differs. Safe to reuse: exactly one branch below renders.
|
|
||||||
const textBlock = (
|
|
||||||
<>
|
|
||||||
{title}
|
|
||||||
<div className="mt-0.5">{channelButton}</div>
|
|
||||||
<div className="text-xs text-muted mt-0.5">{meta}</div>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
|
|
||||||
const actions = (
|
const actions = (
|
||||||
<Actions
|
<Actions
|
||||||
video={video}
|
video={video}
|
||||||
@@ -407,12 +390,13 @@ function VideoCard({
|
|||||||
// A table without being one. Every column is a FIXED width, which is what makes the same datum
|
// A table without being one. Every column is a FIXED width, which is what makes the same datum
|
||||||
// land on the same vertical line in every row — a CSS grid can't do it for us, because
|
// land on the same vertical line in every row — a CSS grid can't do it for us, because
|
||||||
// VirtualFeed renders each row as its own subtree, so there is no shared grid to align to.
|
// VirtualFeed renders each row as its own subtree, so there is no shared grid to align to.
|
||||||
// Widths come from measuring the real data at text-sm (60-video sample), then taking the next
|
// Widths come from measuring the real data at text-sm, then taking the next Tailwind step up:
|
||||||
// Tailwind step up: channel names run to 342px at worst (they truncate — most are far shorter),
|
// channel names run to 342px at worst (they truncate — most are far shorter), views peak at 79,
|
||||||
// views peak at 79, duration + a live chip + the saved pip at ~118, "11 months ago ·
|
// the badges at 130 ("11:46:50" + a STREAM chip + the saved pip — the library really does hold
|
||||||
// Feb 28, 2026" at 181. The actions are 188 (6 x 28 + 5 x 4). Re-measure before trimming any
|
// 11-hour streams), "11 months ago · Feb 28, 2026" at 181, and the actions 188 (6 x 28 + 5 x 4).
|
||||||
// of them — badges first looked like 102 until the chips' own margins were counted, and the
|
// Size against the WORST case in the library, not a page of it: badges were sized 102 off a
|
||||||
// column silently wrapped to a second line.
|
// 60-video sample, which missed both the chips' own margins and any stream over two hours, and
|
||||||
|
// the column silently wrapped to a second line — in the one layout that promises single lines.
|
||||||
// The title takes what's left, which is constant per container width — so it aligns too.
|
// The title takes what's left, which is constant per container width — so it aligns too.
|
||||||
if (noThumb) {
|
if (noThumb) {
|
||||||
const pct = resumePct(video);
|
const pct = resumePct(video);
|
||||||
@@ -423,7 +407,7 @@ function VideoCard({
|
|||||||
<div className="shrink-0 w-48">{actions}</div>
|
<div className="shrink-0 w-48">{actions}</div>
|
||||||
<div className="min-w-0 flex-1">{title}</div>
|
<div className="min-w-0 flex-1">{title}</div>
|
||||||
<div className="shrink-0 w-40">{channelButton}</div>
|
<div className="shrink-0 w-40">{channelButton}</div>
|
||||||
<div className="shrink-0 w-32 text-sm text-muted">
|
<div className="shrink-0 w-36 text-sm text-muted">
|
||||||
<InlineBadges video={video} />
|
<InlineBadges video={video} />
|
||||||
</div>
|
</div>
|
||||||
<div className="shrink-0 w-24 text-sm text-muted truncate">{views}</div>
|
<div className="shrink-0 w-24 text-sm text-muted truncate">{views}</div>
|
||||||
@@ -437,6 +421,24 @@ function VideoCard({
|
|||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Everything below is a STACKED layout — the card and the thumbnailed row. Only they run the
|
||||||
|
// data together as one sentence; the one-line row above gives each datum its own column and has
|
||||||
|
// already returned, so building this for it would be waste dressed up as shared code.
|
||||||
|
const textBlock = (
|
||||||
|
<>
|
||||||
|
{title}
|
||||||
|
<div className="mt-0.5">{channelButton}</div>
|
||||||
|
<div className="text-xs text-muted mt-0.5">
|
||||||
|
{views}
|
||||||
|
{views && " · "}
|
||||||
|
{published}
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
|
||||||
|
if (spec.family === "row") {
|
||||||
return (
|
return (
|
||||||
<div className={clsx(rowShell, "flex gap-3 p-2", watched && "opacity-55")}>
|
<div className={clsx(rowShell, "flex gap-3 p-2", watched && "opacity-55")}>
|
||||||
<Thumb
|
<Thumb
|
||||||
|
|||||||
Reference in New Issue
Block a user