diff --git a/src/components/PreferencesPanel.tsx b/src/components/PreferencesPanel.tsx index 509a2ea..bff2914 100644 --- a/src/components/PreferencesPanel.tsx +++ b/src/components/PreferencesPanel.tsx @@ -1,6 +1,7 @@ import { createSignal } from "solid-js" import { useKeyboard } from "@opentui/solid" import { useAppStore } from "../stores/app" +import { createColorResolver } from "../utils/color" import type { ThemeName } from "../types/settings" type FocusField = "theme" | "font" | "speed" | "explicit" | "auto" @@ -21,6 +22,8 @@ export function PreferencesPanel() { const settings = () => appStore.state().settings const preferences = () => appStore.state().preferences + const resolveColor = createColorResolver(appStore.resolveTheme()) + const handleKey = (key: { name: string; shift?: boolean }) => { if (key.name === "tab") { const fields: FocusField[] = ["theme", "font", "speed", "explicit", "auto"] @@ -76,55 +79,55 @@ export function PreferencesPanel() { return ( - Preferences + Preferences - Theme: + Theme: - {THEME_LABELS.find((t) => t.value === settings().theme)?.label} + {THEME_LABELS.find((t) => t.value === settings().theme)?.label} - [Left/Right] + [Left/Right] - Font Size: + Font Size: - {settings().fontSize}px + {settings().fontSize}px - [Left/Right] + [Left/Right] - Playback: + Playback: - {settings().playbackSpeed}x + {settings().playbackSpeed}x - [Left/Right] + [Left/Right] - Show Explicit: + Show Explicit: - + {preferences().showExplicit ? "On" : "Off"} - [Space] + [Space] - Auto Download: + Auto Download: - + {preferences().autoDownload ? "On" : "Off"} - [Space] + [Space] - Tab to move focus, Left/Right to adjust + Tab to move focus, Left/Right to adjust ) } diff --git a/src/components/SourceManager.tsx b/src/components/SourceManager.tsx index 903c75a..7b1831a 100644 --- a/src/components/SourceManager.tsx +++ b/src/components/SourceManager.tsx @@ -6,6 +6,7 @@ import { createSignal, For } from "solid-js" import { useFeedStore } from "../stores/feed" import { SourceType } from "../types/source" +import { createColorResolver } from "../utils/color" import type { PodcastSource } from "../types/source" interface SourceManagerProps { @@ -148,6 +149,8 @@ export function SourceManager(props: SourceManagerProps) { const sourceExplicit = () => selectedSource()?.allowExplicit !== false const sourceLanguage = () => selectedSource()?.language || "en_us" + const resolveColor = createColorResolver({}) + return ( @@ -155,15 +158,15 @@ export function SourceManager(props: SourceManagerProps) { Podcast Sources - [Esc] Close + [Esc] Close - Manage where to search for podcasts + Manage where to search for podcasts {/* Source list */} - Sources: + Sources: {(source, index) => ( @@ -173,7 +176,7 @@ export function SourceManager(props: SourceManagerProps) { padding={0} backgroundColor={ focusArea() === "list" && index() === selectedIndex() - ? "var(--color-primary)" + ? resolveColor("--color-primary") : undefined } onMouseDown={() => { @@ -184,21 +187,21 @@ export function SourceManager(props: SourceManagerProps) { > {focusArea() === "list" && index() === selectedIndex() ? ">" : " "} - + {source.enabled ? "[x]" : "[ ]"} - {getSourceIcon(source)} + {getSourceIcon(source)} @@ -208,7 +211,7 @@ export function SourceManager(props: SourceManagerProps) { )} - Space/Enter to toggle, d to delete, a to add + Space/Enter to toggle, d to delete, a to add {/* API settings */} diff --git a/src/context/ThemeContext.tsx b/src/context/ThemeContext.tsx index 5b2484a..42b73e6 100644 --- a/src/context/ThemeContext.tsx +++ b/src/context/ThemeContext.tsx @@ -42,18 +42,21 @@ export function ThemeProvider({ children }: { children: any }) { // 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()) + // Check if window and matchMedia are available + if (typeof window !== "undefined" && typeof window.matchMedia === "function") { + 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) + }) } - - mediaQuery.addEventListener("change", handler) - - onCleanup(() => { - mediaQuery.removeEventListener("change", handler) - }) } }) diff --git a/src/utils/color.ts b/src/utils/color.ts new file mode 100644 index 0000000..91e921c --- /dev/null +++ b/src/utils/color.ts @@ -0,0 +1,93 @@ +/** + * Color Resolution Utility + * Converts CSS variable references to actual color values + */ + +/** + * Map of CSS variable names to their default color values + * Used as fallback when CSS variables aren't resolved + */ +const CSS_VARIABLE_MAP: Record = { + "--color-background": "transparent", + "--color-surface": "#1b1f27", + "--color-primary": "#6fa8ff", + "--color-secondary": "#a9b1d6", + "--color-accent": "#f6c177", + "--color-text": "#e6edf3", + "--color-muted": "#7d8590", + "--color-warning": "#f0b429", + "--color-error": "#f47067", + "--color-success": "#3fb950", + "--color-layer0": "transparent", + "--color-layer1": "#1e222e", + "--color-layer2": "#161b22", + "--color-layer3": "#0d1117", +} + +/** + * Resolves a CSS variable reference to an actual color value + * @param variable The CSS variable string (e.g., "var(--color-primary)") + * @returns The resolved color value or a default fallback + */ +export function resolveCSSVariable(variable: string): string { + if (!variable || typeof variable !== "string") { + return "#ff00ff" // Default magenta fallback + } + + // Extract the variable name from var(--name) + const match = variable.match(/var\(([^)]+)\)/) + if (match && match[1]) { + const varName = match[1].trim() + // Get the computed style value from the document + if (typeof document !== "undefined") { + const root = document.documentElement + const computedValue = getComputedStyle(root).getPropertyValue(varName) + if (computedValue) { + return computedValue.trim() + } + } + // Fall back to the map + return CSS_VARIABLE_MAP[varName] || "#ff00ff" + } + + // Return the original value if it's not a CSS variable + return variable +} + +/** + * Creates a function that resolves CSS variables from a theme object + * @param theme The theme object with color properties + * @returns A function that resolves CSS variable strings to actual colors + */ +export function createColorResolver(theme: { + background?: string + surface?: string + primary?: string + secondary?: string + accent?: string + text?: string + muted?: string + warning?: string + error?: string + success?: string + layerBackgrounds?: { + layer0?: string + layer1?: string + layer2?: string + layer3?: string + } +}) { + return (color: string | undefined): string => { + if (!color) { + return "#ff00ff" // Default magenta fallback + } + + // If it's already a valid hex or named color, return it + if (/^#([0-9A-Fa-f]{3}|[0-9A-Fa-f]{6})$/.test(color) || /^[a-z]+$/i.test(color)) { + return color + } + + // Otherwise, try to resolve it as a CSS variable + return resolveCSSVariable(color) + } +}