feat: add 'system' inside the theme toggle (#766)

* feat: add 'system' inside the theme toggle

* chore: generate hugo_stats.json

* fix: missing css

* chore: reorganize code

* feat: menu

* chore: simplify

* chore: some i18n

* review

* fix: remove replace
This commit is contained in:
Ludovic Fernandez
2025-08-20 00:26:32 +02:00
committed by GitHub
parent 363b1e50ff
commit 18a9335d4b
12 changed files with 188 additions and 79 deletions

View File

@@ -3,49 +3,116 @@
const defaultTheme = '{{ site.Params.theme.default | default `system`}}'
const themeToggleButtons = document.querySelectorAll(".hextra-theme-toggle");
const themeToggleOptions = document.querySelectorAll(".hextra-theme-toggle-options p");
// Change the icons of the buttons based on previous settings or system theme
if (
localStorage.getItem("color-theme") === "dark" ||
(!("color-theme" in localStorage) &&
((window.matchMedia("(prefers-color-scheme: dark)").matches && defaultTheme === "system") || defaultTheme === "dark"))
) {
themeToggleButtons.forEach((el) => el.dataset.theme = "dark");
} else {
themeToggleButtons.forEach((el) => el.dataset.theme = "light");
function setSystemTheme() {
const prefersColorScheme = window.matchMedia("(prefers-color-scheme: dark)").matches ? "dark" : "light";
document.documentElement.classList.remove("dark", "light");
document.documentElement.classList.add(prefersColorScheme);
document.documentElement.style.colorScheme = prefersColorScheme;
}
function applyTheme(theme) {
themeToggleButtons.forEach((btn) => btn.parentElement.dataset.theme = theme );
localStorage.setItem("color-theme", theme);
}
function switchTheme(theme) {
switch (theme) {
case "light":
document.documentElement.classList.remove("dark");
document.documentElement.classList.add(theme);
document.documentElement.style.colorScheme = theme;
applyTheme(theme);
break;
case "dark":
document.documentElement.classList.remove("light");
document.documentElement.classList.add(theme);
document.documentElement.style.colorScheme = theme;
applyTheme(theme);
break;
default:
setSystemTheme();
applyTheme("system");
break;
}
}
const colorTheme = "color-theme" in localStorage ? localStorage.getItem("color-theme") : defaultTheme;
switchTheme(colorTheme);
// Add click event handler to the menu items.
themeToggleOptions.forEach((option) => {
option.addEventListener("click", function (e) {
e.preventDefault();
switchTheme(option.dataset.item);
})
})
// Add click event handler to the buttons
themeToggleButtons.forEach((el) => {
el.addEventListener("click", function () {
if (localStorage.getItem("color-theme")) {
if (localStorage.getItem("color-theme") === "light") {
setDarkTheme();
localStorage.setItem("color-theme", "dark");
} else {
setLightTheme();
localStorage.setItem("color-theme", "light");
}
} else {
if (document.documentElement.classList.contains("dark")) {
setLightTheme();
localStorage.setItem("color-theme", "light");
} else {
setDarkTheme();
localStorage.setItem("color-theme", "dark");
}
themeToggleButtons.forEach((toggler) => {
toggler.addEventListener("click", function (e) {
e.preventDefault();
const optionsElement = toggler.nextElementSibling;
optionsElement.classList.toggle('hx:hidden');
// Calculate the position of a language options element.
const switcherRect = toggler.getBoundingClientRect();
// Must be called before optionsElement.clientWidth.
optionsElement.style.minWidth = `${Math.max(switcherRect.width, 50)}px`;
const isOnTop = toggler.dataset.location === 'top';
const isOnBottom = toggler.dataset.location === 'bottom';
const isOnBottomRight = toggler.dataset.location === 'bottom-right';
const isRTL = document.body.dir === 'rtl'
// Stuck on the left side of the switcher.
let translateX = switcherRect.left;
if (isOnTop && !isRTL || isOnBottom && isRTL || isOnBottomRight && !isRTL) {
// Stuck on the right side of the switcher.
translateX = switcherRect.right - optionsElement.clientWidth;
}
el.dataset.theme = document.documentElement.classList.contains("dark") ? "dark" : "light";
// Stuck on the top of the switcher.
let translateY = switcherRect.top - window.innerHeight - 15;
if (isOnTop) {
// Stuck on the bottom of the switcher.
translateY = switcherRect.top - window.innerHeight + 150;
}
optionsElement.style.transform = `translate3d(${translateX}px, ${translateY}px, 0)`;
});
});
// Dismiss the menu when clicking outside
document.addEventListener('click', (e) => {
if (e.target.closest('.hextra-theme-toggle') === null) {
themeToggleButtons.forEach((toggler) => {
toggler.dataset.state = 'closed';
toggler.nextElementSibling.classList.add('hx:hidden');
});
}
});
// Listen for system theme changes
window.matchMedia("(prefers-color-scheme: dark)").addEventListener("change", (e) => {
if (defaultTheme === "system" && !("color-theme" in localStorage)) {
e.matches ? setDarkTheme() : setLightTheme();
themeToggleButtons.forEach((el) =>
el.dataset.theme = document.documentElement.classList.contains("dark") ? "dark" : "light"
);
window.matchMedia("(prefers-color-scheme: dark)").addEventListener("change", () => {
if (localStorage.getItem("color-theme") === "system") {
setSystemTheme();
}
});
})();