@blastlabs/utils 1.12.0 → 1.12.1

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 (34) hide show
  1. package/README.md +20 -13
  2. package/dist/components/auth/AuthGuard.d.ts +95 -0
  3. package/dist/components/auth/AuthGuard.d.ts.map +1 -0
  4. package/dist/components/auth/AuthGuard.js +103 -0
  5. package/dist/components/auth/index.d.ts +5 -0
  6. package/dist/components/auth/index.d.ts.map +1 -0
  7. package/dist/components/auth/index.js +4 -0
  8. package/dist/components/dev/ApiLogger.d.ts +1 -1
  9. package/dist/components/dev/ApiLogger.js +1 -1
  10. package/dist/components/index.d.ts +1 -0
  11. package/dist/components/index.d.ts.map +1 -1
  12. package/dist/components/index.js +1 -0
  13. package/dist/hooks/auth/__tests__/useAuth.test.d.ts +2 -0
  14. package/dist/hooks/auth/__tests__/useAuth.test.d.ts.map +1 -0
  15. package/dist/hooks/auth/__tests__/useAuth.test.js +139 -0
  16. package/dist/hooks/auth/index.d.ts +5 -0
  17. package/dist/hooks/auth/index.d.ts.map +1 -0
  18. package/dist/hooks/auth/index.js +4 -0
  19. package/dist/hooks/auth/useAuth.d.ts +275 -0
  20. package/dist/hooks/auth/useAuth.d.ts.map +1 -0
  21. package/dist/hooks/auth/useAuth.js +384 -0
  22. package/dist/hooks/form/useCRUDForm.d.ts +7 -28
  23. package/dist/hooks/form/useCRUDForm.d.ts.map +1 -1
  24. package/dist/hooks/index.d.ts +1 -0
  25. package/dist/hooks/index.d.ts.map +1 -1
  26. package/dist/hooks/index.js +2 -0
  27. package/dist/hooks/ui/index.d.ts +1 -0
  28. package/dist/hooks/ui/index.d.ts.map +1 -1
  29. package/dist/hooks/ui/index.js +1 -0
  30. package/dist/hooks/ui/useTabs.d.ts +33 -0
  31. package/dist/hooks/ui/useTabs.d.ts.map +1 -0
  32. package/dist/hooks/ui/useTabs.js +117 -0
  33. package/dist/index.js +1 -1
  34. package/package.json +1 -1
@@ -1,37 +1,15 @@
1
- import type { FieldValues } from 'react-hook-form';
1
+ import type { FieldValues, UseFormReturn } from 'react-hook-form';
2
2
  /**
3
3
  * CRUD Form Mode
4
4
  */
5
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
- };
6
+ type TId = string | number | null;
29
7
  /**
30
8
  * useCRUDForm 옵션
31
9
  */
