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>
);
}