simple-nextjs-darkmode
Version:
Simple, automatic dark mode for Next.Js
131 lines (130 loc) • 4.9 kB
JavaScript
;
"use client";
Object.defineProperty(exports, "__esModule", { value: true });
exports.getClientDarkMode = getClientDarkMode;
exports.getClientPreference = getClientPreference;
exports.updateClientPreference = updateClientPreference;
exports.DarkModeManager = DarkModeManager;
const cookies_next_1 = require("cookies-next");
const react_1 = require("react");
const constants_1 = require("./constants");
/**
* Get the client's current dark mode state
*
* ***WARNING***: Only call this function from the client
*
* @returns If dark mode is enabled
*/
function getClientDarkMode() {
if (typeof window === "undefined") {
console.warn("getClientDarkMode should only be called on the client");
}
return (0, cookies_next_1.getCookie)(constants_1.DARK_MODE_COOKIE) === "true";
}
/**
* Get the client's current dark mode preference
*
* ***WARNING***: Only call this function from the client
*
* @returns The dark mode preference
*/
function getClientPreference() {
if (typeof window === "undefined") {
console.warn("getClientPreference should only be called on the client");
}
const cookie = (0, cookies_next_1.getCookie)(constants_1.PREFERENCE_COOKIE);
if (cookie === "dark")
return "dark";
if (cookie === "light")
return "light";
return "system";
}
/**
* Change the user's dark mode preference
*
* ***WARNING***: Only call this function from the client
*
* @param preference The user preference
* @param darkModeClass A custom class that is used for dark mode
*/
function updateClientPreference(preference) {
if (typeof window === "undefined") {
console.warn("updateClientPreference can only be called on the client");
return;
}
(0, cookies_next_1.setCookie)(constants_1.PREFERENCE_COOKIE, preference);
const clientPreference = getClientPreference();
setClientDarkMode(clientPreference === "system"
? getSystemPreferenceQuery().matches
: clientPreference === "dark");
}
/**
* Place this component somewhere in your app to automatically update dark mode
*
* If this component is not rendered, it will not be able to update dark mode, so it's recommended to place in the root layout
*
* ***Note***: This component does not actually render anything, it only performs the functionality
*
* @param alwaysUpdate Set to true if you are not setting dark mode during server-side rendering. Forces dark mode to be updated when the component renders
* @param darkModeClass A custom class that is used for dark mode
*/
function DarkModeManager({ alwaysUpdate, }) {
// VERY effectful code, run after render to prevent hydration errors
(0, react_1.useEffect)(() => {
// Client ONLY
if (typeof window === "undefined")
return;
const systemPreferenceQuery = getSystemPreferenceQuery();
const clientPreference = getClientPreference();
// Register the system preference change listener
systemPreferenceQuery.addEventListener("change", systemPreferenceUpdate);
const targetDarkMode = clientPreference === "system"
? systemPreferenceQuery.matches
: clientPreference === "dark";
const currentDarkMode = getClientDarkMode();
// Update the cookie if the user's preference has changed
if (alwaysUpdate || targetDarkMode !== currentDarkMode) {
setClientDarkMode(targetDarkMode);
}
// Remove the event listener before unmounting / rerendering
return () => systemPreferenceQuery.removeEventListener("change", systemPreferenceUpdate);
}, [alwaysUpdate]);
return null;
}
/// Internals
/**
* Internal
*/
function systemPreferenceUpdate(e) {
// Only update if the user hasn't set a preference
if (getClientPreference() !== "system")
return;
setClientDarkMode(e.matches);
}
/**
* Internal
*/
function getSystemPreferenceQuery() {
return window.matchMedia("(prefers-color-scheme: dark)");
}
/**
* Internal
*/
function setClientDarkMode(enabled) {
var _a;
(0, cookies_next_1.setCookie)(constants_1.DARK_MODE_COOKIE, enabled.toString());
// Get elements to update
const targets = document.querySelectorAll(constants_1.TARGET_SELECTOR);
if (targets.length === 0) {
console.warn("No target found for dark mode! Make sure you have a", constants_1.TARGET_SELECTOR, "attribute an element.");
}
// Toggle the class on each target
for (const target of targets) {
if (!(target instanceof HTMLElement)) {
console.warn(constants_1.TARGET_SELECTOR, "should only be applied to HTML elements");
continue;
}
const className = (_a = target.dataset[constants_1.TARGET_CLASS_NAME_ATTRIBUTE]) !== null && _a !== void 0 ? _a : constants_1.DEFAULT_CLASS_NAME;
target.classList.toggle(className, enabled);
}
}