blog_tech/src/components/ArticleCarousel/index.tsx
Tellsanguis 461fc446ff Ajout carrousel articles avec miniatures auto-générées
- Créer composant React ArticleCarousel avec lecture auto et boucle infinie
- Afficher 6 articles total : 3 articles blog et 3 pages docs, intercalés
- Ajouter badges colorés : bleu pour blog, vert foncé pour docs
- Implémenter plugin personnalisé pour récupérer et organiser articles
- Remplacer section fonctionnalités page d'accueil par nouveau carrousel
- Mettre à jour logo site et favicon avec logo vache (logo_vache.png)
- Ajouter traductions anglaises pour chaînes UI carrousel
2025-12-03 22:58:35 +01:00

214 lines
6.6 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import React, { useState, useEffect } from 'react';
import Link from '@docusaurus/Link';
import { translate } from '@docusaurus/Translate';
import useDocusaurusContext from '@docusaurus/useDocusaurusContext';
import styles from './styles.module.css';
export interface Article {
title: string;
permalink: string;
type: 'blog' | 'doc';
date?: string;
}
interface ArticleCarouselProps {
articles: Article[];
maxVisible?: number;
}
/**
* ArticleCarousel component displays recent blog posts and documentation
* with auto-generated thumbnails featuring title and type badge.
*
* @param articles - Array of articles to display in the carousel
* @param maxVisible - Maximum number of visible carousel items (default: 6)
*/
export default function ArticleCarousel({
articles,
maxVisible = 6
}: ArticleCarouselProps): JSX.Element {
const [currentIndex, setCurrentIndex] = useState(0);
const [isAutoPlaying, setIsAutoPlaying] = useState(true);
const { i18n } = useDocusaurusContext();
// Auto-play carousel every 5 seconds
useEffect(() => {
if (!isAutoPlaying || articles.length <= 1) return;
const interval = setInterval(() => {
setCurrentIndex((prev) => (prev + 1) % articles.length);
}, 5000);
return () => clearInterval(interval);
}, [isAutoPlaying, articles.length]);
// Limit articles to maxVisible
const visibleArticles = articles.slice(0, maxVisible);
const handlePrevious = () => {
setIsAutoPlaying(false);
setCurrentIndex((prev) =>
prev === 0 ? visibleArticles.length - 1 : prev - 1
);
};
const handleNext = () => {
setIsAutoPlaying(false);
setCurrentIndex((prev) => (prev + 1) % visibleArticles.length);
};
const handleDotClick = (index: number) => {
setIsAutoPlaying(false);
setCurrentIndex(index);
};
if (visibleArticles.length === 0) {
return null;
}
// Get badge text based on article type and current locale
const getBadgeText = (type: 'blog' | 'doc'): string => {
return type === 'blog'
? translate({
id: 'carousel.badge.blog',
message: 'BLOG',
description: 'Badge text for blog articles in carousel',
})
: translate({
id: 'carousel.badge.documentation',
message: 'DOCUMENTATION',
description: 'Badge text for documentation pages in carousel',
});
};
// Generate consistent background colors for thumbnails
const getBackgroundColor = (index: number): string => {
const colors = [
'#4A90E2', // Blue
'#50C878', // Emerald
'#9B59B6', // Purple
'#E67E22', // Orange
'#1ABC9C', // Turquoise
'#E74C3C', // Red
];
return colors[index % colors.length];
};
return (
<section className={styles.carouselSection}>
<div className="container">
<h2 className={styles.carouselTitle}>
{translate({
id: 'carousel.title',
message: 'Articles récents',
description: 'Title of the article carousel section',
})}
</h2>
<div className={styles.carouselContainer}>
{/* Navigation Arrow - Previous */}
<button
className={`${styles.carouselArrow} ${styles.carouselArrowLeft}`}
onClick={handlePrevious}
aria-label={translate({
id: 'carousel.previous',
message: 'Article précédent',
description: 'Previous article button aria-label',
})}
>
</button>
{/* Carousel Track */}
<div className={styles.carouselTrack}>
<div
className={styles.carouselInner}
style={{
transform: `translateX(-${currentIndex * 100}%)`,
}}
>
{visibleArticles.map((article, index) => (
<Link
key={`${article.permalink}-${index}`}
to={article.permalink}
className={styles.carouselItem}
>
<div
className={styles.articleThumbnail}
style={{
backgroundColor: getBackgroundColor(index),
}}
>
{/* Type Badge */}
<div
className={`${styles.articleBadge} ${
article.type === 'blog'
? styles.articleBadgeBlog
: styles.articleBadgeDoc
}`}
>
{getBadgeText(article.type)}
</div>
{/* Article Title */}
<div className={styles.articleTitle}>
<h3>{article.title}</h3>
</div>
{/* Date (if available for blog posts) */}
{article.date && (
<div className={styles.articleDate}>
{new Date(article.date).toLocaleDateString(
i18n.currentLocale === 'fr' ? 'fr-FR' : 'en-US',
{
year: 'numeric',
month: 'long',
day: 'numeric',
}
)}
</div>
)}
</div>
</Link>
))}
</div>
</div>
{/* Navigation Arrow - Next */}
<button
className={`${styles.carouselArrow} ${styles.carouselArrowRight}`}
onClick={handleNext}
aria-label={translate({
id: 'carousel.next',
message: 'Article suivant',
description: 'Next article button aria-label',
})}
>
</button>
</div>
{/* Carousel Indicators (Dots) */}
<div className={styles.carouselIndicators}>
{visibleArticles.map((_, index) => (
<button
key={index}
className={`${styles.carouselDot} ${
index === currentIndex ? styles.carouselDotActive : ''
}`}
onClick={() => handleDotClick(index)}
aria-label={translate(
{
id: 'carousel.goToSlide',
message: 'Aller à la diapositive {index}',
description: 'Go to slide button aria-label',
},
{ index: index + 1 }
)}
/>
))}
</div>
</div>
</section>
);
}