180 lines · 5.9 KB · TypeScript
RawDownload
import { onMount, onCleanup, createEffect } from "solid-js"import { createStore } from "solid-js/store"import type { DesktopTheme } from "./types"import { resolveThemeVariant, themeToCss } from "./resolve"import { DEFAULT_THEMES } from "./default-themes"import { createSimpleContext } from "../context/helper" export type ColorScheme = "light" | "dark" | "system" const STORAGE_KEYS = {  THEME_ID: "openscience-theme-id",  COLOR_SCHEME: "openscience-color-scheme",  THEME_CSS_LIGHT: "openscience-theme-css-light",  THEME_CSS_DARK: "openscience-theme-css-dark",} as const const THEME_STYLE_ID = "openscience-theme" function ensureThemeStyleElement(): HTMLStyleElement {  const existing = document.getElementById(THEME_STYLE_ID) as HTMLStyleElement | null  if (existing) return existing  const element = document.createElement("style")  element.id = THEME_STYLE_ID  document.head.appendChild(element)  return element} function getSystemMode(): "light" | "dark" {  return window.matchMedia("(prefers-color-scheme: dark)").matches ? "dark" : "light"} function applyThemeCss(theme: DesktopTheme, themeId: string, mode: "light" | "dark") {  const isDark = mode === "dark"  const variant = isDark ? theme.dark : theme.light  const tokens = resolveThemeVariant(variant, isDark)  const css = themeToCss(tokens)   if (themeId !== "openscience-1") {    try {      localStorage.setItem(isDark ? STORAGE_KEYS.THEME_CSS_DARK : STORAGE_KEYS.THEME_CSS_LIGHT, css)    } catch {}  }   const fullCss = `:root {  color-scheme: ${mode};  --text-mix-blend-mode: ${isDark ? "plus-lighter" : "multiply"};  ${css}}`   document.getElementById("openscience-theme-preload")?.remove()  ensureThemeStyleElement().textContent = fullCss  document.documentElement.dataset.theme = themeId  document.documentElement.dataset.colorScheme = mode} function cacheThemeVariants(theme: DesktopTheme, themeId: string) {  if (themeId === "openscience-1") return  for (const mode of ["light", "dark"] as const) {    const isDark = mode === "dark"    const variant = isDark ? theme.dark : theme.light    const tokens = resolveThemeVariant(variant, isDark)    const css = themeToCss(tokens)    try {      localStorage.setItem(isDark ? STORAGE_KEYS.THEME_CSS_DARK : STORAGE_KEYS.THEME_CSS_LIGHT, css)    } catch {}  }} export const { use: useTheme, provider: ThemeProvider } = createSimpleContext({  name: "Theme",  init: (props: { defaultTheme?: string }) => {    const [store, setStore] = createStore({      themes: DEFAULT_THEMES as Record<string, DesktopTheme>,      themeId: props.defaultTheme ?? "openscience",      colorScheme: "system" as ColorScheme,      mode: getSystemMode(),      previewThemeId: null as string | null,      previewScheme: null as ColorScheme | null,    })     onMount(() => {      const mediaQuery = window.matchMedia("(prefers-color-scheme: dark)")      const handler = () => {        if (store.colorScheme === "system") {          setStore("mode", getSystemMode())        }      }      mediaQuery.addEventListener("change", handler)      onCleanup(() => mediaQuery.removeEventListener("change", handler))       const savedTheme = localStorage.getItem(STORAGE_KEYS.THEME_ID)      const savedScheme = localStorage.getItem(STORAGE_KEYS.COLOR_SCHEME) as ColorScheme | null      if (savedTheme && store.themes[savedTheme]) {        setStore("themeId", savedTheme)      }      if (savedScheme) {        setStore("colorScheme", savedScheme)        if (savedScheme !== "system") {          setStore("mode", savedScheme)        }      }      const currentTheme = store.themes[store.themeId]      if (currentTheme) {        cacheThemeVariants(currentTheme, store.themeId)      }    })     createEffect(() => {      const theme = store.themes[store.themeId]      if (theme) {        applyThemeCss(theme, store.themeId, store.mode)      }    })     const setTheme = (id: string) => {      const theme = store.themes[id]      if (!theme) {        console.warn(`Theme "${id}" not found`)        return      }      setStore("themeId", id)      localStorage.setItem(STORAGE_KEYS.THEME_ID, id)      cacheThemeVariants(theme, id)    }     const setColorScheme = (scheme: ColorScheme) => {      setStore("colorScheme", scheme)      localStorage.setItem(STORAGE_KEYS.COLOR_SCHEME, scheme)      setStore("mode", scheme === "system" ? getSystemMode() : scheme)    }     return {      themeId: () => store.themeId,      colorScheme: () => store.colorScheme,      mode: () => store.mode,      themes: () => store.themes,      setTheme,      setColorScheme,      registerTheme: (theme: DesktopTheme) => setStore("themes", theme.id, theme),      previewTheme: (id: string) => {        const theme = store.themes[id]        if (!theme) return        setStore("previewThemeId", id)        const previewMode = store.previewScheme          ? store.previewScheme === "system"            ? getSystemMode()            : store.previewScheme          : store.mode        applyThemeCss(theme, id, previewMode)      },      previewColorScheme: (scheme: ColorScheme) => {        setStore("previewScheme", scheme)        const previewMode = scheme === "system" ? getSystemMode() : scheme        const id = store.previewThemeId ?? store.themeId        const theme = store.themes[id]        if (theme) {          applyThemeCss(theme, id, previewMode)        }      },      commitPreview: () => {        if (store.previewThemeId) {          setTheme(store.previewThemeId)        }        if (store.previewScheme) {          setColorScheme(store.previewScheme)        }        setStore("previewThemeId", null)        setStore("previewScheme", null)      },      cancelPreview: () => {        setStore("previewThemeId", null)        setStore("previewScheme", null)        const theme = store.themes[store.themeId]        if (theme) {          applyThemeCss(theme, store.themeId, store.mode)        }      },    }  },})

Keyboard shortcuts

General

Show keyboard shortcuts
?
Focus the current search
/

Issues and pull requests

Open filters
f

Issues, pull requests and Inbox

Select next item
j↓
Select previous item
k↑
Open selected item
Enter
Dismiss filters or clear selection
Esc

Repository code

Go to file
t