feat(v3): add 7 editor themes to multi-theme system

Generalize theme system from Catppuccin-only (4 flavors) to 11 themes
across 2 groups. New editor themes: VSCode Dark+, Atom One Dark,
Monokai, Dracula, Nord, Solarized Dark, GitHub Dark.

All themes map to the same 26 --ctp-* CSS custom properties, so every
component works unchanged. ThemeId replaces CatppuccinFlavor as primary
type. Theme store uses getCurrentTheme()/setTheme() with deprecated
wrappers. SettingsTab uses optgroup-based theme selector.
This commit is contained in:
Hibryda 2026-03-07 22:07:14 +01:00
parent 1ba818e7a5
commit ff2d354219
3 changed files with 248 additions and 149 deletions

View file

@ -1,19 +1,21 @@
// Theme store — persists Catppuccin flavor selection via settings bridge
// Theme store — persists theme selection via settings bridge
import { getSetting, setSetting } from '../adapters/settings-bridge';
import {
type ThemeId,
type CatppuccinFlavor,
ALL_THEME_IDS,
buildXtermTheme,
applyCssVariables,
type XtermTheme,
} from '../styles/themes';
let currentFlavor = $state<CatppuccinFlavor>('mocha');
let currentTheme = $state<ThemeId>('mocha');
/** Registered theme-change listeners */
const themeChangeCallbacks = new Set<() => void>();
/** Register a callback invoked after every flavor change. Returns an unsubscribe function. */
/** Register a callback invoked after every theme change. Returns an unsubscribe function. */
export function onThemeChange(callback: () => void): () => void {
themeChangeCallbacks.add(callback);
return () => {
@ -21,18 +23,25 @@ export function onThemeChange(callback: () => void): () => void {
};
}
export function getCurrentTheme(): ThemeId {
return currentTheme;
}
/** @deprecated Use getCurrentTheme() */
export function getCurrentFlavor(): CatppuccinFlavor {
return currentFlavor;
// Return valid CatppuccinFlavor or default to 'mocha'
const catFlavors: string[] = ['latte', 'frappe', 'macchiato', 'mocha'];
return catFlavors.includes(currentTheme) ? currentTheme as CatppuccinFlavor : 'mocha';
}
export function getXtermTheme(): XtermTheme {
return buildXtermTheme(currentFlavor);
return buildXtermTheme(currentTheme);
}
/** Change flavor, apply CSS variables, and persist to settings DB */
export async function setFlavor(flavor: CatppuccinFlavor): Promise<void> {
currentFlavor = flavor;
applyCssVariables(flavor);
/** Change theme, apply CSS variables, and persist to settings DB */
export async function setTheme(theme: ThemeId): Promise<void> {
currentTheme = theme;
applyCssVariables(theme);
// Notify all listeners (e.g. open xterm.js terminals)
for (const cb of themeChangeCallbacks) {
try {
@ -43,25 +52,30 @@ export async function setFlavor(flavor: CatppuccinFlavor): Promise<void> {
}
try {
await setSetting('theme', flavor);
await setSetting('theme', theme);
} catch (e) {
console.error('Failed to persist theme setting:', e);
}
}
/** Load saved flavor from settings DB and apply. Call once on app startup. */
/** @deprecated Use setTheme() */
export async function setFlavor(flavor: CatppuccinFlavor): Promise<void> {
return setTheme(flavor);
}
/** Load saved theme from settings DB and apply. Call once on app startup. */
export async function initTheme(): Promise<void> {
try {
const saved = await getSetting('theme');
if (saved && ['latte', 'frappe', 'macchiato', 'mocha'].includes(saved)) {
currentFlavor = saved as CatppuccinFlavor;
if (saved && ALL_THEME_IDS.includes(saved as ThemeId)) {
currentTheme = saved as ThemeId;
}
} catch {
// Fall back to default (mocha) — catppuccin.css provides Mocha defaults
}
// Always apply to sync CSS vars with current flavor
// Always apply to sync CSS vars with current theme
// (skip if mocha — catppuccin.css already has Mocha values)
if (currentFlavor !== 'mocha') {
applyCssVariables(currentFlavor);
if (currentTheme !== 'mocha') {
applyCssVariables(currentTheme);
}
}