diff --git a/src/app/components/MenuSettings.svelte b/src/app/components/MenuSettings.svelte index 3469263b..caec223f 100644 --- a/src/app/components/MenuSettings.svelte +++ b/src/app/components/MenuSettings.svelte @@ -15,10 +15,8 @@ import LogOut from "@app/components/LogOut.svelte" import {user} from "@app/core" import {pushModal} from "@app/modal" - import {theme} from "@app/theme" const logout = () => pushModal(LogOut) - const toggleTheme = () => theme.set($theme === "dark" ? "light" : "dark") @@ -74,7 +72,7 @@
- + Theme / About / diff --git a/src/app/components/RelaySummary.svelte b/src/app/components/RelaySummary.svelte index a468896b..292d4521 100644 --- a/src/app/components/RelaySummary.svelte +++ b/src/app/components/RelaySummary.svelte @@ -30,7 +30,8 @@
{#if $userSpaceUrls.includes(url)} -
+
{/if} diff --git a/src/app/theme.ts b/src/app/theme.ts index 22efb459..a1bd5b1d 100644 --- a/src/app/theme.ts +++ b/src/app/theme.ts @@ -1,6 +1,8 @@ import twColors from "tailwindcss/colors" -import {kv} from "@app/storage" +import {derived, readable} from "svelte/store" import {synced} from "@welshman/store" +import {FL_THEME} from "@app/env" +import {kv} from "@app/storage" export const colors = [ ["amber", twColors.amber[600]], @@ -24,15 +26,35 @@ export const colors = [ ["zinc", twColors.zinc[600]], ] -export const theme = synced({ - key: "theme", - defaultValue: window.matchMedia("(prefers-color-scheme: dark)").matches ? "dark" : "light", +// Every theme with token values in lib/components/theme.css +export const flThemes = ["clay", "flat", "navy"] + +export const flTheme = synced({ + key: "flTheme", + defaultValue: FL_THEME, storage: kv, }) -// Hook for development -Object.assign(window, { - setFlTheme: (t: string) => { - document.body.setAttribute("data-fl-theme", t) - }, +export const theme = synced({ + key: "theme", + defaultValue: "system", + storage: kv, +}) + +const prefersDark = readable(window.matchMedia("(prefers-color-scheme: dark)").matches, set => { + const query = window.matchMedia("(prefers-color-scheme: dark)") + const onChange = () => set(query.matches) + + query.addEventListener("change", onChange) + + return () => query.removeEventListener("change", onChange) +}) + +// What actually gets stamped on the document, since `theme` may defer to the os +export const activeTheme = derived([theme, prefersDark], ([$theme, $prefersDark]) => { + if ($theme === "system") { + return $prefersDark ? "dark" : "light" + } + + return $theme }) diff --git a/src/routes/+layout.svelte b/src/routes/+layout.svelte index bc8b7f55..dece46dc 100644 --- a/src/routes/+layout.svelte +++ b/src/routes/+layout.svelte @@ -31,7 +31,7 @@ import {setupShareIntents, shareFromNative} from "@app/share" import {shouldUnwrap, syncApplicationData} from "@app/sync" import * as env from "@app/env" - import {theme} from "@app/theme" + import {activeTheme, flTheme, theme} from "@app/theme" import {toast, pushToast} from "@app/toast" import * as notifications from "@app/notifications" import {notifications as notificationPaths, allNotifications} from "@app/notifications" @@ -44,9 +44,17 @@ const {children} = $props() - // Do this asap to avoid a font size flash + // Do this asap to avoid a flash of the wrong font size or theme. The stores these mirror live in + // indexeddb, which doesn't load until well after first paint. + const prefersDark = window.matchMedia("(prefers-color-scheme: dark)").matches + // @ts-ignore document.documentElement.style["font-size"] = `${localStorage.getItem("font-size") || 1.1}rem` + document.body.setAttribute("data-fl-theme", localStorage.getItem("fl-theme") || env.FL_THEME) + document.body.setAttribute( + "data-theme", + localStorage.getItem("theme") || (prefersDark ? "dark" : "light"), + ) // Add stuff to window for convenience Object.assign(window, {get, nip19, theme, app, domain, Logger, ...lib, ...util}) @@ -231,9 +239,13 @@ // Sync theme and font size unsubscribers.push( - theme.subscribe($theme => { - document.body.setAttribute("data-theme", $theme) - document.body.setAttribute("data-fl-theme", env.FL_THEME) + activeTheme.subscribe($activeTheme => { + localStorage.setItem("theme", $activeTheme) + document.body.setAttribute("data-theme", $activeTheme) + }), + flTheme.subscribe($flTheme => { + localStorage.setItem("fl-theme", $flTheme) + document.body.setAttribute("data-fl-theme", $flTheme) }), userSettingsValues.subscribe( debounce(100, $settings => { @@ -273,7 +285,7 @@ {#await unsubscribe} {:then} -
+
{@render children()} diff --git a/src/routes/settings/+layout.svelte b/src/routes/settings/+layout.svelte index cda6f761..731425de 100644 --- a/src/routes/settings/+layout.svelte +++ b/src/routes/settings/+layout.svelte @@ -19,7 +19,6 @@ import LogOut from "@app/components/LogOut.svelte" import {HOSTING_ENABLED} from "@app/hosting" import {pushModal} from "@app/modal" - import {theme} from "@app/theme" type Props = { children?: Snippet @@ -28,8 +27,6 @@ const {children}: Props = $props() const logout = () => pushModal(LogOut) - - const toggleTheme = () => theme.set($theme === "dark" ? "light" : "dark") @@ -62,7 +59,7 @@ Privacy - + Theme diff --git a/src/routes/settings/content/+page.svelte b/src/routes/settings/content/+page.svelte index 70dd8897..42bd9712 100644 --- a/src/routes/settings/content/+page.svelte +++ b/src/routes/settings/content/+page.svelte @@ -87,26 +87,6 @@ {/snippet} - - Accessibility - - {#snippet label()} -

Font size

- {/snippet} - {#snippet secondary()} -

{Math.round(settings.font_size * 100)}%

- {/snippet} - {#snippet input()} - - {/snippet} -
-
Editor Settings diff --git a/src/routes/settings/theme/+page.svelte b/src/routes/settings/theme/+page.svelte new file mode 100644 index 00000000..85e889af --- /dev/null +++ b/src/routes/settings/theme/+page.svelte @@ -0,0 +1,93 @@ + + +{#snippet colorScheme(value: string, label: string, icon: string)} + +{/snippet} + + + + + + Theme Settings + + + {#snippet label()} +

Color scheme

+ {/snippet} + {#snippet input()} +
+ {@render colorScheme("system", "System", Monitor)} + {@render colorScheme("light", "Light", Sun)} + {@render colorScheme("dark", "Dark", Moon)} +
+ {/snippet} + {#snippet info()} +

System follows whatever your device is set to.

+ {/snippet} +
+ + {#snippet label()} +

App theme

+ {/snippet} + {#snippet input()} + + {/snippet} + {#snippet info()} +

Changes the shape and texture of everything, not just its colors.

+ {/snippet} +
+
+ + + + Accessibility + + + {#snippet label()} +

Font size

+ {/snippet} + {#snippet secondary()} +

{Math.round(fontSize * 100)}%

+ {/snippet} + {#snippet input()} + + {/snippet} +
+
+