|
1 | 1 | import React, { useState, useEffect } from 'react'; |
2 | 2 |
|
| 3 | +// Dark mode icons as inline SVG |
| 4 | +const MoonIcon = () => ( |
| 5 | + <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" className="dark-mode-icon"> |
| 6 | + <path fillRule="evenodd" d="M9.528 1.718a.75.75 0 01.162.819A8.97 8.97 0 009 6a9 9 0 009 9 8.97 8.97 0 003.463-.69.75.75 0 01.981.98 10.503 10.503 0 01-9.694 6.46c-5.799 0-10.5-4.701-10.5-10.5 0-4.368 2.667-8.112 6.46-9.694a.75.75 0 01.818.162z" clipRule="evenodd" /> |
| 7 | + </svg> |
| 8 | +); |
| 9 | + |
| 10 | +const SunIcon = () => ( |
| 11 | + <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" className="light-mode-icon"> |
| 12 | + <path d="M12 2.25a.75.75 0 01.75.75v2.25a.75.75 0 01-1.5 0V3a.75.75 0 01.75-.75zM7.5 12a4.5 4.5 0 119 0 4.5 4.5 0 01-9 0zM18.894 6.166a.75.75 0 00-1.06-1.06l-1.591 1.59a.75.75 0 101.06 1.061l1.591-1.59zM21.75 12a.75.75 0 01-.75.75h-2.25a.75.75 0 010-1.5H21a.75.75 0 01.75.75zM17.834 18.894a.75.75 0 001.06-1.06l-1.59-1.591a.75.75 0 10-1.061 1.06l1.59 1.591zM12 18a.75.75 0 01.75.75V21a.75.75 0 01-1.5 0v-2.25A.75.75 0 0112 18zM7.758 17.303a.75.75 0 00-1.061-1.06l-1.591 1.59a.75.75 0 001.06 1.061l1.591-1.59zM6 12a.75.75 0 01-.75.75H3a.75.75 0 010-1.5h2.25A.75.75 0 016 12zM6.697 7.757a.75.75 0 001.06-1.06l-1.59-1.591a.75.75 0 00-1.061 1.06l1.59 1.591z" /> |
| 13 | + </svg> |
| 14 | +); |
| 15 | + |
| 16 | +// Dark mode initializer |
| 17 | +const initializeDarkMode = () => { |
| 18 | + // Check for saved preference or use system preference |
| 19 | + const savedTheme = localStorage.getItem('theme'); |
| 20 | + const prefersDark = window.matchMedia('(prefers-color-scheme: dark)').matches; |
| 21 | + |
| 22 | + if (savedTheme === 'dark' || (!savedTheme && prefersDark)) { |
| 23 | + document.documentElement.classList.add('dark-mode'); |
| 24 | + return true; |
| 25 | + } |
| 26 | + |
| 27 | + return false; |
| 28 | +}; |
| 29 | + |
| 30 | +// Dark mode toggle functionality |
| 31 | +const toggleDarkMode = (isDark, setIsDark) => { |
| 32 | + if (isDark) { |
| 33 | + document.documentElement.classList.remove('dark-mode'); |
| 34 | + localStorage.setItem('theme', 'light'); |
| 35 | + setIsDark(false); |
| 36 | + } else { |
| 37 | + document.documentElement.classList.add('dark-mode'); |
| 38 | + localStorage.setItem('theme', 'dark'); |
| 39 | + setIsDark(true); |
| 40 | + } |
| 41 | +}; |
| 42 | + |
| 43 | +// Dark mode toggle component |
| 44 | +const DarkModeToggle = ({ isDark, setIsDark }) => { |
| 45 | + return ( |
| 46 | + <button |
| 47 | + onClick={() => toggleDarkMode(isDark, setIsDark)} |
| 48 | + className="dark-mode-toggle ml-4" |
| 49 | + aria-label={isDark ? "Switch to light mode" : "Switch to dark mode"} |
| 50 | + title={isDark ? "Switch to light mode" : "Switch to dark mode"} |
| 51 | + > |
| 52 | + {isDark ? <SunIcon /> : <MoonIcon />} |
| 53 | + </button> |
| 54 | + ); |
| 55 | +}; |
| 56 | + |
3 | 57 | // --- API Service Configuration --- |
4 | 58 | const API_URL = '/api'; |
5 | 59 |
|
@@ -144,6 +198,12 @@ const App = () => { |
144 | 198 | const [resourceId, setResourceId] = useState(null); |
145 | 199 | const [middlewareId, setMiddlewareId] = useState(null); |
146 | 200 | const [isEditing, setIsEditing] = useState(false); |
| 201 | + const [isDarkMode, setIsDarkMode] = useState(false); |
| 202 | + |
| 203 | + // Initialize dark mode on component mount |
| 204 | + useEffect(() => { |
| 205 | + setIsDarkMode(initializeDarkMode()); |
| 206 | + }, []); |
147 | 207 |
|
148 | 208 | // Handles navigation between pages |
149 | 209 | const navigateTo = (pageId, id = null) => { |
@@ -190,35 +250,38 @@ const App = () => { |
190 | 250 | <div className="text-xl font-semibold text-gray-700"> |
191 | 251 | Pangolin Middleware Manager |
192 | 252 | </div> |
193 | | - <div className="space-x-4"> |
194 | | - <button |
195 | | - onClick={() => navigateTo('dashboard')} |
196 | | - className={`px-3 py-2 rounded hover:bg-gray-100 ${ |
197 | | - page === 'dashboard' ? 'bg-gray-100' : '' |
198 | | - }`} |
199 | | - > |
200 | | - Dashboard |
201 | | - </button> |
202 | | - <button |
203 | | - onClick={() => navigateTo('resources')} |
204 | | - className={`px-3 py-2 rounded hover:bg-gray-100 ${ |
205 | | - page === 'resources' || page === 'resource-detail' |
206 | | - ? 'bg-gray-100' |
207 | | - : '' |
208 | | - }`} |
209 | | - > |
210 | | - Resources |
211 | | - </button> |
212 | | - <button |
213 | | - onClick={() => navigateTo('middlewares')} |
214 | | - className={`px-3 py-2 rounded hover:bg-gray-100 ${ |
215 | | - page === 'middlewares' || page === 'middleware-form' |
216 | | - ? 'bg-gray-100' |
217 | | - : '' |
218 | | - }`} |
219 | | - > |
220 | | - Middlewares |
221 | | - </button> |
| 253 | + <div className="flex items-center"> |
| 254 | + <div className="space-x-4"> |
| 255 | + <button |
| 256 | + onClick={() => navigateTo('dashboard')} |
| 257 | + className={`px-3 py-2 rounded hover:bg-gray-100 ${ |
| 258 | + page === 'dashboard' ? 'bg-gray-100' : '' |
| 259 | + }`} |
| 260 | + > |
| 261 | + Dashboard |
| 262 | + </button> |
| 263 | + <button |
| 264 | + onClick={() => navigateTo('resources')} |
| 265 | + className={`px-3 py-2 rounded hover:bg-gray-100 ${ |
| 266 | + page === 'resources' || page === 'resource-detail' |
| 267 | + ? 'bg-gray-100' |
| 268 | + : '' |
| 269 | + }`} |
| 270 | + > |
| 271 | + Resources |
| 272 | + </button> |
| 273 | + <button |
| 274 | + onClick={() => navigateTo('middlewares')} |
| 275 | + className={`px-3 py-2 rounded hover:bg-gray-100 ${ |
| 276 | + page === 'middlewares' || page === 'middleware-form' |
| 277 | + ? 'bg-gray-100' |
| 278 | + : '' |
| 279 | + }`} |
| 280 | + > |
| 281 | + Middlewares |
| 282 | + </button> |
| 283 | + </div> |
| 284 | + <DarkModeToggle isDark={isDarkMode} setIsDark={setIsDarkMode} /> |
222 | 285 | </div> |
223 | 286 | </div> |
224 | 287 | </div> |
|
0 commit comments