@blastlabs/utils 1.11.0 → 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 (129) 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.d.ts.map +1 -1
  7. package/dist/components/dev/FormDevTools/FormDevTools.js +11 -11
  8. package/dist/components/dev/FormDevTools/index.d.ts +1 -1
  9. package/dist/components/dev/FormDevTools/index.d.ts.map +1 -1
  10. package/dist/components/dev/{IdSelector.d.ts → IdSelector/IdSelector.d.ts} +3 -4
  11. package/dist/components/dev/IdSelector/IdSelector.d.ts.map +1 -0
  12. package/dist/components/dev/IdSelector/IdSelector.js +60 -0
  13. package/dist/components/dev/IdSelector/IdSelector.test.d.ts +2 -0
  14. package/dist/components/dev/IdSelector/IdSelector.test.d.ts.map +1 -0
  15. package/dist/components/dev/IdSelector/IdSelector.test.js +203 -0
  16. package/dist/components/dev/IdSelector/LoginCard.d.ts +17 -0
  17. package/dist/components/dev/IdSelector/LoginCard.d.ts.map +1 -0
  18. package/dist/components/dev/IdSelector/LoginCard.js +16 -0
  19. package/dist/components/dev/IdSelector/index.d.ts +3 -0
  20. package/dist/components/dev/IdSelector/index.d.ts.map +1 -0
  21. package/dist/components/dev/IdSelector/index.js +1 -0
  22. package/dist/components/dev/IdSelector/styles.d.ts +16 -0
  23. package/dist/components/dev/IdSelector/styles.d.ts.map +1 -0
  24. package/dist/components/dev/IdSelector/styles.js +66 -0
  25. package/dist/components/dev/WindowSizeDisplay.d.ts +1 -1
  26. package/dist/components/dev/WindowSizeDisplay.js +2 -2
  27. package/dist/components/dev/ZIndexDebugger.d.ts +1 -1
  28. package/dist/components/dev/ZIndexDebugger.js +1 -1
  29. package/dist/components/dev/index.d.ts +2 -1
  30. package/dist/components/dev/index.d.ts.map +1 -1
  31. package/dist/hooks/event/index.d.ts +6 -0
  32. package/dist/hooks/event/index.d.ts.map +1 -0
  33. package/dist/hooks/event/index.js +5 -0
  34. package/dist/hooks/event/useClickOutside.d.ts.map +1 -0
  35. package/dist/hooks/event/useEventListener.d.ts.map +1 -0
  36. package/dist/hooks/form/__tests__/useCRUDForm.test.d.ts +2 -0
  37. package/dist/hooks/form/__tests__/useCRUDForm.test.d.ts.map +1 -0
  38. package/dist/hooks/form/__tests__/useCRUDForm.test.js +487 -0
  39. package/dist/hooks/form/index.d.ts +5 -0
  40. package/dist/hooks/form/index.d.ts.map +1 -0
  41. package/dist/hooks/form/index.js +4 -0
  42. package/dist/hooks/form/useCRUDForm.d.ts +232 -0
  43. package/dist/hooks/form/useCRUDForm.d.ts.map +1 -0
  44. package/dist/hooks/form/useCRUDForm.js +287 -0
  45. package/dist/hooks/index.d.ts +9 -14
  46. package/dist/hooks/index.d.ts.map +1 -1
  47. package/dist/hooks/index.js +16 -19
  48. package/dist/hooks/performance/index.d.ts +7 -0
  49. package/dist/hooks/performance/index.d.ts.map +1 -0
  50. package/dist/hooks/performance/index.js +6 -0
  51. package/dist/hooks/performance/useDebounce.d.ts.map +1 -0
  52. package/dist/hooks/performance/useIntersectionObserver.d.ts.map +1 -0
  53. package/dist/hooks/performance/useThrottle.d.ts.map +1 -0
  54. package/dist/hooks/state/index.d.ts +6 -0
  55. package/dist/hooks/state/index.d.ts.map +1 -0
  56. package/dist/hooks/state/index.js +5 -0
  57. package/dist/hooks/state/usePrevious.d.ts.map +1 -0
  58. package/dist/hooks/state/useToggle.d.ts.map +1 -0
  59. package/dist/hooks/storage/index.d.ts +7 -0
  60. package/dist/hooks/storage/index.d.ts.map +1 -0
  61. package/dist/hooks/storage/index.js +6 -0
  62. package/dist/hooks/storage/useCopyToClipboard.d.ts.map +1 -0
  63. package/dist/hooks/storage/useLocalStorage.d.ts.map +1 -0
  64. package/dist/hooks/storage/useSessionStorage.d.ts.map +1 -0
  65. package/dist/hooks/time/__tests__/useCountdown.test.d.ts +2 -0
  66. package/dist/hooks/time/__tests__/useCountdown.test.d.ts.map +1 -0
  67. package/dist/hooks/time/__tests__/useCountdown.test.js +150 -0
  68. package/dist/hooks/time/__tests__/useInterval.test.d.ts +2 -0
  69. package/dist/hooks/time/__tests__/useInterval.test.d.ts.map +1 -0
  70. package/dist/hooks/time/__tests__/useInterval.test.js +39 -0
  71. package/dist/hooks/time/__tests__/useStopwatch.test.d.ts +2 -0
  72. package/dist/hooks/time/__tests__/useStopwatch.test.d.ts.map +1 -0
  73. package/dist/hooks/time/__tests__/useStopwatch.test.js +149 -0
  74. package/dist/hooks/time/index.d.ts +7 -0
  75. package/dist/hooks/time/index.d.ts.map +1 -0
  76. package/dist/hooks/time/index.js +6 -0
  77. package/dist/hooks/time/useCountdown.d.ts +116 -0
  78. package/dist/hooks/time/useCountdown.d.ts.map +1 -0
  79. package/dist/hooks/time/useCountdown.js +152 -0
  80. package/dist/hooks/time/useInterval.d.ts +40 -0
  81. package/dist/hooks/time/useInterval.d.ts.map +1 -0
  82. package/dist/hooks/time/useInterval.js +61 -0
  83. package/dist/hooks/time/useStopwatch.d.ts +142 -0
  84. package/dist/hooks/time/useStopwatch.d.ts.map +1 -0
  85. package/dist/hooks/time/useStopwatch.js +179 -0
  86. package/dist/hooks/ui/index.d.ts +6 -0
  87. package/dist/hooks/ui/index.d.ts.map +1 -0
  88. package/dist/hooks/ui/index.js +5 -0
  89. package/dist/hooks/ui/useMediaQuery.d.ts.map +1 -0
  90. package/dist/hooks/ui/useWindowSize.d.ts.map +1 -0
  91. package/package.json +14 -4
  92. package/dist/components/dev/IdSelector.d.ts.map +0 -1
  93. package/dist/components/dev/IdSelector.js +0 -129
  94. package/dist/hooks/useClickOutside.d.ts.map +0 -1
  95. package/dist/hooks/useCopyToClipboard.d.ts.map +0 -1
  96. package/dist/hooks/useDebounce.d.ts.map +0 -1
  97. package/dist/hooks/useEventListener.d.ts.map +0 -1
  98. package/dist/hooks/useIntersectionObserver.d.ts.map +0 -1
  99. package/dist/hooks/useLocalStorage.d.ts.map +0 -1
  100. package/dist/hooks/useMediaQuery.d.ts.map +0 -1
  101. package/dist/hooks/usePrevious.d.ts.map +0 -1
  102. package/dist/hooks/useSessionStorage.d.ts.map +0 -1
  103. package/dist/hooks/useThrottle.d.ts.map +0 -1
  104. package/dist/hooks/useToggle.d.ts.map +0 -1
  105. package/dist/hooks/useWindowSize.d.ts.map +0 -1
  106. /package/dist/hooks/{useClickOutside.d.ts → event/useClickOutside.d.ts} +0 -0
  107. /package/dist/hooks/{useClickOutside.js → event/useClickOutside.js} +0 -0
  108. /package/dist/hooks/{useEventListener.d.ts → event/useEventListener.d.ts} +0 -0
  109. /package/dist/hooks/{useEventListener.js → event/useEventListener.js} +0 -0
  110. /package/dist/hooks/{useDebounce.d.ts → performance/useDebounce.d.ts} +0 -0
  111. /package/dist/hooks/{useDebounce.js → performance/useDebounce.js} +0 -0
  112. /package/dist/hooks/{useIntersectionObserver.d.ts → performance/useIntersectionObserver.d.ts} +0 -0
  113. /package/dist/hooks/{useIntersectionObserver.js → performance/useIntersectionObserver.js} +0 -0
  114. /package/dist/hooks/{useThrottle.d.ts → performance/useThrottle.d.ts} +0 -0
  115. /package/dist/hooks/{useThrottle.js → performance/useThrottle.js} +0 -0
  116. /package/dist/hooks/{usePrevious.d.ts → state/usePrevious.d.ts} +0 -0
  117. /package/dist/hooks/{usePrevious.js → state/usePrevious.js} +0 -0
  118. /package/dist/hooks/{useToggle.d.ts → state/useToggle.d.ts} +0 -0
  119. /package/dist/hooks/{useToggle.js → state/useToggle.js} +0 -0
  120. /package/dist/hooks/{useCopyToClipboard.d.ts → storage/useCopyToClipboard.d.ts} +0 -0
  121. /package/dist/hooks/{useCopyToClipboard.js → storage/useCopyToClipboard.js} +0 -0
  122. /package/dist/hooks/{useLocalStorage.d.ts → storage/useLocalStorage.d.ts} +0 -0
  123. /package/dist/hooks/{useLocalStorage.js → storage/useLocalStorage.js} +0 -0
  124. /package/dist/hooks/{useSessionStorage.d.ts → storage/useSessionStorage.d.ts} +0 -0
  125. /package/dist/hooks/{useSessionStorage.js → storage/useSessionStorage.js} +0 -0
  126. /package/dist/hooks/{useMediaQuery.d.ts → ui/useMediaQuery.d.ts} +0 -0
  127. /package/dist/hooks/{useMediaQuery.js → ui/useMediaQuery.js} +0 -0
  128. /package/dist/hooks/{useWindowSize.d.ts → ui/useWindowSize.d.ts} +0 -0
  129. /package/dist/hooks/{useWindowSize.js → ui/useWindowSize.js} +0 -0
