add dark theme

This commit is contained in:
2026-07-31 23:15:33 +05:00
parent e90199803d
commit 46f88ca04c
4 changed files with 85 additions and 10 deletions
+14
View File
@@ -5,6 +5,20 @@
<link rel="icon" type="image/svg+xml" href="/favicon.svg" /> <link rel="icon" type="image/svg+xml" href="/favicon.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>RADIUS Admin</title> <title>RADIUS Admin</title>
<!-- Critical: paint the correct background before the app CSS loads (avoids flash). -->
<style>
html { background: oklch(1 0 0); color-scheme: light; }
html.dark { background: oklch(0.145 0 0); color-scheme: dark; }
</style>
<script>
// Apply saved (or system) theme before first paint.
(function () {
var t = localStorage.getItem('radui.theme')
if (t === 'dark' || (!t && matchMedia('(prefers-color-scheme: dark)').matches)) {
document.documentElement.classList.add('dark')
}
})()
</script>
</head> </head>
<body> <body>
<div id="root"></div> <div id="root"></div>
+22 -2
View File
@@ -1,12 +1,29 @@
import { NavLink, Navigate, Route, Routes } from 'react-router-dom' import { NavLink, Navigate, Route, Routes } from 'react-router-dom'
import { LogOut, Router, Wifi } from 'lucide-react' import { LogOut, Moon, Router, Sun, Wifi } from 'lucide-react'
import { useAuth } from '@/auth/auth' import { useAuth } from '@/auth/auth'
import { useTheme } from '@/lib/theme'
import { Login } from '@/pages/Login' import { Login } from '@/pages/Login'
import { Devices } from '@/pages/Devices' import { Devices } from '@/pages/Devices'
import { Vlans } from '@/pages/Vlans' import { Vlans } from '@/pages/Vlans'
import { Button } from '@/components/ui/button' import { Button } from '@/components/ui/button'
import { cn } from '@/lib/utils' import { cn } from '@/lib/utils'
function ThemeToggle() {
const { theme, toggle } = useTheme()
return (
<Button
variant="ghost"
size="icon"
onClick={toggle}
className="text-muted-foreground"
title={theme === 'dark' ? 'Switch to light mode' : 'Switch to dark mode'}
aria-label="Toggle theme"
>
{theme === 'dark' ? <Sun className="h-4 w-4" /> : <Moon className="h-4 w-4" />}
</Button>
)
}
function Nav() { function Nav() {
const { logout } = useAuth() const { logout } = useAuth()
const linkClass = ({ isActive }: { isActive: boolean }) => const linkClass = ({ isActive }: { isActive: boolean }) =>
@@ -28,10 +45,13 @@ function Nav() {
<NavLink to="/vlans" className={linkClass}> <NavLink to="/vlans" className={linkClass}>
<Wifi className="h-4 w-4" /> VLANs <Wifi className="h-4 w-4" /> VLANs
</NavLink> </NavLink>
<Button variant="ghost" size="sm" className="ml-auto text-muted-foreground" onClick={logout}> <div className="ml-auto flex items-center gap-1">
<ThemeToggle />
<Button variant="ghost" size="sm" className="text-muted-foreground" onClick={logout}>
<LogOut className="h-4 w-4" /> Sign out <LogOut className="h-4 w-4" /> Sign out
</Button> </Button>
</div> </div>
</div>
</header> </header>
) )
} }
+38
View File
@@ -0,0 +1,38 @@
import { createContext, useCallback, useContext, useEffect, useState, type ReactNode } from 'react'
type Theme = 'light' | 'dark'
const STORAGE = 'radui.theme'
interface ThemeState {
theme: Theme
toggle: () => void
}
const ThemeContext = createContext<ThemeState | null>(null)
function initialTheme(): Theme {
const saved = localStorage.getItem(STORAGE)
if (saved === 'light' || saved === 'dark') return saved
return window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light'
}
export function ThemeProvider({ children }: { children: ReactNode }) {
const [theme, setTheme] = useState<Theme>(initialTheme)
useEffect(() => {
document.documentElement.classList.toggle('dark', theme === 'dark')
localStorage.setItem(STORAGE, theme)
}, [theme])
const toggle = useCallback(() => {
setTheme((t) => (t === 'dark' ? 'light' : 'dark'))
}, [])
return <ThemeContext value={{ theme, toggle }}>{children}</ThemeContext>
}
export function useTheme(): ThemeState {
const ctx = useContext(ThemeContext)
if (!ctx) throw new Error('useTheme must be used within ThemeProvider')
return ctx
}
+3
View File
@@ -4,15 +4,18 @@ import { BrowserRouter } from 'react-router-dom'
import './index.css' import './index.css'
import App from './App.tsx' import App from './App.tsx'
import { AuthProvider } from './auth/auth.tsx' import { AuthProvider } from './auth/auth.tsx'
import { ThemeProvider } from './lib/theme.tsx'
import { Toaster } from './components/ui/sonner.tsx' import { Toaster } from './components/ui/sonner.tsx'
createRoot(document.getElementById('root')!).render( createRoot(document.getElementById('root')!).render(
<StrictMode> <StrictMode>
<ThemeProvider>
<BrowserRouter> <BrowserRouter>
<AuthProvider> <AuthProvider>
<App /> <App />
<Toaster /> <Toaster />
</AuthProvider> </AuthProvider>
</BrowserRouter> </BrowserRouter>
</ThemeProvider>
</StrictMode>, </StrictMode>,
) )