Retour au catalogue

Metrics Cards

Cards de metriques avec icones, valeurs et indicateurs de tendance (fleches haut/bas).

metricssimple Both Responsive a11y
minimalcorporatesaasecommerceuniversalgrid
Theme
"use client";

import { motion } from "framer-motion";
import { TrendingUp, TrendingDown, Users, DollarSign, ShoppingCart, Eye } from "lucide-react";
import type { LucideIcon } from "lucide-react";

interface MetricCard {
  label: string;
  value: string;
  change?: string;
  trend?: "up" | "down";
  icon?: string;
}

interface MetricsCardsProps {
  title?: string;
  description?: string;
  cards?: MetricCard[];
}

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

const iconMap: Record<string, LucideIcon> = {
  users: Users,
  dollar: DollarSign,
  cart: ShoppingCart,
  eye: Eye,
};

export default function MetricsCards({
  title = "Nos chiffres cles",
  description = "Des resultats concrets et mesurables",
  cards = [],
}: MetricsCardsProps) {
  return (
    <section
      style={{
        padding: "var(--section-padding-y) 0",
        background: "var(--color-background)",
      }}
    >
      <div
        style={{
          maxWidth: "var(--container-max-width)",
          margin: "0 auto",
          padding: "0 var(--container-padding-x)",
        }}
      >
        <motion.div
          initial={{ opacity: 0, y: 16 }}
          whileInView={{ opacity: 1, y: 0 }}
          viewport={{ once: true }}
          transition={{ duration: 0.5, ease: EASE }}
          style={{ textAlign: "center", marginBottom: "3rem" }}
        >
          <h2
            style={{
              fontFamily: "var(--font-sans)",
              fontSize: "clamp(1.75rem, 3.5vw, 2.75rem)",
              fontWeight: 700,
              color: "var(--color-foreground)",
              marginBottom: "0.75rem",
            }}
          >
            {title}
          </h2>
          <p style={{ fontSize: "1.0625rem", color: "var(--color-foreground-muted)", maxWidth: "520px", margin: "0 auto" }}>
            {description}
          </p>
        </motion.div>

        <div
          style={{
            display: "grid",
            gridTemplateColumns: "repeat(auto-fit, minmax(220px, 1fr))",
            gap: "1.25rem",
          }}
        >
          {cards.map((card, i) => {
            const Icon = card.icon ? iconMap[card.icon] : null;
            const TrendIcon = card.trend === "up" ? TrendingUp : TrendingDown;
            const trendColor = card.trend === "up" ? "var(--color-accent)" : "var(--color-foreground-muted)";

            return (
              <motion.div
                key={i}
                initial={{ opacity: 0, y: 20 }}
                whileInView={{ opacity: 1, y: 0 }}
                viewport={{ once: true }}
                transition={{ duration: 0.45, delay: i * 0.06, ease: EASE }}
                style={{
                  padding: "1.5rem",
                  borderRadius: "var(--radius-lg)",
                  background: "var(--color-background-alt)",
                  border: "1px solid var(--color-border)",
                  display: "flex",
                  flexDirection: "column",
                  gap: "0.75rem",
                }}
              >
                <div style={{ display: "flex", alignItems: "center", justifyContent: "space-between" }}>
                  <span style={{ fontSize: "0.8125rem", fontWeight: 500, color: "var(--color-foreground-muted)" }}>
                    {card.label}
                  </span>
                  {Icon && (
                    <div
                      style={{
                        width: 32,
                        height: 32,
                        borderRadius: "var(--radius-md)",
                        background: "var(--color-accent-subtle)",
                        display: "flex",
                        alignItems: "center",
                        justifyContent: "center",
                      }}
                    >
                      <Icon style={{ width: 16, height: 16, color: "var(--color-accent)" }} />
                    </div>
                  )}
                </div>

                <span
                  style={{
                    fontFamily: "var(--font-mono, var(--font-sans))",
                    fontSize: "clamp(1.5rem, 2.5vw, 2rem)",
                    fontWeight: 700,
                    color: "var(--color-foreground)",
                  }}
                >
                  {card.value}
                </span>

                {card.change && card.trend && (
                  <div style={{ display: "flex", alignItems: "center", gap: "0.375rem" }}>
                    <TrendIcon style={{ width: 14, height: 14, color: trendColor }} />
                    <span style={{ fontSize: "0.8125rem", fontWeight: 500, color: trendColor }}>
                      {card.change}
                    </span>
                  </div>
                )}
              </motion.div>
            );
          })}
        </div>
      </div>
    </section>
  );
}

Avis

Metrics Cards — React Metrics Section — Incubator