theme redux

This commit is contained in:
2026-02-05 01:07:22 -05:00
parent 6950deaa88
commit ea9ab4d3f9
25 changed files with 1339 additions and 131 deletions

View File

@@ -1,67 +1,101 @@
import { createContext, useContext, createSignal, createEffect, onCleanup } from "solid-js"
import type { ThemeColors, ThemeName } from "../types/settings"
import { createContext, createEffect, createMemo, createSignal, useContext } from "solid-js"
import { createStore, produce } from "solid-js/store"
import { useRenderer } from "@opentui/solid"
import type { ThemeName } from "../types/settings"
import type { ThemeJson } from "../types/theme-schema"
import { useAppStore } from "../stores/app"
import { applyTheme, setThemeAttribute, getSystemThemeMode } from "../utils/theme"
import { THEME_JSON } from "../constants/themes"
import { resolveTheme } from "../utils/theme-resolver"
import { generateSyntax, generateSubtleSyntax } from "../utils/syntax-highlighter"
import { generateSystemTheme } from "../utils/system-theme"
import { getCustomThemes } from "../utils/custom-themes"
import { setThemeAttribute } from "../utils/theme"
import type { RGBA } from "@opentui/core"
type ThemeContextType = {
themeName: () => ThemeName
setThemeName: (theme: ThemeName) => void
resolvedTheme: () => ThemeColors
isSystemTheme: () => boolean
currentMode: () => "dark" | "light"
type ThemeContextValue = {
theme: Record<string, unknown>
selected: () => string
all: () => Record<string, ThemeJson>
syntax: () => unknown
subtleSyntax: () => unknown
mode: () => "dark" | "light"
setMode: (mode: "dark" | "light") => void
set: (theme: string) => void
ready: () => boolean
}
const ThemeContext = createContext<ThemeContextType>()
const ThemeContext = createContext<ThemeContextValue>()
export function ThemeProvider({ children }: { children: any }) {
const appStore = useAppStore()
const [themeName, setThemeName] = createSignal<ThemeName>(appStore.state().settings.theme)
const [resolvedTheme, setResolvedTheme] = createSignal<ThemeColors>(appStore.resolveTheme())
const [currentMode, setCurrentMode] = createSignal<"dark" | "light">(getSystemThemeMode())
const isSystemTheme = () => themeName() === "system"
// Update theme when appStore theme changes
createEffect(() => {
const currentTheme = appStore.state().settings.theme
setThemeName(currentTheme)
setResolvedTheme(appStore.resolveTheme())
// Apply theme to CSS variables
if (currentTheme === "system") {
const mode = getSystemThemeMode()
setCurrentMode(mode)
applyTheme(resolvedTheme())
} else {
setCurrentMode("dark") // All themes are dark by default
}
setThemeAttribute(currentTheme === "system" ? "system" : currentTheme)
const renderer = useRenderer()
const [ready, setReady] = createSignal(false)
const [store, setStore] = createStore({
themes: { ...THEME_JSON } as Record<string, ThemeJson>,
mode: "dark" as "dark" | "light",
active: appStore.state().settings.theme as ThemeName,
})
// Handle system theme changes
createEffect(() => {
if (isSystemTheme()) {
const mediaQuery = window.matchMedia("(prefers-color-scheme: dark)")
const handler = () => {
const newMode = getSystemThemeMode()
setCurrentMode(newMode)
setResolvedTheme(appStore.resolveTheme())
}
mediaQuery.addEventListener("change", handler)
onCleanup(() => {
mediaQuery.removeEventListener("change", handler)
const init = () => {
getCustomThemes()
.then((custom) => {
setStore(
produce((draft) => {
Object.assign(draft.themes, custom)
})
)
})
}
.finally(() => setReady(true))
}
init()
createEffect(() => {
setStore("active", appStore.state().settings.theme)
setThemeAttribute(appStore.state().settings.theme)
})
return (
<ThemeContext.Provider value={{ themeName, setThemeName, resolvedTheme, isSystemTheme, currentMode }}>
{children}
</ThemeContext.Provider>
createEffect(() => {
if (store.active !== "system") return
renderer
.getPalette({ size: 16 })
.then((colors) => {
setStore(
produce((draft) => {
draft.themes.system = generateSystemTheme(colors, store.mode)
})
)
})
.catch(() => {})
})
const values = createMemo(() => {
const theme = store.themes[store.active] ?? store.themes.opencode
return resolveTheme(theme, store.mode)
})
const syntax = createMemo(() => generateSyntax(values() as unknown as Record<string, RGBA>))
const subtleSyntax = createMemo(() =>
generateSubtleSyntax(values() as unknown as Record<string, RGBA> & { thinkingOpacity?: number })
)
const context: ThemeContextValue = {
theme: new Proxy(values(), {
get(_target, prop) {
return values()[prop as keyof typeof values]
},
}),
selected: () => store.active,
all: () => store.themes,
syntax,
subtleSyntax,
mode: () => store.mode,
setMode: (mode) => setStore("mode", mode),
set: (theme) => appStore.setTheme(theme as ThemeName),
ready,
}
return <ThemeContext.Provider value={context}>{children}</ThemeContext.Provider>
}
export function useTheme() {