feat: add themeable design tokens with cookie-backed SSR

This commit is contained in:
2026-08-11 20:08:53 -04:00
parent a1f39a8dc8
commit d71b53b3c5
8 changed files with 629 additions and 71 deletions
+4 -2
View File
@@ -1,9 +1,11 @@
import { listLibraries } from '$lib/api/library.remote.js';
export const load = async ({ depends }) => {
export const load = async ({ depends, locals }) => {
depends('app:libraries');
return {
libraries: await listLibraries({ pageSize: 100 })
libraries: await listLibraries({ pageSize: 100 }),
theme: locals.theme,
user: locals.user
};
};
+24 -5
View File
@@ -8,17 +8,36 @@
import { setBookOperationsState } from '$lib/state/bookOperations.svelte';
import { setBookshelfState } from '$lib/state/bookshelf.svelte';
import { setLibraryState } from '$lib/state/library.svelte.js';
import { setThemeState } from '$lib/theme/theme.svelte';
import type { ThemeConfig } from '$lib/theme/presets';
import { ModeWatcher } from 'mode-watcher';
import type { Snippet } from 'svelte';
import { untrack, type Snippet } from 'svelte';
import { Toaster } from 'svelte-sonner';
let { data, children }: { data: { libraries: PaginatedResponse<Library> }; children: Snippet } =
$props();
let {
data,
children
}: {
data: { libraries: PaginatedResponse<Library>; theme: ThemeConfig; user: { email: string } };
children: Snippet;
} = $props();
const libraryState = setLibraryState(data.libraries.items);
// These states are seeded once and then own their own data — LibraryState has
// updateLibraries() for later syncing, and ThemeState owns the config after
// init. `untrack` says that explicitly, instead of silently capturing the
// initial value and warning about it.
const libraryState = setLibraryState(untrack(() => data.libraries.items));
const bookshelfState = setBookshelfState();
const bookOps = setBookOperationsState(libraryState.activeLibrary!.id);
const theme = setThemeState(untrack(() => data.theme));
// Inline custom properties live on :root and so are mode-blind. When the
// light/dark switch flips, rewrite them for the mode now showing.
$effect(() => {
theme.mode;
theme.syncMode();
});
</script>
<Toaster />
@@ -27,7 +46,7 @@
<div class="[--header-height:calc(--spacing(14))]">
<Sidebar.Provider class="flex flex-col">
<div class="flex h-screen">
<AppSidebar />
<AppSidebar user={data.user} />
<Sidebar.Inset class="flex min-w-0 flex-1 flex-col overflow-x-hidden">
<SiteHeader />
<div class="mt-4 flex-1 overflow-hidden p-4 pt-0">