Skip to content

Commit

Permalink
add temp dark mode toggle
Browse files Browse the repository at this point in the history
  • Loading branch information
huntabyte committed May 27, 2023
1 parent 4872d6d commit c77c15d
Show file tree
Hide file tree
Showing 7 changed files with 277 additions and 2 deletions.
1 change: 1 addition & 0 deletions apps/www/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@
"clsx": "^1.2.1",
"d3-scale": "^4.0.2",
"escape-html": "^1.0.3",
"esm-env": "^1.0.0",
"hast-util-to-html": "^8.0.4",
"lucide-svelte": "^0.221.0",
"mdast-util-to-string": "^3.2.0",
Expand Down
4 changes: 3 additions & 1 deletion apps/www/pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 1 deletion apps/www/src/lib/components/docs/SiteHeader.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import { buttonVariants } from "$components/ui/button";
import { siteConfig } from "$lib/config/site";
import { cn } from "$lib/utils";
import LightSwitch from "./light-switch/LightSwitch.svelte";
import MobileNav from "./nav/MobileNav.svelte";
</script>

Expand Down Expand Up @@ -56,7 +57,7 @@
<span class="sr-only">Twitter</span>
</div>
</a>
<!-- <ModeToggle /> -->
<LightSwitch />
</nav>
</div>
</div>
Expand Down
75 changes: 75 additions & 0 deletions apps/www/src/lib/components/docs/light-switch/LightSwitch.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
<!--
Derived from SkeletonUI: https://github.com/skeletonlabs/skeleton/blob/master/packages/skeleton/src/lib/utilities/LightSwitch/LightSwitch.svelte
-->

<script lang="ts">
import { Moon, Sun } from "lucide-svelte";
import { onMount } from "svelte";
import { buttonVariants } from "$components/ui/button";
import { cn } from "$lib/utils";
import {
getModeOsPrefers,
modeCurrent,
setModeCurrent,
setModeUserPrefers
} from "./light-switch";
type OnKeyDownEvent = KeyboardEvent & {
currentTarget: EventTarget & HTMLDivElement;
};
function onToggleHandler(): void {
$modeCurrent = !$modeCurrent;
setModeUserPrefers($modeCurrent);
setModeCurrent($modeCurrent);
}
// A11y Input Handlers
function onKeyDown(event: OnKeyDownEvent): void {
// Enter/Space triggers selection event
if (["Enter", "Space"].includes(event.code)) {
event.preventDefault();
event.currentTarget.click();
}
}
// Lifecycle
onMount(() => {
// Sync lightswitch with the theme
if (!("modeCurrent" in localStorage)) {
setModeCurrent(getModeOsPrefers());
}
});
</script>

