Retour au catalogue

Download Comparison Table

Matrice de comparaison des versions telechargeable avec fonctionnalites, prix et boutons de telechargement par tier.

downloadcomplex Both Responsive a11y
corporateminimalsaasuniversalgrid
Theme
"use client";

import { motion } from "framer-motion";
import { Download, Check, X, Crown } from "lucide-react";
import React from "react";

interface Edition {
  id: string;
  name: string;
  price: string;
  priceDetail?: string;
  downloadUrl: string;
  isPopular?: boolean;
}

interface FeatureRow {
  name: string;
  category?: string;
  values: Record<string, boolean | string>;
}

interface DownloadComparisonTableProps {
  title?: string;
  subtitle?: string;
  badge?: string;
  editions?: Edition[];
  features?: FeatureRow[];
}

const EASE = [0.16, 1, 0.3, 1] as const;

export default function DownloadComparisonTable({
  title,
  subtitle,
  badge,
  editions,
  features,
}: DownloadComparisonTableProps) {
  const resolvedTitle = title ?? "Choisissez votre edition";
  const resolvedSubtitle = subtitle ?? "Comparez les fonctionnalites de chaque edition pour trouver celle qui vous convient.";
  const resolvedBadge = badge ?? "Editions";

  const resolvedEditions: Edition[] = editions ?? [
    { id: "community", name: "Community", price: "Gratuit", priceDetail: "pour toujours", downloadUrl: "#" },
    { id: "pro", name: "Pro", price: "29 EUR", priceDetail: "/ mois", downloadUrl: "#", isPopular: true },
    { id: "enterprise", name: "Enterprise", price: "Sur devis", priceDetail: "personnalise", downloadUrl: "#" },
  ];

  const resolvedFeatures: FeatureRow[] = features ?? [
    { name: "Utilisateurs", category: "General", values: { community: "1", pro: "10", enterprise: "Illimite" } },
    { name: "Stockage", category: "General", values: { community: "5 Go", pro: "100 Go", enterprise: "Illimite" } },
    { name: "Projets", category: "General", values: { community: "3", pro: "Illimite", enterprise: "Illimite" } },
    { name: "API REST", category: "Integration", values: { community: true, pro: true, enterprise: true } },
    { name: "Webhooks", category: "Integration", values: { community: false, pro: true, enterprise: true } },
    { name: "SSO / SAML", category: "Securite", values: { community: false, pro: false, enterprise: true } },
    { name: "Audit logs", category: "Securite", values: { community: false, pro: true, enterprise: true } },
    { name: "Support prioritaire", category: "Support", values: { community: false, pro: true, enterprise: true } },
    { name: "Manager dedie", category: "Support", values: { community: false, pro: false, enterprise: true } },
    { name: "SLA garanti", category: "Support", values: { community: false, pro: false, enterprise: true } },
  ];

  // Group features by category
  const categories: { name: string; features: FeatureRow[] }[] = [];
  let currentCategory = "";
  for (const f of resolvedFeatures) {
    const cat = f.category ?? "Autre";
    if (cat !== currentCategory) {
      currentCategory = cat;
      categories.push({ name: cat, features: [] });
    }
    categories[categories.length - 1].features.push(f);
  }

  return (
    <section
      style={{
        padding: "var(--section-padding-y, 6rem) 0",
        background: "var(--color-background)",
      }}
    >
      <div
        style={{
          maxWidth: "var(--container-max-width, 1100px)",
          margin: "0 auto",
          padding: "0 var(--container-padding-x, 1.5rem)",
        }}
      >
        <motion.div
          initial={{ opacity: 0, y: 20 }}
          whileInView={{ opacity: 1, y: 0 }}
          viewport={{ once: true }}
          transition={{ duration: 0.5, ease: EASE }}
          style={{ textAlign: "center", marginBottom: "3rem" }}
        >
          <span style={{ fontSize: "0.75rem", fontWeight: 600, textTransform: "uppercase", letterSpacing: "0.08em", color: "var(--color-accent)", display: "block", marginBottom: "0.75rem" }}>
            {resolvedBadge}
          </span>
          <h2 style={{ fontSize: "clamp(1.75rem, 3vw, 2.5rem)", fontWeight: 700, color: "var(--color-foreground)", marginBottom: "0.75rem", letterSpacing: "-0.02em" }}>
            {resolvedTitle}
          </h2>
          <p style={{ fontSize: "1rem", color: "var(--color-foreground-muted)", maxWidth: "500px", margin: "0 auto", lineHeight: 1.6 }}>
            {resolvedSubtitle}
          </p>
        </motion.div>

        <motion.div
          initial={{ opacity: 0, y: 30 }}
          whileInView={{ opacity: 1, y: 0 }}
          viewport={{ once: true }}
          transition={{ duration: 0.6, delay: 0.1, ease: EASE }}
          style={{
            borderRadius: "16px",
            border: "1px solid var(--color-border)",
            background: "var(--color-background-card)",
            overflow: "hidden",
          }}
        >
          {/* Header row */}
          <div
            style={{
              display: "grid",
              gridTemplateColumns: `minmax(180px, 1fr) repeat(${resolvedEditions.length}, minmax(140px, 1fr))`,
              borderBottom: "1px solid var(--color-border)",
            }}
          >
            <div style={{ padding: "1.5rem" }} />
            {resolvedEditions.map((edition) => (
              <div
                key={edition.id}
                style={{
                  padding: "1.5rem 1rem",
                  textAlign: "center",
                  borderLeft: "1px solid var(--color-border)",
                  position: "relative",
                  background: edition.isPopular ? "color-mix(in srgb, var(--color-accent) 4%, transparent)" : "transparent",
                }}
              >
                {edition.isPopular && (
                  <div style={{
                    position: "absolute",
                    top: "0.5rem",
                    left: "50%",
                    transform: "translateX(-50%)",
                    display: "flex",
                    alignItems: "center",
                    gap: "0.25rem",
                    padding: "0.125rem 0.5rem",
                    borderRadius: "999px",
                    background: "var(--color-accent)",
                    color: "var(--color-background)",
                    fontSize: "0.625rem",
                    fontWeight: 700,
                    textTransform: "uppercase",
                    letterSpacing: "0.05em",
                  }}>
                    <Crown style={{ width: 10, height: 10 }} />
                    Populaire
                  </div>
                )}
                <h3 style={{ fontSize: "1.125rem", fontWeight: 700, color: "var(--color-foreground)", marginBottom: "0.25rem", marginTop: edition.isPopular ? "1rem" : "0" }}>
                  {edition.name}
                </h3>
                <div>
                  <span style={{ fontSize: "1.5rem", fontWeight: 800, color: "var(--color-foreground)" }}>
                    {edition.price}
                  </span>
                  {edition.priceDetail && (
                    <span style={{ fontSize: "0.75rem", color: "var(--color-foreground-muted)", marginLeft: "0.25rem" }}>
                      {edition.priceDetail}
                    </span>
                  )}
                </div>
                <motion.a
                  href={edition.downloadUrl}
                  whileHover={{ scale: 1.03 }}
                  whileTap={{ scale: 0.97 }}
                  style={{
                    display: "inline-flex",
                    alignItems: "center",
                    gap: "0.375rem",
                    marginTop: "0.75rem",
                    padding: "0.5rem 1rem",
                    borderRadius: "8px",
                    background: edition.isPopular ? "var(--color-accent)" : "transparent",
                    border: edition.isPopular ? "none" : "1px solid var(--color-border)",
                    color: edition.isPopular ? "var(--color-background)" : "var(--color-foreground)",
                    fontSize: "0.8125rem",
                    fontWeight: 600,
                    textDecoration: "none",
                    cursor: "pointer",
                  }}
                >
                  <Download style={{ width: 14, height: 14 }} />
                  Telecharger
                </motion.a>
              </div>
            ))}
          </div>

          {/* Feature rows */}
          {categories.map((category) => (
            <React.Fragment key={category.name}>
              {/* Category header */}
              <div
                style={{
                  display: "grid",
                  gridTemplateColumns: `minmax(180px, 1fr) repeat(${resolvedEditions.length}, minmax(140px, 1fr))`,
                  background: "var(--color-background-alt)",
                  borderBottom: "1px solid var(--color-border)",
                }}
              >
                <div style={{ padding: "0.625rem 1.5rem" }}>
                  <span style={{ fontSize: "0.6875rem", fontWeight: 700, textTransform: "uppercase", letterSpacing: "0.08em", color: "var(--color-foreground-muted)" }}>
                    {category.name}
                  </span>
                </div>
                {resolvedEditions.map((e) => (
                  <div key={e.id} style={{ borderLeft: "1px solid var(--color-border)" }} />
                ))}
              </div>

              {/* Feature rows */}
              {category.features.map((feature, fi) => (
                <div
                  key={fi}
                  style={{
                    display: "grid",
                    gridTemplateColumns: `minmax(180px, 1fr) repeat(${resolvedEditions.length}, minmax(140px, 1fr))`,
                    borderBottom: "1px solid var(--color-border)",
                  }}
                >
                  <div style={{ padding: "0.75rem 1.5rem", display: "flex", alignItems: "center" }}>
                    <span style={{ fontSize: "0.8125rem", color: "var(--color-foreground)" }}>
                      {feature.name}
                    </span>
                  </div>
                  {resolvedEditions.map((edition) => {
                    const value = feature.values[edition.id];
                    return (
                      <div
                        key={edition.id}
                        style={{
                          padding: "0.75rem 1rem",
                          borderLeft: "1px solid var(--color-border)",
                          display: "flex",
                          alignItems: "center",
                          justifyContent: "center",
                          background: edition.isPopular ? "color-mix(in srgb, var(--color-accent) 2%, transparent)" : "transparent",
                        }}
                      >
                        {typeof value === "boolean" ? (
                          value ? (
                            <Check style={{ width: 16, height: 16, color: "var(--color-accent)" }} />
                          ) : (
                            <X style={{ width: 16, height: 16, color: "var(--color-foreground-light)" }} />
                          )
                        ) : (
                          <span style={{ fontSize: "0.8125rem", color: "var(--color-foreground)", fontWeight: 500 }}>
                            {value}
                          </span>
                        )}
                      </div>
                    );
                  })}
                </div>
              ))}
            </React.Fragment>
          ))}
        </motion.div>
      </div>
    </section>
  );
}

Avis

Download Comparison Table — React Download Section — Incubator