This commit is contained in:
129
src/components/NavigationBar.vue
Normal file
129
src/components/NavigationBar.vue
Normal file
@@ -0,0 +1,129 @@
|
||||
<script setup lang="ts">
|
||||
import { ref, computed, onMounted, onUnmounted } from "vue";
|
||||
import { config } from "../config";
|
||||
import type { Component } from "vue";
|
||||
|
||||
const props = defineProps<{
|
||||
currentPath: string;
|
||||
}>();
|
||||
|
||||
const isVisible = ref(true);
|
||||
const isScrolling = ref(false);
|
||||
const currentClientPath = ref(props.currentPath);
|
||||
|
||||
// Filter out disabled navigation items
|
||||
const enabledNavigationItems = config.navigationItems.filter(
|
||||
(item) => item.enabled !== false,
|
||||
);
|
||||
|
||||
const activePath = computed(() => currentClientPath.value);
|
||||
|
||||
// Normalize path
|
||||
const normalizedPath = computed(() => {
|
||||
const path = activePath.value;
|
||||
return path.endsWith("/") && path.length > 1 ? path.slice(0, -1) : path;
|
||||
});
|
||||
|
||||
const updatePath = () => {
|
||||
if (typeof window !== "undefined") {
|
||||
currentClientPath.value = window.location.pathname;
|
||||
}
|
||||
};
|
||||
|
||||
// Scroll handling
|
||||
let lastScrollY = 0;
|
||||
let ticking = false;
|
||||
let scrollTimer: ReturnType<typeof setTimeout> | undefined;
|
||||
|
||||
const updateScroll = () => {
|
||||
const currentScrollY = window.scrollY;
|
||||
|
||||
// Always show near top
|
||||
if (currentScrollY < 50) {
|
||||
isVisible.value = true;
|
||||
} else {
|
||||
// Show if scrolling up, hide if scrolling down
|
||||
// Only update if position actually changed to avoid jitter
|
||||
if (Math.abs(currentScrollY - lastScrollY) > 0) {
|
||||
isVisible.value = currentScrollY < lastScrollY;
|
||||
}
|
||||
}
|
||||
|
||||
lastScrollY = currentScrollY;
|
||||
ticking = false;
|
||||
};
|
||||
|
||||
const onScroll = () => {
|
||||
isScrolling.value = true;
|
||||
|
||||
if (scrollTimer) clearTimeout(scrollTimer);
|
||||
scrollTimer = setTimeout(() => {
|
||||
isScrolling.value = false;
|
||||
}, 200);
|
||||
|
||||
if (!ticking) {
|
||||
window.requestAnimationFrame(updateScroll);
|
||||
ticking = true;
|
||||
}
|
||||
};
|
||||
|
||||
onMounted(() => {
|
||||
updatePath();
|
||||
lastScrollY = window.scrollY;
|
||||
|
||||
document.addEventListener("astro:page-load", updatePath);
|
||||
document.addEventListener("astro:after-swap", updatePath);
|
||||
window.addEventListener("popstate", updatePath);
|
||||
window.addEventListener("scroll", onScroll, { passive: true });
|
||||
});
|
||||
|
||||
onUnmounted(() => {
|
||||
document.removeEventListener("astro:page-load", updatePath);
|
||||
document.removeEventListener("astro:after-swap", updatePath);
|
||||
window.removeEventListener("popstate", updatePath);
|
||||
window.removeEventListener("scroll", onScroll);
|
||||
if (scrollTimer) clearTimeout(scrollTimer);
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div
|
||||
class="fixed bottom-3 sm:bottom-4 left-1/2 transform -translate-x-1/2 z-20 transition-all duration-300"
|
||||
:class="[
|
||||
isScrolling ? 'opacity-30' : 'opacity-100',
|
||||
isVisible ? 'translate-y-0' : 'translate-y-20',
|
||||
]"
|
||||
>
|
||||
<div class="overflow-visible">
|
||||
<ul
|
||||
class="menu menu-horizontal bg-base-200 rounded-box border-1 border-solid border-primary p-1.5 sm:p-2 flex flex-nowrap whitespace-nowrap"
|
||||
>
|
||||
<li
|
||||
v-for="item in enabledNavigationItems"
|
||||
:key="item.id"
|
||||
class="mx-0.5 sm:mx-1"
|
||||
>
|
||||
<a
|
||||
:href="item.path"
|
||||
class="tooltip tooltip-top min-h-[44px] min-w-[44px] inline-flex items-center justify-center"
|
||||
:class="{
|
||||
'menu-active': item.isActive
|
||||
? item.isActive(normalizedPath)
|
||||
: normalizedPath === item.path,
|
||||
}"
|
||||
:aria-label="item.tooltip"
|
||||
:data-tip="item.tooltip"
|
||||
data-astro-prefetch="hover"
|
||||
>
|
||||
<component
|
||||
:is="item.icon as Component"
|
||||
:size="18"
|
||||
class="sm:w-5 sm:h-5"
|
||||
/>
|
||||
<span class="sr-only">{{ item.name }}</span>
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
Reference in New Issue
Block a user