Press n or j to go to the next uncovered block, b, p or k for the previous block.
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 | 2x 2x 2x 41x 41x 41x 41x 15x 16x 16x 16x 15x 15x 15x 41x 41x 6x 3x 3x 41x 2x 2x 41x 3x 41x 4x 3x 3x 1x 1x 2x 1x 1x 41x 172x | /**
* @module components/evenements/CarousselEpreuve
* Module de composant CarousselEpreuve pour l'affichage en carrousel des épreuves olympiques
*
* Ce module contient le composant CarousselEpreuve qui affiche une liste d'épreuves
* sous forme de carrousel interactif avec navigation par boutons et gestes tactiles.
* Il s'adapte automatiquement à la taille d'écran pour optimiser l'affichage.
*
* ## Fonctionnalités principales
* - Affichage en carrousel responsive avec calcul dynamique du nombre de cartes
* - Navigation par boutons précédent/suivant
* - Support des gestes tactiles (swipe) pour appareils mobiles
* - Animation fluide entre les slides avec transitions CSS
* - Boucle infinie pour navigation continue
* - Redimensionnement automatique selon la largeur d'écran
*
* ## Navigation
* - **Boutons** : Flèches gauche/droite avec overlay semi-transparent
* - **Tactile** : Swipe gauche/droite avec détection de distance minimale (50px)
* - **Boucle** : Retour automatique au début/fin lors des limites
* - **Keyboard** : Compatible avec la navigation clavier (focus)
*
* ## Responsive Design
* - Calcul automatique : largeur écran ÷ 250px = nombre de cartes visibles
* - Redimensionnement en temps réel sur resize de fenêtre
* - Réinitialisation de position lors du changement de taille
* - Adaptation mobile/desktop automatique
*
* ## Interactions
* - Clic sur carte : transmission de l'ID d'épreuve au parent
* - Swipe tactile : détection de mouvement avec seuil de 50px
* - Hover effects sur les boutons de navigation
* - Transitions fluides de 500ms entre les slides
*
* ## Design et UX
* - Coins arrondis asymétriques (top-left, bottom-right)
* - Boutons de navigation positionnés en dehors du carrousel
* - Padding interne pour espacement des cartes
* - Overflow masqué pour effet de glissement propre
*
* @group Components
*/
"use client";
import {useState, useEffect} from "react";
import { EpreuveCardType } from "@/type/evenement/epreuve";
import CardEpreuve from "@/components/evenements/CardEpreuve";
/**
* Props du composant CarousselEpreuve
*/
interface Props {
/** Liste des épreuves à afficher dans le carrousel (optionnel, tableau vide par défaut) */
epreuves?: EpreuveCardType[];
/** Fonction appelée lors du clic sur une carte d'épreuve, recevant l'ID de l'épreuve */
onCardClickAction: (epreuveId: number) => void;
}
/**
* Composant CarousselEpreuve pour l'affichage en carrousel des épreuves olympiques.
* Voir la documentation du module ci-dessus pour les détails complets.
*
* Le composant crée un carrousel interactif qui s'adapte automatiquement à la taille
* d'écran et offre plusieurs modes de navigation. Il calcule dynamiquement le nombre
* de cartes visibles et gère les transitions fluides entre les slides.
*
* @param props - Les propriétés du composant
* @param props.epreuves - Liste des épreuves à afficher dans le carrousel
* @param props.onCardClickAction - Callback appelé lors du clic sur une carte
*
* @returns Carrousel interactif avec navigation et gestion tactile
*
* @example
* ```tsx
* // Utilisation basique avec liste d'épreuves
* <CarousselEpreuve
* epreuves={epreuvesData}
* onCardClickAction={(id) => setSelectedEpreuve(id)}
* />
*
* // Avec gestion d'état et modal
* const [selectedEpreuve, setSelectedEpreuve] = useState<number | null>(null);
*
* <CarousselEpreuve
* epreuves={filteredEpreuves}
* onCardClickAction={(epreuveId) => {
* setSelectedEpreuve(epreuveId);
* setModalOpen(true);
* }}
* />
*
* // Carrousel vide (affichage conditionnel)
* <CarousselEpreuve
* epreuves={[]}
* onCardClickAction={handleEpreuveSelection}
* />
* ```
*/
export default function CarousselEpreuve({ epreuves = [], onCardClickAction }: Props) {
const [current, setCurrent] = useState(0);
const [visible, setVisible] = useState(2);
const [startX, setStartX] = useState<number | null>(null);
// Calcul dynamique du nombre de cartes visibles
useEffect(() => {
const updateVisible = () => {
const width = window.innerWidth;
setVisible(Math.ceil(width / 250));
setCurrent(0);
};
updateVisible();
window.addEventListener("resize", updateVisible);
return () => window.removeEventListener("resize", updateVisible);
}, [epreuves]);
const cardWidth = 100 / visible;
// Navigation boutons
const nextSlide = () => {
if (current + visible >= epreuves.length) {
setCurrent(0);
} else {
setCurrent(current + 1);
}
};
const prevSlide = () => {
if (current === 0) {
setCurrent(epreuves.length - visible);
} else E{
setCurrent(current - 1);
}
};
// Swipe tactile
const handleTouchStart = (e: React.TouchEvent<HTMLDivElement>) => {
setStartX(e.touches[0].clientX);
};
const handleTouchMove = (e: React.TouchEvent<HTMLDivElement>) => {
if (startX === null) return;
const diff = startX - e.touches[0].clientX;
if (diff > 50) {
nextSlide();
setStartX(null);
} else if (diff < -50) {
prevSlide();
setStartX(null);
}
};
return (
<div className="relative w-full overflow-visible">
<div className="overflow-hidden bg-base-300 rounded-tl-[20px] rounded-br-[20px]">
<div
className="flex transition-transform duration-500"
style={{ transform: `translateX(-${current * cardWidth}%)` }}
onTouchStart={handleTouchStart}
onTouchMove={handleTouchMove}
>
{epreuves.map((epreuve, index) => (
<div
key={index}
style={{
flex: `0 0 ${cardWidth}%`,
maxWidth: `${cardWidth}%`,
boxSizing: "border-box",
padding: "0.5rem",
}}
>
<CardEpreuve epreuve={epreuve} onCardClickAction={onCardClickAction}/>
</div>
))}
</div>
</div>
{/* Boutons navigation */}
<button
onClick={prevSlide}
className="absolute top-1/2 -translate-y-1/2 bg-black/50 text-white p-3 rounded-full hover:bg-black/70 transition"
style={{ left: "-15px" }}
>
‹
</button>
<button
onClick={nextSlide}
className="absolute top-1/2 -translate-y-1/2 bg-black/50 text-white p-3 rounded-full hover:bg-black/70 transition"
style={{ right: "-15px" }}
>
›
</button>
</div>
);
}
|