| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180 |
- 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: "opencode-theme-id",
- COLOR_SCHEME: "opencode-color-scheme",
- THEME_CSS_LIGHT: "opencode-theme-css-light",
- THEME_CSS_DARK: "opencode-theme-css-dark",
- } as const
- const THEME_STYLE_ID = "oc-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 !== "oc-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("oc-theme-preload")?.remove()
- ensureThemeStyleElement().textContent = fullCss
- document.documentElement.dataset.theme = themeId
- document.documentElement.dataset.colorScheme = mode
- }
- function cacheThemeVariants(theme: DesktopTheme, themeId: string) {
- if (themeId === "oc-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 ?? "oc-1",
- 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)
- }
- },
- }
- },
- })
|