diff --git a/src-tauri/src/config.rs b/src-tauri/src/config.rs index fa73cbe..6ca733e 100644 --- a/src-tauri/src/config.rs +++ b/src-tauri/src/config.rs @@ -92,6 +92,10 @@ pub struct HikariConfig { #[serde(default)] pub profile_bio: Option, + + // Custom theme colors + #[serde(default)] + pub custom_theme_colors: CustomThemeColors, } impl Default for HikariConfig { @@ -118,6 +122,7 @@ impl Default for HikariConfig { profile_name: None, profile_avatar_path: None, profile_bio: None, + custom_theme_colors: CustomThemeColors::default(), } } } @@ -150,6 +155,27 @@ pub enum Theme { Light, #[serde(rename = "high-contrast")] HighContrast, + Custom, +} + +#[derive(Debug, Clone, Serialize, Deserialize, Default, PartialEq)] +pub struct CustomThemeColors { + #[serde(default)] + pub bg_primary: Option, + #[serde(default)] + pub bg_secondary: Option, + #[serde(default)] + pub bg_terminal: Option, + #[serde(default)] + pub accent_primary: Option, + #[serde(default)] + pub accent_secondary: Option, + #[serde(default)] + pub text_primary: Option, + #[serde(default)] + pub text_secondary: Option, + #[serde(default)] + pub border_color: Option, } #[cfg(test)] @@ -178,6 +204,7 @@ mod tests { assert!(config.profile_name.is_none()); assert!(config.profile_avatar_path.is_none()); assert!(config.profile_bio.is_none()); + assert_eq!(config.custom_theme_colors, CustomThemeColors::default()); } #[test] @@ -204,6 +231,7 @@ mod tests { profile_name: Some("Test User".to_string()), profile_avatar_path: None, profile_bio: Some("A test bio".to_string()), + custom_theme_colors: CustomThemeColors::default(), }; let json = serde_json::to_string(&config).unwrap(); @@ -232,5 +260,8 @@ mod tests { serde_json::to_string(&high_contrast).unwrap(), "\"high-contrast\"" ); + + let custom = Theme::Custom; + assert_eq!(serde_json::to_string(&custom).unwrap(), "\"custom\""); } } diff --git a/src/lib/components/ConfigSidebar.svelte b/src/lib/components/ConfigSidebar.svelte index 3dd3673..4aea4e0 100644 --- a/src/lib/components/ConfigSidebar.svelte +++ b/src/lib/components/ConfigSidebar.svelte @@ -3,7 +3,9 @@ configStore, type HikariConfig, type Theme, + type CustomThemeColors, applyFontSize, + applyCustomThemeColors, MIN_FONT_SIZE, MAX_FONT_SIZE, DEFAULT_FONT_SIZE, @@ -33,8 +35,20 @@ profile_name: null, profile_avatar_path: null, profile_bio: null, + custom_theme_colors: { + bg_primary: null, + bg_secondary: null, + bg_terminal: null, + accent_primary: null, + accent_secondary: null, + text_primary: null, + text_secondary: null, + border_color: null, + }, }); + let showCustomThemeEditor = $state(false); + let isOpen = $state(false); let isSaving = $state(false); let saveError: string | null = $state(null); @@ -91,9 +105,33 @@ async function handleThemeChange(theme: Theme) { config.theme = theme; - await configStore.setTheme(theme); + showCustomThemeEditor = theme === "custom"; + await configStore.setTheme(theme, config.custom_theme_colors); } + function handleCustomColorChange(key: keyof CustomThemeColors, value: string) { + config.custom_theme_colors = { + ...config.custom_theme_colors, + [key]: value || null, + }; + // Live preview + if (config.theme === "custom") { + applyCustomThemeColors(config.custom_theme_colors); + } + } + + // Default dark theme colors for color picker defaults + const defaultDarkColors: Required> = { + bg_primary: "#1a1a2e", + bg_secondary: "#16213e", + bg_terminal: "#0f0f1a", + accent_primary: "#e94560", + accent_secondary: "#ff6b9d", + text_primary: "#ffffff", + text_secondary: "#a0a0a0", + border_color: "#2a2a4a", + }; + function toggleTool(tool: string) { if (config.auto_granted_tools.includes(tool)) { config.auto_granted_tools = config.auto_granted_tools.filter((t) => t !== tool); @@ -421,10 +459,10 @@
-
+
+
+ + {#if config.theme === "custom" || showCustomThemeEditor} +
+

Custom Theme Colors

+
+
+ +
+ handleCustomColorChange("bg_primary", e.currentTarget.value)} + class="color-picker" + /> + + {config.custom_theme_colors.bg_primary || defaultDarkColors.bg_primary} + +
+
+
+ +
+ handleCustomColorChange("bg_secondary", e.currentTarget.value)} + class="color-picker" + /> + + {config.custom_theme_colors.bg_secondary || defaultDarkColors.bg_secondary} + +
+
+
+ +
+ handleCustomColorChange("bg_terminal", e.currentTarget.value)} + class="color-picker" + /> + + {config.custom_theme_colors.bg_terminal || defaultDarkColors.bg_terminal} + +
+
+
+ +
+ handleCustomColorChange("border_color", e.currentTarget.value)} + class="color-picker" + /> + + {config.custom_theme_colors.border_color || defaultDarkColors.border_color} + +
+
+
+ +
+ handleCustomColorChange("accent_primary", e.currentTarget.value)} + class="color-picker" + /> + + {config.custom_theme_colors.accent_primary || defaultDarkColors.accent_primary} + +
+
+
+ +
+ handleCustomColorChange("accent_secondary", e.currentTarget.value)} + class="color-picker" + /> + + {config.custom_theme_colors.accent_secondary || defaultDarkColors.accent_secondary} + +
+
+
+ +
+ handleCustomColorChange("text_primary", e.currentTarget.value)} + class="color-picker" + /> + + {config.custom_theme_colors.text_primary || defaultDarkColors.text_primary} + +
+
+
+ +
+ handleCustomColorChange("text_secondary", e.currentTarget.value)} + class="color-picker" + /> + + {config.custom_theme_colors.text_secondary || defaultDarkColors.text_secondary} + +
+
+
+

+ Changes preview live. Click Save Settings to persist. +

+
+ {/if} +