summaryrefslogtreecommitdiffhomepage
path: root/packages/frontend/src/lib/components/SettingsPanel.svelte
diff options
context:
space:
mode:
authorAdam Malczewski <[email protected]>2026-06-01 09:15:43 +0900
committerAdam Malczewski <[email protected]>2026-06-01 09:15:43 +0900
commit751e411b3ab321129083f86f0be53687185abd87 (patch)
treecddbb5b9bfc1b8bf86d64a870fdccb39a98419d3 /packages/frontend/src/lib/components/SettingsPanel.svelte
parentdd3c71e3d5c8c1b9b23bcf3fdbc34dc306a80570 (diff)
downloaddispatch-751e411b3ab321129083f86f0be53687185abd87.tar.gz
dispatch-751e411b3ab321129083f86f0be53687185abd87.zip
feat(settings): inline theme picker into Settings panel
The Theme button + ThemeSwitcher modal were a header-triggered modal. That doesn't belong in a sidebar-panel architecture, and theme picking is a UI preference that belongs alongside the other Settings entries. - Add a "Theme" section as the first block in SettingsPanel with the same theme list as ThemeSwitcher. - The localStorage key (`dispatch-theme`) and apply-on-change behavior are unchanged, so the boot-time theme apply in App.svelte's onMount keeps working without modification. - Delete the now-unused ThemeSwitcher.svelte component; no remaining importers.
Diffstat (limited to 'packages/frontend/src/lib/components/SettingsPanel.svelte')
-rw-r--r--packages/frontend/src/lib/components/SettingsPanel.svelte52
1 files changed, 52 insertions, 0 deletions
diff --git a/packages/frontend/src/lib/components/SettingsPanel.svelte b/packages/frontend/src/lib/components/SettingsPanel.svelte
index 392852a..efbaf5f 100644
--- a/packages/frontend/src/lib/components/SettingsPanel.svelte
+++ b/packages/frontend/src/lib/components/SettingsPanel.svelte
@@ -11,6 +11,42 @@ const {
apiBase?: string;
} = $props();
+// Theme picker — was a header-triggered modal (`ThemeSwitcher.svelte`);
+// inlined here so theme picking lives in Settings alongside other UI
+// preferences. The list and localStorage key must stay in sync with the
+// boot-time theme apply in `App.svelte`'s `onMount`.
+const THEMES = [
+ "light",
+ "dark",
+ "dracula",
+ "night",
+ "nord",
+ "sunset",
+ "cyberpunk",
+ "forest",
+ "cmyk",
+ "coffee",
+ "caramellatte",
+ "garden",
+ "luxury",
+] as const;
+
+const THEME_STORAGE_KEY = "dispatch-theme";
+
+let currentTheme = $state(
+ (typeof localStorage !== "undefined" && localStorage.getItem(THEME_STORAGE_KEY)) || "dark",
+);
+
+function selectTheme(theme: string): void {
+ currentTheme = theme;
+ document.documentElement.setAttribute("data-theme", theme);
+ try {
+ localStorage.setItem(THEME_STORAGE_KEY, theme);
+ } catch {
+ // Best-effort — private mode / quota.
+ }
+}
+
let titleKeyId = $state<string | null>(null);
let titleModelId = $state<string | null>(null);
let availableModels = $state<string[]>([]);
@@ -136,6 +172,22 @@ $effect(() => {
<div class="text-xs font-semibold text-base-content/50 uppercase tracking-wide">Settings</div>
<div class="flex flex-col gap-2">
+ <p class="text-xs text-base-content/70">Theme</p>
+ <label class="text-xs text-base-content/60">
+ Appearance
+ <select
+ class="select select-bordered select-sm w-full capitalize"
+ value={currentTheme}
+ onchange={(e) => selectTheme(e.currentTarget.value)}
+ >
+ {#each THEMES as theme (theme)}
+ <option value={theme} class="capitalize">{theme}</option>
+ {/each}
+ </select>
+ </label>
+
+ <div class="divider my-0"></div>
+
<p class="text-xs text-base-content/70">Title Generation Model</p>
<p class="text-xs text-base-content/40">Used to generate short titles for new tabs after the first message.</p>