Skip to content

Commit 415f9d6

Browse files
authored
feat(showcase/shell-dashboard): add iframe link preview on hover (CopilotKit#4864)
## Summary - Adds hover-triggered iframe preview popups to Demo and Code links in the showcase dashboard grid - Hovering a link for 300ms shows a 400×300 popup with a scaled-down (thumbnail) view of the target page - Clicking the popup opens the URL in a new tab; moving away dismisses it after 200ms - Includes loading state (pulsing indicator), fade-in on load, and "Preview unavailable" fallback after 8s timeout - Only one popup visible at a time (singleton); touch devices skip previews entirely ## Test plan - [ ] 16 unit tests covering: hover delay, dismiss timing, bridge gap, iframe src, click overlay, portal rendering, viewport flip, singleton, touch device exclusion, loading/loaded/unavailable states - [ ] `vitest run` — 533 tests pass, 0 failures - [ ] `tsc --noEmit` — no new type errors - [ ] `next build` — builds successfully - [ ] Manual smoke test: hover delay, dismiss, bridge gap, click-through, singleton, loading states all verified in browser 🤖 Generated with [Claude Code](https://claude.com/claude-code)
2 parents 0a40dec + 678d832 commit 415f9d6

7 files changed

Lines changed: 751 additions & 18 deletions

File tree

showcase/shell-dashboard/package-lock.json

Lines changed: 11 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

showcase/shell-dashboard/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
"@testing-library/react": "^16.3.2",
2929
"@types/node": "^22.0.0",
3030
"@types/react": "^19.0.0",
31+
"@types/react-dom": "^19.0.0",
3132
"@vitejs/plugin-react": "^5.2.0",
3233
"jsdom": "^29.0.2",
3334
"postcss": "^8.5.0",

showcase/shell-dashboard/src/app/layout.tsx

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,10 @@ export default function RootLayout({
4141
<head>
4242
<script dangerouslySetInnerHTML={{ __html: themeInitScript }} />
4343
</head>
44-
<body>{children}</body>
44+
<body>
45+
{children}
46+
<div id="link-preview-root" />
47+
</body>
4548
</html>
4649
);
4750
}

showcase/shell-dashboard/src/components/__tests__/composed-cell.test.tsx

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
* Unit tests for ComposedCell — overlay-aware cell renderer that composes
33
* different content layers based on which overlays are currently active.
44
*/
5+
import React from "react";
56
import { describe, it, expect, vi, beforeEach } from "vitest";
67
import { render } from "@testing-library/react";
78
import { ComposedCell } from "../composed-cell";
@@ -34,6 +35,16 @@ vi.mock("@/components/cell-pieces", () => ({
3435
),
3536
}));
3637

38+
vi.mock("@/components/link-preview", () => ({
39+
LinkPreview: vi.fn(
40+
({ children, href }: { children: React.ReactNode; href: string }) => (
41+
<span data-testid="mock-link-preview" data-href={href}>
42+
{children}
43+
</span>
44+
),
45+
),
46+
}));
47+
3748
vi.mock("@/components/command-cell", () => ({
3849
CommandCell: vi.fn(({ ctx }: { ctx: CellContext }) => (
3950
<div data-testid="mock-command-cell">{ctx.demo.command}</div>
@@ -311,6 +322,22 @@ describe("ComposedCell", () => {
311322
expect(queryByText("Demo")).not.toBeInTheDocument();
312323
});
313324

325+
it("wraps Demo and Code links with LinkPreview", () => {
326+
const ctx = makeCtx();
327+
const { getAllByTestId } = render(
328+
<ComposedCell ctx={ctx} overlays={overlaySet("links")} />,
329+
);
330+
331+
const previews = getAllByTestId("mock-link-preview");
332+
expect(previews).toHaveLength(2);
333+
expect(previews[0].getAttribute("data-href")).toBe(
334+
"https://demo.test/preview",
335+
);
336+
expect(previews[1].getAttribute("data-href")).toBe(
337+
"https://demo.test/code",
338+
);
339+
});
340+
314341
it("renders empty div when only parity overlay is active", () => {
315342
const ctx = makeCtx();
316343
const { getByTestId, queryByTestId } = render(

0 commit comments

Comments
 (0)