vhg6j0bp2: Add frosted top bar and progress rail to the mobile feed
This commit is contained in:
@@ -7,6 +7,8 @@ import { HexAvatar } from "./HexAvatar";
|
||||
import { PlayIcon, PostMenu } from "./PostCard";
|
||||
import { VideoPlayer } from "./VideoPlayer";
|
||||
import { FeaturedCarousel } from "./FeaturedCarousel";
|
||||
import { Logo } from "./Logo";
|
||||
import { useTheme } from "@/lib/use-theme";
|
||||
import { MobileDock } from "./MobileDock";
|
||||
import { BottomSheet } from "./BottomSheet";
|
||||
import { CommentsSection } from "./CommentsSection";
|
||||
@@ -220,6 +222,35 @@ function PostSlide({ post, index, current, p }: { post: CardPost; index: number;
|
||||
);
|
||||
}
|
||||
|
||||
/** Height of the progress rail, as a percent: the slide on screen out of the loaded slides. */
|
||||
export function railPercent(index: number, count: number): number {
|
||||
if (count <= 0) return 0;
|
||||
return Math.round((Math.min(Math.max(index, 0), count - 1) + 1) / count * 100);
|
||||
}
|
||||
|
||||
const topIcon = { viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: 1.7, strokeLinecap: "round", strokeLinejoin: "round", "aria-hidden": true } as const;
|
||||
|
||||
/** Frosted pills over the feed (spec "Top bar"): logo and wordmark left, theme toggle right. */
|
||||
function TopBar() {
|
||||
const { theme, toggleTheme } = useTheme();
|
||||
const next = theme === "dark" ? "light" : "dark";
|
||||
return (
|
||||
<div className="ph-top" data-testid="top-bar">
|
||||
<div className="ph-brand ph-glass">
|
||||
<Logo size={24} />
|
||||
<span className="type-wordmark">MINNECT</span>
|
||||
</div>
|
||||
<button type="button" className="ph-theme ph-glass" data-testid="top-theme" aria-label={`Switch to ${next} theme`} onClick={toggleTheme}>
|
||||
{theme === "dark" ? (
|
||||
<svg {...topIcon}><circle cx="12" cy="12" r="4" /><path d="M12 2v2M12 20v2M4.9 4.9l1.4 1.4M17.7 17.7l1.4 1.4M2 12h2M20 12h2M4.9 19.1l1.4-1.4M17.7 6.3l1.4-1.4" /></svg>
|
||||
) : (
|
||||
<svg {...topIcon}><path d="M20.5 14.5A8.5 8.5 0 0 1 9.5 3.5a8.5 8.5 0 1 0 11 11z" /></svg>
|
||||
)}
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function ChevronDown() {
|
||||
return (
|
||||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" aria-hidden="true">
|
||||
@@ -371,6 +402,8 @@ export function MobileFeed(p: FeedViewProps) {
|
||||
const currentPost = p.status === "ready" && currentPostIndex >= 0 ? p.posts[currentPostIndex] ?? null : null;
|
||||
const kind: CurrentSlide["kind"] = currentPost ? "post" : featuredAt >= 0 && index === featuredAt ? "featured" : "load";
|
||||
|
||||
const onMedia = !!currentPost && currentPost.media.some((m) => m.kind === "video" || m.src);
|
||||
|
||||
useEffect(() => {
|
||||
onCurrentChange?.({ index, kind, post: currentPost });
|
||||
}, [index, kind, currentPost, onCurrentChange]);
|
||||
@@ -383,12 +416,16 @@ export function MobileFeed(p: FeedViewProps) {
|
||||
}, [index]);
|
||||
|
||||
return (
|
||||
<section className="reel-feed" aria-label="Home feed">
|
||||
<section className="reel-feed" aria-label="Home feed" data-on-media={onMedia ? "true" : "false"}>
|
||||
{p.newCount > 0 && p.status === "ready" ? (
|
||||
<div className="reel-pill">
|
||||
<GrowButton label={p.newCount === 1 ? "1 new post" : `${p.newCount} new posts`} onClick={p.onShowNew} />
|
||||
</div>
|
||||
) : null}
|
||||
<TopBar />
|
||||
<div className="rail-v" data-testid="feed-rail" aria-hidden="true">
|
||||
<i style={{ height: `${railPercent(index, slideCount)}%` }} />
|
||||
</div>
|
||||
<div
|
||||
className="reel"
|
||||
data-testid="feed-reel"
|
||||
|
||||
Reference in New Issue
Block a user