19
loading...
This website collects cookies to deliver better user experience
export const theme = createWritableStore('theme', { mode: 'dark', color: 'blue' });
mode
in it. You can ignore color
for now, we'll be adding that another time. createWritableStore
was taken from this stackoverflow post.import cookie from 'cookie';
export const getSession = async (request) => {
const cookies = cookie.parse(request.headers.cookie || '');
const theme = cookies.theme || 'dark';
return {
theme,
};
};
getSession
hook, we just want to get the value of our theme from a cookie, and otherwise default it to dark
mode. This will be accessible in load
in our components later.export const handle = async ({ request, render }) => {
// TODO https://github.com/sveltejs/kit/issues/1046
const response = await render({
...request,
method: (request.query.get('_method') || request.method).toUpperCase(),
});
const cookies = cookie.parse(request.headers.cookie || '');
let headers = response.headers;
const cookiesArray = [];
if (!cookies.theme) {
const theme = request.query.get('theme') || 'dark';
cookiesArray.push(`theme=${theme};path=/;expires=Fri, 31 Dec 9999 23:59:59 GMT`);
}
if (cookiesArray.length > 0) {
headers = {
...response.headers,
'set-cookie': cookiesArray,
};
}
return {
...response,
headers,
};
};
handle
, you can skip the beginning (copied from the demo app), and starting at the line const cookies =
, we check to see if we don't have a theme cookie yet. If we don't then we go ahead and set it to a query param of theme if provided, or default to dark
mode. We then set the cookiesArray to our set-cookie
header for SvelteKit. This allows us to set a cookie for the first request. Sadly, we don't have access to the user's prefers-color-scheme
here, so we can't default to their preference yet. We'll do it later in the frontend for users with JS enabled.<script context="module">
export async function load({ session }) {
const localTheme = session.theme;
return { props: { localTheme } };
}
</script>
module
context and load
function, we get our theme from the session. This will be used below to set on a div to ensure everything looks correct without JS enabled.<script>
import { onMount } from 'svelte';
import Nav from '$lib/app/navbar/Nav.svelte';
import { theme } from '$lib/shared/stores';
export let localTheme;
// We load the in the <script> tag in load, but then also here onMount to setup stores
onMount(() => {
if (!('theme' in localStorage)) {
theme.useLocalStorage();
if (window.matchMedia('(prefers-color-scheme: dark)').matches) {
localTheme = 'dark';
theme.set({ ...$theme, mode: 'dark' });
} else {
localTheme = 'light';
theme.set({ ...$theme, mode: 'light' });
}
} else {
theme.useLocalStorage();
}
document.documentElement.classList.remove('dark');
});
</script>
<svelte:head>
<script>
if (!('theme' in localStorage)) {
if (window.matchMedia('(prefers-color-scheme: dark)').matches) {
document.documentElement.classList.add('dark');
document.cookie = 'theme=dark;path=/;expires=Fri, 31 Dec 9999 23:59:59 GMT;';
} else {
document.documentElement.classList.remove('dark');
document.cookie = 'theme=light;path=/;expires=Fri, 31 Dec 9999 23:59:59 GMT;';
}
} else {
let data = localStorage.getItem('theme');
if (data) {
data = JSON.parse(data);
document.documentElement.classList.add(data.mode);
}
}
</script>
</svelte:head>
dark
if we haven't had anything set in localStorage. So for users with JS enabled, we can get their preferred setting and override the dark
cookie which we set in getSession
- just an added nicety for users with JS on. The latter also blocks so will show up without a flicker. The onMount
will run later and keep our localStorage store in sync with the rest.<div id="core" class="{localTheme}">
<main class="dark:bg-black bg-white">
<Nav />
<slot />
</main>
</div>
localTheme
class, which is sent from load
as a prop
. It's created from the cookie value which is provided in the getSession
hook.<script>
import { theme } from '$lib/shared/stores';
import { toggleTheme } from '$lib/shared/theme';
import { UiMoonSolid, UiSunOutline } from '$lib/components/icons';
const klass = 'px-3 py-2 rounded-md leading-5 font-medium \
focus:outline-none focus:text-white focus:bg-primary-300 \
text-neutral-800 hover:text-white hover:bg-primary-300 \
dark:text-white dark:hover:bg-primary-700 dark:focus:bg-primary-700 \
dark:bg-black';
</script>
<nav>
<a
href="/app/theme"
class="block {klass}"
aria-label="Toggle Light and Dark mode"
on:click|preventDefault={() => {
toggleTheme(theme, $theme);
}}
>
<div class="hidden dark:block">
<UiSunOutline />
</div>
<div class="dark:hidden">
<UiMoonSolid />
</div>
</a>
</nav>
GET
request. For users with JS enabled, we call toggleTheme
. For those without JS enabled, it will fall back to the /app/theme
endpoint. It uses Tailwind dark:block
and dark:hidden
to show/hide the correct icon.export function toggleTheme(theme: any, $theme: any): void {
if ($theme.mode === 'light') {
theme.set({ ...$theme, mode: 'dark' });
updateDocument('theme', 'dark', 'light');
} else {
theme.set({ ...$theme, mode: 'light' });
updateDocument('theme', 'light', 'dark');
}
}
function updateDocument(name: string, klass: string, other: string) {
document.cookie = `${name}=${klass};path=/;expires=Fri, 31 Dec 9999 23:59:59 GMT`;
document.getElementById('core').classList.remove(other);
document.documentElement.classList.remove(other);
document.getElementById('core').classList.add(klass);
document.documentElement.classList.add(klass);
}
light
or dark
mode.import cookie from 'cookie';
import type { RequestHandler } from '@sveltejs/kit';
// GET /app/theme
export const get: RequestHandler = async (request) => {
const cookies = cookie.parse(request.headers.cookie || '');
let theme = cookies.theme;
theme = theme === 'dark' ? 'light' : 'dark';
return {
status: 303,
headers: {
location: '/',
'set-cookie': `theme=${theme}; path=/; expires=Fri, 31 Dec 9999 23:59:59 GMT`,
},
body: '',
};
};
GET
endpoint. Like getSession
and handle
we parse the cookies to get the theme. If it's currently set to dark
we change it to light
, and vice versa. We then return an object for SvelteKit to know to 303, redirecting to /
and setting the cookie for the new value we need, along with an empty body. Note that GET
requests should normally be idempotent, so if you want to move this to a POST
, PUT
or a PATCH
that would work too.