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}