feat: add scrolling functionality for winners section with navigation buttons
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
import React, { useEffect, useRef, useState } from 'react';
|
||||
import { Button } from '@/spa/components/ui/button';
|
||||
import { Play, ChevronRight, X, Star } from 'lucide-react';
|
||||
import { Play, ChevronLeft, ChevronRight, X, Star } from 'lucide-react';
|
||||
import { Link } from '@/spa/router';
|
||||
import { PartnerTicker } from '@/spa/components/ui/partner-ticker';
|
||||
import { WINNERS } from '@/spa/data/winners';
|
||||
@@ -225,6 +225,7 @@ function HomeWinnerCard({
|
||||
const Home: React.FC = () => {
|
||||
const [showVideo, setShowVideo] = useState(false);
|
||||
const videoRef = useRef<HTMLVideoElement>(null);
|
||||
const winnersScrollRef = useRef<HTMLDivElement>(null);
|
||||
const isMobile = useIsMobile();
|
||||
const placeholderImage = usePreistraegerPlaceholderImage();
|
||||
const home = (useCmsRoute()?.doc?.home || {}) as HomeCms;
|
||||
@@ -259,9 +260,17 @@ const Home: React.FC = () => {
|
||||
const selectedWinnerDocs = selectedWinners
|
||||
.map((selected) => resolveCmsRelationship(selected, cmsPreistraeger))
|
||||
.filter(Boolean)
|
||||
const eligibleWinners = (cmsPreistraeger.length ? cmsPreistraeger : WINNERS)
|
||||
.filter((winner) => Number((winner as { year?: string | number }).year) === 2025)
|
||||
const selectedWinnerIDs = new Set(
|
||||
selectedWinnerDocs.map((winner) => String((winner as WinnerCardData).id || (winner as WinnerCardData).slug)),
|
||||
)
|
||||
const remainingWinners = selectedWinnerDocs.length >= 8
|
||||
? eligibleWinners.filter((winner) => !selectedWinnerIDs.has(String((winner as WinnerCardData).id || (winner as WinnerCardData).slug)))
|
||||
: []
|
||||
const homeWinners = selectedWinnerDocs.length
|
||||
? selectedWinnerDocs
|
||||
: (cmsPreistraeger.length ? cmsPreistraeger : WINNERS).filter((w) => Number((w as { year?: string | number }).year) === 2025).slice(0, 8);
|
||||
? [...selectedWinnerDocs, ...remainingWinners]
|
||||
: eligibleWinners;
|
||||
const scrollDesktopWinners = !isMobile && homeWinners.length > 8;
|
||||
const scrollWinnerGrid = isMobile || scrollDesktopWinners;
|
||||
const quickCriteria = fallbackArray(quickCheck.criteria, homeContent.quickCheck.criteria);
|
||||
@@ -281,6 +290,16 @@ const Home: React.FC = () => {
|
||||
});
|
||||
}, [showVideo, videoSrc]);
|
||||
|
||||
const scrollWinners = (direction: -1 | 1) => {
|
||||
const container = winnersScrollRef.current;
|
||||
if (!container) return;
|
||||
|
||||
container.scrollBy({
|
||||
left: direction * Math.max(container.clientWidth * 0.8, 320),
|
||||
behavior: 'smooth',
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="animate-fade-in">
|
||||
{/* Hero Section */}
|
||||
@@ -380,15 +399,37 @@ const Home: React.FC = () => {
|
||||
<Lines text={fallbackText(winnersSection.heading, homeContent.winners.heading)} />
|
||||
</h2>
|
||||
</div>
|
||||
<Link
|
||||
to={fallbackText(winnersSection.cta?.url, homeContent.winners.cta.url)}
|
||||
style={{ fontFamily: '"IBM Plex Sans", sans-serif', fontSize: 15, fontWeight: 700, letterSpacing: '0.1em', textTransform: 'uppercase', color: '#EFBF04', textDecoration: 'none', display: 'flex', alignItems: 'center', gap: 6, paddingBottom: 4, borderBottom: '1px solid #EFBF04', whiteSpace: 'nowrap' }}
|
||||
>
|
||||
{fallbackText(winnersSection.cta?.label, homeContent.winners.cta.label)} <ChevronRight size={14} />
|
||||
</Link>
|
||||
<div style={{ display: 'flex', alignItems: 'center', gap: 20 }}>
|
||||
{scrollDesktopWinners && (
|
||||
<div style={{ display: 'flex', alignItems: 'center', gap: 8 }}>
|
||||
<button
|
||||
type="button"
|
||||
aria-label="Vorherige Preisträger anzeigen"
|
||||
onClick={() => scrollWinners(-1)}
|
||||
style={{ width: 42, height: 42, border: '1px solid rgba(255,255,255,0.28)', background: 'transparent', color: '#fff', display: 'inline-flex', alignItems: 'center', justifyContent: 'center', cursor: 'pointer' }}
|
||||
>
|
||||
<ChevronLeft size={18} />
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
aria-label="Weitere Preisträger anzeigen"
|
||||
onClick={() => scrollWinners(1)}
|
||||
style={{ width: 42, height: 42, border: '1px solid rgba(255,255,255,0.28)', background: 'transparent', color: '#fff', display: 'inline-flex', alignItems: 'center', justifyContent: 'center', cursor: 'pointer' }}
|
||||
>
|
||||
<ChevronRight size={18} />
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
<Link
|
||||
to={fallbackText(winnersSection.cta?.url, homeContent.winners.cta.url)}
|
||||
style={{ fontFamily: '"IBM Plex Sans", sans-serif', fontSize: 15, fontWeight: 700, letterSpacing: '0.1em', textTransform: 'uppercase', color: '#EFBF04', textDecoration: 'none', display: 'flex', alignItems: 'center', gap: 6, paddingBottom: 4, borderBottom: '1px solid #EFBF04', whiteSpace: 'nowrap' }}
|
||||
>
|
||||
{fallbackText(winnersSection.cta?.label, homeContent.winners.cta.label)} <ChevronRight size={14} />
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div style={{
|
||||
<div ref={winnersScrollRef} style={{
|
||||
display: 'grid',
|
||||
gridTemplateColumns: scrollWinnerGrid ? undefined : 'repeat(4, 1fr)',
|
||||
gridAutoFlow: scrollWinnerGrid ? 'column' : undefined,
|
||||
@@ -398,9 +439,10 @@ const Home: React.FC = () => {
|
||||
scrollSnapType: scrollWinnerGrid ? 'x mandatory' : undefined,
|
||||
overscrollBehaviorX: scrollWinnerGrid ? 'contain' : undefined,
|
||||
gap: isMobile ? 8 : 0,
|
||||
padding: isMobile ? '0 16px 8px' : 0,
|
||||
padding: isMobile ? '0 16px 8px' : scrollDesktopWinners ? '0 0 12px' : 0,
|
||||
WebkitOverflowScrolling: 'touch',
|
||||
scrollbarWidth: 'none',
|
||||
scrollbarWidth: scrollDesktopWinners ? 'thin' : 'none',
|
||||
scrollbarColor: scrollDesktopWinners ? '#EFBF04 rgba(255,255,255,0.14)' : undefined,
|
||||
}}>
|
||||
{homeWinners.map((w) => (
|
||||
<HomeWinnerCard
|
||||
@@ -849,7 +891,7 @@ function StatusSlideCard({
|
||||
<h2 style={{ fontFamily: FF, fontSize: isMobile ? 'clamp(1.8rem, 8vw, 2.6rem)' : 'clamp(2.4rem, 3.8vw, 3.8rem)', fontWeight: 900, color: '#fff', textTransform: 'uppercase', letterSpacing: '-0.03em', lineHeight: 0.94, margin: '0 0 24px' }}>
|
||||
{phase.headline}
|
||||
</h2>
|
||||
<p style={{ fontFamily: '"Inter", sans-serif', fontSize: 17, color: 'rgba(255,255,255,0.68)', lineHeight: 1.8, marginBottom: 32, maxWidth: 520 }}>
|
||||
<p style={{ fontFamily: '"Inter", sans-serif', fontSize: 17, color: '#C7CAD1', lineHeight: 1.8, marginBottom: 32, maxWidth: isMobile ? 520 : 760 }}>
|
||||
{phase.body}
|
||||
</p>
|
||||
{phase.cta ? (
|
||||
|
||||
Reference in New Issue
Block a user