victor HF Staff commited on
Commit
008f64c
·
1 Parent(s): eb6a961

Add theme subscription and update theme toggle logic

Browse files
src/lib/components/NavMenu.svelte CHANGED
@@ -13,8 +13,9 @@
13
  import Logo from "$lib/components/icons/Logo.svelte";
14
  import IconSun from "$lib/components/icons/IconSun.svelte";
15
  import IconMoon from "$lib/components/icons/IconMoon.svelte";
16
- import { switchTheme } from "$lib/switchTheme";
17
  import { isAborted } from "$lib/stores/isAborted";
 
18
 
19
  import NavConversationItem from "./NavConversationItem.svelte";
20
  import type { LayoutData } from "../../routes/$types";
@@ -100,7 +101,18 @@
100
  }
101
  });
102
 
103
- let theme = $state(browser ? localStorage.theme : "light");
 
 
 
 
 
 
 
 
 
 
 
104
  </script>
105
 
106
  <div
@@ -188,13 +200,12 @@
188
  <button
189
  onclick={() => {
190
  switchTheme();
191
- theme = localStorage.theme;
192
  }}
193
  aria-label="Toggle theme"
194
  class="flex size-9 min-w-[1.5em] flex-none items-center justify-center rounded-lg p-2 text-gray-500 hover:bg-gray-100 dark:text-gray-400 dark:hover:bg-gray-700"
195
  >
196
  {#if browser}
197
- {#if theme === "dark"}
198
  <IconSun />
199
  {:else}
200
  <IconMoon />
 
13
  import Logo from "$lib/components/icons/Logo.svelte";
14
  import IconSun from "$lib/components/icons/IconSun.svelte";
15
  import IconMoon from "$lib/components/icons/IconMoon.svelte";
16
+ import { switchTheme, subscribeToTheme } from "$lib/switchTheme";
17
  import { isAborted } from "$lib/stores/isAborted";
18
+ import { onDestroy } from "svelte";
19
 
20
  import NavConversationItem from "./NavConversationItem.svelte";
21
  import type { LayoutData } from "../../routes/$types";
 
101
  }
102
  });
103
 
104
+ let isDark = $state(false);
105
+ let unsubscribeTheme: (() => void) | undefined;
106
+
107
+ if (browser) {
108
+ unsubscribeTheme = subscribeToTheme(({ isDark: nextIsDark }) => {
109
+ isDark = nextIsDark;
110
+ });
111
+ }
112
+
113
+ onDestroy(() => {
114
+ unsubscribeTheme?.();
115
+ });
116
  </script>
117
 
118
  <div
 
200
  <button
201
  onclick={() => {
202
  switchTheme();
 
203
  }}
204
  aria-label="Toggle theme"
205
  class="flex size-9 min-w-[1.5em] flex-none items-center justify-center rounded-lg p-2 text-gray-500 hover:bg-gray-100 dark:text-gray-400 dark:hover:bg-gray-700"
206
  >
207
  {#if browser}
208
+ {#if isDark}
209
  <IconSun />
210
  {:else}
211
  <IconMoon />
src/lib/switchTheme.ts CHANGED
@@ -1,5 +1,37 @@
1
  export type ThemePreference = "light" | "dark" | "system";
2
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
3
  function setMetaThemeColor(isDark: boolean) {
4
  const metaTheme = document.querySelector('meta[name="theme-color"]') as HTMLMetaElement | null;
5
  if (!metaTheme) return;
@@ -11,11 +43,16 @@ function applyDarkClass(isDark: boolean) {
11
  if (isDark) classList.add("dark");
12
  else classList.remove("dark");
13
  setMetaThemeColor(isDark);
 
14
  }
15
 
16
  export function getThemePreference(): ThemePreference {
17
  const raw = typeof localStorage !== "undefined" ? localStorage.getItem("theme") : null;
18
- if (raw === "light" || raw === "dark" || raw === "system") return raw;
 
 
 
 
19
  return "system";
20
  }
21
 
@@ -33,6 +70,7 @@ export function setTheme(preference: ThemePreference) {
33
  }
34
 
35
  const mql = window.matchMedia("(prefers-color-scheme: dark)");
 
36
  const resolve = () =>
37
  applyDarkClass(preference === "dark" || (preference === "system" && mql.matches));
38
 
 
1
  export type ThemePreference = "light" | "dark" | "system";
2
 
3
+ type ThemeState = {
4
+ preference: ThemePreference;
5
+ isDark: boolean;
6
+ };
7
+
8
+ type ThemeSubscriber = (state: ThemeState) => void;
9
+
10
+ let currentPreference: ThemePreference = "system";
11
+ const subscribers = new Set<ThemeSubscriber>();
12
+
13
+ function notify(preference: ThemePreference, isDark: boolean) {
14
+ for (const subscriber of subscribers) {
15
+ subscriber({ preference, isDark });
16
+ }
17
+ }
18
+
19
+ export function subscribeToTheme(subscriber: ThemeSubscriber) {
20
+ subscribers.add(subscriber);
21
+
22
+ if (typeof document !== "undefined") {
23
+ const preference = getThemePreference();
24
+ const isDark = document.documentElement.classList.contains("dark");
25
+ subscriber({ preference, isDark });
26
+ } else {
27
+ subscriber({ preference: "system", isDark: false });
28
+ }
29
+
30
+ return () => {
31
+ subscribers.delete(subscriber);
32
+ };
33
+ }
34
+
35
  function setMetaThemeColor(isDark: boolean) {
36
  const metaTheme = document.querySelector('meta[name="theme-color"]') as HTMLMetaElement | null;
37
  if (!metaTheme) return;
 
43
  if (isDark) classList.add("dark");
44
  else classList.remove("dark");
45
  setMetaThemeColor(isDark);
46
+ notify(currentPreference, isDark);
47
  }
48
 
49
  export function getThemePreference(): ThemePreference {
50
  const raw = typeof localStorage !== "undefined" ? localStorage.getItem("theme") : null;
51
+ if (raw === "light" || raw === "dark" || raw === "system") {
52
+ currentPreference = raw;
53
+ return raw;
54
+ }
55
+ currentPreference = "system";
56
  return "system";
57
  }
58
 
 
70
  }
71
 
72
  const mql = window.matchMedia("(prefers-color-scheme: dark)");
73
+ currentPreference = preference;
74
  const resolve = () =>
75
  applyDarkClass(preference === "dark" || (preference === "system" && mql.matches));
76