feat: Add CodeBlock component for syntax highlighting in Markdown
This commit is contained in:
		
							parent
							
								
									3161367a90
								
							
						
					
					
						commit
						024d0506f3
					
				
							
								
								
									
										66
									
								
								src/components/Common/CodeBlock.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										66
									
								
								src/components/Common/CodeBlock.tsx
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,66 @@ | ||||
| import { Tooltip } from "antd" | ||||
| import { CheckIcon, ClipboardIcon } from "lucide-react" | ||||
| import { FC, memo, useState } from "react" | ||||
| import { useTranslation } from "react-i18next" | ||||
| import { Prism as SyntaxHighlighter } from "react-syntax-highlighter" | ||||
| import { coldarkDark } from "react-syntax-highlighter/dist/cjs/styles/prism" | ||||
| 
 | ||||
| interface Props { | ||||
|   language: string | ||||
|   value: string | ||||
| } | ||||
| 
 | ||||
| export const CodeBlock: FC<Props> = memo(({ language, value }) => { | ||||
|   const [isBtnPressed, setIsBtnPressed] = useState(false) | ||||
|   const { t } = useTranslation("common") | ||||
|   return ( | ||||
|     <> | ||||
|       <div className="code relative text-base font-sans codeblock bg-zinc-950  rounded-md overflow-hidden"> | ||||
|         <div className="flex bg-gray-800 items-center justify-between py-1.5 px-4"> | ||||
|           <span className="text-xs lowercase text-gray-200">{language}</span> | ||||
| 
 | ||||
|           <div className="flex items-center"> | ||||
|             <Tooltip title={t("copyToClipboard")}> | ||||
|               <button | ||||
|                 onClick={() => { | ||||
|                   navigator.clipboard.writeText(value) | ||||
|                   setIsBtnPressed(true) | ||||
|                   setTimeout(() => { | ||||
|                     setIsBtnPressed(false) | ||||
|                   }, 4000) | ||||
|                 }} | ||||
|                 className="flex gap-1.5 items-center rounded bg-none p-1 text-xs text-gray-200 hover:bg-gray-700 hover:text-gray-100 focus:outline-none focus:ring-2 focus:ring-gray-500 focus:ring-offset-2 focus:ring-offset-gray-100"> | ||||
|                 {!isBtnPressed ? ( | ||||
|                   <ClipboardIcon className="h-4 w-4" /> | ||||
|                 ) : ( | ||||
|                   <CheckIcon className="h-4 w-4 text-green-400" /> | ||||
|                 )} | ||||
|               </button> | ||||
|             </Tooltip> | ||||
|           </div> | ||||
|         </div> | ||||
|         <SyntaxHighlighter | ||||
|           language={language} | ||||
|           style={coldarkDark} | ||||
|           PreTag="div" | ||||
|           customStyle={{ | ||||
|             margin: 0, | ||||
|             width: "100%", | ||||
|             background: "transparent", | ||||
|             padding: "1.5rem 1rem" | ||||
|           }} | ||||
|           lineNumberStyle={{ | ||||
|             userSelect: "none" | ||||
|           }} | ||||
|           codeTagProps={{ | ||||
|             style: { | ||||
|               fontSize: "0.9rem", | ||||
|               fontFamily: "var(--font-mono)" | ||||
|             } | ||||
|           }}> | ||||
|           {value} | ||||
|         </SyntaxHighlighter> | ||||
|       </div> | ||||
|     </> | ||||
|   ) | ||||
| }) | ||||
| @ -1,68 +1,38 @@ | ||||
| import { Prism as SyntaxHighlighter } from "react-syntax-highlighter" | ||||
| import remarkGfm from "remark-gfm" | ||||
| import { nightOwl } from "react-syntax-highlighter/dist/cjs/styles/prism" | ||||
| import remarkMath from "remark-math" | ||||
| import ReactMarkdown from "react-markdown" | ||||
| import ReactMarkdown, { Options } from "react-markdown" | ||||
| 
 | ||||
| import "property-information" | ||||
| import React from "react" | ||||
| import { Tooltip } from "antd" | ||||
| import { CheckIcon, ClipboardIcon } from "lucide-react" | ||||
| import { useTranslation } from "react-i18next" | ||||
| 
 | ||||
