Monorepo for wisp.place. A static site hosting service built on top of the AT Protocol. wisp.place
1import { useEffect, useRef, useState } from 'react' 2 3declare global { 4 interface Window { 5 Prism: { 6 languages: Record<string, any> 7 highlightElement: (element: HTMLElement) => void 8 highlightAll: () => void 9 } 10 } 11} 12 13interface CodeBlockProps { 14 code: string 15 language?: 'bash' | 'yaml' 16 className?: string 17} 18 19export function CodeBlock({ code, language = 'bash', className = '' }: CodeBlockProps) { 20 const [isThemeLoaded, setIsThemeLoaded] = useState(false) 21 const codeRef = useRef<HTMLElement>(null) 22 23 useEffect(() => { 24 // Load Catppuccin theme CSS 25 const loadTheme = async () => { 26 // Detect if user prefers dark mode 27 const prefersDark = window.matchMedia('(prefers-color-scheme: dark)').matches 28 const theme = prefersDark ? 'mocha' : 'latte' 29 30 // Remove any existing theme CSS 31 const existingTheme = document.querySelector('link[data-prism-theme]') 32 if (existingTheme) { 33 existingTheme.remove() 34 } 35 36 // Load the appropriate Catppuccin theme 37 const link = document.createElement('link') 38 link.rel = 'stylesheet' 39 link.href = `https://prismjs.catppuccin.com/${theme}.css` 40 link.setAttribute('data-prism-theme', theme) 41 document.head.appendChild(link) 42 43 // Load PrismJS if not already loaded 44 if (!window.Prism) { 45 const script = document.createElement('script') 46 script.src = 'https://cdnjs.cloudflare.com/ajax/libs/prism/1.30.0/prism.min.js' 47 script.onload = () => { 48 // Load language support if needed 49 if (language === 'yaml' && !window.Prism.languages.yaml) { 50 const yamlScript = document.createElement('script') 51 yamlScript.src = 'https://cdnjs.cloudflare.com/ajax/libs/prism/1.30.0/components/prism-yaml.min.js' 52 yamlScript.onload = () => setIsThemeLoaded(true) 53 document.head.appendChild(yamlScript) 54 } else { 55 setIsThemeLoaded(true) 56 } 57 } 58 document.head.appendChild(script) 59 } else { 60 setIsThemeLoaded(true) 61 } 62 } 63 64 loadTheme() 65 66 // Listen for theme changes 67 const mediaQuery = window.matchMedia('(prefers-color-scheme: dark)') 68 const handleThemeChange = () => loadTheme() 69 mediaQuery.addEventListener('change', handleThemeChange) 70 71 return () => { 72 mediaQuery.removeEventListener('change', handleThemeChange) 73 } 74 }, [language]) 75 76 // Highlight code when Prism is loaded and component is mounted 77 useEffect(() => { 78 if (isThemeLoaded && codeRef.current && window.Prism) { 79 window.Prism.highlightElement(codeRef.current) 80 } 81 }, [isThemeLoaded, code]) 82 83 if (!isThemeLoaded) { 84 return ( 85 <pre className={`p-4 bg-muted rounded-lg overflow-x-auto ${className}`}> 86 <code>{code.trim()}</code> 87 </pre> 88 ) 89 } 90 91 // Map language to Prism language class 92 const languageMap = { 93 'bash': 'language-bash', 94 'yaml': 'language-yaml' 95 } 96 97 const prismLanguage = languageMap[language] || 'language-bash' 98 99 return ( 100 <pre className={`p-4 rounded-lg overflow-x-auto ${className}`}> 101 <code ref={codeRef} className={prismLanguage}>{code.trim()}</code> 102 </pre> 103 ) 104}