goodchuck-utils 1.4.2 → 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 (61) 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 +50 -0
  20. package/dist/components/dev/IdSelector.d.ts.map +1 -0
  21. package/dist/components/dev/IdSelector.js +129 -0
  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 +15 -0
  29. package/dist/components/dev/index.d.ts.map +1 -0
  30. package/dist/components/dev/index.js +12 -0
  31. package/dist/components/index.d.ts +8 -0
  32. package/dist/components/index.d.ts.map +1 -0
  33. package/dist/components/index.js +7 -0
  34. package/dist/hooks/index.d.ts +8 -0
  35. package/dist/hooks/index.d.ts.map +1 -1
  36. package/dist/hooks/index.js +11 -0
  37. package/dist/hooks/useCopyToClipboard.d.ts +67 -0
  38. package/dist/hooks/useCopyToClipboard.d.ts.map +1 -0
  39. package/dist/hooks/useCopyToClipboard.js +79 -0
  40. package/dist/hooks/useDebounce.d.ts +47 -0
  41. package/dist/hooks/useDebounce.d.ts.map +1 -0
  42. package/dist/hooks/useDebounce.js +60 -0
  43. package/dist/hooks/useEventListener.d.ts +79 -0
  44. package/dist/hooks/useEventListener.d.ts.map +1 -0
  45. package/dist/hooks/useEventListener.js +33 -0
  46. package/dist/hooks/useIntersectionObserver.d.ts +109 -0
  47. package/dist/hooks/useIntersectionObserver.d.ts.map +1 -0
  48. package/dist/hooks/useIntersectionObserver.js +128 -0
  49. package/dist/hooks/usePrevious.d.ts +58 -0
  50. package/dist/hooks/usePrevious.d.ts.map +1 -0
  51. package/dist/hooks/usePrevious.js +67 -0
  52. package/dist/hooks/useThrottle.d.ts +57 -0
  53. package/dist/hooks/useThrottle.d.ts.map +1 -0
  54. package/dist/hooks/useThrottle.js +80 -0
  55. package/dist/hooks/useToggle.d.ts +49 -0
  56. package/dist/hooks/useToggle.d.ts.map +1 -0
  57. package/dist/hooks/useToggle.js +56 -0
  58. package/dist/hooks/useWindowSize.d.ts +58 -0
  59. package/dist/hooks/useWindowSize.d.ts.map +1 -0
  60. package/dist/hooks/useWindowSize.js +79 -0
  61. package/package.json +23 -2
