Mobilni mozaik: prvi blok se vrti po 3 novice (9 najnovejsih, 8 s)

This commit is contained in:
sebastjan 2026-08-15 16:28:48 +02:00
parent 6f4cfa8ee9
commit 10af4563a5

View File

@ -183,13 +183,53 @@ function MosaicHeading({ children }: { children: React.ReactNode }) {
}
function MobileMosaic({ latest, popular, focalPoints }: { latest: Article[]; popular?: Article[]; focalPoints?: Record<string, { x: number; y: number }> }) {
const l = (latest || []).slice(0, 3);
const pageSize = 3;
const pool = (latest || []).slice(0, 9);
const totalPages = Math.max(1, Math.ceil(pool.length / pageSize));
const [page, setPage] = useState(0);
const [displayPage, setDisplayPage] = useState(0);
const [fading, setFading] = useState(false);
const [paused, setPaused] = useState(false);
const changePage = useCallback((newPage: number) => {
if (newPage === displayPage) return;
setFading(true);
setTimeout(() => {
setDisplayPage(newPage);
setFading(false);
}, 300);
}, [displayPage]);
useEffect(() => {
changePage(page);
}, [page]);
useEffect(() => {
if (paused || totalPages <= 1) return;
const timer = setInterval(() => setPage((p) => (p + 1) % totalPages), 8000);
return () => clearInterval(timer);
}, [paused, totalPages]);
const l = pool.slice(displayPage * pageSize, displayPage * pageSize + pageSize);
if (l.length === 0) return null;
const p = (popular || []).filter((a) => !l.some((x) => x.id === a.id)).slice(0, 3);
const usedIds = new Set(pool.map((a) => a.id));
const p = (popular || []).filter((a) => !usedIds.has(a.id)).slice(0, 3);
const nextStart = ((displayPage + 1) % totalPages) * pageSize;
const preload = pool.slice(nextStart, nextStart + pageSize);
return (
<div className="md:hidden space-y-6" data-testid="section-mobile-mosaic">
<section>
<section onTouchStart={() => setPaused(true)}>
<MosaicHeading>Neueste Artikel</MosaicHeading>
{preload.length > 0 && (
<div className="hidden" aria-hidden="true">
{preload.map((a) => a.coverImage && <img key={`pre-${a.id}`} src={thumbUrl(a.coverImage)} alt="" />)}
</div>
)}
<div className="transition-opacity duration-300" style={{ opacity: fading ? 0 : 1 }}>
<div className="grid grid-cols-2 gap-2">
{l.slice(0, 2).map((a) => (
<MosaicTile key={a.id} article={a} focalPoints={focalPoints} />
@ -200,6 +240,20 @@ function MobileMosaic({ latest, popular, focalPoints }: { latest: Article[]; pop
<MosaicTile article={l[2]} focalPoints={focalPoints} tall />
</div>
)}
</div>
{totalPages > 1 && (
<div className="flex justify-center gap-2 mt-3" data-testid="mosaic-dots">
{Array.from({ length: totalPages }).map((_, i) => (
<button
key={i}
onClick={() => setPage(i)}
className={`h-2 rounded-full transition-all duration-300 ${i === page ? "bg-primary w-5" : "bg-muted-foreground/30 w-2"}`}
data-testid={`button-mosaic-dot-${i}`}
aria-label={`Seite ${i + 1}`}
/>
))}
</div>
)}
</section>
{p.length > 0 && (