1
- import { useState , memo } from "react" ;
1
+ import { useState , memo , useMemo , useCallback , useEffect } from "react" ;
2
2
import { JsonValue } from "./DynamicJsonForm" ;
3
3
import clsx from "clsx" ;
4
+ import { Copy , CheckCheck } from "lucide-react" ;
5
+ import { Button } from "@/components/ui/button" ;
6
+ import { useToast } from "@/hooks/use-toast" ;
4
7
5
8
interface JsonViewProps {
6
9
data : unknown ;
7
10
name ?: string ;
8
11
initialExpandDepth ?: number ;
12
+ className ?: string ;
13
+ withCopyButton ?: boolean ;
9
14
}
10
15
11
16
function tryParseJson ( str : string ) : { success : boolean ; data : JsonValue } {
@@ -24,22 +29,79 @@ function tryParseJson(str: string): { success: boolean; data: JsonValue } {
24
29
}
25
30
26
31
const JsonView = memo (
27
- ( { data, name, initialExpandDepth = 3 } : JsonViewProps ) => {
28
- const normalizedData =
29
- typeof data === "string"
32
+ ( {
33
+ data,
34
+ name,
35
+ initialExpandDepth = 3 ,
36
+ className,
37
+ withCopyButton = true ,
38
+ } : JsonViewProps ) => {
39
+ const { toast } = useToast ( ) ;
40
+ const [ copied , setCopied ] = useState ( false ) ;
41
+
42
+ useEffect ( ( ) => {
43
+ let timeoutId : NodeJS . Timeout ;
44
+ if ( copied ) {
45
+ timeoutId = setTimeout ( ( ) => {
46
+ setCopied ( false ) ;
47
+ } , 500 ) ;
48
+ }
49
+ return ( ) => {
50
+ if ( timeoutId ) {
51
+ clearTimeout ( timeoutId ) ;
52
+ }
53
+ } ;
54
+ } , [ copied ] ) ;
55
+
56
+ const normalizedData = useMemo ( ( ) => {
57
+ return typeof data === "string"
30
58
? tryParseJson ( data ) . success
31
59
? tryParseJson ( data ) . data
32
60
: data
33
61
: data ;
62
+ } , [ data ] ) ;
63
+
64
+ const handleCopy = useCallback ( ( ) => {
65
+ try {
66
+ navigator . clipboard . writeText (
67
+ typeof normalizedData === "string"
68
+ ? normalizedData
69
+ : JSON . stringify ( normalizedData , null , 2 ) ,
70
+ ) ;
71
+ setCopied ( true ) ;
72
+ } catch ( error ) {
73
+ toast ( {
74
+ title : "Error" ,
75
+ description : `There was an error coping result into the clipboard: ${ error instanceof Error ? error . message : String ( error ) } ` ,
76
+ variant : "destructive" ,
77
+ } ) ;
78
+ }
79
+ } , [ toast , normalizedData ] ) ;
34
80
35
81
return (
36
- < div className = "font-mono text-sm transition-all duration-300 " >
37
- < JsonNode
38
- data = { normalizedData as JsonValue }
39
- name = { name }
40
- depth = { 0 }
41
- initialExpandDepth = { initialExpandDepth }
42
- />
82
+ < div className = { clsx ( "p-4 border rounded relative" , className ) } >
83
+ { withCopyButton && (
84
+ < Button
85
+ size = "icon"
86
+ variant = "ghost"
87
+ className = "absolute top-2 right-2"
88
+ onClick = { handleCopy }
89
+ >
90
+ { copied ? (
91
+ < CheckCheck className = "size-4 dark:text-green-700 text-green-600" />
92
+ ) : (
93
+ < Copy className = "size-4 text-foreground" />
94
+ ) }
95
+ </ Button >
96
+ ) }
97
+ < div className = "font-mono text-sm transition-all duration-300" >
98
+ < JsonNode
99
+ data = { normalizedData as JsonValue }
100
+ name = { name }
101
+ depth = { 0 }
102
+ initialExpandDepth = { initialExpandDepth }
103
+ />
104
+ </ div >
43
105
</ div >
44
106
) ;
45
107
} ,
0 commit comments