diff options
| author | Adam Malczewski <[email protected]> | 2026-06-01 09:15:43 +0900 |
|---|---|---|
| committer | Adam Malczewski <[email protected]> | 2026-06-01 09:15:43 +0900 |
| commit | 751e411b3ab321129083f86f0be53687185abd87 (patch) | |
| tree | cddbb5b9bfc1b8bf86d64a870fdccb39a98419d3 /packages/frontend/src/lib/components/SettingsPanel.svelte | |
| parent | dd3c71e3d5c8c1b9b23bcf3fdbc34dc306a80570 (diff) | |
| download | dispatch-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.svelte | 52 |
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> |