@@ -0,0 +1,40 @@
1
+ /**
2
+ * 일정 시간 간격으로 콜백을 실행하는 hook
3
+ * interval을 자동으로 관리하고 컴포넌트 언마운트 시 정리합니다.
4
+ *
5
+ * @param callback - 일정 간격으로 실행할 함수
6
+ * @param delay - 실행 간격 (밀리초)
7
+ *
8
+ * @example
9
+ * // 1초마다 카운터 증가
10
+ * function TimerComponent() {
11
+ * const [count, setCount] = useState(0);
12
+ *
13
+ * useInterval(() => {
14
+ * setCount(c => c + 1);
15
+ * }, 1000);
16
+ *
17
+ * return <div>Count: {count}</div>;
18
+ * }
19
+ *
20
+ * @example
21
+ * // delay 변경으로 interval 속도 조절
22
+ * function AdjustableTimer() {
23
+ * const [count, setCount] = useState(0);
24
+ * const [delay, setDelay] = useState(1000);
25
+ *
26
+ * useInterval(() => {
27
+ * setCount(c => c + 1);
28
+ * }, delay);
29
+ *
30
+ * return (
31
+ * <div>
32
+ * <div>Count: {count}</div>
33
+ * <button onClick={() => setDelay(500)}>Speed up</button>
34
+ * <button onClick={() => setDelay(2000)}>Slow down</button>
35
+ * </div>
36
+ * );
37
+ * }
38
+ */
39
+ export declare function useInterval(callback: () => void, delay: number): void;
40
+ //# sourceMappingURL=useInterval.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useInterval.d.ts","sourceRoot":"","sources":["../../../src/hooks/time/useInterval.ts"],"names":[],"mappings":"AAEA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAqCG;AACH,wBAAgB,WAAW,CAAC,QAAQ,EAAE,MAAM,IAAI,EAAE,KAAK,EAAE,MAAM,GAAG,IAAI,CA0BrE"}
@@ -0,0 +1,61 @@
1
+ import { useEffect, useRef } from 'react';
2
+ /**
3
+ * 일정 시간 간격으로 콜백을 실행하는 hook
4
+ * interval을 자동으로 관리하고 컴포넌트 언마운트 시 정리합니다.
5
+ *
6
+ * @param callback - 일정 간격으로 실행할 함수
7
+ * @param delay - 실행 간격 (밀리초)
8
+ *
9
+ * @example
10
+ * // 1초마다 카운터 증가
11
+ * function TimerComponent() {
12
+ * const [count, setCount] = useState(0);
13
+ *
14
+ * useInterval(() => {
15
+ * setCount(c => c + 1);
16
+ * }, 1000);
17
+ *
18
+ * return <div>Count: {count}</div>;
19
+ * }
20
+ *
21
+ * @example
22
+ * // delay 변경으로 interval 속도 조절
23
+ * function AdjustableTimer() {
24
+ * const [count, setCount] = useState(0);
25
+ * const [delay, setDelay] = useState(1000);
26
+ *
27
+ * useInterval(() => {
28
+ * setCount(c => c + 1);
29
+ * }, delay);
30
+ *
31
+ * return (
32
+ * <div>
33
+ * <div>Count: {count}</div>
34
+ * <button onClick={() => setDelay(500)}>Speed up</button>
35
+ * <button onClick={() => setDelay(2000)}>Slow down</button>
36
+ * </div>
37
+ * );
38
+ * }
39
+ */
40
+ export function useInterval(callback, delay) {
41
+ const intervalRef = useRef(null);
42
+ const stopInterval = () => {
43
+ if (intervalRef.current) {
44
+ clearInterval(intervalRef.current);
45
+ }
46
+ };
47
+ const startInterval = (nextDelay) => {
48
+ stopInterval();
49
+ intervalRef.current = setInterval(() => {
50
+ callback();
51
+ }, nextDelay);
52
+ };
53
+ useEffect(() => {
54
+ startInterval(delay);
55
+ }, [delay]);
56
+ useEffect(() => {
57
+ return () => {
58
+ stopInterval();
59
+ };
60
+ }, []);
61
+ }
@@ -0,0 +1,142 @@
1
+ export interface UseStopwatchOptions {
2
+ /** 업데이트 간격 (밀리초, 기본값: 10ms) */
3
+ interval?: number;
4
+ /** 자동 시작 여부 (기본값: false) */
5
+ autoStart?: boolean;
6
+ }
7
+ export interface UseStopwatchReturn {
8
+ /** 경과 시간 (밀리초) */
9
+ elapsed: number;
10
+ /** 경과 시간 (초) */
11
+ elapsedSeconds: number;
12
+ /** 스톱워치 시작 */
13
+ start: () => void;
14
+ /** 스톱워치 일시정지 */
15
+ pause: () => void;
16
+ /** 스톱워치 재개 */
17
+ resume: () => void;
18
+ /** 스톱워치 리셋 */
19
+ reset: () => void;
20
+ /** 스톱워치 실행 중 여부 */
21
+ isRunning: boolean;
22
+ }
23
+ /**
24
+ * 스톱워치 hook
25
+ *
26
+ * 경과 시간을 측정하는 스톱워치를 제공합니다.
27
+ *
28
+ * @example
29
+ * ```tsx
30
+ * // 기본 사용
31
+ * function Stopwatch() {
32
+ * const { elapsed, elapsedSeconds, start, pause, reset, isRunning } = useStopwatch();
33
+ *
34
+ * return (
35
+ * <div>
36
+ * <div>경과 시간: {elapsedSeconds.toFixed(2)}초</div>
37
+ * <div>밀리초: {elapsed}ms</div>
38
+ * <button onClick={start} disabled={isRunning}>시작</button>
39
+ * <button onClick={pause} disabled={!isRunning}>일시정지</button>
40
+ * <button onClick={reset}>리셋</button>
41
+ * </div>
42
+ * );
43
+ * }
44
+ * ```
45
+ *
46
+ * @example
47
+ * ```tsx
48
+ * // 포맷팅된 시간 표시
49
+ * function FormattedStopwatch() {
50
+ * const { elapsed } = useStopwatch({ autoStart: true });
51
+ *
52
+ * const formatTime = (ms: number) => {
53
+ * const seconds = Math.floor(ms / 1000);
54
+ * const minutes = Math.floor(seconds / 60);
55
+ * const hours = Math.floor(minutes / 60);
56
+ *
57
+ * const displaySeconds = seconds % 60;
58
+ * const displayMinutes = minutes % 60;
59
+ * const displayMs = ms % 1000;
60
+ *
61
+ * return `${hours.toString().padStart(2, '0')}:${displayMinutes
62
+ * .toString()
63
+ * .padStart(2, '0')}:${displaySeconds.toString().padStart(2, '0')}.${Math.floor(
64
+ * displayMs / 10
65
+ * )
66
+ * .toString()
67
+ * .padStart(2, '0')}`;
68
+ * };
69
+ *
70
+ * return <div>{formatTime(elapsed)}</div>;
71
+ * }
72
+ * ```
73
+ *
74
+ * @example
75
+ * ```tsx
76
+ * // 랩 타임 기록
77
+ * function LapTimer() {
78
+ * const { elapsed, start, pause, reset, isRunning } = useStopwatch();
79
+ * const [laps, setLaps] = useState<number[]>([]);
80
+ *
81
+ * const handleLap = () => {
82
+ * setLaps((prev) => [...prev, elapsed]);
83
+ * };
84
+ *
85
+ * const handleReset = () => {
86
+ * reset();
87
+ * setLaps([]);
88
+ * };
89
+ *
90
+ * return (
91
+ * <div>
92
+ * <div>경과 시간: {(elapsed / 1000).toFixed(2)}초</div>
93
+ * <button onClick={isRunning ? pause : start}>
94
+ * {isRunning ? '일시정지' : '시작'}
95
+ * </button>
96
+ * <button onClick={handleLap} disabled={!isRunning}>
97
+ * 랩
98
+ * </button>
99
+ * <button onClick={handleReset}>리셋</button>
100
+ *
101
+ * <div>
102
+ * <h3>랩 타임</h3>
103
+ * {laps.map((lap, index) => (
104
+ * <div key={index}>
105
+ * Lap {index + 1}: {(lap / 1000).toFixed(2)}초
106
+ * </div>
107
+ * ))}
108
+ * </div>
109
+ * </div>
110
+ * );
111
+ * }
112
+ * ```
113
+ *
114
+ * @example
115
+ * ```tsx
116
+ * // 성능 측정
117
+ * function PerformanceTracker() {
118
+ * const { elapsed, start, pause, reset } = useStopwatch();
119
+ *
120
+ * const handleTaskStart = () => {
121
+ * reset(); // 이전 측정 리셋
122
+ * start(); // 새 측정 시작
123
+ * // 작업 수행...
124
+ * };
125
+ *
126
+ * const handleTaskEnd = () => {
127
+ * pause();
128
+ * console.log(`작업 완료 시간: ${elapsed}ms`);
129
+ * };
130
+ *
131
+ * return (
132
+ * <div>
133
+ * <button onClick={handleTaskStart}>작업 시작</button>
134
+ * <button onClick={handleTaskEnd}>작업 완료</button>
135
+ * <div>측정 시간: {elapsed}ms</div>
136
+ * </div>
137
+ * );
138
+ * }
139
+ * ```
140
+ */
141
+ export declare function useStopwatch(options?: UseStopwatchOptions): UseStopwatchReturn;
142
+ //# sourceMappingURL=useStopwatch.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useStopwatch.d.ts","sourceRoot":"","sources":["../../../src/hooks/time/useStopwatch.ts"],"names":[],"mappings":"AAEA,MAAM,WAAW,mBAAmB;IAClC,+BAA+B;IAC/B,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,4BAA4B;IAC5B,SAAS,CAAC,EAAE,OAAO,CAAC;CACrB;AAED,MAAM,WAAW,kBAAkB;IACjC,kBAAkB;IAClB,OAAO,EAAE,MAAM,CAAC;IAChB,gBAAgB;IAChB,cAAc,EAAE,MAAM,CAAC;IACvB,cAAc;IACd,KAAK,EAAE,MAAM,IAAI,CAAC;IAClB,gBAAgB;IAChB,KAAK,EAAE,MAAM,IAAI,CAAC;IAClB,cAAc;IACd,MAAM,EAAE,MAAM,IAAI,CAAC;IACnB,cAAc;IACd,KAAK,EAAE,MAAM,IAAI,CAAC;IAClB,mBAAmB;IACnB,SAAS,EAAE,OAAO,CAAC;CACpB;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAqHG;AACH,wBAAgB,YAAY,CAAC,OAAO,GAAE,mBAAwB,GAAG,kBAAkB,CAqElF"}
@@ -0,0 +1,179 @@
1
+ import { useState, useCallback, useRef, useEffect } from 'react';
2
+ /**
3
+ * 스톱워치 hook
4
+ *
5
+ * 경과 시간을 측정하는 스톱워치를 제공합니다.
6
+ *
7
+ * @example
8
+ * ```tsx
9
+ * // 기본 사용
10
+ * function Stopwatch() {
11
+ * const { elapsed, elapsedSeconds, start, pause, reset, isRunning } = useStopwatch();
12
+ *
13
+ * return (
14
+ * <div>
15
+ * <div>경과 시간: {elapsedSeconds.toFixed(2)}초</div>
16
+ * <div>밀리초: {elapsed}ms</div>
17
+ * <button onClick={start} disabled={isRunning}>시작</button>
18
+ * <button onClick={pause} disabled={!isRunning}>일시정지</button>
19
+ * <button onClick={reset}>리셋</button>
20
+ * </div>
21
+ * );
22
+ * }
23
+ * ```
24
+ *
25
+ * @example
26
+ * ```tsx
27
+ * // 포맷팅된 시간 표시
28
+ * function FormattedStopwatch() {
29
+ * const { elapsed } = useStopwatch({ autoStart: true });
30
+ *
31
+ * const formatTime = (ms: number) => {
32
+ * const seconds = Math.floor(ms / 1000);
33
+ * const minutes = Math.floor(seconds / 60);
34
+ * const hours = Math.floor(minutes / 60);
35
+ *
36
+ * const displaySeconds = seconds % 60;
37
+ * const displayMinutes = minutes % 60;
38
+ * const displayMs = ms % 1000;
39
+ *
40
+ * return `${hours.toString().padStart(2, '0')}:${displayMinutes
41
+ * .toString()
42
+ * .padStart(2, '0')}:${displaySeconds.toString().padStart(2, '0')}.${Math.floor(
43
+ * displayMs / 10
44
+ * )
45
+ * .toString()
46
+ * .padStart(2, '0')}`;
47
+ * };
48
+ *
49
+ * return <div>{formatTime(elapsed)}</div>;
50
+ * }
51
+ * ```
52
+ *
53
+ * @example
54
+ * ```tsx
55
+ * // 랩 타임 기록
56
+ * function LapTimer() {
57
+ * const { elapsed, start, pause, reset, isRunning } = useStopwatch();
58
+ * const [laps, setLaps] = useState<number[]>([]);
59
+ *
60
+ * const handleLap = () => {
61
+ * setLaps((prev) => [...prev, elapsed]);
62
+ * };
63
+ *
64
+ * const handleReset = () => {
65
+ * reset();
66
+ * setLaps([]);
67
+ * };
68
+ *
69
+ * return (
70
+ * <div>
71
+ * <div>경과 시간: {(elapsed / 1000).toFixed(2)}초</div>
72
+ * <button onClick={isRunning ? pause : start}>
73
+ * {isRunning ? '일시정지' : '시작'}
74
+ * </button>
75
+ * <button onClick={handleLap} disabled={!isRunning}>
76
+ * 랩
77
+ * </button>
78
+ * <button onClick={handleReset}>리셋</button>
79
+ *
80
+ * <div>
81
+ * <h3>랩 타임</h3>
82
+ * {laps.map((lap, index) => (
83
+ * <div key={index}>
84
+ * Lap {index + 1}: {(lap / 1000).toFixed(2)}초
85
+ * </div>
86
+ * ))}
87
+ * </div>
88
+ * </div>
89
+ * );
90
+ * }
91
+ * ```
92
+ *
93
+ * @example
94
+ * ```tsx
95
+ * // 성능 측정
96
+ * function PerformanceTracker() {
97
+ * const { elapsed, start, pause, reset } = useStopwatch();
98
+ *
99
+ * const handleTaskStart = () => {
100
+ * reset(); // 이전 측정 리셋
101
+ * start(); // 새 측정 시작
102
+ * // 작업 수행...
103
+ * };
104
+ *
105
+ * const handleTaskEnd = () => {
106
+ * pause();
107
+ * console.log(`작업 완료 시간: ${elapsed}ms`);
108
+ * };
109
+ *
110
+ * return (
111
+ * <div>
112
+ * <button onClick={handleTaskStart}>작업 시작</button>
113
+ * <button onClick={handleTaskEnd}>작업 완료</button>
114
+ * <div>측정 시간: {elapsed}ms</div>
115
+ * </div>
116
+ * );
117
+ * }
118
+ * ```
119
+ */
120
+ export function useStopwatch(options = {}) {
121
+ const { interval = 10, autoStart = false } = options;
122
+ const [elapsed, setElapsed] = useState(0);
123
+ const [isRunning, setIsRunning] = useState(autoStart);
124
+ const intervalRef = useRef(null);
125
+ const startTimeRef = useRef(null);
126
+ const previousElapsedRef = useRef(0);
127
+ const clear = useCallback(() => {
128
+ if (intervalRef.current) {
129
+ clearInterval(intervalRef.current);
130
+ intervalRef.current = null;
131
+ }
132
+ }, []);
133
+ const start = useCallback(() => {
134
+ if (!isRunning) {
135
+ startTimeRef.current = Date.now() - previousElapsedRef.current;
136
+ setIsRunning(true);
137
+ }
138
+ }, [isRunning]);
139
+ const pause = useCallback(() => {
140
+ clear();
141
+ setIsRunning(false);
142
+ previousElapsedRef.current = elapsed;
143
+ }, [clear, elapsed]);
144
+ const resume = useCallback(() => {
145
+ if (!isRunning) {
146
+ startTimeRef.current = Date.now() - previousElapsedRef.current;
147
+ setIsRunning(true);
148
+ }
149
+ }, [isRunning]);
150
+ const reset = useCallback(() => {
151
+ clear();
152
+ setElapsed(0);
153
+ setIsRunning(false);
154
+ startTimeRef.current = null;
155
+ previousElapsedRef.current = 0;
156
+ }, [clear]);
157
+ useEffect(() => {
158
+ if (isRunning) {
159
+ if (startTimeRef.current === null) {
160
+ startTimeRef.current = Date.now() - previousElapsedRef.current;
161
+ }
162
+ intervalRef.current = setInterval(() => {
163
+ if (startTimeRef.current !== null) {
164
+ setElapsed(Date.now() - startTimeRef.current);
165
+ }
166
+ }, interval);
167
+ return () => clear();
168
+ }
169
+ }, [isRunning, interval, clear]);
170
+ return {
171
+ elapsed,
172
+ elapsedSeconds: elapsed / 1000,
173
+ start,
174
+ pause,
175
+ resume,
176
+ reset,
177
+ isRunning,
178
+ };
179
+ }
@@ -0,0 +1,6 @@
1
+ /**
2
+ * UI/UX Hooks
3
+ */
4
+ export * from './useMediaQuery';
5
+ export * from './useWindowSize';
6
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +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"}
@@ -0,0 +1,5 @@
1
+ /**
2
+ * UI/UX Hooks
3
+ */
4
+ export * from './useMediaQuery';
5
+ export * from './useWindowSize';
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useMediaQuery.d.ts","sourceRoot":"","sources":["../../../src/hooks/ui/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 @@
1
+ {"version":3,"file":"useWindowSize.d.ts","sourceRoot":"","sources":["../../../src/hooks/ui/useWindowSize.ts"],"names":[],"mappings":"AAEA;;GAEG;AACH,MAAM,WAAW,UAAU;IACzB,kBAAkB;IAClB,KAAK,EAAE,MAAM,CAAC;IACd,kBAAkB;IAClB,MAAM,EAAE,MAAM,CAAC;CAChB;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8CG;AACH,wBAAgB,aAAa,IAAI,UAAU,CAoC1C"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@blastlabs/utils",
3
- "version": "1.11.0",
3
+ "version": "1.12.0",
4
4
  "type": "module",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -10,7 +10,7 @@
10
10
  "scripts": {
11
11
  "prepare": "npm run build:tsc",
12
12
  "prepublishOnly": "npm run test:run && npm run clean && npm run build:tsc",
13
- "build": "npm run build",
13
+ "build": "npm run clean && npm run build:tsc",
14
14
  "build:tsc": "tsc",
15
15
  "clean": "rm -rf dist",
16
16
  "dev": "tsc --watch",
@@ -62,15 +62,22 @@
62
62
  "dayjs": "^1.11.19"
63
63
  },
64
64
  "devDependencies": {
65
+ "@testing-library/react": "^16.3.1",
66
+ "@testing-library/user-event": "^14.6.1",
65
67
  "@types/node": "^25.0.6",
66
- "typescript": "^5.9.3",
67
68
  "@types/react": "^19.2.8",
68
69
  "@vitest/ui": "^4.0.16",
70
+ "happy-dom": "^20.1.0",
71
+ "react": "^19.2.3",
72
+ "react-dom": "^19.2.3",
73
+ "react-hook-form": "^7.71.1",
74
+ "typescript": "^5.9.3",
69
75
  "vitest": "^4.0.16"
70
76
  },
71
77
  "peerDependencies": {
72
78
  "react": ">=16.8.0",
73
- "react-dom": ">=16.8.0"
79
+ "react-dom": ">=16.8.0",
80
+ "react-hook-form": ">=7.0.0"
74
81
  },
75
82
  "peerDependenciesMeta": {
76
83
  "react": {
@@ -78,6 +85,9 @@
78
85
  },
79
86
  "react-dom": {
80
87
  "optional": true
88
+ },
89
+ "react-hook-form": {
90
+ "optional": true
81
91
  }
82
92
  },
83
93
  "publishConfig": {
@@ -1 +0,0 @@
1
- {"version":3,"file":"IdSelector.d.ts","sourceRoot":"","sources":["../../../src/components/dev/IdSelector.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAkC,MAAM,OAAO,CAAC;AAEvD,KAAK,SAAS,GAAG;IACf,EAAE,EAAE,MAAM,CAAC;IACX,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;CACd,CAAC;AAEF,KAAK,KAAK,GAAG;IACX,OAAO,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IAC5D,KAAK,EAAE,SAAS,EAAE,CAAC;CACpB,CAAC;AAEF;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAoCG;AACH,MAAM,CAAC,OAAO,UAAU,UAAU,CAAC,EAAE,OAAO,EAAE,KAAK,EAAE,EAAE,KAAK,qBAuH3D"}
@@ -1,129 +0,0 @@
1
- import React, { useState } from 'react';
2
- /**
3
- * 개발용 로그인 shortcut 컴포넌트
4
- *
5
- * @example
6
- * ```tsx
7
- * // Vite 프로젝트
8
- * import { IdSelector } from 'goodchuck-utils/components/dev';
9
- *
10
- * function LoginPage() {
11
- * const handleLogin = async (email: string, password: string) => {
12
- * await loginAPI(email, password);
13
- * };
14
- *
15
- * const devAccounts = [
16
- * { id: 'admin@test.com', pw: 'admin123', memo: '관리자' },
17
- * { id: 'user@test.com', pw: 'user123', memo: '일반 사용자' },
18
- * ];
19
- *
20
- * return (
21
- * <div>
22
- * <LoginForm />
23
- * {import.meta.env.DEV && (
24
- * <IdSelector onLogin={handleLogin} infos={devAccounts} />
25
- * )}
26
- * </div>
27
- * );
28
- * }
29
- * ```
30
- *
31
- * @example
32
- * ```tsx
33
- * // Create React App 프로젝트
34
- * {process.env.NODE_ENV === 'development' && (
35
- * <IdSelector onLogin={handleLogin} infos={devAccounts} />
36
- * )}
37
- * ```
38
- */
39
- export default function IdSelector({ onLogin, infos }) {
40
- const [loading, setLoading] = useState(null);
41
- const [hoveredIndex, setHoveredIndex] = useState(null);
42
- const [hoveredButton, setHoveredButton] = useState(null);
43
- const handleQuickLogin = async (info, index) => {
44
- setLoading(index);
45
- try {
46
- await onLogin(info.id, info.pw);
47
- }
48
- finally {
49
- setLoading(null);
50
- }
51
- };
52
- const containerStyle = {
53
- position: 'fixed',
54
- top: '50%',
55
- right: '16px',
56
- transform: 'translateY(-50%)',
57
- display: 'flex',
58
- flexDirection: 'column',
59
- gap: '12px',
60
- padding: '16px',
61
- backgroundColor: '#ffffff',
62
- borderRadius: '12px',
63
- boxShadow: '0 20px 25px -5px rgba(0, 0, 0, 0.1), 0 10px 10px -5px rgba(0, 0, 0, 0.04)',
64
- border: '1px solid #e5e7eb',
65
- minWidth: '280px',
66
- zIndex: 9999,
67
- };
68
- const headerStyle = {
69
- color: '#111827',
70
- fontSize: '14px',
71
- fontWeight: 'bold',
72
- paddingBottom: '8px',
73
- borderBottom: '1px solid #e5e7eb',
74
- };
75
- const getCardStyle = (index) => ({
76
- display: 'flex',
77
- flexDirection: 'column',
78
- gap: '8px',
79
- padding: '12px',
80
- backgroundColor: '#f9fafb',
81
- borderRadius: '8px',
82
- border: hoveredIndex === index ? '1px solid #60a5fa' : '1px solid #e5e7eb',
83
- transition: 'all 0.2s',
84
- });
85
- const getButtonStyle = (index) => ({
86
- width: '100%',
87
- padding: '8px 16px',
88
- backgroundColor: loading === index ? '#9ca3af' : hoveredButton === index ? '#2563eb' : '#3b82f6',
89
- color: 'white',
90
- fontWeight: 600,
91
- borderRadius: '8px',
92
- border: 'none',
93
- cursor: loading === index ? 'not-allowed' : 'pointer',
94
- transition: 'background-color 0.2s',
95
- });
96
- const infoContainerStyle = {
97
- display: 'flex',
98
- flexDirection: 'column',
99
- gap: '4px',
100
- fontSize: '12px',
101
- color: '#6b7280',
102
- paddingLeft: '4px',
103
- paddingRight: '4px',
104
- };
105
- const infoRowStyle = {
106
- display: 'flex',
107
- alignItems: 'center',
108
- gap: '8px',
109
- };
110
- const labelStyle = {
111
- fontWeight: 600,
112
- minWidth: '24px',
113
- };
114
- const valueStyle = {
115
- fontFamily: 'monospace',
116
- color: '#374151',
117
- };
118
- return (React.createElement("div", { style: containerStyle },
119
- React.createElement("div", { style: headerStyle }, "\uD83D\uDE80 \uAC1C\uBC1C\uC6A9 \uBE60\uB978 \uB85C\uADF8\uC778"),
120
- infos.map((info, index) => (React.createElement("div", { key: index, style: getCardStyle(index), onMouseEnter: () => setHoveredIndex(index), onMouseLeave: () => setHoveredIndex(null) },
121
- React.createElement("button", { onClick: () => handleQuickLogin(info, index), disabled: loading === index, style: getButtonStyle(index), onMouseEnter: () => setHoveredButton(index), onMouseLeave: () => setHoveredButton(null) }, loading === index ? '로그인 중...' : info.memo),
122
- React.createElement("div", { style: infoContainerStyle },
123
- React.createElement("div", { style: infoRowStyle },
124
- React.createElement("span", { style: labelStyle }, "ID"),
125
- React.createElement("span", { style: valueStyle }, info.id)),
126
- React.createElement("div", { style: infoRowStyle },
127
- React.createElement("span", { style: labelStyle }, "PW"),
128
- React.createElement("span", { style: valueStyle }, info.pw))))))));
129
- }
@@ -1 +0,0 @@
1
- {"version":3,"file":"useClickOutside.d.ts","sourceRoot":"","sources":["../../src/hooks/useClickOutside.ts"],"names":[],"mappings":"AAAA,OAAO,EAAa,SAAS,EAAE,MAAM,OAAO,CAAC;AAE7C;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AACH,wBAAgB,eAAe,CAAC,CAAC,SAAS,WAAW,GAAG,WAAW,EACjE,GAAG,EAAE,SAAS,CAAC,CAAC,CAAC,EACjB,OAAO,EAAE,CAAC,KAAK,EAAE,UAAU,GAAG,UAAU,KAAK,IAAI,EACjD,OAAO,GAAE,OAAc,GACtB,IAAI,CA2BN;AAED;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAgB,uBAAuB,CAAC,CAAC,SAAS,WAAW,GAAG,WAAW,EACzE,IAAI,EAAE,SAAS,CAAC,CAAC,CAAC,EAAE,EACpB,OAAO,EAAE,CAAC,KAAK,EAAE,UAAU,GAAG,UAAU,KAAK,IAAI,EACjD,OAAO,GAAE,OAAc,GACtB,IAAI,CA6BN"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"useCopyToClipboard.d.ts","sourceRoot":"","sources":["../../src/hooks/useCopyToClipboard.ts"],"names":[],"mappings":"AAEA;;GAEG;AACH,MAAM,WAAW,qBAAqB;IACpC,8BAA8B;IAC9B,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,oBAAoB;IACpB,IAAI,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC,OAAO,CAAC,CAAC;IACzC,sBAAsB;IACtB,KAAK,EAAE,MAAM,IAAI,CAAC;CACnB;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAqDG;AACH,wBAAgB,kBAAkB,IAAI,qBAAqB,CA0B1D"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"useDebounce.d.ts","sourceRoot":"","sources":["../../src/hooks/useDebounce.ts"],"names":[],"mappings":"AAEA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4CG;AACH,wBAAgB,WAAW,CAAC,CAAC,EAAE,KAAK,EAAE,CAAC,EAAE,KAAK,GAAE,MAAY,GAAG,CAAC,CAgB/D"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"useEventListener.d.ts","sourceRoot":"","sources":["../../src/hooks/useEventListener.ts"],"names":[],"mappings":"AAAA,OAAO,EAAqB,SAAS,EAAE,MAAM,OAAO,CAAC;AAErD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAyEG;AACH,wBAAgB,gBAAgB,CAAC,CAAC,SAAS,MAAM,cAAc,EAC7D,SAAS,EAAE,CAAC,EACZ,OAAO,EAAE,CAAC,KAAK,EAAE,cAAc,CAAC,CAAC,CAAC,KAAK,IAAI,EAC3C,OAAO,CAAC,EAAE,SAAS,EACnB,OAAO,CAAC,EAAE,OAAO,GAAG,uBAAuB,GAC1C,IAAI,CAAC;AAER,wBAAgB,gBAAgB,CAAC,CAAC,SAAS,MAAM,mBAAmB,EAAE,CAAC,SAAS,WAAW,GAAG,cAAc,EAC1G,SAAS,EAAE,CAAC,EACZ,OAAO,EAAE,CAAC,KAAK,EAAE,mBAAmB,CAAC,CAAC,CAAC,KAAK,IAAI,EAChD,OAAO,EAAE,SAAS,CAAC,CAAC,CAAC,EACrB,OAAO,CAAC,EAAE,OAAO,GAAG,uBAAuB,GAC1C,IAAI,CAAC;AAER,wBAAgB,gBAAgB,CAAC,CAAC,SAAS,MAAM,gBAAgB,EAC/D,SAAS,EAAE,CAAC,EACZ,OAAO,EAAE,CAAC,KAAK,EAAE,gBAAgB,CAAC,CAAC,CAAC,KAAK,IAAI,EAC7C,OAAO,EAAE,QAAQ,EACjB,OAAO,CAAC,EAAE,OAAO,GAAG,uBAAuB,GAC1C,IAAI,CAAC"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"useIntersectionObserver.d.ts","sourceRoot":"","sources":["../../src/hooks/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"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"useLocalStorage.d.ts","sourceRoot":"","sources":["../../src/hooks/useLocalStorage.ts"],"names":[],"mappings":"AAAA,OAAO,EAAoC,QAAQ,EAAE,cAAc,EAAE,MAAM,OAAO,CAAC;AAEnF;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,eAAe,CAAC,CAAC,EAC/B,GAAG,EAAE,MAAM,EACX,YAAY,EAAE,CAAC,GACd,CAAC,CAAC,EAAE,QAAQ,CAAC,cAAc,CAAC,CAAC,CAAC,CAAC,EAAE,MAAM,IAAI,CAAC,CA0F9C"}
@@ -1 +0,0 @@
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"}
@@ -1 +0,0 @@
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"}
@@ -1 +0,0 @@
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"}