관리자 카테고리 사이드 바 만들기 (feat. zustand)

yesung·2024년 1월 17일
2

평소에는 포스로 운영하고 관리 토글을 눌렀을 때, 관리자 모드로 전환되어 관리자 페이지가되는 형태의 웹사이트에서는 빼 놓을 수 없는 관리자 카테고리, 그러기 위해서는 카테고리 사이드 바가 필요했다.

※ 구현 시 고려해야할 조건

  • 사이드 바 안에도 토글이 들어가서 기존 토글 상태도 고려
  • 관리자 페이지 첫 진입 시 디폴트 모드는 운영 모드
  • 관리자 모드에서 운영 모드로 전환 시 테이블 관리 페이지로 전환

사용자 경험

사이드 바를 구현하기 앞 서 가장 먼저 사용자 경험 측면에서 크게 총 3가지로 분류해봤다.
테스트 후 보완해야할 점이 생긴다면 디벨롭 해야겠다.

  • 운영 모드일 때, 햄버거 메뉴 클릭 가능

  • 관리자 모드일 때, 사이드 바에서 카테고리 클릭 시 Close 처리

  • 사이드 바 Open 상태 일 때

    • 현재 어떤 모드인지 다른 모드로 전환하려면 어떻게 해야하는지 안내 문구 추가
    • 외부 영역 클릭 시 Close 처리
    • 모드 전환 토글 추가
    • 닫기 버튼 추가

상태 관리

사용자 경험을 고려해서 구현하려면 독립적인 2개의 상태 관리가 필요했다.

  1. 토글 버튼 상태
  2. 사이드 바 상태

컴포넌트를 분리하고 영향이 가는 곳에 조건부 렌더링을 하려면 전역에서 관리를 해야 했고 현 프로젝트의 전역 상태 관리 스택인 zustand 로 상태를 관리했다.

토글

interface ToggleState {
  isChecked: boolean;
  changeToggle: () => void;
}

const useToggleState = create<ToggleState>(set => ({
  isChecked: true,
  changeToggle: () => set(state => ({ isChecked: !state.isChecked })),
}));

사이드 바

interface SideBarState {
  isSideBarOpen: boolean;
  setIsSideBarOpen: (value: boolean) => void;
  toggleIsSideBarOpen: () => void;
}

const useSideBarState = create<SideBarState>(set => ({
  isSideBarOpen: false,
  setIsSideBarOpen: value => set(() => ({ isSideBarOpen: value })),
  toggleIsSideBarOpen: () => set(state => ({ isSideBarOpen: !state.isSideBarOpen })),
}));

모드 전환 토글의 경우, 첫 진입 시 운영 모드이기 때문에 true

사이드 바의 경우, 외부 클릭 & 카테고리 클릭 & 닫기 버튼 등 이벤트를 위해서 직접 true/false 를 받기로 했다.
⎣ 이 부분을 토글 형태의 로직으로 간다면 마음 어려워진다. (해봤다...)

로직 구현

토글

// HeaderToggleButton.tsx
const { isChecked, changeToggle } = useToggleState();

useEffect(() => {
  const currentPath = router.asPath;
  const managementPath = '/admin/management';
  
  // 운영 모드 전환 시 가야할 위치
  if (isChecked && currentPath !== managementPath) {
    router.push(managementPath);
  // 운영 모드 해제 시 가야할 위치
  } else if (!isChecked && currentPath === managementPath) {
    router.push('/admin/table');
  }
}, [isChecked, router]);

Store로 꺼내와서 현 상태 값으로 useEffect에 조건부를 걸어주면 끝이다.

사이드 바

const { isSideBarOpen, setIsSideBarOpen } = useSideBar();

먼저 상태 관리 스토어에서 만든 로직을 가져오자

외부 영역 클릭 시 닫힘

// Sidebar.tsx
const targetRef = useRef<HTMLDivElement>(null);

useEffect(() => {
  const closeSideBar = (e: MouseEvent) => {
    if (targetRef.current && !targetRef.current.contains(e.target as Node)) {
      setIsSideBarOpen(false);
    }
  };

  window.addEventListener('mousedown', closeSideBar);

  return () => {
    window.removeEventListener('mousedown', closeSideBar);
  };
}, [setIsSideBarOpen]);

<aside className={sidebarClass} ref={targetRef}> 생략... </aside>

사용자가 클릭한 곳이 aside(targetRef.current)가 아니면 닫히는 로직이고 이벤트 리스너로는 mousedown을 등록해서 closeSideBar로 이벤트를 처리했다.
useEffect 안에서는 컴포넌트가 언마운트되는 경우, 또는 의존성 배열([setIsSideBarOpen])에 있는 값이 변경되는 경우에 실행되도록 했다.

카테고리 클릭 시 닫힘

const clickNavListHandler = useCallback(
  (id: number, url: string) => {
    
    생략...
    
    setIsSideBarOpen(false);
  },
  [setIsSideBarOpen],
);

카테고리 클릭 시 페이지 전환과 동시에 isSideBarOpen 상태 값을 변경해줌으로써 닫히도록 했다.

내부 토글 버튼 추가 & 닫기 버튼

return (
  <aside className={sidebarClass} ref={targetRef}>
    <div className={styles.closeButton}>
      <CloseButton width={40} height={40} onClick={() => setIsSideBarOpen(false)} />
    </div>
    <div className={styles.toggleButton}>
      <HeaderToggleButton />
    </div>
   생략...
  </aside>
);

닫기 버튼은 SVG를 컴포넌트화 시켜서 사용했고 토글 버튼은 위에서 사용한 HeaderToggleButton 컴포넌트를 재사용했다.

📹 결과


디자이너님과 같이 협업 하면서 시안이 변경되면 로직도 같이 변경되는 부분에 있어서 많은 시행 착오가 있었다.
물론 컴포넌트를 잘 분리하고 상태 관리를 처음부터 잘 해놓으면 시안이 크게 변경되지 않는 이상 로직 자체가 크게 바뀌진 않는다고 생각한다.

단, 시간적인 이슈로 인해 초기에 와이어 프레임만 보고 시안이 나오지 않은 상태에서 작업을 하다 보니 최종 시안이 나온 후 재작업 도중 대변경이 일어났었던 거였다.

그리고 이번에 사이드 바를 작업하면서 가장 크게 느낀 거는 변수명 을 지정하는 부분이었다.

초기에는 사이드 바 상태 관리 변수명을 isSideBarOpen 가 아닌 isOutSide 로 지어서 boolean 타입만으로 생각을 하려고 하니 너무 헷갈렸었지만 변수명을 재정의 하고 나서 작업을 하니까 이해도 빠르고 명확해져서 훨씬 개발 속도가 빨라졌다.

profile
Frontend Developer

0개의 댓글