@blastlabs/utils 1.11.1 → 1.12.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 (128) hide show
  1. package/dist/components/dev/ApiLogger.d.ts +1 -1
  2. package/dist/components/dev/ApiLogger.js +2 -2
  3. package/dist/components/dev/DevPanel.d.ts +1 -1
  4. package/dist/components/dev/DevPanel.js +2 -2
  5. package/dist/components/dev/FormDevTools/FormDevTools.d.ts +1 -1
  6. package/dist/components/dev/FormDevTools/FormDevTools.js +2 -2
  7. package/dist/components/dev/FormDevTools/index.d.ts +1 -1
  8. package/dist/components/dev/FormDevTools/index.d.ts.map +1 -1
  9. package/dist/components/dev/{IdSelector.d.ts → IdSelector/IdSelector.d.ts} +3 -4
  10. package/dist/components/dev/IdSelector/IdSelector.d.ts.map +1 -0
  11. package/dist/components/dev/IdSelector/IdSelector.js +60 -0
  12. package/dist/components/dev/IdSelector/IdSelector.test.d.ts +2 -0
  13. package/dist/components/dev/IdSelector/IdSelector.test.d.ts.map +1 -0
  14. package/dist/components/dev/IdSelector/IdSelector.test.js +203 -0
  15. package/dist/components/dev/IdSelector/LoginCard.d.ts +17 -0
  16. package/dist/components/dev/IdSelector/LoginCard.d.ts.map +1 -0
  17. package/dist/components/dev/IdSelector/LoginCard.js +16 -0
  18. package/dist/components/dev/IdSelector/index.d.ts +3 -0
  19. package/dist/components/dev/IdSelector/index.d.ts.map +1 -0
  20. package/dist/components/dev/IdSelector/index.js +1 -0
  21. package/dist/components/dev/IdSelector/styles.d.ts +16 -0
  22. package/dist/components/dev/IdSelector/styles.d.ts.map +1 -0
  23. package/dist/components/dev/IdSelector/styles.js +66 -0
  24. package/dist/components/dev/WindowSizeDisplay.d.ts +1 -1
  25. package/dist/components/dev/WindowSizeDisplay.js +2 -2
  26. package/dist/components/dev/ZIndexDebugger.d.ts +1 -1
  27. package/dist/components/dev/ZIndexDebugger.js +1 -1
  28. package/dist/components/dev/index.d.ts +2 -1
  29. package/dist/components/dev/index.d.ts.map +1 -1
  30. package/dist/hooks/event/index.d.ts +6 -0
  31. package/dist/hooks/event/index.d.ts.map +1 -0
  32. package/dist/hooks/event/index.js +5 -0
  33. package/dist/hooks/event/useClickOutside.d.ts.map +1 -0
  34. package/dist/hooks/event/useEventListener.d.ts.map +1 -0
  35. package/dist/hooks/form/__tests__/useCRUDForm.test.d.ts +2 -0
  36. package/dist/hooks/form/__tests__/useCRUDForm.test.d.ts.map +1 -0
  37. package/dist/hooks/form/__tests__/useCRUDForm.test.js +487 -0
  38. package/dist/hooks/form/index.d.ts +5 -0
  39. package/dist/hooks/form/index.d.ts.map +1 -0
  40. package/dist/hooks/form/index.js +4 -0
  41. package/dist/hooks/form/useCRUDForm.d.ts +232 -0
  42. package/dist/hooks/form/useCRUDForm.d.ts.map +1 -0
  43. package/dist/hooks/form/useCRUDForm.js +287 -0
  44. package/dist/hooks/index.d.ts +9 -14
  45. package/dist/hooks/index.d.ts.map +1 -1
  46. package/dist/hooks/index.js +16 -19
  47. package/dist/hooks/performance/index.d.ts +7 -0
  48. package/dist/hooks/performance/index.d.ts.map +1 -0
  49. package/dist/hooks/performance/index.js +6 -0
  50. package/dist/hooks/performance/useDebounce.d.ts.map +1 -0
  51. package/dist/hooks/performance/useIntersectionObserver.d.ts.map +1 -0
  52. package/dist/hooks/performance/useThrottle.d.ts.map +1 -0
  53. package/dist/hooks/state/index.d.ts +6 -0
  54. package/dist/hooks/state/index.d.ts.map +1 -0
  55. package/dist/hooks/state/index.js +5 -0
  56. package/dist/hooks/state/usePrevious.d.ts.map +1 -0
  57. package/dist/hooks/state/useToggle.d.ts.map +1 -0
  58. package/dist/hooks/storage/index.d.ts +7 -0
  59. package/dist/hooks/storage/index.d.ts.map +1 -0
  60. package/dist/hooks/storage/index.js +6 -0
  61. package/dist/hooks/storage/useCopyToClipboard.d.ts.map +1 -0
  62. package/dist/hooks/storage/useLocalStorage.d.ts.map +1 -0
  63. package/dist/hooks/storage/useSessionStorage.d.ts.map +1 -0
  64. package/dist/hooks/time/__tests__/useCountdown.test.d.ts +2 -0
  65. package/dist/hooks/time/__tests__/useCountdown.test.d.ts.map +1 -0
  66. package/dist/hooks/time/__tests__/useCountdown.test.js +150 -0
  67. package/dist/hooks/time/__tests__/useInterval.test.d.ts +2 -0
  68. package/dist/hooks/time/__tests__/useInterval.test.d.ts.map +1 -0
  69. package/dist/hooks/time/__tests__/useInterval.test.js +39 -0
  70. package/dist/hooks/time/__tests__/useStopwatch.test.d.ts +2 -0
  71. package/dist/hooks/time/__tests__/useStopwatch.test.d.ts.map +1 -0
  72. package/dist/hooks/time/__tests__/useStopwatch.test.js +149 -0
  73. package/dist/hooks/time/index.d.ts +7 -0
  74. package/dist/hooks/time/index.d.ts.map +1 -0
  75. package/dist/hooks/time/index.js +6 -0
  76. package/dist/hooks/time/useCountdown.d.ts +116 -0
  77. package/dist/hooks/time/useCountdown.d.ts.map +1 -0
  78. package/dist/hooks/time/useCountdown.js +152 -0
  79. package/dist/hooks/time/useInterval.d.ts +40 -0
  80. package/dist/hooks/time/useInterval.d.ts.map +1 -0
  81. package/dist/hooks/time/useInterval.js +61 -0
  82. package/dist/hooks/time/useStopwatch.d.ts +142 -0
  83. package/dist/hooks/time/useStopwatch.d.ts.map +1 -0
  84. package/dist/hooks/time/useStopwatch.js +179 -0
  85. package/dist/hooks/ui/index.d.ts +6 -0
  86. package/dist/hooks/ui/index.d.ts.map +1 -0
  87. package/dist/hooks/ui/index.js +5 -0
  88. package/dist/hooks/ui/useMediaQuery.d.ts.map +1 -0
  89. package/dist/hooks/ui/useWindowSize.d.ts.map +1 -0
  90. package/package.json +14 -4
  91. package/dist/components/dev/IdSelector.d.ts.map +0 -1
  92. package/dist/components/dev/IdSelector.js +0 -129
  93. package/dist/hooks/useClickOutside.d.ts.map +0 -1
  94. package/dist/hooks/useCopyToClipboard.d.ts.map +0 -1
  95. package/dist/hooks/useDebounce.d.ts.map +0 -1
  96. package/dist/hooks/useEventListener.d.ts.map +0 -1
  97. package/dist/hooks/useIntersectionObserver.d.ts.map +0 -1
  98. package/dist/hooks/useLocalStorage.d.ts.map +0 -1
  99. package/dist/hooks/useMediaQuery.d.ts.map +0 -1
  100. package/dist/hooks/usePrevious.d.ts.map +0 -1
  101. package/dist/hooks/useSessionStorage.d.ts.map +0 -1
  102. package/dist/hooks/useThrottle.d.ts.map +0 -1
  103. package/dist/hooks/useToggle.d.ts.map +0 -1
  104. package/dist/hooks/useWindowSize.d.ts.map +0 -1
  105. /package/dist/hooks/{useClickOutside.d.ts → event/useClickOutside.d.ts} +0 -0
  106. /package/dist/hooks/{useClickOutside.js → event/useClickOutside.js} +0 -0
  107. /package/dist/hooks/{useEventListener.d.ts → event/useEventListener.d.ts} +0 -0
  108. /package/dist/hooks/{useEventListener.js → event/useEventListener.js} +0 -0
  109. /package/dist/hooks/{useDebounce.d.ts → performance/useDebounce.d.ts} +0 -0
  110. /package/dist/hooks/{useDebounce.js → performance/useDebounce.js} +0 -0
  111. /package/dist/hooks/{useIntersectionObserver.d.ts → performance/useIntersectionObserver.d.ts} +0 -0
  112. /package/dist/hooks/{useIntersectionObserver.js → performance/useIntersectionObserver.js} +0 -0
  113. /package/dist/hooks/{useThrottle.d.ts → performance/useThrottle.d.ts} +0 -0
  114. /package/dist/hooks/{useThrottle.js → performance/useThrottle.js} +0 -0
  115. /package/dist/hooks/{usePrevious.d.ts → state/usePrevious.d.ts} +0 -0
  116. /package/dist/hooks/{usePrevious.js → state/usePrevious.js} +0 -0
  117. /package/dist/hooks/{useToggle.d.ts → state/useToggle.d.ts} +0 -0
  118. /package/dist/hooks/{useToggle.js → state/useToggle.js} +0 -0
  119. /package/dist/hooks/{useCopyToClipboard.d.ts → storage/useCopyToClipboard.d.ts} +0 -0
  120. /package/dist/hooks/{useCopyToClipboard.js → storage/useCopyToClipboard.js} +0 -0
  121. /package/dist/hooks/{useLocalStorage.d.ts → storage/useLocalStorage.d.ts} +0 -0
  122. /package/dist/hooks/{useLocalStorage.js → storage/useLocalStorage.js} +0 -0
  123. /package/dist/hooks/{useSessionStorage.d.ts → storage/useSessionStorage.d.ts} +0 -0
  124. /package/dist/hooks/{useSessionStorage.js → storage/useSessionStorage.js} +0 -0
  125. /package/dist/hooks/{useMediaQuery.d.ts → ui/useMediaQuery.d.ts} +0 -0
  126. /package/dist/hooks/{useMediaQuery.js → ui/useMediaQuery.js} +0 -0
  127. /package/dist/hooks/{useWindowSize.d.ts → ui/useWindowSize.d.ts} +0 -0
  128. /package/dist/hooks/{useWindowSize.js → ui/useWindowSize.js} +0 -0
