File name
Commit message
Commit date
08-14
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단 구조다 — 「학생 회원」은 「회원정보관리」
// 그룹의 자식이다(「학생회원관리」가 아니다).
//
// 시안에는 이 외에도 포인트관리·콘텐츠관리·시스템관리 3개 그룹이 더 있지만, 전부 대응하는
// 화면/라우트가 아직 없어 지금 추가하면 눌러도 갈 곳 없는 죽은 메뉴가 된다. 각 화면이 실제로
// 구현될 때 해당 그룹과 자식 링크를 추가한다.
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' }],
},
{
// 기획(Figma A_BOA_006/012/014) 사이드바의 그룹명이 「게시판관리(고객센터)」다.
type: 'group',
label: '게시판관리(고객센터)',
children: [
{ type: 'link', label: '공지사항', href: '/boards/notices' },
{ type: 'link', label: '1:1문의', href: '/boards/inquiries' },
{ type: 'link', label: 'FAQ', href: '/boards/faqs' },
],
},
];
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>
</>
);
}