Skip to content

Commit f63dca8

Browse files
committed
design: 모바일 대응 사이드바 접힘 구현
1 parent b2c8497 commit f63dca8

2 files changed

Lines changed: 112 additions & 68 deletions

File tree

‎src/app/(auth)/display/page.tsx‎

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -579,8 +579,8 @@ export default function DisplayPage() {
579579

580580
{/* 일정 편집 모달 */}
581581
{isScheduleModalOpen && selectedDate && (
582-
<div className="fixed inset-0 z-50 flex items-center justify-center bg-black/40">
583-
<div className="w-[480px] rounded-xl bg-[var(--popover)] p-6 shadow-xl">
582+
<div className="fixed inset-0 z-50 flex items-center justify-center bg-black/40 p-4">
583+
<div className="w-full max-w-[480px] rounded-xl bg-[var(--popover)] p-6 shadow-xl">
584584
<div className="flex items-center justify-between mb-6">
585585
<h2 className="text-lg font-bold text-[var(--foreground)]">
586586
{selectedDate} 일정 편집
@@ -645,8 +645,8 @@ export default function DisplayPage() {
645645

646646
{/* 포스터 추가/수정 모달 */}
647647
{isPosterModalOpen && (
648-
<div className="fixed inset-0 z-50 flex items-center justify-center bg-black/40">
649-
<div className="w-[480px] rounded-xl bg-[var(--popover)] p-6 shadow-xl">
648+
<div className="fixed inset-0 z-50 flex items-center justify-center bg-black/40 p-4">
649+
<div className="w-full max-w-[480px] rounded-xl bg-[var(--popover)] p-6 shadow-xl">
650650
<div className="flex items-center justify-between mb-6">
651651
<h2 className="text-lg font-bold text-[var(--foreground)]">
652652
{editingPoster ? '포스터 수정' : '포스터 추가'}
@@ -735,8 +735,8 @@ export default function DisplayPage() {
735735

736736
{/* 삭제 확인 모달 */}
737737
{isDeleteModalOpen && posterToDelete && (
738-
<div className="fixed inset-0 z-50 flex items-center justify-center bg-black/40">
739-
<div className="w-[400px] rounded-xl bg-[var(--popover)] p-6 shadow-xl">
738+
<div className="fixed inset-0 z-50 flex items-center justify-center bg-black/40 p-4">
739+
<div className="w-full max-w-[400px] rounded-xl bg-[var(--popover)] p-6 shadow-xl">
740740
<div className="flex items-center justify-between mb-4">
741741
<h2 className="text-lg font-bold text-[var(--foreground)]">포스터 삭제</h2>
742742
<button

‎src/app/(auth)/layout.tsx‎

Lines changed: 106 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@ import {
1111
Monitor,
1212
Settings,
1313
LogOut,
14+
Menu,
15+
X,
1416
} from 'lucide-react';
1517
import useAuthRedirect from '@/hooks/useAuthRedirect';
1618
import {
@@ -44,6 +46,7 @@ export default function AuthLayout({ children }: AuthLayoutProps) {
4446
const [role, setRole] = useState<Role | null>(null);
4547
const [userInfo, setUserInfo] = useState<UserInfo | null>(null);
4648
const [showAccessDenied, setShowAccessDenied] = useState(false);
49+
const [sidebarOpen, setSidebarOpen] = useState(false);
4750

4851
useEffect(() => {
4952
setRole(getRoleFromToken());
@@ -68,74 +71,115 @@ export default function AuthLayout({ children }: AuthLayoutProps) {
6871
[role],
6972
);
7073

74+
const sidebarContent = (
75+
<>
76+
{/* Logo */}
77+
<div className="flex h-16 items-center justify-between border-b border-[var(--border)] px-6">
78+
<Link href="/rental" className="flex items-center gap-2">
79+
<span className="text-xl font-bold text-[var(--primary)]">빌릴게</span>
80+
<span className="text-sm text-[var(--sidebar-muted)]">관리자 시스템</span>
81+
</Link>
82+
<button
83+
onClick={() => setSidebarOpen(false)}
84+
className="rounded-md p-1 text-[var(--foreground-muted)] hover:bg-[var(--sidebar-hover)] md:hidden cursor-pointer"
85+
>
86+
<X className="h-5 w-5" />
87+
</button>
88+
</div>
89+
90+
{/* Navigation */}
91+
<nav className="flex-1 overflow-y-auto py-4">
92+
<ul className="space-y-1 px-3">
93+
{navItems.map((item) => {
94+
const isActive = pathname === item.href;
95+
const permitted = hasPermission(item.href, role);
96+
return (
97+
<li key={item.href}>
98+
<Link
99+
href={item.href}
100+
onClick={(e) => {
101+
handleNavClick(e, item.href);
102+
setSidebarOpen(false);
103+
}}
104+
className={`flex items-center gap-3 rounded-lg px-3 py-2.5 text-sm font-medium transition-colors ${
105+
!permitted
106+
? 'cursor-not-allowed text-[var(--foreground-subtle)] opacity-50'
107+
: isActive
108+
? 'bg-[var(--sidebar-active)] text-[var(--primary)]'
109+
: 'text-[var(--foreground-muted)] hover:bg-[var(--sidebar-hover)] hover:text-[var(--foreground)]'
110+
}`}
111+
>
112+
<item.icon className="h-5 w-5" />
113+
{item.name}
114+
</Link>
115+
</li>
116+
);
117+
})}
118+
</ul>
119+
</nav>
120+
121+
{/* User Info & Logout */}
122+
<div className="border-t border-[var(--border)] p-3">
123+
{userInfo && (
124+
<div className="mb-2 rounded-lg bg-[var(--sidebar-hover)] px-3 py-2.5 flex-col">
125+
<div className="flex flex-row justify-between">
126+
<p className="text-sm font-medium text-[var(--foreground)]">{userInfo.name}</p>
127+
<span className="inline-block rounded-full bg-[var(--primary)] px-2 py-0.5 text-xs font-medium text-white">
128+
{ROLE_LABEL[userInfo.role]}
129+
</span>
130+
</div>
131+
<p className="text-xs text-[var(--foreground-muted)]">{userInfo.studentId}</p>
132+
</div>
133+
)}
134+
<button
135+
onClick={() => {
136+
localStorage.removeItem('token');
137+
router.push('/login');
138+
}}
139+
className="flex w-full items-center gap-3 rounded-lg px-3 py-2.5 text-sm font-medium text-[var(--foreground-muted)] hover:bg-[var(--sidebar-hover)] hover:text-[var(--error)] cursor-pointer"
140+
>
141+
<LogOut className="h-5 w-5" />
142+
로그아웃
143+
</button>
144+
</div>
145+
</>
146+
);
147+
71148
return (
72149
<div className="flex min-h-screen bg-[var(--background)]">
150+
{/* Mobile Header */}
151+
<header className="fixed left-0 right-0 top-0 z-30 flex h-14 items-center border-b border-[var(--border)] bg-[var(--sidebar)] px-4 md:hidden">
152+
<button
153+
onClick={() => setSidebarOpen(true)}
154+
className="rounded-md p-1.5 text-[var(--foreground-muted)] hover:bg-[var(--sidebar-hover)] cursor-pointer"
155+
>
156+
<Menu className="h-5 w-5" />
157+
</button>
158+
<Link href="/rental" className="ml-3 flex items-center gap-2">
159+
<span className="text-lg font-bold text-[var(--primary)]">빌릴게</span>
160+
<span className="text-xs text-[var(--sidebar-muted)]">관리자 시스템</span>
161+
</Link>
162+
</header>
163+
164+
{/* Mobile Sidebar Overlay */}
165+
{sidebarOpen && (
166+
<div
167+
className="fixed inset-0 z-40 bg-black/40 md:hidden"
168+
onClick={() => setSidebarOpen(false)}
169+
/>
170+
)}
171+
73172
{/* Sidebar */}
74-
<aside className="fixed left-0 top-0 z-20 flex h-full w-64 flex-col border-r border-[var(--border)] bg-[var(--sidebar)]">
75-
{/* Logo */}
76-
<div className="flex h-16 items-center border-b border-[var(--border)] px-6">
77-
<Link href="/rental" className="flex items-center gap-2">
78-
<span className="text-xl font-bold text-[var(--primary)]">빌릴게</span>
79-
<span className="text-sm text-[var(--sidebar-muted)]">관리자 시스템</span>
80-
</Link>
81-
</div>
82-
83-
{/* Navigation */}
84-
<nav className="flex-1 overflow-y-auto py-4">
85-
<ul className="space-y-1 px-3">
86-
{navItems.map((item) => {
87-
const isActive = pathname === item.href;
88-
const permitted = hasPermission(item.href, role);
89-
return (
90-
<li key={item.href}>
91-
<Link
92-
href={item.href}
93-
onClick={(e) => handleNavClick(e, item.href)}
94-
className={`flex items-center gap-3 rounded-lg px-3 py-2.5 text-sm font-medium transition-colors ${
95-
!permitted
96-
? 'cursor-not-allowed text-[var(--foreground-subtle)] opacity-50'
97-
: isActive
98-
? 'bg-[var(--sidebar-active)] text-[var(--primary)]'
99-
: 'text-[var(--foreground-muted)] hover:bg-[var(--sidebar-hover)] hover:text-[var(--foreground)]'
100-
}`}
101-
>
102-
<item.icon className="h-5 w-5" />
103-
{item.name}
104-
</Link>
105-
</li>
106-
);
107-
})}
108-
</ul>
109-
</nav>
110-
111-
{/* User Info & Logout */}
112-
<div className="border-t border-[var(--border)] p-3">
113-
{userInfo && (
114-
<div className="mb-2 rounded-lg bg-[var(--sidebar-hover)] px-3 py-2.5 flex-col">
115-
<div className="flex flex-row justify-between">
116-
<p className="text-sm font-medium text-[var(--foreground)]">{userInfo.name}</p>
117-
<span className="inline-block rounded-full bg-[var(--primary)] px-2 py-0.5 text-xs font-medium text-white">
118-
{ROLE_LABEL[userInfo.role]}
119-
</span>
120-
</div>
121-
<p className="text-xs text-[var(--foreground-muted)]">{userInfo.studentId}</p>
122-
</div>
123-
)}
124-
<button
125-
onClick={() => {
126-
localStorage.removeItem('token');
127-
router.push('/login');
128-
}}
129-
className="flex w-full items-center gap-3 rounded-lg px-3 py-2.5 text-sm font-medium text-[var(--foreground-muted)] hover:bg-[var(--sidebar-hover)] hover:text-[var(--error)] cursor-pointer"
130-
>
131-
<LogOut className="h-5 w-5" />
132-
로그아웃
133-
</button>
134-
</div>
173+
<aside
174+
className={`fixed left-0 top-0 z-50 flex h-full w-64 flex-col border-r border-[var(--border)] bg-[var(--sidebar)] transition-transform duration-200 md:z-20 md:translate-x-0 ${
175+
sidebarOpen ? 'translate-x-0' : '-translate-x-full'
176+
}`}
177+
>
178+
{sidebarContent}
135179
</aside>
136180

137181
{/* Main Content */}
138-
<main className="ml-64 flex-1 p-8">{children}</main>
182+
<main className="min-w-0 flex-1 pt-14 p-4 md:ml-64 md:pt-0 md:p-8">{children}</main>
139183

140184
{/* Access Denied Modal */}
141185
<AccessDeniedModal isOpen={showAccessDenied} onClose={() => setShowAccessDenied(false)} />

0 commit comments

Comments
 (0)