@rozenite/network-activity-plugin 1.0.0-alpha.8 → 1.0.0-alpha.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (87) hide show
  1. package/dist/App.html +2 -2
  2. package/dist/assets/{App-lNMijPJ4.js → App-CA1Fbh0I.js} +11995 -10804
  3. package/dist/assets/{App-R2ZMH9wJ.css → App-DoHQsY5s.css} +46 -0
  4. package/dist/event-source.cjs +22 -0
  5. package/dist/event-source.js +23 -0
  6. package/dist/rozenite.json +1 -1
  7. package/dist/src/react-native/{network-inspector.d.ts → http/network-inspector.d.ts} +1 -1
  8. package/dist/src/react-native/sse/event-source.d.ts +2 -0
  9. package/dist/src/react-native/sse/sse-inspector.d.ts +9 -0
  10. package/dist/src/react-native/sse/sse-interceptor.d.ts +36 -0
  11. package/dist/src/react-native/sse/types.d.ts +6 -0
  12. package/dist/src/react-native/utils.d.ts +6 -0
  13. package/dist/src/react-native/websocket/websocket-inspector.d.ts +9 -0
  14. package/dist/src/react-native/websocket/websocket-interceptor.d.ts +74 -0
  15. package/dist/src/shared/client.d.ts +5 -2
  16. package/dist/src/shared/sse-events.d.ts +35 -0
  17. package/dist/src/shared/websocket-events.d.ts +60 -0
  18. package/dist/src/ui/components/Badge.d.ts +1 -1
  19. package/dist/src/ui/components/Button.d.ts +1 -1
  20. package/dist/src/ui/components/JsonTreeCopyableItem.d.ts +7 -0
  21. package/dist/src/ui/components/RequestList.d.ts +6 -26
  22. package/dist/src/ui/components/SidePanel.d.ts +1 -0
  23. package/dist/src/ui/components/Toolbar.d.ts +1 -0
  24. package/dist/src/ui/hooks/useCopyToClipboard.d.ts +4 -0
  25. package/dist/src/ui/state/derived.d.ts +5 -0
  26. package/dist/src/ui/state/hooks.d.ts +17 -0
  27. package/dist/src/ui/state/model.d.ts +98 -0
  28. package/dist/src/ui/state/store.d.ts +24 -0
  29. package/dist/src/ui/tabs/CookiesTab.d.ts +3 -6
  30. package/dist/src/ui/tabs/HeadersTab.d.ts +3 -15
  31. package/dist/src/ui/tabs/MessagesTab.d.ts +5 -0
  32. package/dist/src/ui/tabs/RequestTab.d.ts +2 -7
  33. package/dist/src/ui/tabs/ResponseTab.d.ts +2 -8
  34. package/dist/src/ui/tabs/SSEMessagesTab.d.ts +5 -0
  35. package/dist/src/ui/tabs/TimingTab.d.ts +3 -5
  36. package/dist/src/ui/types.d.ts +4 -1
  37. package/dist/src/ui/utils/assert.d.ts +1 -0
  38. package/dist/src/ui/utils/copyToClipboard.d.ts +1 -0
  39. package/dist/src/ui/utils/getId.d.ts +1 -0
  40. package/dist/src/ui/utils/getStatusColor.d.ts +1 -0
  41. package/dist/useNetworkActivityDevTools.cjs +423 -34
  42. package/dist/useNetworkActivityDevTools.js +421 -34
  43. package/package.json +19 -8
  44. package/src/react-native/{network-inspector.ts → http/network-inspector.ts} +13 -34
  45. package/src/react-native/{xml-request.d.ts → http/xml-request.d.ts} +1 -0
  46. package/src/react-native/sse/event-source.ts +25 -0
  47. package/src/react-native/sse/sse-inspector.ts +117 -0
  48. package/src/react-native/sse/sse-interceptor.ts +162 -0
  49. package/src/react-native/sse/types.ts +9 -0
  50. package/src/react-native/useNetworkActivityDevTools.ts +75 -1
  51. package/src/react-native/utils.ts +43 -0
  52. package/src/react-native/websocket/websocket-inspector.ts +180 -0
  53. package/src/react-native/websocket/websocket-interceptor.d.ts +4 -0
  54. package/src/react-native/websocket/websocket-interceptor.ts +166 -0
  55. package/src/shared/client.ts +6 -2
  56. package/src/shared/sse-events.ts +44 -0
  57. package/src/shared/websocket-events.ts +79 -0
  58. package/src/ui/components/JsonTree.tsx +13 -0
  59. package/src/ui/components/JsonTreeCopyableItem.tsx +33 -0
  60. package/src/ui/components/RequestList.tsx +42 -124
  61. package/src/ui/components/SidePanel.tsx +323 -0
  62. package/src/ui/components/Tabs.tsx +1 -1
  63. package/src/ui/components/Toolbar.tsx +45 -0
  64. package/src/ui/hooks/useCopyToClipboard.ts +28 -0
  65. package/src/ui/state/derived.ts +112 -0
  66. package/src/ui/state/hooks.ts +44 -0
  67. package/src/ui/state/model.ts +129 -0
  68. package/src/ui/state/store.ts +559 -0
  69. package/src/ui/tabs/CookiesTab.tsx +162 -176
  70. package/src/ui/tabs/HeadersTab.tsx +23 -30
  71. package/src/ui/tabs/MessagesTab.tsx +276 -0
  72. package/src/ui/tabs/RequestTab.tsx +8 -13
  73. package/src/ui/tabs/ResponseTab.tsx +6 -10
  74. package/src/ui/tabs/SSEMessagesTab.tsx +213 -0
  75. package/src/ui/tabs/TimingTab.tsx +30 -43
  76. package/src/ui/types.ts +4 -1
  77. package/src/ui/utils/assert.ts +5 -0
  78. package/src/ui/utils/copyToClipboard.ts +3 -0
  79. package/src/ui/utils/getId.ts +10 -0
  80. package/src/ui/utils/getStatusColor.ts +15 -0
  81. package/src/ui/views/InspectorView.tsx +24 -320
  82. package/tailwind.config.ts +3 -0
  83. package/vite.config.ts +12 -0
  84. /package/dist/src/react-native/{network-requests-registry.d.ts → http/network-requests-registry.d.ts} +0 -0
  85. /package/dist/src/react-native/{xhr-interceptor.d.ts → http/xhr-interceptor.d.ts} +0 -0
  86. /package/src/react-native/{network-requests-registry.ts → http/network-requests-registry.ts} +0 -0
  87. /package/src/react-native/{xhr-interceptor.ts → http/xhr-interceptor.ts} +0 -0
