|
|
|
|
@ -2,10 +2,11 @@ import defaultDarkThemeContent from "../themes/default-dark.css?raw";
|
|
|
|
|
import paperThemeContent from "../themes/paper.css?raw";
|
|
|
|
|
import whitewallThemeContent from "../themes/whitewall.css?raw";
|
|
|
|
|
|
|
|
|
|
const VALID_THEMES = ["default", "default-dark", "paper", "whitewall"] as const;
|
|
|
|
|
const VALID_THEMES = ["system", "default", "default-dark", "paper", "whitewall"] as const;
|
|
|
|
|
type ValidTheme = (typeof VALID_THEMES)[number];
|
|
|
|
|
|
|
|
|
|
const THEME_CONTENT: Record<ValidTheme, string | null> = {
|
|
|
|
|
system: null, // System theme dynamically chooses between default and default-dark
|
|
|
|
|
default: null,
|
|
|
|
|
"default-dark": defaultDarkThemeContent,
|
|
|
|
|
paper: paperThemeContent,
|
|
|
|
|
@ -18,8 +19,9 @@ export interface ThemeOption {
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export const THEME_OPTIONS: ThemeOption[] = [
|
|
|
|
|
{ value: "default", label: "Default Light" },
|
|
|
|
|
{ value: "default-dark", label: "Default Dark" },
|
|
|
|
|
{ value: "system", label: "Sync with system" },
|
|
|
|
|
{ value: "default", label: "Light" },
|
|
|
|
|
{ value: "default-dark", label: "Dark" },
|
|
|
|
|
{ value: "paper", label: "Paper" },
|
|
|
|
|
{ value: "whitewall", label: "Whitewall" },
|
|
|
|
|
];
|
|
|
|
|
@ -38,6 +40,18 @@ export const getSystemTheme = (): "default" | "default-dark" => {
|
|
|
|
|
return "default";
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Resolves the actual theme to apply based on user preference
|
|
|
|
|
* If theme is "system", returns the system preference, otherwise returns the theme as-is
|
|
|
|
|
*/
|
|
|
|
|
export const resolveTheme = (theme: string): "default" | "default-dark" | "paper" | "whitewall" => {
|
|
|
|
|
if (theme === "system") {
|
|
|
|
|
return getSystemTheme();
|
|
|
|
|
}
|
|
|
|
|
const validTheme = validateTheme(theme);
|
|
|
|
|
return validTheme === "system" ? getSystemTheme() : validTheme;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Gets the theme that should be applied on initial load
|
|
|
|
|
* Priority: stored user preference -> system preference -> default
|
|
|
|
|
@ -53,8 +67,8 @@ export const getInitialTheme = (): ValidTheme => {
|
|
|
|
|
// localStorage might not be available
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Fall back to system preference
|
|
|
|
|
return getSystemTheme();
|
|
|
|
|
// Fall back to system preference (return "system" to enable auto-switching)
|
|
|
|
|
return "system";
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
@ -68,12 +82,15 @@ export const applyThemeEarly = (): void => {
|
|
|
|
|
export const loadTheme = (themeName: string): void => {
|
|
|
|
|
const validTheme = validateTheme(themeName);
|
|
|
|
|
|
|
|
|
|
// Resolve "system" to actual theme based on OS preference
|
|
|
|
|
const resolvedTheme = resolveTheme(validTheme);
|
|
|
|
|
|
|
|
|
|
// Remove existing theme
|
|
|
|
|
document.getElementById("instance-theme")?.remove();
|
|
|
|
|
|
|
|
|
|
// Apply theme (skip for default)
|
|
|
|
|
if (validTheme !== "default") {
|
|
|
|
|
const css = THEME_CONTENT[validTheme];
|
|
|
|
|
if (resolvedTheme !== "default") {
|
|
|
|
|
const css = THEME_CONTENT[resolvedTheme];
|
|
|
|
|
if (css) {
|
|
|
|
|
const style = document.createElement("style");
|
|
|
|
|
style.id = "instance-theme";
|
|
|
|
|
@ -82,13 +99,44 @@ export const loadTheme = (themeName: string): void => {
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Set data attribute
|
|
|
|
|
document.documentElement.setAttribute("data-theme", validTheme);
|
|
|
|
|
// Set data attribute with resolved theme
|
|
|
|
|
document.documentElement.setAttribute("data-theme", resolvedTheme);
|
|
|
|
|
|
|
|
|
|
// Store theme preference for future loads
|
|
|
|
|
// Store theme preference (original, not resolved) for future loads
|
|
|
|
|
try {
|
|
|
|
|
localStorage.setItem("memos-theme", validTheme);
|
|
|
|
|
} catch {
|
|
|
|
|
// localStorage might not be available
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Sets up a listener for system theme preference changes
|
|
|
|
|
* Returns a cleanup function to remove the listener
|
|
|
|
|
*/
|
|
|
|
|
export const setupSystemThemeListener = (onThemeChange: () => void): (() => void) => {
|
|
|
|
|
if (typeof window === "undefined" || !window.matchMedia) {
|
|
|
|
|
return () => {}; // No-op cleanup
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const mediaQuery = window.matchMedia("(prefers-color-scheme: dark)");
|
|
|
|
|
|
|
|
|
|
// Handle theme change
|
|
|
|
|
const handleChange = () => {
|
|
|
|
|
onThemeChange();
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// Modern API (addEventListener)
|
|
|
|
|
if (mediaQuery.addEventListener) {
|
|
|
|
|
mediaQuery.addEventListener("change", handleChange);
|
|
|
|
|
return () => mediaQuery.removeEventListener("change", handleChange);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Legacy API (addListener) - for older browsers
|
|
|
|
|
if (mediaQuery.addListener) {
|
|
|
|
|
mediaQuery.addListener(handleChange);
|
|
|
|
|
return () => mediaQuery.removeListener(handleChange);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return () => {}; // No-op cleanup
|
|
|
|
|
};
|
|
|
|
|
|