From 1a45cf507598551d5d94944f008e21d013990d06 Mon Sep 17 00:00:00 2001 From: Sanaullah-Turab Date: Wed, 9 Sep 2026 18:12:16 +0500 Subject: [PATCH] feat: add dark/light theme switching with ThemeContext and localStorage persistence --- frontend/src/ThemeContext.tsx | 46 +++++++++++++++++++++ frontend/src/components/Navbar.tsx | 26 ++++++++---- frontend/src/index.css | 62 ++++++++++++++++++++--------- frontend/src/main.tsx | 7 +++- frontend/src/pages/SettingsPage.tsx | 32 ++++++++++----- 5 files changed, 135 insertions(+), 38 deletions(-) create mode 100644 frontend/src/ThemeContext.tsx diff --git a/frontend/src/ThemeContext.tsx b/frontend/src/ThemeContext.tsx new file mode 100644 index 0000000..e67f3dd --- /dev/null +++ b/frontend/src/ThemeContext.tsx @@ -0,0 +1,46 @@ +import React, { createContext, useContext, useEffect, useState } from 'react'; + +type Theme = 'light' | 'dark'; + +interface ThemeContextType { + theme: Theme; + toggleTheme: () => void; +} + +const ThemeContext = createContext(undefined); + +export const ThemeProvider: React.FC<{ children: React.ReactNode }> = ({ children }) => { + const [theme, setTheme] = useState(() => { + const saved = localStorage.getItem('theme'); + if (saved === 'light' || saved === 'dark') return saved; + if (window.matchMedia && window.matchMedia('(prefers-color-scheme: light)').matches) { + return 'light'; + } + return 'dark'; + }); + + useEffect(() => { + const root = window.document.documentElement; + root.classList.remove('light', 'dark'); + root.classList.add(theme); + localStorage.setItem('theme', theme); + }, [theme]); + + const toggleTheme = () => { + setTheme(prev => (prev === 'dark' ? 'light' : 'dark')); + }; + + return ( + + {children} + + ); +}; + +export const useTheme = () => { + const context = useContext(ThemeContext); + if (context === undefined) { + throw new Error('useTheme must be used within a ThemeProvider'); + } + return context; +}; diff --git a/frontend/src/components/Navbar.tsx b/frontend/src/components/Navbar.tsx index bc833ae..486b56b 100644 --- a/frontend/src/components/Navbar.tsx +++ b/frontend/src/components/Navbar.tsx @@ -1,4 +1,4 @@ -import React from 'react'; +import React from 'react'; import { Activity, Compass, @@ -6,8 +6,11 @@ import { Cpu, BarChart3, Sliders, - Radio + Radio, + Sun, + Moon } from 'lucide-react'; +import { useTheme } from '../ThemeContext'; interface NavbarProps { activeTab: string; @@ -16,6 +19,7 @@ interface NavbarProps { } export const Navbar: React.FC = ({ activeTab, setActiveTab, onlineStatus = true }) => { + const { theme, toggleTheme } = useTheme(); const navItems = [ { id: 'dashboard', label: 'Control Room', icon: Activity }, { id: 'playground', label: 'Playground', icon: Compass }, @@ -27,7 +31,7 @@ export const Navbar: React.FC = ({ activeTab, setActiveTab, onlineS ]; return ( -
+
{/* Brand / Logo */} @@ -35,7 +39,7 @@ export const Navbar: React.FC = ({ activeTab, setActiveTab, onlineS onClick={() => setActiveTab('dashboard')} className="flex items-center gap-3 cursor-pointer select-none shrink-0 group" > -
+
Model Router Logo = ({ activeTab, setActiveTab, onlineS
{/* Navigation Tabs */} -