details, summary를 활용한 아코디언 메뉴 제작

김지환·2024년 7월 3일

개발 유튜브를 보다가 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 로직을 한눈에 파악하기 용이해졌다.

profile
세상의 문제 해결을 즐기는 프론트엔드 개발자

0개의 댓글