diff --git a/src_assets/common/assets/web/Navbar.vue b/src_assets/common/assets/web/Navbar.vue index 9e4e1be6..838c630f 100644 --- a/src_assets/common/assets/web/Navbar.vue +++ b/src_assets/common/assets/web/Navbar.vue @@ -1,60 +1,89 @@ diff --git a/src_assets/common/assets/web/ThemeToggle.vue b/src_assets/common/assets/web/ThemeToggle.vue new file mode 100644 index 00000000..7c34916a --- /dev/null +++ b/src_assets/common/assets/web/ThemeToggle.vue @@ -0,0 +1,46 @@ + + + + + diff --git a/src_assets/common/assets/web/apps.html b/src_assets/common/assets/web/apps.html index 0fd0651a..9a8c65d1 100644 --- a/src_assets/common/assets/web/apps.html +++ b/src_assets/common/assets/web/apps.html @@ -1,5 +1,5 @@ - + <%- header %> @@ -355,10 +355,10 @@ diff --git a/src_assets/common/assets/web/theme.js b/src_assets/common/assets/web/theme.js new file mode 100644 index 00000000..a1f49780 --- /dev/null +++ b/src_assets/common/assets/web/theme.js @@ -0,0 +1,84 @@ +const getStoredTheme = () => localStorage.getItem('theme') +const setStoredTheme = theme => localStorage.setItem('theme', theme) + +export const getPreferredTheme = () => { + const storedTheme = getStoredTheme() + if (storedTheme) { + return storedTheme + } + + return window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light' +} + +const setTheme = theme => { + if (theme === 'auto') { + document.documentElement.setAttribute( + 'data-bs-theme', + (window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light') + ) + } else { + document.documentElement.setAttribute('data-bs-theme', theme) + } +} + +export const showActiveTheme = (theme, focus = false) => { + const themeSwitcher = document.querySelector('#bd-theme') + + if (!themeSwitcher) { + return + } + + const themeSwitcherText = document.querySelector('#bd-theme-text') + const activeThemeIcon = document.querySelector('.theme-icon-active i') + const btnToActive = document.querySelector(`[data-bs-theme-value="${theme}"]`) + const classListOfActiveBtn = btnToActive.querySelector('i').classList + + document.querySelectorAll('[data-bs-theme-value]').forEach(element => { + element.classList.remove('active') + element.setAttribute('aria-pressed', 'false') + }) + + btnToActive.classList.add('active') + btnToActive.setAttribute('aria-pressed', 'true') + activeThemeIcon.classList.remove(...activeThemeIcon.classList.values()) + activeThemeIcon.classList.add(...classListOfActiveBtn) + const themeSwitcherLabel = `${themeSwitcherText.textContent} (${btnToActive.textContent.trim()})` + themeSwitcher.setAttribute('aria-label', themeSwitcherLabel) + + if (focus) { + themeSwitcher.focus() + } +} + +export function setupThemeToggleListener() { + document.querySelectorAll('[data-bs-theme-value]') + .forEach(toggle => { + toggle.addEventListener('click', () => { + const theme = toggle.getAttribute('data-bs-theme-value') + setStoredTheme(theme) + setTheme(theme) + showActiveTheme(theme, true) + }) + }) + + showActiveTheme(getPreferredTheme(), false) +} + +export function loadAutoTheme() { + (() => { + 'use strict' + + setTheme(getPreferredTheme()) + + window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', () => { + const storedTheme = getStoredTheme() + if (storedTheme !== 'light' && storedTheme !== 'dark') { + setTheme(getPreferredTheme()) + } + }) + + window.addEventListener('DOMContentLoaded', () => { + showActiveTheme(getPreferredTheme()) + }) + })() +} diff --git a/src_assets/common/assets/web/troubleshooting.html b/src_assets/common/assets/web/troubleshooting.html index 00497741..0adc1654 100644 --- a/src_assets/common/assets/web/troubleshooting.html +++ b/src_assets/common/assets/web/troubleshooting.html @@ -1,5 +1,5 @@ - + <%- header %> diff --git a/src_assets/common/assets/web/welcome.html b/src_assets/common/assets/web/welcome.html index cf1e74ba..18c67b2e 100644 --- a/src_assets/common/assets/web/welcome.html +++ b/src_assets/common/assets/web/welcome.html @@ -1,5 +1,5 @@ - + <%- header %>