32
- export interface UseCRUDFormOptions<TData extends FieldValues = FieldValues, TId = string | number> {
10
+ export interface UseCRUDFormOptions<TData extends FieldValues = FieldValues> {
33
11
  /** 편집 모드일 때의 ID (있으면 edit mode, 없으면 create mode) */
34
- id?: TId | null;
12
+ id?: TId;
35
13
  /** react-hook-form instance */
36
14
  form: UseFormReturn<TData>;
37
15
  /** 편집 모드일 때 데이터를 fetch하는 함수 */
@@ -39,7 +17,7 @@ export interface UseCRUDFormOptions<TData extends FieldValues = FieldValues, TId
39
17
  /** 생성 API 함수 */
40
18
  createFn?: (data: TData) => Promise<any>;
41
19
  /** 수정 API 함수 */
42
- updateFn?: (id: TId, data: TData) => Promise<any>;
20
+ updateFn?: (id: Exclude<TId, null | undefined>, data: TData) => Promise<any>;
43
21
  /** 삭제 API 함수 (옵션) */
44
22
  deleteFn?: (id: TId) => Promise<any>;
45
23
  /** 성공 시 콜백 */
@@ -228,5 +206,6 @@ export interface UseCRUDFormReturn<TData = any, TId = string | number> {
228
206
  * }
229
207
  * ```
230
208
  */
231
- export declare function useCRUDForm<TData extends FieldValues = FieldValues, TId = string | number>(options: UseCRUDFormOptions<TData, TId>): UseCRUDFormReturn<TData, TId>;
209
+ export declare function useCRUDForm<TData extends FieldValues = FieldValues>(options: UseCRUDFormOptions<TData>): UseCRUDFormReturn<TData>;
210
+ export {};
232
211
  //# sourceMappingURL=useCRUDForm.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"useCRUDForm.d.ts","sourceRoot":"","sources":["../../../src/hooks/form/useCRUDForm.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"useCRUDForm.d.ts","sourceRoot":"","sources":["../../../src/hooks/form/useCRUDForm.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,WAAW,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAC;AAElE;;GAEG;AACH,MAAM,MAAM,QAAQ,GAAG,QAAQ,GAAG,MAAM,CAAC;AAiCzC,KAAK,GAAG,GAAG,MAAM,GAAG,MAAM,GAAG,IAAI,CAAC;AAClC;;GAEG;AACH,MAAM,WAAW,kBAAkB,CAAC,KAAK,SAAS,WAAW,GAAG,WAAW;IACzE,oDAAoD;IACpD,EAAE,CAAC,EAAE,GAAG,CAAC;IAET,+BAA+B;IAC/B,IAAI,EAAE,aAAa,CAAC,KAAK,CAAC,CAAC;IAE3B,+BAA+B;IAC/B,OAAO,CAAC,EAAE,CAAC,EAAE,EAAE,GAAG,KAAK,OAAO,CAAC,KAAK,CAAC,CAAC;IAEtC,gBAAgB;IAChB,QAAQ,CAAC,EAAE,CAAC,IAAI,EAAE,KAAK,KAAK,OAAO,CAAC,GAAG,CAAC,CAAC;IAEzC,gBAAgB;IAChB,QAAQ,CAAC,EAAE,CAAC,EAAE,EAAE,OAAO,CAAC,GAAG,EAAE,IAAI,GAAG,SAAS,CAAC,EAAE,IAAI,EAAE,KAAK,KAAK,OAAO,CAAC,GAAG,CAAC,CAAC;IAE7E,qBAAqB;IACrB,QAAQ,CAAC,EAAE,CAAC,EAAE,EAAE,GAAG,KAAK,OAAO,CAAC,GAAG,CAAC,CAAC;IAErC,cAAc;IACd,SAAS,CAAC,EAAE,CAAC,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,QAAQ,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAElE,cAAc;IACd,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,KAAK,EAAE,IAAI,EAAE,QAAQ,KAAK,IAAI,CAAC;IAEjD,qCAAqC;IACrC,cAAc,CAAC,EAAE,CAAC,IAAI,EAAE,KAAK,KAAK,IAAI,CAAC;IAEvC,oCAAoC;IACpC,aAAa,CAAC,EAAE,CAAC,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,QAAQ,KAAK,GAAG,CAAC;IAErD,sCAAsC;IACtC,mBAAmB,CAAC,EAAE,OAAO,CAAC;CAC/B;AAED;;GAEG;AACH,MAAM,WAAW,iBAAiB,CAAC,KAAK,GAAG,GAAG,EAAE,GAAG,GAAG,MAAM,GAAG,MAAM;IACnE,cAAc;IACd,IAAI,EAAE,QAAQ,CAAC;IAEf,eAAe;IACf,UAAU,EAAE,OAAO,CAAC;IAEpB,eAAe;IACf,YAAY,EAAE,OAAO,CAAC;IAEtB,uBAAuB;IACvB,SAAS,EAAE,OAAO,CAAC;IAEnB,+BAA+B;IAC/B,YAAY,EAAE,OAAO,CAAC;IAEtB,WAAW;IACX,UAAU,EAAE,OAAO,CAAC;IAEpB,SAAS;IACT,KAAK,EAAE,KAAK,GAAG,IAAI,CAAC;IAEpB,4CAA4C;IAC5C,MAAM,EAAE,CAAC,IAAI,EAAE,KAAK,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IAEvC,aAAa;IACb,YAAY,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;IAElC,iBAAiB;IACjB,YAAY,EAAE,KAAK,GAAG,IAAI,CAAC;CAC5B;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAqJG;AACH,wBAAgB,WAAW,CAAC,KAAK,SAAS,WAAW,GAAG,WAAW,EACjE,OAAO,EAAE,kBAAkB,CAAC,KAAK,CAAC,GACjC,iBAAiB,CAAC,KAAK,CAAC,CAiI1B"}
@@ -11,4 +11,5 @@ export * from './time';
11
11
  export * from './form';
12
12
  export * from './ui';
13
13
  export * from './storage';
14
+ export * from './auth';
14
15
  //# 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,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
+ {"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;AAG1B,cAAc,QAAQ,CAAC"}
@@ -18,3 +18,5 @@ export * from './form';
18
18
  export * from './ui';
19
19
  // Storage
20
20
  export * from './storage';
21
+ // Authentication
22
+ export * from './auth';
@@ -3,4 +3,5 @@
3
3
  */
4
4
  export * from './useMediaQuery';
5
5
  export * from './useWindowSize';
6
+ export * from './useTabs';
6
7
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/hooks/ui/index.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,cAAc,iBAAiB,CAAC;AAChC,cAAc,iBAAiB,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/hooks/ui/index.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,cAAc,iBAAiB,CAAC;AAChC,cAAc,iBAAiB,CAAC;AAChC,cAAc,WAAW,CAAC"}
@@ -3,3 +3,4 @@
3
3
  */
4
4
  export * from './useMediaQuery';
5
5
  export * from './useWindowSize';
6
+ export * from './useTabs';
@@ -0,0 +1,33 @@
1
+ type UseTabsOptions = {
2
+ items: Array<{
3
+ id: string;
4
+ }>;
5
+ defaultTab?: string;
6
+ syncWithUrl?: boolean;
7
+ urlParamName?: string;
8
+ };
9
+ type UseTabsReturn = {
10
+ selectedTab: string;
11
+ selectedIndex: number;
12
+ setSelectedTab: (tabId: string) => void;
13
+ setSelectedIndex: (index: number) => void;
14
+ getTabId: (index: number) => string | undefined;
15
+ getTabIndex: (tabId: string) => number;
16
+ };
17
+ /**
18
+ * 탭 상태 관리 훅
19
+ * URL 동기화 및 상태 관리 로직을 제공
20
+ *
21
+ * @example
22
+ * ```tsx
23
+ * const { selectedTab, setSelectedTab, selectedIndex } = useTabs({
24
+ * items: tabs,
25
+ * defaultTab: 'tab1',
26
+ * syncWithUrl: true,
27
+ * urlParamName: 'tab'
28
+ * });
29
+ * ```
30
+ */
31
+ export declare function useTabs({ items, defaultTab, syncWithUrl, urlParamName, }: UseTabsOptions): UseTabsReturn;
32
+ export {};
33
+ //# sourceMappingURL=useTabs.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useTabs.d.ts","sourceRoot":"","sources":["../../../src/hooks/ui/useTabs.ts"],"names":[],"mappings":"AAIA,KAAK,cAAc,GAAG;IACpB,KAAK,EAAE,KAAK,CAAC;QAAE,EAAE,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IAC7B,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB,CAAC;AAEF,KAAK,aAAa,GAAG;IACnB,WAAW,EAAE,MAAM,CAAC;IACpB,aAAa,EAAE,MAAM,CAAC;IACtB,cAAc,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IACxC,gBAAgB,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IAC1C,QAAQ,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,MAAM,GAAG,SAAS,CAAC;IAChD,WAAW,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,MAAM,CAAC;CACxC,CAAC;AAEF;;;;;;;;;;;;;GAaG;AACH,wBAAgB,OAAO,CAAC,EACtB,KAAK,EACL,UAAU,EACV,WAAmB,EACnB,YAAoB,GACrB,EAAE,cAAc,GAAG,aAAa,CA4GhC"}
@@ -0,0 +1,117 @@
1
+ 'use client';
2
+ import { useState, useEffect, useMemo, useCallback } from 'react';
3
+ /**
4
+ * 탭 상태 관리 훅
5
+ * URL 동기화 및 상태 관리 로직을 제공
6
+ *
7
+ * @example
8
+ * ```tsx
9
+ * const { selectedTab, setSelectedTab, selectedIndex } = useTabs({
10
+ * items: tabs,
11
+ * defaultTab: 'tab1',
12
+ * syncWithUrl: true,
13
+ * urlParamName: 'tab'
14
+ * });
15
+ * ```
16
+ */
17
+ export function useTabs({ items, defaultTab, syncWithUrl = false, urlParamName = 'tab', }) {
18
+ // URL에서 탭 ID 가져오기
19
+ const getTabFromUrl = useCallback(() => {
20
+ if (!syncWithUrl || typeof window === 'undefined') {
21
+ return undefined;
22
+ }
23
+ const params = new URLSearchParams(window.location.search);
24
+ return params.get(urlParamName) || undefined;
25
+ }, [syncWithUrl, urlParamName]);
26
+ // 초기 선택된 탭 결정 (우선순위: URL > defaultTab > 첫 번째)
27
+ const initialSelectedTab = useMemo(() => {
28
+ if (syncWithUrl) {
29
+ const urlTab = getTabFromUrl();
30
+ if (urlTab)
31
+ return urlTab;
32
+ }
33
+ return defaultTab || items[0]?.id || '';
34
+ }, [syncWithUrl, defaultTab, items, getTabFromUrl]);
35
+ const [selectedTab, setSelectedTabState] = useState(initialSelectedTab);
36
+ // 선택된 탭의 인덱스
37
+ const selectedIndex = useMemo(() => {
38
+ const index = items.findIndex((item) => item.id === selectedTab);
39
+ return index >= 0 ? index : 0;
40
+ }, [items, selectedTab]);
41
+ // URL 업데이트 함수
42
+ const updateUrl = (tabId) => {
43
+ if (!syncWithUrl || typeof window === 'undefined')
44
+ return;
45
+ const url = new URL(window.location.href);
46
+ if (tabId === defaultTab) {
47
+ // 기본 탭이면 URL에서 제거 (깔끔한 URL 유지)
48
+ url.searchParams.delete(urlParamName);
49
+ }
50
+ else {
51
+ url.searchParams.set(urlParamName, tabId);
52
+ }
53
+ window.history.pushState({}, '', url.toString());
54
+ };
55
+ // 탭 ID로 선택
56
+ const setSelectedTab = (tabId) => {
57
+ setSelectedTabState(tabId);
58
+ if (syncWithUrl) {
59
+ updateUrl(tabId);
60
+ }
61
+ };
62
+ // 인덱스로 선택
63
+ const setSelectedIndex = (index) => {
64
+ const tabId = items[index]?.id;
65
+ if (tabId) {
66
+ setSelectedTab(tabId);
67
+ }
68
+ };
69
+ // 인덱스로 탭 ID 가져오기
70
+ const getTabId = (index) => {
71
+ return items[index]?.id;
72
+ };
73
+ // 탭 ID로 인덱스 가져오기
74
+ const getTabIndex = (tabId) => {
75
+ return items.findIndex((item) => item.id === tabId);
76
+ };
77
+ // 브라우저 뒤로가기/앞으로가기 지원
78
+ useEffect(() => {
79
+ if (!syncWithUrl || typeof window === 'undefined')
80
+ return;
81
+ const handlePopState = () => {
82
+ const urlTab = getTabFromUrl();
83
+ if (urlTab) {
84
+ setSelectedTabState(urlTab);
85
+ }
86
+ else if (defaultTab) {
87
+ setSelectedTabState(defaultTab);
88
+ }
89
+ else if (items[0]?.id) {
90
+ setSelectedTabState(items[0].id);
91
+ }
92
+ };
93
+ window.addEventListener('popstate', handlePopState);
94
+ return () => {
95
+ window.removeEventListener('popstate', handlePopState);
96
+ };
97
+ }, [syncWithUrl, defaultTab, items, getTabFromUrl]);
98
+ // URL 초기 동기화
99
+ useEffect(() => {
100
+ if (!syncWithUrl || typeof window === 'undefined')
101
+ return;
102
+ const urlTab = getTabFromUrl();
103
+ if (urlTab && urlTab !== selectedTab) {
104
+ // URL과 상태가 다를 때만 업데이트 (초기 로드 시)
105
+ setSelectedTabState(urlTab);
106
+ }
107
+ // eslint-disable-next-line react-hooks/exhaustive-deps
108
+ }, [syncWithUrl, urlParamName]); // 초기 마운트 시에만 실행
109
+ return {
110
+ selectedTab,
111
+ selectedIndex,
112
+ setSelectedTab,
113
+ setSelectedIndex,
114
+ getTabId,
115
+ getTabIndex,
116
+ };
117
+ }
package/dist/index.js CHANGED
@@ -8,7 +8,7 @@ export * from './string';
8
8
  export * from './number';
9
9
  // Mock data utilities
10
10
  export * from './mock';
11
- // React Hooks (import separately: 'goodchuck-utils/hooks')
11
+ // React Hooks (import separately: '@blastlabs/utils/hooks')
12
12
  // Note: Hooks are not exported from main entry to avoid React dependency for non-React users
13
13
  // export * from './hooks';
14
14
  // Array utilities (placeholder for future)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@blastlabs/utils",
3
- "version": "1.12.0",
3
+ "version": "1.12.1",
4
4
  "type": "module",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",