goodchuck-utils 1.5.0 → 1.6.0

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 (31) hide show
  1. package/dist/components/dev/ApiLogger.d.ts +136 -0
  2. package/dist/components/dev/ApiLogger.d.ts.map +1 -0
  3. package/dist/components/dev/ApiLogger.js +408 -0
  4. package/dist/components/dev/DevPanel.d.ts +32 -0
  5. package/dist/components/dev/DevPanel.d.ts.map +1 -0
  6. package/dist/components/dev/DevPanel.js +196 -0
  7. package/dist/components/dev/FormDevTools/FormDevTools.d.ts +75 -0
  8. package/dist/components/dev/FormDevTools/FormDevTools.d.ts.map +1 -0
  9. package/dist/components/dev/FormDevTools/FormDevTools.js +218 -0
  10. package/dist/components/dev/FormDevTools/index.d.ts +3 -0
  11. package/dist/components/dev/FormDevTools/index.d.ts.map +1 -0
  12. package/dist/components/dev/FormDevTools/index.js +1 -0
  13. package/dist/components/dev/FormDevTools/styles.d.ts +45 -0
  14. package/dist/components/dev/FormDevTools/styles.d.ts.map +1 -0
  15. package/dist/components/dev/FormDevTools/styles.js +187 -0
  16. package/dist/components/dev/FormDevTools.d.ts +76 -0
  17. package/dist/components/dev/FormDevTools.d.ts.map +1 -0
  18. package/dist/components/dev/FormDevTools.js +399 -0
  19. package/dist/components/dev/IdSelector.d.ts +10 -1
  20. package/dist/components/dev/IdSelector.d.ts.map +1 -1
  21. package/dist/components/dev/IdSelector.js +89 -12
  22. package/dist/components/dev/WindowSizeDisplay.d.ts +44 -0
  23. package/dist/components/dev/WindowSizeDisplay.d.ts.map +1 -0
  24. package/dist/components/dev/WindowSizeDisplay.js +74 -0
  25. package/dist/components/dev/ZIndexDebugger.d.ts +32 -0
  26. package/dist/components/dev/ZIndexDebugger.d.ts.map +1 -0
  27. package/dist/components/dev/ZIndexDebugger.js +184 -0
  28. package/dist/components/dev/index.d.ts +7 -0
  29. package/dist/components/dev/index.d.ts.map +1 -1
  30. package/dist/components/dev/index.js +5 -0
  31. package/package.json +2 -1