@@ -0,0 +1,180 @@
1
+ import { createNanoEvents } from 'nanoevents';
2
+ import { getWebSocketInterceptor } from './websocket-interceptor';
3
+ import {
4
+ WebSocketEvent,
5
+ WebSocketEventMap,
6
+ } from '../../shared/websocket-events';
7
+
8
+ type NanoEventsMap = {
9
+ [K in keyof WebSocketEventMap]: (data: WebSocketEventMap[K]) => void;
10
+ };
11
+
12
+ export type WebSocketInspector = {
13
+ enable: () => void;
14
+ disable: () => void;
15
+ isEnabled: () => boolean;
16
+ dispose: () => void;
17
+ on: <TEventType extends keyof WebSocketEventMap>(
18
+ event: TEventType,
19
+ callback: (data: WebSocketEventMap[TEventType]) => void
20
+ ) => () => void;
21
+ };
22
+
23
+ export const getWebSocketInspector = (): WebSocketInspector => {
24
+ const eventEmitter = createNanoEvents<NanoEventsMap>();
25
+ const socketUrlMap = new Map<number, string>();
26
+ const webSocketInterceptor = getWebSocketInterceptor();
27
+
28
+ return {
29
+ enable: () => {
30
+ webSocketInterceptor.setConnectCallback(
31
+ (
32
+ url: string,
33
+ protocols: string[] | null,
34
+ options: string[],
35
+ socketId: number
36
+ ) => {
37
+ socketUrlMap.set(socketId, url);
38
+ const event: WebSocketEvent = {
39
+ type: 'websocket-connect',
40
+ url,
41
+ socketId,
42
+ timestamp: Date.now(),
43
+ protocols,
44
+ options,
45
+ };
46
+ eventEmitter.emit('websocket-connect', event);
47
+ }
48
+ );
49
+
50
+ webSocketInterceptor.setCloseCallback(
51
+ (code: number | null, reason: string | null, socketId: number) => {
52
+ const url = socketUrlMap.get(socketId);
53
+
54
+ if (!url) {
55
+ return;
56
+ }
57
+
58
+ const event: WebSocketEvent = {
59
+ type: 'websocket-close',
60
+ url,
61
+ socketId,
62
+ timestamp: Date.now(),
63
+ code: code || 0,
64
+ reason: reason || undefined,
65
+ };
66
+ eventEmitter.emit('websocket-close', event);
67
+ socketUrlMap.delete(socketId);
68
+ }
69
+ );
70
+
71
+ webSocketInterceptor.setOnMessageCallback(
72
+ (data: string, socketId: number) => {
73
+ const url = socketUrlMap.get(socketId);
74
+
75
+ if (!url) {
76
+ return;
77
+ }
78
+
79
+ const event: WebSocketEvent = {
80
+ type: 'websocket-message-received',
81
+ url,
82
+ socketId,
83
+ timestamp: Date.now(),
84
+ data,
85
+ messageType: typeof data === 'string' ? 'text' : 'binary',
86
+ };
87
+ eventEmitter.emit('websocket-message-received', event);
88
+ }
89
+ );
90
+
91
+ webSocketInterceptor.setOnErrorCallback(
92
+ (error: string, socketId: number) => {
93
+ const url = socketUrlMap.get(socketId);
94
+
95
+ if (!url) {
96
+ return;
97
+ }
98
+
99
+ const event: WebSocketEvent = {
100
+ type: 'websocket-error',
101
+ url,
102
+ socketId,
103
+ timestamp: Date.now(),
104
+ error,
105
+ };
106
+ eventEmitter.emit('websocket-error', event);
107
+ }
108
+ );
109
+
110
+ webSocketInterceptor.setSendCallback((data: string, socketId: number) => {
111
+ const url = socketUrlMap.get(socketId);
112
+
113
+ if (!url) {
114
+ return;
115
+ }
116
+
117
+ const event: WebSocketEvent = {
118
+ type: 'websocket-message-sent',
119
+ url,
120
+ socketId,
121
+ timestamp: Date.now(),
122
+ data,
123
+ messageType: typeof data === 'string' ? 'text' : 'binary',
124
+ };
125
+ eventEmitter.emit('websocket-message-sent', event);
126
+ });
127
+
128
+ webSocketInterceptor.setOnOpenCallback((socketId: number) => {
129
+ const url = socketUrlMap.get(socketId);
130
+
131
+ if (!url) {
132
+ return;
133
+ }
134
+
135
+ const event: WebSocketEvent = {
136
+ type: 'websocket-open',
137
+ url,
138
+ socketId,
139
+ timestamp: Date.now(),
140
+ };
141
+ eventEmitter.emit('websocket-open', event);
142
+ });
143
+
144
+ webSocketInterceptor.setOnCloseCallback(
145
+ (error: { code: number; reason?: string }, socketId: number) => {
146
+ const url = socketUrlMap.get(socketId);
147
+
148
+ if (!url) {
149
+ return;
150
+ }
151
+
152
+ const event: WebSocketEvent = {
153
+ type: 'websocket-close',
154
+ url,
155
+ socketId,
156
+ timestamp: Date.now(),
157
+ code: error.code,
158
+ reason: error.reason,
159
+ };
160
+ eventEmitter.emit('websocket-close', event);
161
+ socketUrlMap.delete(socketId);
162
+ }
163
+ );
164
+
165
+ webSocketInterceptor.enableInterception();
166
+ },
167
+ disable: () => {
168
+ webSocketInterceptor.disableInterception();
169
+ },
170
+ isEnabled: () => webSocketInterceptor.isInterceptorEnabled(),
171
+ dispose: () => {
172
+ eventEmitter.events = {};
173
+ socketUrlMap.clear();
174
+ },
175
+ on: <TEventType extends keyof WebSocketEventMap>(
176
+ event: TEventType,
177
+ callback: (data: WebSocketEventMap[TEventType]) => void
178
+ ) => eventEmitter.on(event, callback as NanoEventsMap[TEventType]),
179
+ };
180
+ };
@@ -0,0 +1,4 @@
1
+ declare module 'react-native/Libraries/WebSocket/WebSocketInterceptor' {
2
+ const WebSocketInterceptor: unknown;
3
+ export default WebSocketInterceptor;
4
+ }
@@ -0,0 +1,166 @@
1
+ import { Platform } from 'react-native';
2
+ import WebSocketInterceptor from 'react-native/Libraries/WebSocket/WebSocketInterceptor';
3
+
4
+ export interface WebSocketInterceptor {
5
+ /**
6
+ * Invoked when RCTWebSocketModule.close(...) is called.
7
+ */
8
+ setCloseCallback(
9
+ callback: (
10
+ code: number | null,
11
+ reason: string | null,
12
+ socketId: number
13
+ ) => void
14
+ ): void;
15
+
16
+ /**
17
+ * Invoked when RCTWebSocketModule.send(...) or sendBinary(...) is called.
18
+ */
19
+ setSendCallback(callback: (data: string, socketId: number) => void): void;
20
+
21
+ /**
22
+ * Invoked when RCTWebSocketModule.connect(...) is called.
23
+ */
24
+ setConnectCallback(
25
+ callback: (
26
+ url: string,
27
+ protocols: string[] | null,
28
+ options: string[],
29
+ socketId: number
30
+ ) => void
31
+ ): void;
32
+
33
+ /**
34
+ * Invoked when event "websocketOpen" happens.
35
+ */
36
+ setOnOpenCallback(callback: (socketId: number) => void): void;
37
+
38
+ /**
39
+ * Invoked when event "websocketMessage" happens.
40
+ */
41
+ setOnMessageCallback(
42
+ callback: (data: string, socketId: number) => void
43
+ ): void;
44
+
45
+ /**
46
+ * Invoked when event "websocketFailed" happens.
47
+ */
48
+ setOnErrorCallback(callback: (error: string, socketId: number) => void): void;
49
+
50
+ /**
51
+ * Invoked when event "websocketClosed" happens.
52
+ */
53
+ setOnCloseCallback(
54
+ callback: (
55
+ error: { code: number; reason?: string },
56
+ socketId: number
57
+ ) => void
58
+ ): void;
59
+
60
+ isInterceptorEnabled(): boolean;
61
+ enableInterception(): void;
62
+ disableInterception(): void;
63
+ }
64
+
65
+ export interface WebSocketInterceptorPreRN079 {
66
+ /**
67
+ * Invoked when RCTWebSocketModule.close(...) is called.
68
+ */
69
+ setCloseCallback(
70
+ callback: (
71
+ code: number | null,
72
+ reason: string | null,
73
+ socketId: number
74
+ ) => void
75
+ ): void;
76
+
77
+ /**
78
+ * Invoked when RCTWebSocketModule.send(...) or sendBinary(...) is called.
79
+ */
80
+ setSendCallback(callback: (data: string, socketId: number) => void): void;
81
+
82
+ /**
83
+ * Invoked when RCTWebSocketModule.connect(...) is called.
84
+ */
85
+ setConnectCallback(
86
+ callback: (
87
+ url: string,
88
+ protocols: string[] | null,
89
+ options: string[],
90
+ socketId: number
91
+ ) => void
92
+ ): void;
93
+
94
+ /**
95
+ * Invoked when event "websocketOpen" happens.
96
+ */
97
+ setOnOpenCallback(callback: (socketId: number) => void): void;
98
+
99
+ /**
100
+ * Invoked when event "websocketMessage" happens.
101
+ */
102
+ setOnMessageCallback(
103
+ callback: (socketId: number, data: string) => void
104
+ ): void;
105
+
106
+ /**
107
+ * Invoked when event "websocketFailed" happens.
108
+ */
109
+ setOnErrorCallback(callback: (socketId: number, error: string) => void): void;
110
+
111
+ /**
112
+ * Invoked when event "websocketClosed" happens.
113
+ */
114
+ setOnCloseCallback(
115
+ callback: (
116
+ socketId: number,
117
+ error: { code: number; reason?: string }
118
+ ) => void
119
+ ): void;
120
+
121
+ isInterceptorEnabled(): boolean;
122
+ enableInterception(): void;
123
+ disableInterception(): void;
124
+ }
125
+
126
+ export const getWebSocketInterceptor = (): WebSocketInterceptor => {
127
+ /**
128
+ * Note: RN 0.79 changed the order of the arguments.
129
+ * @see https://github.com/facebook/react-native/commit/d2adb976abebcb0f38750903d98fbb5a3f50924b
130
+ */
131
+
132
+ if (Platform.constants.reactNativeVersion.minor >= 79) {
133
+ return WebSocketInterceptor as WebSocketInterceptor;
134
+ } else {
135
+ const WebSocketInterceptorPreRN079 =
136
+ WebSocketInterceptor as WebSocketInterceptorPreRN079;
137
+
138
+ return {
139
+ ...WebSocketInterceptorPreRN079,
140
+ setOnMessageCallback: (
141
+ callback: (data: string, socketId: number) => void
142
+ ) => {
143
+ WebSocketInterceptorPreRN079.setOnMessageCallback((socketId, data) => {
144
+ callback(data, socketId);
145
+ });
146
+ },
147
+ setOnCloseCallback: (
148
+ callback: (
149
+ error: { code: number; reason?: string },
150
+ socketId: number
151
+ ) => void
152
+ ) => {
153
+ WebSocketInterceptorPreRN079.setOnCloseCallback((error, socketId) => {
154
+ callback(socketId, error);
155
+ });
156
+ },
157
+ setOnErrorCallback: (
158
+ callback: (error: string, socketId: number) => void
159
+ ) => {
160
+ WebSocketInterceptorPreRN079.setOnErrorCallback((error, socketId) => {
161
+ callback(socketId, error);
162
+ });
163
+ },
164
+ } as WebSocketInterceptor;
165
+ }
166
+ };
@@ -1,13 +1,16 @@
1
1
  import { RozeniteDevToolsClient } from '@rozenite/plugin-bridge';
