diff --git a/FrontEnd/App.tsx b/FrontEnd/App.tsx index 7e84094..45aed04 100644 --- a/FrontEnd/App.tsx +++ b/FrontEnd/App.tsx @@ -8,6 +8,7 @@ import { HistoryPage } from './pages/HistoryPage'; import { SystemState, Recipe, IOPoint, LogEntry, RobotTarget, ConfigItem } from './types'; import { comms } from './communication'; import { AlertProvider, useAlert } from './contexts/AlertContext'; +import { ThemeProvider } from './contexts/ThemeContext'; import { PickerMoveDialog } from './components/PickerMoveDialog'; // PickerMoveDialog 전역 상태 Context @@ -329,11 +330,13 @@ function AppContent() { ); } -// 외부 App 컴포넌트 - AlertProvider로 감싸기 +// 외부 App 컴포넌트 - ThemeProvider + AlertProvider로 감싸기 export default function App() { return ( - - - + + + + + ); } diff --git a/FrontEnd/components/layout/Footer.tsx b/FrontEnd/components/layout/Footer.tsx index 37b1cac..5b6fe2b 100644 --- a/FrontEnd/components/layout/Footer.tsx +++ b/FrontEnd/components/layout/Footer.tsx @@ -1,6 +1,8 @@ import React, { useState, useEffect, useMemo } from 'react'; +import { Palette, ChevronUp } from 'lucide-react'; import { RobotTarget } from '../../types'; import { comms } from '../../communication'; +import { useTheme, themes, ThemeName } from '../../contexts/ThemeContext'; // HW 상태 타입 (윈폼 HWState와 동일) // status: 0=SET(미설정/회색), 1=ON(연결/녹색), 2=TRIG(트리거/노란색), 3=OFF(연결안됨/빨간색) @@ -38,6 +40,8 @@ const getStatusColor = (status: number): { bg: string; shadow: string; text: str export const Footer: React.FC = ({ isHostConnected, robotTarget }) => { const [hwStatus, setHwStatus] = useState([]); + const [showThemeSelector, setShowThemeSelector] = useState(false); + const { theme, themeName, setTheme, availableThemes } = useTheme(); useEffect(() => { // HW_STATUS_UPDATE 이벤트 구독 @@ -75,6 +79,44 @@ export const Footer: React.FC = ({ isHostConnected, robotTarget }) return errors.sort((a, b) => a.priority - b.priority)[0]; }, [hwStatus]); + // 테마 선택 팝업을 닫기 위한 외부 클릭 감지 + useEffect(() => { + const handleClickOutside = (e: MouseEvent) => { + const target = e.target as HTMLElement; + if (!target.closest('.theme-selector')) { + setShowThemeSelector(false); + } + }; + + if (showThemeSelector) { + document.addEventListener('click', handleClickOutside); + } + + return () => { + document.removeEventListener('click', handleClickOutside); + }; + }, [showThemeSelector]); + + // 테마별 배경색 스타일 (Windows Classic은 특별 처리) + const getFooterBgClass = () => { + switch (themeName) { + case 'windows-classic': + return 'bg-[#c0c0c0] border-t-2 border-white shadow-[inset_0_-1px_0_#808080]'; + case 'dark-modern': + return 'bg-zinc-900/90 border-t border-zinc-700/50'; + case 'matrix': + return 'bg-black/95 border-t border-green-500/30'; + case 'industrial': + return 'bg-stone-900/95 border-t border-orange-500/30'; + case 'otaku': + return 'bg-[#1a1025]/95 border-t border-pink-500/30 shadow-[0_-2px_20px_rgba(255,107,157,0.1)]'; + case 'pink-pink': + return 'bg-[#ffe4e9]/95 border-t-2 border-pink-300 shadow-[0_-2px_10px_rgba(255,105,180,0.2)]'; + default: + return 'bg-black/80 border-t border-neon-blue/30'; + } + }; + return ( <> {/* 하드웨어 오류 표시 배너 - 화면 중앙 상단에 크게 표시 */} @@ -94,8 +136,8 @@ export const Footer: React.FC = ({ isHostConnected, robotTarget }) )} -