feat(feed): pick the feed's view from its own toolbar
The view mode moves out of Settings into a switcher next to sort, on the feed and channel pages alike. It leaves the Settings prefs draft for its own eager save: a toolbar click must not mark that page dirty and trip its leave-page guard. lib/feedView.ts owns the vocabulary and maps the old grid/list prefs forward on read, so nobody's saved choice is lost.
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
import { useEffect, useLayoutEffect, useMemo, useRef, useState } from "react";
|
||||
import { useVirtualizer } from "@tanstack/react-virtual";
|
||||
import type { Video } from "../lib/api";
|
||||
import type { FeedView } from "../lib/feedView";
|
||||
import VideoCard from "./VideoCard";
|
||||
|
||||
// Grid column sizing mirrors the CSS the feed used before virtualization
|
||||
@@ -37,7 +38,7 @@ function getScrollParent(node: HTMLElement | null): HTMLElement {
|
||||
|
||||
export interface VirtualFeedProps {
|
||||
items: Video[];
|
||||
view: "grid" | "list";
|
||||
view: FeedView;
|
||||
onState: (id: string, status: string) => void;
|
||||
onToggleSave: (id: string, saved: boolean) => void;
|
||||
onOpenChannel: (channelId: string, channelName: string) => void;
|
||||
@@ -62,7 +63,7 @@ export default function VirtualFeed({
|
||||
}: VirtualFeedProps) {
|
||||
const listRef = useRef<HTMLDivElement>(null);
|
||||
const [scrollEl, setScrollEl] = useState<HTMLElement | null>(null);
|
||||
const [colCount, setColCount] = useState(view === "list" ? 1 : 4);
|
||||
const [colCount, setColCount] = useState(view === "rows" ? 1 : 4);
|
||||
// Distance from the scroll element's content top to where this list starts (the
|
||||
// feed toolbar sits above it inside the same scroll area).
|
||||
const [scrollMargin, setScrollMargin] = useState(0);
|
||||
@@ -84,7 +85,7 @@ export default function VirtualFeed({
|
||||
setScrollMargin(m);
|
||||
}
|
||||
setColCount(
|
||||
view === "list"
|
||||
view === "rows"
|
||||
? 1
|
||||
: Math.max(1, Math.floor((node.clientWidth + GRID_GAP) / (GRID_MIN_COL + GRID_GAP)))
|
||||
);
|
||||
@@ -101,9 +102,9 @@ export default function VirtualFeed({
|
||||
const virtualizer = useVirtualizer({
|
||||
count: rows.length,
|
||||
getScrollElement: () => scrollEl,
|
||||
estimateSize: () => (view === "grid" ? GRID_ROW_EST : LIST_ROW_EST),
|
||||
estimateSize: () => (view === "cards" ? GRID_ROW_EST : LIST_ROW_EST),
|
||||
overscan: 4,
|
||||
gap: view === "grid" ? GRID_GAP : LIST_GAP,
|
||||
gap: view === "cards" ? GRID_GAP : LIST_GAP,
|
||||
scrollMargin,
|
||||
});
|
||||
|
||||
@@ -137,7 +138,7 @@ export default function VirtualFeed({
|
||||
transform: `translateY(${vr.start - virtualizer.options.scrollMargin}px)`,
|
||||
}}
|
||||
>
|
||||
{view === "grid" ? (
|
||||
{view === "cards" ? (
|
||||
<div
|
||||
className="grid gap-4"
|
||||
style={{ gridTemplateColumns: `repeat(${colCount}, minmax(0, 1fr))` }}
|
||||
@@ -146,7 +147,7 @@ export default function VirtualFeed({
|
||||
<VideoCard
|
||||
key={v.id}
|
||||
video={v}
|
||||
view="grid"
|
||||
view={view}
|
||||
onState={onState}
|
||||
onToggleSave={onToggleSave}
|
||||
onOpenChannel={onOpenChannel}
|
||||
@@ -159,7 +160,7 @@ export default function VirtualFeed({
|
||||
<div className="max-w-4xl mx-auto pb-1">
|
||||
<VideoCard
|
||||
video={row[0]}
|
||||
view="list"
|
||||
view={view}
|
||||
onState={onState}
|
||||
onToggleSave={onToggleSave}
|
||||
onOpenChannel={onOpenChannel}
|
||||
|
||||
Reference in New Issue
Block a user