Retour au catalogue

API Integration Cards

Cartes d'integration API avec badge de statut, snippet de code et documentation inline.

integrationscomplex Both Responsive a11y
minimalcorporatesaasagencygrid
Theme
"use client";

import { useRef, useState } from "react";
import { motion, useInView } from "framer-motion";
import { Code2, Copy, Check, ExternalLink, Terminal } from "lucide-react";

interface ApiCard {
  name: string;
  method: string;
  endpoint: string;
  description: string;
  snippet: string;
  status: string;
}

interface IntegrationApiCardsProps {
  title?: string;
  subtitle?: string;
  apis?: ApiCard[];
  docsLabel?: string;
  docsLink?: string;
}

const EASE: [number, number, number, number] = [0.16, 1, 0.3, 1];

const methodColors: Record<string, string> = {
  GET: "var(--color-accent)",
  POST: "var(--color-accent)",
  PUT: "var(--color-foreground-muted)",
  DELETE: "var(--color-foreground-muted)",
};

export default function IntegrationApiCards({
  title = "API REST",
  subtitle = "Integrez en quelques minutes.",
  apis = [],
  docsLabel = "Documentation",
  docsLink = "#",
}: IntegrationApiCardsProps) {
  const ref = useRef<HTMLDivElement>(null);
  const inView = useInView(ref, { once: true, margin: "-80px" });
  const [copiedIndex, setCopiedIndex] = useState<number | null>(null);

  const handleCopy = (snippet: string, index: number) => {
    navigator.clipboard.writeText(snippet).catch(() => {});
    setCopiedIndex(index);
    setTimeout(() => setCopiedIndex(null), 2000);
  };

  return (
    <section
      ref={ref}
      style={{
        padding: "5rem 1.5rem",
        background: "var(--color-background)",
      }}
    >
      <div style={{ maxWidth: 900, margin: "0 auto" }}>
        {/* Header */}
        <motion.div
          initial={{ opacity: 0, y: 20 }}
          animate={inView ? { opacity: 1, y: 0 } : {}}
          transition={{ duration: 0.6, ease: EASE }}
          style={{ textAlign: "center", marginBottom: "3rem" }}
        >
          <span
            style={{
              display: "inline-flex",
              alignItems: "center",
              gap: 6,
              padding: "0.375rem 0.875rem",
              borderRadius: "var(--radius-full)",
              border: "1px solid var(--color-border)",
              background: "var(--color-background-card)",
              fontSize: "0.8125rem",
              fontWeight: 500,
              color: "var(--color-foreground-muted)",
              marginBottom: "1.25rem",
            }}
          >
            <Terminal style={{ width: 14, height: 14, color: "var(--color-accent)" }} />
            API v3
          </span>
          <h2
            style={{
              fontSize: "clamp(1.5rem, 3vw, 2.25rem)",
              fontWeight: 700,
              color: "var(--color-foreground)",
              letterSpacing: "-0.02em",
              marginBottom: "0.75rem",
            }}
          >
            {title}
          </h2>
          <p style={{ fontSize: "1rem", color: "var(--color-foreground-muted)", lineHeight: 1.6, maxWidth: 480, margin: "0 auto" }}>
            {subtitle}
          </p>
        </motion.div>

        {/* API Cards grid */}
        <div style={{ display: "grid", gridTemplateColumns: "repeat(auto-fill, minmax(380px, 1fr))", gap: 16, marginBottom: "2.5rem" }}>
          {apis.map((api, i) => (
            <motion.div
              key={api.endpoint}
              initial={{ opacity: 0, y: 20 }}
              animate={inView ? { opacity: 1, y: 0 } : {}}
              transition={{ duration: 0.5, delay: i * 0.1, ease: EASE }}
              style={{
                borderRadius: "var(--radius-lg)",
                border: "1px solid var(--color-border)",
                background: "var(--color-background-card)",
                overflow: "hidden",
              }}
            >
              {/* Card header */}
              <div style={{ padding: "1rem 1.25rem", borderBottom: "1px solid var(--color-border)", display: "flex", alignItems: "center", justifyContent: "space-between" }}>
                <div style={{ display: "flex", alignItems: "center", gap: 10 }}>
                  <span
                    style={{
                      padding: "0.2rem 0.5rem",
                      borderRadius: "var(--radius-sm)",
                      background: "var(--color-accent-subtle)",
                      fontSize: "0.6875rem",
                      fontWeight: 700,
                      fontFamily: "monospace",
                      color: methodColors[api.method] || "var(--color-foreground)",
                    }}
                  >
                    {api.method}
                  </span>
                  <code style={{ fontSize: "0.8125rem", color: "var(--color-foreground)", fontFamily: "monospace" }}>
                    {api.endpoint}
                  </code>
                </div>
                <span
                  style={{
                    padding: "0.2rem 0.5rem",
                    borderRadius: "var(--radius-full)",
                    fontSize: "0.625rem",
                    fontWeight: 600,
                    textTransform: "uppercase",
                    letterSpacing: "0.05em",
                    background: api.status === "beta" ? "var(--color-accent-subtle)" : "var(--color-background-alt)",
                    color: api.status === "beta" ? "var(--color-accent)" : "var(--color-foreground-muted)",
                  }}
                >
                  {api.status}
                </span>
              </div>

              {/* Description */}
              <div style={{ padding: "1rem 1.25rem" }}>
                <p style={{ fontSize: "0.875rem", fontWeight: 600, color: "var(--color-foreground)", marginBottom: 4 }}>
                  {api.name}
                </p>
                <p style={{ fontSize: "0.8125rem", color: "var(--color-foreground-muted)", lineHeight: 1.5 }}>
                  {api.description}
                </p>
              </div>

              {/* Code snippet */}
              <div
                style={{
                  margin: "0 1.25rem 1rem",
                  padding: "0.75rem 1rem",
                  borderRadius: "var(--radius-md)",
                  background: "var(--color-background-alt)",
                  border: "1px solid var(--color-border)",
                  position: "relative",
                }}
              >
                <pre style={{ fontSize: "0.75rem", color: "var(--color-foreground-muted)", fontFamily: "monospace", whiteSpace: "pre-wrap", wordBreak: "break-all", margin: 0, lineHeight: 1.5 }}>
                  {api.snippet}
                </pre>
                <button
                  onClick={() => handleCopy(api.snippet, i)}
                  style={{
                    position: "absolute",
                    top: 8,
                    right: 8,
                    padding: 4,
                    borderRadius: "var(--radius-sm)",
                    border: "none",
                    background: "transparent",
                    cursor: "pointer",
                    color: "var(--color-foreground-light)",
                  }}
                  aria-label="Copier"
                >
                  {copiedIndex === i ? (
                    <Check style={{ width: 14, height: 14, color: "var(--color-accent)" }} />
                  ) : (
                    <Copy style={{ width: 14, height: 14 }} />
                  )}
                </button>
              </div>
            </motion.div>
          ))}
        </div>

        {/* Docs link */}
        <div style={{ textAlign: "center" }}>
          <a
            href={docsLink}
            style={{
              display: "inline-flex",
              alignItems: "center",
              gap: 6,
              padding: "0.75rem 1.5rem",
              borderRadius: "var(--radius-md)",
              border: "1px solid var(--color-border)",
              background: "var(--color-background-card)",
              fontSize: "0.875rem",
              fontWeight: 600,
              color: "var(--color-foreground)",
              textDecoration: "none",
            }}
          >
            <Code2 style={{ width: 16, height: 16, color: "var(--color-accent)" }} />
            {docsLabel}
            <ExternalLink style={{ width: 14, height: 14, color: "var(--color-foreground-light)" }} />
          </a>
        </div>
      </div>
    </section>
  );
}

Avis

API Integration Cards — React Integrations Section — Incubator