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