]> danix's work - danix.xyz-2.git/commitdiff
fix: rewrite hamburger menu using Alpine.js event dispatcher
authorDanilo M. <redacted>
Thu, 16 Apr 2026 13:57:37 +0000 (15:57 +0200)
committerDanilo M. <redacted>
Thu, 16 Apr 2026 13:57:37 +0000 (15:57 +0200)
- Replace vanilla JS click handlers with Alpine.js @toggle-menu event
- Hamburger button dispatches toggle-menu event on click
- Menu overlay listens to toggle-menu and toggles menuOpen state
- Alpine controls visibility via :class bindings (opacity/invisible)
- Alpine controls panel slide via :class bindings (translate-x)
- All menu interactions (close button, links, ESC, backdrop) use Alpine
- Remove complex vanilla JS that wasn't working reliably
- Rebuild CSS: main.min.css updated

Co-Authored-By: Claude Haiku 4.5 <redacted>
themes/danix-xyz-hacker/assets/css/main.min.css
themes/danix-xyz-hacker/layouts/partials/hamburger-menu.html
themes/danix-xyz-hacker/layouts/partials/header.html

index 36c66d10b5eafba334d459060bdb4b2dc537e991..ab1f4bb915a956d9f6c84d379a3369111d4c077a 100644 (file)
@@ -1487,23 +1487,6 @@ article.border.border-border\/30.rounded-lg.card.group.bg-bg {
 
 /* Mobile menu overlay */
 
-.menu-overlay {
-  visibility: hidden;
-  position: fixed;
-  inset: 0px;
-  z-index: 40;
-  background-color: var(--bg);
-  opacity: 0;
-  transition-property: all;
-  transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
-  transition-duration: 300ms;
-}
-
-article.border.border-border\/30.rounded-lg.overflow-hidden.group.menu-overlay {
-  border-color: var(--border);
-  box-shadow: 0 0 20px var(--accent-glow);
-}
-
 .menu-overlay.active {
   visibility: visible;
   opacity: 1;
@@ -1817,6 +1800,11 @@ article.border.border-border\/30.rounded-lg.overflow-hidden.group.menu-overlay {
   flex-shrink: 0;
 }
 
+.translate-x-0 {
+  --tw-translate-x: 0px;
+  transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));
+}
+
 .translate-x-full {
   --tw-translate-x: 100%;
   transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));
@@ -2163,6 +2151,10 @@ article.border.border-border\/30.rounded-lg.overflow-hidden.group.menu-overlay {
   opacity: 0;
 }
 
+.opacity-100 {
+  opacity: 1;
+}
+
 .opacity-5 {
   opacity: 0.05;
 }
index 4d02d9c853c222830f23004e225973bd49f0238c..02e6d0dee1bbf0311d9458672ffea075d20ba9b4 100644 (file)
@@ -1,17 +1,22 @@
+<!-- Mobile menu overlay (Alpine.js controlled) -->
 <div
-  id="menu-overlay"
-  class="fixed inset-0 bg-black/50 backdrop-blur opacity-0 invisible transition-all duration-300 z-40"
-  aria-hidden="true"
+  x-data="{ menuOpen: false }"
+  @toggle-menu.window="menuOpen = !menuOpen"
+  @keydown.escape.window="menuOpen = false"
+  :class="{ 'opacity-0 invisible': !menuOpen, 'opacity-100 visible': menuOpen }"
+  class="fixed inset-0 bg-black/50 backdrop-blur transition-all duration-300 z-40"
+  :aria-hidden="!menuOpen"
+  @click="if ($event.target === $el) menuOpen = false"
 >
   <div
-    id="hamburger-menu"
-    class="fixed top-0 right-0 h-screen w-full max-w-sm bg-bg border-l border-border overflow-y-auto transform translate-x-full transition-transform duration-300 z-50"
+    class="fixed top-0 right-0 h-screen w-full max-w-sm bg-bg border-l border-border overflow-y-auto transform transition-transform duration-300 z-50"
+    :class="{ 'translate-x-full': !menuOpen, 'translate-x-0': menuOpen }"
   >
     <!-- Close button -->
     <div class="flex items-center justify-between p-6 border-b border-border">
       <span class="font-bold text-lg text-accent font-oxanium">Menu</span>
       <button
-        id="menu-close"
+        @click="menuOpen = false"
         aria-label="{{ i18n "closeMenu" }}"
         class="p-2 hover:bg-surface rounded transition-colors"
       >