@@ -0,0 +1,136 @@
1
+ import React from 'react';
2
+ export type ApiLogEntry = {
3
+ id: string;
4
+ timestamp: Date;
5
+ method: string;
6
+ url: string;
7
+ status?: number;
8
+ statusText?: string;
9
+ duration?: number;
10
+ requestBody?: unknown;
11
+ responseBody?: unknown;
12
+ error?: string;
13
+ };
14
+ type Props = {
15
+ /** 표시 위치 (기본값: 'bottom-right') */
16
+ position?: 'top-left' | 'top-right' | 'bottom-left' | 'bottom-right';
17
+ /** 최대 로그 개수 (기본값: 50) */
18
+ maxLogs?: number;
19
+ };
20
+ /**
21
+ * API 로그를 추가하는 함수
22
+ * fetch나 axios interceptor에서 호출하여 사용합니다.
23
+ */
24
+ export declare function addApiLog(log: Omit<ApiLogEntry, 'id' | 'timestamp'>): void;
25
+ /**
26
+ * 모든 API 로그를 초기화하는 함수
27
+ */
28
+ export declare function clearApiLogs(): void;
29
+ /**
30
+ * API 요청/응답을 로깅하는 개발용 컴포넌트
31
+ * Axios interceptor 또는 fetch wrapper와 함께 사용하여 API 호출을 모니터링합니다.
32
+ *
33
+ * @example
34
+ * ```tsx
35
+ * // Axios interceptor 사용 (가장 일반적)
36
+ * import axios from 'axios';
37
+ * import { ApiLogger, addApiLog } from 'goodchuck-utils/components/dev';
38
+ *
39
+ * // Request interceptor
40
+ * axios.interceptors.request.use(
41
+ * (config) => {
42
+ * config.metadata = { startTime: Date.now() };
43
+ * return config;
44
+ * },
45
+ * (error) => Promise.reject(error)
46
+ * );
47
+ *
48
+ * // Response interceptor
49
+ * axios.interceptors.response.use(
50
+ * (response) => {
51
+ * const duration = Date.now() - response.config.metadata?.startTime;
52
+ *
53
+ * addApiLog({
54
+ * method: response.config.method?.toUpperCase() || 'GET',
55
+ * url: response.config.url || '',
56
+ * status: response.status,
57
+ * statusText: response.statusText,
58
+ * duration,
59
+ * requestBody: response.config.data,
60
+ * responseBody: response.data,
61
+ * });
62
+ *
63
+ * return response;
64
+ * },
65
+ * (error) => {
66
+ * const duration = Date.now() - error.config?.metadata?.startTime;
67
+ *
68
+ * addApiLog({
69
+ * method: error.config?.method?.toUpperCase() || 'GET',
70
+ * url: error.config?.url || '',
71
+ * status: error.response?.status,
72
+ * statusText: error.response?.statusText,
73
+ * duration,
74
+ * requestBody: error.config?.data,
75
+ * responseBody: error.response?.data,
76
+ * error: error.message,
77
+ * });
78
+ *
79
+ * return Promise.reject(error);
80
+ * }
81
+ * );
82
+ *
83
+ * function App() {
84
+ * return (
85
+ * <div>
86
+ * {import.meta.env.DEV && <ApiLogger />}
87
+ * </div>
88
+ * );
89
+ * }
90
+ * ```
91
+ *
92
+ * @example
93
+ * ```tsx
94
+ * // fetch wrapper 사용
95
+ * import { addApiLog } from 'goodchuck-utils/components/dev';
96
+ *
97
+ * const originalFetch = window.fetch;
98
+ * window.fetch = async (...args) => {
99
+ * const startTime = Date.now();
100
+ * const [url, options] = args;
101
+ *
102
+ * try {
103
+ * const response = await originalFetch(...args);
104
+ * const duration = Date.now() - startTime;
105
+ *
106
+ * addApiLog({
107
+ * method: options?.method || 'GET',
108
+ * url: url.toString(),
109
+ * status: response.status,
110
+ * statusText: response.statusText,
111
+ * duration,
112
+ * requestBody: options?.body,
113
+ * });
114
+ *
115
+ * return response;
116
+ * } catch (error) {
117
+ * addApiLog({
118
+ * method: options?.method || 'GET',
119
+ * url: url.toString(),
120
+ * error: (error as Error).message,
121
+ * duration: Date.now() - startTime,
122
+ * });
123
+ * throw error;
124
+ * }
125
+ * };
126
+ * ```
127
+ *
128
+ * @example
129
+ * ```tsx
130
+ * // Create React App 프로젝트
131
+ * {process.env.NODE_ENV === 'development' && <ApiLogger position="bottom-left" />}
132
+ * ```
133
+ */
134
+ export default function ApiLogger({ position, maxLogs }: Props): React.JSX.Element;
135
+ export {};
136
+ //# sourceMappingURL=ApiLogger.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ApiLogger.d.ts","sourceRoot":"","sources":["../../../src/components/dev/ApiLogger.tsx"],"names":[],"mappings":"AAAA,OAAO,KAA6C,MAAM,OAAO,CAAC;AAGlE,MAAM,MAAM,WAAW,GAAG;IACxB,EAAE,EAAE,MAAM,CAAC;IACX,SAAS,EAAE,IAAI,CAAC;IAChB,MAAM,EAAE,MAAM,CAAC;IACf,GAAG,EAAE,MAAM,CAAC;IACZ,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB,CAAC;AAEF,KAAK,KAAK,GAAG;IACX,kCAAkC;IAClC,QAAQ,CAAC,EAAE,UAAU,GAAG,WAAW,GAAG,aAAa,GAAG,cAAc,CAAC;IACrE,yBAAyB;IACzB,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB,CAAC;AAMF;;;GAGG;AACH,wBAAgB,SAAS,CAAC,GAAG,EAAE,IAAI,CAAC,WAAW,EAAE,IAAI,GAAG,WAAW,CAAC,QASnE;AAED;;GAEG;AACH,wBAAgB,YAAY,SAG3B;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAwGG;AACH,MAAM,CAAC,OAAO,UAAU,SAAS,CAAC,EAAE,QAAyB,EAAE,OAAY,EAAE,EAAE,KAAK,qBAgXnF"}
@@ -0,0 +1,408 @@
1
+ import React, { useState, useEffect } from 'react';
2
+ import { useCopyToClipboard } from '../../hooks/useCopyToClipboard';
3
+ // 전역 로그 저장소
4
+ let globalLogs = [];
5
+ let logListeners = [];
6
+ /**
7
+ * API 로그를 추가하는 함수
8
+ * fetch나 axios interceptor에서 호출하여 사용합니다.
9
+ */
10
+ export function addApiLog(log) {
11
+ const newLog = {
12
+ ...log,
13
+ id: `${Date.now()}-${Math.random()}`,
14
+ timestamp: new Date(),
15
+ };
16
+ globalLogs = [newLog, ...globalLogs].slice(0, 100);
17
+ logListeners.forEach((listener) => listener([...globalLogs]));
18
+ }
19
+ /**
20
+ * 모든 API 로그를 초기화하는 함수
21
+ */
22
+ export function clearApiLogs() {
23
+ globalLogs = [];
24
+ logListeners.forEach((listener) => listener([]));
25
+ }
26
+ /**
27
+ * API 요청/응답을 로깅하는 개발용 컴포넌트
28
+ * Axios interceptor 또는 fetch wrapper와 함께 사용하여 API 호출을 모니터링합니다.
29
+ *
30
+ * @example
31
+ * ```tsx
32
+ * // Axios interceptor 사용 (가장 일반적)
33
+ * import axios from 'axios';
34
+ * import { ApiLogger, addApiLog } from 'goodchuck-utils/components/dev';
35
+ *
36
+ * // Request interceptor
37
+ * axios.interceptors.request.use(
38
+ * (config) => {
39
+ * config.metadata = { startTime: Date.now() };
40
+ * return config;
41
+ * },
42
+ * (error) => Promise.reject(error)
43
+ * );
44
+ *
45
+ * // Response interceptor
46
+ * axios.interceptors.response.use(
47
+ * (response) => {
48
+ * const duration = Date.now() - response.config.metadata?.startTime;
49
+ *
50
+ * addApiLog({
51
+ * method: response.config.method?.toUpperCase() || 'GET',
52
+ * url: response.config.url || '',
53
+ * status: response.status,
54
+ * statusText: response.statusText,
55
+ * duration,
56
+ * requestBody: response.config.data,
57
+ * responseBody: response.data,
58
+ * });
59
+ *
60
+ * return response;
61
+ * },
62
+ * (error) => {
63
+ * const duration = Date.now() - error.config?.metadata?.startTime;
64
+ *
65
+ * addApiLog({
66
+ * method: error.config?.method?.toUpperCase() || 'GET',
67
+ * url: error.config?.url || '',
68
+ * status: error.response?.status,
69
+ * statusText: error.response?.statusText,
70
+ * duration,
71
+ * requestBody: error.config?.data,
72
+ * responseBody: error.response?.data,
73
+ * error: error.message,
74
+ * });
75
+ *
76
+ * return Promise.reject(error);
77
+ * }
78
+ * );
79
+ *
80
+ * function App() {
81
+ * return (
82
+ * <div>
83
+ * {import.meta.env.DEV && <ApiLogger />}
84
+ * </div>
85
+ * );
86
+ * }
87
+ * ```
88
+ *
89
+ * @example
90
+ * ```tsx
91
+ * // fetch wrapper 사용
92
+ * import { addApiLog } from 'goodchuck-utils/components/dev';
93
+ *
94
+ * const originalFetch = window.fetch;
95
+ * window.fetch = async (...args) => {
96
+ * const startTime = Date.now();
97
+ * const [url, options] = args;
98
+ *
99
+ * try {
100
+ * const response = await originalFetch(...args);
101
+ * const duration = Date.now() - startTime;
102
+ *
103
+ * addApiLog({
104
+ * method: options?.method || 'GET',
105
+ * url: url.toString(),
106
+ * status: response.status,
107
+ * statusText: response.statusText,
108
+ * duration,
109
+ * requestBody: options?.body,
110
+ * });
111
+ *
112
+ * return response;
113
+ * } catch (error) {
114
+ * addApiLog({
115
+ * method: options?.method || 'GET',
116
+ * url: url.toString(),
117
+ * error: (error as Error).message,
118
+ * duration: Date.now() - startTime,
119
+ * });
120
+ * throw error;
121
+ * }
122
+ * };
123
+ * ```
124
+ *
125
+ * @example
126
+ * ```tsx
127
+ * // Create React App 프로젝트
128
+ * {process.env.NODE_ENV === 'development' && <ApiLogger position="bottom-left" />}
129
+ * ```
130
+ */
131
+ export default function ApiLogger({ position = 'bottom-right', maxLogs = 50 }) {
132
+ const [isOpen, setIsOpen] = useState(false);
133
+ const [logs, setLogs] = useState([]);
134
+ const [selectedLog, setSelectedLog] = useState(null);
135
+ const { copy, copiedText } = useCopyToClipboard();
136
+ useEffect(() => {
137
+ const listener = (newLogs) => {
138
+ setLogs(newLogs.slice(0, maxLogs));
139
+ };
140
+ logListeners.push(listener);
141
+ listener([...globalLogs]);
142
+ return () => {
143
+ logListeners = logListeners.filter((l) => l !== listener);
144
+ };
145
+ }, [maxLogs]);
146
+ const handleClearLogs = () => {
147
+ if (confirm('모든 API 로그를 삭제하시겠습니까?')) {
148
+ clearApiLogs();
149
+ setSelectedLog(null);
150
+ }
151
+ };
152
+ const handleCopyLog = (log) => {
153
+ const logText = JSON.stringify(log, null, 2);
154
+ copy(logText);
155
+ };
156
+ const isCopied = copiedText !== null;
157
+ const getStatusColor = (status) => {
158
+ if (!status)
159
+ return '#6b7280';
160
+ if (status >= 200 && status < 300)
161
+ return '#10b981';
162
+ if (status >= 300 && status < 400)
163
+ return '#3b82f6';
164
+ if (status >= 400 && status < 500)
165
+ return '#f59e0b';
166
+ return '#ef4444';
167
+ };
168
+ const getMethodColor = (method) => {
169
+ switch (method.toUpperCase()) {
170
+ case 'GET':
171
+ return '#10b981';
172
+ case 'POST':
173
+ return '#3b82f6';
174
+ case 'PUT':
175
+ return '#f59e0b';
176
+ case 'PATCH':
177
+ return '#8b5cf6';
178
+ case 'DELETE':
179
+ return '#ef4444';
180
+ default:
181
+ return '#6b7280';
182
+ }
183
+ };
184
+ const formatTime = (date) => {
185
+ return date.toLocaleTimeString('ko-KR', { hour12: false });
186
+ };
187
+ const formatUrl = (url) => {
188
+ try {
189
+ const urlObj = new URL(url);
190
+ return urlObj.pathname + urlObj.search;
191
+ }
192
+ catch {
193
+ return url;
194
+ }
195
+ };
196
+ const positionStyles = {
197
+ 'top-left': { top: 16, left: 16 },
198
+ 'top-right': { top: 16, right: 16 },
199
+ 'bottom-left': { bottom: 16, left: 16 },
200
+ 'bottom-right': { bottom: 16, right: 16 },
201
+ };
202
+ const containerStyle = {
203
+ position: 'fixed',
204
+ ...positionStyles[position],
205
+ zIndex: 99999,
206
+ fontFamily: 'system-ui, -apple-system, sans-serif',
207
+ };
208
+ const toggleButtonStyle = {
209
+ width: '48px',
210
+ height: '48px',
211
+ borderRadius: '50%',
212
+ backgroundColor: logs.length > 0 ? '#10b981' : '#6b7280',
213
+ color: 'white',
214
+ border: 'none',
215
+ cursor: 'pointer',
216
+ fontSize: '20px',
217
+ boxShadow: '0 4px 6px rgba(0, 0, 0, 0.1)',
218
+ display: 'flex',
219
+ alignItems: 'center',
220
+ justifyContent: 'center',
221
+ position: 'relative',
222
+ transition: 'background-color 0.2s',
223
+ };
224
+ const badgeStyle = {
225
+ position: 'absolute',
226
+ top: '-4px',
227
+ right: '-4px',
228
+ backgroundColor: '#ef4444',
229
+ color: 'white',
230
+ borderRadius: '10px',
231
+ padding: '2px 6px',
232
+ fontSize: '11px',
233
+ fontWeight: 'bold',
234
+ minWidth: '20px',
235
+ textAlign: 'center',
236
+ };
237
+ const panelStyle = {
238
+ position: 'absolute',
239
+ bottom: position.includes('bottom') ? '60px' : undefined,
240
+ top: position.includes('top') ? '60px' : undefined,
241
+ right: position.includes('right') ? '0' : undefined,
242
+ left: position.includes('left') ? '0' : undefined,
243
+ backgroundColor: 'white',
244
+ borderRadius: '12px',
245
+ boxShadow: '0 10px 25px rgba(0, 0, 0, 0.15)',
246
+ border: '1px solid #e5e7eb',
247
+ width: '600px',
248
+ maxHeight: '500px',
249
+ overflow: 'hidden',
250
+ display: 'flex',
251
+ flexDirection: 'column',
252
+ };
253
+ const headerStyle = {
254
+ padding: '12px 16px',
255
+ borderBottom: '1px solid #e5e7eb',
256
+ display: 'flex',
257
+ justifyContent: 'space-between',
258
+ alignItems: 'center',
259
+ backgroundColor: '#f9fafb',
260
+ };
261
+ const headerTitleStyle = {
262
+ fontWeight: 'bold',
263
+ fontSize: '14px',
264
+ color: '#111827',
265
+ };
266
+ const clearButtonStyle = {
267
+ padding: '4px 12px',
268
+ backgroundColor: 'white',
269
+ color: '#ef4444',
270
+ border: '1px solid #fecaca',
271
+ borderRadius: '6px',
272
+ cursor: 'pointer',
273
+ fontSize: '12px',
274
+ fontWeight: 600,
275
+ transition: 'background-color 0.15s',
276
+ };
277
+ const contentStyle = {
278
+ display: 'flex',
279
+ flex: 1,
280
+ overflow: 'hidden',
281
+ };
282
+ const logListStyle = {
283
+ flex: selectedLog ? '0 0 300px' : '1',
284
+ overflowY: 'auto',
285
+ borderRight: selectedLog ? '1px solid #e5e7eb' : 'none',
286
+ };
287
+ const logItemStyle = (isSelected) => ({
288
+ padding: '12px 16px',
289
+ borderBottom: '1px solid #f3f4f6',
290
+ cursor: 'pointer',
291
+ backgroundColor: isSelected ? '#eff6ff' : 'white',
292
+ transition: 'background-color 0.15s',
293
+ });
294
+ const detailStyle = {
295
+ flex: 1,
296
+ overflowY: 'auto',
297
+ padding: '16px',
298
+ fontSize: '13px',
299
+ };
300
+ const methodBadgeStyle = (method) => ({
301
+ display: 'inline-block',
302
+ padding: '2px 6px',
303
+ backgroundColor: getMethodColor(method),
304
+ color: 'white',
305
+ borderRadius: '4px',
306
+ fontSize: '11px',
307
+ fontWeight: 'bold',
308
+ marginRight: '8px',
309
+ });
310
+ const statusBadgeStyle = (status) => ({
311
+ display: 'inline-block',
312
+ padding: '2px 6px',
313
+ backgroundColor: getStatusColor(status),
314
+ color: 'white',
315
+ borderRadius: '4px',
316
+ fontSize: '11px',
317
+ fontWeight: 'bold',
318
+ marginLeft: '8px',
319
+ });
320
+ return (React.createElement("div", { style: containerStyle },
321
+ React.createElement("button", { onClick: () => setIsOpen(!isOpen), style: toggleButtonStyle, onMouseEnter: (e) => (e.currentTarget.style.backgroundColor = logs.length > 0 ? '#059669' : '#4b5563'), onMouseLeave: (e) => (e.currentTarget.style.backgroundColor = logs.length > 0 ? '#10b981' : '#6b7280') },
322
+ isOpen ? '✕' : '📡',
323
+ logs.length > 0 && React.createElement("div", { style: badgeStyle }, logs.length)),
324
+ isOpen && (React.createElement("div", { style: panelStyle },
325
+ React.createElement("div", { style: headerStyle },
326
+ React.createElement("div", { style: headerTitleStyle },
327
+ "\uD83D\uDCE1 API Logger (",
328
+ logs.length,
329
+ ")"),
330
+ React.createElement("button", { onClick: handleClearLogs, style: clearButtonStyle, onMouseEnter: (e) => (e.currentTarget.style.backgroundColor = '#fef2f2'), onMouseLeave: (e) => (e.currentTarget.style.backgroundColor = 'white') }, "Clear")),
331
+ React.createElement("div", { style: contentStyle },
332
+ React.createElement("div", { style: logListStyle }, logs.length === 0 ? (React.createElement("div", { style: { padding: '20px', textAlign: 'center', color: '#9ca3af', fontSize: '13px' } }, "API \uB85C\uADF8\uAC00 \uC5C6\uC2B5\uB2C8\uB2E4.")) : (logs.map((log) => (React.createElement("div", { key: log.id, style: logItemStyle(selectedLog?.id === log.id), onClick: () => setSelectedLog(log), onMouseEnter: (e) => {
333
+ if (selectedLog?.id !== log.id) {
334
+ e.currentTarget.style.backgroundColor = '#f9fafb';
335
+ }
336
+ }, onMouseLeave: (e) => {
337
+ if (selectedLog?.id !== log.id) {
338
+ e.currentTarget.style.backgroundColor = 'white';
339
+ }
340
+ } },
341
+ React.createElement("div", { style: { fontSize: '11px', color: '#6b7280', marginBottom: '4px' } },
342
+ formatTime(log.timestamp),
343
+ log.duration && React.createElement("span", { style: { marginLeft: '8px' } },
344
+ log.duration,
345
+ "ms")),
346
+ React.createElement("div", null,
347
+ React.createElement("span", { style: methodBadgeStyle(log.method) }, log.method),
348
+ log.status && React.createElement("span", { style: statusBadgeStyle(log.status) }, log.status)),
349
+ React.createElement("div", { style: {
350
+ fontSize: '12px',
351
+ color: '#374151',
352
+ marginTop: '4px',
353
+ overflow: 'hidden',
354
+ textOverflow: 'ellipsis',
355
+ whiteSpace: 'nowrap',
356
+ } }, formatUrl(log.url)),
357
+ log.error && (React.createElement("div", { style: { fontSize: '11px', color: '#ef4444', marginTop: '4px' } },
358
+ "Error: ",
359
+ log.error))))))),
360
+ selectedLog && (React.createElement("div", { style: detailStyle },
361
+ React.createElement("div", { style: { marginBottom: '16px', display: 'flex', justifyContent: 'space-between' } },
362
+ React.createElement("h3", { style: { margin: 0, fontSize: '14px', fontWeight: 'bold' } }, "\uC0C1\uC138 \uC815\uBCF4"),
363
+ React.createElement("button", { onClick: () => handleCopyLog(selectedLog), style: {
364
+ padding: '4px 8px',
365
+ backgroundColor: isCopied ? '#10b981' : '#3b82f6',
366
+ color: 'white',
367
+ border: 'none',
368
+ borderRadius: '4px',
369
+ cursor: 'pointer',
370
+ fontSize: '11px',
371
+ transition: 'background-color 0.2s',
372
+ } }, isCopied ? '✓ Copied' : 'Copy JSON')),
373
+ React.createElement("div", { style: { marginBottom: '12px' } },
374
+ React.createElement("strong", null, "URL:"),
375
+ React.createElement("div", { style: {
376
+ marginTop: '4px',
377
+ padding: '8px',
378
+ backgroundColor: '#f3f4f6',
379
+ borderRadius: '6px',
380
+ fontSize: '12px',
381
+ fontFamily: 'monospace',
382
+ wordBreak: 'break-all',
383
+ } }, selectedLog.url)),
384
+ selectedLog.requestBody !== undefined && (React.createElement("div", { style: { marginBottom: '12px' } },
385
+ React.createElement("strong", null, "Request Body:"),
386
+ React.createElement("pre", { style: {
387
+ marginTop: '4px',
388
+ padding: '8px',
389
+ backgroundColor: '#f3f4f6',
390
+ borderRadius: '6px',
391
+ fontSize: '11px',
392
+ fontFamily: 'monospace',
393
+ overflow: 'auto',
394
+ maxHeight: '150px',
395
+ } }, JSON.stringify(selectedLog.requestBody, null, 2)))),
396
+ selectedLog.responseBody !== undefined && (React.createElement("div", { style: { marginBottom: '12px' } },
397
+ React.createElement("strong", null, "Response Body:"),
398
+ React.createElement("pre", { style: {
399
+ marginTop: '4px',
400
+ padding: '8px',
401
+ backgroundColor: '#f3f4f6',
402
+ borderRadius: '6px',
403
+ fontSize: '11px',
404
+ fontFamily: 'monospace',
405
+ overflow: 'auto',
406
+ maxHeight: '150px',
407
+ } }, JSON.stringify(selectedLog.responseBody, null, 2)))))))))));
408
+ }
@@ -0,0 +1,32 @@
1
+ import React from 'react';
2
+ type Props = {
3
+ /** 패널 초기 위치 (기본값: 'bottom-right') */
4
+ position?: 'top-left' | 'top-right' | 'bottom-left' | 'bottom-right';
5
+ };
6
+ /**
7
+ * 개발자 도구 패널
8
+ * 여러 개발용 도구를 하나의 패널에서 관리할 수 있습니다.
9
+ *
10
+ * @example
11
+ * ```tsx
12
+ * // Vite 프로젝트
13
+ * import { DevPanel } from 'goodchuck-utils/components/dev';
14
+ *
15
+ * function App() {
16
+ * return (
17
+ * <div>
18
+ * {import.meta.env.DEV && <DevPanel />}
19
+ * </div>
20
+ * );
21
+ * }
22
+ * ```
23
+ *
24
+ * @example
25
+ * ```tsx
26
+ * // Create React App 프로젝트
27
+ * {process.env.NODE_ENV === 'development' && <DevPanel position="top-left" />}
28
+ * ```
29
+ */
30
+ export default function DevPanel({ position }: Props): React.JSX.Element;
31
+ export {};
32
+ //# sourceMappingURL=DevPanel.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"DevPanel.d.ts","sourceRoot":"","sources":["../../../src/components/dev/DevPanel.tsx"],"names":[],"mappings":"AAAA,OAAO,KAA0C,MAAM,OAAO,CAAC;AAG/D,KAAK,KAAK,GAAG;IACX,qCAAqC;IACrC,QAAQ,CAAC,EAAE,UAAU,GAAG,WAAW,GAAG,aAAa,GAAG,cAAc,CAAC;CACtE,CAAC;AAEF;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,MAAM,CAAC,OAAO,UAAU,QAAQ,CAAC,EAAE,QAAyB,EAAE,EAAE,KAAK,qBA6OpE"}