add dark theme
This commit is contained in:
+14
@@ -5,6 +5,20 @@
|
||||
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<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>
|
||||
<body>
|
||||
<div id="root"></div>
|
||||
|
||||
+24
-4
@@ -1,12 +1,29 @@
|
||||
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 { useTheme } from '@/lib/theme'
|
||||
import { Login } from '@/pages/Login'
|
||||
import { Devices } from '@/pages/Devices'
|
||||
import { Vlans } from '@/pages/Vlans'
|
||||
import { Button } from '@/components/ui/button'
|
||||
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() {
|
||||
const { logout } = useAuth()
|
||||
const linkClass = ({ isActive }: { isActive: boolean }) =>
|
||||
@@ -28,9 +45,12 @@ function Nav() {
|
||||
<NavLink to="/vlans" className={linkClass}>
|
||||
<Wifi className="h-4 w-4" /> VLANs
|
||||
</NavLink>
|
||||
<Button variant="ghost" size="sm" className="ml-auto text-muted-foreground" onClick={logout}>
|
||||
<LogOut className="h-4 w-4" /> Sign out
|
||||
</Button>
|
||||
<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
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
)
|
||||
|
||||
@@ -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
|
||||
}
|
||||
+9
-6
@@ -4,15 +4,18 @@ import { BrowserRouter } from 'react-router-dom'
|
||||
import './index.css'
|
||||
import App from './App.tsx'
|
||||
import { AuthProvider } from './auth/auth.tsx'
|
||||
import { ThemeProvider } from './lib/theme.tsx'
|
||||
import { Toaster } from './components/ui/sonner.tsx'
|
||||
|
||||
createRoot(document.getElementById('root')!).render(
|
||||
<StrictMode>
|
||||
<BrowserRouter>
|
||||
<AuthProvider>
|
||||
<App />
|
||||
<Toaster />
|
||||
</AuthProvider>
|
||||
</BrowserRouter>
|
||||
<ThemeProvider>
|
||||
<BrowserRouter>
|
||||
<AuthProvider>
|
||||
<App />
|
||||
<Toaster />
|
||||
</AuthProvider>
|
||||
</BrowserRouter>
|
||||
</ThemeProvider>
|
||||
</StrictMode>,
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user