Artifact panel

Pro

A document or file to read beside the work, for a SidePanel tab or a full-width view. A document's text is read into safe blocks (headings, lists, emphasis, code) and never rendered as HTML, so a document a model wrote can't inject markup or links. A file with no text shows its type and size, and an image the preview your app serves. Download goes through onDownload, one menu item per format; onFullWidthChange adds Open full width.

Markdown

Preview

Install

With the Candor CLI

First time? Get started sets up the registry and theme. Or with shadcn directly: npx shadcn@latest add @candor/artifact-panel

Brings in: attachment-preview, button, chat, dropdown-menu, locale, message-content, relative-time, safe-markdown, work, utils · npm: lucide-react@^1.67.0

Usage

TSX · The example above
"use client"

import * as React from "react"
import { FileText, Sheet } from "lucide-react"
import { ArtifactPanel } from "@/components/artifact-panel"
import { SidePanel } from "@/components/side-panel"
import type { Artifact } from "@/lib/work"

const report: Artifact = {
  id: "report",
  title: "Pricing review",
  state: "Checked",
  formats: [
    { id: "docx", label: "Word" },
    { id: "pdf", label: "PDF" },
  ],
  // Markdown text is read into safe blocks; <b>tags</b> stay text and links aren't made.
  text: `# Pricing review

Three plans cover **92%** of last quarter's revenue. The other four can merge into them.

## What changes

- Starter and Basic become one plan at the Starter price.
- Team keeps its price; seats above 20 move to Business.

## What stays

Annual discounts and every grandfathered contract.`,
}

const data: Artifact = {
  id: "data",
  title: "Revenue by plan",
  state: "Not yet checked",
  file: { id: "csv", name: "revenue-by-plan.csv", mediaType: "text/csv", size: 184_000 },
}

export default function Example() {
  const [wide, setWide] = React.useState(false)
  const [downloaded, setDownloaded] = React.useState("")
  const download = (id: string, format?: string) => setDownloaded(`${id}${format ? ` as ${format}` : ""}`)
  const panel = (a: Artifact) => <ArtifactPanel artifact={a} onDownload={download} fullWidth={wide} onFullWidthChange={setWide} className="-m-4 h-[calc(100%+2rem)]" />
  return (
    <div className="flex max-w-2xl flex-col gap-2">
      <div className={wide ? "h-[26rem] overflow-hidden rounded-lg border bg-background" : "h-[26rem] max-w-md overflow-hidden rounded-lg border bg-chrome"}>
        <SidePanel
          tabs={[
            { id: "report", label: "Pricing review", Icon: FileText, content: panel(report) },
            { id: "data", label: "Revenue by plan", Icon: Sheet, content: panel(data) },
          ]}
        />
      </div>
      <p className="text-xs text-muted-foreground" aria-live="polite">
        {downloaded && `Your app would download ${downloaded}.`}
      </p>
    </div>
  )
}

<ArtifactPanel>

PropTypeDefaultDescription
artifactrequiredArtifact—What to show: a document (`text` or `blocks`) or a file.
onDownload((id: string, format?: string) => void) | undefined—Called with the artifact's id and the chosen format's id (undefined with no formats).
fullWidthboolean | undefinedfalseWhether it's showing full width; sets a reading width and the toggle's words. Defaults to false.
onFullWidthChange((fullWidth: boolean) => void) | undefined—Adds the Open full width / Back to the panel button; called with the new state.
sourcesMessageSource[] | undefined—Sources the document cites by marker.
onOpenEntity((entity: EntityRef) => void) | undefined—Makes entity chips in the document open their thing.
headingLevel1 | 2 | 3 | 4 | undefined2The level of the title's heading, to fit the page's outline. Defaults to 2.
classNamestring | undefined—Extra classes for the outer element.

Strings

The fixed words, in English by default. Change any of them under artifactPanel in <LocaleProvider strings={{ … }}>; the table is exported as artifactPanelStrings.

KeyEnglish
downloadDownload
downloadAsDownload {format}
openFullWidthOpen full width
backToPanelBack to the panel
updatedUpdated {time}
noPreviewThere’s no preview for this file. Download it to open it.
emptyNothing to show yet.