All files / src/hooks use-theme.ts

73.07% Statements 38/52
69.23% Branches 9/13
60% Functions 3/5
73.07% Lines 38/52

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 751x         3x 3x 3x   1x 3x   3x 3x 3x   3x   3x 3x 3x     3x           3x 3x 3x     3x     3x 3x 3x     3x 3x 3x 3x 3x     3x   3x             3x         3x   3x   3x 3x 3x 3x 3x 3x 3x  
import {useEffect, useState} from "react";
 
// Type for available themes
export type ThemeName = "system" | "light" | "dark" | "cupcake" | "cyberpunk" | "corporate" | "forest" | "aqua";
 
function isDarkMode(): boolean {
    return matchMedia && matchMedia("(prefers-color-scheme: dark)").matches;
}
 
export function useTheme() {
    const [theme, setThemeState] = useState<ThemeName>(() => {
        // Check local storage or default to light theme
        const savedTheme = localStorage.getItem("app-theme") as ThemeName | null;
        return savedTheme || "system";
    });
 
    const [textSize, setTextSize] = useState<number>(() => {
        // Check local storage or default to 16
        const savedTextSize = localStorage.getItem("app-text-size");
        return savedTextSize ? Number(savedTextSize) : 16;
    });
 
    // Set theme and persist to local storage
    function setTheme(newTheme: ThemeName) {
        setThemeState(newTheme);
        localStorage.setItem("app-theme", newTheme);
    }
 
    // Update text size and persist to local storage
    function updateTextSize(size: number) {
        setTextSize(size);
        localStorage.setItem("app-text-size", size.toString());
 
        // Set the base font size on the html element
        document.documentElement.style.fontSize = `${size}px`;
 
        // Set a scaling factor for non-text elements
        const scaleFactor = size / 16;
        document.documentElement.style.setProperty("--scale-factor", scaleFactor.toString());
    }
 
    // Apply theme and initial text size
    useEffect(() => {
        let newTheme: ThemeName;
        if (theme === "system") {
            newTheme = isDarkMode() ? "dark" : "light";
        } else {
            newTheme = theme;
        }
        document.documentElement.setAttribute("data-theme", newTheme);
 
        function updateSystemTheme(event: MediaQueryListEvent) {
            if (theme === "system") {
                const newTheme = event.matches ? "dark" : "light";
                document.documentElement.setAttribute("data-theme", newTheme);
            }
        }
 
        if (matchMedia) {
            const darkModeMatchMedia = matchMedia("(prefers-color-scheme: dark)");
            darkModeMatchMedia.addEventListener("change", updateSystemTheme);
            return () => darkModeMatchMedia.removeEventListener("change", updateSystemTheme);
        }
    }, [theme]);
 
    useEffect(() => updateTextSize(textSize), [textSize]);
 
    return {
        theme,
        setTheme,
        textSize,
        setTextSize: updateTextSize
    };
}