theme redux
This commit is contained in:
8
src/context/ThemeContext.test.ts
Normal file
8
src/context/ThemeContext.test.ts
Normal file
@@ -0,0 +1,8 @@
|
||||
import { describe, expect, it } from "bun:test"
|
||||
import { ThemeProvider } from "./ThemeContext"
|
||||
|
||||
describe("ThemeContext", () => {
|
||||
it("exports provider", () => {
|
||||
expect(typeof ThemeProvider).toBe("function")
|
||||
})
|
||||
})
|
||||
@@ -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() {
|
||||
|
||||
Reference in New Issue
Block a user