fix(feed): only tooltip the channel name where it's actually cut off
I set the attribute unconditionally because the one-line row truncates hard, and never looked at the other four views — where the name fits and the tooltip just repeated what was already on screen. It now shares the title's clamped check.
This commit is contained in:
@@ -164,6 +164,9 @@ function RowMetaCells({ video }: { video: Video }) {
|
||||
? { Icon: VideoIcon, cls: "text-accent", label: t("card.stream") }
|
||||
: null;
|
||||
const StatusIcon = status?.Icon;
|
||||
// 4rem + 1rem + 1rem + two 4px gaps = 104px, which the one-line row's width tally counts on —
|
||||
// widen a slot and the title column silently pays for it. rem, not px, so the whole cell tracks
|
||||
// the text-size setting the same way its contents do.
|
||||
return (
|
||||
<div className="shrink-0 grid grid-cols-[4rem_1rem_1rem] gap-1 items-center text-sm text-muted">
|
||||
{/* Right-aligned: a duration is a number, so the digits line up on their own edge (30px for
|
||||
@@ -356,20 +359,27 @@ function VideoCard({
|
||||
{video.published_at && <>{" · "}{formatDate(video.published_at, i18n.language)}</>}
|
||||
</>
|
||||
);
|
||||
// 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.
|
||||
// Show the full text in a native tooltip only when it's actually cut off — vertically where the
|
||||
// title clamps to 2 lines, horizontally where a fixed column truncates it. Shared by the title
|
||||
// and the channel name: both truncate in some views and fit in others, and a tooltip that just
|
||||
// repeats what's already legible is noise.
|
||||
const titleRef = useRef<HTMLAnchorElement>(null);
|
||||
const channelRef = useRef<HTMLButtonElement>(null);
|
||||
const [titleClamped, setTitleClamped] = useState(false);
|
||||
const [channelClamped, setChannelClamped] = useState(false);
|
||||
useEffect(() => {
|
||||
const el = titleRef.current;
|
||||
if (!el) return;
|
||||
const check = () =>
|
||||
setTitleClamped(el.scrollHeight > el.clientHeight + 1 || el.scrollWidth > el.clientWidth + 1);
|
||||
const cut = (el: HTMLElement) =>
|
||||
el.scrollHeight > el.clientHeight + 1 || el.scrollWidth > el.clientWidth + 1;
|
||||
const els: [HTMLElement | null, (v: boolean) => void][] = [
|
||||
[titleRef.current, setTitleClamped],
|
||||
[channelRef.current, setChannelClamped],
|
||||
];
|
||||
const check = () => els.forEach(([el, set]) => el && set(cut(el)));
|
||||
check();
|
||||
const ro = new ResizeObserver(check);
|
||||
ro.observe(el);
|
||||
els.forEach(([el]) => el && ro.observe(el));
|
||||
return () => ro.disconnect();
|
||||
}, [video.title]);
|
||||
}, [video.title, video.channel_title]);
|
||||
const title = (
|
||||
<a
|
||||
ref={titleRef}
|
||||
@@ -388,13 +398,12 @@ function VideoCard({
|
||||
);
|
||||
const channelButton = (
|
||||
<button
|
||||
ref={channelRef}
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
onOpenChannel?.(video.channel_id, video.channel_title ?? t("card.thisChannel"));
|
||||
}}
|
||||
// The name truncates in every layout (hard in the one-line row's fixed column), so it always
|
||||
// carries the full text — unlike the title, which only earns a tooltip once it's clamped.
|
||||
title={video.channel_title ?? undefined}
|
||||
title={channelClamped ? video.channel_title ?? undefined : undefined}
|
||||
className="text-sm text-muted truncate block w-fit max-w-full text-left hover:text-fg"
|
||||
>
|
||||
{video.channel_title}
|
||||
@@ -418,8 +427,9 @@ function VideoCard({
|
||||
|
||||
if (spec.family === "row") {
|
||||
// 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
|
||||
// VirtualFeed renders each row as its own subtree, so there is no shared grid to align to.
|
||||
// land on the same vertical line in every row. A grid can't align ACROSS rows here — VirtualFeed
|
||||
// renders each as its own subtree, so there's no shared parent to be the grid — but a grid is
|
||||
// still right WITHIN a cell (see RowMetaCells): fixed slots, so they line up across rows too.
|
||||
// Widths are measured, not chosen by eye — against the worst case in the LIBRARY and in BOTH
|
||||
// languages, since both have caught me out here:
|
||||
// actions 188 (6 x 28 + 5 x 4) · meta cells 104 (see RowMetaCells) · views 139 ("101.7K
|
||||
|
||||
Reference in New Issue
Block a user