53 lines
1.5 KiB
TypeScript
53 lines
1.5 KiB
TypeScript
'use client';
|
|
|
|
import { useState, useRef, useEffect } from 'react';
|
|
import type { AppIframeViewerProps } from '@/types/component-props';
|
|
|
|
export default function AppIframeViewer({
|
|
htmlContent,
|
|
title,
|
|
onLoadComplete,
|
|
}: AppIframeViewerProps) {
|
|
const [isLoading, setIsLoading] = useState(true);
|
|
const iframeRef = useRef<HTMLIFrameElement>(null);
|
|
|
|
useEffect(() => {
|
|
if (iframeRef.current) {
|
|
const iframe = iframeRef.current;
|
|
const handleLoad = () => {
|
|
setIsLoading(false);
|
|
onLoadComplete?.();
|
|
};
|
|
|
|
iframe.addEventListener('load', handleLoad);
|
|
return () => iframe.removeEventListener('load', handleLoad);
|
|
}
|
|
}, [onLoadComplete]);
|
|
|
|
return (
|
|
<div className="bg-white border rounded-lg overflow-hidden">
|
|
<div className="bg-gray-100 border-b px-4 py-2 flex items-center justify-between">
|
|
<h3 className="font-semibold text-gray-900">{title}</h3>
|
|
{isLoading && (
|
|
<span className="text-sm text-gray-600">Loading...</span>
|
|
)}
|
|
</div>
|
|
|
|
<div className="relative w-full h-[600px]">
|
|
{isLoading && (
|
|
<div className="absolute inset-0 flex items-center justify-center bg-gray-50">
|
|
<div className="w-8 h-8 border-4 border-blue-500 border-t-transparent rounded-full animate-spin" />
|
|
</div>
|
|
)}
|
|
<iframe
|
|
ref={iframeRef}
|
|
srcDoc={htmlContent}
|
|
title={title}
|
|
sandbox="allow-scripts allow-same-origin"
|
|
className="w-full h-full border-0"
|
|
/>
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|