Retour au catalogue

Product Showcase Comparison

Tableau comparatif cote a cote de 2-3 produits avec features, prix et CTA. Produit recommande mis en avant.

product-showcasecomplex Both Responsive a11y
corporateminimalecommercesaasuniversalgrid
Theme
"use client";

import { motion } from "framer-motion";
import { Check, Minus } from "lucide-react";

interface ComparisonProduct {
  id: string;
  name: string;
  price: string;
  imageSrc?: string;
  imageAlt?: string;
  highlighted?: boolean;
  ctaLabel?: string;
  ctaUrl?: string;
}

interface ComparisonRow {
  label: string;
  values: (string | boolean)[];
}

interface ProductShowcaseComparisonProps {
  badge?: string;
  title?: string;
  subtitle?: string;
  products?: ComparisonProduct[];
  rows?: ComparisonRow[];
}

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

export default function ProductShowcaseComparison({
  badge = "Comparaison",
  title = "Choisissez votre modele",
  subtitle = "Comparez nos produits",
  products = [],
  rows = [],
}: ProductShowcaseComparisonProps) {
  if (products.length === 0) return null;

  return (
    <section
      style={{
        padding: "var(--section-padding-y-lg) 0",
        background: "var(--color-background)",
      }}
    >
      <div
        style={{
          maxWidth: "var(--container-max-width)",
          margin: "0 auto",
          padding: "0 var(--container-padding-x)",
        }}
      >
        {/* Header */}
        <motion.div
          initial={{ opacity: 0, y: 20 }}
          whileInView={{ opacity: 1, y: 0 }}
          viewport={{ once: true, margin: "-80px" }}
          transition={{ duration: 0.5, ease: EASE }}
          style={{ textAlign: "center", maxWidth: "600px", margin: "0 auto 3rem" }}
        >
          {badge && (
            <span
              style={{
                display: "inline-block",
                fontSize: "0.75rem",
                fontWeight: 600,
                textTransform: "uppercase",
                letterSpacing: "0.1em",
                color: "var(--color-accent)",
                marginBottom: "0.75rem",
              }}
            >
              {badge}
            </span>
          )}
          <h2
            style={{
              fontFamily: "var(--font-sans)",
              fontSize: "clamp(1.75rem, 3vw, 2.75rem)",
              fontWeight: 700,
              letterSpacing: "-0.02em",
              color: "var(--color-foreground)",
              marginBottom: "0.75rem",
            }}
          >
            {title}
          </h2>
          <p style={{ fontSize: "1rem", lineHeight: 1.6, color: "var(--color-foreground-muted)" }}>
            {subtitle}
          </p>
        </motion.div>

        {/* Comparison table */}
        <motion.div
          initial={{ opacity: 0, y: 24 }}
          whileInView={{ opacity: 1, y: 0 }}
          viewport={{ once: true, margin: "-40px" }}
          transition={{ duration: 0.6, ease: EASE }}
          style={{ overflowX: "auto" }}
        >
          <table
            style={{
              width: "100%",
              borderCollapse: "collapse",
              minWidth: "600px",
            }}
          >
            {/* Product headers */}
            <thead>
              <tr>
                <th style={{ width: "30%", padding: "1rem", textAlign: "left" }} />
                {products.map((product, pi) => (
                  <th
                    key={product.id}
                    style={{
                      padding: "1.5rem 1rem",
                      textAlign: "center",
                      verticalAlign: "bottom",
                      borderRadius: product.highlighted ? "var(--radius-lg) var(--radius-lg) 0 0" : undefined,
                      background: product.highlighted
                        ? "color-mix(in srgb, var(--color-accent) 8%, var(--color-background))"
                        : undefined,
                    }}
                  >
                    <motion.div
                      initial={{ opacity: 0, y: 12 }}
                      whileInView={{ opacity: 1, y: 0 }}
                      viewport={{ once: true }}
                      transition={{ delay: pi * 0.1, duration: 0.4, ease: EASE }}
                    >
                      {product.highlighted && (
                        <span
                          style={{
                            display: "inline-block",
                            fontSize: "0.6875rem",
                            fontWeight: 600,
                            textTransform: "uppercase",
                            letterSpacing: "0.08em",
                            color: "var(--color-accent)",
                            marginBottom: "0.75rem",
                          }}
                        >
                          Recommande
                        </span>
                      )}
                      <div
                        style={{
                          width: 64,
                          height: 64,
                          margin: "0 auto 0.75rem",
                          borderRadius: "var(--radius-md)",
                          background: "var(--color-background-alt)",
                          overflow: "hidden",
                        }}
                      >
                        {product.imageSrc ? (
                          <img
                            src={product.imageSrc}
                            alt={product.imageAlt || product.name}
                            style={{ width: "100%", height: "100%", objectFit: "contain" }}
                          />
                        ) : (
                          <div
                            style={{
                              width: "100%",
                              height: "100%",
                              background: `radial-gradient(circle, color-mix(in srgb, var(--color-accent) 10%, transparent), transparent)`,
                            }}
                          />
                        )}
                      </div>
                      <div
                        style={{
                          fontWeight: 700,
                          fontSize: "1rem",
                          color: "var(--color-foreground)",
                        }}
                      >
                        {product.name}
                      </div>
                      <div
                        style={{
                          fontSize: "1.125rem",
                          fontWeight: 700,
                          color: "var(--color-accent)",
                          marginTop: "0.25rem",
                        }}
                      >
                        {product.price}
                      </div>
                    </motion.div>
                  </th>
                ))}
              </tr>
            </thead>

            {/* Comparison rows */}
            <tbody>
              {rows.map((row, ri) => (
                <tr key={ri}>
                  <td
                    style={{
                      padding: "0.875rem 1rem",
                      fontSize: "0.9375rem",
                      color: "var(--color-foreground-muted)",
                      borderBottom: "1px solid var(--color-border)",
                    }}
                  >
                    {row.label}
                  </td>
                  {row.values.map((val, vi) => (
                    <td
                      key={vi}
                      style={{
                        padding: "0.875rem 1rem",
                        textAlign: "center",
                        borderBottom: "1px solid var(--color-border)",
                        background: products[vi]?.highlighted
                          ? "color-mix(in srgb, var(--color-accent) 8%, var(--color-background))"
                          : undefined,
                      }}
                    >
                      {typeof val === "boolean" ? (
                        val ? (
                          <Check style={{ width: 18, height: 18, color: "var(--color-accent)", margin: "0 auto" }} />
                        ) : (
                          <Minus style={{ width: 18, height: 18, color: "var(--color-foreground-muted)", margin: "0 auto", opacity: 0.4 }} />
                        )
                      ) : (
                        <span style={{ fontSize: "0.9375rem", fontWeight: 500, color: "var(--color-foreground)" }}>
                          {val}
                        </span>
                      )}
                    </td>
                  ))}
                </tr>
              ))}
            </tbody>

            {/* CTA row */}
            <tfoot>
              <tr>
                <td style={{ padding: "1.5rem 1rem" }} />
                {products.map((product) => (
                  <td
                    key={product.id}
                    style={{
                      padding: "1.5rem 1rem",
                      textAlign: "center",
                      borderRadius: product.highlighted ? "0 0 var(--radius-lg) var(--radius-lg)" : undefined,
                      background: product.highlighted
                        ? "color-mix(in srgb, var(--color-accent) 8%, var(--color-background))"
                        : undefined,
                    }}
                  >
                    <a
                      href={product.ctaUrl || "#"}
                      style={{
                        display: "inline-block",
                        padding: "0.75rem 1.5rem",
                        borderRadius: "var(--radius-full)",
                        background: product.highlighted ? "var(--color-accent)" : "transparent",
                        border: product.highlighted ? "none" : "1px solid var(--color-border)",
                        color: product.highlighted ? "var(--color-foreground)" : "var(--color-foreground-muted)",
                        fontWeight: 600,
                        fontSize: "0.875rem",
                        textDecoration: "none",
                      }}
                    >
                      {product.ctaLabel || "Choisir"}
                    </a>
                  </td>
                ))}
              </tr>
            </tfoot>
          </table>
        </motion.div>
      </div>
    </section>
  );
}

Avis

Product Showcase Comparison — React Product-showcase Section — Incubator