diff options
| author | Adam <[email protected]> | 2025-12-28 19:26:46 -0600 |
|---|---|---|
| committer | Adam <[email protected]> | 2025-12-28 19:27:36 -0600 |
| commit | 0156f03e0ef4e256236256aac05f4fc10577280f (patch) | |
| tree | 36e3c2148ec30292d1bec0b689317f5879326c51 /packages/ui/src/theme | |
| parent | e0bb96a9f9edf925c445607bc3e19742ba14af8c (diff) | |
| download | opencode-0156f03e0ef4e256236256aac05f4fc10577280f.tar.gz opencode-0156f03e0ef4e256236256aac05f4fc10577280f.zip | |
chore: cleanup theme stuff
Diffstat (limited to 'packages/ui/src/theme')
| -rw-r--r-- | packages/ui/src/theme/context.tsx | 293 |
1 files changed, 114 insertions, 179 deletions
diff --git a/packages/ui/src/theme/context.tsx b/packages/ui/src/theme/context.tsx index 210773f0c..c1c1637d6 100644 --- a/packages/ui/src/theme/context.tsx +++ b/packages/ui/src/theme/context.tsx @@ -1,52 +1,24 @@ -import { - createContext, - useContext, - createSignal, - onMount, - onCleanup, - createEffect, - type JSX, - type Accessor, -} from "solid-js" +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" -interface ThemeContextValue { - themeId: Accessor<string> - colorScheme: Accessor<ColorScheme> - mode: Accessor<"light" | "dark"> - themes: Accessor<Record<string, DesktopTheme>> - setTheme: (id: string) => void - setColorScheme: (scheme: ColorScheme) => void - registerTheme: (theme: DesktopTheme) => void - previewTheme: (id: string) => void - previewColorScheme: (scheme: ColorScheme) => void - commitPreview: () => void - cancelPreview: () => void -} - -const ThemeContext = createContext<ThemeContextValue>() - const STORAGE_KEYS = { THEME_ID: "opencode-theme-id", COLOR_SCHEME: "opencode-color-scheme", - THEME_CSS_PREFIX: "opencode-theme-css", + THEME_CSS_LIGHT: "opencode-theme-css-light", + THEME_CSS_DARK: "opencode-theme-css-dark", } as const -function getThemeCacheKey(themeId: string, mode: "light" | "dark"): string { - return `${STORAGE_KEYS.THEME_CSS_PREFIX}-${themeId}-${mode}` -} - const THEME_STYLE_ID = "oc-theme" function ensureThemeStyleElement(): HTMLStyleElement { const existing = document.getElementById(THEME_STYLE_ID) as HTMLStyleElement | null - if (existing) { - return existing - } + if (existing) return existing const element = document.createElement("style") element.id = THEME_STYLE_ID document.head.appendChild(element) @@ -57,16 +29,15 @@ function getSystemMode(): "light" | "dark" { return window.matchMedia("(prefers-color-scheme: dark)").matches ? "dark" : "light" } -function applyThemeCss(theme: DesktopTheme, themeId: string, mode: "light" | "dark"): void { +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") { - const cacheKey = getThemeCacheKey(themeId, mode) try { - localStorage.setItem(cacheKey, css) + localStorage.setItem(isDark ? STORAGE_KEYS.THEME_CSS_DARK : STORAGE_KEYS.THEME_CSS_LIGHT, css) } catch {} } @@ -76,170 +47,134 @@ function applyThemeCss(theme: DesktopTheme, themeId: string, mode: "light" | "da ${css} }` - const preloadStyle = document.getElementById("oc-theme-preload") - if (preloadStyle) { - preloadStyle.remove() - } - - const themeStyleElement = ensureThemeStyleElement() - themeStyleElement.textContent = fullCss - + 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): void { +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) - const cacheKey = getThemeCacheKey(themeId, mode) try { - localStorage.setItem(cacheKey, css) + localStorage.setItem(isDark ? STORAGE_KEYS.THEME_CSS_DARK : STORAGE_KEYS.THEME_CSS_LIGHT, css) } catch {} } } -export function ThemeProvider(props: { children: JSX.Element; defaultTheme?: string }) { - const [themes, setThemes] = createSignal<Record<string, DesktopTheme>>(DEFAULT_THEMES) - const [themeId, setThemeIdSignal] = createSignal(props.defaultTheme ?? "oc-1") - const [colorScheme, setColorSchemeSignal] = createSignal<ColorScheme>("system") - const [mode, setMode] = createSignal<"light" | "dark">(getSystemMode()) - const [previewThemeId, setPreviewThemeId] = createSignal<string | null>(null) - const [previewScheme, setPreviewScheme] = createSignal<ColorScheme | null>(null) - - onMount(() => { - const mediaQuery = window.matchMedia("(prefers-color-scheme: dark)") - const handler = () => { - if (colorScheme() === "system") { - setMode(getSystemMode()) +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)) + 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 && themes()[savedTheme]) { - setThemeIdSignal(savedTheme) - } - if (savedScheme) { - setColorSchemeSignal(savedScheme) - if (savedScheme !== "system") { - setMode(savedScheme) + 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) } - } - const currentTheme = themes()[themeId()] - if (currentTheme) { - cacheThemeVariants(currentTheme, themeId()) - } - }) - - createEffect(() => { - const id = themeId() - const m = mode() - const theme = themes()[id] - if (theme) { - applyThemeCss(theme, id, m) - } - }) - - const setTheme = (id: string) => { - const theme = themes()[id] - if (!theme) { - console.warn(`Theme "${id}" not found`) - return - } - setThemeIdSignal(id) - localStorage.setItem(STORAGE_KEYS.THEME_ID, id) - cacheThemeVariants(theme, id) - } - - const setColorSchemePref = (scheme: ColorScheme) => { - setColorSchemeSignal(scheme) - localStorage.setItem(STORAGE_KEYS.COLOR_SCHEME, scheme) - if (scheme === "system") { - setMode(getSystemMode()) - } else { - setMode(scheme) - } - } - - const registerTheme = (theme: DesktopTheme) => { - setThemes((prev) => ({ - ...prev, - [theme.id]: theme, - })) - } + if (savedScheme) { + setStore("colorScheme", savedScheme) + if (savedScheme !== "system") { + setStore("mode", savedScheme) + } + } + const currentTheme = store.themes[store.themeId] + if (currentTheme) { + cacheThemeVariants(currentTheme, store.themeId) + } + }) - const previewTheme = (id: string) => { - const theme = themes()[id] - if (!theme) return - setPreviewThemeId(id) - const previewMode = previewScheme() ? (previewScheme() === "system" ? getSystemMode() : previewScheme()!) : mode() - applyThemeCss(theme, id, previewMode as "light" | "dark") - } + createEffect(() => { + const theme = store.themes[store.themeId] + if (theme) { + applyThemeCss(theme, store.themeId, store.mode) + } + }) - const previewColorScheme = (scheme: ColorScheme) => { - setPreviewScheme(scheme) - const previewMode = scheme === "system" ? getSystemMode() : scheme - const id = previewThemeId() ?? themeId() - const theme = themes()[id] - if (theme) { - applyThemeCss(theme, id, previewMode) + 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 commitPreview = () => { - const id = previewThemeId() - const scheme = previewScheme() - if (id) { - setTheme(id) - } - if (scheme) { - setColorSchemePref(scheme) + const setColorScheme = (scheme: ColorScheme) => { + setStore("colorScheme", scheme) + localStorage.setItem(STORAGE_KEYS.COLOR_SCHEME, scheme) + setStore("mode", scheme === "system" ? getSystemMode() : scheme) } - setPreviewThemeId(null) - setPreviewScheme(null) - } - const cancelPreview = () => { - setPreviewThemeId(null) - setPreviewScheme(null) - const theme = themes()[themeId()] - if (theme) { - applyThemeCss(theme, themeId(), mode()) + 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) + } + }, } - } - - return ( - <ThemeContext.Provider - value={{ - themeId, - colorScheme, - mode, - themes, - setTheme, - setColorScheme: setColorSchemePref, - registerTheme, - previewTheme, - previewColorScheme, - commitPreview, - cancelPreview, - }} - > - {props.children} - </ThemeContext.Provider> - ) -} - -export function useTheme(): ThemeContextValue { - const ctx = useContext(ThemeContext) - if (!ctx) { - throw new Error("useTheme must be used within a ThemeProvider") - } - return ctx -} + }, +}) |
