A self-hosted orchestration system crafted for power users. composia.xyz
Something went wrong. Try again.
TypeScript
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178import { writable, type Unsubscriber } from "svelte/store";
import { availableLocales, defaultLocale, type Locale,} from "$lib/i18n/locales";
export const availableThemeModes = ["system", "light", "dark"] as const;export const availableAccentColors = [ "blue", "emerald", "violet", "rose", "amber",] as const;
export type ThemeMode = (typeof availableThemeModes)[number];export type AccentColor = (typeof availableAccentColors)[number];
const storageKeys = { locale: "composia.locale", themeMode: "composia.theme-mode", accentColor: "composia.accent-color",} as const;
export const accentMetadata: Record<AccentColor, { preview: string }> = { blue: { preview: "hsl(221 83% 53%)" }, emerald: { preview: "hsl(160 84% 39%)" }, violet: { preview: "hsl(262 83% 58%)" }, rose: { preview: "hsl(347 77% 50%)" }, amber: { preview: "hsl(38 92% 50%)" },};
function readStoredValue(key: string) { if (typeof window === "undefined") { return null; } return window.localStorage.getItem(key);}
function normalizeThemeMode(value: string | null | undefined): ThemeMode { if (value === "light" || value === "dark" || value === "system") { return value; } return "system";}
function normalizeAccentColor(value: string | null | undefined): AccentColor { if (value && availableAccentColors.includes(value as AccentColor)) { return value as AccentColor; } return "blue";}
function normalizeLocale(value: string | null | undefined): Locale { if (!value) return defaultLocale; const match = availableLocales.find( (locale) => locale.toLowerCase() === value.toLowerCase(), ); return match ?? defaultLocale;}
const initialThemeMode = normalizeThemeMode( typeof document === "undefined" ? "system" : document.documentElement.dataset.themeMode,);const initialAccentColor = normalizeAccentColor( typeof document === "undefined" ? "blue" : document.documentElement.dataset.accent,);const initialLocale = normalizeLocale( typeof document === "undefined" ? defaultLocale : document.documentElement.lang,);
export const themeMode = writable<ThemeMode>(initialThemeMode);export const accentColor = writable<AccentColor>(initialAccentColor);export const preferredLocale = writable<Locale>(initialLocale);
function resolveThemeMode(mode: ThemeMode) { if (mode === "system" && typeof window !== "undefined") { return window.matchMedia("(prefers-color-scheme: dark)").matches ? "dark" : "light"; } return mode;}
function applyThemeMode(mode: ThemeMode) { if (typeof document === "undefined") { return; } const root = document.documentElement; const resolved = resolveThemeMode(mode); root.dataset.themeMode = mode; root.classList.toggle("dark", resolved === "dark"); root.style.colorScheme = resolved; window.localStorage.setItem(storageKeys.themeMode, mode);}
function applyAccentColor(accent: AccentColor) { if (typeof document === "undefined") { return; } document.documentElement.dataset.accent = accent; window.localStorage.setItem(storageKeys.accentColor, accent);}
function applyLocale(locale: Locale, setLocale: (locale: Locale) => void) { if (typeof document === "undefined") { return; } document.documentElement.lang = locale; window.localStorage.setItem(storageKeys.locale, locale); document.cookie = `composia.locale=${encodeURIComponent(locale)}; Path=/; Max-Age=31536000; SameSite=Lax`; setLocale(locale);}
export function setThemeMode(nextMode: ThemeMode) { themeMode.set(nextMode);}
export function setAccentColor(nextAccent: AccentColor) { accentColor.set(nextAccent);}
export function setPreferredLocale(nextLocale: Locale) { preferredLocale.set(nextLocale);}
export function initializePreferences(setLocale: (locale: Locale) => void) { const storedThemeMode = normalizeThemeMode( readStoredValue(storageKeys.themeMode), ); const storedAccentColor = normalizeAccentColor( readStoredValue(storageKeys.accentColor), ); const storedLocale = normalizeLocale( readStoredValue(storageKeys.locale) ?? document.documentElement.lang, );
themeMode.set(storedThemeMode); accentColor.set(storedAccentColor); preferredLocale.set(storedLocale);
const unsubscribers: Unsubscriber[] = [ themeMode.subscribe((mode) => applyThemeMode(mode)), accentColor.subscribe((accent) => applyAccentColor(accent)), preferredLocale.subscribe((locale) => applyLocale(locale, setLocale)), ];
const media = typeof window === "undefined" ? null : window.matchMedia("(prefers-color-scheme: dark)"); const handleSystemThemeChange = () => { let currentMode: ThemeMode = "system"; const unsubscribe = themeMode.subscribe((mode) => { currentMode = mode; }); unsubscribe(); if (currentMode === "system") { applyThemeMode(currentMode); } };
media?.addEventListener("change", handleSystemThemeChange);
return () => { unsubscribers.forEach((unsubscribe) => unsubscribe()); media?.removeEventListener("change", handleSystemThemeChange); };}