From 55d849d7cc14d0ec60c75e8b179472313f8d1e1c Mon Sep 17 00:00:00 2001 From: transcaffeine Date: Thu, 15 Aug 2024 20:19:38 +0200 Subject: [PATCH] feat: allow 'auto' as a colortheme preference --- netbox/project-static/js/setmode.js | 15 ++++- netbox/project-static/src/colorMode.ts | 77 ++++++++++++++------------ netbox/project-static/src/global.d.ts | 2 +- 3 files changed, 57 insertions(+), 37 deletions(-) diff --git a/netbox/project-static/js/setmode.js b/netbox/project-static/js/setmode.js index 9418a9f39..45e9eade7 100644 --- a/netbox/project-static/js/setmode.js +++ b/netbox/project-static/js/setmode.js @@ -1,10 +1,15 @@ /** * Set the color mode on the `` element and in local storage. * - * @param mode {"dark" | "light"} UI color mode. + * @param mode {"dark" | "light" | "auto"} UI color 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); } @@ -23,6 +28,12 @@ function initMode() { if (clientMode !== null) { 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 if (preferDark) { diff --git a/netbox/project-static/src/colorMode.ts b/netbox/project-static/src/colorMode.ts index 453617740..a086d2e32 100644 --- a/netbox/project-static/src/colorMode.ts +++ b/netbox/project-static/src/colorMode.ts @@ -1,6 +1,6 @@ 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. @@ -9,28 +9,41 @@ function isColorMode(value: unknown): value is ColorMode { 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. * - * @param mode `'light'` or `'dark'` + * @param mode `'light'`, `'dark'` or `'auto'` * @returns `true` if the color mode was successfully set, `false` if not. */ -function storeColorMode(mode: ColorMode): void { - return localStorage.setItem(COLOR_MODE_KEY, mode); +function storeColorMode(modePreference: ColorModePreference): void { + return localStorage.setItem(COLOR_MODE_PREFERENCE_KEY, mode); } -function updateElements(targetMode: ColorMode): void { +function updateElements(targetMode: ColorModePreference): void { const body = document.querySelector('body'); - if (body && targetMode == 'dark') { - body.setAttribute('data-bs-theme', 'dark'); - } else if (body) { - body.setAttribute('data-bs-theme', 'light'); + const mediaQuery = window.matchMedia('(prefers-color-scheme: dark)'); + const theme = (targetMode === 'auto') + ? (mediaQuery.matches ? 'dark' : '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('.rack_elevation')) { const svg = elevation.contentDocument?.querySelector('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. */ -export function setColorMode(mode: ColorMode): void { +export function setColorMode(mode: ColorModePreference): void { storeColorMode(mode); updateElements(mode); } @@ -49,11 +62,11 @@ export function setColorMode(mode: ColorMode): void { * Toggle the color mode when a color mode toggle is clicked. */ function handleColorModeToggle(): void { - const currentValue = localStorage.getItem(COLOR_MODE_KEY); - if (currentValue === 'light') { - setColorMode('dark'); - } else if (currentValue === 'dark') { - setColorMode('light'); + const prevValue = localStorage.getItem(COLOR_MODE_PREFERENCE_KEY); + if (isColorMode(prevValue)) { + setColorMode(prevValue === 'light' ? 'dark' : 'light'); + } else if (prevValue === 'auto') { + console.log('Ignoring color mode toggle in auto mode'); } else { console.warn('Unable to determine the current color mode'); } @@ -64,33 +77,29 @@ function handleColorModeToggle(): void { */ function defaultColorMode(): void { // Get the current color mode value from local storage. - const currentValue = localStorage.getItem(COLOR_MODE_KEY) as Nullable; - - if (isTruthy(currentValue)) { - return setColorMode(currentValue); - } - - let preference: ColorModePreference = 'none'; - - // Determine if the user prefers dark or light mode. - for (const mode of ['dark', 'light']) { - if (window.matchMedia(`(prefers-color-scheme: ${mode})`).matches) { - preference = mode as ColorModePreference; - break; - } - } + const currentValue = localStorage.getItem(COLOR_MODE_PREFERENCE_KEY) as Nullable; if (isTruthy(currentValue) && isColorMode(currentValue)) { return setColorMode(currentValue); } + let preference: ColorModePreference = 'none'; + + // Determine if the user prefers dark, light or auto mode. + if (preference !== 'auto') { + for (const mode of ['dark', 'light']) { + if (window.matchMedia(`(prefers-color-scheme: ${mode})`).matches) { + preference = mode as ColorModePreference; + break; + } + } + } switch (preference) { + case 'auto': case 'dark': - return setColorMode('dark'); case 'light': - return setColorMode('light'); + return setColorMode(preference); case 'none': - return setColorMode('light'); default: return setColorMode('light'); } diff --git a/netbox/project-static/src/global.d.ts b/netbox/project-static/src/global.d.ts index d7244339a..1afc4a7b2 100644 --- a/netbox/project-static/src/global.d.ts +++ b/netbox/project-static/src/global.d.ts @@ -78,4 +78,4 @@ declare const messages: string[]; type FormControls = HTMLInputElement | HTMLSelectElement | HTMLTextAreaElement; type ColorMode = 'light' | 'dark'; -type ColorModePreference = ColorMode | 'none'; +type ColorModePreference = ColorMode | 'none' | 'auto';