goodchuck-utils 1.6.0 → 1.7.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.
@@ -1 +1 @@
1
- {"version":3,"file":"FormDevTools.d.ts","sourceRoot":"","sources":["../../../../src/components/dev/FormDevTools/FormDevTools.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAsC,MAAM,OAAO,CAAC;AA0B3D,MAAM,MAAM,SAAS,GAAG;IACtB,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,MAAM,MAAM,KAAK,GAAG;IAClB,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,qBA0OnH"}
1
+ {"version":3,"file":"FormDevTools.d.ts","sourceRoot":"","sources":["../../../../src/components/dev/FormDevTools/FormDevTools.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAsC,MAAM,OAAO,CAAC;AAwB3D,MAAM,MAAM,SAAS,GAAG;IACtB,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,MAAM,MAAM,KAAK,GAAG;IAClB,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,qBA2MnH"}
@@ -1,6 +1,6 @@
1
1
  import React, { useState, useRef, useEffect } from 'react';
2
2
  import { useCopyToClipboard } from '../../../hooks/useCopyToClipboard';
3
- import { getContainerStyle, getToggleButtonStyle, getPanelStyle, headerStyle, headerTitleStyle, getStatusBadgeStyle, getCopyButtonStyle, tabContainerStyle, getTabStyle, contentStyle, sectionTitleStyle, codeBlockStyle, errorItemStyle, errorLabelStyle, errorMessageStyle, statsContainerStyle, statCardStyle, statLabelStyle, statValueStyle, resizeHandleStyle, resizeHandleIndicatorStyle, } from './styles';
3
+ import { getContainerStyle, getToggleButtonStyle, getPanelStyle, headerStyle, headerTitleStyle, getStatusBadgeStyle, getCopyButtonStyle, contentStyle, sectionTitleStyle, codeBlockStyle, errorItemStyle, errorLabelStyle, errorMessageStyle, statsContainerStyle, statCardStyle, statLabelStyle, statValueStyle, resizeHandleStyle, resizeHandleIndicatorStyle, } from './styles';
4
4
  /**
5
5
  * react-hook-form의 상태를 실시간으로 시각화하는 개발용 컴포넌트
6
6
  * form의 values, errors, dirtyFields, touchedFields 등을 한눈에 확인할 수 있습니다.
@@ -56,9 +56,8 @@ import { getContainerStyle, getToggleButtonStyle, getPanelStyle, headerStyle, he
56
56
  */
57
57
  export default function FormDevTools({ formState, values, position = 'bottom-left', title = 'Form DevTools' }) {
58
58
  const [isOpen, setIsOpen] = useState(false);
59
- const [activeTab, setActiveTab] = useState('values');
60
59
  const [panelPosition, setPanelPosition] = useState({ x: 0, y: 0 });
61
- const [panelSize, setPanelSize] = useState({ width: 500, height: 600 });
60
+ const [panelSize, setPanelSize] = useState({ width: 500, height: 400 });
62
61
  const [isDragging, setIsDragging] = useState(false);
63
62
  const [isResizing, setIsResizing] = useState(false);
64
63
  const [dragStart, setDragStart] = useState({ x: 0, y: 0 });
@@ -96,9 +95,10 @@ export default function FormDevTools({ formState, values, position = 'bottom-lef
96
95
  if (isResizing) {
97
96
  const deltaX = e.clientX - resizeStart.x;
98
97
  const deltaY = e.clientY - resizeStart.y;
98
+ const maxHeight = window.innerHeight * 0.85; // 화면 높이의 85%를 최대값으로
99
99
  setPanelSize({
100
100
  width: Math.max(300, resizeStart.width + deltaX),
101
- height: Math.max(200, resizeStart.height + deltaY),
101
+ height: Math.min(maxHeight, Math.max(200, resizeStart.height + deltaY)),
102
102
  });
103
103
  }
104
104
  };
@@ -163,54 +163,32 @@ export default function FormDevTools({ formState, values, position = 'bottom-lef
163
163
  if (!isCopied)
164
164
  e.currentTarget.style.backgroundColor = '#3b82f6';
165
165
  } }, isCopied ? '✓ Copied' : 'Copy All')),
166
- React.createElement("div", { style: tabContainerStyle },
167
- React.createElement("button", { onClick: () => setActiveTab('values'), style: getTabStyle(activeTab === 'values'), onMouseEnter: (e) => {
168
- if (activeTab !== 'values')
169
- e.currentTarget.style.backgroundColor = '#f3f4f6';
170
- }, onMouseLeave: (e) => {
171
- if (activeTab !== 'values')
172
- e.currentTarget.style.backgroundColor = 'transparent';
173
- } }, "Values"),
174
- React.createElement("button", { onClick: () => setActiveTab('errors'), style: getTabStyle(activeTab === 'errors'), onMouseEnter: (e) => {
175
- if (activeTab !== 'errors')
176
- e.currentTarget.style.backgroundColor = '#f3f4f6';
177
- }, onMouseLeave: (e) => {
178
- if (activeTab !== 'errors')
179
- e.currentTarget.style.backgroundColor = 'transparent';
180
- } },
181
- "Errors (",
182
- errorCount,
183
- ")"),
184
- React.createElement("button", { onClick: () => setActiveTab('state'), style: getTabStyle(activeTab === 'state'), onMouseEnter: (e) => {
185
- if (activeTab !== 'state')
186
- e.currentTarget.style.backgroundColor = '#f3f4f6';
187
- }, onMouseLeave: (e) => {
188
- if (activeTab !== 'state')
189
- e.currentTarget.style.backgroundColor = 'transparent';
190
- } }, "State")),
191
166
  React.createElement("div", { style: contentStyle },
192
- activeTab === 'values' && (React.createElement(React.Fragment, null,
193
- React.createElement("div", { style: sectionTitleStyle }, "Form Values"),
194
- React.createElement("pre", { style: codeBlockStyle }, JSON.stringify(values || {}, null, 2)))),
195
- activeTab === 'errors' && (React.createElement(React.Fragment, null,
196
- React.createElement("div", { style: sectionTitleStyle }, "Validation Errors"),
167
+ React.createElement("div", { style: statsContainerStyle },
168
+ React.createElement("div", { style: statCardStyle },
169
+ React.createElement("div", { style: statLabelStyle }, "Dirty Fields"),
170
+ React.createElement("div", { style: statValueStyle }, dirtyFieldsCount)),
171
+ React.createElement("div", { style: statCardStyle },
172
+ React.createElement("div", { style: statLabelStyle }, "Touched Fields"),
173
+ React.createElement("div", { style: statValueStyle }, touchedFieldsCount)),
174
+ React.createElement("div", { style: statCardStyle },
175
+ React.createElement("div", { style: statLabelStyle }, "Submit Count"),
176
+ React.createElement("div", { style: statValueStyle }, formState.submitCount || 0)),
177
+ React.createElement("div", { style: statCardStyle },
178
+ React.createElement("div", { style: statLabelStyle }, "Submitting"),
179
+ React.createElement("div", { style: statValueStyle }, formState.isSubmitting ? 'Yes' : 'No'))),
180
+ React.createElement("div", { style: sectionTitleStyle }, "Form Values"),
181
+ React.createElement("pre", { style: codeBlockStyle }, JSON.stringify(values || {}, null, 2)),
182
+ errorCount > 0 && (React.createElement(React.Fragment, null,
183
+ React.createElement("div", { style: sectionTitleStyle },
184
+ "Validation Errors (",
185
+ errorCount,
186
+ ")"),
197
187
  renderErrors())),
198
- activeTab === 'state' && (React.createElement(React.Fragment, null,
199
- React.createElement("div", { style: statsContainerStyle },
200
- React.createElement("div", { style: statCardStyle },
201
- React.createElement("div", { style: statLabelStyle }, "Dirty Fields"),
202
- React.createElement("div", { style: statValueStyle }, dirtyFieldsCount)),
203
- React.createElement("div", { style: statCardStyle },
204
- React.createElement("div", { style: statLabelStyle }, "Touched Fields"),
205
- React.createElement("div", { style: statValueStyle }, touchedFieldsCount)),
206
- React.createElement("div", { style: statCardStyle },
207
- React.createElement("div", { style: statLabelStyle }, "Submit Count"),
208
- React.createElement("div", { style: statValueStyle }, formState.submitCount || 0)),
209
- React.createElement("div", { style: statCardStyle },
210
- React.createElement("div", { style: statLabelStyle }, "Submitting"),
211
- React.createElement("div", { style: statValueStyle }, formState.isSubmitting ? 'Yes' : 'No'))),
188
+ dirtyFieldsCount > 0 && (React.createElement(React.Fragment, null,
212
189
  React.createElement("div", { style: sectionTitleStyle }, "Dirty Fields"),
213
- React.createElement("pre", { style: codeBlockStyle }, JSON.stringify(formState.dirtyFields || {}, null, 2)),
190
+ React.createElement("pre", { style: codeBlockStyle }, JSON.stringify(formState.dirtyFields || {}, null, 2)))),
191
+ touchedFieldsCount > 0 && (React.createElement(React.Fragment, null,
214
192
  React.createElement("div", { style: sectionTitleStyle }, "Touched Fields"),
215
193
  React.createElement("pre", { style: codeBlockStyle }, JSON.stringify(formState.touchedFields || {}, null, 2))))),
216
194
  React.createElement("div", { onMouseDown: handleResizeMouseDown, style: resizeHandleStyle }),
@@ -28,8 +28,6 @@ export declare const headerStyle: CSSProperties;
28
28
  export declare const headerTitleStyle: CSSProperties;
29
29
  export declare const getStatusBadgeStyle: (isValid: boolean | undefined) => CSSProperties;
30
30
  export declare const getCopyButtonStyle: (isCopied: boolean) => CSSProperties;
31
- export declare const tabContainerStyle: CSSProperties;
32
- export declare const getTabStyle: (isActive: boolean) => CSSProperties;
33
31
  export declare const contentStyle: CSSProperties;
34
32
  export declare const sectionTitleStyle: CSSProperties;
35
33
  export declare const codeBlockStyle: CSSProperties;
@@ -1 +1 @@
1
- {"version":3,"file":"styles.d.ts","sourceRoot":"","sources":["../../../../src/components/dev/FormDevTools/styles.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,OAAO,CAAC;AAEtC,MAAM,MAAM,UAAU,GAAG;IACvB,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,QAAQ,EAAE,OAAO,CAAC;IAClB,QAAQ,EAAE,OAAO,CAAC;IAClB,UAAU,EAAE,OAAO,CAAC;IACpB,QAAQ,EAAE,MAAM,CAAC;IACjB,aAAa,EAAE;QAAE,CAAC,EAAE,MAAM,CAAC;QAAC,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;IACxC,SAAS,EAAE;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,CAAC;CAC9C,CAAC;AAEF,eAAO,MAAM,iBAAiB,QAAO,MAAM,CAAC,MAAM,EAAE,aAAa,CAK/D,CAAC;AAEH,eAAO,MAAM,iBAAiB,GAAI,UAAU,MAAM,KAAG,aAQpD,CAAC;AAEF,eAAO,MAAM,oBAAoB,GAAI,SAAS,OAAO,GAAG,SAAS,KAAG,aAclE,CAAC;AAEH,eAAO,MAAM,aAAa,GACxB,UAAU,MAAM,EAChB,eAAe;IAAE,CAAC,EAAE,MAAM,CAAC;IAAC,CAAC,EAAE,MAAM,CAAA;CAAE,EACvC,WAAW;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,EAC5C,YAAY,OAAO,KAClB,aAgBD,CAAC;AAEH,eAAO,MAAM,WAAW,EAAE,aASzB,CAAC;AAEF,eAAO,MAAM,gBAAgB,EAAE,aAI9B,CAAC;AAEF,eAAO,MAAM,mBAAmB,GAAI,SAAS,OAAO,GAAG,SAAS,KAAG,aAUjE,CAAC;AAEH,eAAO,MAAM,kBAAkB,GAAI,UAAU,OAAO,KAAG,aAUrD,CAAC;AAEH,eAAO,MAAM,iBAAiB,EAAE,aAI/B,CAAC;AAEF,eAAO,MAAM,WAAW,GAAI,UAAU,OAAO,KAAG,aAW9C,CAAC;AAEH,eAAO,MAAM,YAAY,EAAE,aAI1B,CAAC;AAEF,eAAO,MAAM,iBAAiB,EAAE,aAO/B,CAAC;AAEF,eAAO,MAAM,cAAc,EAAE,aAU5B,CAAC;AAEF,eAAO,MAAM,cAAc,EAAE,aAM5B,CAAC;AAEF,eAAO,MAAM,eAAe,EAAE,aAK7B,CAAC;AAEF,eAAO,MAAM,iBAAiB,EAAE,aAG/B,CAAC;AAEF,eAAO,MAAM,mBAAmB,EAAE,aAKjC,CAAC;AAEF,eAAO,MAAM,aAAa,EAAE,aAK3B,CAAC;AAEF,eAAO,MAAM,cAAc,EAAE,aAM5B,CAAC;AAEF,eAAO,MAAM,cAAc,EAAE,aAI5B,CAAC;AAEF,eAAO,MAAM,iBAAiB,EAAE,aAS/B,CAAC;AAEF,eAAO,MAAM,0BAA0B,EAAE,aAUxC,CAAC"}
1
+ {"version":3,"file":"styles.d.ts","sourceRoot":"","sources":["../../../../src/components/dev/FormDevTools/styles.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,OAAO,CAAC;AAEtC,MAAM,MAAM,UAAU,GAAG;IACvB,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,QAAQ,EAAE,OAAO,CAAC;IAClB,QAAQ,EAAE,OAAO,CAAC;IAClB,UAAU,EAAE,OAAO,CAAC;IACpB,QAAQ,EAAE,MAAM,CAAC;IACjB,aAAa,EAAE;QAAE,CAAC,EAAE,MAAM,CAAC;QAAC,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;IACxC,SAAS,EAAE;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,CAAC;CAC9C,CAAC;AAEF,eAAO,MAAM,iBAAiB,QAAO,MAAM,CAAC,MAAM,EAAE,aAAa,CAK/D,CAAC;AAEH,eAAO,MAAM,iBAAiB,GAAI,UAAU,MAAM,KAAG,aAQpD,CAAC;AAEF,eAAO,MAAM,oBAAoB,GAAI,SAAS,OAAO,GAAG,SAAS,KAAG,aAclE,CAAC;AAEH,eAAO,MAAM,aAAa,GACxB,UAAU,MAAM,EAChB,eAAe;IAAE,CAAC,EAAE,MAAM,CAAC;IAAC,CAAC,EAAE,MAAM,CAAA;CAAE,EACvC,WAAW;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,EAC5C,YAAY,OAAO,KAClB,aAoBF,CAAC;AAEF,eAAO,MAAM,WAAW,EAAE,aASzB,CAAC;AAEF,eAAO,MAAM,gBAAgB,EAAE,aAI9B,CAAC;AAEF,eAAO,MAAM,mBAAmB,GAAI,SAAS,OAAO,GAAG,SAAS,KAAG,aAUjE,CAAC;AAEH,eAAO,MAAM,kBAAkB,GAAI,UAAU,OAAO,KAAG,aAUrD,CAAC;AAEH,eAAO,MAAM,YAAY,EAAE,aAM1B,CAAC;AAEF,eAAO,MAAM,iBAAiB,EAAE,aAO/B,CAAC;AAEF,eAAO,MAAM,cAAc,EAAE,aAU5B,CAAC;AAEF,eAAO,MAAM,cAAc,EAAE,aAM5B,CAAC;AAEF,eAAO,MAAM,eAAe,EAAE,aAK7B,CAAC;AAEF,eAAO,MAAM,iBAAiB,EAAE,aAG/B,CAAC;AAEF,eAAO,MAAM,mBAAmB,EAAE,aAKjC,CAAC;AAEF,eAAO,MAAM,aAAa,EAAE,aAK3B,CAAC;AAEF,eAAO,MAAM,cAAc,EAAE,aAM5B,CAAC;AAEF,eAAO,MAAM,cAAc,EAAE,aAI5B,CAAC;AAEF,eAAO,MAAM,iBAAiB,EAAE,aAS/B,CAAC;AAEF,eAAO,MAAM,0BAA0B,EAAE,aAUxC,CAAC"}
@@ -28,23 +28,27 @@ export const getToggleButtonStyle = (isValid) => ({
28
28
  justifyContent: 'center',
29
29
  transition: 'background-color 0.2s',
30
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
- });
31
+ export const getPanelStyle = (position, panelPosition, panelSize, isDragging) => {
32
+ const maxHeight = typeof window !== 'undefined' ? window.innerHeight * 0.85 : 600;
33
+ return {
34
+ position: 'fixed',
35
+ top: panelPosition.y || (position.includes('top') ? 60 : undefined),
36
+ bottom: !panelPosition.y && position.includes('bottom') ? 60 : undefined,
37
+ left: panelPosition.x || (position.includes('left') ? 0 : undefined),
38
+ right: !panelPosition.x && position.includes('right') ? 0 : undefined,
39
+ backgroundColor: 'white',
40
+ borderRadius: '12px',
41
+ boxShadow: '0 10px 25px rgba(0, 0, 0, 0.15)',
42
+ border: '1px solid #e5e7eb',
43
+ width: `${panelSize.width}px`,
44
+ height: `${Math.min(panelSize.height, maxHeight)}px`,
45
+ maxHeight: `${maxHeight}px`,
46
+ overflow: 'hidden',
47
+ display: 'flex',
48
+ flexDirection: 'column',
49
+ cursor: isDragging ? 'grabbing' : 'default',
50
+ };
51
+ };
48
52
  export const headerStyle = {
49
53
  padding: '12px 16px',
50
54
  borderBottom: '1px solid #e5e7eb',
@@ -82,26 +86,11 @@ export const getCopyButtonStyle = (isCopied) => ({
82
86
  fontWeight: 600,
83
87
  transition: 'background-color 0.2s',
84
88
  });
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
89
  export const contentStyle = {
103
90
  flex: 1,
91
+ minHeight: 0, // flexbox에서 스크롤이 제대로 작동하도록 필수
104
92
  overflowY: 'auto',
93
+ overflowX: 'hidden',
105
94
  padding: '16px',
106
95
  };
107
96
  export const sectionTitleStyle = {
@@ -119,7 +108,7 @@ export const codeBlockStyle = {
119
108
  fontSize: '12px',
120
109
  fontFamily: 'monospace',
121
110
  overflow: 'auto',
122
- maxHeight: '400px',
111
+ maxHeight: '300px',
123
112
  marginBottom: '16px',
124
113
  color: '#111827', // 검은색 텍스트
125
114
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "goodchuck-utils",
3
- "version": "1.6.0",
3
+ "version": "1.7.0",
4
4
  "type": "module",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -9,9 +9,10 @@
9
9
  ],
10
10
  "scripts": {
11
11
  "prepare": "npm run build:tsc",
12
- "prepublishOnly": "npm run test:run && npm run build:tsc",
12
+ "prepublishOnly": "npm run test:run && npm run clean && npm run build:tsc",
13
13
  "build": "npm run build",
14
14
  "build:tsc": "tsc",
15
+ "clean": "rm -rf dist",
15
16
  "dev": "tsc --watch",
16
17
  "test": "vitest",
17
18
  "test:ui": "vitest --ui",
@@ -1,76 +0,0 @@
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
@@ -1 +0,0 @@
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"}
@@ -1,399 +0,0 @@
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
- }