@@ -0,0 +1,232 @@
1
+ import type { FieldValues } from 'react-hook-form';
2
+ /**
3
+ * CRUD Form Mode
4
+ */
5
+ export type FormMode = 'create' | 'edit';
6
+ export type UseFormReturn<TFieldValues extends Record<string, any> = Record<string, any>> = {
7
+ watch: (name?: any) => any;
8
+ getValues: (name?: any) => any;
9
+ setValue: (name: any, value: any, options?: {
10
+ shouldValidate?: boolean;
11
+ shouldDirty?: boolean;
12
+ shouldTouch?: boolean;
13
+ }) => void;
14
+ trigger?: (name?: any) => Promise<boolean>;
15
+ formState: {
16
+ errors?: Record<string, any>;
17
+ dirtyFields?: Record<string, any>;
18
+ touchedFields?: Record<string, any>;
19
+ isValid?: boolean;
20
+ isSubmitting?: boolean;
21
+ submitCount?: number;
22
+ defaultValues?: TFieldValues;
23
+ };
24
+ handleSubmit?: (...args: any[]) => any;
25
+ register?: (...args: any[]) => any;
26
+ reset?: (...args: any[]) => any;
27
+ [key: string]: any;
28
+ };
29
+ /**
30
+ * useCRUDForm 옵션
31
+ */
32
+ export interface UseCRUDFormOptions<TData extends FieldValues = FieldValues, TId = string | number> {
33
+ /** 편집 모드일 때의 ID (있으면 edit mode, 없으면 create mode) */
34
+ id?: TId | null;
35
+ /** react-hook-form instance */
36
+ form: UseFormReturn<TData>;
37
+ /** 편집 모드일 때 데이터를 fetch하는 함수 */
38
+ fetchFn?: (id: TId) => Promise<TData>;
39
+ /** 생성 API 함수 */
40
+ createFn?: (data: TData) => Promise<any>;
41
+ /** 수정 API 함수 */
42
+ updateFn?: (id: TId, data: TData) => Promise<any>;
43
+ /** 삭제 API 함수 (옵션) */
44
+ deleteFn?: (id: TId) => Promise<any>;
45
+ /** 성공 시 콜백 */
46
+ onSuccess?: (data: TData, mode: FormMode) => void | Promise<void>;
47
+ /** 에러 시 콜백 */
48
+ onError?: (error: Error, mode: FormMode) => void;
49
+ /** fetch 성공 시 콜백 (편집 모드 데이터 로드 후) */
50
+ onFetchSuccess?: (data: TData) => void;
51
+ /** 데이터 변환 함수 (submit 전에 데이터를 가공) */
52
+ transformData?: (data: TData, mode: FormMode) => any;
53
+ /** 수정 모드에서 변경된 필드만 전송 (기본값: false) */
54
+ sendOnlyDirtyFields?: boolean;
55
+ }
56
+ /**
57
+ * useCRUDForm 반환 타입
58
+ */
59
+ export interface UseCRUDFormReturn<TData = any, TId = string | number> {
60
+ /** 현재 폼 모드 */
61
+ mode: FormMode;
62
+ /** 편집 모드 여부 */
63
+ isEditMode: boolean;
64
+ /** 생성 모드 여부 */
65
+ isCreateMode: boolean;
66
+ /** 데이터 로딩 중 (fetch) */
67
+ isLoading: boolean;
68
+ /** Submit 중 (create/update) */
69
+ isSubmitting: boolean;
70
+ /** 삭제 중 */
71
+ isDeleting: boolean;
72
+ /** 에러 */
73
+ error: Error | null;
74
+ /** Submit 핸들러 - form.handleSubmit과 함께 사용 */
75
+ submit: (data: TData) => Promise<void>;
76
+ /** 삭제 핸들러 */
77
+ handleDelete: () => Promise<void>;
78
+ /** 로드된 원본 데이터 */
79
+ originalData: TData | null;
80
+ }
81
+ /**
82
+ * 생성/수정 폼의 공통 패턴을 처리하는 hook
83
+ *
84
+ * ID 존재 여부로 자동으로 create/edit 모드를 판단하고,
85
+ * 데이터 fetch, submit 처리를 통합 관리합니다.
86
+ *
87
+ * @example
88
+ * ```tsx
89
+ * // 기본 사용
90
+ * function UserForm({ userId }: { userId?: string }) {
91
+ * const form = useForm<User>({
92
+ * defaultValues: {
93
+ * name: '',
94
+ * email: '',
95
+ * }
96
+ * });
97
+ *
98
+ * const {
99
+ * mode,
100
+ * isLoading,
101
+ * isSubmitting,
102
+ * submit,
103
+ * } = useCRUDForm({
104
+ * id: userId,
105
+ * form,
106
+ * fetchFn: (id) => api.getUser(id),
107
+ * createFn: (data) => api.createUser(data),
108
+ * updateFn: (id, data) => api.updateUser(id, data),
109
+ * onSuccess: (data, mode) => {
110
+ * toast.success(mode === 'create' ? '생성 완료' : '수정 완료');
111
+ * navigate('/users');
112
+ * },
113
+ * });
114
+ *
115
+ * if (isLoading) return <div>Loading...</div>;
116
+ *
117
+ * return (
118
+ * <form onSubmit={form.handleSubmit(submit)}>
119
+ * <input {...form.register('name')} />
120
+ * <input {...form.register('email')} />
121
+ * <button type="submit" disabled={isSubmitting}>
122
+ * {mode === 'create' ? '생성' : '수정'}
123
+ * </button>
124
+ * </form>
125
+ * );
126
+ * }
127
+ * ```
128
+ *
129
+ * @example
130
+ * ```tsx
131
+ * // 삭제 기능 포함
132
+ * function UserForm({ userId }: { userId?: string }) {
133
+ * const form = useForm<User>();
134
+ *
135
+ * const {
136
+ * mode,
137
+ * isLoading,
138
+ * isSubmitting,
139
+ * isDeleting,
140
+ * submit,
141
+ * handleDelete,
142
+ * } = useCRUDForm({
143
+ * id: userId,
144
+ * form,
145
+ * fetchFn: (id) => api.getUser(id),
146
+ * createFn: (data) => api.createUser(data),
147
+ * updateFn: (id, data) => api.updateUser(id, data),
148
+ * deleteFn: (id) => api.deleteUser(id),
149
+ * onSuccess: (data, mode) => {
150
+ * navigate('/users');
151
+ * },
152
+ * });
153
+ *
154
+ * return (
155
+ * <form onSubmit={form.handleSubmit(submit)}>
156
+ * <input {...form.register('name')} />
157
+ * <button type="submit" disabled={isSubmitting}>
158
+ * {isSubmitting ? '처리중...' : mode === 'create' ? '생성' : '수정'}
159
+ * </button>
160
+ * {mode === 'edit' && (
161
+ * <button type="button" onClick={handleDelete} disabled={isDeleting}>
162
+ * {isDeleting ? '삭제중...' : '삭제'}
163
+ * </button>
164
+ * )}
165
+ * </form>
166
+ * );
167
+ * }
168
+ * ```
169
+ *
170
+ * @example
171
+ * ```tsx
172
+ * // 데이터 변환 사용
173
+ * function PostForm({ postId }: { postId?: string }) {
174
+ * const form = useForm<PostFormData>();
175
+ *
176
+ * const { mode, isLoading, submit } = useCRUDForm({
177
+ * id: postId,
178
+ * form,
179
+ * fetchFn: async (id) => {
180
+ * const post = await api.getPost(id);
181
+ * // API 응답을 폼 형식으로 변환
182
+ * return {
183
+ * ...post,
184
+ * tags: post.tags.join(', '), // 배열 -> 문자열
185
+ * };
186
+ * },
187
+ * createFn: (data) => api.createPost(data),
188
+ * updateFn: (id, data) => api.updatePost(id, data),
189
+ * transformData: (data, mode) => {
190
+ * // Submit 전에 폼 데이터를 API 형식으로 변환
191
+ * return {
192
+ * ...data,
193
+ * tags: data.tags.split(',').map(t => t.trim()), // 문자열 -> 배열
194
+ * };
195
+ * },
196
+ * });
197
+ *
198
+ * return <form onSubmit={form.handleSubmit(submit)}>...</form>;
199
+ * }
200
+ * ```
201
+ *
202
+ * @example
203
+ * ```tsx
204
+ * // 변경된 필드만 전송 (PATCH 방식)
205
+ * function UserProfileForm({ userId }: { userId: string }) {
206
+ * const form = useForm<UserProfile>();
207
+ *
208
+ * const { mode, isLoading, submit } = useCRUDForm({
209
+ * id: userId,
210
+ * form,
211
+ * fetchFn: (id) => api.getUser(id),
212
+ * updateFn: (id, data) => api.patchUser(id, data), // PATCH API
213
+ * sendOnlyDirtyFields: true, // 변경된 필드만 전송
214
+ * onSuccess: () => {
215
+ * toast.success('프로필이 업데이트되었습니다');
216
+ * },
217
+ * });
218
+ *
219
+ * // 예: 사용자가 name만 수정하면 { name: "새이름" }만 전송됨
220
+ * return (
221
+ * <form onSubmit={form.handleSubmit(submit)}>
222
+ * <input {...form.register('name')} />
223
+ * <input {...form.register('email')} />
224
+ * <input {...form.register('phone')} />
225
+ * <button type="submit">저장</button>
226
+ * </form>
227
+ * );
228
+ * }
229
+ * ```
230
+ */
231
+ export declare function useCRUDForm<TData extends FieldValues = FieldValues, TId = string | number>(options: UseCRUDFormOptions<TData, TId>): UseCRUDFormReturn<TData, TId>;
232
+ //# sourceMappingURL=useCRUDForm.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useCRUDForm.d.ts","sourceRoot":"","sources":["../../../src/hooks/form/useCRUDForm.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,287 @@
1
+ import { useState, useEffect, useCallback } from 'react';
2
+ /**
3
+ * dirtyFields를 기반으로 변경된 필드만 추출하는 헬퍼 함수
4
+ */
5
+ function extractDirtyFields(data, dirtyFields) {
6
+ const result = {};
7
+ Object.keys(dirtyFields).forEach((key) => {
8
+ const dirtyValue = dirtyFields[key];
9
+ if (dirtyValue === true) {
10
+ // 최종 필드인 경우
11
+ result[key] = data[key];
12
+ }
13
+ else if (typeof dirtyValue === 'object' && dirtyValue !== null && !Array.isArray(dirtyValue)) {
14
+ // 중첩된 객체인 경우 재귀적으로 처리
15
+ if (data[key] && typeof data[key] === 'object') {
16
+ result[key] = extractDirtyFields(data[key], dirtyValue);
17
+ }
18
+ }
19
+ else if (dirtyValue === true || Array.isArray(dirtyValue)) {
20
+ // 배열이거나 true인 경우
21
+ result[key] = data[key];
22
+ }
23
+ });
24
+ return result;
25
+ }
26
+ /**
27
+ * 생성/수정 폼의 공통 패턴을 처리하는 hook
28
+ *
29
+ * ID 존재 여부로 자동으로 create/edit 모드를 판단하고,
30
+ * 데이터 fetch, submit 처리를 통합 관리합니다.
31
+ *
32
+ * @example
33
+ * ```tsx
34
+ * // 기본 사용
35
+ * function UserForm({ userId }: { userId?: string }) {
36
+ * const form = useForm<User>({
37
+ * defaultValues: {
38
+ * name: '',
39
+ * email: '',
40
+ * }
41
+ * });
42
+ *
43
+ * const {
44
+ * mode,
45
+ * isLoading,
46
+ * isSubmitting,
47
+ * submit,
48
+ * } = useCRUDForm({
49
+ * id: userId,
50
+ * form,
51
+ * fetchFn: (id) => api.getUser(id),
52
+ * createFn: (data) => api.createUser(data),
53
+ * updateFn: (id, data) => api.updateUser(id, data),
54
+ * onSuccess: (data, mode) => {
55
+ * toast.success(mode === 'create' ? '생성 완료' : '수정 완료');
56
+ * navigate('/users');
57
+ * },
58
+ * });
59
+ *
60
+ * if (isLoading) return <div>Loading...</div>;
61
+ *
62
+ * return (
63
+ * <form onSubmit={form.handleSubmit(submit)}>
64
+ * <input {...form.register('name')} />
65
+ * <input {...form.register('email')} />
66
+ * <button type="submit" disabled={isSubmitting}>
67
+ * {mode === 'create' ? '생성' : '수정'}
68
+ * </button>
69
+ * </form>
70
+ * );
71
+ * }
72
+ * ```
73
+ *
74
+ * @example
75
+ * ```tsx
76
+ * // 삭제 기능 포함
77
+ * function UserForm({ userId }: { userId?: string }) {
78
+ * const form = useForm<User>();
79
+ *
80
+ * const {
81
+ * mode,
82
+ * isLoading,
83
+ * isSubmitting,
84
+ * isDeleting,
85
+ * submit,
86
+ * handleDelete,
87
+ * } = useCRUDForm({
88
+ * id: userId,
89
+ * form,
90
+ * fetchFn: (id) => api.getUser(id),
91
+ * createFn: (data) => api.createUser(data),
92
+ * updateFn: (id, data) => api.updateUser(id, data),
93
+ * deleteFn: (id) => api.deleteUser(id),
94
+ * onSuccess: (data, mode) => {
95
+ * navigate('/users');
96
+ * },
97
+ * });
98
+ *
99
+ * return (
100
+ * <form onSubmit={form.handleSubmit(submit)}>
101
+ * <input {...form.register('name')} />
102
+ * <button type="submit" disabled={isSubmitting}>
103
+ * {isSubmitting ? '처리중...' : mode === 'create' ? '생성' : '수정'}
104
+ * </button>
105
+ * {mode === 'edit' && (
106
+ * <button type="button" onClick={handleDelete} disabled={isDeleting}>
107
+ * {isDeleting ? '삭제중...' : '삭제'}
108
+ * </button>
109
+ * )}
110
+ * </form>
111
+ * );
112
+ * }
113
+ * ```
114
+ *
115
+ * @example
116
+ * ```tsx
117
+ * // 데이터 변환 사용
118
+ * function PostForm({ postId }: { postId?: string }) {
119
+ * const form = useForm<PostFormData>();
120
+ *
121
+ * const { mode, isLoading, submit } = useCRUDForm({
122
+ * id: postId,
123
+ * form,
124
+ * fetchFn: async (id) => {
125
+ * const post = await api.getPost(id);
126
+ * // API 응답을 폼 형식으로 변환
127
+ * return {
128
+ * ...post,
129
+ * tags: post.tags.join(', '), // 배열 -> 문자열
130
+ * };
131
+ * },
132
+ * createFn: (data) => api.createPost(data),
133
+ * updateFn: (id, data) => api.updatePost(id, data),
134
+ * transformData: (data, mode) => {
135
+ * // Submit 전에 폼 데이터를 API 형식으로 변환
136
+ * return {
137
+ * ...data,
138
+ * tags: data.tags.split(',').map(t => t.trim()), // 문자열 -> 배열
139
+ * };
140
+ * },
141
+ * });
142
+ *
143
+ * return <form onSubmit={form.handleSubmit(submit)}>...</form>;
144
+ * }
145
+ * ```
146
+ *
147
+ * @example
148
+ * ```tsx
149
+ * // 변경된 필드만 전송 (PATCH 방식)
150
+ * function UserProfileForm({ userId }: { userId: string }) {
151
+ * const form = useForm<UserProfile>();
152
+ *
153
+ * const { mode, isLoading, submit } = useCRUDForm({
154
+ * id: userId,
155
+ * form,
156
+ * fetchFn: (id) => api.getUser(id),
157
+ * updateFn: (id, data) => api.patchUser(id, data), // PATCH API
158
+ * sendOnlyDirtyFields: true, // 변경된 필드만 전송
159
+ * onSuccess: () => {
160
+ * toast.success('프로필이 업데이트되었습니다');
161
+ * },
162
+ * });
163
+ *
164
+ * // 예: 사용자가 name만 수정하면 { name: "새이름" }만 전송됨
165
+ * return (
166
+ * <form onSubmit={form.handleSubmit(submit)}>
167
+ * <input {...form.register('name')} />
168
+ * <input {...form.register('email')} />
169
+ * <input {...form.register('phone')} />
170
+ * <button type="submit">저장</button>
171
+ * </form>
172
+ * );
173
+ * }
174
+ * ```
175
+ */
176
+ export function useCRUDForm(options) {
177
+ const { id, form, fetchFn, createFn, updateFn, deleteFn, onSuccess, onError, onFetchSuccess, transformData, sendOnlyDirtyFields = false, } = options;
178
+ const mode = id ? 'edit' : 'create';
179
+ const isEditMode = mode === 'edit';
180
+ const isCreateMode = mode === 'create';
181
+ const [isLoading, setIsLoading] = useState(false);
182
+ const [isSubmitting, setIsSubmitting] = useState(false);
183
+ const [isDeleting, setIsDeleting] = useState(false);
184
+ const [error, setError] = useState(null);
185
+ const [originalData, setOriginalData] = useState(null);
186
+ // 편집 모드일 때 데이터 fetch
187
+ useEffect(() => {
188
+ if (isEditMode && id && fetchFn) {
189
+ const fetchData = async () => {
190
+ setIsLoading(true);
191
+ setError(null);
192
+ try {
193
+ const data = await fetchFn(id);
194
+ setOriginalData(data);
195
+ form.reset?.(data);
196
+ onFetchSuccess?.(data);
197
+ }
198
+ catch (err) {
199
+ const error = err instanceof Error ? err : new Error('Failed to fetch data');
200
+ setError(error);
201
+ onError?.(error, mode);
202
+ }
203
+ finally {
204
+ setIsLoading(false);
205
+ }
206
+ };
207
+ fetchData();
208
+ }
209
+ }, [id, isEditMode]);
210
+ // Submit 핸들러
211
+ const submit = useCallback(async (data) => {
212
+ setIsSubmitting(true);
213
+ setError(null);
214
+ try {
215
+ // 수정 모드이고 sendOnlyDirtyFields가 true면 변경된 필드만 추출
216
+ let dataToSubmit = data;
217
+ if (isEditMode && sendOnlyDirtyFields && form.formState.dirtyFields) {
218
+ dataToSubmit = extractDirtyFields(data, form.formState.dirtyFields);
219
+ }
220
+ // 데이터 변환 (있으면)
221
+ const submitData = transformData ? transformData(dataToSubmit, mode) : dataToSubmit;
222
+ let result;
223
+ if (isCreateMode) {
224
+ if (!createFn) {
225
+ throw new Error('createFn is required for create mode');
226
+ }
227
+ result = await createFn(submitData);
228
+ }
229
+ else {
230
+ if (!updateFn) {
231
+ throw new Error('updateFn is required for edit mode');
232
+ }
233
+ if (!id) {
234
+ throw new Error('id is required for edit mode');
235
+ }
236
+ result = await updateFn(id, submitData);
237
+ }
238
+ await onSuccess?.(data, mode);
239
+ return result;
240
+ }
241
+ catch (err) {
242
+ const error = err instanceof Error ? err : new Error('Failed to submit form');
243
+ setError(error);
244
+ onError?.(error, mode);
245
+ throw error;
246
+ }
247
+ finally {
248
+ setIsSubmitting(false);
249
+ }
250
+ }, [mode, isCreateMode, isEditMode, createFn, updateFn, id, transformData, sendOnlyDirtyFields, form.formState.dirtyFields, onSuccess, onError]);
251
+ // 삭제 핸들러
252
+ const handleDelete = useCallback(async () => {
253
+ if (!isEditMode || !id) {
254
+ throw new Error('Delete is only available in edit mode');
255
+ }
256
+ if (!deleteFn) {
257
+ throw new Error('deleteFn is required for delete operation');
258
+ }
259
+ setIsDeleting(true);
260
+ setError(null);
261
+ try {
262
+ await deleteFn(id);
263
+ await onSuccess?.(originalData, mode);
264
+ }
265
+ catch (err) {
266
+ const error = err instanceof Error ? err : new Error('Failed to delete');
267
+ setError(error);
268
+ onError?.(error, mode);
269
+ throw error;
270
+ }
271
+ finally {
272
+ setIsDeleting(false);
273
+ }
274
+ }, [isEditMode, id, deleteFn, originalData, mode, onSuccess, onError]);
275
+ return {
276
+ mode,
277
+ isEditMode,
278
+ isCreateMode,
279
+ isLoading,
280
+ isSubmitting,
281
+ isDeleting,
282
+ error,
283
+ submit,
284
+ handleDelete,
285
+ originalData,
286
+ };
287
+ }
@@ -1,19 +1,14 @@
1
1
  /**
2
- * React Hooks
2
+ * React Hooks Collection
3
3
  *
4
- * This module provides React hooks for common use cases.
4
+ * 카테고리별로 정리된 유용한 React hooks 모음입니다.
5
5
  * Note: Requires React as a peer dependency.
6
6
  */
