"use client" import React, { useState, useCallback, useRef, useEffect } from 'react' /** Loading skeleton for iframe embeds — clean iframe-sized rectangle. * Uses `bg-ods-card` (visible token) rather than `bg-ods-skeleton` * which resolves to transparent in this build (the same gotcha * documented in `chat-message-row.tsx`'s skeleton). No fake inner * placeholder cruft — a real loading iframe shows a blank rectangle. * * Exported so a viewer that is still RESOLVING where its frame points * (ClaudeEmbed probing for a self-hosted mirror, before it has a `src`) * can render the IDENTICAL skeleton through `EmbedViewerFrame`, keeping * every embed's loading state 1:1 with the Figma/Sheets/PDF viewers. */ export function EmbedLoadingSkeleton({ height }: { height?: string }) { return (
) } export interface EmbedIframeProps { /** The URL to embed */ src: string /** Accessible title for the iframe */ title: string /** Additional class names for the outer container */ className?: string /** Container height (CSS value). Defaults to `calc(100vh - 250px)` */ height?: string /** iframe `allow` attribute */ allow?: string /** iframe `referrerPolicy` attribute */ referrerPolicy?: React.IframeHTMLAttributes['referrerPolicy'] /** iframe `loading` attribute */ loading?: 'eager' | 'lazy' /** iframe `allowFullScreen` attribute */ allowFullScreen?: boolean /** * iframe `sandbox` attribute. Omitted by default — a first-party vendor * (Figma, Google Sheets) is trusted with the full frame contract. * * Pass it for a frame whose contents are AUTHORED BY USERS: without the * attribute an embedded document may navigate the top window, and a sandbox * that omits `allow-top-navigation` takes that away. On a CROSS-ORIGIN frame * `allow-same-origin` grants the frame its own origin, never the host's. */ sandbox?: string } /** * Base iframe wrapper with loading skeleton and proper memory cleanup. * * Prevents memory leaks by: * - Using `key={src}` to force full unmount/remount when src changes * - Setting iframe src to about:blank on unmount to release the embedded document * - Resetting loaded state when src changes */ export function EmbedIframe({ src, title, className, height, allow, referrerPolicy, loading, allowFullScreen, sandbox, }: EmbedIframeProps) { const [isLoaded, setIsLoaded] = useState(false) const iframeRef = useRef(null) const handleLoad = useCallback(() => setIsLoaded(true), []) useEffect(() => { setIsLoaded(false) }, [src]) useEffect(() => { const iframe = iframeRef.current return () => { if (iframe) { try { iframe.src = 'about:blank' } catch { // Cross-origin iframes may throw — safe to ignore } } } }, [src]) const resolvedHeight = height || 'calc(100vh - 250px)' return ( <> {!isLoaded && }