"use client"; import { useMemo, useState } from "react"; import { cn, toPrettyJson } from "@/lib/utils"; type DataViewerProps = { title: string; value: unknown; defaultExpandedDepth?: number; maxHeight?: number; }; const highlightJson = (json: string | undefined): React.ReactNode[] => { if (!json) return []; const parts: React.ReactNode[] = []; let i = 0; const regex = /("(?:\\.|[^"\\])*")\s*:|("(?:\\.|[^"\\])*")|(-?\d+(?:\.\d+)?(?:[eE][+-]?\d+)?)|(\btrue\b|\bfalse\b)|(\bnull\b)/g; let lastIndex = 0; let match: RegExpExecArray | null = regex.exec(json); while (match !== null) { if (match.index > lastIndex) { parts.push( {json.slice(lastIndex, match.index)} , ); } if (match[1]) { // Key parts.push( {match[1]} , ); parts.push( : , ); } else if (match[2]) { // String value parts.push( {match[2]} , ); } else if (match[3]) { // Number parts.push( {match[3]} , ); } else if (match[4]) { // Boolean parts.push( {match[4]} , ); } else if (match[5]) { // Null parts.push( {match[5]} , ); } lastIndex = regex.lastIndex; match = regex.exec(json); } if (lastIndex < json.length) { parts.push( {json.slice(lastIndex)} , ); } return parts; }; const renderPrimitive = ({ value }: { value: unknown }) => { if (value === null) return null; if (value === undefined) return undefined; if (typeof value === "string") return "{value}"; if (typeof value === "number" || typeof value === "boolean") { return ( {String(value)} ); } return ( {String(value)} ); }; const JsonNode = ({ name, value, depth, defaultExpandedDepth, }: { name: string; value: unknown; depth: number; defaultExpandedDepth: number; }) => { if (value === null || typeof value !== "object") { return (
{name}: {renderPrimitive({ value })}
); } const entries = Array.isArray(value) ? value.map((item, index) => [String(index), item] as const) : Object.entries(value as Record); return (
{name} {Array.isArray(value) ? `[${entries.length}]` : `{${entries.length}}`}
{entries.length === 0 ? (
empty
) : ( entries.map(([key, nextValue]) => ( )) )}
); }; export const DataViewer = ({ title, value, defaultExpandedDepth = 2, maxHeight = 420, }: DataViewerProps) => { const [viewMode, setViewMode] = useState<"tree" | "raw">("raw"); const [copied, setCopied] = useState(false); const prettyJson = useMemo(() => toPrettyJson({ value }), [value]); const onCopy = async () => { await navigator.clipboard.writeText(prettyJson); setCopied(true); setTimeout(() => setCopied(false), 1500); }; return (

{title}

{viewMode === "tree" ? ( ) : (
{highlightJson(prettyJson)}
)}
); };