A show card's picture opened the show page everywhere but Home, whose shelves carried the
resolved `next`. Now every Plex picture plays: a show asks `GET /api/plex/show/{id}/next`
(the in-progress episode, else the first unwatched, else S1·E1 — the rule Home and the show
page's Resume follow) on the click, fresh each time. A started show shows Continue + Restart;
its title, an ⓘ and the menu's "Open show" row open its page; one with no episodes opens there.
The question is asked per show, never carried on every card: three probes a show would cost a
grid page up to 180 queries. The hover preview asks it once it opens and names the episode
("Next: S6·E16 — Title"). Related shows on a show page play the same way.
51 lines
2.1 KiB
TypeScript
51 lines
2.1 KiB
TypeScript
import type { ReactNode } from "react";
|
||
import { PopoverSurface } from "./AnchoredPopover";
|
||
import type { useMediaPreview } from "./useMediaPreview";
|
||
import { SURFACE } from "./surfaces";
|
||
|
||
// A media card's preview (docs/DESIGN.md "Media card" — the owner's pick C, 2026-09-26):
|
||
// rest the mouse on a card for a moment and a panel beside it shows what the card has no room for —
|
||
// the whole title, the facts, the synopsis in lines short enough to read. It replaced the synopsis as
|
||
// the picture's native tooltip, which fired anywhere on the art, ran one long line, and shared its
|
||
// mechanism with the cut title's own tooltip.
|
||
//
|
||
// Mouse only (`pointerOnly`): a keyboard walk down a grid would pop a panel at every stop, and on
|
||
// touch a tap is the card's play. The same facts are one row away there ("…" → Media info). Passive
|
||
// (`passthrough`), so the panel lying over the next card never eats the click aimed at it; any press
|
||
// on the card closes it, so it never sits over a menu the card just opened.
|
||
|
||
export function MediaPreview({
|
||
preview,
|
||
title,
|
||
facts,
|
||
note,
|
||
summary,
|
||
}: {
|
||
preview: ReturnType<typeof useMediaPreview>;
|
||
title: string;
|
||
/** One line under the title: year, length, score. */
|
||
facts?: ReactNode;
|
||
/** A line of its own under the facts (a show's "Next: S6·E16 — Title"). */
|
||
note?: ReactNode;
|
||
summary?: string | null;
|
||
}) {
|
||
return (
|
||
<PopoverSurface
|
||
popover={preview.popover}
|
||
passthrough
|
||
// 18rem at text-xs: ~50 characters a line, inside the 45–75 a reader follows without losing
|
||
// the next line's start.
|
||
className={`${SURFACE.popover} w-72 px-3.5 py-3 text-left normal-case tracking-normal animate-[fadeIn_0.12s_ease]`}
|
||
>
|
||
<p className="text-sm font-semibold leading-snug text-fg">{title}</p>
|
||
{facts && <div className="mt-0.5 text-xs text-muted">{facts}</div>}
|
||
{note && <p className="mt-1 text-xs font-medium text-fg">{note}</p>}
|
||
{summary && (
|
||
<p className="mt-2 line-clamp-8 whitespace-pre-line text-xs leading-relaxed text-fg">
|
||
{summary}
|
||
</p>
|
||
)}
|
||
</PopoverSurface>
|
||
);
|
||
}
|