|
1 | | -import { useEffect, MutableRefObject } from "react"; |
| 1 | +import { useEffect, RefObject } from "react"; |
2 | 2 |
|
3 | | -function useClickOutside<T extends HTMLElement>( |
4 | | - ref: MutableRefObject<T | null>, |
5 | | - onClickOutside: () => void |
| 3 | +/** |
| 4 | + * 클릭 시 이벤트를 닫아주는 공통 hook |
| 5 | + * @param {RefObject<HTMLElement | HTMLElement[]>} ref - Element ref |
| 6 | + * @param {Function} handler - 이벤트 핸들러 |
| 7 | + * @param {boolean} isActive - 활성화 유무 |
| 8 | + * @param {number} targetIndex - 닫아야 할 창이 여러 개일 때 닫을 창의 index |
| 9 | + * @param {boolean} ignoreSiblings - 형제 요소 클릭 시에도 닫힐지 여부 |
| 10 | + */ |
| 11 | +function useClickOutside( |
| 12 | + ref: RefObject<HTMLElement | HTMLElement[]>, |
| 13 | + handler: (event: MouseEvent) => void, |
| 14 | + isActive = true, |
| 15 | + targetIndex = 0, |
| 16 | + ignoreSiblings = false |
6 | 17 | ) { |
7 | 18 | useEffect(() => { |
8 | | - /** |
9 | | - * Invoke Function onClick outside of element |
10 | | - */ |
11 | | - function handleClickOutside(event: MouseEvent) { |
12 | | - // 이벤트가 상위로 전파되지 않도록 수정 |
13 | | - if (ref.current && !ref.current.contains(event.target as Node)) { |
14 | | - onClickOutside(); |
15 | | - event.stopPropagation(); // 상위 모달로의 이벤트 전파를 막음 |
| 19 | + if (!isActive) return; |
| 20 | + |
| 21 | + const listener = (event: MouseEvent) => { |
| 22 | + const target = event.target as HTMLElement; |
| 23 | + |
| 24 | + // ref가 비어 있거나, ref 내부를 클릭한 경우 무시 |
| 25 | + const elements = Array.isArray(ref.current) ? ref.current : [ref.current]; |
| 26 | + const isClickInside = elements.some((element) => element?.contains(target)); |
| 27 | + if (isClickInside) return; |
| 28 | + |
| 29 | + // ignoreSiblings 옵션이 true인 경우 형제 요소 클릭 시 닫음 |
| 30 | + if (ignoreSiblings) { |
| 31 | + const parentNode = elements[targetIndex]?.parentNode; |
| 32 | + if (parentNode && Array.from(parentNode.children).some((sibling) => sibling.contains(target))) { |
| 33 | + return; |
| 34 | + } |
16 | 35 | } |
17 | | - } |
18 | 36 |
|
19 | | - // Bind |
20 | | - document.addEventListener("click", handleClickOutside); |
21 | | - return () => { |
22 | | - // dispose |
23 | | - document.removeEventListener("click", handleClickOutside); |
| 37 | + // 핸들러 실행 (버튼이든 어떤 요소든 외부 클릭이면) |
| 38 | + handler(event); |
24 | 39 | }; |
25 | | - }, [ref, onClickOutside]); |
| 40 | + |
| 41 | + document.addEventListener("click", listener); |
| 42 | + return () => document.removeEventListener("click", listener); |
| 43 | + }, [ref, handler, isActive, targetIndex, ignoreSiblings]); |
26 | 44 | } |
27 | 45 |
|
28 | 46 | export default useClickOutside; |
0 commit comments