File name
Commit message
Commit date
File name
Commit message
Commit date
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,
};
}