Retour au catalogue

Roadmap Gantt Style

Diagramme de Gantt simplifie avec barres horizontales, entete de timeline trimestrielle et categories colorees.

roadmapcomplex Both Responsive a11y
corporateminimalsaasagencyuniversalgrid
Theme
"use client";

import { motion } from "framer-motion";
import { BarChart3 } from "lucide-react";

interface GanttTask {
  label: string;
  category: string;
  start: number;
  end: number;
  color: string;
}

interface RoadmapGanttStyleProps {
  title?: string;
  description?: string;
  quarters?: string[];
  tasks?: GanttTask[];
}

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

export default function RoadmapGanttStyle({
  title = "Planning produit",
  description = "Vue d'ensemble de nos chantiers",
  quarters = [],
  tasks = [],
}: RoadmapGanttStyleProps) {
  const totalCols = quarters.length || 4;

  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: "2.5rem" }}
        >
          <div style={{ display: "inline-flex", alignItems: "center", gap: "0.5rem", marginBottom: "0.75rem" }}>
            <BarChart3 style={{ width: 20, height: 20, color: "var(--color-accent)" }} />
            <h2 style={{ fontFamily: "var(--font-sans)", fontSize: "clamp(1.75rem, 3.5vw, 2.75rem)", fontWeight: 700, color: "var(--color-foreground)" }}>
              {title}
            </h2>
          </div>
          <p style={{ fontSize: "1.0625rem", color: "var(--color-foreground-muted)", maxWidth: "520px", margin: "0 auto" }}>
            {description}
          </p>
        </motion.div>

        <div style={{ maxWidth: "800px", margin: "0 auto", overflowX: "auto" }}>
          {/* Quarter headers */}
          <div style={{ display: "grid", gridTemplateColumns: `180px repeat(${totalCols}, 1fr)`, gap: 0, marginBottom: "0.5rem" }}>
            <div />
            {quarters.map((q, i) => (
              <div key={i} style={{ fontSize: "0.75rem", fontWeight: 700, color: "var(--color-foreground-muted)", textTransform: "uppercase", letterSpacing: "0.05em", textAlign: "center", padding: "0.5rem 0", borderBottom: "2px solid var(--color-border)" }}>
                {q}
              </div>
            ))}
          </div>

          {/* Task rows */}
          {tasks.map((task, i) => (
            <motion.div
              key={i}
              initial={{ opacity: 0, x: -16 }}
              whileInView={{ opacity: 1, x: 0 }}
              viewport={{ once: true }}
              transition={{ duration: 0.4, delay: i * 0.06, ease: EASE }}
              style={{ display: "grid", gridTemplateColumns: `180px repeat(${totalCols}, 1fr)`, gap: 0, alignItems: "center", minHeight: 48, borderBottom: "1px solid var(--color-border)" }}
            >
              <div style={{ padding: "0.5rem 0.75rem 0.5rem 0" }}>
                <div style={{ fontSize: "0.8125rem", fontWeight: 500, color: "var(--color-foreground)" }}>{task.label}</div>
                <div style={{ fontSize: "0.6875rem", color: "var(--color-foreground-muted)" }}>{task.category}</div>
              </div>
              {Array.from({ length: totalCols }).map((_, ci) => (
                <div key={ci} style={{ padding: "0.25rem 2px", height: "100%", display: "flex", alignItems: "center" }}>
                  {ci >= task.start && ci < task.end && (
                    <motion.div
                      initial={{ scaleX: 0 }}
                      whileInView={{ scaleX: 1 }}
                      viewport={{ once: true }}
                      transition={{ duration: 0.5, delay: 0.1 + i * 0.06, ease: EASE }}
                      style={{
                        width: "100%", height: 28, borderRadius: "var(--radius-sm)",
                        background: task.color, opacity: 0.85,
                        transformOrigin: "left center",
                        borderTopLeftRadius: ci === task.start ? "var(--radius-md)" : 0,
                        borderBottomLeftRadius: ci === task.start ? "var(--radius-md)" : 0,
                        borderTopRightRadius: ci === task.end - 1 ? "var(--radius-md)" : 0,
                        borderBottomRightRadius: ci === task.end - 1 ? "var(--radius-md)" : 0,
                      }}
                    />
                  )}
                </div>
              ))}
            </motion.div>
          ))}
        </div>
      </div>
    </section>
  );
}

Avis

Roadmap Gantt Style — React Roadmap Section — Incubator