@@ -24,6 +29,7 @@
       {{ range .Site.Menus.main }}
         <a
           href="{{ .URL }}"
+          @click="menuOpen = false"
           class="block py-4 text-lg font-medium hover:text-accent transition-colors border-b border-border/30"
         >
           {{ i18n .Name }}
@@ -54,6 +60,7 @@
           {{ end }}
           <a
             href="{{ $url }}"
+            @click="menuOpen = false"
             class="flex-1 py-2 px-3 text-center rounded transition-colors {{ if $current }}bg-accent text-white{{ else }}bg-surface hover:bg-surface/80{{ end }}"
           >
             {{ $langName }}
 </div>
 
 <script>
-  function initializeHamburgerMenu() {
+  // Make menuOpen accessible from the menu toggle button
+  document.addEventListener('DOMContentLoaded', () => {
     const menuToggle = document.getElementById('menu-toggle');
-    const menuClose = document.getElementById('menu-close');
-    const menuOverlay = document.getElementById('menu-overlay');
-    const hamburgerMenu = document.getElementById('hamburger-menu');
-
-    if (!menuToggle || !menuOverlay || !hamburgerMenu) {
-      console.warn('Hamburger menu elements not found', {
-        menuToggle: !!menuToggle,
-        menuOverlay: !!menuOverlay,
-        hamburgerMenu: !!hamburgerMenu
-      });
-      return;
-    }
-
-    const openMenu = () => {
-      menuOverlay.classList.remove('opacity-0', 'invisible');
-      hamburgerMenu.classList.remove('translate-x-full');
-      menuOverlay.setAttribute('aria-hidden', 'false');
-      document.body.style.overflow = 'hidden';
-      menuToggle.setAttribute('aria-expanded', 'true');
-    };
-
-    const closeMenu = () => {
-      menuOverlay.classList.add('opacity-0', 'invisible');
-      hamburgerMenu.classList.add('translate-x-full');
-      menuOverlay.setAttribute('aria-hidden', 'true');
-      document.body.style.overflow = '';
-      menuToggle.setAttribute('aria-expanded', 'false');
-    };
-
-    // Toggle button click
-    menuToggle.addEventListener('click', (e) => {
-      e.preventDefault();
-      e.stopPropagation();
-      if (menuOverlay.classList.contains('opacity-0')) {
-        openMenu();
-      } else {
-        closeMenu();
-      }
-    });
-
-    // Close button click
-    if (menuClose) {
-      menuClose.addEventListener('click', (e) => {
+    if (menuToggle && window.__alpineInstances) {
+      menuToggle.addEventListener('click', (e) => {
         e.preventDefault();
-        closeMenu();
+        // Find the Alpine component and toggle it
+        const overlay = document.querySelector('[x-data*="menuOpen"]');
+        if (overlay && overlay.__x) {
+          overlay.__x.$data.menuOpen = !overlay.__x.$data.menuOpen;
+        }
       });
     }
-
-    // Close on ESC key
-    document.addEventListener('keydown', (e) => {
-      if (e.key === 'Escape' && !menuOverlay.classList.contains('opacity-0')) {
-        closeMenu();
-      }
-    });
-
-    // Close when clicking menu links
-    const menuLinks = hamburgerMenu.querySelectorAll('a');
-    menuLinks.forEach(link => {
-      link.addEventListener('click', (e) => {
-        closeMenu();
-      });
-    });
-
-    // Close when clicking the overlay (but not the menu panel)
-    menuOverlay.addEventListener('click', (e) => {
-      if (e.target === menuOverlay) {
-        closeMenu();
-      }
-    });
-  }
-
-  // Run when DOM is ready
-  if (document.readyState === 'loading') {
-    document.addEventListener('DOMContentLoaded', initializeHamburgerMenu);
-  } else {
-    initializeHamburgerMenu();
-  }
+  });
 </script>
index cd77032fc129447dba3870d653a5accdb6e90422..8722e814be424ec76e141dfb2bdf3fc6c31c6568 100644 (file)
@@ -57,9 +57,9 @@
 
       <!-- Hamburger menu button (mobile only) -->
       <button
-        id="menu-toggle"
+        x-data
+        @click="$dispatch('toggle-menu')"
         aria-label="{{ i18n "toggleMenu" }}"
-        aria-expanded="false"
         aria-controls="hamburger-menu"
         class="md:hidden p-2 rounded hover:bg-surface transition-colors"
       >