Skip to main content
Glama
WebComponentPreview.tsx3.44 kB
'use client'; import { useEffect, useRef, useState } from 'react'; interface WebComponentPreviewProps { code: string; className?: string; } // Track if the web components script has been loaded let scriptLoaded = false; let scriptLoading = false; const loadCallbacks: (() => void)[] = []; /** * Load the @mcpsystem/ui web components script */ function loadWebComponentsScript(): Promise<void> { return new Promise((resolve) => { if (scriptLoaded) { resolve(); return; } loadCallbacks.push(resolve); if (scriptLoading) { return; } scriptLoading = true; const script = document.createElement('script'); script.type = 'module'; script.src = '/mcp-ui.min.js'; script.onload = () => { scriptLoaded = true; scriptLoading = false; // Give custom elements time to register setTimeout(() => { loadCallbacks.forEach((cb) => cb()); loadCallbacks.length = 0; }, 50); }; script.onerror = () => { scriptLoading = false; // Still resolve to show static content loadCallbacks.forEach((cb) => cb()); loadCallbacks.length = 0; }; document.head.appendChild(script); }); } /** * Renders live previews of @mcpsystem/ui web components. * Loads the component library and renders the actual web components. */ export function WebComponentPreview({ code, className = '' }: WebComponentPreviewProps) { const containerRef = useRef<HTMLDivElement>(null); const [isLoaded, setIsLoaded] = useState(scriptLoaded); const lastCodeRef = useRef<string>(''); useEffect(() => { loadWebComponentsScript().then(() => { setIsLoaded(true); }); }, []); useEffect(() => { if (containerRef.current && isLoaded && code !== lastCodeRef.current) { // Remove script tags for safety const cleanCode = code.replace(/<script[\s\S]*?<\/script>/gi, ''); containerRef.current.innerHTML = cleanCode; lastCodeRef.current = code; } }, [code, isLoaded]); return ( <div className={`bg-surface-sunken border border-default rounded-xl overflow-hidden ${className}`}> {/* Preview Header */} <div className="flex items-center justify-between px-4 py-2 border-b border-default bg-surface-raised"> <span className="text-xs font-medium text-muted">Preview</span> {isLoaded && ( <span className="text-xs text-success-foreground">Live</span> )} </div> {/* Preview Area */} <div className="p-6"> {!isLoaded ? ( <div className="flex items-center justify-center py-8 text-muted"> <svg className="w-5 h-5 animate-spin mr-2" viewBox="0 0 24 24"> <circle className="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" strokeWidth="4" fill="none" /> <path className="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z" /> </svg> Loading components... </div> ) : ( <div ref={containerRef} className="web-component-preview space-y-4" /> )} </div> </div> ); }

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/heyadam/mcpsystemdesign'

If you have feedback or need assistance with the MCP directory API, please join our Discord server