| import { FC, memo } from "react" | ||||
| import { CodeBlock } from "./CodeBlock" | ||||
| 
 | ||||
| export const MemoizedReactMarkdown: FC<Options> = memo( | ||||
|   ReactMarkdown, | ||||
|   (prevProps, nextProps) => | ||||
|     prevProps.children === nextProps.children && | ||||
|     prevProps.className === nextProps.className | ||||
| ) | ||||
| 
 | ||||
| export default function Markdown({ message }: { message: string }) { | ||||
|   const [isBtnPressed, setIsBtnPressed] = React.useState(false) | ||||
|   const { t } = useTranslation("common") | ||||
| 
 | ||||
|   return ( | ||||
|     <React.Fragment> | ||||
|       <ReactMarkdown | ||||
|       <MemoizedReactMarkdown | ||||
|         className="prose break-words dark:prose-invert prose-p:leading-relaxed prose-pre:p-0 dark:prose-dark" | ||||
|         remarkPlugins={[remarkGfm, remarkMath]} | ||||
|         components={{ | ||||
|           code({ node, inline, className, children, ...props }) { | ||||
|             const match = /language-(\w+)/.exec(className || "") | ||||
|             return !inline ? ( | ||||
|               <div className="code relative text-base bg-gray-800 rounded-md overflow-hidden"> | ||||
|                 <div className="flex items-center justify-between py-1.5 px-4"> | ||||
|                   <span className="text-xs lowercase text-gray-200"> | ||||
|                     {className && className.replace("language-", "")} | ||||
|                   </span> | ||||
| 
 | ||||
|                   <div className="flex items-center"> | ||||
|                     <Tooltip title={t("copyToClipboard")}> | ||||
|                       <button | ||||
|                         onClick={() => { | ||||
|                           navigator.clipboard.writeText(children[0] as string) | ||||
|                           setIsBtnPressed(true) | ||||
|                           setTimeout(() => { | ||||
|                             setIsBtnPressed(false) | ||||
|                           }, 4000) | ||||
|                         }} | ||||
|                         className="flex gap-1.5 items-center rounded bg-none p-1 text-xs text-gray-200 hover:bg-gray-700 hover:text-gray-100 focus:outline-none focus:ring-2 focus:ring-gray-500 focus:ring-offset-2 focus:ring-offset-gray-100"> | ||||
|                         {!isBtnPressed ? ( | ||||
|                           <ClipboardIcon className="h-4 w-4" /> | ||||
|                         ) : ( | ||||
|                           <CheckIcon className="h-4 w-4 text-green-400" /> | ||||
|                         )} | ||||
|                       </button> | ||||
|                     </Tooltip> | ||||
|                   </div> | ||||
|                 </div> | ||||
|                 <SyntaxHighlighter | ||||
|                   {...props} | ||||
|                   children={String(children).replace(/\n$/, "")} | ||||
|                   style={nightOwl} | ||||
|                   key={Math.random()} | ||||
|                   customStyle={{ | ||||
|                     margin: 0, | ||||
|                     fontSize: "1rem", | ||||
|                     lineHeight: "1.5rem" | ||||
|                   }} | ||||
|                   language={(match && match[1]) || ""} | ||||
|                   codeTagProps={{ | ||||
|                     className: "text-sm" | ||||
|                   }} | ||||
|                 /> | ||||
|               </div> | ||||
|             <CodeBlock | ||||
|               language={match ? match[1] : ""} | ||||
|               value={String(children).replace(/\n$/, "")} | ||||
|             /> | ||||
|             ) : ( | ||||
|               <code className={`${className} font-semibold`} {...props}> | ||||
|                 {children} | ||||
| @ -85,7 +55,7 @@ export default function Markdown({ message }: { message: string }) { | ||||
|           } | ||||
|         }}> | ||||
|         {message} | ||||
|       </ReactMarkdown> | ||||
|       </MemoizedReactMarkdown> | ||||
|     </React.Fragment> | ||||
|   ) | ||||
| } | ||||
|  | ||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user