2
+ import { WebSocketEventMap } from './websocket-events';
3
+ import { SSEEventMap } from './sse-events';
2
4
 
3
5
  export type HttpHeaders = Record<string, string>;
6
+ export type HttpMethod = 'GET' | 'POST' | 'PUT' | 'DELETE' | 'PATCH' | 'HEAD';
4
7
 
5
8
  export type RequestId = string;
6
9
  export type Timestamp = number;
7
10
 
8
11
  export type Request = {
9
12
  url: string;
10
- method: string;
13
+ method: HttpMethod;
11
14
  headers: HttpHeaders;
12
15
  postData?: string;
13
16
  };
@@ -76,7 +79,8 @@ export type NetworkActivityEventMap = {
76
79
  requestId: RequestId;
77
80
  body: string | null;
78
81
  };
79
- };
82
+ } & WebSocketEventMap &
83
+ SSEEventMap;
80
84
 
81
85
  export type NetworkActivityDevToolsClient =
82
86
  RozeniteDevToolsClient<NetworkActivityEventMap>;
@@ -0,0 +1,44 @@
1
+ import type { Response } from './client';
2
+
3
+ export type SSEConnectionStatus = 'connecting' | 'open' | 'closed';
4
+ export type SSERequestId = string;
5
+
6
+ export type SSEOpenEvent = {
7
+ type: 'sse-open';
8
+ requestId: SSERequestId;
9
+ timestamp: number;
10
+ response: Response;
11
+ };
12
+
13
+ export type SSEMessageEvent = {
14
+ type: 'sse-message';
15
+ requestId: SSERequestId;
16
+ timestamp: number;
17
+ data: string;
18
+ };
19
+
20
+ export type SSEErrorEvent = {
21
+ type: 'sse-error';
22
+ requestId: SSERequestId;
23
+ timestamp: number;
24
+ error: {
25
+ type: 'error' | 'timeout' | 'exception';
26
+ message: string;
27
+ };
28
+ };
29
+
30
+ export type SSECloseEvent = {
31
+ type: 'sse-close';
32
+ requestId: SSERequestId;
33
+ timestamp: number;
34
+ };
35
+
36
+ export type SSEEvent =
37
+ | SSEOpenEvent
38
+ | SSEMessageEvent
39
+ | SSEErrorEvent
40
+ | SSECloseEvent;
41
+
42
+ export type SSEEventMap = {
43
+ [K in SSEEvent['type']]: Extract<SSEEvent, { type: K }>;
44
+ };
@@ -0,0 +1,79 @@
1
+ export type WebSocketMessageType = 'text' | 'binary';
2
+
3
+ export type WebSocketConnectionStatus =
4
+ | 'connecting'
5
+ | 'open'
6
+ | 'closing'
7
+ | 'closed';
8
+
9
+ export type WebSocketConnectEvent = {
10
+ type: 'websocket-connect';
11
+ url: string;
12
+ socketId: number;
13
+ timestamp: number;
14
+ protocols: string[] | null;
15
+ options: string[];
16
+ };
17
+
18
+ export type WebSocketOpenEvent = {
19
+ type: 'websocket-open';
20
+ url: string;
21
+ socketId: number;
22
+ timestamp: number;
23
+ };
24
+
25
+ export type WebSocketCloseEvent = {
26
+ type: 'websocket-close';
27
+ url: string;
28
+ socketId: number;
29
+ timestamp: number;
30
+ code: number;
31
+ reason?: string;
32
+ };
33
+
34
+ export type WebSocketMessageSentEvent = {
35
+ type: 'websocket-message-sent';
36
+ url: string;
37
+ socketId: number;
38
+ timestamp: number;
39
+ data: string;
40
+ messageType: WebSocketMessageType;
41
+ };
42
+
43
+ export type WebSocketMessageReceivedEvent = {
44
+ type: 'websocket-message-received';
45
+ url: string;
46
+ socketId: number;
47
+ timestamp: number;
48
+ data: string;
49
+ messageType: WebSocketMessageType;
50
+ };
51
+
52
+ export type WebSocketErrorEvent = {
53
+ type: 'websocket-error';
54
+ url: string;
55
+ socketId: number;
56
+ timestamp: number;
57
+ error: string;
58
+ };
59
+
60
+ export type WebSocketConnectionStatusChangedEvent = {
61
+ type: 'websocket-connection-status-changed';
62
+ url: string;
63
+ socketId: number;
64
+ timestamp: number;
65
+ status: WebSocketConnectionStatus;
66
+ };
67
+
68
+ export type WebSocketEvent =
69
+ | WebSocketConnectEvent
70
+ | WebSocketOpenEvent
71
+ | WebSocketCloseEvent
72
+ | WebSocketMessageSentEvent
73
+ | WebSocketMessageReceivedEvent
74
+ | WebSocketErrorEvent
75
+ | WebSocketConnectionStatusChangedEvent;
76
+
77
+ export type WebSocketEventMap = {
78
+ [K in WebSocketEvent['type']]: Extract<WebSocketEvent, { type: K }>;
79
+ };
@@ -1,4 +1,5 @@
1
1
  import { JSONTree } from 'react-json-tree';
