diff hg-web/src/components/theme.tsx @ 193:9f4429c49733 hg-web

[HgWeb] Making progress....
author MrJuneJune <me@mrjunejune.com>
date Sun, 25 Jan 2026 20:04:55 -0800
parents
children
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/hg-web/src/components/theme.tsx	Sun Jan 25 20:04:55 2026 -0800
@@ -0,0 +1,72 @@
+import React, { createContext, useContext, useState, useEffect, useCallback, ReactNode } from 'react';
+
+interface ThemeContextType {
+  isDark: boolean;
+  toggleTheme: () => void;
+}
+
+const ThemeContext = createContext<ThemeContextType | undefined>(undefined);
+
+// Apply theme class to document root
+function applyTheme(isDark: boolean) {
+  const root = document.documentElement;
+  if (isDark) {
+    root.classList.add('dark');
+    root.classList.remove('light');
+  } else {
+    root.classList.add('light');
+    root.classList.remove('dark');
+  }
+}
+
+interface ThemeProviderProps {
+  children: ReactNode;
+}
+
+function ThemeProvider({ children }: ThemeProviderProps) {
+  const [isDark, setIsDark] = useState(() => {
+    const saved = localStorage.getItem('theme');
+    if (saved) return saved === 'dark';
+    return window.matchMedia('(prefers-color-scheme: dark)').matches;
+  });
+
+  // Apply theme on mount and change
+  useEffect(() => {
+    applyTheme(isDark);
+    localStorage.setItem('theme', isDark ? 'dark' : 'light');
+  }, [isDark]);
+
+  // Listen for system theme changes
+  useEffect(() => {
+    const mediaQuery = window.matchMedia('(prefers-color-scheme: dark)');
+    const handleChange = (e: MediaQueryListEvent) => {
+      // Only apply if no explicit preference is saved
+      if (!localStorage.getItem('theme')) {
+        setIsDark(e.matches);
+      }
+    };
+
+    mediaQuery.addEventListener('change', handleChange);
+    return () => mediaQuery.removeEventListener('change', handleChange);
+  }, []);
+
+  const toggleTheme = useCallback(() => {
+    setIsDark(prev => !prev);
+  }, []);
+
+  return (
+    <ThemeContext.Provider value={{ isDark, toggleTheme }}>
+      {children}
+    </ThemeContext.Provider>
+  );
+}
+
+function useTheme(): ThemeContextType {
+  const context = useContext(ThemeContext);
+  if (context === undefined) {
+    throw new Error('useTheme must be used within a ThemeProvider');
+  }
+  return context;
+}
+
+export { ThemeProvider, useTheme };