Files
siftlode/frontend/src/components/VideoFollowUp.tsx
T
peter 75ae810866 feat(design): a video's one action list (C1) — the card's "…", the menu holds the rest (E28 S3e1)
- useContextMenu gains buttonProps (a row's visible "…" opens the same menu under the button; a
  second press closes it) and anchor (the element the menu came from, for a panel a row opens).
- ui/MoreButton; ActionMenu rows can run inGesture (a clipboard write inside the click, Safari).
- useVideoActions: one builder for a video's menu (play / restart, channel, watched, save, add to
  playlist, download, details, copy title / link, hide, reset, pin), composed by the feed and by
  Home's rails; the playlists and details panels open anchored beside the card (VideoFollowUp).
- The card keeps watched / save / add to playlist and the "…": on the picture on hover (the
  duration steps aside), in the rows' action column; on touch only a 44px "…" beside the title.
  Download, hide, reset and the hover info card leave the card.
- The title is selectable: a click ending a selection neither plays nor follows the link.
- DESIGN.md: how C1 is built + the log row.
2026-09-26 01:23:44 +02:00

36 lines
1.3 KiB
TypeScript

import { lazy, Suspense } from "react";
import { type Video } from "../lib/api";
import { AddToPlaylistAt } from "./AddToPlaylist";
import VideoDetailsAt from "./VideoInfoCard";
// Lazy for the same reason DownloadButton's is: the dialog and its profile editor load only when
// someone downloads, not with every feed.
const DownloadDialog = lazy(() => import("./DownloadDialog"));
/** What a video menu row opened that outlives the menu: a panel anchored where the menu came from
* (the playlists, the details) or the download dialog. */
export type VideoFollowUpState =
| { kind: "playlist" | "details"; video: Video; anchor: Element }
| { kind: "download"; video: Video };
/** The one follow-up a video list has open, mounted once per list beside its menu (see
* `useVideoActions`) rather than once per card. */
export function VideoFollowUp({
state,
onClose,
}: {
state: VideoFollowUpState;
onClose: () => void;
}) {
const { video } = state;
if (state.kind === "download")
return (
<Suspense fallback={null}>
<DownloadDialog source={video.id} title={video.title} onClose={onClose} />
</Suspense>
);
if (state.kind === "playlist")
return <AddToPlaylistAt videoId={video.id} anchor={state.anchor} onClose={onClose} />;
return <VideoDetailsAt video={video} anchor={state.anchor} onClose={onClose} />;
}