AIcss
Components/Text Outputs

Code Block

A code block with a language label and one-click copy button.

Component byKevin@kvnkld

Preview

utils.ts
1export const sum = (a: number, b: number) =>
2 a + b;
3 
4export const clamp = (n: number, min: number, max: number) =>
5 Math.min(Math.max(n, min), max);

Code

CodeBlock.tsx
import { useState } from "react";

export function CodeBlock({ lang, code }: { lang: string; code: string }) {
  const [copied, setCopied] = useState(false);
  const lines = code.split("\n");
  const copy = () => {
    navigator.clipboard.writeText(code);
    setCopied(true);
    setTimeout(() => setCopied(false), 1200);
  };
  return (
    <div className="cb">
      <div className="cb-head">
        <span className="cb-file">
          <svg className="cb-icon" viewBox="0 0 24 24" width="15" height="15" aria-hidden="true"><path d="m8 6-6 6 6 6M16 6l6 6-6 6" fill="none" stroke="currentColor" strokeWidth="1.8" strokeLinecap="round" strokeLinejoin="round" /></svg>
          <span className="cb-lang">{lang}</span>
        </span>
        <button className="cb-copy" onClick={copy} aria-label="Copy code">
          {copied ? "Copied" : "Copy"}
        </button>
      </div>
      <div className="cb-body">
        {lines.map((line, i) => (
          <div className="cb-row" key={i}>
            <span className="cb-ln">{i + 1}</span>
            <code className="cb-code">{line || "\u00A0"}</code>
          </div>
        ))}
      </div>
    </div>
  );
}

/* CodeBlock.css */
.cb { border-radius: 12px; background: #fff; box-shadow: 0 0 0 1px #e6e8ec; padding: 12px 16px 0; overflow: hidden; }
.cb-head { display: flex; align-items: center; gap: 8px; margin: -12px -16px 0; padding: 10px 12px 10px 16px; background: #fafafa; border-bottom: 0.5px solid #e6e8ec; }
.cb-file { display: inline-flex; align-items: center; gap: 7px; }
.cb-icon { display: block; width: 15px; height: 15px; color: #a1a1a1; flex: none; transform: translateY(-1px); }
.cb-lang { font-family: ui-monospace, monospace; font-size: 12.5px; line-height: 1; color: #1a1a1a; transform: translateY(-1px); }
.cb-copy { margin-left: auto; font-size: 12px; color: #a1a1a1; border: 0; background: none; padding: 5px 7px; border-radius: 7px; cursor: pointer; }
.cb-copy:hover { color: #1a1a1a; background: #f4f5f7; }
.cb-body { position: relative; margin: 0 -16px; padding: 10px 0; font-family: ui-monospace, monospace; font-size: 12.5px; line-height: 20px; }
.cb-body::before { content: ""; position: absolute; top: 0; bottom: 0; left: 32px; width: 0.5px; background: #e6e8ec; }
.cb-row { display: grid; grid-template-columns: 32px 1fr; }
.cb-ln { user-select: none; text-align: right; padding: 0 7px; color: #a1a1a1; font-size: 11px; }
.cb-code { white-space: pre; padding: 0 12px 0 8px; color: #1a1a1a; overflow-x: auto; scrollbar-width: none; }
.cb-code::-webkit-scrollbar { display: none; }
@media (prefers-color-scheme: dark) {
  .cb { background: #1a1a1a; box-shadow: 0 0 0 1px #303030; }
  .cb-head { background: #242424; border-bottom-color: #303030; }
  .cb-lang { color: #f5f5f5; }
  .cb-copy:hover { color: #f5f5f5; background: #242424; }
  .cb-body::before { background: #303030; }
  .cb-code { color: #f5f5f5; }
}