import { Clipboard, ClipboardCheck } from "lucide-react"; import type { Language, PrismTheme } from "prism-react-renderer"; import Highlight, { defaultProps } from "prism-react-renderer"; import { forwardRef, useCallback, useState } from "react"; import { cn } from "~/utils/cn"; import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "../primitives/Tooltip"; //This is a fork of https://github.com/mantinedev/mantine/blob/master/src/mantine-prism/src/Prism/Prism.tsx //it didn't support highlighting lines by dimming the rest of the code, or animations on the highlighting type CodeBlockProps = { /** Code which will be highlighted */ code: string; /** Programming language that should be highlighted */ language?: Language; /** Show copy to clipboard button */ showCopyButton?: boolean; /** Display line numbers */ showLineNumbers?: boolean; /** Highlight line at given line number with color from theme.colors */ highlightedRanges?: [number, number][]; /** Add/override classes on the overall element */ className?: string; /** Add/override code theme */ theme?: PrismTheme; /** Max lines */ maxLines?: number; /** Whether to show the chrome, if you provide a string it will be used as the title, */ showChrome?: boolean; /** filename */ fileName?: string; }; const dimAmount = 0.5; const extraLinesWhenClipping = 0.35; const defaultTheme: PrismTheme = { plain: { color: "#9CDCFE", backgroundColor: "#0e1521", }, styles: [ { types: ["prolog"], style: { color: "rgb(0, 0, 128)", }, }, { types: ["comment"], style: { color: "rgb(106, 153, 85)", }, }, { types: ["builtin", "changed", "keyword", "interpolation-punctuation"], style: { color: "rgb(86, 156, 214)", }, }, { types: ["number", "inserted"], style: { color: "rgb(181, 206, 168)", }, }, { types: ["constant"], style: { color: "rgb(100, 102, 149)", }, }, { types: ["attr-name", "variable"], style: { color: "rgb(156, 220, 254)", }, }, { types: ["deleted", "string", "attr-value", "template-punctuation"], style: { color: "rgb(206, 145, 120)", }, }, { types: ["selector"], style: { color: "rgb(215, 186, 125)", }, }, { // Fix tag color types: ["tag"], style: { color: "rgb(78, 201, 176)", }, }, { // Fix tag color for HTML types: ["tag"], languages: ["markup"], style: { color: "rgb(86, 156, 214)", }, }, { types: ["punctuation", "operator"], style: { color: "rgb(212, 212, 212)", }, }, { // Fix punctuation color for HTML types: ["punctuation"], languages: ["markup"], style: { color: "#808080", }, }, { types: ["function"], style: { color: "rgb(220, 220, 170)", }, }, { types: ["class-name"], style: { color: "rgb(78, 201, 176)", }, }, { types: ["char"], style: { color: "rgb(209, 105, 105)", }, }, ], }; export const CodeBlock = forwardRef( ( { showCopyButton = true, showLineNumbers = true, highlightedRanges, code, className, language = "typescript", theme = defaultTheme, maxLines, showChrome = false, fileName, ...props }: CodeBlockProps, ref ) => { const [mouseOver, setMouseOver] = useState(false); const [copied, setCopied] = useState(false); const onCopied = useCallback( (event: React.MouseEvent) => { event.preventDefault(); event.stopPropagation(); navigator.clipboard.writeText(code); setCopied(true); setTimeout(() => { setCopied(false); }, 1500); }, [code] ); code = code.trim(); const lineCount = code.split("\n").length; const maxLineWidth = lineCount.toString().length; let maxHeight: string | undefined = undefined; if (maxLines && lineCount > maxLines) { maxHeight = `calc(${(maxLines + extraLinesWhenClipping) * 0.75 * 1.625}rem + 1.5rem )`; } const highlightLines = highlightedRanges?.flatMap(([start, end]) => Array.from({ length: end - start + 1 }, (_, i) => start + i) ); // if there are more than 1000 lines, don't highlight const shouldHighlight = lineCount <= 1000; return (
{showChrome && } {showCopyButton && ( setMouseOver(true)} onMouseLeave={() => setMouseOver(false)} className={cn( "absolute right-3 z-50 transition-colors duration-100 hover:cursor-pointer", showChrome ? "top-10" : "top-3", copied ? "text-emerald-500" : "text-slate-500 hover:text-slate-300" )} > {copied ? ( ) : ( )} {copied ? "Copied" : "Copy"} )} {shouldHighlight ? ( {({ className: inheritedClassName, style: inheritedStyle, tokens, getLineProps, getTokenProps, }) => (
                  {tokens
                    .map((line, index) => {
                      if (
                        index === tokens.length - 1 &&
                        line.length === 1 &&
                        line[0].content === "\n"
                      ) {
                        return null;
                      }

                      const lineNumber = index + 1;
                      const lineProps = getLineProps({ line, key: index });

                      let hasAnyHighlights = highlightLines ? highlightLines.length > 0 : false;

                      let shouldDim = hasAnyHighlights;
                      if (hasAnyHighlights && highlightLines?.includes(lineNumber)) {
                        shouldDim = false;
                      }

                      return (
                        
{showLineNumbers && (
{lineNumber}
)}
{line.map((token, key) => { const tokenProps = getTokenProps({ token, key }); return ( ); })}
); }) .filter(Boolean)}
)}
) : (
              {code}
            
)}
); } ); CodeBlock.displayName = "CodeBlock"; function Chrome({ title }: { title?: string }) { return (
{title}
); }