Code Block

code-block and preview of the code

Component Preview - Preview

app/api/mcp/route.ts

1import { z } from 'zod';2import { createMcpHandler } from 'mcp-handler';3 4const handler = createMcpHandler(5(server) => {6  server.tool(7    'roll_dice',8    'Rolls an N-sided die',9    { sides: z.number().int().min(2) },10    async ({ sides }) => {11      const value = 1 + Math.floor(Math.random() * sides);12      return {13        content: [{ type: 'text', text: `🎲 You rolled a 10!` }],14      };15    },16  );17},18{},19{ basePath: '/api' },20);

pages/index.tsx

1import { useChat } from '@ai-sdk/react';2import { useState } from 'react';3 4export default function Chat() {5const [input, setInput] = useState('');6const { messages, sendMessage } = useChat();7return (8  <div className="flex flex-col w-full max-w-md py-24 mx-auto stretch">9    {messages.map(message => (10      <div key={message.id} className="whitespace-pre-wrap">11        {message.role === 'user' ? 'User: ' : 'AI: '}12        {message.parts.map((part, i) => {13          switch (part.type) {14            case 'text':15              return <div>{part.text}</div>;16          }17        })}18      </div>19    ))}20    <form21      onSubmit={e => {22        e.preventDefault();23        sendMessage({ text: input });24        setInput('');25      }}26    >27      <input28        className="fixed dark:bg-zinc-900 bottom-0 w-full max-w-md p-2 mb-8 border border-zinc-300 dark:border-zinc-800 rounded shadow-xl"29        value={input}30        placeholder="Say something..."31        onChange={e => setInput(e.currentTarget.value)}32      />33    </form>34  </div>35);36}

Manual Installation

1

Install dependencies

Terminal
bash
npm i clsx tailwind-merge react-icons shaki
2

Add the util file

lib/utils.ts
ts
import { ClassValue, clsx } from "clsx";
import { twMerge } from "tailwind-merge";

export function cn(...inputs: ClassValue[]) {
return twMerge(clsx(inputs));
}
3

Copy the source code

components/lokalhost_io/code-blocks/component-preview.tsx
tsx
"use client";

import React, { useState, useEffect, CSSProperties } from "react";
import { IoCheckmarkOutline } from "react-icons/io5";
import { BiLogoTypescript } from "react-icons/bi";
import { codeToHtml } from "shiki";
import { useTheme } from "next-themes";
import { cn } from "@/lib/utils";
import { PiCopyDuotone } from "react-icons/pi";

interface CodeBlockProps {
  code: string;
  fileName?: string;
  language?: string;
  showLineNumbers?: boolean;
  lightTheme?: string; // e.g. "github-light"
  darkTheme?: string;  // e.g. "github-dark"

  // layout
  width?: string | number;   // e.g. 600, "100%", "40rem"
  height?: string | number;  // e.g. 400, "auto"
  borderRadius?: string | number; // e.g. 8, "0.5rem", "9999px"

  // colors (override the neutral defaults)
  bgColor?: string;          // light mode background
  darkBgColor?: string;      // dark mode background
  borderColor?: string;      // light mode border
  darkBorderColor?: string;  // dark mode border
  headerBgColor?: string;    // optional separate header bg (defaults to bgColor)
  darkHeaderBgColor?: string;

  className?: string; // escape hatch for anything else
}

function CodeBlock({
  code,
  fileName = "app/api/mcp/route.ts",
  language = "tsx",
  showLineNumbers = false,
  lightTheme = "github-light",
  darkTheme = "github-dark",

  width = 600,
  height = 400,
  borderRadius = 0,

  bgColor,
  darkBgColor,
  borderColor,
  darkBorderColor,
  headerBgColor,
  darkHeaderBgColor,

  className,
}: CodeBlockProps) {
  const { resolvedTheme } = useTheme();
  const [copied, setCopied] = useState(false);
  const isDark = resolvedTheme === "dark";

  const handleCopy = async () => {
    try {
      await navigator.clipboard.writeText(code);
      setCopied(true);
      setTimeout(() => setCopied(false), 1500);
    } catch (err) {
      console.error("Failed to copy code:", err);
    }
  };

  const containerStyle: CSSProperties = {
    width: typeof width === "number" ? `widthpx` : width,
    height: typeof height === "number" ? `heightpx` : height,
    borderRadius: typeof borderRadius === "number" ? `borderRadiuspx` : borderRadius,
    backgroundColor: isDark ? darkBgColor ?? bgColor : bgColor,
    borderColor: isDark ? darkBorderColor ?? borderColor : borderColor,
  };

  const headerStyle: CSSProperties = {
    backgroundColor: isDark
      ? darkHeaderBgColor ?? headerBgColor
      : headerBgColor,
  };

  return (
    <div className="w-full flex justify-center items-center">
      <div
        style={containerStyle}
        className={cn(
          "flex flex-col border overflow-hidden",
          // fall back to defaults only where no override color was given
          !bgColor && "bg-neutral-100",
          !darkBgColor && "dark:bg-black",
          !borderColor && "border-neutral-400",
          !darkBorderColor && "dark:border-neutral-700",
          className
        )}
      >
        {/* Header */}
        <div
          style={headerStyle}
          className={cn(
            "flex w-full justify-between items-center h-[50px] shrink-0 border-b",
            !borderColor && "border-neutral-400",
            !darkBorderColor && "dark:border-neutral-700"
          )}
        >
          <div className="flex items-center gap-2 px-4">
            <div>
              <BiLogoTypescript size={20} />
            </div>
            <p className="text-sm font-medium text-neutral-700 dark:text-neutral-300">
              {fileName}
            </p>
          </div>
          <div className="flex items-center gap-2 px-4">
            <button
              onClick={handleCopy}
              aria-label="Copy code"
              className="cursor-pointer flex items-center justify-center p-1 rounded-sm bg-neutral-200 dark:bg-neutral-800 hover:bg-neutral-300 dark:hover:bg-neutral-700 transition-colors"
            >
              {copied ? (
                <IoCheckmarkOutline size={16} className="text-neutral-600 dark:text-neutral-500" />
              ) : (
                <PiCopyDuotone size={16} />
              )}
            </button>
          </div>
        </div>

        {/* Code block part */}
        <div className="flex-1 min-h-0 overflow-auto">
          <CodeHighlight
            code={code}
            language={language}
            shikiTheme={isDark ? darkTheme : lightTheme}
            showLineNumbers={showLineNumbers}
          />
        </div>
      </div>
    </div>
  );
}

