Retour au catalogue

Code Refactor

Comparaison avant/apres de refactoring de code avec coloration syntaxique et diff anime.

before-aftermedium Both Responsive a11y
minimalelegantsaasagencysplit
Theme
"use client";

import { useRef, useState } from "react";
import { motion, useInView } from "framer-motion";
import { Code2, ArrowRight, TrendingUp, Minus, Plus } from "lucide-react";

interface Metric {
  label: string;
  before: string;
  after: string;
  improvement: string;
}

interface BeforeAfterCodeRefactorProps {
  title?: string;
  subtitle?: string;
  beforeLabel?: string;
  afterLabel?: string;
  beforeCode?: string;
  afterCode?: string;
  metrics?: Metric[];
}

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

export default function BeforeAfterCodeRefactor({
  title = "Avant / Apres",
  subtitle = "Transformez votre code.",
  beforeLabel = "Avant",
  afterLabel = "Apres",
  beforeCode = "",
  afterCode = "",
  metrics = [],
}: BeforeAfterCodeRefactorProps) {
  const ref = useRef<HTMLDivElement>(null);
  const inView = useInView(ref, { once: true, margin: "-80px" });
  const [activeTab, setActiveTab] = useState<"before" | "after">("before");

  return (
    <section
      ref={ref}
      style={{ padding: "5rem 1.5rem", background: "var(--color-background)" }}
    >
      <div style={{ maxWidth: 960, 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",
            }}
          >
            <Code2 style={{ width: 14, height: 14, color: "var(--color-accent)" }} />
            Refactoring
          </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>

        {/* Code comparison — desktop: side by side, mobile: tabbed */}
        {/* Tab switcher for mobile */}
        <div
          style={{
            display: "none",
            justifyContent: "center",
            gap: 4,
            marginBottom: "1rem",
            padding: 4,
            borderRadius: "var(--radius-md)",
            background: "var(--color-background-alt)",
            maxWidth: 240,
            margin: "0 auto 1rem",
          }}
          className="mobile-tabs"
        >
          <button
            onClick={() => setActiveTab("before")}
            style={{
              flex: 1,
              padding: "0.5rem",
              borderRadius: "var(--radius-sm)",
              border: "none",
              background: activeTab === "before" ? "var(--color-background-card)" : "transparent",
              color: activeTab === "before" ? "var(--color-foreground)" : "var(--color-foreground-muted)",
              fontWeight: 500,
              fontSize: "0.8125rem",
              cursor: "pointer",
              display: "flex",
              alignItems: "center",
              justifyContent: "center",
              gap: 4,
            }}
          >
            <Minus style={{ width: 12, height: 12 }} />
            {beforeLabel}
          </button>
          <button
            onClick={() => setActiveTab("after")}
            style={{
              flex: 1,
              padding: "0.5rem",
              borderRadius: "var(--radius-sm)",
              border: "none",
              background: activeTab === "after" ? "var(--color-background-card)" : "transparent",
              color: activeTab === "after" ? "var(--color-foreground)" : "var(--color-foreground-muted)",
              fontWeight: 500,
              fontSize: "0.8125rem",
              cursor: "pointer",
              display: "flex",
              alignItems: "center",
              justifyContent: "center",
              gap: 4,
            }}
          >
            <Plus style={{ width: 12, height: 12 }} />
            {afterLabel}
          </button>
        </div>

        {/* Split view */}
        <motion.div
          initial={{ opacity: 0, y: 20 }}
          animate={inView ? { opacity: 1, y: 0 } : {}}
          transition={{ duration: 0.5, delay: 0.15, ease: EASE }}
          style={{ display: "grid", gridTemplateColumns: "1fr 1fr", gap: 16, marginBottom: "2.5rem" }}
        >
          {/* Before */}
          <div
            style={{
              borderRadius: "var(--radius-lg)",
              border: "1px solid var(--color-border)",
              background: "var(--color-background-card)",
              overflow: "hidden",
            }}
          >
            <div
              style={{
                padding: "0.75rem 1rem",
                borderBottom: "1px solid var(--color-border)",
                display: "flex",
                alignItems: "center",
                gap: 8,
                background: "var(--color-background-alt)",
              }}
            >
              <div style={{ display: "flex", gap: 6 }}>
                <div style={{ width: 10, height: 10, borderRadius: "50%", background: "var(--color-border)" }} />
                <div style={{ width: 10, height: 10, borderRadius: "50%", background: "var(--color-border)" }} />
                <div style={{ width: 10, height: 10, borderRadius: "50%", background: "var(--color-border)" }} />
              </div>
              <span style={{ fontSize: "0.75rem", fontWeight: 600, color: "var(--color-foreground-muted)", marginLeft: 8 }}>
                {beforeLabel}
              </span>
            </div>
            <pre
              style={{
                padding: "1rem 1.25rem",
                margin: 0,
                fontSize: "0.75rem",
                lineHeight: 1.7,
                color: "var(--color-foreground-muted)",
                fontFamily: "monospace",
                whiteSpace: "pre-wrap",
                overflow: "auto",
                minHeight: 200,
              }}
            >
              {beforeCode}
            </pre>
          </div>

          {/* Arrow indicator */}
          <div style={{ display: "none", alignItems: "center", justifyContent: "center" }}>
            <motion.div
              animate={{ x: [0, 8, 0] }}
              transition={{ duration: 1.5, repeat: Infinity, ease: "easeInOut" }}
            >
              <ArrowRight style={{ width: 24, height: 24, color: "var(--color-accent)" }} />
            </motion.div>
          </div>

          {/* After */}
          <div
            style={{
              borderRadius: "var(--radius-lg)",
              border: "1px solid var(--color-accent)",
              background: "var(--color-background-card)",
              overflow: "hidden",
              boxShadow: "0 0 0 1px var(--color-accent-subtle)",
            }}
          >
            <div
              style={{
                padding: "0.75rem 1rem",
                borderBottom: "1px solid var(--color-border)",
                display: "flex",
                alignItems: "center",
                gap: 8,
                background: "var(--color-accent-subtle)",
              }}
            >
              <div style={{ display: "flex", gap: 6 }}>
                <div style={{ width: 10, height: 10, borderRadius: "50%", background: "var(--color-accent)" }} />
                <div style={{ width: 10, height: 10, borderRadius: "50%", background: "var(--color-border)" }} />
                <div style={{ width: 10, height: 10, borderRadius: "50%", background: "var(--color-border)" }} />
              </div>
              <span style={{ fontSize: "0.75rem", fontWeight: 600, color: "var(--color-accent)", marginLeft: 8 }}>
                {afterLabel}
              </span>
            </div>
            <pre
              style={{
                padding: "1rem 1.25rem",
                margin: 0,
                fontSize: "0.75rem",
                lineHeight: 1.7,
                color: "var(--color-foreground)",
                fontFamily: "monospace",
                whiteSpace: "pre-wrap",
                overflow: "auto",
                minHeight: 200,
              }}
            >
              {afterCode}
            </pre>
          </div>
        </motion.div>

        {/* Metrics */}
        {metrics.length > 0 && (
          <motion.div
            initial={{ opacity: 0, y: 20 }}
            animate={inView ? { opacity: 1, y: 0 } : {}}
            transition={{ duration: 0.5, delay: 0.3, ease: EASE }}
            style={{ display: "grid", gridTemplateColumns: `repeat(${metrics.length}, 1fr)`, gap: 12 }}
          >
            {metrics.map((metric, i) => (
              <motion.div
                key={metric.label}
                initial={{ opacity: 0, y: 12 }}
                animate={inView ? { opacity: 1, y: 0 } : {}}
                transition={{ duration: 0.4, delay: 0.4 + i * 0.1, ease: EASE }}
                style={{
                  padding: "1rem 1.25rem",
                  borderRadius: "var(--radius-lg)",
                  border: "1px solid var(--color-border)",
                  background: "var(--color-background-card)",
                  textAlign: "center",
                }}
              >
                <p style={{ fontSize: "0.6875rem", fontWeight: 500, textTransform: "uppercase", letterSpacing: "0.1em", color: "var(--color-foreground-light)", marginBottom: "0.5rem" }}>
                  {metric.label}
                </p>
                <div style={{ display: "flex", alignItems: "center", justifyContent: "center", gap: 8, marginBottom: "0.5rem" }}>
                  <span style={{ fontSize: "0.875rem", color: "var(--color-foreground-muted)", textDecoration: "line-through" }}>{metric.before}</span>
                  <ArrowRight style={{ width: 12, height: 12, color: "var(--color-foreground-light)" }} />
                  <span style={{ fontSize: "1.125rem", fontWeight: 700, color: "var(--color-foreground)" }}>{metric.after}</span>
                </div>
                <div style={{ display: "inline-flex", alignItems: "center", gap: 4, padding: "0.2rem 0.5rem", borderRadius: "var(--radius-full)", background: "var(--color-accent-subtle)" }}>
                  <TrendingUp style={{ width: 12, height: 12, color: "var(--color-accent)" }} />
                  <span style={{ fontSize: "0.75rem", fontWeight: 600, color: "var(--color-accent)" }}>{metric.improvement}</span>
                </div>
              </motion.div>
            ))}
          </motion.div>
        )}
      </div>
    </section>
  );
}

Avis

Code Refactor — React Before-after Section — Incubator