goodchuck-utils 1.4.2 → 1.5.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 (37) hide show
  1. package/dist/components/dev/IdSelector.d.ts +41 -0
  2. package/dist/components/dev/IdSelector.d.ts.map +1 -0
  3. package/dist/components/dev/IdSelector.js +52 -0
  4. package/dist/components/dev/index.d.ts +8 -0
  5. package/dist/components/dev/index.d.ts.map +1 -0
  6. package/dist/components/dev/index.js +7 -0
  7. package/dist/components/index.d.ts +8 -0
  8. package/dist/components/index.d.ts.map +1 -0
  9. package/dist/components/index.js +7 -0
  10. package/dist/hooks/index.d.ts +8 -0
  11. package/dist/hooks/index.d.ts.map +1 -1
  12. package/dist/hooks/index.js +11 -0
  13. package/dist/hooks/useCopyToClipboard.d.ts +67 -0
  14. package/dist/hooks/useCopyToClipboard.d.ts.map +1 -0
  15. package/dist/hooks/useCopyToClipboard.js +79 -0
  16. package/dist/hooks/useDebounce.d.ts +47 -0
  17. package/dist/hooks/useDebounce.d.ts.map +1 -0
  18. package/dist/hooks/useDebounce.js +60 -0
  19. package/dist/hooks/useEventListener.d.ts +79 -0
  20. package/dist/hooks/useEventListener.d.ts.map +1 -0
  21. package/dist/hooks/useEventListener.js +33 -0
  22. package/dist/hooks/useIntersectionObserver.d.ts +109 -0
  23. package/dist/hooks/useIntersectionObserver.d.ts.map +1 -0
  24. package/dist/hooks/useIntersectionObserver.js +128 -0
  25. package/dist/hooks/usePrevious.d.ts +58 -0
  26. package/dist/hooks/usePrevious.d.ts.map +1 -0
  27. package/dist/hooks/usePrevious.js +67 -0
  28. package/dist/hooks/useThrottle.d.ts +57 -0
  29. package/dist/hooks/useThrottle.d.ts.map +1 -0
  30. package/dist/hooks/useThrottle.js +80 -0
  31. package/dist/hooks/useToggle.d.ts +49 -0
  32. package/dist/hooks/useToggle.d.ts.map +1 -0
  33. package/dist/hooks/useToggle.js +56 -0
  34. package/dist/hooks/useWindowSize.d.ts +58 -0
  35. package/dist/hooks/useWindowSize.d.ts.map +1 -0
  36. package/dist/hooks/useWindowSize.js +79 -0
  37. package/package.json +22 -2
