38 lines · 1.5 KB
import { useCopy } from "~/hooks/use-copy";
import { cn } from "~/utils/cn";

type Props = { title?: string; source: string; className?: string };

function lineClass(line: string): string {
  return line.trimStart().startsWith("#") ? "text-gray-500" : "text-gray-200";
}

/** Read-only snippet with a title bar and copy button. Comment lines (`#`) are dimmed. */
export function CodeBlock({ title, source, className }: Props) {
  const { copied, copy } = useCopy();
  return (
    <figure className={cn("overflow-hidden rounded-lg border border-gray-800 bg-gray-950", className)}>
      <figcaption className="flex items-center justify-between border-b border-gray-800 bg-gray-900/60 px-4 py-2">
        <span className="font-mono text-xs text-gray-500">{title}</span>
        <button
          type="button"
          onClick={() => void copy(source)}
          className="font-mono text-xs text-gray-500 transition-colors hover:text-accent-400"
        >
          <span aria-live="polite">{copied ? "copied" : "copy"}</span>
        </button>
      </figcaption>
      <pre className="overflow-x-auto p-4 font-mono text-sm leading-6">
        <code>
          {source.split("\n").map((line, index) => (
            // oxlint-disable-next-line react/no-array-index-key -- lines are static and may repeat
            <span key={index} className={cn("block", lineClass(line))}>
              {line || " "}
            </span>
          ))}
        </code>
      </pre>
    </figure>
  );
}