// CRITICAL 'use client'; import { useRef, useEffect, useCallback, useId } from 'react'; import { Play, Square, Copy, Check, Download, Maximize2, Minimize2, RefreshCw, AlertTriangle, } from 'lucide-react'; import { useAppStore } from '@/store'; interface CodeSandboxProps { code: string; language: 'html' | 'react' | 'javascript'; title?: string; autoRun?: boolean; onOutput?: (output: string) => void; onError?: (error: string) => void; } const transformReactCode = (code: string) => { let transformed = code || ''; // Strip common import/export patterns that won't work in a plain iframe without bundling. transformed = transformed.replace(/^\s*import\s+.*?;?\s*$/gm, ''); transformed = transformed.replace(/^\s*export\s+\{[^}]+\}\s*;?\s*$/gm, ''); transformed = transformed.replace(/^\s*export\s+(const|let|var|function|class)\s+/gm, '$1 '); // Capture default export into a known global. transformed = transformed.replace(/\bexport\s+default\s+/g, 'window.__DEFAULT_EXPORT__ = '); return transformed.trim(); }; // Template for React execution const REACT_TEMPLATE = (code: string) => `
`; // Template for vanilla JavaScript const JS_TEMPLATE = (code: string) => `
`; export function CodeSandbox({ code, language, title, autoRun = false, onOutput, onError, }: CodeSandboxProps) { const sandboxId = useId(); const sandboxState = useAppStore( (state) => state.codeSandboxState[sandboxId] ?? { isRunning: false, isFullscreen: false, copied: false, error: null, }, ); const updateCodeSandboxState = useAppStore((state) => state.updateCodeSandboxState); const { isRunning, isFullscreen, copied, error } = sandboxState; const updateState = (partial: Partial) => { updateCodeSandboxState(sandboxId, (prev) => ({ ...prev, ...partial })); }; const iframeRef = useRef(null); const getSrcDoc = useCallback(() => { switch (language) { case 'html': return code; case 'react': return REACT_TEMPLATE(code); case 'javascript': return JS_TEMPLATE(code); default: return code; } }, [code, language]); const runCode = useCallback(() => { updateState({ isRunning: true, error: null }); if (iframeRef.current) { try { iframeRef.current.srcdoc = getSrcDoc(); } catch (e) { const errorMsg = e instanceof Error ? e.message : 'Failed to run code'; updateState({ error: errorMsg }); onError?.(errorMsg); } } }, [getSrcDoc, onError, updateState]); const stopCode = useCallback(() => { updateState({ isRunning: false }); if (iframeRef.current) { iframeRef.current.srcdoc = ''; } }, [updateState]); const copyCode = useCallback(() => { navigator.clipboard.writeText(code); updateState({ copied: true }); setTimeout(() => updateState({ copied: false }), 2000); }, [code, updateState]); const downloadCode = useCallback(() => { const blob = new Blob([getSrcDoc()], { type: 'text/html' }); const url = URL.createObjectURL(blob); const a = document.createElement('a'); a.href = url; a.download = `${title || 'artifact'}.html`; document.body.appendChild(a); a.click(); document.body.removeChild(a); URL.revokeObjectURL(url); }, [getSrcDoc, title]); // Auto-run on mount if enabled useEffect(() => { if (!autoRun) return; const id = window.setTimeout(() => { runCode(); }, 0); return () => window.clearTimeout(id); }, [autoRun, runCode]); // Listen for iframe errors useEffect(() => { const handleMessage = (event: MessageEvent) => { if (event.data?.type === 'error') { updateState({ error: event.data.message }); onError?.(event.data.message); } else if (event.data?.type === 'output') { onOutput?.(event.data.message); } }; window.addEventListener('message', handleMessage); return () => window.removeEventListener('message', handleMessage); }, [onError, onOutput, updateState]); // Toolbar buttons component to avoid duplication const ToolbarButtons = ({ inFooter = false }: { inFooter?: boolean }) => (
{/* Run/Stop - always visible */} {isRunning ? ( ) : ( )} {/* Refresh */} {/* Copy */} {/* Download */} {/* Fullscreen/Minimize - ALWAYS visible */}
); return ( <> {/* Fullscreen backdrop */} {isFullscreen && (
updateState({ isFullscreen: false })} /> )}
{/* Header - minimal in fullscreen */}
{title || `${language.toUpperCase()}`}
{/* Show controls in header only when NOT fullscreen */} {!isFullscreen && } {/* In fullscreen, just show minimize button in header */} {isFullscreen && ( )}
{/* Error Display */} {error && (
{error}
)} {/* Preview */}