summaryrefslogtreecommitdiffhomepage
path: root/packages/ui/src/theme
diff options
context:
space:
mode:
authorAdam <[email protected]>2025-12-28 19:26:46 -0600
committerAdam <[email protected]>2025-12-28 19:27:36 -0600
commit0156f03e0ef4e256236256aac05f4fc10577280f (patch)
tree36e3c2148ec30292d1bec0b689317f5879326c51 /packages/ui/src/theme
parente0bb96a9f9edf925c445607bc3e19742ba14af8c (diff)
downloadopencode-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.tsx293
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
-}
+ },
+})