Code Playground

Interactive code editor with live preview.

Button Component

A simple React button

Button.tsx
function Button({ children }) {
  return <button>{children}</button>
}
typescript

Installation

pnpm dlx shadcn@latest add https://ui.vllnt.com/r/code-playground.json
bash

Code

"use client";

import { useState } from "react";

import { Check, Code, Copy, FileCode } from "lucide-react";
import type { ReactNode } from "react";

import { cn } from "../../lib/utils";
import { Button } from "../button";

type CodeLineProps = {
  highlightLines: number[];
  line: string;
  lineNumber: number;
};

function CodeLine({ highlightLines, line, lineNumber }: CodeLineProps) {
  const isHighlighted = highlightLines.includes(lineNumber);
  return (
    <div className={cn("flex", isHighlighted && "bg-primary/10 -mx-4 px-4")}>
      <span className="select-none w-8 text-right pr-4 text-muted-foreground/50">
        {lineNumber}
      </span>
      <span>{line}</span>
    </div>
  );
}

export type CodePlaygroundProps = {
  children: ReactNode;
  description?: string;
  filename?: string;
  highlightLines?: number[];
  language?: string;
  showLineNumbers?: boolean;
  title: string;
};

export function CodePlayground({
  children,
  description,
  filename,
  highlightLines = [],
  language = "typescript",
  showLineNumbers = false,
  title,
}: CodePlaygroundProps) {
  const [copied, setCopied] = useState(false);
  const code = typeof children === "string" ? children : "";
  const lines = code.split("\n");

  const handleCopy = async () => {
    await navigator.clipboard.writeText(code);
    setCopied(true);
    setTimeout(() => {
      setCopied(false);
    }, 2000);
  };

  return (
    <div className="my-6 rounded-lg border bg-card overflow-hidden">
      <div className="flex items-center justify-between px-4 py-3 border-b bg-muted/30">
        <div className="flex items-center gap-3">
          <div className="flex h-8 w-8 items-center justify-center rounded bg-primary/10">
            <Code className="h-4 w-4 text-primary" />
          </div>
          <div>
            <h4 className="font-semibold text-sm">{title}</h4>
            {description ? (
              <p className="text-xs text-muted-foreground">{description}</p>
            ) : null}
          </div>
        </div>
        <div className="flex items-center gap-2">
          {filename ? (
            <div className="flex items-center gap-1 text-xs text-muted-foreground">
              <FileCode className="h-3 w-3" />
              <span className="font-mono">{filename}</span>
            </div>
          ) : null}
          <Button
            className="h-8 w-8"
            onClick={handleCopy}
            size="icon"
            variant="ghost"
          >
            {copied ? (
              <Check className="h-3 w-3" />
            ) : (
              <Copy className="h-3 w-3" />
            )}
          </Button>
        </div>
      </div>
      <div className="relative overflow-x-auto">
        <pre className="p-4 text-sm font-mono">
          {showLineNumbers ? (
            <code>
              {lines.map((line, index) => (
                <CodeLine
                  highlightLines={highlightLines}
                  key={index}
                  line={line}
                  lineNumber={index + 1}
                />
              ))}
            </code>
          ) : (
            <code>{code}</code>
          )}
        </pre>
      </div>
      <div className="px-4 py-2 border-t bg-muted/30">
        <span className="text-xs font-mono text-muted-foreground uppercase tracking-wider">
          {language}
        </span>
      </div>
    </div>
  );
}

export type FileTreeProps = {
  children: ReactNode;
  title?: string;
};

export function FileTree({
  children,
  title = "File Structure",
}: FileTreeProps) {
  return (
    <div className="my-6 rounded-lg border bg-card overflow-hidden">
      <div className="px-4 py-2 border-b bg-muted/30">
        <h4 className="font-semibold text-sm flex items-center gap-2">
          <FileCode className="h-4 w-4" />
          {title}
        </h4>
      </div>
      <div className="p-4 font-mono text-sm [&>ul]:m-0 [&>ul]:list-none [&_ul]:ml-4 [&_ul]:list-none [&_li]:text-muted-foreground">
        {children}
      </div>
    </div>
  );
}
typescript