개발 유튜브를 보다가 HTML의 deatils와 summary를 활용해 아코디언 메뉴를 손쉽게 만들 수 있다는 것을 확인했다.
하지만, 기본적으로 제공되는 기능만으로는 펼쳐진 아코디언 메뉴가
다른 메뉴가 펼쳐질 때 닫히지 않았기 때문에
이를 추가해서 구현해봤다.
const weeklyRef = useRef<HTMLDetailsElement>(null);
const dailyRef = useRef<HTMLDetailsElement>(null);
useEffect(() => {
function handleOutsideClick(e: MouseEvent) {
if (weeklyRef.current && weeklyRef.current.contains(e.target as Node)) {
// 열린다는 것
if (!weeklyRef.current.open) {
if (dailyRef.current) dailyRef.current.removeAttribute("open");
}
}
if (dailyRef.current && dailyRef.current.contains(e.target as Node)) {
// 열린다는 것
if (!dailyRef.current.open) {
if (weeklyRef.current) weeklyRef.current.removeAttribute("open");
}
}
}
document.addEventListener("click", handleOutsideClick);
return () => {
document.removeEventListener("click", handleOutsideClick);
};
}, []);
...
...
<nav>
<details ref={weeklyRef}>
<summary>주간 기록 관리</summary>
<ul>
...
</ul>
</DetailStyle>
<details ref={dailyRef}>
<summary>일간 기록 관리</summary>
<ul>
...
</ul>
</DetailStyle>
</nav>
구현 중에 가장 문제가 됐던 부분은
weeklyRef.current.open 이 부분이다.
console.log(weeklyRef.current);
// <details open>
console.log(weeklyRef.current.open);
// false
console.log(weeklyRef.current);
// <details>
console.log(weeklyRef.current.open);
// true
위에서와 같이 실제 details의 속성과 그 boolean 값이 반대로 나오는 부분이 가장 문제가 됐다.
때문에 handleOutsideClick을 다음과 같이 구성했다.
function handleOutsideClick(e: MouseEvent) {
if (weeklyRef.current && weeklyRef.current.contains(e.target as Node)) {
// 열린다는 것
if (!weeklyRef.current.open) {
if (dailyRef.current) dailyRef.current.removeAttribute("open");
}
}
if (dailyRef.current && dailyRef.current.contains(e.target as Node)) {
// 열린다는 것
if (!dailyRef.current.open) {
if (weeklyRef.current) weeklyRef.current.removeAttribute("open");
}
}
}
결과적으로 이전의 코드와 비교했을 때
현재 어떤 메뉴를 선택했는지를 파악하는 상태가 필요 없어짐에 따라
display 로직을 한눈에 파악하기 용이해졌다.