@blastlabs/utils 1.11.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 (137) hide show
  1. package/README.md +267 -0
  2. package/dist/components/dev/ApiLogger.d.ts +136 -0
  3. package/dist/components/dev/ApiLogger.d.ts.map +1 -0
  4. package/dist/components/dev/ApiLogger.js +408 -0
  5. package/dist/components/dev/DevPanel.d.ts +32 -0
  6. package/dist/components/dev/DevPanel.d.ts.map +1 -0
  7. package/dist/components/dev/DevPanel.js +196 -0
  8. package/dist/components/dev/FormDevTools/FormDevTools.d.ts +136 -0
  9. package/dist/components/dev/FormDevTools/FormDevTools.d.ts.map +1 -0
  10. package/dist/components/dev/FormDevTools/FormDevTools.js +442 -0
  11. package/dist/components/dev/FormDevTools/index.d.ts +3 -0
  12. package/dist/components/dev/FormDevTools/index.d.ts.map +1 -0
  13. package/dist/components/dev/FormDevTools/index.js +1 -0
  14. package/dist/components/dev/FormDevTools/styles.d.ts +45 -0
  15. package/dist/components/dev/FormDevTools/styles.d.ts.map +1 -0
  16. package/dist/components/dev/FormDevTools/styles.js +197 -0
  17. package/dist/components/dev/IdSelector.d.ts +50 -0
  18. package/dist/components/dev/IdSelector.d.ts.map +1 -0
  19. package/dist/components/dev/IdSelector.js +129 -0
  20. package/dist/components/dev/WindowSizeDisplay.d.ts +44 -0
  21. package/dist/components/dev/WindowSizeDisplay.d.ts.map +1 -0
  22. package/dist/components/dev/WindowSizeDisplay.js +74 -0
  23. package/dist/components/dev/ZIndexDebugger.d.ts +32 -0
  24. package/dist/components/dev/ZIndexDebugger.d.ts.map +1 -0
  25. package/dist/components/dev/ZIndexDebugger.js +184 -0
  26. package/dist/components/dev/index.d.ts +15 -0
  27. package/dist/components/dev/index.d.ts.map +1 -0
  28. package/dist/components/dev/index.js +12 -0
  29. package/dist/components/index.d.ts +8 -0
  30. package/dist/components/index.d.ts.map +1 -0
  31. package/dist/components/index.js +7 -0
  32. package/dist/date/index.d.ts +64 -0
  33. package/dist/date/index.d.ts.map +1 -0
  34. package/dist/date/index.js +92 -0
  35. package/dist/date/index.test.d.ts +2 -0
  36. package/dist/date/index.test.d.ts.map +1 -0
  37. package/dist/date/index.test.js +166 -0
  38. package/dist/form/__tests__/formatter.test.d.ts +2 -0
  39. package/dist/form/__tests__/formatter.test.d.ts.map +1 -0
  40. package/dist/form/__tests__/formatter.test.js +74 -0
  41. package/dist/form/__tests__/helpers.test.d.ts +2 -0
  42. package/dist/form/__tests__/helpers.test.d.ts.map +1 -0
  43. package/dist/form/__tests__/helpers.test.js +42 -0
  44. package/dist/form/__tests__/validation.test.d.ts +2 -0
  45. package/dist/form/__tests__/validation.test.d.ts.map +1 -0
  46. package/dist/form/__tests__/validation.test.js +67 -0
  47. package/dist/form/formatter.d.ts +34 -0
  48. package/dist/form/formatter.d.ts.map +1 -0
  49. package/dist/form/formatter.js +76 -0
  50. package/dist/form/helpers.d.ts +16 -0
  51. package/dist/form/helpers.d.ts.map +1 -0
  52. package/dist/form/helpers.js +34 -0
  53. package/dist/form/index.d.ts +9 -0
  54. package/dist/form/index.d.ts.map +1 -0
  55. package/dist/form/index.js +11 -0
  56. package/dist/form/validation.d.ts +33 -0
  57. package/dist/form/validation.d.ts.map +1 -0
  58. package/dist/form/validation.js +56 -0
  59. package/dist/hooks/index.d.ts +19 -0
  60. package/dist/hooks/index.d.ts.map +1 -0
  61. package/dist/hooks/index.js +23 -0
  62. package/dist/hooks/useClickOutside.d.ts +49 -0
  63. package/dist/hooks/useClickOutside.d.ts.map +1 -0
  64. package/dist/hooks/useClickOutside.js +94 -0
  65. package/dist/hooks/useCopyToClipboard.d.ts +67 -0
  66. package/dist/hooks/useCopyToClipboard.d.ts.map +1 -0
  67. package/dist/hooks/useCopyToClipboard.js +79 -0
  68. package/dist/hooks/useDebounce.d.ts +47 -0
  69. package/dist/hooks/useDebounce.d.ts.map +1 -0
  70. package/dist/hooks/useDebounce.js +60 -0
  71. package/dist/hooks/useEventListener.d.ts +79 -0
  72. package/dist/hooks/useEventListener.d.ts.map +1 -0
  73. package/dist/hooks/useEventListener.js +33 -0
  74. package/dist/hooks/useIntersectionObserver.d.ts +109 -0
  75. package/dist/hooks/useIntersectionObserver.d.ts.map +1 -0
  76. package/dist/hooks/useIntersectionObserver.js +128 -0
  77. package/dist/hooks/useLocalStorage.d.ts +19 -0
  78. package/dist/hooks/useLocalStorage.d.ts.map +1 -0
  79. package/dist/hooks/useLocalStorage.js +91 -0
  80. package/dist/hooks/useMediaQuery.d.ts +56 -0
  81. package/dist/hooks/useMediaQuery.d.ts.map +1 -0
  82. package/dist/hooks/useMediaQuery.js +104 -0
  83. package/dist/hooks/usePrevious.d.ts +58 -0
  84. package/dist/hooks/usePrevious.d.ts.map +1 -0
  85. package/dist/hooks/usePrevious.js +67 -0
  86. package/dist/hooks/useSessionStorage.d.ts +19 -0
  87. package/dist/hooks/useSessionStorage.d.ts.map +1 -0
  88. package/dist/hooks/useSessionStorage.js +85 -0
  89. package/dist/hooks/useThrottle.d.ts +57 -0
  90. package/dist/hooks/useThrottle.d.ts.map +1 -0
  91. package/dist/hooks/useThrottle.js +80 -0
  92. package/dist/hooks/useToggle.d.ts +49 -0
  93. package/dist/hooks/useToggle.d.ts.map +1 -0
  94. package/dist/hooks/useToggle.js +56 -0
  95. package/dist/hooks/useWindowSize.d.ts +58 -0
  96. package/dist/hooks/useWindowSize.d.ts.map +1 -0
  97. package/dist/hooks/useWindowSize.js +79 -0
  98. package/dist/index.d.ts +6 -0
  99. package/dist/index.d.ts.map +1 -0
  100. package/dist/index.js +17 -0
  101. package/dist/mock/form.d.ts +41 -0
  102. package/dist/mock/form.d.ts.map +1 -0
  103. package/dist/mock/form.js +195 -0
  104. package/dist/mock/generators.d.ts +112 -0
  105. package/dist/mock/generators.d.ts.map +1 -0
  106. package/dist/mock/generators.js +195 -0
  107. package/dist/mock/index.d.ts +8 -0
  108. package/dist/mock/index.d.ts.map +1 -0
  109. package/dist/mock/index.js +9 -0
  110. package/dist/number/format.d.ts +116 -0
  111. package/dist/number/format.d.ts.map +1 -0
  112. package/dist/number/format.js +165 -0
  113. package/dist/number/index.d.ts +7 -0
  114. package/dist/number/index.d.ts.map +1 -0
  115. package/dist/number/index.js +7 -0
  116. package/dist/string/__tests__/case.test.d.ts +2 -0
  117. package/dist/string/__tests__/case.test.d.ts.map +1 -0
  118. package/dist/string/__tests__/case.test.js +61 -0
  119. package/dist/string/__tests__/manipulation.test.d.ts +2 -0
  120. package/dist/string/__tests__/manipulation.test.d.ts.map +1 -0
  121. package/dist/string/__tests__/manipulation.test.js +109 -0
  122. package/dist/string/__tests__/validation.test.d.ts +2 -0
  123. package/dist/string/__tests__/validation.test.d.ts.map +1 -0
  124. package/dist/string/__tests__/validation.test.js +101 -0
  125. package/dist/string/case.d.ts +42 -0
  126. package/dist/string/case.d.ts.map +1 -0
  127. package/dist/string/case.js +71 -0
  128. package/dist/string/index.d.ts +9 -0
  129. package/dist/string/index.d.ts.map +1 -0
  130. package/dist/string/index.js +11 -0
  131. package/dist/string/manipulation.d.ts +61 -0
  132. package/dist/string/manipulation.d.ts.map +1 -0
  133. package/dist/string/manipulation.js +106 -0
  134. package/dist/string/validation.d.ts +79 -0
  135. package/dist/string/validation.d.ts.map +1 -0
  136. package/dist/string/validation.js +115 -0
  137. package/package.json +86 -0
