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 | |
| 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.
| -rw-r--r-- | packages/frontend/src/lib/components/SettingsPanel.svelte | 52 | ||||
| -rw-r--r-- | packages/frontend/src/lib/components/ThemeSwitcher.svelte | 58 |
2 files changed, 52 insertions, 58 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> diff --git a/packages/frontend/src/lib/components/ThemeSwitcher.svelte b/packages/frontend/src/lib/components/ThemeSwitcher.svelte deleted file mode 100644 index 418fcea..0000000 --- a/packages/frontend/src/lib/components/ThemeSwitcher.svelte +++ /dev/null @@ -1,58 +0,0 @@ -<script lang="ts"> -const THEMES = [ - "light", - "dark", - "dracula", - "night", - "nord", - "sunset", - "cyberpunk", - "forest", - "cmyk", - "coffee", - "caramellatte", - "garden", - "luxury", -] as const; - -const STORAGE_KEY = "dispatch-theme"; - -const { onclose }: { onclose: () => void } = $props(); - -let currentTheme = $state( - (typeof localStorage !== "undefined" && localStorage.getItem(STORAGE_KEY)) || "dark", -); - -let dialogEl: HTMLDialogElement | undefined = $state(); - -$effect(() => { - if (dialogEl && !dialogEl.open) dialogEl.showModal(); -}); - -function selectTheme(theme: string) { - currentTheme = theme; - document.documentElement.setAttribute("data-theme", theme); - localStorage.setItem(STORAGE_KEY, theme); - onclose(); -} -</script> - -<dialog class="modal" bind:this={dialogEl} oncancel={onclose}> - <div class="modal-box w-56"> - <h3 class="text-sm font-semibold mb-3">Select Theme</h3> - <ul class="menu menu-sm"> - {#each THEMES as theme} - <li> - <button - type="button" - class="capitalize {currentTheme === theme ? 'menu-active' : ''}" - onclick={() => selectTheme(theme)} - > - {theme} - </button> - </li> - {/each} - </ul> - </div> - <form method="dialog" class="modal-backdrop"><button>close</button></form> -</dialog> |
