Retour au catalogue
Animated Bars
Barres horizontales animees comparant deux options, croissance au scroll. Visuel et impactant.
comparisonmedium Both Responsive a11y
boldcorporatesaasuniversalstacked
Theme
"use client";
import { motion } from "framer-motion";
import { TrendingUp } from "lucide-react";
interface Metric {
label: string;
valueA: number;
valueB: number;
unit: string;
}
interface Option {
name: string;
label: string;
}
interface ComparisonAnimatedBarsProps {
title?: string;
subtitle?: string;
optionA?: Option;
optionB?: Option;
metrics?: Metric[];
}
const E: [number, number, number, number] = [0.16, 1, 0.3, 1];
export default function ComparisonAnimatedBars({
title = "Comparaison",
subtitle = "",
optionA = { name: "Option A", label: "A" },
optionB = { name: "Option B", label: "B" },
metrics = [],
}: ComparisonAnimatedBarsProps) {
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: 20 }} whileInView={{ opacity: 1, y: 0 }} viewport={{ once: true }} transition={{ duration: 0.6, ease: E }} style={{ textAlign: "center", marginBottom: "3rem" }}>
<h2 style={{ fontFamily: "var(--font-serif)", fontSize: "clamp(1.75rem, 3vw, 2.5rem)", fontWeight: 600, color: "var(--color-foreground)", marginBottom: "0.75rem" }}>{title}</h2>
{subtitle && <p style={{ fontSize: "1rem", color: "var(--color-foreground-muted)", fontFamily: "var(--font-sans)" }}>{subtitle}</p>}
</motion.div>
{/* Legend */}
<div style={{ display: "flex", justifyContent: "center", gap: "2rem", marginBottom: "2rem" }}>
<div style={{ display: "flex", alignItems: "center", gap: "0.5rem" }}>
<div style={{ width: "12px", height: "12px", borderRadius: "var(--radius-full)", background: "var(--color-accent)" }} />
<span style={{ fontSize: "0.875rem", fontWeight: 600, color: "var(--color-foreground)", fontFamily: "var(--font-sans)" }}>{optionA.name}</span>
</div>
<div style={{ display: "flex", alignItems: "center", gap: "0.5rem" }}>
<div style={{ width: "12px", height: "12px", borderRadius: "var(--radius-full)", background: "var(--color-border)" }} />
<span style={{ fontSize: "0.875rem", fontWeight: 500, color: "var(--color-foreground-muted)", fontFamily: "var(--font-sans)" }}>{optionB.name}</span>
</div>
</div>
{/* Bars */}
<div style={{ maxWidth: "720px", margin: "0 auto", display: "flex", flexDirection: "column", gap: "1.5rem" }}>
{metrics.map((m, i) => (
<motion.div key={m.label} initial={{ opacity: 0, y: 12 }} whileInView={{ opacity: 1, y: 0 }} viewport={{ once: true }} transition={{ duration: 0.5, delay: i * 0.08, ease: E }}>
<div style={{ display: "flex", alignItems: "center", justifyContent: "space-between", marginBottom: "0.5rem" }}>
<span style={{ fontSize: "0.875rem", fontWeight: 500, color: "var(--color-foreground)", fontFamily: "var(--font-sans)" }}>{m.label}</span>
<div style={{ display: "flex", alignItems: "center", gap: "0.375rem" }}>
<TrendingUp size={14} style={{ color: "var(--color-accent)" }} />
<span style={{ fontSize: "0.8125rem", fontWeight: 700, color: "var(--color-accent)", fontFamily: "var(--font-sans)" }}>{m.valueA}{m.unit}</span>
</div>
</div>
{/* Option A bar */}
<div style={{ position: "relative", height: "28px", borderRadius: "var(--radius-full)", background: "var(--color-background-alt)", overflow: "hidden", marginBottom: "0.375rem" }}>
<motion.div
initial={{ width: 0 }}
whileInView={{ width: `${m.valueA}%` }}
viewport={{ once: true }}
transition={{ duration: 1, delay: 0.2 + i * 0.1, ease: E }}
style={{ height: "100%", borderRadius: "var(--radius-full)", background: "var(--color-accent)", display: "flex", alignItems: "center", justifyContent: "flex-end", paddingRight: "0.75rem" }}
>
<span style={{ fontSize: "0.6875rem", fontWeight: 700, color: "var(--color-background)", fontFamily: "var(--font-sans)" }}>{optionA.label}</span>
</motion.div>
</div>
{/* Option B bar */}
<div style={{ position: "relative", height: "20px", borderRadius: "var(--radius-full)", background: "var(--color-background-alt)", overflow: "hidden" }}>
<motion.div
initial={{ width: 0 }}
whileInView={{ width: `${m.valueB}%` }}
viewport={{ once: true }}
transition={{ duration: 1, delay: 0.3 + i * 0.1, ease: E }}
style={{ height: "100%", borderRadius: "var(--radius-full)", background: "var(--color-border)", display: "flex", alignItems: "center", justifyContent: "flex-end", paddingRight: "0.75rem" }}
>
<span style={{ fontSize: "0.625rem", fontWeight: 600, color: "var(--color-foreground-muted)", fontFamily: "var(--font-sans)" }}>{m.valueB}{m.unit}</span>
</motion.div>
</div>
</motion.div>
))}
</div>
</div>
</section>
);
}