{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "artifact-panel",
  "title": "Artifact Panel",
  "description": "A panel for content an assistant generates, with Preview and Code tabs, version stepping, copy, download and a streaming state.",
  "dependencies": [
    "@radix-ui/react-tabs",
    "lucide-react",
    "motion"
  ],
  "files": [
    {
      "path": "registry/tealmac/artifact-panel.tsx",
      "content": "// Adapted from Ballmac UI (https://github.com/vamsiy78/ballmac-ui), MIT License, Copyright (c) 2026 Ballmac.\n\"use client\"\n\nimport * as React from \"react\"\nimport * as TabsPrimitive from \"@radix-ui/react-tabs\"\nimport {\n  Check,\n  ChevronLeft,\n  ChevronRight,\n  Copy,\n  Download,\n  X,\n} from \"lucide-react\"\nimport { motion, useReducedMotion } from \"motion/react\"\n\nimport { cn } from \"@/lib/utils\"\n\ntype ArtifactPanelProps = Omit<\n  React.ComponentProps<\"section\">,\n  \"title\" | \"onChange\"\n> & {\n  /** Name of the generated thing, such as \"Pricing table\". */\n  title: string\n  /** Kind of artifact, shown under the title, such as \"React component\". */\n  kind?: string\n  /** Icon in the header tile. */\n  icon?: React.ReactNode\n  /** Source text. Shown in the Code tab and used for Copy and Download. */\n  code?: string\n  /** File name for Download and the code header, such as \"pricing-table.tsx\". */\n  filename?: string\n  /** Language label for the code, such as \"tsx\". */\n  language?: string\n  /** The rendered result. Put an iframe, a component or a document here. */\n  children?: React.ReactNode\n  /** How many versions exist. With more than one, arrows to step through them appear. */\n  versions?: number\n  /** Which version is shown, starting at 1 (controlled). */\n  version?: number\n  /** Called when the version changes. */\n  onVersionChange?: (version: number) => void\n  /** True while the model is still writing. Shows a progress edge and disables copy and download. */\n  streaming?: boolean\n  /** Tab shown first. */\n  defaultTab?: \"preview\" | \"code\"\n  /** Adds a close button. */\n  onClose?: () => void\n  /** Called after the code was copied. */\n  onCopy?: () => void\n  /** Extra header actions, placed before Copy. */\n  actions?: React.ReactNode\n  /** Classes for the preview canvas. */\n  previewClassName?: string\n}\n\nconst iconButton =\n  \"inline-flex size-8 shrink-0 items-center justify-center rounded-md text-muted-foreground outline-none transition-colors hover:bg-accent hover:text-foreground focus-visible:ring-[3px] focus-visible:ring-ring/50 disabled:pointer-events-none disabled:opacity-40 [&_svg]:size-4\"\n\nfunction ArtifactPanel({\n  title,\n  kind,\n  icon,\n  code,\n  filename,\n  language,\n  children,\n  versions = 1,\n  version: versionProp,\n  onVersionChange,\n  streaming = false,\n  defaultTab = \"preview\",\n  onClose,\n  onCopy,\n  actions,\n  previewClassName,\n  className,\n  ...props\n}: ArtifactPanelProps) {\n  const reduce = useReducedMotion()\n  const [internalVersion, setInternalVersion] = React.useState(versions)\n  const version = Math.min(\n    Math.max(versionProp ?? internalVersion, 1),\n    versions\n  )\n  const [copied, setCopied] = React.useState(false)\n  const timer = React.useRef<ReturnType<typeof setTimeout>>(undefined)\n  React.useEffect(() => () => clearTimeout(timer.current), [])\n  const hasCode = code !== undefined\n  const hasPreview = children !== undefined && children !== null\n\n  function go(next: number) {\n    const clamped = Math.min(Math.max(next, 1), versions)\n    setInternalVersion(clamped)\n    onVersionChange?.(clamped)\n  }\n\n  async function copy() {\n    if (!code) return\n    try {\n      await navigator.clipboard.writeText(code)\n    } catch {\n      return\n    }\n    setCopied(true)\n    onCopy?.()\n    clearTimeout(timer.current)\n    timer.current = setTimeout(() => setCopied(false), 1800)\n  }\n\n  function download() {\n    if (!code) return\n    const url = URL.createObjectURL(\n      new Blob([code], { type: \"text/plain;charset=utf-8\" })\n    )\n    const a = document.createElement(\"a\")\n    a.href = url\n    a.download = filename ?? \"artifact.txt\"\n    a.click()\n    URL.revokeObjectURL(url)\n  }\n\n  const tabClass =\n    \"relative inline-flex h-7 items-center rounded-md px-3 text-[13px] font-medium text-muted-foreground outline-none transition-colors hover:text-foreground focus-visible:ring-[3px] focus-visible:ring-ring/50 data-[state=active]:bg-background data-[state=active]:text-foreground data-[state=active]:shadow-xs\"\n\n  return (\n    <section\n      data-slot=\"artifact-panel\"\n      aria-label={title}\n      aria-busy={streaming || undefined}\n      className={cn(\n        \"bg-card text-card-foreground relative flex w-full flex-col overflow-hidden rounded-xl border shadow-xs\",\n        className\n      )}\n      {...props}\n    >\n      <TabsPrimitive.Root\n        defaultValue={hasPreview ? defaultTab : \"code\"}\n        className=\"flex min-h-0 flex-1 flex-col\"\n      >\n        <header className=\"relative flex flex-wrap items-center gap-x-3 gap-y-2 border-b px-3 py-2.5\">\n          <div className=\"flex min-w-[10rem] flex-1 items-center gap-2.5\">\n            {icon && (\n              <span\n                aria-hidden=\"true\"\n                className=\"bg-muted text-foreground flex size-8 shrink-0 items-center justify-center rounded-lg border [&_svg]:size-4\"\n              >\n                {icon}\n              </span>\n            )}\n            <div className=\"min-w-0\">\n              <h3 className=\"text-foreground truncate text-sm leading-5 font-semibold\">\n                {title}\n              </h3>\n              {(kind || streaming) && (\n                <p className=\"text-muted-foreground truncate text-xs leading-4\">\n                  {streaming ? \"Writing…\" : kind}\n                </p>\n              )}\n            </div>\n          </div>\n\n          {hasPreview && hasCode && (\n            <TabsPrimitive.List\n              aria-label={\"View\"}\n              className=\"bg-muted inline-flex items-center gap-0.5 rounded-lg p-0.5\"\n            >\n              <TabsPrimitive.Trigger value=\"preview\" className={tabClass}>\n                Preview\n              </TabsPrimitive.Trigger>\n              <TabsPrimitive.Trigger value=\"code\" className={tabClass}>\n                Code\n              </TabsPrimitive.Trigger>\n            </TabsPrimitive.List>\n          )}\n\n          <div className=\"flex items-center gap-0.5\">\n            {versions > 1 && (\n              <span\n                className=\"me-1 inline-flex items-center\"\n                role=\"group\"\n                aria-label={\"Versions\"}\n              >\n                <button\n                  type=\"button\"\n                  aria-label={\"Previous version\"}\n                  disabled={version <= 1 || streaming}\n                  onClick={() => go(version - 1)}\n                  className={iconButton}\n                >\n                  <ChevronLeft aria-hidden=\"true\" className=\"rtl:rotate-180\" />\n                </button>\n                <span\n                  className=\"text-muted-foreground min-w-12 text-center font-mono text-xs tabular-nums\"\n                  aria-live=\"polite\"\n                >\n                  v{version} <span aria-hidden=\"true\">/ {versions}</span>\n                  <span className=\"sr-only\"> {`of ${versions}`}</span>\n                </span>\n                <button\n                  type=\"button\"\n                  aria-label={\"Next version\"}\n                  disabled={version >= versions || streaming}\n                  onClick={() => go(version + 1)}\n                  className={iconButton}\n                >\n                  <ChevronRight aria-hidden=\"true\" className=\"rtl:rotate-180\" />\n                </button>\n              </span>\n            )}\n            {actions}\n            {hasCode && (\n              <>\n                <button\n                  type=\"button\"\n                  aria-label={copied ? \"Copied\" : \"Copy code\"}\n                  title={copied ? \"Copied\" : \"Copy code\"}\n                  disabled={streaming}\n                  onClick={copy}\n                  className={iconButton}\n                >\n                  {copied ? (\n                    <Check aria-hidden=\"true\" />\n                  ) : (\n                    <Copy aria-hidden=\"true\" />\n                  )}\n                </button>\n                <button\n                  type=\"button\"\n                  aria-label={`Download ${filename ?? \"file\"}`}\n                  title={\"Download\"}\n                  disabled={streaming}\n                  onClick={download}\n                  className={iconButton}\n                >\n                  <Download aria-hidden=\"true\" />\n                </button>\n              </>\n            )}\n            {onClose && (\n              <button\n                type=\"button\"\n                aria-label={\"Close artifact\"}\n                title={\"Close\"}\n                onClick={onClose}\n                className={iconButton}\n              >\n                <X aria-hidden=\"true\" />\n              </button>\n            )}\n          </div>\n          {streaming && (\n            <span\n              aria-hidden=\"true\"\n              className=\"absolute inset-x-0 bottom-[-1px] h-0.5 overflow-hidden\"\n            >\n              {reduce ? (\n                <span className=\"bg-foreground/30 block h-full w-full\" />\n              ) : (\n                <motion.span\n                  className=\"bg-foreground/70 block h-full w-1/3\"\n                  animate={{ x: [\"-100%\", \"300%\"] }}\n                  transition={{\n                    duration: 1.4,\n                    repeat: Infinity,\n                    ease: \"easeInOut\",\n                  }}\n                />\n              )}\n            </span>\n          )}\n        </header>\n\n        {hasPreview && (\n          <TabsPrimitive.Content\n            value=\"preview\"\n            className={cn(\n              \"focus-visible:ring-ring/50 min-h-0 flex-1 overflow-auto bg-[radial-gradient(var(--border)_1px,transparent_1px)] [background-size:16px_16px] p-5 outline-none focus-visible:ring-[3px] focus-visible:ring-inset\",\n              previewClassName\n            )}\n            tabIndex={0}\n          >\n            {children}\n          </TabsPrimitive.Content>\n        )}\n        {hasCode && (\n          <TabsPrimitive.Content\n            value=\"code\"\n            className=\"bg-muted/30 focus-visible:ring-ring/50 min-h-0 flex-1 overflow-auto outline-none focus-visible:ring-[3px] focus-visible:ring-inset\"\n            tabIndex={0}\n          >\n            {filename || language ? (\n              <div className=\"bg-muted/60 text-muted-foreground sticky top-0 flex h-8 items-center gap-2 border-b px-4 font-mono text-[11px] backdrop-blur\">\n                {filename}\n                {language && (\n                  <span className=\"ms-auto uppercase\">{language}</span>\n                )}\n              </div>\n            ) : null}\n            <pre\n              dir=\"ltr\"\n              className=\"text-foreground p-4 font-mono text-xs leading-5\"\n            >\n              <code>{code}</code>\n              {streaming && (\n                <span\n                  aria-hidden=\"true\"\n                  className=\"bg-foreground ms-0.5 inline-block h-3.5 w-1.5 translate-y-0.5 animate-pulse motion-reduce:animate-none\"\n                />\n              )}\n            </pre>\n          </TabsPrimitive.Content>\n        )}\n      </TabsPrimitive.Root>\n    </section>\n  )\n}\n\nexport { ArtifactPanel, type ArtifactPanelProps }\n",
      "type": "registry:ui"
    }
  ],
  "type": "registry:ui"
}