feat: allow 'auto' as a colortheme preference

This commit is contained in:
transcaffeine 2024-08-15 20:19:38 +02:00
parent 277b7039d8
commit 55d849d7cc
No known key found for this signature in database
GPG Key ID: 03624C433676E465
3 changed files with 57 additions and 37 deletions

View File

@ -1,10 +1,15 @@
/** /**
* Set the color mode on the `<html/>` element and in local storage. * Set the color mode on the `<html/>` element and in local storage.
* *
* @param mode {"dark" | "light"} UI color mode. * @param mode {"dark" | "light" | "auto"} UI color mode.
*/ */
function setMode(mode) { function setMode(mode) {
document.documentElement.setAttribute("data-bs-theme", mode); document.documentElement.setAttribute(
"data-bs-theme",
mode === "auto"
? window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light'
: mode
);
localStorage.setItem("netbox-color-mode", mode); localStorage.setItem("netbox-color-mode", mode);
} }
@ -23,6 +28,12 @@ function initMode() {
if (clientMode !== null) { if (clientMode !== null) {
return setMode(clientMode, false); return setMode(clientMode, false);
} }
// If client wants to auto-switch, attach an onchange listener to the media query
if (clientMode === 'auto') {
window.matchMedia('(prefers-color-scheme: light)').onchange = event => {
document.documentElement.dataset.bsTheme = (e.matches) ? 'light' : 'dark';
}
}
// Fall back to the mode preferred by the browser, if specified // Fall back to the mode preferred by the browser, if specified
if (preferDark) { if (preferDark) {

View File

@ -1,6 +1,6 @@
import { getElements, isTruthy } from './util'; import { getElements, isTruthy } from './util';
const COLOR_MODE_KEY = 'netbox-color-mode'; const COLOR_MODE_PREFERENCE_KEY = 'netbox-color-mode-preference';
/** /**
* Determine if a value is a supported color mode string value. * Determine if a value is a supported color mode string value.
@ -9,28 +9,41 @@ function isColorMode(value: unknown): value is ColorMode {
return value === 'dark' || value === 'light'; return value === 'dark' || value === 'light';
} }
function isDefinedColorModePreference(value: unknown): value is ColorModePreference {
return value === 'auto' || isColorMode(value);
}
/** /**
* Set the color mode to light or dark. * Set the color mode to light or dark.
* *
* @param mode `'light'` or `'dark'` * @param mode `'light'`, `'dark'` or `'auto'`
* @returns `true` if the color mode was successfully set, `false` if not. * @returns `true` if the color mode was successfully set, `false` if not.
*/ */
function storeColorMode(mode: ColorMode): void { function storeColorMode(modePreference: ColorModePreference): void {
return localStorage.setItem(COLOR_MODE_KEY, mode); return localStorage.setItem(COLOR_MODE_PREFERENCE_KEY, mode);
} }
function updateElements(targetMode: ColorMode): void { function updateElements(targetMode: ColorModePreference): void {
const body = document.querySelector('body'); const body = document.querySelector('body');
if (body && targetMode == 'dark') { const mediaQuery = window.matchMedia('(prefers-color-scheme: dark)');
body.setAttribute('data-bs-theme', 'dark'); const theme = (targetMode === 'auto')
} else if (body) { ? (mediaQuery.matches ? 'dark' : 'light'
body.setAttribute('data-bs-theme', 'light'); : (targetMode === 'none' ? targetMode : 'light');
if (body) {
body.setAttribute('data-bs-theme', theme);
}
if (body && targetMode === 'auto') {
mediaQuery.onchange = event => {
body.setAttribute('data-bs-theme', event.matches ? 'dark' : 'light');
}
} }
for (const elevation of getElements<HTMLObjectElement>('.rack_elevation')) { for (const elevation of getElements<HTMLObjectElement>('.rack_elevation')) {
const svg = elevation.contentDocument?.querySelector('svg') ?? null; const svg = elevation.contentDocument?.querySelector('svg') ?? null;
if (svg !== null) { if (svg !== null) {
svg.setAttribute(`data-bs-theme`, targetMode); svg.setAttribute(`data-bs-theme`, theme);
} }
} }
} }
@ -40,7 +53,7 @@ function updateElements(targetMode: ColorMode): void {
* *
* @param mode Target color mode. * @param mode Target color mode.
*/ */
export function setColorMode(mode: ColorMode): void { export function setColorMode(mode: ColorModePreference): void {
storeColorMode(mode); storeColorMode(mode);
updateElements(mode); updateElements(mode);
} }
@ -49,11 +62,11 @@ export function setColorMode(mode: ColorMode): void {
* Toggle the color mode when a color mode toggle is clicked. * Toggle the color mode when a color mode toggle is clicked.
*/ */
function handleColorModeToggle(): void { function handleColorModeToggle(): void {
const currentValue = localStorage.getItem(COLOR_MODE_KEY); const prevValue = localStorage.getItem(COLOR_MODE_PREFERENCE_KEY);
if (currentValue === 'light') { if (isColorMode(prevValue)) {
setColorMode('dark'); setColorMode(prevValue === 'light' ? 'dark' : 'light');
} else if (currentValue === 'dark') { } else if (prevValue === 'auto') {
setColorMode('light'); console.log('Ignoring color mode toggle in auto mode');
} else { } else {
console.warn('Unable to determine the current color mode'); console.warn('Unable to determine the current color mode');
} }
@ -64,33 +77,29 @@ function handleColorModeToggle(): void {
*/ */
function defaultColorMode(): void { function defaultColorMode(): void {
// Get the current color mode value from local storage. // Get the current color mode value from local storage.
const currentValue = localStorage.getItem(COLOR_MODE_KEY) as Nullable<ColorMode>; const currentValue = localStorage.getItem(COLOR_MODE_PREFERENCE_KEY) as Nullable<ColorModePreference>;
if (isTruthy(currentValue)) { if (isTruthy(currentValue) && isColorMode(currentValue)) {
return setColorMode(currentValue); return setColorMode(currentValue);
} }
let preference: ColorModePreference = 'none'; let preference: ColorModePreference = 'none';
// Determine if the user prefers dark or light mode. // Determine if the user prefers dark, light or auto mode.
if (preference !== 'auto') {
for (const mode of ['dark', 'light']) { for (const mode of ['dark', 'light']) {
if (window.matchMedia(`(prefers-color-scheme: ${mode})`).matches) { if (window.matchMedia(`(prefers-color-scheme: ${mode})`).matches) {
preference = mode as ColorModePreference; preference = mode as ColorModePreference;
break; break;
} }
} }
if (isTruthy(currentValue) && isColorMode(currentValue)) {
return setColorMode(currentValue);
} }
switch (preference) { switch (preference) {
case 'auto':
case 'dark': case 'dark':
return setColorMode('dark');
case 'light': case 'light':
return setColorMode('light'); return setColorMode(preference);
case 'none': case 'none':
return setColorMode('light');
default: default:
return setColorMode('light'); return setColorMode('light');
} }

View File

@ -78,4 +78,4 @@ declare const messages: string[];
type FormControls = HTMLInputElement | HTMLSelectElement | HTMLTextAreaElement; type FormControls = HTMLInputElement | HTMLSelectElement | HTMLTextAreaElement;
type ColorMode = 'light' | 'dark'; type ColorMode = 'light' | 'dark';
type ColorModePreference = ColorMode | 'none'; type ColorModePreference = ColorMode | 'none' | 'auto';