fix(navbar): menu positions (#804)

* fix: menu positions

* refactor: factorize menu management and handle window resize

* chore: the placement is better with +4 then +10

* chore: the placement is better with -10 than -15
This commit is contained in:
Ludovic Fernandez
2025-08-30 00:09:23 +02:00
committed by GitHub
parent bbffff1f52
commit 82e25c0b0d
4 changed files with 60 additions and 63 deletions

View File

@@ -1,44 +1,18 @@
(function () {
const languageSwitchers = document.querySelectorAll('.hextra-language-switcher');
languageSwitchers.forEach((switcher) => {
switcher.addEventListener('click', (e) => {
e.preventDefault();
switcher.dataset.state = switcher.dataset.state === 'open' ? 'closed' : 'open';
const optionsElement = switcher.nextElementSibling;
optionsElement.classList.toggle('hx:hidden');
// Calculate the position of a language options element.
const switcherRect = switcher.getBoundingClientRect();
// Must be called before optionsElement.clientWidth.
optionsElement.style.minWidth = `${Math.max(switcherRect.width, 50)}px`;
const isOnTop = switcher.dataset.location === 'top';
const isRTL = document.body.dir === 'rtl'
// Stuck on the left side of the switcher.
let translateX = switcherRect.left;
if (isOnTop && !isRTL || !isOnTop && isRTL) {
// Stuck on the right side of the switcher.
translateX = switcherRect.right - optionsElement.clientWidth;
}
// 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 + 180;
}
optionsElement.style.transform = `translate3d(${translateX}px, ${translateY}px, 0)`;
toggleMenu(switcher);
});
});
window.addEventListener("resize", () => languageSwitchers.forEach(resizeMenu))
// Dismiss language switcher when clicking outside
document.addEventListener('click', (e) => {
if (e.target.closest('.hextra-language-switcher') === null) {

View File

@@ -0,0 +1,52 @@
function computeMenuTranslation(switcher, optionsElement) {
// Calculate the position of a language options element.
const switcherRect = switcher.getBoundingClientRect();
// Must be called before optionsElement.clientWidth.
optionsElement.style.minWidth = `${Math.max(switcherRect.width, 50)}px`;
const isOnTop = switcher.dataset.location === 'top';
const isOnBottom = switcher.dataset.location === 'bottom';
const isOnBottomRight = switcher.dataset.location === 'bottom-right';
const isRTL = document.body.dir === 'rtl'
// Stuck on the left side of the switcher.
let x = switcherRect.left;
if (isOnTop && !isRTL || isOnBottom && isRTL || isOnBottomRight && !isRTL) {
// Stuck on the right side of the switcher.
x = switcherRect.right - optionsElement.clientWidth;
}
// Stuck on the top of the switcher.
let y = switcherRect.top - window.innerHeight - 10;
if (isOnTop) {
// Stuck on the bottom of the switcher.
y = switcherRect.top - window.innerHeight + optionsElement.clientHeight + switcher.clientHeight + 4;
}
return { x: x, y: y };
}
function toggleMenu(switcher) {
const optionsElement = switcher.nextElementSibling;
optionsElement.classList.toggle('hx:hidden');
// Calculate the position of a language options element.
const translate = computeMenuTranslation(switcher, optionsElement);
optionsElement.style.transform = `translate3d(${translate.x}px, ${translate.y}px, 0)`;
}
function resizeMenu(switcher) {
const optionsElement = switcher.nextElementSibling;
if (optionsElement.classList.contains('hx:hidden')) return;
// Calculate the position of a language options element.
const translate = computeMenuTranslation(switcher, optionsElement);
optionsElement.style.transform = `translate3d(${translate.x}px, ${translate.y}px, 0)`;
}

View File

@@ -36,41 +36,11 @@
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;
}
// 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)`;
toggleMenu(toggler);
});
});
window.addEventListener("resize", () => themeToggleButtons.forEach(resizeMenu))
// Dismiss the menu when clicking outside
document.addEventListener('click', (e) => {

View File

@@ -1,3 +1,4 @@
{{- $jsSwitcherMenu := resources.Get "js/switcher-menu.js" -}}
{{- $jsTheme := resources.Get "js/theme.js" | resources.ExecuteAsTemplate "theme.js" . -}}
{{- $jsBanner := resources.Get "js/banner.js" | resources.ExecuteAsTemplate "banner.js" . -}}
{{- $jsMenu := resources.Get "js/menu.js" -}}
@@ -11,7 +12,7 @@
{{- $jsTocScroll := resources.Get "js/toc-scroll.js" -}}
{{- $jsFavicon := resources.Get "js/favicon.js" | resources.ExecuteAsTemplate "favicon.js" . -}}
{{- $scripts := slice $jsTheme $jsBanner $jsMenu $jsCodeCopy $jsTabs $jsLang $jsNavMenu $jsFileTree $jsSidebar $jsBackToTop $jsTocScroll $jsFavicon | resources.Concat "js/main.js" -}}
{{- $scripts := slice $jsSwitcherMenu $jsTheme $jsBanner $jsMenu $jsCodeCopy $jsTabs $jsLang $jsNavMenu $jsFileTree $jsSidebar $jsBackToTop $jsTocScroll $jsFavicon | resources.Concat "js/main.js" -}}
{{- if hugo.IsProduction -}}
{{- $scripts = $scripts | minify | fingerprint -}}
{{- end -}}