feat: align figma browse and category sections

This commit is contained in:
TerryM
2026-05-28 15:11:13 +08:00
parent 16f3f06431
commit e65c473369
7 changed files with 149 additions and 55 deletions

View File

@@ -1,5 +1,6 @@
import { useSearchParams } from "react-router-dom";
import { MessageStream } from "../../components/messageStream/MessageStream";
import { SectionHeader } from "../../components/SectionHeader";
import { useI18n } from "../../i18n";
export function Browse() {
@@ -7,10 +8,14 @@ export function Browse() {
const [sp] = useSearchParams();
const q = sp.get("q") || "";
return (
<section className="space-y-3">
<h1 className="mx-auto max-w-full px-4 text-2xl font-bold md:max-w-[820px] lg:max-w-[1080px] xl:max-w-[1180px]">
{q ? `${t("search")}: ${q}` : t("all")}
</h1>
<section>
<div className="mx-auto max-w-full px-4 md:max-w-[820px] lg:max-w-[1080px] xl:max-w-[1180px]">
<SectionHeader
title={q ? `${t("search")}: ${q}` : t("all")}
viewAllTo="/browse"
viewAllLabel={t("viewAll")}
/>
</div>
<MessageStream scope={{ kind: "all" }} />
</section>
);

View File

@@ -26,6 +26,8 @@ export function Home() {
const [latest, setLatest] = useState<PostBackedResource[]>([]);
const [err, setErr] = useState<string | null>(null);
const recRowRef = useRef<HTMLDivElement>(null);
const categoryRowRef = useRef<HTMLDivElement>(null);
const [activeCategoryPage, setActiveCategoryPage] = useState(0);
const [canScrollRec, setCanScrollRec] = useState(false);
const [recScroll, setRecScroll] = useState({ ratio: 1, progress: 0 });
@@ -58,6 +60,31 @@ export function Home() {
const iconKeyForResource = (r: PostBackedResource) =>
cats.find((c) => c.id === r.categoryId)?.iconKey ?? "folder";
const categoryPages: Category[][] = [];
for (let index = 0; index < cats.length; index += 9) {
categoryPages.push(cats.slice(index, index + 9));
}
useEffect(() => {
const row = categoryRowRef.current;
if (!row) return;
const update = () => {
const width = row.clientWidth || 1;
const next = Math.round(row.scrollLeft / width);
setActiveCategoryPage((prev) => (prev === next ? prev : next));
};
update();
row.addEventListener("scroll", update, { passive: true });
return () => row.removeEventListener("scroll", update);
}, [cats.length]);
useEffect(() => {
setActiveCategoryPage(0);
categoryRowRef.current?.scrollTo({ left: 0 });
}, [lang]);
useEffect(() => {
const row = recRowRef.current;
if (!row) {
@@ -113,7 +140,70 @@ export function Home() {
viewAllTo="/browse"
viewAllLabel={t("viewAll")}
/>
<div className="mt-7 grid grid-cols-3 gap-3 min-[440px]:gap-3.5 md:grid-cols-5 md:gap-3 lg:grid-cols-6 xl:grid-cols-7 xl:gap-4">
<div className="md:hidden">
<div
ref={categoryRowRef}
className="flex snap-x snap-mandatory overflow-x-auto overflow-y-hidden scroll-smooth [-ms-overflow-style:none] [scrollbar-width:none] [&::-webkit-scrollbar]:hidden"
aria-label={t("categorySection")}
>
{categoryPages.map((page, pageIndex) => (
<div
key={`category-page-${pageIndex}`}
className="grid w-full shrink-0 snap-start grid-cols-3 gap-2"
>
{page.map((c) => (
<Link
key={c.id}
to={`/category/${c.slug}`}
className="group flex h-[88px] min-w-0 flex-col items-center justify-center gap-2 rounded-xl bg-[#1D1E23] px-4 py-3 text-center transition hover:bg-[#252630] focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ark-gold/80 focus-visible:ring-offset-2 focus-visible:ring-offset-ark-bg"
>
<CategoryIcon
iconKey={c.iconKey}
categorySlug={c.slug}
className="h-9 w-9 shrink-0 text-ark-gold"
/>
<div className="w-full truncate text-[13px] font-medium leading-[19.5px] text-white">
{c.name}
</div>
</Link>
))}
</div>
))}
</div>
{categoryPages.length > 1 ? (
<div
className="flex h-[30px] items-center justify-center gap-1.5"
aria-label="Category pagination"
>
{categoryPages.map((_, index) => (
<button
key={`category-dot-${index}`}
type="button"
aria-label={`Go to category page ${index + 1}`}
aria-current={activeCategoryPage === index}
onClick={() => {
const row = categoryRowRef.current;
if (!row) return;
row.scrollTo({
left: row.clientWidth * index,
behavior: "smooth",
});
setActiveCategoryPage(index);
}}
className={`h-1.5 rounded-full transition-all ${
activeCategoryPage === index
? "w-6 bg-ark-gold"
: "w-1.5 bg-[#7C7C7C]"
}`}
/>
))}
</div>
) : null}
</div>
<div className="mt-7 hidden grid-cols-3 gap-3 min-[440px]:gap-3.5 md:grid md:grid-cols-5 md:gap-3 lg:grid-cols-6 xl:grid-cols-7 xl:gap-4">
{cats.map((c) => {
const { line1, line2 } = categoryCardLines(c.name);
return (