The ONE viewer shell shared by every embed viewer (Google Sheets / PDF / Figma): header row (icon + title + caller-supplied `actions` ReactNode) over `EmbedIframe`, inline empty state when `src` is falsy. ## Props (`EmbedViewerFrameProps`) | Prop | Type | Required | Description | |---|---|---|---| | `icon` | `ReactNode` | ✅ | Header icon (caller sizes it, viewers pass `w-5 h-5`) | | `title` | `string` | ✅ | Header text | | `titleVariant` | `'h3' \| 'h6'` | ❌ | `h2.text-h3` (default, sheets/pdf) vs `span.text-h6 font-semibold` (figma) | | `actions` | `ReactNode` | ❌ | Rendered verbatim in the header row — caller owns container AND state (figma's present/browse ToggleGroup keeps its useState in FigmaEmbed) | | `src` | `string \| null \| undefined` | ✅ | Falsy → inline empty state under the header | | `emptyIcon` / `emptyMessage` | — | ❌ | Inline empty-state content (viewers pass `w-16 h-16` icons) | | `height` / `allow` / `allowFullScreen` / `loading` | — | ❌ | Passed to `EmbedIframe` | | `className` | `string` | ❌ | Wrapper override (default `space-y-4`; figma passes `my-6 space-y-3`) | Output parity with the pre-extraction viewers is pinned by `__tests__/embed-viewer-frame.test.tsx`. Viewers that render a standalone header-LESS empty state (sheets/pdf with no URL) early-return before mounting the frame.