@@ -0,0 +1,56 @@
1
+ /**
2
+ * 미디어 쿼리 매칭 여부를 반환하는 hook
3
+ *
4
+ * @param query - CSS 미디어 쿼리 문자열
5
+ * @returns 미디어 쿼리 매칭 여부
6
+ *
7
+ * @example
8
+ * // 모바일 체크
9
+ * const isMobile = useMediaQuery('(max-width: 768px)');
10
+ *
11
+ * @example
12
+ * // 태블릿 체크
13
+ * const isTablet = useMediaQuery('(min-width: 769px) and (max-width: 1024px)');
14
+ *
15
+ * @example
16
+ * // 데스크톱 체크
17
+ * const isDesktop = useMediaQuery('(min-width: 1025px)');
18
+ *
19
+ * @example
20
+ * // 다크모드 체크
21
+ * const isDarkMode = useMediaQuery('(prefers-color-scheme: dark)');
22
+ *
23
+ * @example
24
+ * // 가로 모드 체크
25
+ * const isLandscape = useMediaQuery('(orientation: landscape)');
26
+ */
27
+ export declare function useMediaQuery(query: string): boolean;
28
+ /**
29
+ * 일반적인 브레이크포인트를 위한 헬퍼 hook들
30
+ */
31
+ /**
32
+ * 모바일 디바이스 체크 (768px 이하)
33
+ * @example
34
+ * const isMobile = useIsMobile();
35
+ * if (isMobile) return <MobileView />;
36
+ */
37
+ export declare function useIsMobile(): boolean;
38
+ /**
39
+ * 태블릿 디바이스 체크 (769px ~ 1024px)
40
+ * @example
41
+ * const isTablet = useIsTablet();
42
+ */
43
+ export declare function useIsTablet(): boolean;
44
+ /**
45
+ * 데스크톱 디바이스 체크 (1025px 이상)
46
+ * @example
47
+ * const isDesktop = useIsDesktop();
48
+ */
49
+ export declare function useIsDesktop(): boolean;
50
+ /**
51
+ * 다크모드 체크
52
+ * @example
53
+ * const isDarkMode = useIsDarkMode();
54
+ */
55
+ export declare function useIsDarkMode(): boolean;
56
+ //# sourceMappingURL=useMediaQuery.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useMediaQuery.d.ts","sourceRoot":"","sources":["../../src/hooks/useMediaQuery.ts"],"names":[],"mappings":"AAEA;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AACH,wBAAgB,aAAa,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CA8CpD;AAED;;GAEG;AAEH;;;;;GAKG;AACH,wBAAgB,WAAW,IAAI,OAAO,CAErC;AAED;;;;GAIG;AACH,wBAAgB,WAAW,IAAI,OAAO,CAErC;AAED;;;;GAIG;AACH,wBAAgB,YAAY,IAAI,OAAO,CAEtC;AAED;;;;GAIG;AACH,wBAAgB,aAAa,IAAI,OAAO,CAEvC"}
@@ -0,0 +1,104 @@
1
+ import { useState, useEffect } from 'react';
2
+ /**
3
+ * 미디어 쿼리 매칭 여부를 반환하는 hook
4
+ *
5
+ * @param query - CSS 미디어 쿼리 문자열
6
+ * @returns 미디어 쿼리 매칭 여부
7
+ *
8
+ * @example
9
+ * // 모바일 체크
10
+ * const isMobile = useMediaQuery('(max-width: 768px)');
11
+ *
12
+ * @example
13
+ * // 태블릿 체크
14
+ * const isTablet = useMediaQuery('(min-width: 769px) and (max-width: 1024px)');
15
+ *
16
+ * @example
17
+ * // 데스크톱 체크
18
+ * const isDesktop = useMediaQuery('(min-width: 1025px)');
19
+ *
20
+ * @example
21
+ * // 다크모드 체크
22
+ * const isDarkMode = useMediaQuery('(prefers-color-scheme: dark)');
23
+ *
24
+ * @example
25
+ * // 가로 모드 체크
26
+ * const isLandscape = useMediaQuery('(orientation: landscape)');
27
+ */
28
+ export function useMediaQuery(query) {
29
+ // SSR 안전성을 위한 초기값 설정
30
+ const getMatches = (query) => {
31
+ if (typeof window !== 'undefined') {
32
+ return window.matchMedia(query).matches;
33
+ }
34
+ return false;
35
+ };
36
+ const [matches, setMatches] = useState(getMatches(query));
37
+ useEffect(() => {
38
+ if (typeof window === 'undefined') {
39
+ return;
40
+ }
41
+ const mediaQuery = window.matchMedia(query);
42
+ // 초기 상태 설정
43
+ setMatches(mediaQuery.matches);
44
+ // 미디어 쿼리 변경 감지 (최신 API)
45
+ const handleChange = (event) => {
46
+ setMatches(event.matches);
47
+ };
48
+ // 이벤트 리스너 등록
49
+ // addEventListener를 지원하는 브라우저
50
+ if (mediaQuery.addEventListener) {
51
+ mediaQuery.addEventListener('change', handleChange);
52
+ }
53
+ else {
54
+ // 구형 브라우저 지원 (deprecated)
55
+ mediaQuery.addListener(handleChange);
56
+ }
57
+ // 클린업
58
+ return () => {
59
+ if (mediaQuery.removeEventListener) {
60
+ mediaQuery.removeEventListener('change', handleChange);
61
+ }
62
+ else {
63
+ mediaQuery.removeListener(handleChange);
64
+ }
65
+ };
66
+ }, [query]);
67
+ return matches;
68
+ }
69
+ /**
70
+ * 일반적인 브레이크포인트를 위한 헬퍼 hook들
71
+ */
72
+ /**
73
+ * 모바일 디바이스 체크 (768px 이하)
74
+ * @example
75
+ * const isMobile = useIsMobile();
76
+ * if (isMobile) return <MobileView />;
77
+ */
78
+ export function useIsMobile() {
79
+ return useMediaQuery('(max-width: 768px)');
80
+ }
81
+ /**
82
+ * 태블릿 디바이스 체크 (769px ~ 1024px)
83
+ * @example
84
+ * const isTablet = useIsTablet();
85
+ */
86
+ export function useIsTablet() {
87
+ return useMediaQuery('(min-width: 769px) and (max-width: 1024px)');
88
+ }
89
+ /**
90
+ * 데스크톱 디바이스 체크 (1025px 이상)
91
+ * @example
92
+ * const isDesktop = useIsDesktop();
93
+ */
94
+ export function useIsDesktop() {
95
+ return useMediaQuery('(min-width: 1025px)');
96
+ }
97
+ /**
98
+ * 다크모드 체크
99
+ * @example
100
+ * const isDarkMode = useIsDarkMode();
101
+ */
102
+ export function useIsDarkMode() {
103
+ return useMediaQuery('(prefers-color-scheme: dark)');
104
+ }
@@ -0,0 +1,58 @@
1
+ /**
2
+ * 이전 렌더링의 값을 저장하는 hook
3
+ *
4
+ * @param value - 추적할 값
5
+ * @returns 이전 렌더링의 값 (첫 렌더링에서는 undefined)
6
+ *
7
+ * @example
8
+ * // 값 변경 감지
9
+ * function Counter() {
10
+ * const [count, setCount] = useState(0);
11
+ * const prevCount = usePrevious(count);
12
+ *
13
+ * return (
14
+ * <div>
15
+ * <p>Current: {count}</p>
16
+ * <p>Previous: {prevCount}</p>
17
+ * <p>Changed: {count !== prevCount ? 'Yes' : 'No'}</p>
18
+ * <button onClick={() => setCount(count + 1)}>Increment</button>
19
+ * </div>
20
+ * );
21
+ * }
22
+ *
23
+ * @example
24
+ * // 증가/감소 방향 표시
25
+ * function PriceDisplay({ price }: { price: number }) {
26
+ * const prevPrice = usePrevious(price);
27
+ *
28
+ * const trend = prevPrice === undefined
29
+ * ? null
30
+ * : price > prevPrice
31
+ * ? '📈 Up'
32
+ * : price < prevPrice
33
+ * ? '📉 Down'
34
+ * : '➡️ Same';
35
+ *
36
+ * return (
37
+ * <div>
38
+ * <span>${price}</span>
39
+ * {trend && <span>{trend}</span>}
40
+ * </div>
41
+ * );
42
+ * }
43
+ *
44
+ * @example
45
+ * // 애니메이션 방향 결정
46
+ * function AnimatedList({ items }: { items: string[] }) {
47
+ * const prevItems = usePrevious(items);
48
+ * const isAdding = prevItems && items.length > prevItems.length;
49
+ *
50
+ * return (
51
+ * <ul className={isAdding ? 'slide-in' : 'slide-out'}>
52
+ * {items.map(item => <li key={item}>{item}</li>)}
53
+ * </ul>
54
+ * );
55
+ * }
56
+ */
57
+ export declare function usePrevious<T>(value: T): T | undefined;
58
+ //# sourceMappingURL=usePrevious.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"usePrevious.d.ts","sourceRoot":"","sources":["../../src/hooks/usePrevious.ts"],"names":[],"mappings":"AAEA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAuDG;AACH,wBAAgB,WAAW,CAAC,CAAC,EAAE,KAAK,EAAE,CAAC,GAAG,CAAC,GAAG,SAAS,CAWtD"}
@@ -0,0 +1,67 @@
1
+ import { useRef, useEffect } from 'react';
2
+ /**
3
+ * 이전 렌더링의 값을 저장하는 hook
4
+ *
5
+ * @param value - 추적할 값
6
+ * @returns 이전 렌더링의 값 (첫 렌더링에서는 undefined)
7
+ *
8
+ * @example
9
+ * // 값 변경 감지
10
+ * function Counter() {
11
+ * const [count, setCount] = useState(0);
12
+ * const prevCount = usePrevious(count);
13
+ *
14
+ * return (
15
+ * <div>
16
+ * <p>Current: {count}</p>
17
+ * <p>Previous: {prevCount}</p>
18
+ * <p>Changed: {count !== prevCount ? 'Yes' : 'No'}</p>
19
+ * <button onClick={() => setCount(count + 1)}>Increment</button>
20
+ * </div>
21
+ * );
22
+ * }
23
+ *
24
+ * @example
25
+ * // 증가/감소 방향 표시
26
+ * function PriceDisplay({ price }: { price: number }) {
27
+ * const prevPrice = usePrevious(price);
28
+ *
29
+ * const trend = prevPrice === undefined
30
+ * ? null
31
+ * : price > prevPrice
32
+ * ? '📈 Up'
33
+ * : price < prevPrice
34
+ * ? '📉 Down'
35
+ * : '➡️ Same';
36
+ *
37
+ * return (
38
+ * <div>
39
+ * <span>${price}</span>
40
+ * {trend && <span>{trend}</span>}
41
+ * </div>
42
+ * );
43
+ * }
44
+ *
45
+ * @example
46
+ * // 애니메이션 방향 결정
47
+ * function AnimatedList({ items }: { items: string[] }) {
48
+ * const prevItems = usePrevious(items);
49
+ * const isAdding = prevItems && items.length > prevItems.length;
50
+ *
51
+ * return (
52
+ * <ul className={isAdding ? 'slide-in' : 'slide-out'}>
53
+ * {items.map(item => <li key={item}>{item}</li>)}
54
+ * </ul>
55
+ * );
56
+ * }
57
+ */
58
+ export function usePrevious(value) {
59
+ // ref는 리렌더링 간에 값을 유지
60
+ const ref = useRef(undefined);
61
+ // 렌더링 후에 현재 값을 ref에 저장
62
+ useEffect(() => {
63
+ ref.current = value;
64
+ }, [value]);
65
+ // 현재 렌더링에서는 이전 값을 반환
66
+ return ref.current;
67
+ }
@@ -0,0 +1,19 @@
1
+ import { Dispatch, SetStateAction } from 'react';
2
+ /**
3
+ * sessionStorage와 동기화되는 state hook
4
+ *
5
+ * @param key - sessionStorage 키
6
+ * @param initialValue - 초기값
7
+ * @returns [storedValue, setValue, removeValue]
8
+ *
9
+ * @example
10
+ * const [token, setToken, removeToken] = useSessionStorage('auth-token', '');
11
+ *
12
+ * // 값 설정
13
+ * setToken('abc123');
14
+ *
15
+ * // 값 제거
16
+ * removeToken();
17
+ */
18
+ export declare function useSessionStorage<T>(key: string, initialValue: T): [T, Dispatch<SetStateAction<T>>, () => void];
19
+ //# sourceMappingURL=useSessionStorage.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useSessionStorage.d.ts","sourceRoot":"","sources":["../../src/hooks/useSessionStorage.ts"],"names":[],"mappings":"AAAA,OAAO,EAAoC,QAAQ,EAAE,cAAc,EAAE,MAAM,OAAO,CAAC;AAEnF;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,iBAAiB,CAAC,CAAC,EACjC,GAAG,EAAE,MAAM,EACX,YAAY,EAAE,CAAC,GACd,CAAC,CAAC,EAAE,QAAQ,CAAC,cAAc,CAAC,CAAC,CAAC,CAAC,EAAE,MAAM,IAAI,CAAC,CAoF9C"}
@@ -0,0 +1,85 @@
1
+ import { useState, useEffect, useCallback } from 'react';
2
+ /**
3
+ * sessionStorage와 동기화되는 state hook
4
+ *
5
+ * @param key - sessionStorage 키
6
+ * @param initialValue - 초기값
7
+ * @returns [storedValue, setValue, removeValue]
8
+ *
9
+ * @example
10
+ * const [token, setToken, removeToken] = useSessionStorage('auth-token', '');
11
+ *
12
+ * // 값 설정
13
+ * setToken('abc123');
14
+ *
15
+ * // 값 제거
16
+ * removeToken();
17
+ */
18
+ export function useSessionStorage(key, initialValue) {
19
+ // SSR 안전성 체크
20
+ const isBrowser = typeof window !== 'undefined';
21
+ // 초기값을 sessionStorage에서 가져오기
22
+ const readValue = useCallback(() => {
23
+ if (!isBrowser) {
24
+ return initialValue;
25
+ }
26
+ try {
27
+ const item = window.sessionStorage.getItem(key);
28
+ return item ? JSON.parse(item) : initialValue;
29
+ }
30
+ catch (error) {
31
+ console.warn(`Error reading sessionStorage key "${key}":`, error);
32
+ return initialValue;
33
+ }
34
+ }, [initialValue, key, isBrowser]);
35
+ const [storedValue, setStoredValue] = useState(readValue);
36
+ // 값 설정 함수
37
+ const setValue = useCallback((value) => {
38
+ if (!isBrowser) {
39
+ console.warn(`Tried setting sessionStorage key "${key}" even though environment is not a client`);
40
+ return;
41
+ }
42
+ try {
43
+ // useState와 동일하게 함수형 업데이트 지원
44
+ const newValue = value instanceof Function ? value(storedValue) : value;
45
+ // sessionStorage에 저장
46
+ window.sessionStorage.setItem(key, JSON.stringify(newValue));
47
+ // state 업데이트
48
+ setStoredValue(newValue);
49
+ // storage event 발생
50
+ window.dispatchEvent(new Event('session-storage'));
51
+ }
52
+ catch (error) {
53
+ console.warn(`Error setting sessionStorage key "${key}":`, error);
54
+ }
55
+ }, [key, storedValue, isBrowser]);
56
+ // 값 제거 함수
57
+ const removeValue = useCallback(() => {
58
+ if (!isBrowser) {
59
+ return;
60
+ }
61
+ try {
62
+ window.sessionStorage.removeItem(key);
63
+ setStoredValue(initialValue);
64
+ window.dispatchEvent(new Event('session-storage'));
65
+ }
66
+ catch (error) {
67
+ console.warn(`Error removing sessionStorage key "${key}":`, error);
68
+ }
69
+ }, [key, initialValue, isBrowser]);
70
+ // 같은 페이지 내의 변경사항 감지
71
+ useEffect(() => {
72
+ if (!isBrowser) {
73
+ return;
74
+ }
75
+ const handleStorageChange = () => {
76
+ setStoredValue(readValue());
77
+ };
78
+ // 같은 페이지 내의 변경사항
79
+ window.addEventListener('session-storage', handleStorageChange);
80
+ return () => {
81
+ window.removeEventListener('session-storage', handleStorageChange);
82
+ };
83
+ }, [key, readValue, isBrowser]);
84
+ return [storedValue, setValue, removeValue];
85
+ }
@@ -0,0 +1,57 @@
1
+ /**
2
+ * 값의 업데이트를 일정 시간 간격으로 제한하는 hook
3
+ * debounce와 달리 일정 간격마다 최신 값을 업데이트합니다.
4
+ *
5
+ * @param value - throttle할 값
6
+ * @param interval - 업데이트 간격 (밀리초, 기본값: 500ms)
7
+ * @returns throttle된 값
8
+ *
9
+ * @example
10
+ * // 스크롤 위치 추적 최적화
11
+ * function ScrollTracker() {
12
+ * const [scrollY, setScrollY] = useState(0);
13
+ * const throttledScrollY = useThrottle(scrollY, 200);
14
+ *
15
+ * useEffect(() => {
16
+ * const handleScroll = () => setScrollY(window.scrollY);
17
+ * window.addEventListener('scroll', handleScroll);
18
+ * return () => window.removeEventListener('scroll', handleScroll);
19
+ * }, []);
20
+ *
21
+ * return <div>Scroll position: {throttledScrollY}px</div>;
22
+ * }
23
+ *
24
+ * @example
25
+ * // 무한 스크롤
26
+ * function InfiniteScroll() {
27
+ * const [scrollY, setScrollY] = useState(0);
28
+ * const throttledScrollY = useThrottle(scrollY, 300);
29
+ *
30
+ * useEffect(() => {
31
+ * const bottom = document.documentElement.scrollHeight - window.innerHeight;
32
+ * if (throttledScrollY >= bottom - 100) {
33
+ * loadMoreData();
34
+ * }
35
+ * }, [throttledScrollY]);
36
+ *
37
+ * return <div>...</div>;
38
+ * }
39
+ *
40
+ * @example
41
+ * // 검색 입력 (실시간 검색에 적합)
42
+ * function LiveSearch() {
43
+ * const [query, setQuery] = useState('');
44
+ * const throttledQuery = useThrottle(query, 500);
45
+ *
46
+ * useEffect(() => {
47
+ * // 500ms마다 최대 한 번씩만 API 호출
48
+ * if (throttledQuery) {
49
+ * searchAPI(throttledQuery);
50
+ * }
51
+ * }, [throttledQuery]);
52
+ *
53
+ * return <input value={query} onChange={(e) => setQuery(e.target.value)} />;
54
+ * }
55
+ */
56
+ export declare function useThrottle<T>(value: T, interval?: number): T;
57
+ //# sourceMappingURL=useThrottle.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useThrottle.d.ts","sourceRoot":"","sources":["../../src/hooks/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,80 @@
1
+ import { useState, useEffect, useRef } from 'react';
2
+ /**
3
+ * 값의 업데이트를 일정 시간 간격으로 제한하는 hook
4
+ * debounce와 달리 일정 간격마다 최신 값을 업데이트합니다.
5
+ *
6
+ * @param value - throttle할 값
7
+ * @param interval - 업데이트 간격 (밀리초, 기본값: 500ms)
8
+ * @returns throttle된 값
9
+ *
10
+ * @example
11
+ * // 스크롤 위치 추적 최적화
12
+ * function ScrollTracker() {
13
+ * const [scrollY, setScrollY] = useState(0);
14
+ * const throttledScrollY = useThrottle(scrollY, 200);
15
+ *
16
+ * useEffect(() => {
17
+ * const handleScroll = () => setScrollY(window.scrollY);
18
+ * window.addEventListener('scroll', handleScroll);
19
+ * return () => window.removeEventListener('scroll', handleScroll);
20
+ * }, []);
21
+ *
22
+ * return <div>Scroll position: {throttledScrollY}px</div>;
23
+ * }
24
+ *
25
+ * @example
26
+ * // 무한 스크롤
27
+ * function InfiniteScroll() {
28
+ * const [scrollY, setScrollY] = useState(0);
29
+ * const throttledScrollY = useThrottle(scrollY, 300);
30
+ *
31
+ * useEffect(() => {
32
+ * const bottom = document.documentElement.scrollHeight - window.innerHeight;
33
+ * if (throttledScrollY >= bottom - 100) {
34
+ * loadMoreData();
35
+ * }
36
+ * }, [throttledScrollY]);
37
+ *
38
+ * return <div>...</div>;
39
+ * }
40
+ *
41
+ * @example
42
+ * // 검색 입력 (실시간 검색에 적합)
43
+ * function LiveSearch() {
44
+ * const [query, setQuery] = useState('');
45
+ * const throttledQuery = useThrottle(query, 500);
46
+ *
47
+ * useEffect(() => {
48
+ * // 500ms마다 최대 한 번씩만 API 호출
49
+ * if (throttledQuery) {
50
+ * searchAPI(throttledQuery);
51
+ * }
52
+ * }, [throttledQuery]);
53
+ *
54
+ * return <input value={query} onChange={(e) => setQuery(e.target.value)} />;
55
+ * }
56
+ */
57
+ export function useThrottle(value, interval = 500) {
58
+ const [throttledValue, setThrottledValue] = useState(value);
59
+ const lastExecuted = useRef(Date.now());
60
+ useEffect(() => {
61
+ // 마지막 실행으로부터 경과된 시간
62
+ const timeSinceLastExecution = Date.now() - lastExecuted.current;
63
+ if (timeSinceLastExecution >= interval) {
64
+ // 간격이 지났으면 즉시 업데이트
65
+ setThrottledValue(value);
66
+ lastExecuted.current = Date.now();
67
+ }
68
+ else {
69
+ // 아직 간격이 안 지났으면 남은 시간 후에 업데이트
70
+ const timer = setTimeout(() => {
71
+ setThrottledValue(value);
72
+ lastExecuted.current = Date.now();
73
+ }, interval - timeSinceLastExecution);
74
+ return () => {
75
+ clearTimeout(timer);
76
+ };
77
+ }
78
+ }, [value, interval]);
79
+ return throttledValue;
80
+ }
@@ -0,0 +1,49 @@
1
+ /**
2
+ * boolean 상태를 쉽게 토글할 수 있는 hook
3
+ *
4
+ * @param initialValue - 초기값 (기본값: false)
5
+ * @returns [현재 값, 토글 함수, 값 설정 함수]
6
+ *
7
+ * @example
8
+ * // 기본 사용
9
+ * function Modal() {
10
+ * const [isOpen, toggleOpen, setIsOpen] = useToggle(false);
11
+ *
12
+ * return (
13
+ * <>
14
+ * <button onClick={toggleOpen}>Toggle Modal</button>
15
+ * <button onClick={() => setIsOpen(true)}>Open Modal</button>
16
+ * <button onClick={() => setIsOpen(false)}>Close Modal</button>
17
+ * {isOpen && <div>Modal Content</div>}
18
+ * </>
19
+ * );
20
+ * }
21
+ *
22
+ * @example
23
+ * // 다크모드 토글
24
+ * function ThemeToggle() {
25
+ * const [isDark, toggleTheme] = useToggle(false);
26
+ *
27
+ * return (
28
+ * <button onClick={toggleTheme}>
29
+ * {isDark ? '🌙 Dark' : '☀️ Light'}
30
+ * </button>
31
+ * );
32
+ * }
33
+ *
34
+ * @example
35
+ * // 메뉴 열기/닫기
36
+ * function Sidebar() {
37
+ * const [isExpanded, toggleExpanded] = useToggle(true);
38
+ *
39
+ * return (
40
+ * <aside className={isExpanded ? 'expanded' : 'collapsed'}>
41
+ * <button onClick={toggleExpanded}>
42
+ * {isExpanded ? '◀' : '▶'}
43
+ * </button>
44
+ * </aside>
45
+ * );
46
+ * }
47
+ */
48
+ export declare function useToggle(initialValue?: boolean): [boolean, () => void, (value: boolean) => void];
49
+ //# sourceMappingURL=useToggle.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useToggle.d.ts","sourceRoot":"","sources":["../../src/hooks/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,56 @@
1
+ import { useState, useCallback } from 'react';
2
+ /**
3
+ * boolean 상태를 쉽게 토글할 수 있는 hook
4
+ *
5
+ * @param initialValue - 초기값 (기본값: false)
6
+ * @returns [현재 값, 토글 함수, 값 설정 함수]
7
+ *
8
+ * @example
9
+ * // 기본 사용
10
+ * function Modal() {
11
+ * const [isOpen, toggleOpen, setIsOpen] = useToggle(false);
12
+ *
13
+ * return (
14
+ * <>
15
+ * <button onClick={toggleOpen}>Toggle Modal</button>
16
+ * <button onClick={() => setIsOpen(true)}>Open Modal</button>
17
+ * <button onClick={() => setIsOpen(false)}>Close Modal</button>
18
+ * {isOpen && <div>Modal Content</div>}
19
+ * </>
20
+ * );
21
+ * }
22
+ *
23
+ * @example
24
+ * // 다크모드 토글
25
+ * function ThemeToggle() {
26
+ * const [isDark, toggleTheme] = useToggle(false);
27
+ *
28
+ * return (
29
+ * <button onClick={toggleTheme}>
30
+ * {isDark ? '🌙 Dark' : '☀️ Light'}
31
+ * </button>
32
+ * );
33
+ * }
34
+ *
35
+ * @example
36
+ * // 메뉴 열기/닫기
37
+ * function Sidebar() {
38
+ * const [isExpanded, toggleExpanded] = useToggle(true);
39
+ *
40
+ * return (
41
+ * <aside className={isExpanded ? 'expanded' : 'collapsed'}>
42
+ * <button onClick={toggleExpanded}>
43
+ * {isExpanded ? '◀' : '▶'}
44
+ * </button>
45
+ * </aside>
46
+ * );
47
+ * }
48
+ */
49
+ export function useToggle(initialValue = false) {
50
+ const [value, setValue] = useState(initialValue);
51
+ // 토글 함수는 리렌더링 시에도 동일한 참조를 유지
52
+ const toggle = useCallback(() => {
53
+ setValue((prev) => !prev);
54
+ }, []);
55
+ return [value, toggle, setValue];
56
+ }