2
+ import { JsonTreeCopyableItem } from './JsonTreeCopyableItem';
2
3
 
3
4
  export type JsonTreeProps = {
4
5
  data: unknown;
@@ -32,6 +33,18 @@ export const JsonTree = ({
32
33
  }}
33
34
  invertTheme={false}
34
35
  shouldExpandNodeInitially={shouldExpandNodeInitially}
36
+ // For objects and arrays
37
+ getItemString={(_type, data, itemType, itemString) => (
38
+ <JsonTreeCopyableItem getCopyableValue={() => JSON.stringify(data, null, 2)}>
39
+ <>{itemType} {itemString}</>
40
+ </JsonTreeCopyableItem>
41
+ )}
42
+ // For primitives
43
+ valueRenderer={(valueAsString, value) => (
44
+ <JsonTreeCopyableItem getCopyableValue={() => String(value)} className="ml-2">
45
+ {String(valueAsString)}
46
+ </JsonTreeCopyableItem>
47
+ )}
35
48
  />
36
49
  );
37
50
  };
@@ -0,0 +1,33 @@
1
+ import { Check, Copy } from "lucide-react";
2
+ import { MouseEvent, PropsWithChildren } from "react";
3
+ import { useCopyToClipboard } from "../hooks/useCopyToClipboard";
4
+ import { cn } from "../utils/cn";
5
+
6
+ type JsonTreeCopyableItemProps = PropsWithChildren<{
7
+ getCopyableValue: () => string;
8
+ className?: string;
9
+ }>;
10
+
11
+ export const JsonTreeCopyableItem = ({ children, getCopyableValue, className }: JsonTreeCopyableItemProps) => {
12
+ const { isCopied, copy } = useCopyToClipboard();
13
+
14
+ const handleCopy = (event: MouseEvent) => {
15
+ event.stopPropagation();
16
+
17
+ copy(getCopyableValue());
18
+ }
19
+
20
+ const Icon = isCopied ? Check : Copy;
21
+
22
+ return (
23
+ <span className={cn('inline-block group', className)}>
24
+ {children}
25
+ <div
26
+ className="inline-block cursor-pointer opacity-0 group-hover:opacity-100 text-gray-500 hover:text-gray-300 transition-all p-2 -m-2 ml-0 translate-y-0.75"
27
+ onClick={handleCopy}
28
+ >
29
+ <Icon className='h-4 w-4' />
30
+ </div>
31
+ </span>
32
+ );
33
+ }