Artifact panel
ProA 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.
Preview
Install
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
"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>
| Prop | Type | Default | Description |
|---|---|---|---|
| artifactrequired | Artifact | — | 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). |
| fullWidth | boolean | undefined | false | Whether 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. |
| sources | MessageSource[] | undefined | — | Sources the document cites by marker. |
| onOpenEntity | ((entity: EntityRef) => void) | undefined | — | Makes entity chips in the document open their thing. |
| headingLevel | 1 | 2 | 3 | 4 | undefined | 2 | The level of the title's heading, to fit the page's outline. Defaults to 2. |
| className | string | 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.
| Key | English |
|---|---|
| download | Download |
| downloadAs | Download {format} |
| openFullWidth | Open full width |
| backToPanel | Back to the panel |
| updated | Updated {time} |
| noPreview | There’s no preview for this file. Download it to open it. |
| empty | Nothing to show yet. |