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.
This commit is contained in:
@@ -0,0 +1,35 @@
|
||||
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} />;
|
||||
}
|
||||
Reference in New Issue
Block a user