평소에는 포스로 운영하고 관리 토글을 눌렀을 때, 관리자 모드로 전환되어 관리자 페이지가되는 형태의 웹사이트에서는 빼 놓을 수 없는 관리자 카테고리, 그러기 위해서는 카테고리 사이드 바가 필요했다.
※ 구현 시 고려해야할 조건
- 사이드 바 안에도 토글이 들어가서 기존 토글 상태도 고려
- 관리자 페이지 첫 진입 시 디폴트 모드는 운영 모드
- 관리자 모드에서 운영 모드로 전환 시 테이블 관리 페이지로 전환
사이드 바를 구현하기 앞 서 가장 먼저 사용자 경험 측면에서 크게 총 3가지로 분류해봤다.
⎣ 테스트 후 보완해야할 점이 생긴다면 디벨롭 해야겠다.
운영 모드일 때, 햄버거 메뉴 클릭 가능
관리자 모드일 때, 사이드 바에서 카테고리 클릭 시 Close 처리
사이드 바 Open 상태 일 때
사용자 경험을 고려해서 구현하려면 독립적인 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 타입만으로 생각을 하려고 하니 너무 헷갈렸었지만 변수명을 재정의 하고 나서 작업을 하니까 이해도 빠르고 명확해져서 훨씬 개발 속도가 빨라졌다.