Add a site-wide dark mode
This commit is contained in:
+156
-22
@@ -3,6 +3,7 @@
|
||||
import favicon from '$lib/assets/favicon.svg';
|
||||
import { page } from '$app/state';
|
||||
import { resolve } from '$app/paths';
|
||||
import { onMount } from 'svelte';
|
||||
import type { Snippet } from 'svelte';
|
||||
import type { LayoutData } from './$types';
|
||||
import './layout.css';
|
||||
@@ -12,6 +13,17 @@
|
||||
|
||||
let menuOpen = $state(false);
|
||||
let adminOpen = $state(false);
|
||||
let dark = $state(false);
|
||||
|
||||
onMount(() => {
|
||||
dark = document.documentElement.classList.contains('dark');
|
||||
});
|
||||
|
||||
function toggleTheme() {
|
||||
dark = !dark;
|
||||
document.documentElement.classList.toggle('dark', dark);
|
||||
localStorage.setItem('theme', dark ? 'dark' : 'light');
|
||||
}
|
||||
|
||||
const isAdmin = $derived((data.userRoleId ?? 4) <= 3);
|
||||
const adminModels = [
|
||||
@@ -49,8 +61,8 @@
|
||||
|
||||
function activeClass(path: string) {
|
||||
return isActive(path)
|
||||
? 'bg-indigo-50 text-indigo-700'
|
||||
: 'text-slate-600 hover:bg-slate-100 hover:text-slate-900';
|
||||
? 'bg-indigo-50 text-indigo-700 dark:bg-indigo-950 dark:text-indigo-300'
|
||||
: 'text-slate-600 dark:text-slate-400 hover:bg-slate-100 dark:hover:bg-slate-800 hover:text-slate-900 dark:hover:text-slate-100';
|
||||
}
|
||||
|
||||
function closeMenu() {
|
||||
@@ -75,7 +87,9 @@
|
||||
<meta name="twitter:card" content="summary" />
|
||||
</svelte:head>
|
||||
|
||||
<header class="sticky top-0 z-40 border-b border-slate-200 bg-white/90 backdrop-blur">
|
||||
<header
|
||||
class="sticky top-0 z-40 border-b border-slate-200 bg-white/90 backdrop-blur dark:border-slate-800 dark:bg-slate-900/90"
|
||||
>
|
||||
<div class="mx-auto flex h-14 max-w-6xl items-center justify-between gap-4 px-4">
|
||||
<a href={resolve('/')} class="flex shrink-0 items-center gap-2">
|
||||
<span
|
||||
@@ -96,7 +110,9 @@
|
||||
/></svg
|
||||
>
|
||||
</span>
|
||||
<span class="text-base font-semibold tracking-tight text-slate-900">Radiant Wave</span>
|
||||
<span class="text-base font-semibold tracking-tight text-slate-900 dark:text-slate-100"
|
||||
>Radiant Wave</span
|
||||
>
|
||||
</a>
|
||||
|
||||
{#if $session.data?.user}
|
||||
@@ -128,8 +144,8 @@
|
||||
onclick={() => (adminOpen = !adminOpen)}
|
||||
class={`flex items-center gap-1 rounded-md px-3 py-2 text-sm font-medium transition ${
|
||||
isActive('/admin')
|
||||
? 'bg-indigo-50 text-indigo-700'
|
||||
: 'text-slate-600 hover:bg-slate-100 hover:text-slate-900'
|
||||
? 'bg-indigo-50 text-indigo-700 dark:bg-indigo-950 dark:text-indigo-300'
|
||||
: 'text-slate-600 hover:bg-slate-100 hover:text-slate-900 dark:text-slate-400 dark:hover:bg-slate-800 dark:hover:text-slate-100'
|
||||
}`}
|
||||
>
|
||||
Admin
|
||||
@@ -150,7 +166,7 @@
|
||||
|
||||
{#if adminOpen}
|
||||
<div
|
||||
class="absolute top-full left-0 mt-1 min-w-56 rounded-lg border border-slate-200 bg-white p-1 shadow-lg"
|
||||
class="absolute top-full left-0 mt-1 min-w-56 rounded-lg border border-slate-200 bg-white p-1 shadow-lg dark:border-slate-800 dark:bg-slate-900"
|
||||
>
|
||||
{#each adminModels as model (model.slug)}
|
||||
<a
|
||||
@@ -158,8 +174,8 @@
|
||||
onclick={() => (adminOpen = false)}
|
||||
class={`block rounded-md px-3 py-2 text-sm transition ${
|
||||
isActive('/admin/' + model.slug)
|
||||
? 'bg-indigo-50 text-indigo-700'
|
||||
: 'text-slate-600 hover:bg-slate-100 hover:text-slate-900'
|
||||
? 'bg-indigo-50 text-indigo-700 dark:bg-indigo-950 dark:text-indigo-300'
|
||||
: 'text-slate-600 hover:bg-slate-100 hover:text-slate-900 dark:text-slate-400 dark:hover:bg-slate-800 dark:hover:text-slate-100'
|
||||
}`}
|
||||
>
|
||||
{model.displayName}
|
||||
@@ -170,11 +186,47 @@
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<span class="mx-2 h-5 w-px bg-slate-200" aria-hidden="true"></span>
|
||||
<button
|
||||
type="button"
|
||||
aria-label="Toggle dark mode"
|
||||
aria-pressed={dark}
|
||||
onclick={toggleTheme}
|
||||
class="flex h-9 w-9 items-center justify-center rounded-md text-slate-600 transition hover:bg-slate-100 hover:text-slate-900 dark:text-slate-400 dark:hover:bg-slate-800 dark:hover:text-slate-100"
|
||||
>
|
||||
{#if dark}
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="18"
|
||||
height="18"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
stroke-width="2"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
><circle cx="12" cy="12" r="4" /><path
|
||||
d="M12 2v2M12 20v2M4.93 4.93l1.41 1.41M17.66 17.66l1.41 1.41M2 12h2M20 12h2M6.34 17.66l-1.41 1.41M19.07 4.93l-1.41 1.41"
|
||||
/></svg
|
||||
>
|
||||
{:else}
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="18"
|
||||
height="18"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
stroke-width="2"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"><path d="M12 3a6 6 0 0 0 9 9 9 9 0 1 1-9-9Z" /></svg
|
||||
>
|
||||
{/if}
|
||||
</button>
|
||||
<span class="mx-2 h-5 w-px bg-slate-200 dark:bg-slate-700" aria-hidden="true"></span>
|
||||
<button
|
||||
type="button"
|
||||
onclick={handleSignOut}
|
||||
class="rounded-md px-3 py-2 text-sm font-medium text-slate-500 transition hover:bg-slate-100 hover:text-slate-900"
|
||||
class="rounded-md px-3 py-2 text-sm font-medium text-slate-500 transition hover:bg-slate-100 hover:text-slate-900 dark:text-slate-400 dark:hover:bg-slate-800 dark:hover:text-slate-100"
|
||||
>
|
||||
Sign Out
|
||||
</button>
|
||||
@@ -193,15 +245,87 @@
|
||||
>
|
||||
Sign In
|
||||
</a>
|
||||
<button
|
||||
type="button"
|
||||
aria-label="Toggle dark mode"
|
||||
aria-pressed={dark}
|
||||
onclick={toggleTheme}
|
||||
class="ml-1 flex h-9 w-9 items-center justify-center rounded-md text-slate-600 transition hover:bg-slate-100 hover:text-slate-900 dark:text-slate-400 dark:hover:bg-slate-800 dark:hover:text-slate-100"
|
||||
>
|
||||
{#if dark}
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="18"
|
||||
height="18"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
stroke-width="2"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
><circle cx="12" cy="12" r="4" /><path
|
||||
d="M12 2v2M12 20v2M4.93 4.93l1.41 1.41M17.66 17.66l1.41 1.41M2 12h2M20 12h2M6.34 17.66l-1.41 1.41M19.07 4.93l-1.41 1.41"
|
||||
/></svg
|
||||
>
|
||||
{:else}
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="18"
|
||||
height="18"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
stroke-width="2"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"><path d="M12 3a6 6 0 0 0 9 9 9 9 0 1 1-9-9Z" /></svg
|
||||
>
|
||||
{/if}
|
||||
</button>
|
||||
</nav>
|
||||
{/if}
|
||||
|
||||
<button
|
||||
type="button"
|
||||
aria-label="Toggle dark mode"
|
||||
aria-pressed={dark}
|
||||
onclick={toggleTheme}
|
||||
class="flex h-10 w-10 items-center justify-center rounded-lg text-slate-700 hover:bg-slate-100 md:hidden dark:text-slate-300 dark:hover:bg-slate-800"
|
||||
>
|
||||
{#if dark}
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="20"
|
||||
height="20"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
stroke-width="2"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
><circle cx="12" cy="12" r="4" /><path
|
||||
d="M12 2v2M12 20v2M4.93 4.93l1.41 1.41M17.66 17.66l1.41 1.41M2 12h2M20 12h2M6.34 17.66l-1.41 1.41M19.07 4.93l-1.41 1.41"
|
||||
/></svg
|
||||
>
|
||||
{:else}
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="20"
|
||||
height="20"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
stroke-width="2"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"><path d="M12 3a6 6 0 0 0 9 9 9 9 0 1 1-9-9Z" /></svg
|
||||
>
|
||||
{/if}
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
aria-label="Toggle navigation menu"
|
||||
aria-expanded={menuOpen}
|
||||
onclick={() => (menuOpen = !menuOpen)}
|
||||
class="flex h-10 w-10 items-center justify-center rounded-lg text-slate-700 hover:bg-slate-100 md:hidden"
|
||||
class="flex h-10 w-10 items-center justify-center rounded-lg text-slate-700 hover:bg-slate-100 md:hidden dark:text-slate-300 dark:hover:bg-slate-800"
|
||||
>
|
||||
{#if menuOpen}
|
||||
<svg
|
||||
@@ -240,7 +364,7 @@
|
||||
|
||||
{#if menuOpen}
|
||||
<div
|
||||
class="fixed inset-0 top-14 z-50 overflow-y-auto border-t border-slate-200 bg-white shadow-lg md:hidden"
|
||||
class="fixed inset-0 top-14 z-50 overflow-y-auto border-t border-slate-200 bg-white shadow-lg md:hidden dark:border-slate-800 dark:bg-slate-900"
|
||||
>
|
||||
<nav class="mx-auto max-w-6xl px-4 py-4">
|
||||
{#if $session.data?.user}
|
||||
@@ -268,15 +392,15 @@
|
||||
</a>
|
||||
|
||||
{#if isAdmin}
|
||||
<div class="mt-2 border-t border-slate-200 pt-2">
|
||||
<div class="mt-2 border-t border-slate-200 pt-2 dark:border-slate-800">
|
||||
<button
|
||||
type="button"
|
||||
aria-expanded={adminOpen}
|
||||
onclick={() => (adminOpen = !adminOpen)}
|
||||
class={`flex w-full items-center justify-between rounded-lg px-4 py-3 text-base font-semibold transition ${
|
||||
isActive('/admin')
|
||||
? 'bg-indigo-50 text-indigo-700'
|
||||
: 'text-slate-700 hover:bg-indigo-50'
|
||||
? 'bg-indigo-50 text-indigo-700 dark:bg-indigo-950 dark:text-indigo-300'
|
||||
: 'text-slate-700 hover:bg-indigo-50 dark:text-slate-300 dark:hover:bg-indigo-950'
|
||||
}`}
|
||||
>
|
||||
<span>Admin</span>
|
||||
@@ -314,7 +438,7 @@
|
||||
<button
|
||||
type="button"
|
||||
onclick={handleSignOut}
|
||||
class="mt-2 rounded-lg border-t border-slate-200 px-4 py-3 text-base font-medium text-slate-500 hover:bg-slate-100"
|
||||
class="mt-2 rounded-lg border-t border-slate-200 px-4 py-3 text-base font-medium text-slate-500 hover:bg-slate-100 dark:border-slate-800 dark:text-slate-400 dark:hover:bg-slate-800"
|
||||
>
|
||||
Sign Out
|
||||
</button>
|
||||
@@ -341,21 +465,31 @@
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<div class="flex min-h-[calc(100dvh-3.5rem)] flex-col bg-slate-50 text-slate-900">
|
||||
<div
|
||||
class="flex min-h-[calc(100dvh-3.5rem)] flex-col bg-slate-50 text-slate-900 dark:bg-slate-950 dark:text-slate-100"
|
||||
>
|
||||
<main class="flex-1">
|
||||
{@render children()}
|
||||
</main>
|
||||
<footer class="sticky bottom-0 z-10 border-t border-slate-200 bg-white">
|
||||
<footer
|
||||
class="sticky bottom-0 z-10 border-t border-slate-200 bg-white dark:border-slate-800 dark:bg-slate-900"
|
||||
>
|
||||
<div
|
||||
class="mx-auto flex max-w-6xl flex-col items-center justify-between gap-2 px-4 py-6 text-sm text-slate-500 sm:flex-row"
|
||||
class="mx-auto flex max-w-6xl flex-col items-center justify-between gap-2 px-4 py-6 text-sm text-slate-500 sm:flex-row dark:text-slate-400"
|
||||
>
|
||||
<p>© {new Date().getFullYear()} Radiant Wave Technologies. All rights reserved.</p>
|
||||
{#if $session.data?.user}
|
||||
<a href={resolve('/profile')} class="transition hover:text-slate-900">
|
||||
<a
|
||||
href={resolve('/profile')}
|
||||
class="transition hover:text-slate-900 dark:hover:text-slate-100"
|
||||
>
|
||||
Manage your profile
|
||||
</a>
|
||||
{:else}
|
||||
<a href={resolve('/login')} class="transition hover:text-slate-900">Sign in</a>
|
||||
<a
|
||||
href={resolve('/login')}
|
||||
class="transition hover:text-slate-900 dark:hover:text-slate-100">Sign in</a
|
||||
>
|
||||
{/if}
|
||||
</div>
|
||||
</footer>
|
||||
|
||||
Reference in New Issue
Block a user