You can not select more than 25 topics
			Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
		
		
		
		
		
			
		
			
				
					
					
						
							83 lines
						
					
					
						
							2.3 KiB
						
					
					
				
			
		
		
	
	
							83 lines
						
					
					
						
							2.3 KiB
						
					
					
				| 'use client';
 | |
| 
 | |
| import { useState } from 'react';
 | |
| import { Check, Copy } from 'lucide-react';
 | |
| 
 | |
| type CodeBlockProps = {
 | |
|   children: React.ReactNode;
 | |
| };
 | |
| 
 | |
| export function CodeBlock({ children }: CodeBlockProps) {
 | |
|   const [showCopy, setShowCopy] = useState(false);
 | |
|   const [isCopied, setIsCopied] = useState(false);
 | |
| 
 | |
|   const copy = async () => {
 | |
|     await navigator.clipboard.writeText(
 | |
|       extractText(children as React.ReactElement),
 | |
|     );
 | |
| 
 | |
|     setIsCopied(true);
 | |
|     setTimeout(() => setIsCopied(false), 2000);
 | |
|   };
 | |
| 
 | |
|   return (
 | |
|     <pre
 | |
|       className="relative mx-auto max-w-3xl"
 | |
|       onMouseEnter={() => setShowCopy(true)}
 | |
|       onMouseLeave={() => setShowCopy(false)}
 | |
|       onFocus={() => setShowCopy(true)}
 | |
|       onBlur={() => setShowCopy(false)}
 | |
|     >
 | |
|       {showCopy && (
 | |
|         <button
 | |
|           className="absolute right-2 top-2 flex h-7 w-7 items-center justify-center rounded bg-white dark:bg-slate-800"
 | |
|           onClick={copy}
 | |
|           disabled={isCopied}
 | |
|         >
 | |
|           {isCopied ? (
 | |
|             <Check
 | |
|               className="h-6 w-6 animate-pulse text-accent dark:text-accent-dark"
 | |
|               aria-label="Copied"
 | |
|             />
 | |
|           ) : (
 | |
|             <Copy
 | |
|               className="h-6 w-6 text-slate-300 hover:text-accent dark:text-slate-600 dark:hover:text-accent-dark"
 | |
|               aria-label="Copy code"
 | |
|             />
 | |
|           )}
 | |
|         </button>
 | |
|       )}
 | |
|       {children}
 | |
|     </pre>
 | |
|   );
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * Extracts the text from a ReactElement
 | |
|  */
 | |
| const extractText = (element: React.ReactElement | string): string => {
 | |
|   // If the element is a string, return it
 | |
|   if (typeof element === 'string') {
 | |
|     return element;
 | |
|   }
 | |
| 
 | |
|   // If the element is a ReactElement, check if it has children
 | |
|   // If the children is a single string, return it
 | |
|   if (typeof element.props.children === 'string') {
 | |
|     return element.props.children;
 | |
|   }
 | |
| 
 | |
|   // If the children is an array, map over it and extract the text
 | |
|   if (Array.isArray(element.props.children)) {
 | |
|     return (element.props.children as (React.ReactElement | string)[])
 | |
|       .map((child) => extractText(child))
 | |
|       .join('');
 | |
|   }
 | |
| 
 | |
|   // If the children is an object (ReactElement), extract the text from it recursively
 | |
|   if (typeof element.props.children === 'object') {
 | |
|     return extractText(element.props.children);
 | |
|   }
 | |
| 
 | |
|   return '';
 | |
| };
 | |
| 
 |