'use client' import { useState } from 'react' import { cn } from '../../lib/cn' import { ChevronRightIcon } from '../internal/icons' import { Surface } from '../Surface' export interface JsonViewerProps { /** Any JSON-serialisable value. */ data: unknown /** Accessible name for the viewer. */ label: string /** How many levels start open. */ defaultExpandDepth?: number /** Name shown for the root. Omit to show the root's contents directly. */ rootName?: string /** Merged last, so it wins. */ className?: string } /** The code palette, so JSON here and in a CodeBlock read the same. */ const COLOR = { string: 'var(--syntax-string)', number: 'var(--syntax-number)', literal: 'var(--syntax-keyword)', } function Primitive({ value }: { value: unknown }) { if (typeof value === 'string') return "{value}" if (typeof value === 'number') return {String(value)} if (typeof value === 'boolean' || value === null) { return {String(value)} } return {String(value)} } function Key({ name, quoted }: { name: string; quoted: boolean }) { return ( <> {quoted ? `"${name}"` : name} : ) } function Branch({ name, quoted, value, depth, expandDepth, }: { name?: string quoted: boolean value: unknown depth: number expandDepth: number }) { const nested = value !== null && typeof value === 'object' const [open, setOpen] = useState(depth < expandDepth) if (!nested) { return (
  • {name !== undefined && }
  • ) } const array = Array.isArray(value) const entries: [string, unknown][] = array ? (value as unknown[]).map((item, index) => [String(index), item]) : Object.entries(value as Record) const summary = array ? `[${entries.length}]` : `{${entries.length}}` return (
  • {open && entries.length > 0 && ( )}
  • ) } /** * A payload, as a collapsible tree — an API response, a webhook body, a log line. * * Each object and array is a disclosure button with aria-expanded, so the tree * is navigable by Tab and announced as open or closed without the full ARIA * tree pattern a reader would have to learn. Values take the code palette, so * JSON here and JSON in a CodeBlock read the same, and nothing is truncated: * collapsing hides a branch, it never cuts a value short. */ export function JsonViewer({ data, label, defaultExpandDepth = 1, rootName, className, }: JsonViewerProps) { const top = rootName === undefined && data !== null && typeof data === 'object' ? Array.isArray(data) ? data.map((item, index) => [String(index), item] as [string, unknown]) : Object.entries(data as Record) : null return (
      {top ? top.map(([key, item]) => ( )) : ( )}
    ) }