<div
on:click={onToggleHandler}
on:click
on:keydown={onKeyDown}
on:keydown
on:keyup
on:keypress
role="switch"
aria-label="Light Switch"
aria-checked={$modeCurrent}
title="Toggle {$modeCurrent === true ? 'Dark' : 'Light'} Mode"
tabindex="0"
>
<div
class={cn(
buttonVariants({
size: "sm",
variant: "ghost"
}),
"w-9 px-0"
)}
>
{#if $modeCurrent}
<Moon class="h-5 w-5" />
<span class="sr-only">Dark</span>
{:else}
<Sun class="h-5 w-5" />
<span class="sr-only">Light</span>
{/if}
</div>
</div>
104 changes: 104 additions & 0 deletions apps/www/src/lib/components/docs/light-switch/light-switch.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
// Source: https://github.com/skeletonlabs/skeleton/blob/master/packages/skeleton/src/lib/utilities/LightSwitch/lightswitch.ts

// Lightswitch Service

import { get } from "svelte/store";
// DO NOT replace this ⬇ import, it has to be imported directly
import { localStorageStore } from "./local-storage-store";

// Stores ---
// TRUE: light, FALSE: dark

/** Store: OS Preference Mode */
export const modeOsPrefers = localStorageStore<boolean>("modeOsPrefers", false);
/** Store: User Preference Mode */
export const modeUserPrefers = localStorageStore<boolean | undefined>(
"modeUserPrefers",
undefined
);
/** Store: Current Mode State */
export const modeCurrent = localStorageStore<boolean>("modeCurrent", false);

// Get ---

/** Get the OS Preference for light/dark mode */
export function getModeOsPrefers(): boolean {
const prefersLightMode = window.matchMedia(
"(prefers-color-scheme: light)"
).matches;
modeOsPrefers.set(prefersLightMode);
return prefersLightMode;
}

/** Get the User for light/dark mode */
export function getModeUserPrefers(): boolean | undefined {
return get(modeUserPrefers);
}

/** Get the Automatic Preference light/dark mode */
export function getModeAutoPrefers(): boolean {
const os = getModeOsPrefers();
const user = getModeUserPrefers();
const modeValue = user !== undefined ? user : os;
return modeValue;
}

// Set ---

/** Set the User Preference for light/dark mode */
export function setModeUserPrefers(value: boolean): void {
modeUserPrefers.set(value);
}

/** Set the the current light/dark mode */
export function setModeCurrent(value: boolean) {
const elemHtmlClasses = document.documentElement.classList;
const classDark = `dark`;
value === true
? elemHtmlClasses.remove(classDark)
: elemHtmlClasses.add(classDark);
modeCurrent.set(value);
}

// Lightswitch Utility

/** Set the visible light/dark mode on page load. */
export function setInitialClassState() {
const elemHtmlClasses = document.documentElement.classList;
// Conditions
const condLocalStorageUserPrefs =
localStorage.getItem("modeUserPrefers") === "false";
const condLocalStorageUserPrefsExists = !(
"modeUserPrefers" in localStorage
);
const condMatchMedia = window.matchMedia(
"(prefers-color-scheme: dark)"
).matches;
// Add/remove `.dark` class to HTML element
if (
condLocalStorageUserPrefs ||
(condLocalStorageUserPrefsExists && condMatchMedia)
) {
elemHtmlClasses.add("dark");
} else {
elemHtmlClasses.remove("dark");
}
}

// Auto-Switch Utility

/** Automatically set the visible light/dark, updates on change. */
export function autoModeWatcher(): void {
const mql = window.matchMedia("(prefers-color-scheme: light)");
function setMode(value: boolean) {
const elemHtmlClasses = document.documentElement.classList;
const classDark = `dark`;
value === true
? elemHtmlClasses.remove(classDark)
: elemHtmlClasses.add(classDark);
}
setMode(mql.matches);
mql.onchange = () => {
setMode(mql.matches);
};
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
// Source: https://github.com/joshnuss/svelte-local-storage-store
// https://github.com/joshnuss/svelte-local-storage-store/blob/master/index.ts
// Represents version v0.4.0 (2023-01-18)
import type { Writable } from "svelte/store";
import { BROWSER } from "esm-env";
import { get, writable as internal } from "svelte/store";

declare type Updater<T> = (value: T) => T;
declare type StoreDict<T> = { [key: string]: Writable<T> };

/* eslint-disable @typescript-eslint/no-explicit-any */
const stores: StoreDict<any> = {};

interface Serializer<T> {
parse(text: string): T;
stringify(object: T): string;
}

type StorageType = "local" | "session";

interface Options<T> {
serializer?: Serializer<T>;
storage?: StorageType;
}

function getStorage(type: StorageType) {
return type === "local" ? localStorage : sessionStorage;
}

export function localStorageStore<T>(
key: string,
initialValue: T,
options?: Options<T>
): Writable<T> {
const serializer = options?.serializer ?? JSON;
const storageType = options?.storage ?? "local";

function updateStorage(key: string, value: T) {
if (!BROWSER) return;

getStorage(storageType).setItem(key, serializer.stringify(value));
}

if (!stores[key]) {
const store = internal(initialValue, (set) => {
const json = BROWSER ? getStorage(storageType).getItem(key) : null;

if (json) {
set(<T>serializer.parse(json));
}

if (BROWSER) {
const handleStorage = (event: StorageEvent) => {
if (event.key === key)
set(
event.newValue
? serializer.parse(event.newValue)
: null
);
};

window.addEventListener("storage", handleStorage);

return () =>
window.removeEventListener("storage", handleStorage);
}
});

const { subscribe, set } = store;

stores[key] = {
set(value: T) {
updateStorage(key, value);
set(value);
},
update(updater: Updater<T>) {
const value = updater(get(store));

updateStorage(key, value);
set(value);
},
subscribe
};
}

return stores[key];
}
5 changes: 5 additions & 0 deletions apps/www/src/routes/+layout.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,14 @@
import SiteFooter from "$components/docs/SiteFooter.svelte";
import SiteHeader from "$components/docs/SiteHeader.svelte";
import TailwindIndicator from "$components/docs/TailwindIndicator.svelte";
import { setInitialClassState } from "$components/docs/light-switch/light-switch";
import "../styles/globals.css";
</script>

<svelte:head>
{@html `<script nonce="%sveltekit.nonce%">(${setInitialClassState.toString()})();</script>`}
</svelte:head>

<div class="relative flex min-h-screen flex-col">
<SiteHeader />
<div class="flex-1">
Expand Down

1 comment on commit c77c15d

@vercel
Copy link

@vercel vercel bot commented on c77c15d May 27, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please sign in to comment.