7
- export * from './useLocalStorage';
8
- export * from './useSessionStorage';
9
- export * from './useMediaQuery';
10
- export * from './useClickOutside';
11
- export * from './useDebounce';
12
- export * from './useToggle';
13
- export * from './useCopyToClipboard';
14
- export * from './useWindowSize';
15
- export * from './usePrevious';
16
- export * from './useThrottle';
17
- export * from './useIntersectionObserver';
18
- export * from './useEventListener';
7
+ export * from './state';
8
+ export * from './performance';
9
+ export * from './event';
10
+ export * from './time';
11
+ export * from './form';
12
+ export * from './ui';
13
+ export * from './storage';
19
14
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/hooks/index.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAGH,cAAc,mBAAmB,CAAC;AAClC,cAAc,qBAAqB,CAAC;AAGpC,cAAc,iBAAiB,CAAC;AAChC,cAAc,mBAAmB,CAAC;AAGlC,cAAc,eAAe,CAAC;AAC9B,cAAc,aAAa,CAAC;AAC5B,cAAc,sBAAsB,CAAC;AACrC,cAAc,iBAAiB,CAAC;AAChC,cAAc,eAAe,CAAC;AAG9B,cAAc,eAAe,CAAC;AAC9B,cAAc,2BAA2B,CAAC;AAG1C,cAAc,oBAAoB,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/hooks/index.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAGH,cAAc,SAAS,CAAC;AAGxB,cAAc,eAAe,CAAC;AAG9B,cAAc,SAAS,CAAC;AAGxB,cAAc,QAAQ,CAAC;AAGvB,cAAc,QAAQ,CAAC;AAGvB,cAAc,MAAM,CAAC;AAGrB,cAAc,WAAW,CAAC"}
@@ -1,23 +1,20 @@
1
1
  /**
2
- * React Hooks
2
+ * React Hooks Collection
3
3
  *
4
- * This module provides React hooks for common use cases.
4
+ * 카테고리별로 정리된 유용한 React hooks 모음입니다.
5
5
  * Note: Requires React as a peer dependency.
6
6
  */
