fix(feed): make the row's widths actually come from one table
The comment claimed the boxes were generated from ROW_COL. They weren't — still w-32/w-16/w-48 by hand — so a comment promised a link that didn't exist, which is worse than not claiming it. The boxes take the numbers directly now (inline, since Tailwind can't read a runtime value), and the thresholds add up in plain arithmetic rather than an IIFE whose result depended on key order.
This commit is contained in:
@@ -322,33 +322,40 @@ function Thumb({
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// The one-line row's column widths, in ONE place — the Tailwind classes below are generated from
|
// The one-line row's column widths, in ONE place: the boxes below take these directly (as inline
|
||||||
// them, so the thresholds can't drift out of step with the boxes they describe (they did when both
|
// widths — Tailwind can't accept a runtime value), and so do the drop-out thresholds. Two
|
||||||
// were written by hand). Each is measured against the worst case in the library AND in both
|
// hand-written sets would drift, and the symptom would be silent: a column appearing just before
|
||||||
// languages; see the row's own comment for where each number comes from.
|
// it actually fits. Each is measured against the worst case in the LIBRARY and in BOTH languages —
|
||||||
|
// see the row's own comment for where each number comes from.
|
||||||
const ROW_COL = {
|
const ROW_COL = {
|
||||||
actions: 192, // w-48 — six buttons at 28 + five 4px gaps = 188
|
actions: 192, // six buttons at 28 + five 4px gaps = 188
|
||||||
title: 120, // — not a column: the floor below which a title isn't worth showing
|
channel: 128,
|
||||||
channel: 128, // w-32
|
meta: 104, // RowMetaCells' own grid: 4rem + 1rem + 1rem + two 4px gaps
|
||||||
meta: 104, // — RowMetaCells' own grid: 4rem + 1rem + 1rem + two 4px gaps
|
views: 64,
|
||||||
views: 64, // w-16
|
published: 192,
|
||||||
published: 192, // w-48
|
|
||||||
} as const;
|
} as const;
|
||||||
const ROW_GAP = 12; // gap-3
|
const ROW_GAP = 12; // gap-3
|
||||||
const ROW_PAD = 26; // px-3 both sides, plus a rounding allowance
|
const ROW_PAD = 26; // px-3 both sides, plus a rounding allowance
|
||||||
|
const ROW_TITLE_MIN = 120; // not a column: the floor below which a title isn't worth showing
|
||||||
|
|
||||||
// What the row must measure before each column earns its place — cumulative from the left, so each
|
// What the row must measure before each column earns its place — cumulative, so each appears only
|
||||||
// one only appears once everything ahead of it AND a readable title already fit.
|
// once everything to its left AND a readable title already fit.
|
||||||
const ROW_FITS = (() => {
|
const ROW_BASE = ROW_PAD + ROW_COL.actions + ROW_GAP + ROW_TITLE_MIN;
|
||||||
let w = ROW_PAD + ROW_COL.actions + ROW_GAP + ROW_COL.title;
|
const ROW_FITS = {
|
||||||
const at = (px: number) => (w += ROW_GAP + px);
|
channel: ROW_BASE + ROW_GAP + ROW_COL.channel,
|
||||||
return {
|
meta: ROW_BASE + ROW_GAP + ROW_COL.channel + ROW_GAP + ROW_COL.meta,
|
||||||
channel: at(ROW_COL.channel),
|
views: ROW_BASE + ROW_GAP + ROW_COL.channel + ROW_GAP + ROW_COL.meta + ROW_GAP + ROW_COL.views,
|
||||||
meta: at(ROW_COL.meta),
|
published:
|
||||||
views: at(ROW_COL.views),
|
ROW_BASE +
|
||||||
published: at(ROW_COL.published),
|
ROW_GAP +
|
||||||
};
|
ROW_COL.channel +
|
||||||
})();
|
ROW_GAP +
|
||||||
|
ROW_COL.meta +
|
||||||
|
ROW_GAP +
|
||||||
|
ROW_COL.views +
|
||||||
|
ROW_GAP +
|
||||||
|
ROW_COL.published,
|
||||||
|
};
|
||||||
|
|
||||||
function VideoCard({
|
function VideoCard({
|
||||||
video,
|
video,
|
||||||
@@ -489,7 +496,9 @@ function VideoCard({
|
|||||||
<div className={clsx(rowShell, "flex items-center gap-3 px-3 py-2", watched && "opacity-55")}>
|
<div className={clsx(rowShell, "flex items-center gap-3 px-3 py-2", watched && "opacity-55")}>
|
||||||
{/* Actions lead: this is where the eye already is (the title is the thing you read), so
|
{/* Actions lead: this is where the eye already is (the title is the thing you read), so
|
||||||
they're the shortest pointer trip from it. */}
|
they're the shortest pointer trip from it. */}
|
||||||
<div className="shrink-0 w-48">{actions}</div>
|
<div className="shrink-0" style={{ width: ROW_COL.actions }}>
|
||||||
|
{actions}
|
||||||
|
</div>
|
||||||
{/* Everything else is shrink-0, so the title is the only thing that can give — which means
|
{/* Everything else is shrink-0, so the title is the only thing that can give — which means
|
||||||
once the fixed columns outgrow the row, it gives ALL of it and vanishes. Columns drop
|
once the fixed columns outgrow the row, it gives ALL of it and vanishes. Columns drop
|
||||||
from the right as the row narrows, least important first: a title with no date beats a
|
from the right as the row narrows, least important first: a title with no date beats a
|
||||||
@@ -497,18 +506,30 @@ function VideoCard({
|
|||||||
filter panel are doing. Until the first measure lands (width 0) show everything: the
|
filter panel are doing. Until the first measure lands (width 0) show everything: the
|
||||||
row is off-screen for that frame anyway, and guessing narrow would flash. */}
|
row is off-screen for that frame anyway, and guessing narrow would flash. */}
|
||||||
<div className="min-w-0 flex-1">{title}</div>
|
<div className="min-w-0 flex-1">{title}</div>
|
||||||
{fits(ROW_FITS.channel) && <div className="shrink-0 w-32">{channelButton}</div>}
|
{fits(ROW_FITS.channel) && (
|
||||||
|
<div className="shrink-0" style={{ width: ROW_COL.channel }}>
|
||||||
|
{channelButton}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
{fits(ROW_FITS.meta) && <RowMetaCells video={video} />}
|
{fits(ROW_FITS.meta) && <RowMetaCells video={video} />}
|
||||||
{/* Right-aligned like the duration: it's a number, so the digits share an edge. 64px
|
{/* Right-aligned like the duration: it's a number, so the digits share an edge. 64px
|
||||||
holds the widest bare form ("101.7K" at 48) in either language — dropping the word
|
holds the widest bare form ("101.7K" at 48) in either language — dropping the word
|
||||||
took this from 144 and handed the difference to the title. */}
|
took this from 144 and handed the difference to the title. */}
|
||||||
{fits(ROW_FITS.views) && (
|
{fits(ROW_FITS.views) && (
|
||||||
<div className="shrink-0 w-16 text-sm text-muted text-right tabular-nums truncate">
|
<div
|
||||||
|
className="shrink-0 text-sm text-muted text-right tabular-nums truncate"
|
||||||
|
style={{ width: ROW_COL.views }}
|
||||||
|
>
|
||||||
{viewsCell(false)}
|
{viewsCell(false)}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
{fits(ROW_FITS.published) && (
|
{fits(ROW_FITS.published) && (
|
||||||
<div className="shrink-0 w-48 text-sm text-muted truncate">{published}</div>
|
<div
|
||||||
|
className="shrink-0 text-sm text-muted truncate"
|
||||||
|
style={{ width: ROW_COL.published }}
|
||||||
|
>
|
||||||
|
{published}
|
||||||
|
</div>
|
||||||
)}
|
)}
|
||||||
{/* Thumb draws this over the image; with no image it goes on the row itself. */}
|
{/* Thumb draws this over the image; with no image it goes on the row itself. */}
|
||||||
{pct > 0 && (
|
{pct > 0 && (
|
||||||
|
|||||||
Reference in New Issue
Block a user