여럿 상속되는 드롭다운 메뉴를 만들기 위해 상수 구조와 재귀 컴포넌트를 제작하려 하였다.
또한 계속 사용될 dropdown 관련 함수를 하나로 묶어 훅으로 관리하려 한다.
구현 완료된 사진은 다음과 같다.

먼저 데이터 구조(타입)는 다음과 같다.
interface MenuItemType {
text: string
media?: number
onClick?: MouseEventHandler
subsets?: MenuItemType[]
}
subsets에 자기 자신의 타입을 받아 여러 Depth의 드롭다운을 받도록 하였다. (해당 구조에 position을 더하여 드롭다운의 위치를 조절해도 된다.)
다음으로 이를 이용하여 컴포넌트를 재귀 형식으로 만들어준다.
export default function Dropdown({ subsets, position }: ComponentProps) {
return (
<Container position={position}>
{subsets.map((menu, idx) => (
<DropdownItem key={idx} {...menu} />
))}
</Container>
)
}
export default function DropdownItem({
text,
media,
subsets,
onClick,
position,
}: ComponentProps) {
const { ref, isOpen, toggleOpen, onMouseEnter, onMouseLeave } = useDropdown()
return (
<S.Container
ref={ref}
media={media}
active={isOpen}
onClick={subsets ? toggleOpen : onClick}
onMouseEnter={subsets && onMouseEnter}
onMouseLeave={subsets && onMouseLeave}
>
<S.Text>{text}</S.Text>
{subsets && <S.ChevronIcon />}
{isOpen && <Dropdown subsets={subsets} position={position} />}
</S.Container>
)
}
DropdownItem 에서 subsets을 가지는 경우 다시 Dropdown을 호출하며 재귀 형식으로 계속하여 펼쳐질 수 있는 컴포넌트를 만들었다.
다음으로, Dropdown 에서 계속 사용할 useDropdown 함수를 다음과 같이 작성하였다.
import { useEffect, useRef, useState } from "react"
export default function useDropdown() {
const ref = useRef<HTMLDivElement & HTMLLIElement>()
const [isOpen, setIsOpen] = useState(false)
const toggleOpen = () => {
setIsOpen(!isOpen)
}
const [timeOutId, setTimeOutId] = useState<NodeJS.Timeout>()
const onMouseEnter = () => {
const id = setTimeout(() => setIsOpen(true), 500)
setTimeOutId(id)
}
const onMouseLeave = () => {
if (timeOutId) {
clearTimeout(timeOutId)
setTimeOutId(null)
setIsOpen(false)
}
}
useEffect(() => {
const handleClickOutside = (e: MouseEvent) => {
if (ref.current && !ref.current.contains(e.target as Node)) {
setIsOpen(false)
}
}
if (isOpen) {
document.addEventListener("click", handleClickOutside)
} else {
document.removeEventListener("click", handleClickOutside)
}
return () => {
document.removeEventListener("click", handleClickOutside)
}
}, [isOpen])
return { ref, isOpen, toggleOpen, onMouseEnter, onMouseLeave }
}
useDropdown의 기능은 다음과 같다.
1. 드롭다운이 펼쳐져있는지 확인한다.
2. 드롭다운을 열고 닫을 수 있다.
3. 드롭다운의 외부를 클릭할 경우 드롭다운이 닫힌다.
4. 드롭다운 아이템에 마우스를 0.5초 이상 올려둘 경우 subsets 가 보인다.
5. 드롭다운에서 벗어나면 subsets를 닫는다.