All files / src/components/common ThemeToggle.tsx

81.25% Statements 52/64
91.66% Branches 11/12
100% Functions 2/2
81.25% Lines 52/64

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 941x                         1x 10x 10x 10x     10x 5x   5x 2x 2x 2x 5x 3x 3x 3x 3x                             10x   10x 2x 2x     2x   1x 1x 1x 1x   1x 1x 1x 1x   2x 2x 2x   10x 10x 10x 10x 10x 10x   10x 4x 4x 4x 4x 4x 4x 4x   6x 6x 6x   10x   10x   1x  
import React, { useEffect, useState } from "react";
 
interface ThemeToggleProps {
  testId?: string;
}
 
/**
 * Component for toggling between light and dark themes
 *
 * ## Business Perspective
 * Improves user experience by allowing customization of the interface
 * based on personal preference and environmental conditions. 🎨
 */
const ThemeToggle: React.FC<ThemeToggleProps> = ({
  testId = "theme-toggle",
}) => {
  const [darkMode, setDarkMode] = useState<boolean>(false);
 
  // Initialize theme from localStorage or system preference
  useEffect(() => {
    const savedTheme = localStorage.getItem("theme");
 
    if (savedTheme === "dark") {
      setDarkMode(true);
      document.documentElement.classList.remove("light");
      document.documentElement.classList.add("dark");
    } else if (savedTheme === "light" || !savedTheme) {
      setDarkMode(false);
      document.documentElement.classList.remove("dark");
      document.documentElement.classList.add("light");
    } else {
      // Check system preference
      const prefersDark = window.matchMedia(
        "(prefers-color-scheme: dark)"
      ).matches;
      setDarkMode(prefersDark);
 
      if (prefersDark) {
        document.documentElement.classList.remove("light");
        document.documentElement.classList.add("dark");
      } else {
        document.documentElement.classList.remove("dark");
        document.documentElement.classList.add("light");
      }
    }
  }, []);
 
  const toggleTheme = () => {
    setDarkMode((prev) => {
      const newDarkMode = !prev;
 
      // Handle class changes synchronously for testing
      if (newDarkMode) {
        // To dark mode
        document.documentElement.classList.remove("light");
        document.documentElement.classList.add("dark");
        localStorage.setItem("theme", "dark");
      } else {
        // To light mode - ensure we set light class properly
        document.documentElement.classList.remove("dark");
        document.documentElement.classList.add("light");
        localStorage.setItem("theme", "light");
      }
 
      return newDarkMode;
    });
  };
 
  return (
    <button
      data-testid={testId}
      onClick={toggleTheme}
      className="p-2 rounded-md text-gray-500 hover:text-gray-700 dark:text-gray-400 dark:hover:text-white focus:outline-none focus:ring-2 focus:ring-primary-500"
      aria-label={darkMode ? "Switch to light theme" : "Switch to dark theme"}
    >
      {darkMode ? (
        <svg className="w-5 h-5" fill="currentColor" viewBox="0 0 20 20">
          <path
            fillRule="evenodd"
            d="M10 2a1 1 0 011 1v1a1 1 0 11-2 0V3a1 1 0 011-1zm4 8a4 4 0 11-8 0 4 4 0 018 0zm-.464 4.95l.707.707a1 1 0 001.414-1.414l-.707-.707a1 1 0 00-1.414 1.414zm2.12-10.607a1 1 0 010 1.414l-.706.707a1 1 0 11-1.414-1.414l.707-.707a1 1 0 011.414 0zM17 11a1 1 0 100-2h-1a1 1 0 100 2h1zm-7 4a1 1 0 011 1v1a1 1 0 11-2 0v-1a1 1 0 011-1zM5.05 6.464A1 1 0 106.465 5.05l-.708-.707a1 1 0 00-1.414 1.414l.707.707zm1.414 8.486l-.707.707a1 1 0 01-1.414-1.414l.707-.707a1 1 0 011.414 1.414zM4 11a1 1 0 100-2H3a1 1 0 000 2h1z"
            clipRule="evenodd"
          />
        </svg>
      ) : (
        <svg className="w-5 h-5" fill="currentColor" viewBox="0 0 20 20">
          <path d="M17.293 13.293A8 8 0 016.707 2.707a8.001 8.001 0 1010.586 10.586z" />
        </svg>
      )}
    </button>
  );
};
 
export default ThemeToggle;