File name
Commit message
Commit date
File name
Commit message
Commit date
09-03
File name
Commit message
Commit date
File name
Commit message
Commit date
File name
Commit message
Commit date
'use client';
import { usePathname } from 'next/navigation';
import { useCallback, useState } from 'react';
export interface SidebarState {
isMobileOpen: boolean;
isDesktopCollapsed: boolean;
openMobile: () => void;
closeMobile: () => void;
toggleDesktopCollapse: () => void;
}
/**
* Basic 레이아웃 사이드바의 화면 로컬 상태(ViewModel 대응) — 모바일 오버레이 열림 여부와
* 데스크톱 접힘 여부만 값으로 들고 있는다. 실제로 어느 상태가 화면에 반영되는지(모바일 오버레이 vs
* 데스크톱 push)는 Tailwind 반응형 클래스가 판단한다 — matchMedia로 브레이크포인트를 직접 판별하면
* 서버 렌더와 최초 클라이언트 렌더가 어긋나 hydration 불일치를 일으킬 수 있어 이 훅에서는 다루지
* 않는다.
*/
export function useSidebar(): SidebarState {
const pathname = usePathname();
const [isMobileOpen, setIsMobileOpen] = useState(false);
const [isDesktopCollapsed, setIsDesktopCollapsed] = useState(false);
// 라우트가 바뀌면 모바일 오버레이는 항상 닫는다 (링크 클릭 후 열려있는 채로 남는 것 방지).
// effect로 동기화하지 않는다 — 렌더 중 상태를 조정하는 React 공식 패턴(state 값 변화를
// 렌더 중 감지해 즉시 setState)을 사용해 effect발 cascading render를 피한다.
const [prevPathname, setPrevPathname] = useState(pathname);
if (pathname !== prevPathname) {
setPrevPathname(pathname);
setIsMobileOpen(false);
}
const openMobile = useCallback(() => {
setIsMobileOpen(true);
}, []);
const closeMobile = useCallback(() => {
setIsMobileOpen(false);
}, []);
const toggleDesktopCollapse = useCallback(() => {
setIsDesktopCollapsed((prev) => !prev);
}, []);
return {
isMobileOpen,
isDesktopCollapsed,
openMobile,
closeMobile,
toggleDesktopCollapse,
};
}