{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "component-preview",
  "title": "Component Preview",
  "description": "A framed example with a tab for the source beside it.",
  "dependencies": [
    "lucide-react@^1.31.0"
  ],
  "registryDependencies": [
    "utils"
  ],
  "files": [
    {
      "path": "registry/default/component-preview/component-preview.tsx",
      "content": "\"use client\"\n\nimport * as React from \"react\"\nimport { Check, Clipboard } from \"lucide-react\"\n\nimport { cn } from \"@/lib/utils\"\n\nexport type ComponentPreviewProps = Omit<\n  React.HTMLAttributes<HTMLDivElement>,\n  \"children\" | \"title\"\n> & {\n  /** The living example. */\n  children: React.ReactNode\n  /** Source for the second tab. Without it there is only the preview. */\n  code?: string\n  title?: React.ReactNode\n  defaultView?: \"preview\" | \"code\"\n  previewLabel?: string\n  codeLabel?: string\n  /** Extra controls in the toolbar, such as a restart button. */\n  actions?: React.ReactNode\n  align?: \"center\" | \"start\"\n  frameClassName?: string\n  copyable?: boolean\n}\n\ntype View = \"preview\" | \"code\"\n\nexport function ComponentPreview({\n  children,\n  code,\n  title,\n  defaultView = \"preview\",\n  previewLabel = \"Preview\",\n  codeLabel = \"Code\",\n  actions,\n  align = \"center\",\n  frameClassName,\n  copyable = true,\n  className,\n  ...rootProps\n}: ComponentPreviewProps) {\n  const id = React.useId()\n  const [view, setView] = React.useState<View>(code ? defaultView : \"preview\")\n  const [copyState, setCopyState] = React.useState<\"idle\" | \"copied\" | \"error\">(\n    \"idle\"\n  )\n  const timer = React.useRef<ReturnType<typeof setTimeout> | null>(null)\n\n  React.useEffect(\n    () => () => {\n      if (timer.current) clearTimeout(timer.current)\n    },\n    []\n  )\n\n  const views: View[] = code ? [\"preview\", \"code\"] : [\"preview\"]\n  const current = code ? view : \"preview\"\n\n  async function copy() {\n    if (!code) return\n\n    try {\n      await navigator.clipboard.writeText(code)\n      setCopyState(\"copied\")\n    } catch {\n      // Denied permission, an insecure context, or a sandboxed frame.\n      setCopyState(\"error\")\n    }\n    if (timer.current) clearTimeout(timer.current)\n    timer.current = setTimeout(() => setCopyState(\"idle\"), 1400)\n  }\n\n  // Arrow keys move between tabs, which is what a tablist is expected to do.\n  function onKeyDown(event: React.KeyboardEvent<HTMLDivElement>) {\n    const step =\n      event.key === \"ArrowRight\" ? 1 : event.key === \"ArrowLeft\" ? -1 : 0\n\n    if (step === 0) return\n\n    event.preventDefault()\n    const next =\n      views[(views.indexOf(current) + step + views.length) % views.length]!\n    setView(next)\n    document.getElementById(`${id}-${next}-tab`)?.focus()\n  }\n\n  return (\n    <div\n      data-slot=\"component-preview\"\n      className={cn(\n        \"border-border bg-muted/40 min-w-0 overflow-hidden rounded-[calc(var(--radius)+0.15rem)] border\",\n        className\n      )}\n      {...rootProps}\n    >\n      <div className=\"border-border flex items-center gap-2 border-b px-2.5 py-1.5\">\n        {title ? (\n          <span className=\"text-muted-foreground mr-1 min-w-0 truncate text-xs\">\n            {title}\n          </span>\n        ) : null}\n\n        <div\n          role=\"tablist\"\n          aria-label=\"Example view\"\n          onKeyDown={onKeyDown}\n          className=\"flex min-w-0 items-center gap-3\"\n        >\n          {views.map((entry) => (\n            <button\n              key={entry}\n              id={`${id}-${entry}-tab`}\n              type=\"button\"\n              role=\"tab\"\n              aria-selected={current === entry}\n              aria-controls={`${id}-${entry}-panel`}\n              tabIndex={current === entry ? 0 : -1}\n              className=\"text-muted-foreground hover:text-foreground aria-selected:text-foreground after:bg-primary relative min-h-8 px-0.5 pb-1.5 text-xs font-medium transition-colors duration-150 after:absolute after:inset-x-0 after:bottom-0 after:h-[2px] after:rounded-full after:opacity-0 aria-selected:after:opacity-100 motion-reduce:transition-none\"\n              onClick={() => setView(entry)}\n            >\n              {entry === \"preview\" ? previewLabel : codeLabel}\n            </button>\n          ))}\n        </div>\n\n        <div className=\"ml-auto flex shrink-0 items-center gap-0.5\">\n          {actions}\n\n          {code && copyable ? (\n            <button\n              type=\"button\"\n              aria-label={copyState === \"copied\" ? \"Copied\" : \"Copy code\"}\n              className=\"text-muted-foreground hover:bg-card hover:text-foreground inline-flex size-8 items-center justify-center rounded-md transition-colors duration-150 motion-reduce:transition-none\"\n              onClick={copy}\n            >\n              {copyState === \"copied\" ? (\n                <Check aria-hidden=\"true\" size={14} />\n              ) : (\n                <Clipboard aria-hidden=\"true\" size={14} />\n              )}\n            </button>\n          ) : null}\n        </div>\n      </div>\n\n      {/*\n        The preview stays mounted while the source is showing, so anything the\n        reader set up in the example is still there when they switch back.\n      */}\n      <div\n        id={`${id}-preview-panel`}\n        role=\"tabpanel\"\n        aria-labelledby={`${id}-preview-tab`}\n        hidden={current !== \"preview\"}\n        className={cn(\n          \"bg-card flex min-h-40 p-6 md:p-10\",\n          align === \"center\" ? \"items-center justify-center\" : \"items-start\",\n          frameClassName\n        )}\n      >\n        {children}\n      </div>\n\n      {code ? (\n        <div\n          id={`${id}-code-panel`}\n          role=\"tabpanel\"\n          aria-labelledby={`${id}-code-tab`}\n          hidden={current !== \"code\"}\n        >\n          <pre\n            data-slot=\"component-preview-code\"\n            tabIndex={0}\n            className=\"text-foreground m-0 max-h-96 overflow-auto rounded-none bg-transparent px-3 py-2.5 font-[family-name:var(--font-mono),monospace] text-xs leading-relaxed\"\n          >\n            <code>{code}</code>\n          </pre>\n        </div>\n      ) : null}\n\n      <span aria-live=\"polite\" className=\"sr-only\">\n        {copyState === \"copied\"\n          ? \"Code copied to clipboard.\"\n          : copyState === \"error\"\n            ? \"Code could not be copied.\"\n            : \"\"}\n      </span>\n    </div>\n  )\n}\n",
      "type": "registry:ui"
    }
  ],
  "categories": [
    "docs",
    "preview",
    "tabs"
  ],
  "type": "registry:ui"
}