feat(header): hide nav on scroll-down, reveal on scroll-up

Standard VueUse recipe: useWindowScroll for the reactive position,
compare against the watcher's previous value for direction. Header
translates off the top when scrolling down and slides back in on
scroll-up; always shown near the top and while the mobile sheet is open.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Padreug 2026-06-14 16:50:07 +02:00
commit a76dce449f

View file

@ -1,5 +1,6 @@
<script setup lang="ts"> <script setup lang="ts">
import { ref, computed, watch } from 'vue' import { ref, computed, watch } from 'vue'
import { useWindowScroll } from '@vueuse/core'
import { useI18n } from 'vue-i18n' import { useI18n } from 'vue-i18n'
import { RouterLink, useRoute } from 'vue-router' import { RouterLink, useRoute } from 'vue-router'
import { cn } from '@/lib/utils' import { cn } from '@/lib/utils'
@ -26,6 +27,18 @@ const route = useRoute()
const mobileOpen = ref(false) const mobileOpen = ref(false)
// Hide the header on scroll-down, reveal on scroll-up the standard VueUse
// recipe: `useWindowScroll` for the reactive position, then compare against
// the watcher's previous value for direction. Always shown near the top of
// the page; the 8px deadband stops jitter from tiny scroll deltas.
const hidden = ref(false)
const { y } = useWindowScroll()
watch(y, (cur, prev) => {
if (cur < 80) hidden.value = false
else if (cur - prev > 8) hidden.value = true
else if (prev - cur > 8) hidden.value = false
})
interface NavItem { interface NavItem {
to: string to: string
label: string label: string
@ -88,7 +101,8 @@ watch(() => route.path, closeMobile)
<template> <template>
<header <header
class="sticky top-0 z-40 border-b border-white/10 bg-background/65 backdrop-blur-xl backdrop-saturate-150" class="sticky top-0 z-40 border-b border-white/10 bg-background/65 backdrop-blur-xl backdrop-saturate-150 transition-transform duration-300 ease-out"
:class="hidden && !mobileOpen ? '-translate-y-full' : 'translate-y-0'"
> >
<div class="mx-auto max-w-7xl px-4 lg:px-6"> <div class="mx-auto max-w-7xl px-4 lg:px-6">
<div class="flex h-16 items-center justify-between gap-4"> <div class="flex h-16 items-center justify-between gap-4">