react-theme.js 2.2 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788
  1. import {
  2. createContext,
  3. useContext,
  4. useState,
  5. useEffect,
  6. createElement,
  7. useRef,
  8. useMemo,
  9. } from "react";
  10. import { isDevMode, lastCompiledTimeStamp } from "$/utils/context";
  11. const ThemeContext = createContext();
  12. export function ThemeProvider({ children, defaultTheme = "system" }) {
  13. const [theme, setTheme] = useState(defaultTheme);
  14. const [systemTheme, setSystemTheme] = useState(
  15. defaultTheme !== "system" ? defaultTheme : "light",
  16. );
  17. const firstRender = useRef(true);
  18. useEffect(() => {
  19. if (!firstRender.current) {
  20. return;
  21. }
  22. firstRender.current = false;
  23. if (isDevMode) {
  24. const lastCompiledTimeInLocalStorage =
  25. localStorage.getItem("last_compiled_time");
  26. if (lastCompiledTimeInLocalStorage !== lastCompiledTimeStamp) {
  27. // on app startup, make sure the application color mode is persisted correctly.
  28. localStorage.setItem("last_compiled_time", lastCompiledTimeStamp);
  29. return;
  30. }
  31. }
  32. // Load saved theme from localStorage
  33. const savedTheme = localStorage.getItem("theme") || defaultTheme;
  34. setTheme(savedTheme);
  35. });
  36. const resolvedTheme = useMemo(
  37. () => (theme === "system" ? systemTheme : theme),
  38. [theme, systemTheme],
  39. );
  40. useEffect(() => {
  41. // Set up media query for system preference detection
  42. const mediaQuery = window.matchMedia("(prefers-color-scheme: dark)");
  43. // Listen for system preference changes
  44. const handleChange = () => {
  45. setSystemTheme(mediaQuery.matches ? "dark" : "light");
  46. };
  47. handleChange();
  48. mediaQuery.addEventListener("change", handleChange);
  49. return () => {
  50. mediaQuery.removeEventListener("change", handleChange);
  51. };
  52. });
  53. // Save theme to localStorage whenever it changes
  54. useEffect(() => {
  55. localStorage.setItem("theme", theme);
  56. }, [theme]);
  57. useEffect(() => {
  58. const root = window.document.documentElement;
  59. root.classList.remove("light", "dark");
  60. root.classList.add(resolvedTheme);
  61. }, [resolvedTheme]);
  62. return createElement(
  63. ThemeContext.Provider,
  64. { value: { theme, resolvedTheme, setTheme } },
  65. children,
  66. );
  67. }
  68. export function useTheme() {
  69. return useContext(ThemeContext);
  70. }