function CodeHighlight({
  code,
  language = "tsx",
  shikiTheme,
  showLineNumbers,
}: {
  code: string;
  language?: string;
  shikiTheme: string;
  showLineNumbers: boolean;
}) {
  const [html, setHtml] = useState<string | null>(null);

  useEffect(() => {
    let cancelled = false;
    codeToHtml(code, {
      lang: language,
      theme: shikiTheme,
    }).then((h) => {
      if (!cancelled) setHtml(h);
    });
    return () => {
      cancelled = true;
    };
  }, [code, language, shikiTheme]);

  if (!html) {
    return <CodeFallback code={code} showLineNumbers={showLineNumbers} />;
  }

  return (
    <>
      {showLineNumbers && (
        <style>{`
          .shiki-lines code { counter-reset: line; }
          .shiki-lines .line { counter-increment: line; display: inline-block; width: 100%; }
          .shiki-lines .line::before {
            content: counter(line);
            display: inline-block;
            width: 2rem;
            margin-right: 0.50rem;
            text-align: right;
            color: hsl(var(--muted-foreground));
            opacity: 0.4;
            user-select: none;
          }
        `}</style>
      )}
      <div
        style={{ fontFamily: "'Geist Mono', 'Geist Mono Fallback', monospace" }}
        className={cn(
          showLineNumbers && "shiki-lines",
          "[&>pre]:overflow-x-auto [&>pre]:text-[12px] [&>pre]:leading-relaxed [&>pre]:py-4",
          showLineNumbers ? "[&>pre]:pl-0 [&>pre]:pr-4" : "[&>pre]:px-4",
          // force transparent bg so the container's own bg shows through
          "[&>pre]:!bg-transparent [&_code]:!bg-transparent [&>pre]:![font-family:inherit]"
        )}
        dangerouslySetInnerHTML={{ __html: html }}
      />
    </>
  );
}

function CodeFallback({
  code,
  showLineNumbers,
}: {
  code: string;
  showLineNumbers: boolean;
}) {
  const lines = code.split("
");
  return (
    <pre className="overflow-x-auto text-[12px] leading-relaxed font-mono text-foreground px-4 py-4">
      <code
        className={showLineNumbers ? "grid" : "block"}
        style={showLineNumbers ? { gridTemplateColumns: "auto 1fr" } : undefined}
      >
        {lines.map((line, i) =>
          showLineNumbers ? (
            <span key={i} className="contents">
              <span className="select-none pr-2 text-right text-muted-foreground/40">
                {i + 1}
              </span>
              <span>{line || " "}</span>
            </span>
          ) : (
            <span key={i} className="block">
              {line || " "}
            </span>
          )
        )}
      </code>
    </pre>
  );
}

export default CodeBlock;

Props

PropTypeDescription
code*stringThe raw source code to display and highlight.
fileNamestringFile path or name shown in the header next to the language icon.
languagestringShiki-supported language id used for syntax highlighting (e.g. "ts", "tsx", "bash", "json").
showLineNumbersbooleanToggles a line-number gutter alongside the code.
lightThemestringShiki theme id used when resolved app theme is light.
darkThemestringShiki theme id used when resolved app theme is dark.
widthstring | numberWidth of the component. Numbers are treated as pixels; strings are used as-is (e.g. "100%", "40rem").
heightstring | numberHeight of the component. Numbers are treated as pixels; strings are used as-is (e.g. "auto", "60vh").
borderRadiusstring | numberCorner radius of the outer card. Numbers are treated as pixels.
bgColorstringBackground color in light mode. Falls back to the default neutral background if omitted.
darkBgColorstringBackground color in dark mode. Falls back to the default neutral background if omitted.
borderColorstringBorder color in light mode. Falls back to the default neutral border if omitted.
darkBorderColorstringBorder color in dark mode. Falls back to the default neutral border if omitted.
headerBgColorstringBackground color for the header row in light mode. Defaults to bgColor if omitted.
darkHeaderBgColorstringBackground color for the header row in dark mode. Defaults to darkBgColor if omitted.
classNamestringAdditional classes applied to the outer card for one-off style overrides.