diff --git a/apps/www/package.json b/apps/www/package.json index 739997e02..f14bd4ab0 100644 --- a/apps/www/package.json +++ b/apps/www/package.json @@ -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", diff --git a/apps/www/pnpm-lock.yaml b/apps/www/pnpm-lock.yaml index 5660cb2bc..f84189687 100644 --- a/apps/www/pnpm-lock.yaml +++ b/apps/www/pnpm-lock.yaml @@ -21,6 +21,9 @@ dependencies: escape-html: specifier: ^1.0.3 version: 1.0.3 + esm-env: + specifier: ^1.0.0 + version: 1.0.0 hast-util-to-html: specifier: ^8.0.4 version: 8.0.4 @@ -1804,7 +1807,6 @@ packages: /esm-env@1.0.0: resolution: {integrity: sha512-Cf6VksWPsTuW01vU9Mk/3vRue91Zevka5SjyNf3nEpokFRuqt/KjUQoGAwq9qMmhpLTHmXzSIrFRw8zxWzmFBA==} - dev: true /espree@9.5.2: resolution: {integrity: sha512-7OASN1Wma5fum5SrNhFMAMJxOUAbhyfQ8dQ//PJaJbNw0URTPWqIghHWt1MmAANKhHZIYOHruW4Kw4ruUWOdGw==} diff --git a/apps/www/src/lib/components/docs/SiteHeader.svelte b/apps/www/src/lib/components/docs/SiteHeader.svelte index c63505b79..81942cdcf 100644 --- a/apps/www/src/lib/components/docs/SiteHeader.svelte +++ b/apps/www/src/lib/components/docs/SiteHeader.svelte @@ -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"; @@ -56,7 +57,7 @@ Twitter - + diff --git a/apps/www/src/lib/components/docs/light-switch/LightSwitch.svelte b/apps/www/src/lib/components/docs/light-switch/LightSwitch.svelte new file mode 100644 index 000000000..210eb6b34 --- /dev/null +++ b/apps/www/src/lib/components/docs/light-switch/LightSwitch.svelte @@ -0,0 +1,75 @@ + + + + +
+
+ {#if $modeCurrent} + + Dark + {:else} + + Light + {/if} +
+
diff --git a/apps/www/src/lib/components/docs/light-switch/light-switch.ts b/apps/www/src/lib/components/docs/light-switch/light-switch.ts new file mode 100644 index 000000000..d742c935a --- /dev/null +++ b/apps/www/src/lib/components/docs/light-switch/light-switch.ts @@ -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("modeOsPrefers", false); +/** Store: User Preference Mode */ +export const modeUserPrefers = localStorageStore( + "modeUserPrefers", + undefined +); +/** Store: Current Mode State */ +export const modeCurrent = localStorageStore("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); + }; +} diff --git a/apps/www/src/lib/components/docs/light-switch/local-storage-store.ts b/apps/www/src/lib/components/docs/light-switch/local-storage-store.ts new file mode 100644 index 000000000..7e2a63488 --- /dev/null +++ b/apps/www/src/lib/components/docs/light-switch/local-storage-store.ts @@ -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 = (value: T) => T; +declare type StoreDict = { [key: string]: Writable }; + +/* eslint-disable @typescript-eslint/no-explicit-any */ +const stores: StoreDict = {}; + +interface Serializer { + parse(text: string): T; + stringify(object: T): string; +} + +type StorageType = "local" | "session"; + +interface Options { + serializer?: Serializer; + storage?: StorageType; +} + +function getStorage(type: StorageType) { + return type === "local" ? localStorage : sessionStorage; +} + +export function localStorageStore( + key: string, + initialValue: T, + options?: Options +): Writable { + 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(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) { + const value = updater(get(store)); + + updateStorage(key, value); + set(value); + }, + subscribe + }; + } + + return stores[key]; +} diff --git a/apps/www/src/routes/+layout.svelte b/apps/www/src/routes/+layout.svelte index 5a3eb68c9..e04702ff2 100644 --- a/apps/www/src/routes/+layout.svelte +++ b/apps/www/src/routes/+layout.svelte @@ -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"; + + {@html ``} + +