feat(feed): add the small-card, compact-row and tile views
Tiles answer the row view's wasted space: the actions move under the meta into vertical slack the thumbnail already reserves, which frees ~168px of width, so the block survives in a narrow column and the list can go multi-column. Rows stay single-column on purpose — reading down one edge is why that view exists. The column count now comes from a per-view minimum width, so every multi-column view reflows on the mechanism the card grid already used.
This commit is contained in:
@@ -1,12 +1,49 @@
|
||||
// The feed's view vocabulary. Lifted out of Feed (like feedSort.ts) because it has three
|
||||
// consumers that can't share module-private state: App owns the pref, the toolbar's
|
||||
// ViewSwitcher offers it, and VirtualFeed/VideoCard render it.
|
||||
export type FeedView = "cards" | "rows";
|
||||
export type FeedView = "cards" | "cardsSmall" | "rows" | "rowsCompact" | "tiles";
|
||||
|
||||
export const FEED_VIEWS: readonly FeedView[] = ["cards", "rows"] as const;
|
||||
export const FEED_VIEWS: readonly FeedView[] = [
|
||||
"cards",
|
||||
"cardsSmall",
|
||||
"rows",
|
||||
"rowsCompact",
|
||||
"tiles",
|
||||
] as const;
|
||||
|
||||
const FEED_VIEW_DEFAULT: FeedView = "cards";
|
||||
|
||||
export interface FeedViewSpec {
|
||||
/** Which shape VideoCard renders: a portrait card (thumbnail on top) or a horizontal block. */
|
||||
family: "card" | "row";
|
||||
/** Narrowest grid column in px, or null for ONE full-width column. VirtualFeed derives the
|
||||
* column count from this and the container width, so every multi-column view reflows for free. */
|
||||
minCol: number | null;
|
||||
/** card family: a tighter card. row family: no thumbnail at all. */
|
||||
compact: boolean;
|
||||
/** row family: actions sit under the meta rather than docked at the right edge. Frees ~168px of
|
||||
* width (measured), which is what lets a row block survive in a narrow tile column. */
|
||||
actionsBelow: boolean;
|
||||
}
|
||||
|
||||
// The whole matrix in one place. Adding a view here makes TS point at every switch that needs it.
|
||||
export const FEED_VIEW_SPEC: Record<FeedView, FeedViewSpec> = {
|
||||
cards: { family: "card", minCol: 260, compact: false, actionsBelow: false },
|
||||
cardsSmall: { family: "card", minCol: 180, compact: true, actionsBelow: false },
|
||||
// Single column on purpose: the point of the list is that your eye runs down one edge with the
|
||||
// titles stacked. Density must not cost that — `tiles` is the multi-column option instead.
|
||||
rows: { family: "row", minCol: null, compact: false, actionsBelow: false },
|
||||
rowsCompact: { family: "row", minCol: null, compact: true, actionsBelow: false },
|
||||
// 380 = thumbnail (160) + gap (12) + ~192 for the text + the block's own padding (16). The text
|
||||
// floor is the action row's measured 156px plus enough for a title to be worth reading.
|
||||
// Tuned by measuring the real layouts, not guessed: 3 columns need 3n+32 px, so 380 turns the
|
||||
// channel page (1208px) and the unpinned feed into 3 columns while the filter-pinned feed
|
||||
// (~955px) still gets 2. 470 was tried first and needed 956px for two — the common layout fell
|
||||
// back to ONE column and the mode looked broken; 400 then left 596px tiles whose titles ended
|
||||
// halfway, which is the very waste this mode exists to kill.
|
||||
tiles: { family: "row", minCol: 380, compact: false, actionsBelow: true },
|
||||
};
|
||||
|
||||
function isFeedView(v: unknown): v is FeedView {
|
||||
return typeof v === "string" && (FEED_VIEWS as readonly string[]).includes(v);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user