feat: 라이트/다크 모드 - 팝업 메뉴 및 핵심 색상 변경

- 테마 토글을 팝업 메뉴로 변경 (시스템/다크/라이트 선택)
- App.jsx: 모든 레이아웃을 CSS 변수로 변경
- Sidebar.jsx: 배경색, 프로필 메뉴, 드롭다운을 CSS 변수로 변경
- 라이트 모드에서 기본 배경/텍스트 색상 적용
This commit is contained in:
caadiq 2025-12-31 19:14:53 +09:00
parent 91d751fc93
commit 47cf949a32
2 changed files with 77 additions and 21 deletions

View file

@ -49,7 +49,7 @@ function App() {
return (
<ThemeProvider>
<DeviceLayout>
<div className="min-h-screen bg-mc-bg text-gray-200 font-sans selection:bg-mc-green/30">
<div className="min-h-screen bg-[var(--bg-primary)] text-[var(--text-primary)] font-sans selection:bg-mc-green/30">
<div className="absolute inset-0 bg-[url('https://www.transparenttextures.com/patterns/cubes.png')] opacity-[0.03] pointer-events-none"></div>
<AnimatePresence mode="wait">
<Routes location={location} key={location.pathname}>
@ -69,7 +69,7 @@ function App() {
return (
<ThemeProvider>
<DeviceLayout>
<div className="bg-mc-bg text-gray-200 font-sans selection:bg-mc-green/30">
<div className="bg-[var(--bg-primary)] text-[var(--text-primary)] font-sans selection:bg-mc-green/30">
<div className="absolute inset-0 bg-[url('https://www.transparenttextures.com/patterns/cubes.png')] opacity-[0.03] pointer-events-none"></div>
<AnimatePresence mode="wait">
<Routes location={location} key={location.pathname}>
@ -86,7 +86,7 @@ function App() {
return (
<ThemeProvider>
<DeviceLayout>
<div className="min-h-screen bg-mc-bg text-gray-200 font-sans selection:bg-mc-green/30">
<div className="min-h-screen bg-[var(--bg-primary)] text-[var(--text-primary)] font-sans selection:bg-mc-green/30">
<div className="absolute inset-0 bg-[url('https://www.transparenttextures.com/patterns/cubes.png')] opacity-[0.03] pointer-events-none"></div>
{/* 사이드바 + 메인 콘텐츠 레이아웃 */}

View file

@ -175,10 +175,10 @@ const Sidebar = ({ isMobile = false }) => {
animate={{ opacity: 1, y: 0, scale: 1 }}
exit={{ opacity: 0, y: -10, scale: 0.95 }}
transition={{ duration: 0.15 }}
className="absolute bottom-full left-0 right-0 mb-2 bg-zinc-800 border border-zinc-700 rounded-xl shadow-xl overflow-hidden z-50"
className="absolute bottom-full left-0 right-0 mb-2 bg-[var(--bg-secondary)] border border-[var(--border-color)] rounded-xl shadow-xl overflow-hidden z-50"
>
{/* 프로필 정보 */}
<div className="p-4 bg-zinc-800/80 border-b border-zinc-700">
<div className="p-4 bg-[var(--bg-tertiary)] border-b border-[var(--border-color)]">
<div className="flex items-center gap-3">
<img
src={user?.profileUrl || '/default-profile.png'}
@ -202,7 +202,7 @@ const Sidebar = ({ isMobile = false }) => {
<div className="py-1">
<button
onClick={() => { setShowProfileMenu(false); navigate('/profile'); }}
className="w-full flex items-center gap-3 px-4 py-2.5 text-sm text-zinc-300 hover:bg-zinc-700 hover:text-white transition-colors"
className="w-full flex items-center gap-3 px-4 py-2.5 text-sm text-[var(--text-secondary)] hover:bg-[var(--bg-hover)] hover:text-[var(--text-primary)] transition-colors"
>
<User size={16} />
프로필 관리
@ -290,8 +290,24 @@ const Sidebar = ({ isMobile = false }) => {
</AnimatePresence>
);
//
//
const [showThemeMenu, setShowThemeMenu] = useState(false);
const themeMenuRef = useRef(null);
//
useEffect(() => {
const handleClickOutside = (e) => {
if (themeMenuRef.current && !themeMenuRef.current.contains(e.target)) {
setShowThemeMenu(false);
}
};
document.addEventListener('mousedown', handleClickOutside);
return () => document.removeEventListener('mousedown', handleClickOutside);
}, []);
const ThemeToggle = () => {
const { setTheme } = useTheme();
const getThemeIcon = () => {
if (theme === 'system') return <Monitor size={18} />;
if (theme === 'dark') return <Moon size={18} />;
@ -304,15 +320,55 @@ const Sidebar = ({ isMobile = false }) => {
return '라이트';
};
const themeOptions = [
{ value: 'system', label: '시스템', icon: <Monitor size={16} /> },
{ value: 'dark', label: '다크', icon: <Moon size={16} /> },
{ value: 'light', label: '라이트', icon: <Sun size={16} /> },
];
return (
<div className="relative px-4 mb-2" ref={themeMenuRef}>
<button
onClick={cycleTheme}
className="w-full flex items-center gap-3 px-4 py-3 mx-4 mb-2 rounded-xl text-[var(--text-secondary)] hover:text-[var(--text-primary)] hover:bg-[var(--bg-hover)] transition-colors"
style={{ width: 'calc(100% - 32px)' }}
onClick={() => setShowThemeMenu(!showThemeMenu)}
className="w-full flex items-center gap-3 px-4 py-3 rounded-xl text-[var(--text-secondary)] hover:text-[var(--text-primary)] hover:bg-[var(--bg-hover)] transition-colors"
>
{getThemeIcon()}
<span className="font-medium">테마: {getThemeLabel()}</span>
</button>
<AnimatePresence>
{showThemeMenu && (
<motion.div
initial={{ opacity: 0, y: 10, scale: 0.95 }}
animate={{ opacity: 1, y: 0, scale: 1 }}
exit={{ opacity: 0, y: 10, scale: 0.95 }}
transition={{ duration: 0.15 }}
className="absolute bottom-full left-4 right-4 mb-2 bg-[var(--bg-secondary)] border border-[var(--border-color)] rounded-xl shadow-xl overflow-hidden z-50"
>
{themeOptions.map((option) => (
<button
key={option.value}
onClick={() => {
setTheme(option.value);
setShowThemeMenu(false);
}}
className={`w-full flex items-center gap-3 px-4 py-2.5 text-sm transition-colors ${
theme === option.value
? 'text-mc-green bg-mc-green/10'
: 'text-[var(--text-secondary)] hover:bg-[var(--bg-hover)] hover:text-[var(--text-primary)]'
}`}
>
{option.icon}
{option.label}
{theme === option.value && (
<span className="ml-auto text-mc-green"></span>
)}
</button>
))}
</motion.div>
)}
</AnimatePresence>
</div>
);
};
@ -358,7 +414,7 @@ const Sidebar = ({ isMobile = false }) => {
)}
</AnimatePresence>
{/* 상단 툴바 */}
<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">
<header className="fixed top-0 left-0 right-0 z-50 bg-[var(--bg-secondary)]/95 backdrop-blur-xl border-b border-white/10 safe-area-top">
<div className="flex items-center justify-between h-14 px-4">
{/* 햄버거 메뉴 버튼 */}
<button
@ -394,10 +450,10 @@ const Sidebar = ({ isMobile = false }) => {
animate={{ opacity: 1, y: 0, scale: 1 }}
exit={{ opacity: 0, y: -10, scale: 0.95 }}
transition={{ duration: 0.15 }}
className="absolute top-full right-0 mt-2 w-56 bg-zinc-800 border border-zinc-700 rounded-xl shadow-xl overflow-hidden z-[60]"
className="absolute top-full right-0 mt-2 w-56 bg-[var(--bg-secondary)] border border-[var(--border-color)] rounded-xl shadow-xl overflow-hidden z-[60]"
>
{/* 프로필 정보 */}
<div className="p-3 bg-zinc-800/80 border-b border-zinc-700">
<div className="p-3 bg-[var(--bg-tertiary)] border-b border-[var(--border-color)]">
<div className="flex items-center gap-3">
<img
src={user?.profileUrl || '/default-profile.png'}
@ -416,7 +472,7 @@ const Sidebar = ({ isMobile = false }) => {
<div className="py-1">
<button
onClick={() => { setShowProfileMenu(false); navigate('/profile'); }}
className="w-full flex items-center gap-3 px-4 py-2.5 text-sm text-zinc-300 hover:bg-zinc-700 hover:text-white transition-colors"
className="w-full flex items-center gap-3 px-4 py-2.5 text-sm text-[var(--text-secondary)] hover:bg-[var(--bg-hover)] hover:text-[var(--text-primary)] transition-colors"
>
<User size={16} />
프로필 수정
@ -489,7 +545,7 @@ const Sidebar = ({ isMobile = false }) => {
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"
className="fixed left-0 top-0 bottom-0 w-72 bg-[var(--bg-secondary)]/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">
@ -571,7 +627,7 @@ const Sidebar = ({ isMobile = false }) => {
)}
</AnimatePresence>
{/* 데스크탑 사이드바 (항상 표시) */}
<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">
<aside className="hidden md:flex fixed left-0 top-0 bottom-0 w-64 bg-[var(--bg-secondary)]/95 backdrop-blur-xl border-r border-white/10 z-30 flex-col">
{/* 로고 */}
<div className="p-6 border-b border-white/10">
<div className="flex items-center gap-3">