Create a code block component in nextjs.
// code.tsx
"use client";
import { useTheme } from "@gmana/react/theme-provider";
import { transformerNotationDiff, transformerNotationHighlight } from "@shikijs/transformers";
import React, { useEffect, useMemo, useState } from "react";
import type { BundledLanguage } from "shiki";
import { codeToHtml } from "shiki";
import CopyToClipboard from "./copy-to-clipboard";
const SUPPORTED_THEMES = {
light: "github-light",
dark: "github-dark",
} as const;
interface CodeProps {
children: string;
lang?: BundledLanguage;
filename?: string;
}
const Code: React.FC<CodeProps> = ({ children, lang = "typescript", filename }) => {
const { theme } = useTheme();
const currentTheme = useMemo(() => SUPPORTED_THEMES[theme as keyof typeof SUPPORTED_THEMES], [theme]);
const [highlightedCode, setHighlightedCode] = useState<string>("");
useEffect(() => {
const initHighlighter = async () => {
const html = await codeToHtml(children, {
lang,
theme: theme === "dark" ? "github-dark" : "github-light",
transformers: [transformerNotationHighlight(), transformerNotationDiff()],
});
setHighlightedCode(html);
};
initHighlighter();
}, [lang, currentTheme]);
return (
<div className="my-3 max-w-full overflow-hidden rounded-gmana border border-gmana bg-gmana font-mono">
{filename && (
<div className="border-b border-gray-200 bg-gray-100 px-4 py-2 text-sm font-medium text-gray-700 dark:border-gray-600 dark:bg-gray-700 dark:text-gray-300">{filename}</div>
)}
<div className="relative">
<div
className="text-sm [&>pre]:overflow-x-auto [&>pre]:!bg-gmana [&>pre]:py-3 [&>pre]:pl-4 [&>pre]:pr-5 [&>pre]:leading-snug [&_code]:block [&_code]:w-fit [&_code]:min-w-full"
dangerouslySetInnerHTML={{ __html: highlightedCode }}
/>
<div className="absolute end-3 top-3">
<CopyToClipboard code={children} />
</div>
</div>
</div>
);
};
export default Code;
// copy-to-clipboard.tsx
"use client";
import { CheckCheck, Copy } from "lucide-react";
import { useState } from "react";
export default function CopyToClipboard({ code }: { code: string }) {
const [copy, setCopy] = useState(false);
const copyToClipboard = async () => {
try {
await navigator.clipboard.writeText(code);
setCopy(true);
} catch (error) {
} finally {
setTimeout(() => {
setCopy(false);
}, 2000);
}
};
return (
<button className="text-gmana hover:text-brand-service" onClick={copyToClipboard} aria-label={copy ? "Copied to clipboard" : "Copy to clipboard"}>
{copy ? <CheckCheck size={18} /> : <Copy size={18} />}
</button>
);
}
158 views