All files / memory-monitor/src/hooks useTheme.ts

65.71% Statements 23/35
40% Branches 8/20
58.33% Functions 7/12
66.66% Lines 22/33

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142                                                                              27x     27x                   64x 64x                                                 64x   64x 64x     64x 27x       64x 27x       27x   27x         27x 27x 27x                 64x     64x 27x     64x       64x                   64x                
import { useState, useEffect, useCallback } from "react";
import type { ThemeSetting } from "../types";
import { isSSR } from "../utils/environment";
 
/**
 * Resolved theme (actual theme being applied)
 */
export type ResolvedTheme = "light" | "dark";
 
/**
 * Options for useTheme hook
 */
export interface UseThemeOptions {
  /** Theme setting (light, dark, or system) */
  theme?: ThemeSetting;
  /** Callback when resolved theme changes */
  onThemeChange?: (theme: ResolvedTheme) => void;
}
 
/**
 * Return type for useTheme hook
 */
export interface UseThemeReturn {
  /** Current theme setting */
  themeSetting: ThemeSetting;
  /** Resolved theme (light or dark) */
  resolvedTheme: ResolvedTheme;
  /** Whether system prefers dark mode */
  systemPrefersDark: boolean;
  /** Set the theme */
  setTheme: (theme: ThemeSetting) => void;
  /** Toggle between light and dark */
  toggleTheme: () => void;
}
 
/**
 * Get system color scheme preference
 */
function getSystemPreference(): boolean {
  Iif (isSSR()) {
    return false;
  }
  return window.matchMedia?.("(prefers-color-scheme: dark)").matches ?? false;
}
 
/**
 * Resolve theme setting to actual theme
 */
function resolveTheme(
  setting: ThemeSetting,
  systemPrefersDark: boolean
): ResolvedTheme {
  Eif (setting === "system") {
    return systemPrefersDark ? "dark" : "light";
  }
  return setting;
}
 
/**
 * Hook to manage theme with system preference detection
 *
 * @param options - Configuration options
 * @returns Theme state and control functions
 *
 * @example
 * ```tsx
 * const { resolvedTheme, toggleTheme } = useTheme({
 *   theme: 'system',
 * });
 *
 * return (
 *   <div className={resolvedTheme === 'dark' ? 'dark' : ''}>
 *     <button onClick={toggleTheme}>Toggle Theme</button>
 *   </div>
 * );
 * ```
 */
export function useTheme(options: UseThemeOptions = {}): UseThemeReturn {
  const { theme: themeProp = "system", onThemeChange } = options;
 
  const [themeSetting, setThemeSetting] = useState<ThemeSetting>(themeProp);
  const [systemPrefersDark, setSystemPrefersDark] = useState(getSystemPreference);
 
  // Sync with external theme prop changes
  useEffect(() => {
    setThemeSetting(themeProp);
  }, [themeProp]);
 
  // Listen for system preference changes
  useEffect(() => {
    Iif (isSSR()) {
      return;
    }
 
    const mediaQuery = window.matchMedia("(prefers-color-scheme: dark)");
 
    const handleChange = (event: MediaQueryListEvent) => {
      setSystemPrefersDark(event.matches);
    };
 
    // Modern browsers
    Eif (mediaQuery.addEventListener) {
      mediaQuery.addEventListener("change", handleChange);
      return () => mediaQuery.removeEventListener("change", handleChange);
    }
 
    // Legacy browsers
    mediaQuery.addListener(handleChange);
    return () => mediaQuery.removeListener(handleChange);
  }, []);
 
  // Resolve current theme
  const resolvedTheme = resolveTheme(themeSetting, systemPrefersDark);
 
  // Notify on theme change
  useEffect(() => {
    onThemeChange?.(resolvedTheme);
  }, [resolvedTheme, onThemeChange]);
 
  const setTheme = useCallback((theme: ThemeSetting) => {
    setThemeSetting(theme);
  }, []);
 
  const toggleTheme = useCallback(() => {
    setThemeSetting((prev) => {
      if (prev === "system") {
        // If system, toggle to opposite of current resolved
        return systemPrefersDark ? "light" : "dark";
      }
      return prev === "light" ? "dark" : "light";
    });
  }, [systemPrefersDark]);
 
  return {
    themeSetting,
    resolvedTheme,
    systemPrefersDark,
    setTheme,
    toggleTheme,
  };
}