@@ -0,0 +1,128 @@
1
+ import { useEffect, useState } from 'react';
2
+ /**
3
+ * Intersection Observer를 사용하여 요소의 가시성을 감지하는 hook
4
+ *
5
+ * @param ref - 관찰할 요소의 ref
6
+ * @param options - Intersection Observer 옵션
7
+ * @returns IntersectionObserverEntry 또는 undefined
8
+ *
9
+ * @example
10
+ * // 기본 사용 - 요소가 화면에 보이는지 감지
11
+ * function LazyImage({ src }: { src: string }) {
12
+ * const imageRef = useRef<HTMLImageElement>(null);
13
+ * const entry = useIntersectionObserver(imageRef, {
14
+ * threshold: 0.1,
15
+ * freezeOnceVisible: true
16
+ * });
17
+ * const isVisible = entry?.isIntersecting;
18
+ *
19
+ * return (
20
+ * <img
21
+ * ref={imageRef}
22
+ * src={isVisible ? src : undefined}
23
+ * alt="lazy loaded"
24
+ * />
25
+ * );
26
+ * }
27
+ *
28
+ * @example
29
+ * // 무한 스크롤
30
+ * function InfiniteScrollList() {
31
+ * const loadMoreRef = useRef<HTMLDivElement>(null);
32
+ * const entry = useIntersectionObserver(loadMoreRef, {
33
+ * threshold: 1.0
34
+ * });
35
+ *
36
+ * useEffect(() => {
37
+ * if (entry?.isIntersecting) {
38
+ * loadMoreItems();
39
+ * }
40
+ * }, [entry?.isIntersecting]);
41
+ *
42
+ * return (
43
+ * <div>
44
+ * {items.map(item => <Item key={item.id} {...item} />)}
45
+ * <div ref={loadMoreRef}>Loading...</div>
46
+ * </div>
47
+ * );
48
+ * }
49
+ *
50
+ * @example
51
+ * // 애니메이션 트리거
52
+ * function AnimatedSection() {
53
+ * const sectionRef = useRef<HTMLElement>(null);
54
+ * const entry = useIntersectionObserver(sectionRef, {
55
+ * threshold: 0.5,
56
+ * freezeOnceVisible: true
57
+ * });
58
+ *
59
+ * return (
60
+ * <section
61
+ * ref={sectionRef}
62
+ * className={entry?.isIntersecting ? 'fade-in' : 'fade-out'}
63
+ * >
64
+ * Content
65
+ * </section>
66
+ * );
67
+ * }
68
+ *
69
+ * @example
70
+ * // rootMargin 사용 (요소가 화면에 들어오기 전에 미리 감지)
71
+ * function PreloadImage({ src }: { src: string }) {
72
+ * const imageRef = useRef<HTMLImageElement>(null);
73
+ * const entry = useIntersectionObserver(imageRef, {
74
+ * rootMargin: '200px', // 화면 기준 200px 전에 감지
75
+ * freezeOnceVisible: true
76
+ * });
77
+ *
78
+ * return <img ref={imageRef} src={entry?.isIntersecting ? src : undefined} />;
79
+ * }
80
+ */
81
+ export function useIntersectionObserver(ref, options = {}) {
82
+ const { threshold = 0, root = null, rootMargin = '0%', freezeOnceVisible = false } = options;
83
+ const [entry, setEntry] = useState();
84
+ const frozen = entry?.isIntersecting && freezeOnceVisible;
85
+ useEffect(() => {
86
+ const element = ref.current;
87
+ // 요소가 없거나, 이미 frozen 상태면 observer 생성하지 않음
88
+ if (!element || frozen) {
89
+ return;
90
+ }
91
+ // Intersection Observer가 지원되지 않는 환경 체크
92
+ if (!window.IntersectionObserver) {
93
+ console.warn('IntersectionObserver is not supported in this browser');
94
+ return;
95
+ }
96
+ const observer = new IntersectionObserver(([entry]) => {
97
+ setEntry(entry);
98
+ }, { threshold, root, rootMargin });
99
+ observer.observe(element);
100
+ return () => {
101
+ observer.disconnect();
102
+ };
103
+ }, [ref, threshold, root, rootMargin, frozen]);
104
+ return entry;
105
+ }
106
+ /**
107
+ * 요소가 화면에 보이는지 여부만 반환하는 간단한 버전
108
+ *
109
+ * @param ref - 관찰할 요소의 ref
110
+ * @param options - Intersection Observer 옵션
111
+ * @returns 요소가 화면에 보이는지 여부
112
+ *
113
+ * @example
114
+ * function Section() {
115
+ * const ref = useRef<HTMLDivElement>(null);
116
+ * const isVisible = useIsVisible(ref);
117
+ *
118
+ * return (
119
+ * <div ref={ref}>
120
+ * {isVisible ? 'I am visible!' : 'I am hidden'}
121
+ * </div>
122
+ * );
123
+ * }
124
+ */
125
+ export function useIsVisible(ref, options) {
126
+ const entry = useIntersectionObserver(ref, options);
127
+ return entry?.isIntersecting ?? false;
128
+ }
@@ -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,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
+ }
@@ -0,0 +1,58 @@
1
+ /**
2
+ * 윈도우 크기 정보
3
+ */
4
+ export interface WindowSize {
5
+ /** 윈도우 너비 (픽셀) */
6
+ width: number;
7
+ /** 윈도우 높이 (픽셀) */
8
+ height: number;
9
+ }
10
+ /**
11
+ * 윈도우 크기를 추적하는 hook
12
+ *
13
+ * @returns 윈도우의 현재 너비와 높이
14
+ *
15
+ * @example
16
+ * // 기본 사용
17
+ * function ResponsiveComponent() {
18
+ * const { width, height } = useWindowSize();
19
+ *
20
+ * return (
21
+ * <div>
22
+ * Window size: {width} x {height}
23
+ * </div>
24
+ * );
25
+ * }
26
+ *
27
+ * @example
28
+ * // 반응형 레이아웃
29
+ * function AdaptiveLayout() {
30
+ * const { width } = useWindowSize();
31
+ *
32
+ * if (width < 768) {
33
+ * return <MobileLayout />;
34
+ * } else if (width < 1024) {
35
+ * return <TabletLayout />;
36
+ * } else {
37
+ * return <DesktopLayout />;
38
+ * }
39
+ * }
40
+ *
41
+ * @example
42
+ * // 캔버스 크기 조정
43
+ * function Canvas() {
44
+ * const { width, height } = useWindowSize();
45
+ * const canvasRef = useRef<HTMLCanvasElement>(null);
46
+ *
47
+ * useEffect(() => {
48
+ * if (canvasRef.current) {
49
+ * canvasRef.current.width = width;
50
+ * canvasRef.current.height = height;
51
+ * }
52
+ * }, [width, height]);
53
+ *
54
+ * return <canvas ref={canvasRef} />;
55
+ * }
56
+ */
57
+ export declare function useWindowSize(): WindowSize;
58
+ //# sourceMappingURL=useWindowSize.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useWindowSize.d.ts","sourceRoot":"","sources":["../../src/hooks/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"}
@@ -0,0 +1,79 @@
1
+ import { useState, useEffect } from 'react';
2
+ /**
3
+ * 윈도우 크기를 추적하는 hook
4
+ *
5
+ * @returns 윈도우의 현재 너비와 높이
6
+ *
7
+ * @example
8
+ * // 기본 사용
9
+ * function ResponsiveComponent() {
10
+ * const { width, height } = useWindowSize();
11
+ *
12
+ * return (
13
+ * <div>
14
+ * Window size: {width} x {height}
15
+ * </div>
16
+ * );
17
+ * }
18
+ *
19
+ * @example
20
+ * // 반응형 레이아웃
21
+ * function AdaptiveLayout() {
22
+ * const { width } = useWindowSize();
23
+ *
24
+ * if (width < 768) {
25
+ * return <MobileLayout />;
26
+ * } else if (width < 1024) {
27
+ * return <TabletLayout />;
28
+ * } else {
29
+ * return <DesktopLayout />;
30
+ * }
31
+ * }
32
+ *
33
+ * @example
34
+ * // 캔버스 크기 조정
35
+ * function Canvas() {
36
+ * const { width, height } = useWindowSize();
37
+ * const canvasRef = useRef<HTMLCanvasElement>(null);
38
+ *
39
+ * useEffect(() => {
40
+ * if (canvasRef.current) {
41
+ * canvasRef.current.width = width;
42
+ * canvasRef.current.height = height;
43
+ * }
44
+ * }, [width, height]);
45
+ *
46
+ * return <canvas ref={canvasRef} />;
47
+ * }
48
+ */
49
+ export function useWindowSize() {
50
+ // SSR 환경에서는 기본값 사용
51
+ const [windowSize, setWindowSize] = useState(() => {
52
+ if (typeof window === 'undefined') {
53
+ return { width: 0, height: 0 };
54
+ }
55
+ return {
56
+ width: window.innerWidth,
57
+ height: window.innerHeight,
58
+ };
59
+ });
60
+ useEffect(() => {
61
+ // SSR 환경에서는 이벤트 리스너 추가하지 않음
62
+ if (typeof window === 'undefined') {
63
+ return;
64
+ }
65
+ const handleResize = () => {
66
+ setWindowSize({
67
+ width: window.innerWidth,
68
+ height: window.innerHeight,
69
+ });
70
+ };
71
+ // 초기 크기 설정
72
+ handleResize();
73
+ window.addEventListener('resize', handleResize);
74
+ return () => {
75
+ window.removeEventListener('resize', handleResize);
76
+ };
77
+ }, []);
78
+ return windowSize;
79
+ }