Retour au catalogue

Code Block Live Editor

Editeur de code en split-view avec apercu en direct a droite. Onglets de fichiers, coloration syntaxique simulee et zone de preview interactive.

code-blockcomplex Both Responsive a11y
darkboldsaaseducationuniversalsplit
Theme
"use client";

import { motion } from "framer-motion";
import { Play, FileCode, Eye } from "lucide-react";
import { useState } from "react";

interface CodeLine {
  text: string;
  color?: "accent" | "muted" | "string" | "comment" | "keyword";
}

interface Tab {
  name: string;
  language: string;
  lines: CodeLine[];
}

interface CodeBlockLiveEditorProps {
  title?: string;
  description?: string;
  tabs?: Tab[];
  previewTitle?: string;
}

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

const colorMap: Record<string, string> = {
  accent: "var(--color-accent)",
  muted: "var(--color-foreground-muted)",
  string: "#a5d6a7",
  comment: "var(--color-foreground-light)",
  keyword: "#ce93d8",
};

export default function CodeBlockLiveEditor({
  title = "Editeur en direct",
  description = "Modifiez le code et voyez le resultat instantanement.",
  tabs = [],
  previewTitle = "Apercu",
}: CodeBlockLiveEditorProps) {
  const [activeTab, setActiveTab] = useState(0);
  const currentTab = tabs[activeTab];

  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)",
        }}
      >
        {/* Header */}
        <motion.div
          initial={{ opacity: 0, y: 16 }}
          animate={{ opacity: 1, y: 0 }}
          transition={{ duration: 0.5, ease: EASE }}
          style={{ textAlign: "center", marginBottom: "2.5rem" }}
        >
          <h2
            style={{
              fontFamily: "var(--font-sans)",
              fontSize: "clamp(1.75rem, 3vw, 2.5rem)",
              fontWeight: 700,
              color: "var(--color-foreground)",
              marginBottom: "0.75rem",
              letterSpacing: "-0.02em",
            }}
          >
            {title}
          </h2>
          <p
            style={{
              fontSize: "1.0625rem",
              color: "var(--color-foreground-muted)",
              maxWidth: 480,
              margin: "0 auto",
              lineHeight: 1.6,
            }}
          >
            {description}
          </p>
        </motion.div>

        {/* Editor */}
        <motion.div
          initial={{ opacity: 0, y: 24 }}
          animate={{ opacity: 1, y: 0 }}
          transition={{ duration: 0.6, delay: 0.1, ease: EASE }}
          style={{
            maxWidth: 960,
            margin: "0 auto",
            borderRadius: "var(--radius-lg)",
            border: "1px solid var(--color-border)",
            background: "var(--color-background-alt)",
            overflow: "hidden",
            display: "grid",
            gridTemplateColumns: "1fr 1fr",
            minHeight: 420,
          }}
        >
          {/* Left: code editor */}
          <div
            style={{
              borderRight: "1px solid var(--color-border)",
              display: "flex",
              flexDirection: "column",
            }}
          >
            {/* Tab bar */}
            <div
              style={{
                display: "flex",
                alignItems: "center",
                borderBottom: "1px solid var(--color-border)",
                background: "var(--color-background-card)",
              }}
            >
              {tabs.map((tab, i) => (
                <button
                  key={i}
                  onClick={() => setActiveTab(i)}
                  style={{
                    padding: "0.625rem 1rem",
                    fontSize: "0.75rem",
                    fontFamily: "var(--font-mono, monospace)",
                    color:
                      i === activeTab
                        ? "var(--color-foreground)"
                        : "var(--color-foreground-muted)",
                    background:
                      i === activeTab
                        ? "var(--color-background-alt)"
                        : "transparent",
                    border: "none",
                    borderBottom:
                      i === activeTab
                        ? "2px solid var(--color-accent)"
                        : "2px solid transparent",
                    cursor: "pointer",
                    display: "flex",
                    alignItems: "center",
                    gap: "6px",
                  }}
                >
                  <FileCode style={{ width: 12, height: 12 }} />
                  {tab.name}
                </button>
              ))}
            </div>

            {/* Code body */}
            <div
              style={{
                padding: "1rem 0",
                fontFamily: "var(--font-mono, monospace)",
                fontSize: "0.8125rem",
                lineHeight: 1.9,
                overflowX: "auto",
                flex: 1,
              }}
            >
              {currentTab?.lines.map((line, i) => (
                <motion.div
                  key={`${activeTab}-${i}`}
                  initial={{ opacity: 0, x: -6 }}
                  animate={{ opacity: 1, x: 0 }}
                  transition={{
                    duration: 0.25,
                    delay: i * 0.03,
                    ease: EASE,
                  }}
                  style={{ display: "flex" }}
                >
                  <span
                    style={{
                      width: 40,
                      textAlign: "right",
                      padding: "0 12px 0 0",
                      color: "var(--color-foreground-light)",
                      opacity: 0.3,
                      fontSize: "0.6875rem",
                      userSelect: "none",
                    }}
                  >
                    {i + 1}
                  </span>
                  <span
                    style={{
                      color: colorMap[line.color ?? "muted"],
                      paddingLeft: "0.75rem",
                      whiteSpace: "pre",
                    }}
                  >
                    {line.text}
                  </span>
                </motion.div>
              ))}
            </div>
          </div>

          {/* Right: preview */}
          <div
            style={{
              display: "flex",
              flexDirection: "column",
              background: "var(--color-background)",
            }}
          >
            {/* Preview header */}
            <div
              style={{
                display: "flex",
                alignItems: "center",
                justifyContent: "space-between",
                padding: "0.625rem 1rem",
                borderBottom: "1px solid var(--color-border)",
                background: "var(--color-background-card)",
              }}
            >
              <div
                style={{
                  display: "flex",
                  alignItems: "center",
                  gap: "6px",
                  fontSize: "0.75rem",
                  color: "var(--color-foreground-muted)",
                }}
              >
                <Eye style={{ width: 12, height: 12 }} />
                {previewTitle}
              </div>
              <div
                style={{
                  display: "flex",
                  alignItems: "center",
                  gap: "4px",
                  padding: "3px 8px",
                  borderRadius: "var(--radius-sm)",
                  background: "var(--color-accent)",
                  color: "var(--color-background)",
                  fontSize: "0.6875rem",
                  fontWeight: 600,
                }}
              >
                <Play style={{ width: 10, height: 10 }} />
                Live
              </div>
            </div>

            {/* Preview content (simulated) */}
            <div
              style={{
                flex: 1,
                display: "flex",
                flexDirection: "column",
                alignItems: "center",
                justifyContent: "center",
                padding: "2rem",
                gap: "1rem",
              }}
            >
              <motion.div
                initial={{ opacity: 0, scale: 0.95 }}
                animate={{ opacity: 1, scale: 1 }}
                transition={{ duration: 0.5, delay: 0.3, ease: EASE }}
                style={{ textAlign: "center" }}
              >
                <h3
                  style={{
                    fontFamily: "var(--font-sans)",
                    fontSize: "1.5rem",
                    fontWeight: 700,
                    color: "var(--color-foreground)",
                    marginBottom: "1rem",
                  }}
                >
                  Bonjour le monde
                </h3>
                <motion.button
                  whileHover={{ scale: 1.05 }}
                  whileTap={{ scale: 0.95 }}
                  style={{
                    padding: "0.625rem 1.5rem",
                    borderRadius: "var(--radius-md)",
                    background: "var(--color-accent)",
                    color: "var(--color-background)",
                    border: "none",
                    fontSize: "0.9375rem",
                    fontWeight: 600,
                    cursor: "pointer",
                    fontFamily: "var(--font-sans)",
                  }}
                >
                  Cliquez ici
                </motion.button>
              </motion.div>

              {/* Console output */}
              <div
                style={{
                  width: "100%",
                  marginTop: "auto",
                  padding: "0.75rem 1rem",
                  borderRadius: "var(--radius-sm)",
                  background: "var(--color-background-alt)",
                  border: "1px solid var(--color-border)",
                  fontFamily: "var(--font-mono, monospace)",
                  fontSize: "0.6875rem",
                  color: "var(--color-foreground-muted)",
                }}
              >
                <span style={{ color: "var(--color-accent)", opacity: 0.7 }}>
                  {">"}{" "}
                </span>
                Rendu reussi en 12ms
              </div>
            </div>
          </div>
        </motion.div>
      </div>

      <style>{`
        @media (max-width: 768px) {
          section > div > div:last-child {
            grid-template-columns: 1fr !important;
          }
        }
      `}</style>
    </section>
  );
}

Avis

Code Block Live Editor — React Code-block Section — Incubator