All files / components/dashboard/theme-toggle theme-toggle.tsx

90.47% Statements 38/42
70.83% Branches 17/24
91.66% Functions 11/12
92.1% Lines 35/38

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 948x             8x 8x 8x     265x       128x 128x 76x   76x       137x 137x   128x 128x   76x       1x       21x   21x               21x 21x 21x   21x 21x 21x 21x         23x 23x       1x 1x 1x         47x     47x 22x     47x   47x     1x                  
"use client";
 
import {Moon, Sun} from "lucide-react";
import {useEffect, useSyncExternalStore} from "react";
import {Button} from "@/components/ui/button/button";
 
type Theme = "light" | "dark";
const themeDatasetKey = "dashboardTheme";
const themeStorageKey = "dashboard-theme";
const themeChangeEvent = "dashboard-theme-change";
 
function isTheme(value: string | undefined | null): value is Theme {
  return value === "light" || value === "dark";
}
 
function getDomTheme(): Theme | null {
  const datasetTheme = document.documentElement.dataset[themeDatasetKey];
  if (isTheme(datasetTheme)) return datasetTheme;
  Iif (document.documentElement.classList.contains("dark")) return "dark";
 
  return null;
}
 
function getBrowserTheme(): Theme {
  const stored = window.localStorage.getItem(themeStorageKey);
  if (isTheme(stored)) return stored;
 
  const domTheme = getDomTheme();
  if (domTheme) return domTheme;
 
  return window.matchMedia("(prefers-color-scheme: dark)").matches ? "dark" : "light";
}
 
function getServerTheme(): Theme {
  return "light";
}
 
function subscribeToThemeChanges(onStoreChange: () => void) {
  const colorSchemeQuery = window.matchMedia("(prefers-color-scheme: dark)");
 
  const synchronizeSystemTheme = () => {
    if (!isTheme(window.localStorage.getItem(themeStorageKey))) {
      // Update the DOM-backed snapshot before notifying React.
      applyTheme(colorSchemeQuery.matches ? "dark" : "light");
    }
    onStoreChange();
  };
 
  window.addEventListener("storage", onStoreChange);
  window.addEventListener(themeChangeEvent, onStoreChange);
  colorSchemeQuery.addEventListener("change", synchronizeSystemTheme);
 
  return () => {
    window.removeEventListener("storage", onStoreChange);
    window.removeEventListener(themeChangeEvent, onStoreChange);
    colorSchemeQuery.removeEventListener("change", synchronizeSystemTheme);
  };
}
 
function applyTheme(theme: Theme) {
  document.documentElement.classList.toggle("dark", theme === "dark");
  document.documentElement.dataset[themeDatasetKey] = theme;
}
 
function persistTheme(theme: Theme) {
  applyTheme(theme);
  window.localStorage.setItem(themeStorageKey, theme);
  window.dispatchEvent(new Event(themeChangeEvent));
}
 
/** Persisted light/dark theme toggle used in the dashboard header. */
export function ThemeToggle() {
  const theme = useSyncExternalStore(subscribeToThemeChanges, getBrowserTheme, getServerTheme);
 
  // The layout script handles first paint; this keeps later browser changes in sync.
  useEffect(() => {
    applyTheme(getBrowserTheme());
  }, [theme]);
 
  const isDark = theme === "dark";
 
  return (
      <Button
          aria-label={isDark ? "Use light theme" : "Use dark theme"}
          onClick={() => persistTheme(isDark ? "light" : "dark")}
          size="icon"
          title={isDark ? "Use light theme" : "Use dark theme"}
          variant="secondary"
      >
        {isDark ? <Sun aria-hidden="true" size={17}/> : <Moon aria-hidden="true" size={17}/>}
      </Button>
  );
}