2be3416baa
Add system theme detection with automatic switching when OS theme changes. - Add ThemeMode type with "light", "dark", and "system" options - Add system theme listener using matchMedia API - Update theme toggle to cycle through System → Light → Dark - Add combined sun/moon icon for system theme mode - Migrate existing theme preferences to new format
114 lines
3.2 KiB
TypeScript
114 lines
3.2 KiB
TypeScript
import { writable, derived } from "svelte/store";
|
|
import { persistentStore } from "./persistent";
|
|
import type { ScreenWidth } from "../lib/types";
|
|
|
|
export type ThemeMode = "light" | "dark" | "system";
|
|
|
|
function getInitialThemeMode(): ThemeMode {
|
|
if (typeof window !== "undefined") {
|
|
try {
|
|
const saved = localStorage.getItem("theme");
|
|
if (saved !== null) {
|
|
const oldTheme = JSON.parse(saved);
|
|
localStorage.removeItem("theme");
|
|
return oldTheme ? "dark" : "light";
|
|
}
|
|
} catch (e) {
|
|
console.error("Error parsing stored theme", e);
|
|
}
|
|
}
|
|
return "system";
|
|
}
|
|
|
|
// Persistent stores
|
|
export const themeMode = persistentStore<ThemeMode>("theme-mode", getInitialThemeMode());
|
|
export const appTitle = persistentStore<string>("app-title", "llama-swap");
|
|
|
|
const prefersDarkQuery = "(prefers-color-scheme: dark)";
|
|
|
|
function getSystemPrefersDark(): boolean {
|
|
return (
|
|
typeof window !== "undefined" &&
|
|
typeof window.matchMedia === "function" &&
|
|
window.matchMedia(prefersDarkQuery).matches
|
|
);
|
|
}
|
|
|
|
// Internal store for the raw OS dark preference
|
|
const systemPrefersDark = writable(getSystemPrefersDark());
|
|
|
|
// Derived store for actual dark mode state
|
|
export const isDarkMode = derived(
|
|
[themeMode, systemPrefersDark],
|
|
([$themeMode, $systemPrefersDark]) => {
|
|
if ($themeMode === "system") return $systemPrefersDark;
|
|
return $themeMode === "dark";
|
|
}
|
|
);
|
|
|
|
// Non-persistent stores
|
|
export const screenWidth = writable<ScreenWidth>("md");
|
|
export const connectionState = writable<"connected" | "connecting" | "disconnected">("disconnected");
|
|
|
|
// Derived store for narrow screens
|
|
export const isNarrow = derived(screenWidth, ($screenWidth) => {
|
|
return $screenWidth === "xs" || $screenWidth === "sm" || $screenWidth === "md";
|
|
});
|
|
|
|
// Function to toggle theme
|
|
export function toggleTheme(): void {
|
|
themeMode.update((current) => {
|
|
if (current === "system") return "light";
|
|
if (current === "light") return "dark";
|
|
return "system";
|
|
});
|
|
}
|
|
|
|
|
|
|
|
// Function to check and update screen width
|
|
export function checkScreenWidth(): void {
|
|
const innerWidth = window.innerWidth;
|
|
let newWidth: ScreenWidth;
|
|
|
|
if (innerWidth < 640) {
|
|
newWidth = "xs";
|
|
} else if (innerWidth < 768) {
|
|
newWidth = "sm";
|
|
} else if (innerWidth < 1024) {
|
|
newWidth = "md";
|
|
} else if (innerWidth < 1280) {
|
|
newWidth = "lg";
|
|
} else if (innerWidth < 1536) {
|
|
newWidth = "xl";
|
|
} else {
|
|
newWidth = "2xl";
|
|
}
|
|
|
|
screenWidth.set(newWidth);
|
|
}
|
|
|
|
// Initialize screen width and set up resize listener
|
|
export function initScreenWidth(): () => void {
|
|
checkScreenWidth();
|
|
window.addEventListener("resize", checkScreenWidth);
|
|
|
|
return () => {
|
|
window.removeEventListener("resize", checkScreenWidth);
|
|
};
|
|
}
|
|
|
|
// Initialize system theme listener
|
|
export function initSystemThemeListener(): () => void {
|
|
if (typeof window === "undefined" || typeof window.matchMedia !== "function") return () => {};
|
|
|
|
const mediaQuery = window.matchMedia(prefersDarkQuery);
|
|
systemPrefersDark.set(mediaQuery.matches);
|
|
const handleChange = (e: MediaQueryListEvent) => {
|
|
systemPrefersDark.set(e.matches);
|
|
};
|
|
|
|
mediaQuery.addEventListener("change", handleChange);
|
|
return () => mediaQuery.removeEventListener("change", handleChange);
|
|
}
|