feat: Unify navigation with hamburger menu across all screen sizes
- Renamed MobileMenu to Menu component (no longer mobile-only) - Added 500ms debounce to prevent accidental double-tap menu toggles - Improved click-outside detection using ref-based containment check - Removed mobile/desktop navigation split - menu now consistent everywhere - All pages now use single hamburger menu on both mobile and desktop - Simplified header layouts across index, sermon, profile, admin, and users pages This provides a cleaner, more consistent UX with the hamburger menu available on all screen sizes. The debounce prevents the menu from closing accidentally when navigating between pages or double-tapping. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -1,9 +1,10 @@
|
||||
<template>
|
||||
<div class="relative">
|
||||
<div class="relative" ref="menuContainer">
|
||||
<!-- Hamburger Button -->
|
||||
<button
|
||||
@click="isOpen = !isOpen"
|
||||
class="p-2 text-gray-700 hover:bg-gray-100 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500"
|
||||
@click="toggleMenu"
|
||||
:disabled="isToggling"
|
||||
class="p-2 text-gray-700 hover:bg-gray-100 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500 disabled:opacity-50"
|
||||
aria-label="Menu"
|
||||
>
|
||||
<svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
@@ -120,6 +121,8 @@ const props = defineProps<{
|
||||
}>()
|
||||
|
||||
const isOpen = ref(false)
|
||||
const isToggling = ref(false)
|
||||
const menuContainer = ref<HTMLElement | null>(null)
|
||||
const route = useRoute()
|
||||
|
||||
const loginRedirectUrl = computed(() => {
|
||||
@@ -127,6 +130,19 @@ const loginRedirectUrl = computed(() => {
|
||||
return `/login?redirect=${encodeURIComponent(redirectPath)}`
|
||||
})
|
||||
|
||||
// Toggle menu with debounce to prevent accidental double-taps
|
||||
const toggleMenu = () => {
|
||||
if (isToggling.value) return
|
||||
|
||||
isToggling.value = true
|
||||
isOpen.value = !isOpen.value
|
||||
|
||||
// Re-enable after 500ms
|
||||
setTimeout(() => {
|
||||
isToggling.value = false
|
||||
}, 500)
|
||||
}
|
||||
|
||||
const handleLogout = async () => {
|
||||
isOpen.value = false
|
||||
await $fetch('/api/auth/logout', { method: 'POST' })
|
||||
@@ -135,17 +151,21 @@ const handleLogout = async () => {
|
||||
|
||||
// Close menu when clicking outside
|
||||
const handleClickOutside = (event: MouseEvent) => {
|
||||
if (!isOpen.value || !menuContainer.value) return
|
||||
|
||||
const target = event.target as HTMLElement
|
||||
if (isOpen.value && !target.closest('.relative')) {
|
||||
// Check if click is outside the menu container
|
||||
if (!menuContainer.value.contains(target)) {
|
||||
isOpen.value = false
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
document.addEventListener('click', handleClickOutside)
|
||||
// Use capture phase to ensure we catch the event before other handlers
|
||||
document.addEventListener('click', handleClickOutside, true)
|
||||
})
|
||||
|
||||
onUnmounted(() => {
|
||||
document.removeEventListener('click', handleClickOutside)
|
||||
document.removeEventListener('click', handleClickOutside, true)
|
||||
})
|
||||
</script>
|
||||
Reference in New Issue
Block a user