import type { VariantProps } from "class-variance-authority"; import { Check, Copy } from "lucide-react"; import { type HTMLProps, type ReactNode, useCallback, useEffect, useState } from "react"; import CopyToClipboard from "react-copy-to-clipboard"; import { useTranslation } from "react-i18next"; import { PrismLight as SyntaxHighlighter } from "react-syntax-highlighter"; import java from "react-syntax-highlighter/dist/esm/languages/prism/java"; import materialDark from "react-syntax-highlighter/dist/esm/styles/prism/material-dark"; import { toast } from "sonner"; import { Button, type buttonVariants } from "@/components/ui/button"; import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger, } from "@/components/ui/dropdown-menu"; import { cn } from "@/lib/utils"; SyntaxHighlighter.registerLanguage("java", java); interface CopyButtonProps extends React.ComponentProps<"button"> { value: string; src?: string; } export interface CopyOption { label: string; value: string; disabled?: boolean; } export function CopyButton({ value, className, ...buttonProps }: Readonly>) { const [hasCopied, setHasCopied] = useState(false); const { t } = useTranslation(["common"]); useEffect(() => { if (hasCopied) { const timer = setTimeout(() => { setHasCopied(false); }, 1000); return () => clearTimeout(timer); } }, [hasCopied]); const handleCopy = useCallback(() => { if (!hasCopied) { setHasCopied(true); toast.success(t("copySuccess"), { duration: 1000 }); } }, [hasCopied, t]); return ( ); } export function CopyMenuButton({ options, className, ...buttonProps }: Readonly< { options: CopyOption[] } & React.ComponentProps<"button"> & VariantProps >) { const [hasCopied, setHasCopied] = useState(false); const { t } = useTranslation(["common"]); useEffect(() => { if (hasCopied) { const timer = setTimeout(() => { setHasCopied(false); }, 1000); return () => clearTimeout(timer); } }, [hasCopied]); const handleCopy = useCallback(() => { if (!hasCopied) { setHasCopied(true); toast.success(t("copySuccess"), { duration: 1000 }); } }, [hasCopied, t]); return ( } > {hasCopied ? : } {options.map((option) => ( {option.label} ))} ); } export default function CodeViewer({ code, header, button, language, height, showLineNumbers = true, wrapLongLines = true, copyLabel, copyDisabled = false, copyOptions, }: Readonly) { const lineProps: lineTagPropsFunction | HTMLProps | undefined = wrapLongLines ? { style: { overflowWrap: "break-word", whiteSpace: "pre-wrap" } } : undefined; return (
{header}
{button} {copyOptions ? ( ) : ( )}
{code}
); } interface CodeViewerProps { code: string; language: string; header?: ReactNode; button?: ReactNode; height?: string | number; showLineNumbers?: boolean; wrapLongLines?: boolean; lineProps?: (lineNumber: number) => React.HTMLProps; copyLabel?: string; copyDisabled?: boolean; copyOptions?: CopyOption[]; }