goodchuck-utils 1.7.1 → 1.9.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.
- package/dist/components/dev/FormDevTools/FormDevTools.d.ts +88 -24
- package/dist/components/dev/FormDevTools/FormDevTools.d.ts.map +1 -1
- package/dist/components/dev/FormDevTools/FormDevTools.js +297 -56
- package/dist/components/dev/FormDevTools/styles.d.ts +2 -0
- package/dist/components/dev/FormDevTools/styles.d.ts.map +1 -1
- package/dist/components/dev/FormDevTools/styles.js +21 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -0
- package/dist/mock/form.d.ts +41 -0
- package/dist/mock/form.d.ts.map +1 -0
- package/dist/mock/form.js +195 -0
- package/dist/mock/generators.d.ts +112 -0
- package/dist/mock/generators.d.ts.map +1 -0
- package/dist/mock/generators.js +195 -0
- package/dist/mock/index.d.ts +8 -0
- package/dist/mock/index.d.ts.map +1 -0
- package/dist/mock/index.js +9 -0
- package/dist/number/format.d.ts +116 -0
- package/dist/number/format.d.ts.map +1 -0
- package/dist/number/format.js +165 -0
- package/dist/number/index.d.ts +7 -0
- package/dist/number/index.d.ts.map +1 -0
- package/dist/number/index.js +7 -0
- package/package.json +1 -1
|
@@ -13,6 +13,28 @@ export type Props = {
|
|
|
13
13
|
formState: FormState;
|
|
14
14
|
/** 현재 폼 values (watch() 결과) */
|
|
15
15
|
values?: Record<string, any>;
|
|
16
|
+
/** 초기값 (defaultValues 또는 reset()으로 설정한 원본 값, changedFields 계산에 사용) */
|
|
17
|
+
originalValues?: Record<string, any>;
|
|
18
|
+
/** Validation 스키마 정보 (zod, yup 등) - refine 조건 표시에 사용 */
|
|
19
|
+
validationSchema?: Record<string, any>;
|
|
20
|
+
/** react-hook-form의 reset 함수 (mock 데이터 생성 후 폼에 채우기 위해 필요) */
|
|
21
|
+
reset?: (values: Record<string, any>, options?: {
|
|
22
|
+
keepDirty?: boolean;
|
|
23
|
+
keepTouched?: boolean;
|
|
24
|
+
}) => void;
|
|
25
|
+
/** react-hook-form의 setValue 함수 (dirty 상태를 true로 만들기 위해 필요, 선택사항) */
|
|
26
|
+
setValue?: (name: string, value: any, options?: {
|
|
27
|
+
shouldDirty?: boolean;
|
|
28
|
+
shouldValidate?: boolean;
|
|
29
|
+
}) => void;
|
|
30
|
+
/** react-hook-form의 trigger 함수 (validation 실행을 위해 필요, 선택사항) */
|
|
31
|
+
trigger?: (name?: string | string[]) => Promise<boolean>;
|
|
32
|
+
/** Mock 데이터 생성 함수 (비동기 가능) */
|
|
33
|
+
generateMock?: (params: {
|
|
34
|
+
values?: Record<string, any>;
|
|
35
|
+
originalValues?: Record<string, any>;
|
|
36
|
+
validationSchema?: Record<string, any>;
|
|
37
|
+
}) => Promise<Record<string, any>> | Record<string, any>;
|
|
16
38
|
/** 표시 위치 (기본값: 'bottom-left') */
|
|
17
39
|
position?: 'top-left' | 'top-right' | 'bottom-left' | 'bottom-right';
|
|
18
40
|
/** 패널 제목 (기본값: 'Form DevTools') */
|
|
@@ -29,29 +51,51 @@ export type Props = {
|
|
|
29
51
|
* import { FormDevTools } from 'goodchuck-utils/components/dev';
|
|
30
52
|
*
|
|
31
53
|
* function MyForm() {
|
|
54
|
+
* // 신규 폼: defaultValues 사용
|
|
55
|
+
* const defaultValues = {
|
|
56
|
+
* username: '',
|
|
57
|
+
* email: '',
|
|
58
|
+
* age: 0,
|
|
59
|
+
* };
|
|
60
|
+
*
|
|
32
61
|
* const { register, handleSubmit, formState, watch } = useForm({
|
|
33
|
-
* defaultValues
|
|
34
|
-
* username: '',
|
|
35
|
-
* email: '',
|
|
36
|
-
* age: 0,
|
|
37
|
-
* }
|
|
62
|
+
* defaultValues,
|
|
38
63
|
* });
|
|
39
64
|
*
|
|
40
|
-
* const values = watch();
|
|
41
|
-
*
|
|
42
|
-
* const onSubmit = (data) => {
|
|
43
|
-
* console.log(data);
|
|
44
|
-
* };
|
|
65
|
+
* const values = watch();
|
|
45
66
|
*
|
|
46
67
|
* return (
|
|
47
68
|
* <form onSubmit={handleSubmit(onSubmit)}>
|
|
48
|
-
* <input {...register('username'
|
|
49
|
-
* <input {...register('email'
|
|
50
|
-
* <input type="number" {...register('age', { min: 18 })} />
|
|
69
|
+
* <input {...register('username')} />
|
|
70
|
+
* <input {...register('email')} />
|
|
51
71
|
* <button type="submit">Submit</button>
|
|
52
72
|
*
|
|
53
73
|
* {import.meta.env.DEV && (
|
|
54
|
-
* <FormDevTools
|
|
74
|
+
* <FormDevTools
|
|
75
|
+
* formState={formState}
|
|
76
|
+
* values={values}
|
|
77
|
+
* originalValues={defaultValues}
|
|
78
|
+
* reset={reset} // mock 데이터 생성 후 폼에 채우기 위해 필요
|
|
79
|
+
* generateMock={async ({ values, validationSchema }) => {
|
|
80
|
+
* // AI를 사용한 mock 데이터 생성 (예시)
|
|
81
|
+
* const response = await fetch('https://api.openai.com/v1/chat/completions', {
|
|
82
|
+
* method: 'POST',
|
|
83
|
+
* headers: {
|
|
84
|
+
* 'Content-Type': 'application/json',
|
|
85
|
+
* 'Authorization': `Bearer ${process.env.OPENAI_API_KEY}`,
|
|
86
|
+
* },
|
|
87
|
+
* body: JSON.stringify({
|
|
88
|
+
* model: 'gpt-3.5-turbo',
|
|
89
|
+
* messages: [{
|
|
90
|
+
* role: 'user',
|
|
91
|
+
* content: `다음 폼 필드에 맞는 mock 데이터를 생성해주세요: ${JSON.stringify(values)}`
|
|
92
|
+
* }],
|
|
93
|
+
* }),
|
|
94
|
+
* });
|
|
95
|
+
* const data = await response.json();
|
|
96
|
+
* return JSON.parse(data.choices[0].message.content);
|
|
97
|
+
* }}
|
|
98
|
+
* />
|
|
55
99
|
* )}
|
|
56
100
|
* </form>
|
|
57
101
|
* );
|
|
@@ -60,16 +104,36 @@ export type Props = {
|
|
|
60
104
|
*
|
|
61
105
|
* @example
|
|
62
106
|
* ```tsx
|
|
63
|
-
* //
|
|
64
|
-
* {
|
|
65
|
-
*
|
|
66
|
-
*
|
|
67
|
-
*
|
|
68
|
-
*
|
|
69
|
-
*
|
|
70
|
-
*
|
|
71
|
-
*
|
|
107
|
+
* // 수정 폼: reset()으로 초기값 설정
|
|
108
|
+
* function EditForm({ userData }) {
|
|
109
|
+
* const { register, handleSubmit, formState, watch, reset } = useForm();
|
|
110
|
+
*
|
|
111
|
+
* useEffect(() => {
|
|
112
|
+
* // 서버에서 가져온 데이터로 폼 초기화
|
|
113
|
+
* reset(userData);
|
|
114
|
+
* }, [userData, reset]);
|
|
115
|
+
*
|
|
116
|
+
* const values = watch();
|
|
117
|
+
*
|
|
118
|
+
* return (
|
|
119
|
+
* <form onSubmit={handleSubmit(onSubmit)}>
|
|
120
|
+
* <input {...register('username')} />
|
|
121
|
+
* <input {...register('email')} />
|
|
122
|
+
* <button type="submit">Update</button>
|
|
123
|
+
*
|
|
124
|
+
* {process.env.NODE_ENV === 'development' && (
|
|
125
|
+
* <FormDevTools
|
|
126
|
+
* formState={formState}
|
|
127
|
+
* values={values}
|
|
128
|
+
* originalValues={userData} // reset()으로 설정한 원본 값
|
|
129
|
+
* position="top-right"
|
|
130
|
+
* title="Edit Form Debug"
|
|
131
|
+
* />
|
|
132
|
+
* )}
|
|
133
|
+
* </form>
|
|
134
|
+
* );
|
|
135
|
+
* }
|
|
72
136
|
* ```
|
|
73
137
|
*/
|
|
74
|
-
export default function FormDevTools({ formState, values, position, title }: Props): React.JSX.Element;
|
|
138
|
+
export default function FormDevTools({ formState, values, originalValues, validationSchema, reset, setValue, trigger, generateMock, position, title }: Props): React.JSX.Element;
|
|
75
139
|
//# sourceMappingURL=FormDevTools.d.ts.map
|
|
@@ -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;
|
|
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,sEAAsE;IACtE,cAAc,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;IACrC,wDAAwD;IACxD,gBAAgB,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;IACvC,6DAA6D;IAC7D,KAAK,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,EAAE,OAAO,CAAC,EAAE;QAAE,SAAS,CAAC,EAAE,OAAO,CAAC;QAAC,WAAW,CAAC,EAAE,OAAO,CAAA;KAAE,KAAK,IAAI,CAAC;IACxG,qEAAqE;IACrE,QAAQ,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,GAAG,EAAE,OAAO,CAAC,EAAE;QAAE,WAAW,CAAC,EAAE,OAAO,CAAC;QAAC,cAAc,CAAC,EAAE,OAAO,CAAA;KAAE,KAAK,IAAI,CAAC;IAC7G,+DAA+D;IAC/D,OAAO,CAAC,EAAE,CAAC,IAAI,CAAC,EAAE,MAAM,GAAG,MAAM,EAAE,KAAK,OAAO,CAAC,OAAO,CAAC,CAAC;IACzD,8BAA8B;IAC9B,YAAY,CAAC,EAAE,CAAC,MAAM,EAAE;QACtB,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;QAC7B,cAAc,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;QACrC,gBAAgB,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;KACxC,KAAK,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;IACzD,iCAAiC;IACjC,QAAQ,CAAC,EAAE,UAAU,GAAG,WAAW,GAAG,aAAa,GAAG,cAAc,CAAC;IACrE,mCAAmC;IACnC,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB,CAAC;AAEF;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8FG;AACH,MAAM,CAAC,OAAO,UAAU,YAAY,CAAC,EAAE,SAAS,EAAE,MAAM,EAAE,cAAc,EAAE,gBAAgB,EAAE,KAAK,EAAE,QAAQ,EAAE,OAAO,EAAE,YAAY,EAAE,QAAwB,EAAE,KAAuB,EAAE,EAAE,KAAK,qBAggB7L"}
|
|
@@ -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, contentStyle, sectionTitleStyle, codeBlockStyle, errorItemStyle, errorLabelStyle, errorMessageStyle, statsContainerStyle, statCardStyle, statLabelStyle, statValueStyle, resizeHandleStyle, resizeHandleIndicatorStyle, } from './styles';
|
|
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';
|
|
4
4
|
/**
|
|
5
5
|
* react-hook-form의 상태를 실시간으로 시각화하는 개발용 컴포넌트
|
|
6
6
|
* form의 values, errors, dirtyFields, touchedFields 등을 한눈에 확인할 수 있습니다.
|
|
@@ -12,29 +12,51 @@ import { getContainerStyle, getToggleButtonStyle, getPanelStyle, headerStyle, he
|
|
|
12
12
|
* import { FormDevTools } from 'goodchuck-utils/components/dev';
|
|
13
13
|
*
|
|
14
14
|
* function MyForm() {
|
|
15
|
+
* // 신규 폼: defaultValues 사용
|
|
16
|
+
* const defaultValues = {
|
|
17
|
+
* username: '',
|
|
18
|
+
* email: '',
|
|
19
|
+
* age: 0,
|
|
20
|
+
* };
|
|
21
|
+
*
|
|
15
22
|
* const { register, handleSubmit, formState, watch } = useForm({
|
|
16
|
-
* defaultValues
|
|
17
|
-
* username: '',
|
|
18
|
-
* email: '',
|
|
19
|
-
* age: 0,
|
|
20
|
-
* }
|
|
23
|
+
* defaultValues,
|
|
21
24
|
* });
|
|
22
25
|
*
|
|
23
|
-
* const values = watch();
|
|
24
|
-
*
|
|
25
|
-
* const onSubmit = (data) => {
|
|
26
|
-
* console.log(data);
|
|
27
|
-
* };
|
|
26
|
+
* const values = watch();
|
|
28
27
|
*
|
|
29
28
|
* return (
|
|
30
29
|
* <form onSubmit={handleSubmit(onSubmit)}>
|
|
31
|
-
* <input {...register('username'
|
|
32
|
-
* <input {...register('email'
|
|
33
|
-
* <input type="number" {...register('age', { min: 18 })} />
|
|
30
|
+
* <input {...register('username')} />
|
|
31
|
+
* <input {...register('email')} />
|
|
34
32
|
* <button type="submit">Submit</button>
|
|
35
33
|
*
|
|
36
34
|
* {import.meta.env.DEV && (
|
|
37
|
-
* <FormDevTools
|
|
35
|
+
* <FormDevTools
|
|
36
|
+
* formState={formState}
|
|
37
|
+
* values={values}
|
|
38
|
+
* originalValues={defaultValues}
|
|
39
|
+
* reset={reset} // mock 데이터 생성 후 폼에 채우기 위해 필요
|
|
40
|
+
* generateMock={async ({ values, validationSchema }) => {
|
|
41
|
+
* // AI를 사용한 mock 데이터 생성 (예시)
|
|
42
|
+
* const response = await fetch('https://api.openai.com/v1/chat/completions', {
|
|
43
|
+
* method: 'POST',
|
|
44
|
+
* headers: {
|
|
45
|
+
* 'Content-Type': 'application/json',
|
|
46
|
+
* 'Authorization': `Bearer ${process.env.OPENAI_API_KEY}`,
|
|
47
|
+
* },
|
|
48
|
+
* body: JSON.stringify({
|
|
49
|
+
* model: 'gpt-3.5-turbo',
|
|
50
|
+
* messages: [{
|
|
51
|
+
* role: 'user',
|
|
52
|
+
* content: `다음 폼 필드에 맞는 mock 데이터를 생성해주세요: ${JSON.stringify(values)}`
|
|
53
|
+
* }],
|
|
54
|
+
* }),
|
|
55
|
+
* });
|
|
56
|
+
* const data = await response.json();
|
|
57
|
+
* return JSON.parse(data.choices[0].message.content);
|
|
58
|
+
* }}
|
|
59
|
+
* />
|
|
38
60
|
* )}
|
|
39
61
|
* </form>
|
|
40
62
|
* );
|
|
@@ -43,31 +65,55 @@ import { getContainerStyle, getToggleButtonStyle, getPanelStyle, headerStyle, he
|
|
|
43
65
|
*
|
|
44
66
|
* @example
|
|
45
67
|
* ```tsx
|
|
46
|
-
* //
|
|
47
|
-
* {
|
|
48
|
-
*
|
|
49
|
-
*
|
|
50
|
-
*
|
|
51
|
-
*
|
|
52
|
-
*
|
|
53
|
-
*
|
|
54
|
-
*
|
|
68
|
+
* // 수정 폼: reset()으로 초기값 설정
|
|
69
|
+
* function EditForm({ userData }) {
|
|
70
|
+
* const { register, handleSubmit, formState, watch, reset } = useForm();
|
|
71
|
+
*
|
|
72
|
+
* useEffect(() => {
|
|
73
|
+
* // 서버에서 가져온 데이터로 폼 초기화
|
|
74
|
+
* reset(userData);
|
|
75
|
+
* }, [userData, reset]);
|
|
76
|
+
*
|
|
77
|
+
* const values = watch();
|
|
78
|
+
*
|
|
79
|
+
* return (
|
|
80
|
+
* <form onSubmit={handleSubmit(onSubmit)}>
|
|
81
|
+
* <input {...register('username')} />
|
|
82
|
+
* <input {...register('email')} />
|
|
83
|
+
* <button type="submit">Update</button>
|
|
84
|
+
*
|
|
85
|
+
* {process.env.NODE_ENV === 'development' && (
|
|
86
|
+
* <FormDevTools
|
|
87
|
+
* formState={formState}
|
|
88
|
+
* values={values}
|
|
89
|
+
* originalValues={userData} // reset()으로 설정한 원본 값
|
|
90
|
+
* position="top-right"
|
|
91
|
+
* title="Edit Form Debug"
|
|
92
|
+
* />
|
|
93
|
+
* )}
|
|
94
|
+
* </form>
|
|
95
|
+
* );
|
|
96
|
+
* }
|
|
55
97
|
* ```
|
|
56
98
|
*/
|
|
57
|
-
export default function FormDevTools({ formState, values, position = 'bottom-left', title = 'Form DevTools' }) {
|
|
99
|
+
export default function FormDevTools({ formState, values, originalValues, validationSchema, reset, setValue, trigger, generateMock, position = 'bottom-left', title = 'Form DevTools' }) {
|
|
58
100
|
const [isOpen, setIsOpen] = useState(false);
|
|
101
|
+
const [activeTab, setActiveTab] = useState('all');
|
|
59
102
|
const [panelPosition, setPanelPosition] = useState({ x: 0, y: 0 });
|
|
60
103
|
const [panelSize, setPanelSize] = useState({ width: 500, height: 400 });
|
|
61
104
|
const [isDragging, setIsDragging] = useState(false);
|
|
62
105
|
const [isResizing, setIsResizing] = useState(false);
|
|
63
106
|
const [dragStart, setDragStart] = useState({ x: 0, y: 0 });
|
|
64
107
|
const [resizeStart, setResizeStart] = useState({ x: 0, y: 0, width: 0, height: 0 });
|
|
108
|
+
const [isGenerating, setIsGenerating] = useState(false);
|
|
109
|
+
const [generateError, setGenerateError] = useState(null);
|
|
65
110
|
const panelRef = useRef(null);
|
|
66
111
|
const { copy, copiedText } = useCopyToClipboard();
|
|
67
112
|
const handleCopy = () => {
|
|
68
113
|
const data = {
|
|
69
114
|
values,
|
|
70
115
|
errors: formState.errors,
|
|
116
|
+
changedFields,
|
|
71
117
|
dirtyFields: formState.dirtyFields,
|
|
72
118
|
touchedFields: formState.touchedFields,
|
|
73
119
|
isValid: formState.isValid,
|
|
@@ -80,6 +126,40 @@ export default function FormDevTools({ formState, values, position = 'bottom-lef
|
|
|
80
126
|
const errorCount = Object.keys(formState.errors || {}).length;
|
|
81
127
|
const dirtyFieldsCount = Object.keys(formState.dirtyFields || {}).length;
|
|
82
128
|
const touchedFieldsCount = Object.keys(formState.touchedFields || {}).length;
|
|
129
|
+
// Changed Fields 계산: dirtyFields를 기반으로 실제 변경된 값들을 추출
|
|
130
|
+
const getChangedFields = () => {
|
|
131
|
+
if (!formState.dirtyFields || !values)
|
|
132
|
+
return {};
|
|
133
|
+
const changed = {};
|
|
134
|
+
const getNestedValue = (obj, path) => {
|
|
135
|
+
return path.split('.').reduce((acc, key) => acc?.[key], obj);
|
|
136
|
+
};
|
|
137
|
+
const processDirtyFields = (dirty, prefix = '') => {
|
|
138
|
+
Object.keys(dirty).forEach((key) => {
|
|
139
|
+
const fullPath = prefix ? `${prefix}.${key}` : key;
|
|
140
|
+
const dirtyValue = dirty[key];
|
|
141
|
+
if (dirtyValue === true) {
|
|
142
|
+
// 최종 필드인 경우
|
|
143
|
+
const currentValue = getNestedValue(values, fullPath);
|
|
144
|
+
const originalValue = originalValues ? getNestedValue(originalValues, fullPath) : undefined;
|
|
145
|
+
if (JSON.stringify(currentValue) !== JSON.stringify(originalValue)) {
|
|
146
|
+
changed[fullPath] = {
|
|
147
|
+
from: originalValue,
|
|
148
|
+
to: currentValue,
|
|
149
|
+
};
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
else if (typeof dirtyValue === 'object' && dirtyValue !== null) {
|
|
153
|
+
// 중첩된 객체인 경우 재귀적으로 처리
|
|
154
|
+
processDirtyFields(dirtyValue, fullPath);
|
|
155
|
+
}
|
|
156
|
+
});
|
|
157
|
+
};
|
|
158
|
+
processDirtyFields(formState.dirtyFields);
|
|
159
|
+
return changed;
|
|
160
|
+
};
|
|
161
|
+
const changedFields = getChangedFields();
|
|
162
|
+
const changedFieldsCount = Object.keys(changedFields).length;
|
|
83
163
|
// 드래그 핸들러
|
|
84
164
|
useEffect(() => {
|
|
85
165
|
const handleMouseMove = (e) => {
|
|
@@ -147,6 +227,52 @@ export default function FormDevTools({ formState, values, position = 'bottom-lef
|
|
|
147
227
|
React.createElement("div", { style: errorLabelStyle }, field),
|
|
148
228
|
React.createElement("div", { style: errorMessageStyle }, error?.message || 'Invalid value'))));
|
|
149
229
|
};
|
|
230
|
+
// Mock 데이터 생성 핸들러
|
|
231
|
+
const handleGenerateMock = async () => {
|
|
232
|
+
if (!reset) {
|
|
233
|
+
setGenerateError('reset 함수가 필요합니다');
|
|
234
|
+
return;
|
|
235
|
+
}
|
|
236
|
+
if (!generateMock) {
|
|
237
|
+
setGenerateError('generateMock 함수가 필요합니다');
|
|
238
|
+
return;
|
|
239
|
+
}
|
|
240
|
+
setIsGenerating(true);
|
|
241
|
+
setGenerateError(null);
|
|
242
|
+
try {
|
|
243
|
+
// 사용자가 제공한 generateMock 함수 사용
|
|
244
|
+
const mockData = await generateMock({
|
|
245
|
+
values,
|
|
246
|
+
originalValues,
|
|
247
|
+
validationSchema,
|
|
248
|
+
});
|
|
249
|
+
// reset으로 값 설정
|
|
250
|
+
// keepDirty: false는 reset 후 dirty를 초기화하는데,
|
|
251
|
+
// 이후 setValue로 shouldDirty: true를 설정하면 dirty가 true가 됨
|
|
252
|
+
reset(mockData, { keepDirty: false, keepTouched: false });
|
|
253
|
+
// dirty와 validate를 true로 만들기 위해 setValue 사용
|
|
254
|
+
// reset 후에 setValue를 호출하면 dirty 상태가 true가 됨
|
|
255
|
+
if (setValue) {
|
|
256
|
+
// 모든 필드를 dirty로 만들고 validation 실행
|
|
257
|
+
Object.keys(mockData).forEach((key) => {
|
|
258
|
+
setValue(key, mockData[key], {
|
|
259
|
+
shouldDirty: true, // dirty를 true로 설정
|
|
260
|
+
shouldValidate: true, // validation 실행
|
|
261
|
+
});
|
|
262
|
+
});
|
|
263
|
+
}
|
|
264
|
+
// 전체 validation 실행
|
|
265
|
+
if (trigger) {
|
|
266
|
+
await trigger();
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
catch (error) {
|
|
270
|
+
setGenerateError(error.message || 'Mock 데이터 생성 실패');
|
|
271
|
+
}
|
|
272
|
+
finally {
|
|
273
|
+
setIsGenerating(false);
|
|
274
|
+
}
|
|
275
|
+
};
|
|
150
276
|
return (React.createElement("div", { style: getContainerStyle(position) },
|
|
151
277
|
React.createElement("button", { onClick: () => setIsOpen(!isOpen), style: getToggleButtonStyle(formState.isValid), onMouseEnter: (e) => (e.currentTarget.style.backgroundColor = formState.isValid === false ? '#dc2626' : '#7c3aed'), onMouseLeave: (e) => (e.currentTarget.style.backgroundColor = formState.isValid === false ? '#ef4444' : '#8b5cf6') }, isOpen ? '✕' : '📝'),
|
|
152
278
|
isOpen && (React.createElement("div", { ref: panelRef, style: getPanelStyle(position, panelPosition, panelSize, isDragging) },
|
|
@@ -156,41 +282,156 @@ export default function FormDevTools({ formState, values, position = 'bottom-lef
|
|
|
156
282
|
"\uD83D\uDCDD ",
|
|
157
283
|
title),
|
|
158
284
|
React.createElement("div", { style: getStatusBadgeStyle(formState.isValid) }, formState.isValid ? '✓ Valid' : `✗ ${errorCount} Error${errorCount > 1 ? 's' : ''}`)),
|
|
159
|
-
React.createElement("
|
|
160
|
-
|
|
161
|
-
|
|
285
|
+
React.createElement("div", { style: { display: 'flex', gap: '8px', alignItems: 'center' } },
|
|
286
|
+
reset && generateMock && (React.createElement("button", { onClick: handleGenerateMock, disabled: isGenerating, style: {
|
|
287
|
+
...getCopyButtonStyle(false),
|
|
288
|
+
backgroundColor: isGenerating ? '#9ca3af' : '#10b981',
|
|
289
|
+
opacity: isGenerating ? 0.6 : 1,
|
|
290
|
+
cursor: isGenerating ? 'not-allowed' : 'pointer',
|
|
291
|
+
}, onMouseEnter: (e) => {
|
|
292
|
+
if (!isGenerating)
|
|
293
|
+
e.currentTarget.style.backgroundColor = '#059669';
|
|
294
|
+
}, onMouseLeave: (e) => {
|
|
295
|
+
if (!isGenerating)
|
|
296
|
+
e.currentTarget.style.backgroundColor = '#10b981';
|
|
297
|
+
} }, isGenerating ? '⏳ Generating...' : '🤖 Generate Mock')),
|
|
298
|
+
React.createElement("button", { onClick: handleCopy, style: getCopyButtonStyle(isCopied), onMouseEnter: (e) => {
|
|
299
|
+
if (!isCopied)
|
|
300
|
+
e.currentTarget.style.backgroundColor = '#2563eb';
|
|
301
|
+
}, onMouseLeave: (e) => {
|
|
302
|
+
if (!isCopied)
|
|
303
|
+
e.currentTarget.style.backgroundColor = '#3b82f6';
|
|
304
|
+
} }, isCopied ? '✓ Copied' : 'Copy All'))),
|
|
305
|
+
generateError && (React.createElement("div", { style: {
|
|
306
|
+
padding: '8px 16px',
|
|
307
|
+
backgroundColor: '#fef2f2',
|
|
308
|
+
borderBottom: '1px solid #fecaca',
|
|
309
|
+
fontSize: '12px',
|
|
310
|
+
color: '#991b1b',
|
|
311
|
+
} }, generateError)),
|
|
312
|
+
React.createElement("div", { style: tabContainerStyle },
|
|
313
|
+
React.createElement("button", { onClick: () => setActiveTab('all'), style: getTabStyle(activeTab === 'all'), onMouseEnter: (e) => {
|
|
314
|
+
if (activeTab !== 'all')
|
|
315
|
+
e.currentTarget.style.backgroundColor = '#f3f4f6';
|
|
316
|
+
}, onMouseLeave: (e) => {
|
|
317
|
+
if (activeTab !== 'all')
|
|
318
|
+
e.currentTarget.style.backgroundColor = 'transparent';
|
|
319
|
+
} }, "All"),
|
|
320
|
+
React.createElement("button", { onClick: () => setActiveTab('values'), style: getTabStyle(activeTab === 'values'), onMouseEnter: (e) => {
|
|
321
|
+
if (activeTab !== 'values')
|
|
322
|
+
e.currentTarget.style.backgroundColor = '#f3f4f6';
|
|
323
|
+
}, onMouseLeave: (e) => {
|
|
324
|
+
if (activeTab !== 'values')
|
|
325
|
+
e.currentTarget.style.backgroundColor = 'transparent';
|
|
326
|
+
} }, "Values"),
|
|
327
|
+
React.createElement("button", { onClick: () => setActiveTab('errors'), style: getTabStyle(activeTab === 'errors'), onMouseEnter: (e) => {
|
|
328
|
+
if (activeTab !== 'errors')
|
|
329
|
+
e.currentTarget.style.backgroundColor = '#f3f4f6';
|
|
162
330
|
}, onMouseLeave: (e) => {
|
|
163
|
-
if (
|
|
164
|
-
e.currentTarget.style.backgroundColor = '
|
|
165
|
-
} },
|
|
331
|
+
if (activeTab !== 'errors')
|
|
332
|
+
e.currentTarget.style.backgroundColor = 'transparent';
|
|
333
|
+
} },
|
|
334
|
+
"Errors ",
|
|
335
|
+
errorCount > 0 && `(${errorCount})`),
|
|
336
|
+
React.createElement("button", { onClick: () => setActiveTab('changed'), style: getTabStyle(activeTab === 'changed'), onMouseEnter: (e) => {
|
|
337
|
+
if (activeTab !== 'changed')
|
|
338
|
+
e.currentTarget.style.backgroundColor = '#f3f4f6';
|
|
339
|
+
}, onMouseLeave: (e) => {
|
|
340
|
+
if (activeTab !== 'changed')
|
|
341
|
+
e.currentTarget.style.backgroundColor = 'transparent';
|
|
342
|
+
} },
|
|
343
|
+
"Changed ",
|
|
344
|
+
changedFieldsCount > 0 && `(${changedFieldsCount})`),
|
|
345
|
+
React.createElement("button", { onClick: () => setActiveTab('state'), style: getTabStyle(activeTab === 'state'), onMouseEnter: (e) => {
|
|
346
|
+
if (activeTab !== 'state')
|
|
347
|
+
e.currentTarget.style.backgroundColor = '#f3f4f6';
|
|
348
|
+
}, onMouseLeave: (e) => {
|
|
349
|
+
if (activeTab !== 'state')
|
|
350
|
+
e.currentTarget.style.backgroundColor = 'transparent';
|
|
351
|
+
} }, "State"),
|
|
352
|
+
validationSchema && (React.createElement("button", { onClick: () => setActiveTab('validation'), style: getTabStyle(activeTab === 'validation'), onMouseEnter: (e) => {
|
|
353
|
+
if (activeTab !== 'validation')
|
|
354
|
+
e.currentTarget.style.backgroundColor = '#f3f4f6';
|
|
355
|
+
}, onMouseLeave: (e) => {
|
|
356
|
+
if (activeTab !== 'validation')
|
|
357
|
+
e.currentTarget.style.backgroundColor = 'transparent';
|
|
358
|
+
} }, "Validation"))),
|
|
166
359
|
React.createElement("div", { style: contentStyle },
|
|
167
|
-
React.createElement(
|
|
168
|
-
React.createElement("div", { style:
|
|
169
|
-
React.createElement("div", { style:
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
React.createElement("div", { style:
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
React.createElement("div", { style:
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
React.createElement("div", { style:
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
360
|
+
activeTab === 'all' && (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 }, "Form Values"),
|
|
375
|
+
React.createElement("pre", { style: codeBlockStyle }, JSON.stringify(values || {}, null, 2)),
|
|
376
|
+
errorCount > 0 && (React.createElement(React.Fragment, null,
|
|
377
|
+
React.createElement("div", { style: sectionTitleStyle },
|
|
378
|
+
"Validation Errors (",
|
|
379
|
+
errorCount,
|
|
380
|
+
")"),
|
|
381
|
+
renderErrors())),
|
|
382
|
+
changedFieldsCount > 0 && (React.createElement(React.Fragment, null,
|
|
383
|
+
React.createElement("div", { style: sectionTitleStyle },
|
|
384
|
+
"Changed Fields (",
|
|
385
|
+
changedFieldsCount,
|
|
386
|
+
")"),
|
|
387
|
+
React.createElement("pre", { style: codeBlockStyle }, JSON.stringify(changedFields, null, 2)))),
|
|
388
|
+
dirtyFieldsCount > 0 && (React.createElement(React.Fragment, null,
|
|
389
|
+
React.createElement("div", { style: sectionTitleStyle }, "Dirty Fields"),
|
|
390
|
+
React.createElement("pre", { style: codeBlockStyle }, JSON.stringify(formState.dirtyFields || {}, null, 2)))),
|
|
391
|
+
touchedFieldsCount > 0 && (React.createElement(React.Fragment, null,
|
|
392
|
+
React.createElement("div", { style: sectionTitleStyle }, "Touched Fields"),
|
|
393
|
+
React.createElement("pre", { style: codeBlockStyle }, JSON.stringify(formState.touchedFields || {}, null, 2)))))),
|
|
394
|
+
activeTab === 'values' && (React.createElement(React.Fragment, null, values && Object.keys(values).length > 0 ? (React.createElement(React.Fragment, null,
|
|
395
|
+
React.createElement("div", { style: sectionTitleStyle }, "Form Values"),
|
|
396
|
+
React.createElement("pre", { style: codeBlockStyle }, JSON.stringify(values, null, 2)))) : (React.createElement("div", { style: { textAlign: 'center', color: '#9ca3af', padding: '40px 20px', fontSize: '13px' } },
|
|
397
|
+
React.createElement("div", { style: { marginBottom: '8px' } }, "No form values"),
|
|
398
|
+
React.createElement("div", { style: { fontSize: '11px', color: '#d1d5db' } }, "Form values will appear here"))))),
|
|
399
|
+
activeTab === 'errors' && (React.createElement(React.Fragment, null,
|
|
400
|
+
React.createElement("div", { style: sectionTitleStyle },
|
|
401
|
+
"Validation Errors ",
|
|
402
|
+
errorCount > 0 && `(${errorCount})`),
|
|
403
|
+
errorCount > 0 ? renderErrors() : (React.createElement("div", { style: { textAlign: 'center', color: '#9ca3af', padding: '20px', fontSize: '13px' } }, "No validation errors")))),
|
|
404
|
+
activeTab === 'changed' && (React.createElement(React.Fragment, null, changedFieldsCount > 0 ? (React.createElement(React.Fragment, null,
|
|
183
405
|
React.createElement("div", { style: sectionTitleStyle },
|
|
184
|
-
"
|
|
185
|
-
|
|
406
|
+
"Changed Fields (",
|
|
407
|
+
changedFieldsCount,
|
|
186
408
|
")"),
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
React.createElement("div", { style:
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
409
|
+
React.createElement("pre", { style: codeBlockStyle }, JSON.stringify(changedFields, null, 2)))) : (React.createElement("div", { style: { textAlign: 'center', color: '#9ca3af', padding: '40px 20px', fontSize: '13px' } },
|
|
410
|
+
React.createElement("div", { style: { marginBottom: '8px' } }, "No changed fields"),
|
|
411
|
+
React.createElement("div", { style: { fontSize: '11px', color: '#d1d5db' } }, "Fields will appear here when you modify form values"))))),
|
|
412
|
+
activeTab === 'state' && (React.createElement(React.Fragment, null,
|
|
413
|
+
React.createElement("div", { style: statsContainerStyle },
|
|
414
|
+
React.createElement("div", { style: statCardStyle },
|
|
415
|
+
React.createElement("div", { style: statLabelStyle }, "Dirty Fields"),
|
|
416
|
+
React.createElement("div", { style: statValueStyle }, dirtyFieldsCount)),
|
|
417
|
+
React.createElement("div", { style: statCardStyle },
|
|
418
|
+
React.createElement("div", { style: statLabelStyle }, "Touched Fields"),
|
|
419
|
+
React.createElement("div", { style: statValueStyle }, touchedFieldsCount)),
|
|
420
|
+
React.createElement("div", { style: statCardStyle },
|
|
421
|
+
React.createElement("div", { style: statLabelStyle }, "Submit Count"),
|
|
422
|
+
React.createElement("div", { style: statValueStyle }, formState.submitCount || 0)),
|
|
423
|
+
React.createElement("div", { style: statCardStyle },
|
|
424
|
+
React.createElement("div", { style: statLabelStyle }, "Submitting"),
|
|
425
|
+
React.createElement("div", { style: statValueStyle }, formState.isSubmitting ? 'Yes' : 'No'))),
|
|
426
|
+
dirtyFieldsCount > 0 && (React.createElement(React.Fragment, null,
|
|
427
|
+
React.createElement("div", { style: sectionTitleStyle }, "Dirty Fields"),
|
|
428
|
+
React.createElement("pre", { style: codeBlockStyle }, JSON.stringify(formState.dirtyFields || {}, null, 2)))),
|
|
429
|
+
touchedFieldsCount > 0 && (React.createElement(React.Fragment, null,
|
|
430
|
+
React.createElement("div", { style: sectionTitleStyle }, "Touched Fields"),
|
|
431
|
+
React.createElement("pre", { style: codeBlockStyle }, JSON.stringify(formState.touchedFields || {}, null, 2)))))),
|
|
432
|
+
activeTab === 'validation' && validationSchema && (React.createElement(React.Fragment, null,
|
|
433
|
+
React.createElement("div", { style: sectionTitleStyle }, "Validation Schema"),
|
|
434
|
+
React.createElement("pre", { style: codeBlockStyle }, JSON.stringify(validationSchema, null, 2))))),
|
|
194
435
|
React.createElement("div", { onMouseDown: handleResizeMouseDown, style: resizeHandleStyle }),
|
|
195
436
|
React.createElement("div", { onMouseDown: handleResizeMouseDown, style: resizeHandleIndicatorStyle })))));
|
|
196
437
|
}
|
|
@@ -28,6 +28,8 @@ 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;
|
|
31
33
|
export declare const contentStyle: CSSProperties;
|
|
32
34
|
export declare const sectionTitleStyle: CSSProperties;
|
|
33
35
|
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,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,
|
|
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,iBAAiB,EAAE,aAK/B,CAAC;AAEF,eAAO,MAAM,WAAW,GAAI,UAAU,OAAO,KAAG,aAY9C,CAAC;AAEH,eAAO,MAAM,YAAY,EAAE,aAM1B,CAAC;AAEF,eAAO,MAAM,iBAAiB,EAAE,aAO/B,CAAC;AAEF,eAAO,MAAM,cAAc,EAAE,aAY5B,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"}
|
|
@@ -86,6 +86,25 @@ export const getCopyButtonStyle = (isCopied) => ({
|
|
|
86
86
|
fontWeight: 600,
|
|
87
87
|
transition: 'background-color 0.2s',
|
|
88
88
|
});
|
|
89
|
+
export const tabContainerStyle = {
|
|
90
|
+
display: 'flex',
|
|
91
|
+
borderBottom: '1px solid #e5e7eb',
|
|
92
|
+
backgroundColor: '#f9fafb',
|
|
93
|
+
overflowX: 'auto',
|
|
94
|
+
};
|
|
95
|
+
export const getTabStyle = (isActive) => ({
|
|
96
|
+
flex: '0 0 auto',
|
|
97
|
+
padding: '10px 16px',
|
|
98
|
+
border: 'none',
|
|
99
|
+
backgroundColor: isActive ? 'white' : 'transparent',
|
|
100
|
+
color: isActive ? '#8b5cf6' : '#6b7280',
|
|
101
|
+
cursor: 'pointer',
|
|
102
|
+
fontSize: '13px',
|
|
103
|
+
fontWeight: isActive ? 600 : 400,
|
|
104
|
+
borderBottom: isActive ? '2px solid #8b5cf6' : '2px solid transparent',
|
|
105
|
+
transition: 'all 0.15s',
|
|
106
|
+
whiteSpace: 'nowrap',
|
|
107
|
+
});
|
|
89
108
|
export const contentStyle = {
|
|
90
109
|
flex: 1,
|
|
91
110
|
minHeight: 0, // flexbox에서 스크롤이 제대로 작동하도록 필수
|
|
@@ -111,6 +130,8 @@ export const codeBlockStyle = {
|
|
|
111
130
|
maxHeight: '300px',
|
|
112
131
|
marginBottom: '16px',
|
|
113
132
|
color: '#111827', // 검은색 텍스트
|
|
133
|
+
textAlign: 'left', // 왼쪽 정렬
|
|
134
|
+
whiteSpace: 'pre', // 공백과 줄바꿈 유지
|
|
114
135
|
};
|
|
115
136
|
export const errorItemStyle = {
|
|
116
137
|
padding: '8px 12px',
|
package/dist/index.d.ts
CHANGED
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,cAAc,QAAQ,CAAC;AAGvB,cAAc,QAAQ,CAAC;AAGvB,cAAc,UAAU,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,cAAc,QAAQ,CAAC;AAGvB,cAAc,QAAQ,CAAC;AAGvB,cAAc,UAAU,CAAC;AAGzB,cAAc,UAAU,CAAC;AAGzB,cAAc,QAAQ,CAAC"}
|