Skip to main content
Glama
theme_tools.ts4.54 kB
/* This is set of tools for dealing with dark/light mode theming. It includes: - a single root theme setting based on the system preferences, or a user override persisted to localstorage - other components can override this theme, either with a static value, or with their own theme setting logic - individual components can get their current theme, which (using provide/inject) will find the theme value, whether coming from the room or a parent overriding - utilities which help apply sets of css classes depending on whether dark or light mode is active for a component All of this is needed so that we can override dark/light mode in specific regions of the page separately from the root which is not possible with tailwind `dark:XXX` style classes -- they only respect the root "dark" class */ import { computed, InjectionKey, provide, inject, Ref, ref, watch, isRef, } from "vue"; import storage from "local-storage-fallback"; export type ThemeValue = "dark" | "light"; const THEME_STORAGE_KEY = "SI:THEME"; const THEME_INJECTION_KEY: InjectionKey<Ref<ThemeValue>> = Symbol("THEME"); // NOTE - some issues with window here when running SSG. Tried a few things but try/catch finally worked... // track the system theme - based off of `prefers-color-scheme` function getSystemTheme(): ThemeValue { try { return window.matchMedia("(prefers-color-scheme: dark)").matches ? "dark" : "light"; } catch (err) { return "dark"; } } export const systemTheme = ref(getSystemTheme()); try { window .matchMedia("(prefers-color-scheme: dark)") .addEventListener("change", () => { systemTheme.value = getSystemTheme(); }); } catch (err) { // dont need to do anything... } // single user-selected theme (overriding the system theme) saved to localstorage // we export the user-set theme directly, but we only need to use it for theme switcher components // as most components will get current value via inject in `useTheme()` export const userOverrideTheme = ref<ThemeValue | null>(null); if (!import.meta.env.SSR) { userOverrideTheme.value = (storage.getItem(THEME_STORAGE_KEY) as ThemeValue) || null; } // watcher to update the user theme in local-storage when it changes watch( () => userOverrideTheme.value, (newTheme) => { if (newTheme) { storage.setItem(THEME_STORAGE_KEY, newTheme); } else { storage.removeItem(THEME_STORAGE_KEY); } }, ); // computed which returns the active theme at the root, regardless if set by user or system const rootActiveTheme = computed( () => userOverrideTheme.value || systemTheme.value, ); // makes a component provide theme value to all children, takes an arg which can be used in a few ways: // - undefined -- provides the "root theme" value, useful for the root component or to _unset_ override from a parent // - ThemeValue -- useful to override in a particular section // - Ref<ThemeValue> -- useful if a component has it's own theme selection logic / picker // could add a mode which flips the current theme...? export function useThemeContainer(themeValue?: ThemeValue | Ref<ThemeValue>) { let providedTheme: Ref<ThemeValue> = rootActiveTheme; if (themeValue) { if (isRef(themeValue)) providedTheme = themeValue; else providedTheme = computed(() => themeValue); } provide(THEME_INJECTION_KEY, providedTheme); // TODO - we could try to see if this is the root component and call useHead to inject body class... // but useHead does not support merging for bodyAttrs yet - https://github.com/vueuse/head/issues/55 // so we have to do this in App.vue anyway const themeContainerClasses = computed( () => `color-scheme-${providedTheme.value}`, ); // currently just used for scrollbars // we'll also return the theme being provided in case the component needs it (like to inject into head) return { theme: providedTheme, themeContainerClasses }; } // used by components who just want to use the current theme // which will usually be the root, but could be a parent that is overriding that value export function useTheme() { const theme = inject( THEME_INJECTION_KEY, // dont think this default is ever actually used, but helps TS not complain rootActiveTheme, ); return { theme }; } // class utility to help apply specific classes based on current theme export function themeClasses(lightClasses: string, darkClasses: string) { const { theme } = useTheme(); return theme.value === "light" ? lightClasses : darkClasses; }

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/systeminit/si'

If you have feedback or need assistance with the MCP directory API, please join our Discord server