Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Replace booleans with dark | light strings #10

Merged
merged 4 commits into from
Oct 26, 2023
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/shiny-onions-share.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'mode-watcher': patch
---

Change persistent stores to use `dark` | `light` strings instead of booleans
6 changes: 3 additions & 3 deletions src/lib/mode-watcher.svelte
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
<script lang="ts">
import { onMount } from 'svelte';
import { getModeOsPrefers, setInitialClassState, setModeCurrent } from './mode.js';
import { getSystemPrefersMode, setInitialClassState, setCurrentMode } from './mode.js';
ollema marked this conversation as resolved.
Show resolved Hide resolved

onMount(() => {
if (!('modeCurrent' in localStorage)) {
setModeCurrent(getModeOsPrefers());
if (!('mode' in localStorage)) {
setCurrentMode(getSystemPrefersMode());
ollema marked this conversation as resolved.
Show resolved Hide resolved
}
});
</script>
Expand Down
139 changes: 52 additions & 87 deletions src/lib/mode.ts
Original file line number Diff line number Diff line change
@@ -1,145 +1,110 @@
// Modified version of the light switch by: https://skeleton.dev

import { derived, get } from 'svelte/store';
import { persisted } from 'svelte-persisted-store';

/**
* Stores
* ----
* TRUE: Light mode | FALSE: Dark mode
*/

const modeOsPrefers = persisted<boolean>('modeOsPrefers', false);
const modeUserPrefers = persisted<boolean | undefined>('modeUserPrefers', undefined);
const modeCurrent = persisted<boolean>('modeCurrent', false);

/** Derived store with either `"light"` or `"dark"` depending on the current mode */
export const mode = derived(modeCurrent, ($modeCurrent) => ($modeCurrent ? 'light' : 'dark'));
const systemPrefersMode = persisted<'dark' | 'light'>('systemPrefersMode', 'dark');
const userPrefersMode = persisted<'dark' | 'light' | undefined>('userPrefersMode', undefined);
export const mode = persisted<'dark' | 'light'>('mode', 'dark');
ollema marked this conversation as resolved.
Show resolved Hide resolved

/**
* Getters
*/

/** Get the OS preference */
export function getModeOsPrefers(): boolean {
const prefersLightMode = window.matchMedia('(prefers-color-scheme: light)').matches;
modeOsPrefers.set(prefersLightMode);
/** Get the operating system preference */
export function getSystemPrefersMode(): 'dark' | 'light' {
const prefersLightMode = window.matchMedia('(prefers-color-scheme: light)').matches
? 'light'
: 'dark';
systemPrefersMode.set(prefersLightMode);
return prefersLightMode;
}

/** Get the User preference */
function getModeUserPrefers(): boolean | undefined {
return get(modeUserPrefers);
}

/** Get the automatic preference */
export function getModeAutoPrefers(): boolean {
const os = getModeOsPrefers();
const user = getModeUserPrefers();
return user !== undefined ? user : os;
}

/**
* Setters
*/

/** Set the user preference */
function setModeUserPrefers(value: boolean | undefined): void {
modeUserPrefers.set(value);
function setUserPrefersMode(value: 'dark' | 'light' | undefined): void {
userPrefersMode.set(value);
}

/** Set the current mode */
export function setModeCurrent(value: boolean): void {
export function setCurrentMode(value: 'dark' | 'light'): void {
ollema marked this conversation as resolved.
Show resolved Hide resolved
const htmlEl = document.documentElement;
const classDark = 'dark';
if (value === true) {
htmlEl.classList.remove(classDark);

if (value === 'light') {
htmlEl.classList.remove('dark');
htmlEl.style.colorScheme = 'light';
} else {
htmlEl.classList.add(classDark);
htmlEl.classList.add('dark');
htmlEl.style.colorScheme = 'dark';
}
modeCurrent.set(value);

mode.set(value);
ollema marked this conversation as resolved.
Show resolved Hide resolved
}

/**
* Lightswitch Utility
*/

/** Set the visible light/dark mode on page load */
/**
* Set light/dark class based on user/system preference
*
* Should be added to <head> to prevent FOUC
*
* This function needs to be able to be stringified and thus it cannot use other functions
*/
export function setInitialClassState() {
const htmlEl = document.documentElement;

const condLocalStorageUserPrefs = localStorage.getItem('modeUserPrefers') === 'false';
const condLocalStorageUserPrefsExist = !('modeUserPrefers' in localStorage);
const condMatchMedia = window.matchMedia('(prefers-color-scheme: dark)').matches;

if (condLocalStorageUserPrefs || (condLocalStorageUserPrefsExist && condMatchMedia)) {
htmlEl.classList.add('dark');
htmlEl.style.colorScheme = 'dark';
} else {
htmlEl.classList.remove('dark');
htmlEl.style.colorScheme = 'light';
let userPref: string | null = null;
try {
userPref = JSON.parse(localStorage.getItem('userPrefersMode') || 'null');
} catch {
// ignore JSON parsing errors
}
}

/**
* Auto Mode Watcher
*/
const systemPref = window.matchMedia('(prefers-color-scheme: light)').matches ? 'light' : 'dark';

/** Automatically set the visible light/dark updates on change */
export function autoModeWatcher(): void {
const mql = window.matchMedia('(prefers-color-scheme: dark)');
function setMode(value: boolean) {
const htmlEl = document.documentElement;
const classDark = 'dark';
if (value === true) {
htmlEl.classList.remove(classDark);
htmlEl.style.colorScheme = 'light';
} else {
htmlEl.classList.add(classDark);
htmlEl.style.colorScheme = 'dark';
}
if (userPref === 'light' || (userPref === null && systemPref === 'light')) {
htmlEl.classList.remove('dark');
htmlEl.style.colorScheme = 'light';
} else {
htmlEl.classList.add('dark');
htmlEl.style.colorScheme = 'dark';
}
setMode(mql.matches);
mql.onchange = () => {
setMode(mql.matches);
};
}

/**
* Toggle between light and dark mode
*/
/** Toggle between light and dark mode */
export function toggleMode(): void {
modeCurrent.update((curr) => {
const next = !curr;
setModeUserPrefers(next);
setModeCurrent(next);
mode.update((curr) => {
ollema marked this conversation as resolved.
Show resolved Hide resolved
const next = curr === 'dark' ? 'light' : 'dark';
setUserPrefersMode(next);
setCurrentMode(next);
ollema marked this conversation as resolved.
Show resolved Hide resolved
return next;
});
}

/**
* Set the mode to light or dark
*/
export function setMode(mode: 'light' | 'dark'): void {
modeCurrent.update((curr) => {
const next = mode === 'light';
/** Set the mode to light or dark */
export function setMode(next: 'dark' | 'light'): void {
mode.update((curr) => {
if (curr === next) return curr;
setModeUserPrefers(next);
setModeCurrent(next);
setUserPrefersMode(next);
setCurrentMode(next);
ollema marked this conversation as resolved.
Show resolved Hide resolved
return next;
});
}

/**
* Reset the mode to OS preference
*/
/** Reset the mode to operating system preference */
export function resetMode(): void {
modeCurrent.update(() => {
setModeUserPrefers(undefined);
const next = getModeOsPrefers();
setModeCurrent(next);
mode.update(() => {
ollema marked this conversation as resolved.
Show resolved Hide resolved
setUserPrefersMode(undefined);
const next = getSystemPrefersMode();
setCurrentMode(next);
ollema marked this conversation as resolved.
Show resolved Hide resolved
return next;
});
}