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>
  );
}

Avis

Changelog Cards — React Changelog Section — Incubator