feat: link nav to home sections

This commit is contained in:
TerryM
2026-05-28 15:55:37 +08:00
parent 28b0ef3f9a
commit 9d977be2d2
4 changed files with 139 additions and 46 deletions

View File

@@ -1,5 +1,5 @@
import { ChevronRight } from "lucide-react";
import { Link } from "react-router-dom";
import { Link, useLocation } from "react-router-dom";
import { useEffect, useRef, useState } from "react";
import { getJSON, itemsOrEmpty, type Category } from "../../api";
import { CategoryIcon } from "../../components/CategoryIcon";
@@ -44,10 +44,13 @@ function figmaCategoryRank(category: Category): number {
export function Home() {
const { t, lang } = useI18n();
const { hash } = useLocation();
const [cats, setCats] = useState<Category[]>([]);
const [rec, setRec] = useState<PostBackedResource[]>([]);
const [latest, setLatest] = useState<PostBackedResource[]>([]);
const [latestPosts, setLatestPosts] = useState<Post[]>([]);
const [popular, setPopular] = useState<PostBackedResource[]>([]);
const [popularPosts, setPopularPosts] = useState<Post[]>([]);
const [err, setErr] = useState<string | null>(null);
const recRowRef = useRef<HTMLDivElement>(null);
const categoryRowRef = useRef<HTMLDivElement>(null);
@@ -64,8 +67,11 @@ export function Home() {
getJSON<Category[]>(`/api/categories${catQ}`),
getJSON<{ items: Post[] }>(`/api/posts/recommended${postQ}&limit=12`),
getJSON<{ items: Post[] }>(`/api/posts/latest${postQ}&limit=8`),
getJSON<{ items: Post[] }>(
`/api/posts${postQ}&sort=popular&limit=8`,
).catch((): { items: Post[] } => ({ items: [] })),
])
.then(([c, r, l]) => {
.then(([c, r, l, p]) => {
setCats(itemsOrEmpty(c));
setRec(
itemsOrEmpty(r.items).map((post) =>
@@ -79,6 +85,13 @@ export function Home() {
postToResource(post, lang, itemsOrEmpty(c)),
),
);
const popularItems = itemsOrEmpty<Post>(p.items);
setPopularPosts(popularItems);
setPopular(
popularItems.map((post) =>
postToResource(post, lang, itemsOrEmpty(c)),
),
);
})
.catch((e) => setErr(String(e)));
}, [lang]);
@@ -152,7 +165,18 @@ export function Home() {
recRowRef.current?.scrollBy({ left: dir * 280, behavior: "smooth" });
};
useEffect(() => {
if (!hash) return;
const id = hash.slice(1);
if (!id) return;
const frame = window.requestAnimationFrame(() => {
document.getElementById(id)?.scrollIntoView({ block: "start" });
});
return () => window.cancelAnimationFrame(frame);
}, [hash, cats.length, rec.length, latest.length, popular.length]);
const latestPlaceholderCount = Math.max(0, 5 - latest.length);
const popularPlaceholderCount = Math.max(0, 5 - popular.length);
const recommendedDotCount = Math.max(1, Math.min(4, rec.length || 4));
const activeRecommendedDot = Math.min(
recommendedDotCount - 1,
@@ -356,7 +380,31 @@ export function Home() {
</div>
</section>
<span id="popular" className="block scroll-mt-24" aria-hidden="true" />
<section id="popular" className="scroll-mt-24">
<div className="px-4 md:px-0">
<SectionHeader
title={t("popularSection")}
viewAllTo="/browse?sort=popular"
viewAllLabel={t("viewAll")}
/>
</div>
<div className="flex flex-col gap-3 md:hidden">
{popularPosts.slice(0, 4).map((post) => (
<MessageBubble key={post.id} post={post} />
))}
</div>
<div className="mt-7 hidden gap-3 min-[576px]:grid-cols-2 md:grid md:gap-4 lg:grid-cols-3 xl:grid-cols-5">
{popular.map((r) => (
<LatestUpdateRow key={r.id} r={r} iconKey={iconKeyForResource(r)} />
))}
{Array.from({ length: popularPlaceholderCount }).map((_, index) => (
<ComingSoonLatestUpdateRow
key={`popular-coming-soon-${index}`}
index={popular.length + index}
/>
))}
</div>
</section>
</div>
);
}