fix: route home view-all links

This commit is contained in:
TerryM
2026-05-28 16:49:30 +08:00
parent 4c15e01460
commit 5fec82dbba
6 changed files with 90 additions and 68 deletions

View File

@@ -5,6 +5,8 @@ import {
useState,
type PointerEvent as ReactPointerEvent,
} from "react";
import { assetUrl, getJSON, itemsOrEmpty } from "../api";
import { langQuery, useI18n, type Lang } from "../i18n";
const FIGMA_ASSET_BASE = "/assets/ark-library/figma";
@@ -17,42 +19,46 @@ type BannerSlide = {
mobile: string;
desktop: string;
alt: string;
linkUrl?: string;
};
const BANNERS_BASE = "/assets/ark-library/banners";
type BannerApiItem = {
id: number | string;
imageUrl: string;
linkUrl?: string;
sortOrder?: number;
};
const BANNER_SLIDES: BannerSlide[] = [
{
id: "ark-banner-1",
mobile: `${BANNERS_BASE}/banner-1.png`,
desktop: `${BANNERS_BASE}/banner-1.png`,
alt: "",
},
{
id: "ark-banner-2",
mobile: `${BANNERS_BASE}/banner-2.png`,
desktop: `${BANNERS_BASE}/banner-2.png`,
alt: "",
},
{
id: "ark-banner-3",
mobile: `${BANNERS_BASE}/banner-3.png`,
desktop: `${BANNERS_BASE}/banner-3.png`,
alt: "",
},
{
id: "ark-banner-4",
mobile: `${BANNERS_BASE}/banner-4.png`,
desktop: `${BANNERS_BASE}/banner-4.png`,
alt: "",
},
];
type BannerApiResponse = {
items?: BannerApiItem[] | null;
};
const AUTOPLAY_MS = 3000;
const RESUME_AFTER_INTERACTION_MS = 8000;
function bannerLangParam(lang: Lang): string {
return langQuery(lang).toLowerCase();
}
function toSlides(items: BannerApiItem[]): BannerSlide[] {
return [...items]
.sort((a, b) => (a.sortOrder ?? 0) - (b.sortOrder ?? 0))
.filter((item) => item.imageUrl)
.map((item) => {
const imageUrl = assetUrl(item.imageUrl);
return {
id: String(item.id),
mobile: imageUrl,
desktop: imageUrl,
alt: "",
linkUrl: item.linkUrl || undefined,
};
});
}
export function FigmaBanner() {
const slides = BANNER_SLIDES;
const { lang } = useI18n();
const [slides, setSlides] = useState<BannerSlide[]>([]);
const scrollerRef = useRef<HTMLDivElement>(null);
const [activeIndex, setActiveIndex] = useState(0);
const [autoplayPaused, setAutoplayPaused] = useState(false);
@@ -65,6 +71,22 @@ export function FigmaBanner() {
} | null>(null);
const hasMultiple = slides.length > 1;
useEffect(() => {
let cancelled = false;
setActiveIndex(0);
getJSON<BannerApiResponse>(`/api/banners?lang=${bannerLangParam(lang)}`)
.then((res) => {
if (cancelled) return;
setSlides(toSlides(itemsOrEmpty(res.items)));
})
.catch(() => {
if (!cancelled) setSlides([]);
});
return () => {
cancelled = true;
};
}, [lang]);
const goTo = useCallback((index: number, behavior: ScrollBehavior) => {
const scroller = scrollerRef.current;
if (!scroller) return;
@@ -168,6 +190,8 @@ export function FigmaBanner() {
}
};
if (slides.length === 0) return null;
const pagination = hasMultiple ? (
<div
className="flex items-center justify-center gap-1.5 md:gap-2"
@@ -213,14 +237,8 @@ export function FigmaBanner() {
aria-roledescription="carousel"
aria-label="ARK Library banner"
>
{slides.map((slide, index) => (
<div
key={slide.id}
className="relative w-full shrink-0 snap-start"
role="group"
aria-roledescription="slide"
aria-label={`${index + 1} / ${slides.length}`}
>
{slides.map((slide, index) => {
const image = (
<picture className="block w-full overflow-hidden bg-black md:rounded-xl">
<source media="(max-width: 767px)" srcSet={slide.mobile} />
<img
@@ -234,8 +252,26 @@ export function FigmaBanner() {
draggable={false}
/>
</picture>
</div>
))}
);
return (
<div
key={slide.id}
className="relative w-full shrink-0 snap-start"
role="group"
aria-roledescription="slide"
aria-label={`${index + 1} / ${slides.length}`}
>
{slide.linkUrl ? (
<a href={slide.linkUrl} className="block" rel="noreferrer">
{image}
</a>
) : (
image
)}
</div>
);
})}
</div>
{hasMultiple ? (