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
import { Sidebar } from '@/components/ui/sidebar';
import { SidebarGroup } from '@/components/ui/sidebar-group';
import { SidebarLink } from '@/components/ui/sidebar-link';
import {
useSidebarNav,
type SidebarNavNode,
} from '@/app/(protected)/(basic)/_hooks/use-sidebar-nav';
// 신규 화면이 추가될 때마다 이 트리에 항목을 늘린다. 기획(Figma ADM_MEM_101)의 사이드바는
// 그룹 헤딩(라우트 없음) 아래 자식 링크를 두는 2단 구조다 — 「학생 회원」은 「회원정보관리」
// 그룹의 자식이다(「학생회원관리」가 아니다).
//
// 시안에는 이 외에도 게시판관리(고객센터)·포인트관리·콘텐츠관리·시스템관리 4개 그룹이 더
// 있지만, 전부 대응하는 화면/라우트가 아직 없어 지금 추가하면 눌러도 갈 곳 없는 죽은 메뉴가
// 된다. 각 화면이 실제로 구현될 때 해당 그룹과 자식 링크를 추가한다.
const SIDEBAR_NAV_TREE: SidebarNavNode[] = [
{ type: 'link', label: '홈', href: '/' },
{
type: 'group',
label: '회원정보관리',
children: [{ type: 'link', label: '학생 회원', href: '/students' }],
},
{
// 기획(Figma ADM_ADM_101)의 화면경로가 「관리자 회원 < 관리자정보관리」다 —
// 「회원정보관리」가 아니라 별도 그룹의 자식이다.
type: 'group',
label: '관리자정보관리',
children: [{ type: 'link', label: '관리자 회원', href: '/admins' }],
},
{
// 기획(Figma ADM_ITM_101)의 화면경로가 「아이템 관리 < 꾸미기아이템관리」다. 셋트아이템은
// 같은 화면(/decoration-items)을 유형 탭으로 전환해 재사용하므로 별도 링크를 두지 않는다
// (decoration-item-query.ts 상단 주석 참조).
type: 'group',
label: '꾸미기아이템관리',
children: [{ type: 'link', label: '아이템 관리', href: '/decoration-items' }],
},
];
interface AdminSidebarProps {
isMobileOpen: boolean;
isDesktopCollapsed: boolean;
onCloseMobile: () => void;
}
/**
* 사이드바 배치 — 비주얼 셸(Sidebar)과 내비 항목(SidebarGroup/SidebarLink)은 design 공용
* 컴포넌트를 그대로 쓰고, 데스크톱 push/접힘과 모바일 오버레이+백드롭 등 반응형 배치(공용
* 컴포넌트가 제공하지 않음)만 className 패스스루로 처리한다. 전부 Tailwind 반응형
* 클래스(CSS-first) — JS로 브레이크포인트를 판별하지 않는다.
*
* 그룹 펼침/접힘 상태와 활성 링크·그룹 판정은 `useSidebarNav` 훅(ViewModel)이 소유한다 —
* 이 컴포넌트는 트리 데이터를 훅에 넘기고 반환값으로 렌더링만 담당한다.
*
* 주의(공용 컴포넌트 부족분 — 실증됨): SidebarLink는 아직 collapsed(md:w-16) 상태 전용
* 아이콘 표시 variant를 제공하지 않는다. 글자 단위 세로 줄바꿈 문제 자체는 SidebarLink의
* `min-w-0 truncate`로 완화돼 있지만(한 줄 유지 + 말줄임), 접힘 폭에서 라벨 텍스트가
* 말줄임으로 잘려 보이는 근본 한계는 남아 있다 — design 레인 소관(§4.2 공용 컴포넌트
* 신설)이라 이번 범위에서는 수정하지 않는다.
*/
export function AdminSidebar({
isMobileOpen,
isDesktopCollapsed,
onCloseMobile,
}: AdminSidebarProps) {
const { isLinkActive, isGroupExpanded, toggleGroup } =
useSidebarNav(SIDEBAR_NAV_TREE);
return (
<>
{isMobileOpen && (
<button
type="button"
aria-label="사이드바 닫기"
onClick={onCloseMobile}
className="fixed inset-0 z-30 bg-scrim md:hidden"
/>
)}
<Sidebar
className={`fixed inset-y-0 left-0 z-40 overflow-hidden transition-all duration-200 md:relative md:z-auto md:translate-x-0 ${
isMobileOpen ? 'translate-x-0' : '-translate-x-full'
} ${isDesktopCollapsed ? 'md:w-16' : 'md:w-64'}`}
>
{SIDEBAR_NAV_TREE.map((node) =>
node.type === 'link' ? (
<SidebarLink
key={node.href}
href={node.href}
label={node.label}
isActive={isLinkActive(node.href)}
/>
) : (
<SidebarGroup
key={node.label}
label={node.label}
isExpanded={isGroupExpanded(node.label)}
onToggle={() => toggleGroup(node.label)}
>
{node.children.map((link) => (
<SidebarLink
key={link.href}
href={link.href}
label={link.label}
isActive={isLinkActive(link.href)}
isNested
/>
))}
</SidebarGroup>
)
)}
</Sidebar>
</>
);
}