166 lines
3.9 KiB
Vue
166 lines
3.9 KiB
Vue
<script setup lang="ts">
|
|
import { ref, onMounted, onUnmounted } from 'vue'
|
|
import { useRouter, useRoute } from 'vue-router'
|
|
import { useI18n } from 'vue-i18n'
|
|
|
|
const router = useRouter()
|
|
const route = useRoute()
|
|
const { t } = useI18n()
|
|
|
|
const navItems = [
|
|
{ name: 'map', path: '/map', icon: 'map' },
|
|
{ name: 'schedules', path: '/schedules', icon: 'schedule' },
|
|
{ name: 'discover', path: '/discover', icon: 'explore' },
|
|
{ name: 'taxi', path: '/transporte', icon: 'directions_bus' }
|
|
]
|
|
|
|
const isNavigating = ref(false)
|
|
|
|
const navigateTo = async (path: string) => {
|
|
// Prevent rapid multiple navigations (debounce guard)
|
|
if (isNavigating.value) return
|
|
if (route.path === path) return
|
|
|
|
try {
|
|
isNavigating.value = true
|
|
await router.push(path)
|
|
} catch (e: any) {
|
|
if (e?.name !== 'NavigationDuplicated') {
|
|
console.error('SIBU | Error de navegación en el menú inferior:', e)
|
|
}
|
|
} finally {
|
|
// Add a small delay to prevent rapid double-taps
|
|
setTimeout(() => { isNavigating.value = false }, 300)
|
|
}
|
|
}
|
|
|
|
const isActive = (path: string) => {
|
|
return route.path === path || route.path.startsWith(path + '/')
|
|
}
|
|
|
|
// Scroll detection logic
|
|
const isVisible = ref(true)
|
|
let lastScrollPosition = 0
|
|
|
|
const handleScroll = () => {
|
|
const currentScrollPosition = window.pageYOffset || document.documentElement.scrollTop
|
|
if (currentScrollPosition < 0) return // For iOS elastic scroll
|
|
|
|
if (Math.abs(currentScrollPosition - lastScrollPosition) < 10) return
|
|
|
|
isVisible.value = currentScrollPosition < lastScrollPosition || currentScrollPosition < 50
|
|
lastScrollPosition = currentScrollPosition
|
|
}
|
|
|
|
onMounted(() => {
|
|
window.addEventListener('scroll', handleScroll)
|
|
})
|
|
|
|
onUnmounted(() => {
|
|
window.removeEventListener('scroll', handleScroll)
|
|
})
|
|
</script>
|
|
|
|
<template>
|
|
<nav class="bottom-nav" :class="{ 'nav-hidden': !isVisible }">
|
|
<div
|
|
v-for="item in navItems"
|
|
:key="item.name"
|
|
class="nav-item"
|
|
:class="{ active: isActive(item.path), 'opacity-50 pointer-events-none': isNavigating }"
|
|
@click.prevent="navigateTo(item.path)"
|
|
@touchend.prevent="navigateTo(item.path)"
|
|
>
|
|
<span class="material-icons">{{ item.icon }}</span>
|
|
<span class="nav-label">{{ t('navigation.' + item.name) }}</span>
|
|
</div>
|
|
</nav>
|
|
</template>
|
|
|
|
<style scoped>
|
|
.bottom-nav {
|
|
position: fixed;
|
|
bottom: 0;
|
|
left: 0;
|
|
right: 0;
|
|
height: calc(70px + var(--safe-area-bottom));
|
|
background: var(--header-bg);
|
|
backdrop-filter: blur(20px);
|
|
-webkit-backdrop-filter: blur(20px);
|
|
border-top: 1px solid var(--border-color);
|
|
display: flex;
|
|
justify-content: space-around;
|
|
align-items: center;
|
|
padding-bottom: var(--safe-area-bottom);
|
|
z-index: 1000;
|
|
box-shadow: 0 -10px 30px rgba(0,0,0,0.3);
|
|
transition: transform 0.4s cubic-bezier(0.4, 0, 0.2, 1);
|
|
will-change: transform;
|
|
}
|
|
|
|
.nav-hidden {
|
|
transform: translateY(100%);
|
|
}
|
|
|
|
.nav-item {
|
|
display: flex;
|
|
flex-direction: column;
|
|
align-items: center;
|
|
gap: 4px;
|
|
color: var(--text-secondary);
|
|
cursor: pointer;
|
|
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
|
padding: 8px 12px;
|
|
border-radius: 16px;
|
|
flex: 1;
|
|
}
|
|
|
|
.nav-item:hover {
|
|
background: var(--hover-bg);
|
|
}
|
|
|
|
.nav-item.active {
|
|
color: var(--active-color);
|
|
transform: translateY(-4px);
|
|
}
|
|
|
|
.material-icons {
|
|
font-size: 26px;
|
|
transition: transform 0.3s;
|
|
}
|
|
|
|
.nav-item.active .material-icons {
|
|
transform: scale(1.1);
|
|
}
|
|
|
|
.nav-label {
|
|
font-size: 0.75rem;
|
|
font-weight: 700;
|
|
letter-spacing: -0.01em;
|
|
}
|
|
|
|
@media (min-width: 900px) {
|
|
.bottom-nav {
|
|
left: 50%;
|
|
right: auto;
|
|
width: 600px;
|
|
bottom: 24px;
|
|
border-radius: 24px;
|
|
border: 1px solid var(--border-color);
|
|
box-shadow: 0 20px 50px rgba(0,0,0,0.4);
|
|
height: 80px;
|
|
padding: 0 20px;
|
|
/* En desktop no la ocultamos para mantener la UX de cursor */
|
|
transform: translateX(-50%);
|
|
}
|
|
|
|
.nav-hidden {
|
|
transform: translate(-50%, 150%);
|
|
}
|
|
|
|
.nav-item {
|
|
border-radius: 12px;
|
|
}
|
|
}
|
|
</style>
|