Elements

49 / 122 · Structured output

Web preview

Chrome for a sandboxed preview: a URL bar, reload, and open-in-new around a frame you isolate.

scf.auiusercontent.com
Loading preview

Installation

npx shadcn@latest add "@assistant-ui/elements-web-preview"

Usage

import { WebPreview } from "@/components/elements/web-preview";

<WebPreview origin="scf.auiusercontent.com" loading={false} onReload={reload}>
  <iframe title="Preview" src={sandboxUrl} sandbox="allow-scripts" />
</WebPreview>

Props

origin*stringHost the content is served from. Show the sandbox origin, not your own, so the isolation is legible. Displaying it does not create the isolation; the frame you pass as children must already be sandboxed.
loading*booleanFades the frame out and shimmers a status while the sandbox is being set up.
children*React.ReactNodeThe frame itself. In production this is a sandboxed iframe, not inline markup.
onReload() => voidCalled from the reload control.
onOpenExternal() => voidCalled from the open-in-new-tab control.
classNamestringExtra classes merged onto the root.

Source

web-preview.tsx
"use client";

import { ExternalLinkIcon, RotateCwIcon } from "lucide-react";
import { cn } from "@/lib/utils";
import { field, ghostButton, mono, paper } from "./surfaces";

/**
 * Chrome around a preview: a URL bar, reload, and open-in-new. It renders
 * `children` as given and enforces no isolation of its own, so the caller is
 * responsible for passing an already-sandboxed frame.
 */
export function WebPreview({
  origin,
  loading,
  children,
  onReload,
  onOpenExternal,
  className,
}: {
  origin: string;
  loading: boolean;
  children: React.ReactNode;
  onReload?: () => void;
  onOpenExternal?: () => void;
  className?: string;
}) {
  return (
    <div
      className={cn(
        paper,
        "flex w-full max-w-md flex-col overflow-hidden rounded-2xl",
        className,
      )}
    >
      <div className="flex items-center gap-1.5 px-2.5 py-2">
        <button
          type="button"
          aria-label="Reload the preview"
          onClick={onReload}
          className={cn(ghostButton, "size-7 shrink-0")}
        >
          <RotateCwIcon
            className={cn(
              "size-3.5",
              loading && "animate-spin motion-reduce:animate-none",
            )}
          />
        </button>

        <span
          className={cn(
            field,
            "flex min-w-0 flex-1 items-center gap-1.5 rounded-full px-2.5 py-1",
          )}
        >
          <span className={cn(mono, "text-foreground/45 min-w-0 truncate")}>
            {origin}
          </span>
        </span>

        <button
          type="button"
          aria-label="Open the preview in a new tab"
          onClick={onOpenExternal}
          className={cn(ghostButton, "size-7 shrink-0")}
        >
          <ExternalLinkIcon className="size-3.5" />
        </button>
      </div>

      <div className="border-foreground/[0.07] relative min-h-[9rem] border-t">
        <div
          aria-hidden={loading}
          className={cn(
            "transition-opacity duration-300 motion-reduce:transition-none",
            loading && "invisible opacity-0",
          )}
        >
          {children}
        </div>
        {loading && (
          <div className="absolute inset-0 flex items-center justify-center">
            <span className="text-foreground/40 relative inline-block text-xs leading-none">
              <span>Loading preview</span>
              <span
                aria-hidden
                className="shimmer pointer-events-none absolute inset-0 motion-reduce:animate-none"
              >
                Loading preview
              </span>
            </span>
          </div>
        )}
      </div>
    </div>
  );
}