AR
Ananya Rao2h · 5th Block
Lost: grey tabby near the 5th Block park. Answers to Pepper, blue collar with a bell.
249Share
Animated screens
A shimmering placeholder that resolves into a real community feed, post by post.
shimmer skeleton · staggered fade-in · photo post · event post
Lost: grey tabby near the 5th Block park. Answers to Pepper, blue collar with a bell.
Easy 10K loop from Agara Lake. All paces welcome, filter coffee after.
// Skeleton → content: a shimmering placeholder while the feed loads, then posts fade up one after another.
// Mobile-first page on the Taste preset (animate-shimmer and animate-rise come with it); maps to React Native via NativeWind.
"use client";
import { useEffect, useState } from "react";
import { Bell, CalendarDays, Heart, MessageCircle, Search, Share } from "lucide-react";
type Post = { id: string; name: string; initials: string; meta: string; body: string; photo?: boolean; event?: string; likes: number; comments: number };
const demo: Post[] = [
{ id: "1", name: "Ananya Rao", initials: "AR", meta: "2h · 5th Block", body: "Lost: grey tabby near the 5th Block park. Answers to Pepper, blue collar with a bell.", photo: true, likes: 24, comments: 9 },
{ id: "2", name: "HSR Runners", initials: "HR", meta: "5h · Club", event: "Sun 19 Oct · 6:00 AM", body: "Easy 10K loop from Agara Lake. All paces welcome, filter coffee after.", likes: 41, comments: 12 },
];
// Swap for your data call.
const loadFeed = () => new Promise<Post[]>((done) => setTimeout(() => done(demo), 1200));
const bone = "block rounded-md bg-[linear-gradient(90deg,var(--tst-surface-2)_36%,var(--tst-surface)_50%,var(--tst-surface-2)_64%)] bg-[length:300%_100%] animate-shimmer motion-reduce:animate-none";
export default function Feed() {
const [posts, setPosts] = useState<Post[] | null>(null);
useEffect(() => {
let live = true;
loadFeed().then((p) => live && setPosts(p));
return () => {
live = false;
};
}, []);
let n = 0; // stagger index across every block that fades in
const rise = () => ({ className: "animate-rise motion-reduce:animate-none", style: { animationDelay: n++ * 110 + "ms" } });
return (
<main className="mx-auto flex min-h-dvh max-w-md flex-col bg-bg font-sans text-fg">
<header className="flex h-14 items-center gap-1 pl-4 pr-1.5">
<div className="min-w-0 flex-1">
<h1 className="text-lg font-semibold leading-tight tracking-tight">Commons</h1>
<p className="text-xs text-fg-muted">Koramangala · 5th Block</p>
</div>
<button type="button" aria-label="Search" className="grid size-10 place-items-center rounded-full hover:bg-surface-2"><Search className="size-5" /></button>
<button type="button" aria-label="Notifications" className="grid size-10 place-items-center rounded-full hover:bg-surface-2"><Bell className="size-5" /></button>
</header>
<section aria-busy={!posts} aria-live="polite" className="flex-1">
{!posts
? [0, 1].map((k) => (
<div key={k} aria-hidden className="grid gap-2.5 border-b border-border px-4 py-3.5">
<div className="flex items-center gap-2.5">
<span className={bone + " size-8 rounded-full"} />
<div className="grid flex-1 gap-1.5"><span className={bone + " h-[11px] w-[46%]"} /><span className={bone + " h-[9px] w-[28%]"} /></div>
</div>
<span className={bone + " h-[11px] w-full"} />
<span className={bone + " h-[11px] w-[72%]"} />
{k === 0 && <span className={bone + " h-[116px] rounded-lg"} />}
<span className={bone + " h-[11px] w-[44%]"} />
</div>
))
: posts.map((p) => (
<article key={p.id} className="grid gap-2.5 border-b border-border px-4 py-3.5">
<div {...rise()}>
<div className="flex items-center gap-2.5">
<span className="grid size-8 place-items-center rounded-full bg-primary-soft text-[11.5px] font-semibold text-primary-soft-fg">{p.initials}</span>
<div><p className="text-[13.5px] font-semibold leading-[18px]">{p.name}</p><p className="text-xs text-fg-muted">{p.meta}</p></div>
</div>
</div>
{p.event && <p {...rise()}><span className="inline-flex h-6 items-center gap-1.5 rounded-full bg-info-soft px-2 text-[11.5px] font-medium text-info-fg"><CalendarDays className="size-3.5" />{p.event}</span></p>}
<p {...rise()}><span className="text-[13.5px] leading-[19px]">{p.body}</span></p>
{p.photo && <div {...rise()}><div className="h-[116px] rounded-lg bg-gradient-to-br from-neutral-300 to-neutral-600" /></div>}
<div {...rise()}>
<div className="flex gap-4 text-[12.5px] text-fg-muted">
<button type="button" className="inline-flex items-center gap-1.5"><Heart className="size-4" />{p.likes}</button>
<button type="button" className="inline-flex items-center gap-1.5"><MessageCircle className="size-4" />{p.comments}</button>
<button type="button" className="inline-flex items-center gap-1.5"><Share className="size-4" />Share</button>
</div>
</div>
</article>
))}
</section>
</main>
);
}
The code uses the Taste Tailwind preset. Make yours on the Taste page with Export my system.