minecraft-web/frontend/src/components/Sidebar.jsx

185 lines
6.6 KiB
React
Raw Normal View History

2025-12-16 08:40:32 +09:00
import React, { useState } from 'react';
import { NavLink, useLocation } from 'react-router-dom';
import { Home, Globe, Users, Menu, X, Gamepad2, Map } from 'lucide-react';
import { motion, AnimatePresence } from 'framer-motion';
// 사이드바 네비게이션 컴포넌트
const Sidebar = ({ isMobile = false }) => {
const [isOpen, setIsOpen] = useState(false);
const location = useLocation();
const menuItems = [
{ path: '/', icon: Home, label: '홈' },
{ path: '/players', icon: Users, label: '플레이어', matchPaths: ['/players', '/player/'] },
{ path: '/worlds', icon: Globe, label: '월드 정보' },
{ path: '/worldmap', icon: Map, label: '월드맵' },
];
// 커스텀 활성 상태 확인 함수
const isMenuActive = (item) => {
if (item.matchPaths) {
return item.matchPaths.some(path => location.pathname.startsWith(path));
}
return location.pathname === item.path;
};
// 모바일: 상단 툴바 + 햄버거 메뉴 사이드바
if (isMobile) {
return (
<>
{/* 상단 툴바 */}
<header className="fixed top-0 left-0 right-0 z-50 bg-mc-dark/95 backdrop-blur-xl border-b border-white/10 safe-area-top">
<div className="flex items-center justify-between h-14 px-4">
{/* 햄버거 메뉴 버튼 */}
<button
onClick={() => setIsOpen(true)}
className="p-2 -ml-2 rounded-lg text-white hover:bg-white/10 transition-all"
>
<Menu size={24} />
</button>
{/* 로고/타이틀 */}
<div className="flex items-center gap-2">
<Gamepad2 className="text-mc-green" size={20} />
<span className="font-bold text-white">마인크래프트</span>
</div>
{/* 우측 공간 (균형용) */}
<div className="w-10" />
</div>
</header>
{/* 상단 툴바 높이만큼 공간 확보 */}
<div className="h-14" />
{/* 오버레이 */}
<AnimatePresence>
{isOpen && (
<motion.div
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
exit={{ opacity: 0 }}
transition={{ duration: 0.2 }}
className="fixed inset-0 bg-black/60 backdrop-blur-sm z-[60]"
onClick={() => setIsOpen(false)}
/>
)}
</AnimatePresence>
{/* 사이드바 (슬라이드) */}
<AnimatePresence>
{isOpen && (
<motion.aside
initial={{ x: -280 }}
animate={{ x: 0 }}
exit={{ x: -280 }}
transition={{ type: 'tween', duration: 0.25, ease: 'easeOut' }}
className="fixed left-0 top-0 bottom-0 w-72 bg-mc-dark/98 backdrop-blur-xl border-r border-white/10 z-[70] flex flex-col safe-area-left"
>
{/* 사이드바 헤더 */}
<div className="flex items-center justify-between p-4 border-b border-white/10">
<div className="flex items-center gap-3">
<div className="p-2 rounded-xl bg-gradient-to-br from-mc-green/20 to-mc-emerald/20 border border-mc-green/30">
<Gamepad2 className="text-mc-green" size={20} />
</div>
<div>
<h1 className="text-base font-bold text-white">마인크래프트</h1>
<p className="text-xs text-gray-500">서버 대시보드</p>
</div>
</div>
<button
onClick={() => setIsOpen(false)}
className="p-2 rounded-lg text-gray-400 hover:text-white hover:bg-white/10 transition-all"
>
<X size={20} />
</button>
</div>
{/* 메뉴 */}
<nav className="flex-1 p-3 space-y-1">
{menuItems.map((item) => {
const active = isMenuActive(item);
return (
<NavLink
key={item.path}
to={item.path}
onClick={() => setIsOpen(false)}
className={`flex items-center gap-3 px-4 py-3 rounded-xl transition-colors outline-none focus:ring-0 border ${
active
? 'bg-mc-green/10 text-mc-green border-mc-green/20'
: 'text-gray-400 hover:text-white hover:bg-white/5 border-transparent'
}`}
>
<item.icon size={20} />
<span className="font-medium">{item.label}</span>
</NavLink>
);
})}
</nav>
</motion.aside>
)}
</AnimatePresence>
</>
);
}
// PC: 기존 사이드바
return (
<>
{/* 데스크탑 사이드바 (항상 표시) */}
<aside className="hidden md:flex fixed left-0 top-0 bottom-0 w-64 bg-mc-dark/95 backdrop-blur-xl border-r border-white/10 z-30 flex-col">
<SidebarContent menuItems={menuItems} isMenuActive={isMenuActive} />
</aside>
{/* 데스크톱용 사이드바 spacer */}
<div className="hidden md:block w-64 shrink-0" />
</>
);
};
// 사이드바 내용 컴포넌트 (PC 전용)
const SidebarContent = ({ menuItems, isMenuActive, onClose }) => (
<>
{/* 로고 */}
<div className="p-6 border-b border-white/10">
<div className="flex items-center gap-3">
<div className="p-2 rounded-xl bg-gradient-to-br from-mc-green/20 to-mc-emerald/20 border border-mc-green/30">
<Gamepad2 className="text-mc-green" size={24} />
</div>
<div>
<h1 className="text-lg font-bold text-white">마인크래프트</h1>
<p className="text-xs text-gray-500">서버 대시보드</p>
</div>
</div>
</div>
{/* 메뉴 */}
<nav className="flex-1 p-4 space-y-2">
{menuItems.map((item) => {
const active = isMenuActive(item);
return (
<NavLink
key={item.path}
to={item.path}
onClick={onClose}
className={`flex items-center gap-3 px-4 py-3 rounded-xl transition-colors outline-none focus:ring-0 border ${
active
? 'bg-mc-green/10 text-mc-green border-mc-green/20'
: 'text-gray-400 hover:text-white hover:bg-white/5 border-transparent'
}`}
>
<item.icon size={20} />
<span className="font-medium">{item.label}</span>
</NavLink>
);
})}
</nav>
</>
);
export default Sidebar;