7
- // Storage hooks
8
- export * from './useLocalStorage';
9
- export * from './useSessionStorage';
10
- // UI/UX hooks
11
- export * from './useMediaQuery';
12
- export * from './useClickOutside';
13
- // Utility hooks
14
- export * from './useDebounce';
15
- export * from './useToggle';
16
- export * from './useCopyToClipboard';
17
- export * from './useWindowSize';
18
- export * from './usePrevious';
19
- // Performance hooks
20
- export * from './useThrottle';
21
- export * from './useIntersectionObserver';
22
- // Event hooks
23
- export * from './useEventListener';
7
+ // State Management
8
+ export * from './state';
9
+ // Performance Optimization
10
+ export * from './performance';
11
+ // Event Handling
12
+ export * from './event';
13
+ // Time-related
14
+ export * from './time';
15
+ // Form Management
16
+ export * from './form';
17
+ // UI/UX
18
+ export * from './ui';
19
+ // Storage
20
+ export * from './storage';
@@ -0,0 +1,7 @@
1
+ /**
2
+ * Performance Optimization Hooks
3
+ */
4
+ export * from './useThrottle';
5
+ export * from './useDebounce';
6
+ export * from './useIntersectionObserver';
7
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/hooks/performance/index.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,cAAc,eAAe,CAAC;AAC9B,cAAc,eAAe,CAAC;AAC9B,cAAc,2BAA2B,CAAC"}
@@ -0,0 +1,6 @@
1
+ /**
2
+ * Performance Optimization Hooks
3
+ */
4
+ export * from './useThrottle';
5
+ export * from './useDebounce';
6
+ export * from './useIntersectionObserver';
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useDebounce.d.ts","sourceRoot":"","sources":["../../../src/hooks/performance/useDebounce.ts"],"names":[],"mappings":"AAEA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4CG;AACH,wBAAgB,WAAW,CAAC,CAAC,EAAE,KAAK,EAAE,CAAC,EAAE,KAAK,GAAE,MAAY,GAAG,CAAC,CAgB/D"}
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useIntersectionObserver.d.ts","sourceRoot":"","sources":["../../../src/hooks/performance/useIntersectionObserver.ts"],"names":[],"mappings":"AAAA,OAAO,EAAuB,SAAS,EAAE,MAAM,OAAO,CAAC;AAEvD;;GAEG;AACH,MAAM,WAAW,8BAA+B,SAAQ,wBAAwB;IAC9E,+CAA+C;IAC/C,iBAAiB,CAAC,EAAE,OAAO,CAAC;CAC7B;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8EG;AACH,wBAAgB,uBAAuB,CACrC,GAAG,EAAE,SAAS,CAAC,OAAO,CAAC,EACvB,OAAO,GAAE,8BAAmC,GAC3C,yBAAyB,GAAG,SAAS,CAoCvC;AAED;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAgB,YAAY,CAC1B,GAAG,EAAE,SAAS,CAAC,OAAO,CAAC,EACvB,OAAO,CAAC,EAAE,8BAA8B,GACvC,OAAO,CAGT"}
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useThrottle.d.ts","sourceRoot":"","sources":["../../../src/hooks/performance/useThrottle.ts"],"names":[],"mappings":"AAEA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAsDG;AACH,wBAAgB,WAAW,CAAC,CAAC,EAAE,KAAK,EAAE,CAAC,EAAE,QAAQ,GAAE,MAAY,GAAG,CAAC,CA0BlE"}
@@ -0,0 +1,6 @@
1
+ /**
2
+ * State Management Hooks
3
+ */
4
+ export * from './useToggle';
5
+ export * from './usePrevious';
6
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/hooks/state/index.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,cAAc,aAAa,CAAC;AAC5B,cAAc,eAAe,CAAC"}
@@ -0,0 +1,5 @@
1
+ /**
2
+ * State Management Hooks
3
+ */
4
+ export * from './useToggle';
5
+ export * from './usePrevious';
@@ -0,0 +1 @@
1
+ {"version":3,"file":"usePrevious.d.ts","sourceRoot":"","sources":["../../../src/hooks/state/usePrevious.ts"],"names":[],"mappings":"AAEA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAuDG;AACH,wBAAgB,WAAW,CAAC,CAAC,EAAE,KAAK,EAAE,CAAC,GAAG,CAAC,GAAG,SAAS,CAWtD"}
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useToggle.d.ts","sourceRoot":"","sources":["../../../src/hooks/state/useToggle.ts"],"names":[],"mappings":"AAEA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8CG;AACH,wBAAgB,SAAS,CACvB,YAAY,GAAE,OAAe,GAC5B,CAAC,OAAO,EAAE,MAAM,IAAI,EAAE,CAAC,KAAK,EAAE,OAAO,KAAK,IAAI,CAAC,CASjD"}
@@ -0,0 +1,7 @@
1
+ /**
2
+ * Storage Hooks
3
+ */
4
+ export * from './useLocalStorage';
5
+ export * from './useSessionStorage';
6
+ export * from './useCopyToClipboard';
7
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/hooks/storage/index.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,cAAc,mBAAmB,CAAC;AAClC,cAAc,qBAAqB,CAAC;AACpC,cAAc,sBAAsB,CAAC"}