feat: dedicated dark theme option with system preference support (#5007)
* implement OS level theme switching and dark mode option * simplify * fix logo bug in login | place back useTheme comment --------- Co-authored-by: shatfield4 <seanhatfield5@gmail.com> Co-authored-by: Timothy Carambat <rambat1010@gmail.com>
This commit is contained in:
parent
a961fb8f75
commit
e025df9b87
@ -6,18 +6,21 @@ import DefaultLoginLogoDark from "./media/illustrations/login-logo-light.svg";
|
|||||||
import System from "./models/system";
|
import System from "./models/system";
|
||||||
|
|
||||||
export const REFETCH_LOGO_EVENT = "refetch-logo";
|
export const REFETCH_LOGO_EVENT = "refetch-logo";
|
||||||
|
|
||||||
|
function isLightMode() {
|
||||||
|
return document.documentElement.getAttribute("data-theme") === "light";
|
||||||
|
}
|
||||||
export const LogoContext = createContext();
|
export const LogoContext = createContext();
|
||||||
|
|
||||||
export function LogoProvider({ children }) {
|
export function LogoProvider({ children }) {
|
||||||
const [logo, setLogo] = useState("");
|
const [logo, setLogo] = useState("");
|
||||||
const [loginLogo, setLoginLogo] = useState("");
|
const [loginLogo, setLoginLogo] = useState("");
|
||||||
const [isCustomLogo, setIsCustomLogo] = useState(false);
|
const [isCustomLogo, setIsCustomLogo] = useState(false);
|
||||||
const DefaultLoginLogo =
|
|
||||||
localStorage.getItem("theme") !== "default"
|
|
||||||
? DefaultLoginLogoDark
|
|
||||||
: DefaultLoginLogoLight;
|
|
||||||
|
|
||||||
async function fetchInstanceLogo() {
|
async function fetchInstanceLogo() {
|
||||||
|
const DefaultLoginLogo = isLightMode()
|
||||||
|
? DefaultLoginLogoDark
|
||||||
|
: DefaultLoginLogoLight;
|
||||||
try {
|
try {
|
||||||
const { isCustomLogo, logoURL } = await System.fetchLogo();
|
const { isCustomLogo, logoURL } = await System.fetchLogo();
|
||||||
if (logoURL) {
|
if (logoURL) {
|
||||||
@ -25,16 +28,12 @@ export function LogoProvider({ children }) {
|
|||||||
setLoginLogo(isCustomLogo ? logoURL : DefaultLoginLogo);
|
setLoginLogo(isCustomLogo ? logoURL : DefaultLoginLogo);
|
||||||
setIsCustomLogo(isCustomLogo);
|
setIsCustomLogo(isCustomLogo);
|
||||||
} else {
|
} else {
|
||||||
localStorage.getItem("theme") !== "default"
|
isLightMode() ? setLogo(AnythingLLMDark) : setLogo(AnythingLLM);
|
||||||
? setLogo(AnythingLLMDark)
|
|
||||||
: setLogo(AnythingLLM);
|
|
||||||
setLoginLogo(DefaultLoginLogo);
|
setLoginLogo(DefaultLoginLogo);
|
||||||
setIsCustomLogo(false);
|
setIsCustomLogo(false);
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
localStorage.getItem("theme") !== "default"
|
isLightMode() ? setLogo(AnythingLLMDark) : setLogo(AnythingLLM);
|
||||||
? setLogo(AnythingLLMDark)
|
|
||||||
: setLogo(AnythingLLM);
|
|
||||||
setLoginLogo(DefaultLoginLogo);
|
setLoginLogo(DefaultLoginLogo);
|
||||||
setIsCustomLogo(false);
|
setIsCustomLogo(false);
|
||||||
console.error("Failed to fetch logo:", err);
|
console.error("Failed to fetch logo:", err);
|
||||||
|
|||||||
@ -2,33 +2,46 @@ import { REFETCH_LOGO_EVENT } from "@/LogoContext";
|
|||||||
import { useState, useEffect } from "react";
|
import { useState, useEffect } from "react";
|
||||||
|
|
||||||
const availableThemes = {
|
const availableThemes = {
|
||||||
default: "Default",
|
system: "System",
|
||||||
light: "Light",
|
light: "Light",
|
||||||
|
dark: "Dark",
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Determines the current theme of the application
|
* Determines the current theme of the application.
|
||||||
* @returns {{theme: ('default' | 'light'), setTheme: function, availableThemes: object}} The current theme, a function to set the theme, and the available themes
|
* "system" follows the OS preference, "light" and "dark" force that mode.
|
||||||
|
* @returns {{theme: ('system' | 'light' | 'dark'), setTheme: function, availableThemes: object}}
|
||||||
*/
|
*/
|
||||||
export function useTheme() {
|
export function useTheme() {
|
||||||
const [theme, _setTheme] = useState(() => {
|
const [theme, _setTheme] = useState(() => {
|
||||||
return localStorage.getItem("theme") || "default";
|
const stored = localStorage.getItem("theme");
|
||||||
|
if (stored === "default") return "dark"; // migrate legacy value
|
||||||
|
return stored || "system";
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const [systemTheme, setSystemTheme] = useState(() =>
|
||||||
|
window.matchMedia?.("(prefers-color-scheme: light)").matches
|
||||||
|
? "light"
|
||||||
|
: "dark"
|
||||||
|
);
|
||||||
|
|
||||||
|
// Listen for OS level theme changes
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (localStorage.getItem("theme") !== null) return;
|
|
||||||
if (!window.matchMedia) return;
|
if (!window.matchMedia) return;
|
||||||
if (window.matchMedia("(prefers-color-scheme: light)").matches)
|
const mql = window.matchMedia("(prefers-color-scheme: light)");
|
||||||
return _setTheme("light");
|
const handler = (e) => setSystemTheme(e.matches ? "light" : "dark");
|
||||||
_setTheme("default");
|
mql.addEventListener("change", handler);
|
||||||
|
return () => mql.removeEventListener("change", handler);
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
const resolvedTheme = theme === "system" ? systemTheme : theme;
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
document.documentElement.setAttribute("data-theme", theme);
|
document.documentElement.setAttribute("data-theme", resolvedTheme);
|
||||||
document.body.classList.toggle("light", theme === "light");
|
document.body.classList.toggle("light", resolvedTheme === "light");
|
||||||
localStorage.setItem("theme", theme);
|
localStorage.setItem("theme", theme);
|
||||||
window.dispatchEvent(new Event(REFETCH_LOGO_EVENT));
|
window.dispatchEvent(new Event(REFETCH_LOGO_EVENT));
|
||||||
}, [theme]);
|
}, [resolvedTheme, theme]);
|
||||||
|
|
||||||
// In development, attach keybind combinations to toggle theme
|
// In development, attach keybind combinations to toggle theme
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@ -36,7 +49,7 @@ export function useTheme() {
|
|||||||
function toggleOnKeybind(e) {
|
function toggleOnKeybind(e) {
|
||||||
if (e.metaKey && e.key === ".") {
|
if (e.metaKey && e.key === ".") {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
setTheme((prev) => (prev === "light" ? "default" : "light"));
|
_setTheme((prev) => (prev === "light" ? "dark" : "light"));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
document.addEventListener("keydown", toggleOnKeybind);
|
document.addEventListener("keydown", toggleOnKeybind);
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user