feat(feed): hover infobox on feed cards — full title, description, tags (E8·S3)
This commit is contained in:
@@ -17,6 +17,7 @@ import {
|
||||
import Avatar from "./Avatar";
|
||||
import AddToPlaylist from "./AddToPlaylist";
|
||||
import DownloadButton from "./DownloadButton";
|
||||
import VideoInfoCard from "./VideoInfoCard";
|
||||
import clsx from "clsx";
|
||||
import type { Video, VideoStatus } from "../lib/api";
|
||||
import { FEED_VIEW_SPEC, type FeedView } from "../lib/feedView";
|
||||
@@ -105,6 +106,17 @@ function Actions({
|
||||
);
|
||||
}
|
||||
|
||||
// The hover-infobox trigger skins (E8·S3). The ⓘ is hover-revealed and dropped on touch (`reveal-hover`
|
||||
// + the group opacity toggle), and lives OUTSIDE the six-button action budget — as a thumbnail-corner
|
||||
// overlay for the thumbnailed views, and inline beside the actions for the one-line row.
|
||||
const INFO_REVEAL =
|
||||
"reveal-hover opacity-0 group-hover:opacity-100 group-focus-within:opacity-100 focus-visible:opacity-100 transition-opacity";
|
||||
const INFO_OVERLAY = clsx(
|
||||
INFO_REVEAL,
|
||||
"absolute bottom-1.5 left-1.5 z-10 rounded-md bg-black/70 p-1 text-white hover:bg-black/85",
|
||||
);
|
||||
const INFO_INLINE = clsx(INFO_REVEAL, "rounded-md p-1.5 text-muted hover:bg-surface hover:text-fg");
|
||||
|
||||
// Plain left-click opens the in-app player (the experiment); Ctrl/Cmd/middle-click
|
||||
// keep the native "open youtube.com in a new tab" behavior via the underlying href.
|
||||
function openInApp(
|
||||
@@ -312,6 +324,10 @@ function Thumb({
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
{/* Hover infobox trigger (E8·S3), docked bottom-left so it clears the centered play overlay, the
|
||||
corner badges (saved / stream, top corners) and the duration (bottom-right). Revealed with the
|
||||
rest of the hover chrome, dropped on touch. */}
|
||||
<VideoInfoCard video={video} className={INFO_OVERLAY} />
|
||||
{/* Resume progress bar (sits at the very bottom, under the duration badge). */}
|
||||
{pct > 0 && (
|
||||
<div className="absolute bottom-0 left-0 right-0 h-1 bg-black/40">
|
||||
@@ -330,7 +346,7 @@ function Thumb({
|
||||
// Keep this table and the classes in step; each number is measured against the worst case in the
|
||||
// LIBRARY and in BOTH languages — see the row's own comment for where each comes from.
|
||||
const ROW_COL = {
|
||||
actions: 192, // w-48 — six buttons at 28 + five 4px gaps = 188
|
||||
actions: 224, // w-48 (six buttons: 6×28 + 5×4 = 188) + gap-1 + the 28px inline info trigger
|
||||
channel: 128, // w-32
|
||||
meta: 104, // RowMetaCells' own grid: 4rem + 1rem + 1rem + two 4px gaps
|
||||
views: 64, // w-16
|
||||
@@ -508,8 +524,13 @@ function VideoCard({
|
||||
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
|
||||
they're the shortest pointer trip from it. */}
|
||||
<div className="shrink-0 w-48">{actions}</div>
|
||||
they're the shortest pointer trip from it. This row has no thumbnail to host the info
|
||||
trigger, so it sits inline right after the actions (still outside their six-button
|
||||
budget). ROW_COL.actions already counts its width. */}
|
||||
<div className="shrink-0 flex items-center gap-1">
|
||||
<div className="w-48">{actions}</div>
|
||||
<VideoInfoCard video={video} className={INFO_INLINE} />
|
||||
</div>
|
||||
{/* 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
|
||||
from the right as the row narrows, least important first: a title with no date beats a
|
||||
|
||||
Reference in New Issue
Block a user