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