theme.toggle()}
- src={$theme === 'dark' ? SunIcon : MoonIcon}
- class="{className} h-10 w-10 text-zinc-500 hover:text-indigo-800 dark:bg-zinc-800 dark:text-zinc-200 dark:hover:text-indigo-600"
-/>
diff --git a/apps/web/src/lib/stores/theme.ts b/apps/web/src/lib/stores/theme.ts
deleted file mode 100644
index ba7557db..00000000
--- a/apps/web/src/lib/stores/theme.ts
+++ /dev/null
@@ -1,54 +0,0 @@
-import { browser } from '$app/environment';
-import { writable } from 'svelte/store';
-
-const THEME_KEY = 'theme';
-const DEFAULT_THEME = 'light';
-
-export function getInferredDefaultTheme(): 'dark' | 'light' {
- if (!window.matchMedia) {
- return DEFAULT_THEME;
- }
- if (window.matchMedia('(prefers-color-scheme: dark)').matches) {
- return 'dark';
- } else {
- return 'light';
- }
-}
-
-function createThemeStore() {
- const { subscribe, set, update } = writable<'light' | 'dark'>(DEFAULT_THEME);
-
- if (browser) {
- let data = localStorage?.getItem(THEME_KEY) as 'light' | 'dark';
-
- if (!data) {
- data = getInferredDefaultTheme();
- }
- set(data);
- if (data === 'dark') {
- document.querySelector('html')?.classList.add('dark');
- }
- }
-
- subscribe((value) => {
- if (browser) {
- localStorage?.setItem(THEME_KEY, value);
- }
- });
-
- function toggle() {
- update((state) => {
- state = state === 'dark' ? 'light' : 'dark';
- return state;
- });
- document.querySelector('html')?.classList.toggle('dark');
- }
-
- return {
- subscribe,
- set,
- toggle
- };
-}
-
-export const theme = createThemeStore();
diff --git a/apps/web/src/routes/+layout.svelte b/apps/web/src/routes/+layout.svelte
index 9fd7ee7e..d3f0cc72 100644
--- a/apps/web/src/routes/+layout.svelte
+++ b/apps/web/src/routes/+layout.svelte
@@ -9,14 +9,13 @@
import '../app.css';
import { onMount } from 'svelte';
import { page } from '$app/stores';
- import { Toasts, NavBar, Footer } from '@yd/ui';
+ import { Toasts, NavBar, Footer, ThemeToggle } from '@yd/ui';
import Loading from '$lib/components/Loading.svelte';
import { session } from '$lib/stores/session';
import { downloads } from '$lib/stores/downloads';
import config from '$lib/config';
import { RoutePathConstants, links } from '$lib/utils/route';
import Logo from '$lib/components/Logo.svelte';
- import ThemeChangeIcon from '$lib/components/ThemeChangeIcon.svelte';
// Use session as token when making requests with client
OpenAPI.TOKEN = async () => {
@@ -38,7 +37,7 @@
-
+
diff --git a/packages/ui/src/lib/components/theme/ThemeToggle.svelte b/packages/ui/src/lib/components/theme/ThemeToggle.svelte
new file mode 100644
index 00000000..1809ef99
--- /dev/null
+++ b/packages/ui/src/lib/components/theme/ThemeToggle.svelte
@@ -0,0 +1,11 @@
+
+
+ theme.toggle()}
+ src={$theme === 'dark' ? SunIcon : MoonIcon}
+ class="h-10 w-10 text-zinc-500 hover:text-indigo-800 dark:bg-zinc-800 dark:text-zinc-200 dark:hover:text-indigo-600"
+/>
diff --git a/packages/ui/src/lib/components/theme/index.ts b/packages/ui/src/lib/components/theme/index.ts
new file mode 100644
index 00000000..26ef9731
--- /dev/null
+++ b/packages/ui/src/lib/components/theme/index.ts
@@ -0,0 +1,2 @@
+export { theme } from './store';
+export { default as ThemeToggle } from './ThemeToggle.svelte';
diff --git a/packages/ui/src/lib/components/theme/store.ts b/packages/ui/src/lib/components/theme/store.ts
new file mode 100644
index 00000000..366398b9
--- /dev/null
+++ b/packages/ui/src/lib/components/theme/store.ts
@@ -0,0 +1,61 @@
+import { writable } from 'svelte/store';
+import { browser } from '../../utilities';
+
+type Theme = 'dark' | 'light';
+
+const THEME_LOCALSTORAGE_KEY = 'theme';
+const DEFAULT_THEME: Theme = 'light';
+
+// Get the default theme value inferred by the browser settings.
+function getInferredDefaultTheme(): Theme {
+ if (browser()) {
+ // Cant match preferred color scheme, return default.
+ if (!window.matchMedia) {
+ return DEFAULT_THEME;
+ }
+ // Use dark theme if preferred by the user.
+ if (window.matchMedia('(prefers-color-scheme: dark)').matches) {
+ return 'dark';
+ } else {
+ return 'light';
+ }
+ } else {
+ return DEFAULT_THEME;
+ }
+}
+
+function createThemeStore() {
+ const { subscribe, set, update } = writable(getInferredDefaultTheme());
+
+ if (browser()) {
+ let data = localStorage?.getItem(THEME_LOCALSTORAGE_KEY) as Theme;
+ if (!data) {
+ data = getInferredDefaultTheme();
+ }
+ set(data);
+ if (data === 'dark') {
+ document.querySelector('html')?.classList.add('dark');
+ }
+ }
+
+ subscribe((value) => {
+ if (browser()) {
+ localStorage?.setItem(THEME_LOCALSTORAGE_KEY, value);
+ }
+ });
+
+ function toggle() {
+ update((state) => {
+ state = state === 'dark' ? 'light' : 'dark';
+ return state;
+ });
+ document.querySelector('html')?.classList.toggle('dark');
+ }
+
+ return {
+ subscribe,
+ toggle
+ };
+}
+
+export const theme = createThemeStore();
diff --git a/packages/ui/src/lib/index.ts b/packages/ui/src/lib/index.ts
index 18ba1484..65439701 100644
--- a/packages/ui/src/lib/index.ts
+++ b/packages/ui/src/lib/index.ts
@@ -1,5 +1,6 @@
// Component exports
export { NavBar } from './components/nav';
+export { ThemeToggle, theme } from './components/theme';
export { Toast, Toasts, toast } from './components/toast';
export { Description, List, Title } from './components/typography';
export { default as Alert, type AlertVariants } from './components/Alert.svelte';
diff --git a/packages/ui/src/lib/utilities.ts b/packages/ui/src/lib/utilities.ts
index 6494ac4a..e3fdd840 100644
--- a/packages/ui/src/lib/utilities.ts
+++ b/packages/ui/src/lib/utilities.ts
@@ -55,3 +55,9 @@ export function toIcon(value: unknown, extras?: { loading?: boolean }): IconSour
return undefined;
}
}
+
+/**
+ * Helper function to check if running in browser
+ * @returns true when running in browser, false otherwise
+ */
+export const browser = () => typeof window !== 'undefined';