170 lines
8.8 KiB
TypeScript
Executable File
170 lines
8.8 KiB
TypeScript
Executable File
|
|
import React, { useState, useEffect } from 'react';
|
|
|
|
interface SidebarItemProps {
|
|
icon: React.ReactNode;
|
|
label: string;
|
|
isActive?: boolean;
|
|
isCollapsed: boolean;
|
|
onClick?: () => void;
|
|
}
|
|
|
|
const SidebarItem: React.FC<SidebarItemProps> = ({ icon, label, isActive, isCollapsed, onClick }) => {
|
|
const baseClasses = "flex items-center gap-3 px-3 py-2.5 sm:py-3 rounded-xl transition-all cursor-pointer group mb-1 relative overflow-hidden";
|
|
const activeClasses = "bg-[#a6ffea] text-[#121a1d]";
|
|
const inactiveClasses = "text-gray-400 hover:bg-white/5 hover:text-white";
|
|
|
|
return (
|
|
<div
|
|
onClick={onClick}
|
|
className={`${baseClasses} ${isActive ? activeClasses : inactiveClasses} ${isCollapsed ? 'justify-center w-10 h-10 sm:w-12 sm:h-12 mx-auto px-0' : ''}`}
|
|
title={isCollapsed ? label : ""}
|
|
>
|
|
<div className={`shrink-0 flex items-center justify-center transition-colors ${isActive ? 'text-[#121a1d]' : ''}`}>
|
|
{icon}
|
|
</div>
|
|
{!isCollapsed && <span className="text-xs sm:text-sm font-bold whitespace-nowrap">{label}</span>}
|
|
</div>
|
|
);
|
|
};
|
|
|
|
interface SidebarProps {
|
|
activeItem: string;
|
|
onNavigate: (id: string) => void;
|
|
}
|
|
|
|
const Sidebar: React.FC<SidebarProps> = ({ activeItem, onNavigate }) => {
|
|
const [isCollapsed, setIsCollapsed] = useState(false);
|
|
const [isMobileOpen, setIsMobileOpen] = useState(false);
|
|
|
|
// 모바일에서는 기본적으로 사이드바 숨기기
|
|
useEffect(() => {
|
|
const handleResize = () => {
|
|
if (window.innerWidth < 768) {
|
|
setIsMobileOpen(false);
|
|
}
|
|
};
|
|
|
|
handleResize();
|
|
window.addEventListener('resize', handleResize);
|
|
return () => window.removeEventListener('resize', handleResize);
|
|
}, []);
|
|
|
|
// 네비게이션 시 모바일에서 사이드바 닫기
|
|
const handleNavigate = (id: string) => {
|
|
onNavigate(id);
|
|
if (window.innerWidth < 768) {
|
|
setIsMobileOpen(false);
|
|
}
|
|
};
|
|
|
|
const menuItems = [
|
|
{ id: '새 프로젝트 만들기', label: '새 프로젝트 만들기', icon: <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5"><line x1="12" y1="5" x2="12" y2="19"/><line x1="5" y1="12" x2="19" y2="12"/></svg> },
|
|
{ id: '내 보관함', label: '내 보관함', icon: <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5"><path d="M22 19a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h5l2 3h9a2 2 0 0 1 2 2z"/></svg> },
|
|
{ id: '에셋 관리', label: '에셋 관리', icon: <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5"><rect x="3" y="3" width="18" height="18" rx="2" ry="2"/><circle cx="8.5" cy="8.5" r="1.5"/><polyline points="21 15 16 10 5 21"/></svg> },
|
|
{ id: '내 펜션', label: '내 펜션', icon: <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5"><path d="M3 9l9-7 9 7v11a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z"/><polyline points="9 22 9 12 15 12 15 22"/></svg> },
|
|
{ id: '계정 설정', label: '계정 설정', icon: <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5"><path d="M20 21v-2a4 4 0 0 0-4-4H8a4 4 0 0 0-4 4v2"/><circle cx="12" cy="7" r="4"/></svg> },
|
|
{ id: '대시보드', label: '대시보드', icon: <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5"><rect x="3" y="3" width="7" height="9"/><rect x="14" y="3" width="7" height="5"/><rect x="14" y="12" width="7" height="9"/><rect x="3" y="16" width="7" height="5"/></svg> },
|
|
{ id: '비즈니스 설정', label: '비즈니스 설정', icon: <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5"><circle cx="12" cy="12" r="3"/><path d="M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 0 1 0 2.83 2 2 0 0 1-2.83 0l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 0 1-2 2 2 2 0 0 1-2-2v-.09A1.65 1.65 0 0 0 9 19.4a1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 0 1-2.83 0 2 2 0 0 1 0-2.83l.06-.06a1.65 1.65 0 0 0 .33-1.82 1.65 1.65 0 0 0-1.51-1H3a2 2 0 0 1-2-2 2 2 0 0 1 2-2h.09A1.65 1.65 0 0 0 4.6 9a1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 0 1 0-2.83 2 2 0 0 1 2.83 0l.06.06a1.65 1.65 0 0 0 1.82.33H9a1.65 1.65 0 0 0 1-1.51V3a2 2 0 0 1-2-2 2 2 0 0 1 2 2v.09a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 0 1 2.83 0 2 2 0 0 1 0 2.83l-.06.06a1.65 1.65 0 0 0-.33 1.82V9a1.65 1.65 0 0 0 1.51 1H21a2 2 0 0 1 2 2 2 2 0 0 1-2 2h-.09a1.65 1.65 0 0 0-1.51 1z"/></svg> },
|
|
];
|
|
|
|
return (
|
|
<>
|
|
{/* 모바일 햄버거 버튼 */}
|
|
<button
|
|
onClick={() => setIsMobileOpen(true)}
|
|
className="fixed top-4 left-4 z-40 p-2 bg-[#1c2a2e] rounded-lg border border-white/10 text-gray-400 hover:text-white md:hidden"
|
|
>
|
|
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2">
|
|
<line x1="3" y1="12" x2="21" y2="12"/><line x1="3" y1="6" x2="21" y2="6"/><line x1="3" y1="18" x2="21" y2="18"/>
|
|
</svg>
|
|
</button>
|
|
|
|
{/* 모바일 오버레이 */}
|
|
{isMobileOpen && (
|
|
<div
|
|
className="fixed inset-0 bg-black/50 z-40 md:hidden"
|
|
onClick={() => setIsMobileOpen(false)}
|
|
/>
|
|
)}
|
|
|
|
{/* 사이드바 */}
|
|
<div className={`
|
|
fixed md:relative h-screen flex flex-col bg-[#121a1d] border-r border-white/5 transition-all duration-300 z-50
|
|
${isCollapsed ? 'w-16 sm:w-20' : 'w-56 sm:w-64'}
|
|
${isMobileOpen ? 'translate-x-0' : '-translate-x-full md:translate-x-0'}
|
|
`}>
|
|
<div className={`p-4 sm:p-6 flex items-center justify-between ${isCollapsed ? 'flex-col gap-4' : ''}`}>
|
|
{!isCollapsed && <span className="font-serif-logo italic text-xl sm:text-2xl font-bold tracking-tight text-white">CASTAD</span>}
|
|
<button
|
|
onClick={() => {
|
|
if (window.innerWidth < 768) {
|
|
setIsMobileOpen(false);
|
|
} else {
|
|
setIsCollapsed(!isCollapsed);
|
|
}
|
|
}}
|
|
className="p-1 text-gray-400 hover:text-white"
|
|
>
|
|
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2">
|
|
<line x1="3" y1="12" x2="21" y2="12"/><line x1="3" y1="6" x2="21" y2="6"/><line x1="3" y1="18" x2="21" y2="18"/>
|
|
</svg>
|
|
</button>
|
|
</div>
|
|
|
|
<div className="flex-1 px-3 sm:px-4 mt-2 sm:mt-4 overflow-y-auto no-scrollbar">
|
|
{menuItems.map(item => (
|
|
<SidebarItem
|
|
key={item.id}
|
|
icon={item.icon}
|
|
label={item.label}
|
|
isCollapsed={isCollapsed}
|
|
isActive={activeItem === item.id}
|
|
onClick={() => handleNavigate(item.id)}
|
|
/>
|
|
))}
|
|
</div>
|
|
|
|
<div className="p-3 sm:p-4 mt-auto space-y-3 sm:space-y-4">
|
|
{!isCollapsed && (
|
|
<div className="bg-[#1c2a2e] rounded-xl sm:rounded-2xl p-3 sm:p-4 border border-white/5">
|
|
<div className="flex justify-between items-center mb-2">
|
|
<span className="text-gray-400 text-[9px] sm:text-[10px] font-medium">Credit</span>
|
|
<span className="text-white text-[9px] sm:text-[10px] font-bold">850 / 1000</span>
|
|
</div>
|
|
<div className="w-full h-1 sm:h-1.5 bg-gray-800 rounded-full overflow-hidden mb-2 sm:mb-3">
|
|
<div className="h-full bg-[#a6ffea]" style={{ width: '85%' }}></div>
|
|
</div>
|
|
<button className="w-full py-1.5 sm:py-2 bg-[#121a1d] text-white text-[9px] sm:text-[10px] font-bold rounded-lg border border-gray-700 hover:bg-gray-800 transition-colors">
|
|
업그레이드
|
|
</button>
|
|
</div>
|
|
)}
|
|
|
|
<div className={`flex items-center gap-2 sm:gap-3 px-2 ${isCollapsed ? 'flex-col' : ''}`}>
|
|
<img
|
|
src="https://picsum.photos/seed/user/100/100"
|
|
alt="Profile"
|
|
className="w-8 h-8 sm:w-10 sm:h-10 rounded-full border border-gray-700"
|
|
/>
|
|
{!isCollapsed && (
|
|
<div className="flex-1 min-w-0">
|
|
<p className="text-white text-[10px] sm:text-xs font-bold truncate">username1234</p>
|
|
</div>
|
|
)}
|
|
</div>
|
|
|
|
<button className={`w-full flex items-center gap-2 sm:gap-3 px-3 py-2 sm:py-3 text-gray-400 hover:text-white transition-colors ${isCollapsed ? 'justify-center' : ''}`}>
|
|
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5">
|
|
<path d="M9 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h4"/><polyline points="16 17 21 12 16 7"/><line x1="21" y1="12" x2="9" y2="12"/>
|
|
</svg>
|
|
{!isCollapsed && <span className="text-[10px] sm:text-xs font-bold">로그아웃</span>}
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</>
|
|
);
|
|
};
|
|
|
|
export default Sidebar;
|