{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "json-output",
  "title": "JSON Output",
  "description": "Component for rendering JSON data in an interactive, expandable tree view. Useful for inspecting complex data structures and API responses.",
  "dependencies": [
    "@uiw/react-json-view"
  ],
  "registryDependencies": [
    "collapsible"
  ],
  "files": [
    {
      "path": "registry/outputs/json-output.tsx",
      "content": "\"use client\";\n\nimport { ChevronRightIcon } from \"lucide-react\";\nimport {\n  createContext,\n  type HTMLAttributes,\n  useContext,\n  useState,\n} from \"react\";\nimport {\n  Collapsible,\n  CollapsibleContent,\n  CollapsibleTrigger,\n} from \"@/components/ui/collapsible\";\nimport { cn } from \"@/lib/utils\";\n\ninterface JsonViewerContextType {\n  expandedPaths: Set<string>;\n  togglePath: (path: string) => void;\n  displayDataTypes: boolean;\n}\n\nconst JsonViewerContext = createContext<JsonViewerContextType>({\n  expandedPaths: new Set(),\n  togglePath: () => undefined,\n  displayDataTypes: false,\n});\n\ninterface JsonOutputProps {\n  /**\n   * The JSON data to render. Can be any JSON-serializable value.\n   */\n  data: unknown;\n  /**\n   * Collapse nested objects beyond this depth.\n   * Set to `false` to expand all, or a number for depth limit.\n   * @default false\n   */\n  collapsed?: boolean | number;\n  /**\n   * Show data types alongside values\n   * @default false\n   */\n  displayDataTypes?: boolean;\n  /**\n   * Additional CSS classes\n   */\n  className?: string;\n}\n\n/**\n * JsonOutput component for rendering JSON data in notebook outputs\n *\n * Displays JSON data in an interactive, expandable tree view.\n * Useful for inspecting complex data structures, API responses,\n * and object representations from Jupyter kernels.\n */\nexport function JsonOutput({\n  data,\n  collapsed = false,\n  displayDataTypes = false,\n  className = \"\",\n}: JsonOutputProps) {\n  // Calculate initial expanded paths based on collapsed prop\n  const getInitialExpanded = (): Set<string> => {\n    if (collapsed === false) {\n      // Expand all - collect all paths\n      return collectAllPaths(data, \"$\");\n    }\n    if (collapsed === true) {\n      // Collapse all\n      return new Set();\n    }\n    // Expand up to specified depth\n    return collectPathsToDepth(data, \"$\", collapsed);\n  };\n\n  const [expandedPaths, setExpandedPaths] =\n    useState<Set<string>>(getInitialExpanded);\n\n  const togglePath = (path: string) => {\n    const newExpanded = new Set(expandedPaths);\n    if (newExpanded.has(path)) {\n      newExpanded.delete(path);\n    } else {\n      newExpanded.add(path);\n    }\n    setExpandedPaths(newExpanded);\n  };\n\n  if (data === undefined || data === null) {\n    return (\n      <div\n        data-slot=\"json-output\"\n        className={cn(\n          \"not-prose py-2 font-mono text-sm text-muted-foreground\",\n          className,\n        )}\n      >\n        <JsonPrimitive value={data} displayDataTypes={displayDataTypes} />\n      </div>\n    );\n  }\n\n  return (\n    <JsonViewerContext.Provider\n      value={{ expandedPaths, togglePath, displayDataTypes }}\n    >\n      <div data-slot=\"json-output\" className={cn(\"not-prose py-2\", className)}>\n        <div className=\"rounded-lg border bg-background p-3 font-mono text-sm\">\n          <JsonValue value={data} path=\"$\" keyName={null} isLast />\n        </div>\n      </div>\n    </JsonViewerContext.Provider>\n  );\n}\n\nfunction collectAllPaths(value: unknown, path: string): Set<string> {\n  const paths = new Set<string>();\n  if (value && typeof value === \"object\") {\n    paths.add(path);\n    if (Array.isArray(value)) {\n      value.forEach((item, index) => {\n        const childPaths = collectAllPaths(item, `${path}[${index}]`);\n        childPaths.forEach((p) => paths.add(p));\n      });\n    } else {\n      Object.entries(value).forEach(([key, val]) => {\n        const childPaths = collectAllPaths(val, `${path}.${key}`);\n        childPaths.forEach((p) => paths.add(p));\n      });\n    }\n  }\n  return paths;\n}\n\nfunction collectPathsToDepth(\n  value: unknown,\n  path: string,\n  maxDepth: number,\n  currentDepth = 0,\n): Set<string> {\n  const paths = new Set<string>();\n  if (value && typeof value === \"object\" && currentDepth < maxDepth) {\n    paths.add(path);\n    if (Array.isArray(value)) {\n      value.forEach((item, index) => {\n        const childPaths = collectPathsToDepth(\n          item,\n          `${path}[${index}]`,\n          maxDepth,\n          currentDepth + 1,\n        );\n        childPaths.forEach((p) => paths.add(p));\n      });\n    } else {\n      Object.entries(value).forEach(([key, val]) => {\n        const childPaths = collectPathsToDepth(\n          val,\n          `${path}.${key}`,\n          maxDepth,\n          currentDepth + 1,\n        );\n        childPaths.forEach((p) => paths.add(p));\n      });\n    }\n  }\n  return paths;\n}\n\ninterface JsonValueProps {\n  value: unknown;\n  path: string;\n  keyName: string | null;\n  isLast: boolean;\n}\n\nfunction JsonValue({ value, path, keyName, isLast }: JsonValueProps) {\n  const { displayDataTypes } = useContext(JsonViewerContext);\n\n  if (value === null || value === undefined) {\n    return (\n      <JsonEntry keyName={keyName} isLast={isLast}>\n        <JsonPrimitive value={value} displayDataTypes={displayDataTypes} />\n      </JsonEntry>\n    );\n  }\n\n  if (Array.isArray(value)) {\n    return (\n      <JsonArray value={value} path={path} keyName={keyName} isLast={isLast} />\n    );\n  }\n\n  if (typeof value === \"object\") {\n    return (\n      <JsonObject\n        value={value as Record<string, unknown>}\n        path={path}\n        keyName={keyName}\n        isLast={isLast}\n      />\n    );\n  }\n\n  return (\n    <JsonEntry keyName={keyName} isLast={isLast}>\n      <JsonPrimitive value={value} displayDataTypes={displayDataTypes} />\n    </JsonEntry>\n  );\n}\n\ninterface JsonEntryProps extends HTMLAttributes<HTMLDivElement> {\n  keyName: string | null;\n  isLast: boolean;\n}\n\nfunction JsonEntry({\n  keyName,\n  isLast,\n  children,\n  className,\n  ...props\n}: JsonEntryProps) {\n  return (\n    <div className={cn(\"flex items-baseline gap-1\", className)} {...props}>\n      {keyName !== null && (\n        <>\n          <span className=\"text-foreground\">&quot;{keyName}&quot;</span>\n          <span className=\"text-muted-foreground\">:</span>\n        </>\n      )}\n      {children}\n      {!isLast && <span className=\"text-muted-foreground\">,</span>}\n    </div>\n  );\n}\n\ninterface JsonObjectProps {\n  value: Record<string, unknown>;\n  path: string;\n  keyName: string | null;\n  isLast: boolean;\n}\n\nfunction JsonObject({ value, path, keyName, isLast }: JsonObjectProps) {\n  const { expandedPaths, togglePath, displayDataTypes } =\n    useContext(JsonViewerContext);\n  const isExpanded = expandedPaths.has(path);\n  const entries = Object.entries(value);\n  const isEmpty = entries.length === 0;\n\n  if (isEmpty) {\n    return (\n      <JsonEntry keyName={keyName} isLast={isLast}>\n        <span className=\"text-muted-foreground\">{\"{}\"}</span>\n        {displayDataTypes && <JsonTypeLabel type=\"object\" />}\n      </JsonEntry>\n    );\n  }\n\n  return (\n    <Collapsible open={isExpanded} onOpenChange={() => togglePath(path)}>\n      <div>\n        <CollapsibleTrigger asChild>\n          <button\n            type=\"button\"\n            className=\"group flex items-baseline gap-1 text-left hover:bg-muted/50 rounded px-1 -mx-1\"\n          >\n            <ChevronRightIcon\n              className={cn(\n                \"size-3 shrink-0 text-muted-foreground transition-transform mt-1 self-start\",\n                isExpanded && \"rotate-90\",\n              )}\n            />\n            {keyName !== null && (\n              <>\n                <span className=\"text-foreground\">&quot;{keyName}&quot;</span>\n                <span className=\"text-muted-foreground\">:</span>\n              </>\n            )}\n            <span className=\"text-muted-foreground\">{\"{\"}</span>\n            {!isExpanded && (\n              <>\n                <span className=\"text-muted-foreground/60\">...</span>\n                <span className=\"text-muted-foreground\">{\"}\"}</span>\n                {displayDataTypes && <JsonTypeLabel type=\"object\" />}\n                <span className=\"text-muted-foreground/60 text-xs ml-1\">\n                  {entries.length} {entries.length === 1 ? \"key\" : \"keys\"}\n                </span>\n              </>\n            )}\n          </button>\n        </CollapsibleTrigger>\n        <CollapsibleContent>\n          <div className=\"ml-4 border-l border-border pl-3\">\n            {entries.map(([key, val], index) => (\n              <JsonValue\n                key={key}\n                value={val}\n                path={`${path}.${key}`}\n                keyName={key}\n                isLast={index === entries.length - 1}\n              />\n            ))}\n          </div>\n          <div className=\"flex items-baseline gap-1\">\n            <span className=\"size-3 shrink-0\" />\n            <span className=\"text-muted-foreground\">{\"}\"}</span>\n            {displayDataTypes && <JsonTypeLabel type=\"object\" />}\n            {!isLast && <span className=\"text-muted-foreground\">,</span>}\n          </div>\n        </CollapsibleContent>\n      </div>\n    </Collapsible>\n  );\n}\n\ninterface JsonArrayProps {\n  value: unknown[];\n  path: string;\n  keyName: string | null;\n  isLast: boolean;\n}\n\nfunction JsonArray({ value, path, keyName, isLast }: JsonArrayProps) {\n  const { expandedPaths, togglePath, displayDataTypes } =\n    useContext(JsonViewerContext);\n  const isExpanded = expandedPaths.has(path);\n  const isEmpty = value.length === 0;\n\n  if (isEmpty) {\n    return (\n      <JsonEntry keyName={keyName} isLast={isLast}>\n        <span className=\"text-muted-foreground\">[]</span>\n        {displayDataTypes && <JsonTypeLabel type=\"array\" />}\n      </JsonEntry>\n    );\n  }\n\n  return (\n    <Collapsible open={isExpanded} onOpenChange={() => togglePath(path)}>\n      <div>\n        <CollapsibleTrigger asChild>\n          <button\n            type=\"button\"\n            className=\"group flex items-baseline gap-1 text-left hover:bg-muted/50 rounded px-1 -mx-1\"\n          >\n            <ChevronRightIcon\n              className={cn(\n                \"size-3 shrink-0 text-muted-foreground transition-transform mt-1 self-start\",\n                isExpanded && \"rotate-90\",\n              )}\n            />\n            {keyName !== null && (\n              <>\n                <span className=\"text-foreground\">&quot;{keyName}&quot;</span>\n                <span className=\"text-muted-foreground\">:</span>\n              </>\n            )}\n            <span className=\"text-muted-foreground\">[</span>\n            {!isExpanded && (\n              <>\n                <span className=\"text-muted-foreground/60\">...</span>\n                <span className=\"text-muted-foreground\">]</span>\n                {displayDataTypes && <JsonTypeLabel type=\"array\" />}\n                <span className=\"text-muted-foreground/60 text-xs ml-1\">\n                  {value.length} {value.length === 1 ? \"item\" : \"items\"}\n                </span>\n              </>\n            )}\n          </button>\n        </CollapsibleTrigger>\n        <CollapsibleContent>\n          <div className=\"ml-4 border-l border-border pl-3\">\n            {value.map((item, index) => (\n              <JsonValue\n                key={index}\n                value={item}\n                path={`${path}[${index}]`}\n                keyName={null}\n                isLast={index === value.length - 1}\n              />\n            ))}\n          </div>\n          <div className=\"flex items-baseline gap-1\">\n            <span className=\"size-3 shrink-0\" />\n            <span className=\"text-muted-foreground\">]</span>\n            {displayDataTypes && <JsonTypeLabel type=\"array\" />}\n            {!isLast && <span className=\"text-muted-foreground\">,</span>}\n          </div>\n        </CollapsibleContent>\n      </div>\n    </Collapsible>\n  );\n}\n\ninterface JsonPrimitiveProps {\n  value: unknown;\n  displayDataTypes: boolean;\n}\n\nfunction JsonPrimitive({ value, displayDataTypes }: JsonPrimitiveProps) {\n  if (value === null) {\n    return (\n      <span className=\"inline-flex items-baseline gap-1\">\n        <span className=\"text-orange-600 dark:text-orange-400\">null</span>\n        {displayDataTypes && <JsonTypeLabel type=\"null\" />}\n      </span>\n    );\n  }\n\n  if (value === undefined) {\n    return (\n      <span className=\"inline-flex items-baseline gap-1\">\n        <span className=\"text-muted-foreground\">undefined</span>\n        {displayDataTypes && <JsonTypeLabel type=\"undefined\" />}\n      </span>\n    );\n  }\n\n  if (typeof value === \"string\") {\n    return (\n      <span className=\"inline-flex items-baseline gap-1\">\n        <span className=\"text-green-600 dark:text-green-400\">\n          &quot;{value}&quot;\n        </span>\n        {displayDataTypes && <JsonTypeLabel type=\"string\" />}\n      </span>\n    );\n  }\n\n  if (typeof value === \"number\") {\n    return (\n      <span className=\"inline-flex items-baseline gap-1\">\n        <span className=\"text-blue-600 dark:text-blue-400\">{value}</span>\n        {displayDataTypes && <JsonTypeLabel type=\"number\" />}\n      </span>\n    );\n  }\n\n  if (typeof value === \"boolean\") {\n    return (\n      <span className=\"inline-flex items-baseline gap-1\">\n        <span className=\"text-purple-600 dark:text-purple-400\">\n          {value ? \"true\" : \"false\"}\n        </span>\n        {displayDataTypes && <JsonTypeLabel type=\"boolean\" />}\n      </span>\n    );\n  }\n\n  // Fallback for other types\n  return <span className=\"text-muted-foreground\">{String(value)}</span>;\n}\n\nfunction JsonTypeLabel({ type }: { type: string }) {\n  return <span className=\"text-xs text-muted-foreground/60 ml-1\">{type}</span>;\n}\n",
      "type": "registry:component",
      "target": "components/outputs/json-output.tsx"
    }
  ],
  "type": "registry:component"
}