1+ import { useState , memo } from "react"
2+ import { Button } from "./ui/button"
3+ import { ChevronRight , ChevronDown } from "lucide-react"
4+
5+ interface CollapsibleJSONProps {
6+ data : any
7+ level ?: number
8+ isExpanded ?: boolean
9+ maxAutoExpandDepth ?: number
10+ maxAutoExpandArraySize ?: number
11+ maxAutoExpandObjectSize ?: number
12+ }
13+
14+ // Memoize the JSON node to prevent unnecessary re-renders
15+ export const CollapsibleJSON = memo ( function CollapsibleJSON ( {
16+ data,
17+ level = 0 ,
18+ isExpanded = true ,
19+ maxAutoExpandDepth = 2 ,
20+ maxAutoExpandArraySize = 10 ,
21+ maxAutoExpandObjectSize = 5
22+ } : CollapsibleJSONProps ) {
23+ const shouldAutoExpand = ( ) => {
24+ if ( level >= maxAutoExpandDepth ) return false
25+
26+ const isObject = typeof data === 'object' && data !== null
27+ if ( ! isObject ) return true
28+
29+ const entries = Object . entries ( data )
30+ if ( Array . isArray ( data ) && entries . length > maxAutoExpandArraySize ) return false
31+ if ( ! Array . isArray ( data ) && entries . length > maxAutoExpandObjectSize ) return false
32+
33+ return true
34+ }
35+
36+ const [ expanded , setExpanded ] = useState ( isExpanded && shouldAutoExpand ( ) )
37+ const isObject = typeof data === 'object' && data !== null
38+ const isArray = Array . isArray ( data )
39+
40+ if ( ! isObject ) {
41+ return (
42+ < span className = { typeof data === 'string' ? 'text-green-400' : 'text-blue-400' } >
43+ { JSON . stringify ( data ) }
44+ </ span >
45+ )
46+ }
47+
48+ const entries = Object . entries ( data )
49+ const isEmpty = entries . length === 0
50+
51+ if ( isEmpty ) {
52+ return < span > { isArray ? '[]' : '{}' } </ span >
53+ }
54+
55+ const toggleExpand = ( e : React . MouseEvent ) => {
56+ e . stopPropagation ( )
57+ setExpanded ( ! expanded )
58+ }
59+
60+ return (
61+ < div className = "relative" style = { { paddingLeft : level > 0 ? '0.5rem' : 0 } } >
62+ < div className = "flex items-center gap-0.5 cursor-pointer hover:bg-muted/50 rounded px-0.5" onClick = { toggleExpand } >
63+ < Button variant = "ghost" size = "icon" className = "h-4 w-4 p-0" >
64+ { expanded ? < ChevronDown className = "h-3 w-3" /> : < ChevronRight className = "h-3 w-3" /> }
65+ </ Button >
66+ < span > { isArray ? '[' : '{' } </ span >
67+ { ! expanded && (
68+ < span className = "text-muted-foreground text-xs ml-1" >
69+ { isArray ? `${ entries . length } items` : `${ entries . length } properties` }
70+ </ span >
71+ ) }
72+ </ div >
73+ { expanded && (
74+ < div className = "pl-3 border-l border-muted-foreground/20" >
75+ { entries . map ( ( [ key , value ] ) => (
76+ < div key = { key } className = "flex items-start py-0.5" >
77+ < div className = "flex-1 flex" >
78+ < span className = "text-yellow-400 whitespace-nowrap" >
79+ { ! isArray && `"${ key } "` } { isArray && key }
80+ </ span >
81+ < span className = "mx-1" > :</ span >
82+ < div className = "flex-1 min-w-0" >
83+ < CollapsibleJSON
84+ data = { value }
85+ level = { level + 1 }
86+ maxAutoExpandDepth = { maxAutoExpandDepth }
87+ maxAutoExpandArraySize = { maxAutoExpandArraySize }
88+ maxAutoExpandObjectSize = { maxAutoExpandObjectSize }
89+ />
90+ </ div >
91+ </ div >
92+ </ div >
93+ ) ) }
94+ </ div >
95+ ) }
96+ < div className = { expanded ? "pl-3" : "" } >
97+ < span > { isArray ? ']' : '}' } </ span >
98+ </ div >
99+ </ div >
100+ )
101+ } )
0 commit comments