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

@ -14,8 +14,8 @@
} from '../../stores/workspace.svelte';
import { deriveIdentifier } from '../../types/groups';
import { getSetting, setSetting } from '../../adapters/settings-bridge';
import { getCurrentFlavor, setFlavor } from '../../stores/theme.svelte';
import type { CatppuccinFlavor } from '../../styles/themes';
import { getCurrentTheme, setTheme } from '../../stores/theme.svelte';
import { THEME_LIST, type ThemeId } from '../../styles/themes';
let activeGroupId = $derived(getActiveGroupId());
let activeGroup = $derived(getActiveGroup());
@ -29,8 +29,17 @@
// Global settings
let defaultShell = $state('');
let defaultCwd = $state('');
let themeFlavor = $state<CatppuccinFlavor>(getCurrentFlavor());
const flavors: CatppuccinFlavor[] = ['latte', 'frappe', 'macchiato', 'mocha'];
let selectedTheme = $state<ThemeId>(getCurrentTheme());
// Group themes by category for <optgroup>
const themeGroups = $derived(() => {
const map = new Map<string, typeof THEME_LIST>();
for (const t of THEME_LIST) {
if (!map.has(t.group)) map.set(t.group, []);
map.get(t.group)!.push(t);
}
return [...map.entries()];
});
onMount(async () => {
const [shell, cwd] = await Promise.all([
@ -39,7 +48,7 @@
]);
defaultShell = shell ?? '';
defaultCwd = cwd ?? '';
themeFlavor = getCurrentFlavor();
selectedTheme = getCurrentTheme();
});
async function saveGlobalSetting(key: string, value: string) {
@ -50,9 +59,9 @@
}
}
async function handleThemeChange(flavor: CatppuccinFlavor) {
themeFlavor = flavor;
await setFlavor(flavor);
async function handleThemeChange(themeId: ThemeId) {
selectedTheme = themeId;
await setTheme(themeId);
}
// New project form
@ -92,14 +101,18 @@
<h2>Global</h2>
<div class="global-settings">
<div class="setting-row">
<label for="theme-flavor">Theme</label>
<label for="theme-select">Theme</label>
<select
id="theme-flavor"
value={themeFlavor}
onchange={e => handleThemeChange((e.target as HTMLSelectElement).value as CatppuccinFlavor)}
id="theme-select"
value={selectedTheme}
onchange={e => handleThemeChange((e.target as HTMLSelectElement).value as ThemeId)}
>
{#each flavors as f}
<option value={f}>{f.charAt(0).toUpperCase() + f.slice(1)}</option>
{#each themeGroups() as [groupName, themes]}
<optgroup label={groupName}>
{#each themes as t}
<option value={t.id}>{t.label}</option>
{/each}
</optgroup>
{/each}
</select>
</div>
@ -154,14 +167,14 @@
{#each activeGroup.projects as project}
<div class="project-settings-row">
<label class="project-field">
<label class="project-field project-field-grow">
<span class="field-label">Name</span>
<input
value={project.name}
onchange={e => updateProject(activeGroupId, project.id, { name: (e.target as HTMLInputElement).value })}
/>
</label>
<label class="project-field">
<label class="project-field project-field-grow">
<span class="field-label">CWD</span>
<input
value={project.cwd}
@ -237,6 +250,7 @@
padding: 6px 10px;
background: var(--ctp-surface0);
border-radius: 4px;
min-width: 0;
}
.setting-row label {
@ -255,12 +269,23 @@
color: var(--ctp-text);
font-size: 0.8rem;
flex: 1;
min-width: 0;
}
.setting-row select {
cursor: pointer;
}
.setting-row select optgroup {
font-weight: 600;
color: var(--ctp-subtext0);
}
.setting-row select option {
font-weight: normal;
color: var(--ctp-text);
}
.group-list {
display: flex;
flex-direction: column;
@ -306,12 +331,18 @@
border-radius: 4px;
margin-bottom: 4px;
flex-wrap: wrap;
min-width: 0;
}
.project-field {
display: flex;
flex-direction: column;
gap: 2px;
min-width: 0;
}
.project-field-grow {
flex: 1;
}
.field-label {
@ -327,6 +358,8 @@
border-radius: 3px;
color: var(--ctp-text);
font-size: 0.8rem;
min-width: 0;
width: 100%;
}
.add-form {
@ -334,6 +367,7 @@
gap: 8px;
align-items: center;
margin-top: 8px;
min-width: 0;
}
.add-form input {
@ -344,6 +378,7 @@
color: var(--ctp-text);
font-size: 0.8rem;
flex: 1;
min-width: 0;
}
.btn-primary {
@ -370,6 +405,7 @@
border-radius: 3px;
font-size: 0.75rem;
cursor: pointer;
white-space: nowrap;
}
.limit-notice {