Retour au catalogue
Changelog Cards
Cards par version avec date, titre, description et liste de changements icones.
changelogmedium Both Responsive a11y
minimalcorporatesaasuniversalstacked
Theme
"use client";
import { motion } from "framer-motion";
import { Sparkles, Bug, TrendingUp, Calendar } from "lucide-react";
type TagType = "new" | "fix" | "improvement";
interface ChangeEntry {
version: string;
date: string;
title: string;
description: string;
changes: { text: string; tag: TagType }[];
}
interface ChangelogCardsProps {
title?: string;
description?: string;
entries?: ChangeEntry[];
}
const EASE = [0.16, 1, 0.3, 1] as const;
const tagConfig: Record<TagType, { label: string; icon: typeof Sparkles }> = {
new: { label: "Nouveau", icon: Sparkles },
fix: { label: "Correction", icon: Bug },
improvement: { label: "Amelioration", icon: TrendingUp },
};
export default function ChangelogCards({
title = "Nouveautes",
description = "Decouvrez les dernieres evolutions de notre plateforme.",
entries = [
{
version: "v3.1.0",
date: "1 Mars 2026",
title: "Integration IA",
description: "Notre assistant intelligent est maintenant integre a l'editeur.",
changes: [
{ text: "Assistant IA contextuel", tag: "new" },
{ text: "Suggestions intelligentes", tag: "new" },
{ text: "Performance de l'indexation amelioree", tag: "improvement" },
],
},
{
version: "v3.0.2",
date: "20 Fevrier 2026",
title: "Correctifs de stabilite",
description: "Plusieurs correctifs importants pour ameliorer la fiabilite.",
changes: [
{ text: "Correction de la synchronisation", tag: "fix" },
{ text: "Gestion memoire optimisee", tag: "improvement" },
{ text: "Correction affichage des graphiques", tag: "fix" },
],
},
{
version: "v3.0.0",
date: "5 Fevrier 2026",
title: "Refonte complete",
description: "Une nouvelle interface repensee de A a Z pour une meilleure experience.",
changes: [
{ text: "Nouvelle interface utilisateur", tag: "new" },
{ text: "Moteur de recherche global", tag: "new" },
{ text: "Temps de chargement reduit de 60%", tag: "improvement" },
],
},
],
}: ChangelogCardsProps) {
return (
<section
style={{
paddingTop: "var(--section-padding-y-lg)",
paddingBottom: "var(--section-padding-y-lg)",
background: "var(--color-background)",
}}
>
<div
style={{
width: "100%",
maxWidth: "var(--container-max-width)",
margin: "0 auto",
padding: "0 var(--container-padding-x)",
}}
>
{/* Header */}
<motion.div
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.6, ease: EASE }}
style={{ textAlign: "center", marginBottom: "3rem", maxWidth: "540px", margin: "0 auto 3rem" }}
>
<h2
style={{
fontFamily: "var(--font-sans)",
fontSize: "clamp(1.75rem, 3vw, 2.5rem)",
fontWeight: 700,
color: "var(--color-foreground)",
marginBottom: "0.75rem",
}}
>
{title}
</h2>
<p style={{ fontSize: "1.0625rem", lineHeight: 1.7, color: "var(--color-foreground-muted)" }}>{description}</p>
</motion.div>
{/* Cards */}
<div style={{ display: "flex", flexDirection: "column", gap: "1.5rem", maxWidth: "720px", margin: "0 auto" }}>
{entries.map((entry, i) => (
<motion.article
key={entry.version}
initial={{ opacity: 0, y: 16 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.5, delay: 0.08 * i, ease: EASE }}
style={{
padding: "1.75rem",
borderRadius: "var(--radius-lg)",
border: "1px solid var(--color-border)",
background: "var(--color-background-card)",
}}
>
{/* Card header */}
<div style={{ display: "flex", alignItems: "center", gap: "0.75rem", marginBottom: "0.75rem", flexWrap: "wrap" }}>
<span
style={{
fontSize: "0.8125rem",
fontWeight: 700,
padding: "0.25rem 0.75rem",
borderRadius: "var(--radius-full)",
background: "var(--color-accent-subtle)",
color: "var(--color-accent)",
}}
>
{entry.version}
</span>
<span style={{ display: "inline-flex", alignItems: "center", gap: "4px", fontSize: "0.8125rem", color: "var(--color-foreground-muted)" }}>
<Calendar style={{ width: 13, height: 13 }} />
{entry.date}
</span>
</div>
<h3 style={{ fontSize: "1.25rem", fontWeight: 600, color: "var(--color-foreground)", marginBottom: "0.5rem" }}>
{entry.title}
</h3>
<p style={{ fontSize: "0.9375rem", lineHeight: 1.6, color: "var(--color-foreground-muted)", marginBottom: "1rem" }}>
{entry.description}
</p>
{/* Changes list */}
<ul style={{ listStyle: "none", padding: 0, margin: 0, display: "flex", flexDirection: "column", gap: "0.5rem" }}>
{entry.changes.map((change, j) => {
const cfg = tagConfig[change.tag];
const Icon = cfg.icon;
return (
<li key={j} style={{ display: "flex", alignItems: "center", gap: "0.5rem", fontSize: "0.875rem", color: "var(--color-foreground-muted)" }}>
<Icon style={{ width: 13, height: 13, color: "var(--color-accent)", flexShrink: 0 }} />
{change.text}
</li>
);
})}
</ul>
</motion.article>
))}
</div>
</div>
</section>
);
}