diff --git a/client/index.html b/client/index.html index a6d1d08..de11f05 100644 --- a/client/index.html +++ b/client/index.html @@ -3,6 +3,10 @@ + + + Active Directory Management +
diff --git a/client/public/favicon.svg b/client/public/favicon.svg new file mode 100644 index 0000000..95f71ab --- /dev/null +++ b/client/public/favicon.svg @@ -0,0 +1,10 @@ + + + + + \ No newline at end of file diff --git a/client/src/components/theme-toggle.tsx b/client/src/components/theme-toggle.tsx index 8bcce77..492bbf4 100644 --- a/client/src/components/theme-toggle.tsx +++ b/client/src/components/theme-toggle.tsx @@ -1,11 +1,12 @@ import * as React from "react"; -import { Moon, Sun, Laptop } from "lucide-react"; +import { Moon, Sun, Laptop, Check } from "lucide-react"; import { Button } from "@/components/ui/button"; import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger, + DropdownMenuSeparator, } from "@/components/ui/dropdown-menu"; import { useTheme } from "@/hooks/use-theme"; @@ -28,29 +29,83 @@ export function ThemeToggle() { // Component that uses the theme context function ThemeToggleWithContext() { - const { theme, setTheme } = useTheme(); + const { theme, setTheme, resolvedTheme } = useTheme(); + + const iconClassName = "h-5 w-5 transition-all duration-200"; + + // Determine which icon to show in the button based on current theme + const getButtonIcon = () => { + if (theme === 'system') { + return resolvedTheme === 'dark' + ? + : ; + } else if (theme === 'dark') { + return ; + } else { + return ; + } + }; return ( - - setTheme("light")}> - - Light +
+ Theme +
+ + setTheme("light")} + className="flex justify-between" + > +
+ + Light +
+ {theme === "light" && }
- setTheme("dark")}> - - Dark + setTheme("dark")} + className="flex justify-between" + > +
+ + Dark +
+ {theme === "dark" && }
- setTheme("system")}> - - System + setTheme("system")} + className="flex justify-between" + > +
+ + System +
+ {theme === "system" && ( +
+ {resolvedTheme === "dark" ? ( + + ) : ( + + )} + +
+ )}
diff --git a/client/src/hooks/use-theme.tsx b/client/src/hooks/use-theme.tsx index 4c242d5..8c9ad4f 100644 --- a/client/src/hooks/use-theme.tsx +++ b/client/src/hooks/use-theme.tsx @@ -5,6 +5,7 @@ type Theme = 'dark' | 'light' | 'system'; interface ThemeContextType { theme: Theme; setTheme: (theme: Theme) => void; + resolvedTheme: 'dark' | 'light'; } const ThemeContext = createContext(undefined); @@ -24,25 +25,60 @@ export function ThemeProvider({ const [theme, setTheme] = useState( () => (localStorage.getItem(storageKey) as Theme) || defaultTheme ); + const [resolvedTheme, setResolvedTheme] = useState<'dark' | 'light'>( + window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light' + ); + // Apply the theme to the document useEffect(() => { const root = window.document.documentElement; root.classList.remove('light', 'dark'); + let effectiveTheme: 'light' | 'dark'; + if (theme === 'system') { - const systemTheme = window.matchMedia('(prefers-color-scheme: dark)').matches - ? 'dark' - : 'light'; - - root.classList.add(systemTheme); - return; + effectiveTheme = resolvedTheme; + } else { + effectiveTheme = theme as 'light' | 'dark'; } + + root.classList.add(effectiveTheme); - root.classList.add(theme); - }, [theme]); + // Add a transition for smoother theme changes + root.style.colorScheme = effectiveTheme; + + // Set meta theme-color for mobile browsers + document.querySelector('meta[name="theme-color"]')?.setAttribute( + 'content', + effectiveTheme === 'dark' ? '#020817' : '#ffffff' + ); + }, [theme, resolvedTheme]); + + // Listen for system theme changes + useEffect(() => { + const mediaQuery = window.matchMedia('(prefers-color-scheme: dark)'); + + // Define handler for system theme changes + const handleSystemThemeChange = (event: MediaQueryListEvent) => { + const newSystemTheme = event.matches ? 'dark' : 'light'; + setResolvedTheme(newSystemTheme); + + // Log the change for debugging + console.log(`System theme changed to ${newSystemTheme}`); + }; + + // Add listener for system theme changes + mediaQuery.addEventListener('change', handleSystemThemeChange); + + // Clean up listener on component unmount + return () => { + mediaQuery.removeEventListener('change', handleSystemThemeChange); + }; + }, []); const value = { theme, + resolvedTheme, setTheme: (theme: Theme) => { localStorage.setItem(storageKey, theme); setTheme(theme); @@ -64,6 +100,9 @@ export const useTheme = (): ThemeContextType => { // This prevents crashes but won't sync across components const storageKey = 'ad-management-theme'; const defaultTheme = 'system'; + const resolvedSystemTheme = window.matchMedia('(prefers-color-scheme: dark)').matches + ? 'dark' + : 'light'; // Create a fallback state using localStorage const getThemeFromStorage = (): Theme => { @@ -77,19 +116,29 @@ export const useTheme = (): ThemeContextType => { const currentTheme = getThemeFromStorage(); + const getResolvedTheme = (theme: Theme): 'light' | 'dark' => { + if (theme === 'system') { + return resolvedSystemTheme; + } + return theme as 'light' | 'dark'; + }; + const applyThemeToDOM = (newTheme: Theme) => { try { const root = window.document.documentElement; root.classList.remove('light', 'dark'); - if (newTheme === 'system') { - const systemTheme = window.matchMedia('(prefers-color-scheme: dark)').matches - ? 'dark' - : 'light'; - root.classList.add(systemTheme); - } else { - root.classList.add(newTheme); - } + const effectiveTheme = getResolvedTheme(newTheme); + root.classList.add(effectiveTheme); + + // Add a transition for smoother theme changes + root.style.colorScheme = effectiveTheme; + + // Set meta theme-color for mobile browsers + document.querySelector('meta[name="theme-color"]')?.setAttribute( + 'content', + effectiveTheme === 'dark' ? '#020817' : '#ffffff' + ); } catch (e) { console.error('Error applying theme to DOM:', e); } @@ -101,6 +150,7 @@ export const useTheme = (): ThemeContextType => { // Return a fallback implementation return { theme: currentTheme, + resolvedTheme: getResolvedTheme(currentTheme), setTheme: (newTheme: Theme) => { try { localStorage.setItem(storageKey, newTheme);