@@ -0,0 +1,187 @@
1
+ export const getPositionStyles = () => ({
2
+ 'top-left': { top: 16, left: 16 },
3
+ 'top-right': { top: 16, right: 16 },
4
+ 'bottom-left': { bottom: 16, left: 16 },
5
+ 'bottom-right': { bottom: 16, right: 16 },
6
+ });
7
+ export const getContainerStyle = (position) => {
8
+ const positionStyles = getPositionStyles();
9
+ return {
10
+ position: 'fixed',
11
+ ...positionStyles[position],
12
+ zIndex: 99999,
13
+ fontFamily: 'system-ui, -apple-system, sans-serif',
14
+ };
15
+ };
16
+ export const getToggleButtonStyle = (isValid) => ({
17
+ width: '48px',
18
+ height: '48px',
19
+ borderRadius: '50%',
20
+ backgroundColor: isValid === false ? '#ef4444' : '#8b5cf6',
21
+ color: 'white',
22
+ border: 'none',
23
+ cursor: 'pointer',
24
+ fontSize: '20px',
25
+ boxShadow: '0 4px 6px rgba(0, 0, 0, 0.1)',
26
+ display: 'flex',
27
+ alignItems: 'center',
28
+ justifyContent: 'center',
29
+ transition: 'background-color 0.2s',
30
+ });
31
+ export const getPanelStyle = (position, panelPosition, panelSize, isDragging) => ({
32
+ position: 'fixed',
33
+ top: panelPosition.y || (position.includes('top') ? 60 : undefined),
34
+ bottom: !panelPosition.y && position.includes('bottom') ? 60 : undefined,
35
+ left: panelPosition.x || (position.includes('left') ? 0 : undefined),
36
+ right: !panelPosition.x && position.includes('right') ? 0 : undefined,
37
+ backgroundColor: 'white',
38
+ borderRadius: '12px',
39
+ boxShadow: '0 10px 25px rgba(0, 0, 0, 0.15)',
40
+ border: '1px solid #e5e7eb',
41
+ width: `${panelSize.width}px`,
42
+ height: `${panelSize.height}px`,
43
+ overflow: 'hidden',
44
+ display: 'flex',
45
+ flexDirection: 'column',
46
+ cursor: isDragging ? 'grabbing' : 'default',
47
+ });
48
+ export const headerStyle = {
49
+ padding: '12px 16px',
50
+ borderBottom: '1px solid #e5e7eb',
51
+ display: 'flex',
52
+ justifyContent: 'space-between',
53
+ alignItems: 'center',
54
+ backgroundColor: '#f9fafb',
55
+ cursor: 'grab',
56
+ userSelect: 'none',
57
+ };
58
+ export const headerTitleStyle = {
59
+ fontWeight: 'bold',
60
+ fontSize: '14px',
61
+ color: '#111827',
62
+ };
63
+ export const getStatusBadgeStyle = (isValid) => ({
64
+ display: 'inline-flex',
65
+ alignItems: 'center',
66
+ gap: '4px',
67
+ padding: '4px 8px',
68
+ backgroundColor: isValid ? '#dcfce7' : '#fee2e2',
69
+ color: isValid ? '#166534' : '#991b1b',
70
+ borderRadius: '6px',
71
+ fontSize: '11px',
72
+ fontWeight: 600,
73
+ });
74
+ export const getCopyButtonStyle = (isCopied) => ({
75
+ padding: '4px 12px',
76
+ backgroundColor: isCopied ? '#10b981' : '#3b82f6',
77
+ color: 'white',
78
+ border: 'none',
79
+ borderRadius: '6px',
80
+ cursor: 'pointer',
81
+ fontSize: '11px',
82
+ fontWeight: 600,
83
+ transition: 'background-color 0.2s',
84
+ });
85
+ export const tabContainerStyle = {
86
+ display: 'flex',
87
+ borderBottom: '1px solid #e5e7eb',
88
+ backgroundColor: '#f9fafb',
89
+ };
90
+ export const getTabStyle = (isActive) => ({
91
+ flex: 1,
92
+ padding: '10px 16px',
93
+ border: 'none',
94
+ backgroundColor: isActive ? 'white' : 'transparent',
95
+ color: isActive ? '#8b5cf6' : '#6b7280',
96
+ cursor: 'pointer',
97
+ fontSize: '13px',
98
+ fontWeight: isActive ? 600 : 400,
99
+ borderBottom: isActive ? '2px solid #8b5cf6' : '2px solid transparent',
100
+ transition: 'all 0.15s',
101
+ });
102
+ export const contentStyle = {
103
+ flex: 1,
104
+ overflowY: 'auto',
105
+ padding: '16px',
106
+ };
107
+ export const sectionTitleStyle = {
108
+ fontSize: '12px',
109
+ fontWeight: 'bold',
110
+ color: '#6b7280',
111
+ marginBottom: '8px',
112
+ textTransform: 'uppercase',
113
+ letterSpacing: '0.5px',
114
+ };
115
+ export const codeBlockStyle = {
116
+ backgroundColor: '#f3f4f6',
117
+ borderRadius: '6px',
118
+ padding: '12px',
119
+ fontSize: '12px',
120
+ fontFamily: 'monospace',
121
+ overflow: 'auto',
122
+ maxHeight: '400px',
123
+ marginBottom: '16px',
124
+ color: '#111827', // 검은색 텍스트
125
+ };
126
+ export const errorItemStyle = {
127
+ padding: '8px 12px',
128
+ backgroundColor: '#fef2f2',
129
+ border: '1px solid #fecaca',
130
+ borderRadius: '6px',
131
+ marginBottom: '8px',
132
+ };
133
+ export const errorLabelStyle = {
134
+ fontSize: '12px',
135
+ fontWeight: 600,
136
+ color: '#991b1b',
137
+ marginBottom: '4px',
138
+ };
139
+ export const errorMessageStyle = {
140
+ fontSize: '11px',
141
+ color: '#7f1d1d',
142
+ };
143
+ export const statsContainerStyle = {
144
+ display: 'grid',
145
+ gridTemplateColumns: '1fr 1fr',
146
+ gap: '12px',
147
+ marginBottom: '16px',
148
+ };
149
+ export const statCardStyle = {
150
+ padding: '12px',
151
+ backgroundColor: '#f9fafb',
152
+ borderRadius: '8px',
153
+ border: '1px solid #e5e7eb',
154
+ };
155
+ export const statLabelStyle = {
156
+ fontSize: '11px',
157
+ color: '#6b7280',
158
+ marginBottom: '4px',
159
+ textTransform: 'uppercase',
160
+ letterSpacing: '0.5px',
161
+ };
162
+ export const statValueStyle = {
163
+ fontSize: '18px',
164
+ fontWeight: 'bold',
165
+ color: '#111827',
166
+ };
167
+ export const resizeHandleStyle = {
168
+ position: 'absolute',
169
+ bottom: 0,
170
+ right: 0,
171
+ width: '20px',
172
+ height: '20px',
173
+ cursor: 'nwse-resize',
174
+ backgroundColor: 'transparent',
175
+ zIndex: 1,
176
+ };
177
+ export const resizeHandleIndicatorStyle = {
178
+ position: 'absolute',
179
+ bottom: 0,
180
+ right: 0,
181
+ width: '12px',
182
+ height: '12px',
183
+ cursor: 'nwse-resize',
184
+ borderRight: '3px solid #9ca3af',
185
+ borderBottom: '3px solid #9ca3af',
186
+ borderRadius: '0 0 12px 0',
187
+ };
@@ -0,0 +1,76 @@
1
+ import React from 'react';
2
+ type FormState = {
3
+ values?: Record<string, any>;
4
+ errors?: Record<string, any>;
5
+ dirtyFields?: Record<string, any>;
6
+ touchedFields?: Record<string, any>;
7
+ isValid?: boolean;
8
+ isSubmitting?: boolean;
9
+ submitCount?: number;
10
+ };
11
+ type Props = {
12
+ /** react-hook-form의 formState */
13
+ formState: FormState;
14
+ /** 현재 폼 values (watch() 결과) */
15
+ values?: Record<string, any>;
16
+ /** 표시 위치 (기본값: 'bottom-left') */
17
+ position?: 'top-left' | 'top-right' | 'bottom-left' | 'bottom-right';
18
+ /** 패널 제목 (기본값: 'Form DevTools') */
19
+ title?: string;
20
+ };
21
+ /**
22
+ * react-hook-form의 상태를 실시간으로 시각화하는 개발용 컴포넌트
23
+ * form의 values, errors, dirtyFields, touchedFields 등을 한눈에 확인할 수 있습니다.
24
+ *
25
+ * @example
26
+ * ```tsx
27
+ * // Vite 프로젝트
28
+ * import { useForm } from 'react-hook-form';
29
+ * import { FormDevTools } from 'goodchuck-utils/components/dev';
30
+ *
31
+ * function MyForm() {
32
+ * const { register, handleSubmit, formState, watch } = useForm({
33
+ * defaultValues: {
34
+ * username: '',
35
+ * email: '',
36
+ * age: 0,
37
+ * }
38
+ * });
39
+ *
40
+ * const values = watch(); // 모든 값 watch
41
+ *
42
+ * const onSubmit = (data) => {
43
+ * console.log(data);
44
+ * };
45
+ *
46
+ * return (
47
+ * <form onSubmit={handleSubmit(onSubmit)}>
48
+ * <input {...register('username', { required: 'Username is required' })} />
49
+ * <input {...register('email', { required: 'Email is required' })} />
50
+ * <input type="number" {...register('age', { min: 18 })} />
51
+ * <button type="submit">Submit</button>
52
+ *
53
+ * {import.meta.env.DEV && (
54
+ * <FormDevTools formState={formState} values={values} />
55
+ * )}
56
+ * </form>
57
+ * );
58
+ * }
59
+ * ```
60
+ *
61
+ * @example
62
+ * ```tsx
63
+ * // Create React App 프로젝트
64
+ * {process.env.NODE_ENV === 'development' && (
65
+ * <FormDevTools
66
+ * formState={formState}
67
+ * values={values}
68
+ * position="top-right"
69
+ * title="User Form Debug"
70
+ * />
71
+ * )}
72
+ * ```
73
+ */
74
+ export default function FormDevTools({ formState, values, position, title }: Props): React.JSX.Element;
75
+ export {};
76
+ //# sourceMappingURL=FormDevTools.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"FormDevTools.d.ts","sourceRoot":"","sources":["../../../src/components/dev/FormDevTools.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAqD,MAAM,OAAO,CAAC;AAG1E,KAAK,SAAS,GAAG;IACf,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;IAC7B,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;IAC7B,WAAW,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;IAClC,aAAa,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;IACpC,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB,CAAC;AAEF,KAAK,KAAK,GAAG;IACX,iCAAiC;IACjC,SAAS,EAAE,SAAS,CAAC;IACrB,+BAA+B;IAC/B,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;IAC7B,iCAAiC;IACjC,QAAQ,CAAC,EAAE,UAAU,GAAG,WAAW,GAAG,aAAa,GAAG,cAAc,CAAC;IACrE,mCAAmC;IACnC,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB,CAAC;AAEF;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAoDG;AACH,MAAM,CAAC,OAAO,UAAU,YAAY,CAAC,EAAE,SAAS,EAAE,MAAM,EAAE,QAAwB,EAAE,KAAuB,EAAE,EAAE,KAAK,qBAocnH"}
@@ -0,0 +1,399 @@
1
+ import React, { useState, useRef, useEffect } from 'react';
2
+ import { useCopyToClipboard } from '../../hooks/useCopyToClipboard';
3
+ /**
4
+ * react-hook-form의 상태를 실시간으로 시각화하는 개발용 컴포넌트
5
+ * form의 values, errors, dirtyFields, touchedFields 등을 한눈에 확인할 수 있습니다.
6
+ *
7
+ * @example
8
+ * ```tsx
9
+ * // Vite 프로젝트
10
+ * import { useForm } from 'react-hook-form';
11
+ * import { FormDevTools } from 'goodchuck-utils/components/dev';
12
+ *
13
+ * function MyForm() {
14
+ * const { register, handleSubmit, formState, watch } = useForm({
15
+ * defaultValues: {
16
+ * username: '',
17
+ * email: '',
18
+ * age: 0,
19
+ * }
20
+ * });
21
+ *
22
+ * const values = watch(); // 모든 값 watch
23
+ *
24
+ * const onSubmit = (data) => {
25
+ * console.log(data);
26
+ * };
27
+ *
28
+ * return (
29
+ * <form onSubmit={handleSubmit(onSubmit)}>
30
+ * <input {...register('username', { required: 'Username is required' })} />
31
+ * <input {...register('email', { required: 'Email is required' })} />
32
+ * <input type="number" {...register('age', { min: 18 })} />
33
+ * <button type="submit">Submit</button>
34
+ *
35
+ * {import.meta.env.DEV && (
36
+ * <FormDevTools formState={formState} values={values} />
37
+ * )}
38
+ * </form>
39
+ * );
40
+ * }
41
+ * ```
42
+ *
43
+ * @example
44
+ * ```tsx
45
+ * // Create React App 프로젝트
46
+ * {process.env.NODE_ENV === 'development' && (
47
+ * <FormDevTools
48
+ * formState={formState}
49
+ * values={values}
50
+ * position="top-right"
51
+ * title="User Form Debug"
52
+ * />
53
+ * )}
54
+ * ```
55
+ */
56
+ export default function FormDevTools({ formState, values, position = 'bottom-left', title = 'Form DevTools' }) {
57
+ const [isOpen, setIsOpen] = useState(false);
58
+ const [activeTab, setActiveTab] = useState('values');
59
+ const [panelPosition, setPanelPosition] = useState({ x: 0, y: 0 });
60
+ const [panelSize, setPanelSize] = useState({ width: 500, height: 600 });
61
+ const [isDragging, setIsDragging] = useState(false);
62
+ const [isResizing, setIsResizing] = useState(false);
63
+ const [dragStart, setDragStart] = useState({ x: 0, y: 0 });
64
+ const [resizeStart, setResizeStart] = useState({ x: 0, y: 0, width: 0, height: 0 });
65
+ const panelRef = useRef(null);
66
+ const { copy, copiedText } = useCopyToClipboard();
67
+ const handleCopy = () => {
68
+ const data = {
69
+ values,
70
+ errors: formState.errors,
71
+ dirtyFields: formState.dirtyFields,
72
+ touchedFields: formState.touchedFields,
73
+ isValid: formState.isValid,
74
+ isSubmitting: formState.isSubmitting,
75
+ submitCount: formState.submitCount,
76
+ };
77
+ copy(JSON.stringify(data, null, 2));
78
+ };
79
+ const isCopied = copiedText !== null;
80
+ const positionStyles = {
81
+ 'top-left': { top: 16, left: 16 },
82
+ 'top-right': { top: 16, right: 16 },
83
+ 'bottom-left': { bottom: 16, left: 16 },
84
+ 'bottom-right': { bottom: 16, right: 16 },
85
+ };
86
+ const containerStyle = {
87
+ position: 'fixed',
88
+ ...positionStyles[position],
89
+ zIndex: 99999,
90
+ fontFamily: 'system-ui, -apple-system, sans-serif',
91
+ };
92
+ const toggleButtonStyle = {
93
+ width: '48px',
94
+ height: '48px',
95
+ borderRadius: '50%',
96
+ backgroundColor: formState.isValid === false ? '#ef4444' : '#8b5cf6',
97
+ color: 'white',
98
+ border: 'none',
99
+ cursor: 'pointer',
100
+ fontSize: '20px',
101
+ boxShadow: '0 4px 6px rgba(0, 0, 0, 0.1)',
102
+ display: 'flex',
103
+ alignItems: 'center',
104
+ justifyContent: 'center',
105
+ transition: 'background-color 0.2s',
106
+ };
107
+ const panelStyle = {
108
+ position: 'fixed',
109
+ top: panelPosition.y || (position.includes('top') ? 60 : undefined),
110
+ bottom: !panelPosition.y && position.includes('bottom') ? 60 : undefined,
111
+ left: panelPosition.x || (position.includes('left') ? 0 : undefined),
112
+ right: !panelPosition.x && position.includes('right') ? 0 : undefined,
113
+ backgroundColor: 'white',
114
+ borderRadius: '12px',
115
+ boxShadow: '0 10px 25px rgba(0, 0, 0, 0.15)',
116
+ border: '1px solid #e5e7eb',
117
+ width: `${panelSize.width}px`,
118
+ height: `${panelSize.height}px`,
119
+ overflow: 'hidden',
120
+ display: 'flex',
121
+ flexDirection: 'column',
122
+ cursor: isDragging ? 'grabbing' : 'default',
123
+ };
124
+ const headerStyle = {
125
+ padding: '12px 16px',
126
+ borderBottom: '1px solid #e5e7eb',
127
+ display: 'flex',
128
+ justifyContent: 'space-between',
129
+ alignItems: 'center',
130
+ backgroundColor: '#f9fafb',
131
+ cursor: 'grab',
132
+ userSelect: 'none',
133
+ };
134
+ const headerTitleStyle = {
135
+ fontWeight: 'bold',
136
+ fontSize: '14px',
137
+ color: '#111827',
138
+ };
139
+ const statusBadgeStyle = {
140
+ display: 'inline-flex',
141
+ alignItems: 'center',
142
+ gap: '4px',
143
+ padding: '4px 8px',
144
+ backgroundColor: formState.isValid ? '#dcfce7' : '#fee2e2',
145
+ color: formState.isValid ? '#166534' : '#991b1b',
146
+ borderRadius: '6px',
147
+ fontSize: '11px',
148
+ fontWeight: 600,
149
+ };
150
+ const copyButtonStyle = {
151
+ padding: '4px 12px',
152
+ backgroundColor: isCopied ? '#10b981' : '#3b82f6',
153
+ color: 'white',
154
+ border: 'none',
155
+ borderRadius: '6px',
156
+ cursor: 'pointer',
157
+ fontSize: '11px',
158
+ fontWeight: 600,
159
+ transition: 'background-color 0.2s',
160
+ };
161
+ const tabContainerStyle = {
162
+ display: 'flex',
163
+ borderBottom: '1px solid #e5e7eb',
164
+ backgroundColor: '#f9fafb',
165
+ };
166
+ const tabStyle = (isActive) => ({
167
+ flex: 1,
168
+ padding: '10px 16px',
169
+ border: 'none',
170
+ backgroundColor: isActive ? 'white' : 'transparent',
171
+ color: isActive ? '#8b5cf6' : '#6b7280',
172
+ cursor: 'pointer',
173
+ fontSize: '13px',
174
+ fontWeight: isActive ? 600 : 400,
175
+ borderBottom: isActive ? '2px solid #8b5cf6' : '2px solid transparent',
176
+ transition: 'all 0.15s',
177
+ });
178
+ const contentStyle = {
179
+ flex: 1,
180
+ overflowY: 'auto',
181
+ padding: '16px',
182
+ };
183
+ const sectionTitleStyle = {
184
+ fontSize: '12px',
185
+ fontWeight: 'bold',
186
+ color: '#6b7280',
187
+ marginBottom: '8px',
188
+ textTransform: 'uppercase',
189
+ letterSpacing: '0.5px',
190
+ };
191
+ const codeBlockStyle = {
192
+ backgroundColor: '#f3f4f6',
193
+ borderRadius: '6px',
194
+ padding: '12px',
195
+ fontSize: '12px',
196
+ fontFamily: 'monospace',
197
+ overflow: 'auto',
198
+ maxHeight: '400px',
199
+ marginBottom: '16px',
200
+ color: '#111827', // 검은색 텍스트
201
+ };
202
+ const errorItemStyle = {
203
+ padding: '8px 12px',
204
+ backgroundColor: '#fef2f2',
205
+ border: '1px solid #fecaca',
206
+ borderRadius: '6px',
207
+ marginBottom: '8px',
208
+ };
209
+ const errorLabelStyle = {
210
+ fontSize: '12px',
211
+ fontWeight: 600,
212
+ color: '#991b1b',
213
+ marginBottom: '4px',
214
+ };
215
+ const errorMessageStyle = {
216
+ fontSize: '11px',
217
+ color: '#7f1d1d',
218
+ };
219
+ const statsContainerStyle = {
220
+ display: 'grid',
221
+ gridTemplateColumns: '1fr 1fr',
222
+ gap: '12px',
223
+ marginBottom: '16px',
224
+ };
225
+ const statCardStyle = {
226
+ padding: '12px',
227
+ backgroundColor: '#f9fafb',
228
+ borderRadius: '8px',
229
+ border: '1px solid #e5e7eb',
230
+ };
231
+ const statLabelStyle = {
232
+ fontSize: '11px',
233
+ color: '#6b7280',
234
+ marginBottom: '4px',
235
+ textTransform: 'uppercase',
236
+ letterSpacing: '0.5px',
237
+ };
238
+ const statValueStyle = {
239
+ fontSize: '18px',
240
+ fontWeight: 'bold',
241
+ color: '#111827',
242
+ };
243
+ const errorCount = Object.keys(formState.errors || {}).length;
244
+ const dirtyFieldsCount = Object.keys(formState.dirtyFields || {}).length;
245
+ const touchedFieldsCount = Object.keys(formState.touchedFields || {}).length;
246
+ // 드래그 핸들러
247
+ useEffect(() => {
248
+ const handleMouseMove = (e) => {
249
+ if (isDragging) {
250
+ const deltaX = e.clientX - dragStart.x;
251
+ const deltaY = e.clientY - dragStart.y;
252
+ setPanelPosition((prev) => ({
253
+ x: prev.x + deltaX,
254
+ y: prev.y + deltaY,
255
+ }));
256
+ setDragStart({ x: e.clientX, y: e.clientY });
257
+ }
258
+ if (isResizing) {
259
+ const deltaX = e.clientX - resizeStart.x;
260
+ const deltaY = e.clientY - resizeStart.y;
261
+ setPanelSize({
262
+ width: Math.max(300, resizeStart.width + deltaX),
263
+ height: Math.max(200, resizeStart.height + deltaY),
264
+ });
265
+ }
266
+ };
267
+ const handleMouseUp = () => {
268
+ setIsDragging(false);
269
+ setIsResizing(false);
270
+ };
271
+ if (isDragging || isResizing) {
272
+ document.addEventListener('mousemove', handleMouseMove);
273
+ document.addEventListener('mouseup', handleMouseUp);
274
+ return () => {
275
+ document.removeEventListener('mousemove', handleMouseMove);
276
+ document.removeEventListener('mouseup', handleMouseUp);
277
+ };
278
+ }
279
+ }, [isDragging, isResizing, dragStart, resizeStart]);
280
+ const handleHeaderMouseDown = (e) => {
281
+ if (panelRef.current) {
282
+ const rect = panelRef.current.getBoundingClientRect();
283
+ setIsDragging(true);
284
+ setDragStart({
285
+ x: e.clientX,
286
+ y: e.clientY,
287
+ });
288
+ setPanelPosition({
289
+ x: rect.left,
290
+ y: rect.top,
291
+ });
292
+ }
293
+ };
294
+ const handleResizeMouseDown = (e) => {
295
+ e.stopPropagation();
296
+ setIsResizing(true);
297
+ setResizeStart({
298
+ x: e.clientX,
299
+ y: e.clientY,
300
+ width: panelSize.width,
301
+ height: panelSize.height,
302
+ });
303
+ };
304
+ const renderErrors = () => {
305
+ if (!formState.errors || Object.keys(formState.errors).length === 0) {
306
+ return (React.createElement("div", { style: { textAlign: 'center', color: '#9ca3af', padding: '20px', fontSize: '13px' } }, "No validation errors"));
307
+ }
308
+ return Object.entries(formState.errors).map(([field, error]) => (React.createElement("div", { key: field, style: errorItemStyle },
309
+ React.createElement("div", { style: errorLabelStyle }, field),
310
+ React.createElement("div", { style: errorMessageStyle }, error?.message || 'Invalid value'))));
311
+ };
312
+ return (React.createElement("div", { style: containerStyle },
313
+ React.createElement("button", { onClick: () => setIsOpen(!isOpen), style: toggleButtonStyle, onMouseEnter: (e) => (e.currentTarget.style.backgroundColor = formState.isValid === false ? '#dc2626' : '#7c3aed'), onMouseLeave: (e) => (e.currentTarget.style.backgroundColor = formState.isValid === false ? '#ef4444' : '#8b5cf6') }, isOpen ? '✕' : '📝'),
314
+ isOpen && (React.createElement("div", { ref: panelRef, style: panelStyle },
315
+ React.createElement("div", { style: headerStyle, onMouseDown: handleHeaderMouseDown },
316
+ React.createElement("div", { style: { display: 'flex', alignItems: 'center', gap: '8px' } },
317
+ React.createElement("div", { style: headerTitleStyle },
318
+ "\uD83D\uDCDD ",
319
+ title),
320
+ React.createElement("div", { style: statusBadgeStyle }, formState.isValid ? '✓ Valid' : `✗ ${errorCount} Error${errorCount > 1 ? 's' : ''}`)),
321
+ React.createElement("button", { onClick: handleCopy, style: copyButtonStyle, onMouseEnter: (e) => {
322
+ if (!isCopied)
323
+ e.currentTarget.style.backgroundColor = '#2563eb';
324
+ }, onMouseLeave: (e) => {
325
+ if (!isCopied)
326
+ e.currentTarget.style.backgroundColor = '#3b82f6';
327
+ } }, isCopied ? '✓ Copied' : 'Copy All')),
328
+ React.createElement("div", { style: tabContainerStyle },
329
+ React.createElement("button", { onClick: () => setActiveTab('values'), style: tabStyle(activeTab === 'values'), onMouseEnter: (e) => {
330
+ if (activeTab !== 'values')
331
+ e.currentTarget.style.backgroundColor = '#f3f4f6';
332
+ }, onMouseLeave: (e) => {
333
+ if (activeTab !== 'values')
334
+ e.currentTarget.style.backgroundColor = 'transparent';
335
+ } }, "Values"),
336
+ React.createElement("button", { onClick: () => setActiveTab('errors'), style: tabStyle(activeTab === 'errors'), onMouseEnter: (e) => {
337
+ if (activeTab !== 'errors')
338
+ e.currentTarget.style.backgroundColor = '#f3f4f6';
339
+ }, onMouseLeave: (e) => {
340
+ if (activeTab !== 'errors')
341
+ e.currentTarget.style.backgroundColor = 'transparent';
342
+ } },
343
+ "Errors (",
344
+ errorCount,
345
+ ")"),
346
+ React.createElement("button", { onClick: () => setActiveTab('state'), style: tabStyle(activeTab === 'state'), onMouseEnter: (e) => {
347
+ if (activeTab !== 'state')
348
+ e.currentTarget.style.backgroundColor = '#f3f4f6';
349
+ }, onMouseLeave: (e) => {
350
+ if (activeTab !== 'state')
351
+ e.currentTarget.style.backgroundColor = 'transparent';
352
+ } }, "State")),
353
+ React.createElement("div", { style: contentStyle },
354
+ activeTab === 'values' && (React.createElement(React.Fragment, null,
355
+ React.createElement("div", { style: sectionTitleStyle }, "Form Values"),
356
+ React.createElement("pre", { style: codeBlockStyle }, JSON.stringify(values || {}, null, 2)))),
357
+ activeTab === 'errors' && (React.createElement(React.Fragment, null,
358
+ React.createElement("div", { style: sectionTitleStyle }, "Validation Errors"),
359
+ renderErrors())),
360
+ activeTab === 'state' && (React.createElement(React.Fragment, null,
361
+ React.createElement("div", { style: statsContainerStyle },
362
+ React.createElement("div", { style: statCardStyle },
363
+ React.createElement("div", { style: statLabelStyle }, "Dirty Fields"),
364
+ React.createElement("div", { style: statValueStyle }, dirtyFieldsCount)),
365
+ React.createElement("div", { style: statCardStyle },
366
+ React.createElement("div", { style: statLabelStyle }, "Touched Fields"),
367
+ React.createElement("div", { style: statValueStyle }, touchedFieldsCount)),
368
+ React.createElement("div", { style: statCardStyle },
369
+ React.createElement("div", { style: statLabelStyle }, "Submit Count"),
370
+ React.createElement("div", { style: statValueStyle }, formState.submitCount || 0)),
371
+ React.createElement("div", { style: statCardStyle },
372
+ React.createElement("div", { style: statLabelStyle }, "Submitting"),
373
+ React.createElement("div", { style: statValueStyle }, formState.isSubmitting ? 'Yes' : 'No'))),
374
+ React.createElement("div", { style: sectionTitleStyle }, "Dirty Fields"),
375
+ React.createElement("pre", { style: codeBlockStyle }, JSON.stringify(formState.dirtyFields || {}, null, 2)),
376
+ React.createElement("div", { style: sectionTitleStyle }, "Touched Fields"),
377
+ React.createElement("pre", { style: codeBlockStyle }, JSON.stringify(formState.touchedFields || {}, null, 2))))),
378
+ React.createElement("div", { onMouseDown: handleResizeMouseDown, style: {
379
+ position: 'absolute',
380
+ bottom: 0,
381
+ right: 0,
382
+ width: '20px',
383
+ height: '20px',
384
+ cursor: 'nwse-resize',
385
+ backgroundColor: 'transparent',
386
+ zIndex: 1,
387
+ } }),
388
+ React.createElement("div", { onMouseDown: handleResizeMouseDown, style: {
389
+ position: 'absolute',
390
+ bottom: 0,
391
+ right: 0,
392
+ width: '12px',
393
+ height: '12px',
394
+ cursor: 'nwse-resize',
395
+ borderRight: '3px solid #9ca3af',
396
+ borderBottom: '3px solid #9ca3af',
397
+ borderRadius: '0 0 12px 0',
398
+ } })))));
399
+ }