feat: collapsible header on scroll for catalog pages
All checks were successful
Build Docker Image / build (push) Successful in 4m24s

- Add useScrollCollapse composable to track scroll and collapse state
- Update topnav.vue to show collapsed bar with chevron when scrolled
- Add collapse button (chevron up) to SubNavigation
- Make SubNavigation sticky below MainNavigation
- Update CatalogPage map/searchbar positions based on header state
This commit is contained in:
Ruslan Bakiev
2026-01-14 21:34:06 +07:00
parent 25da16f854
commit 9d0b1a6b15
4 changed files with 136 additions and 8 deletions

View File

@@ -1,6 +1,15 @@
<template>
<nav v-if="items.length > 0" class="bg-base-100 border-b border-base-300">
<nav v-if="items.length > 0" class="sticky top-16 z-30 bg-base-100 border-b border-base-300">
<div class="flex items-center gap-1 py-2 px-4 lg:px-6 overflow-x-auto">
<!-- Collapse button (chevron up) -->
<button
v-if="showCollapseButton"
class="btn btn-ghost btn-xs btn-circle mr-1 flex-shrink-0"
@click="emit('collapse')"
>
<Icon name="lucide:chevron-up" size="16" />
</button>
<NuxtLink
v-for="item in items"
:key="item.path"
@@ -17,6 +26,11 @@
<script setup lang="ts">
const props = defineProps<{
section: 'catalog' | 'orders' | 'seller' | 'settings'
showCollapseButton?: boolean
}>()
const emit = defineEmits<{
collapse: []
}>()
const localePath = useLocalePath()

View File

@@ -13,7 +13,7 @@
<!-- Content -->
<template v-else>
<!-- Search bar slot (sticky third bar - like navigation) -->
<div v-if="$slots.searchBar" class="sticky top-0 z-20 -mx-3 lg:-mx-6 px-3 lg:px-6 py-2 bg-base-100 border-b border-base-300">
<div v-if="$slots.searchBar" class="sticky z-20 -mx-3 lg:-mx-6 px-3 lg:px-6 py-2 bg-base-100 border-b border-base-300" :class="searchBarTopClass">
<slot name="searchBar" :displayed-count="displayItems.length" :total-count="totalCount" />
</div>
@@ -213,13 +213,37 @@ const props = withDefaults(defineProps<{
totalCount: 0
})
// Map positioning - dynamic based on search bar presence
// Without searchBar: MainNav (4rem) + SubNav (3rem) + padding (1rem) = 8rem
// With searchBar: 8rem + SearchBar (3rem) = 11rem
// Inject header collapsed state from layout
const headerCollapsed = inject<Ref<boolean>>('headerCollapsed', ref(false))
// Map positioning - dynamic based on search bar presence and header collapsed state
// Expanded: MainNav (4rem) + SubNav (3rem) = 7rem, with SearchBar = 10rem
// Collapsed: CollapsedBar (2rem), with SearchBar = 5rem
const slots = useSlots()
const hasSearchBar = computed(() => !!slots.searchBar)
const mapTopClass = computed(() => hasSearchBar.value ? 'top-[11rem]' : 'top-32')
const mapHeightClass = computed(() => hasSearchBar.value ? 'h-[calc(100vh-12rem)]' : 'h-[calc(100vh-9rem)]')
// SearchBar position: below header (sticky)
const searchBarTopClass = computed(() => {
if (headerCollapsed.value) {
return 'top-8' // 2rem collapsed bar
}
return 'top-[7rem]' // 4rem MainNav + 3rem SubNav
})
// Map position: below header + searchbar (fixed)
const mapTopClass = computed(() => {
if (headerCollapsed.value) {
return hasSearchBar.value ? 'top-[5rem]' : 'top-8' // collapsed bar + searchbar
}
return hasSearchBar.value ? 'top-[10rem]' : 'top-[7rem]' // full header + searchbar
})
const mapHeightClass = computed(() => {
if (headerCollapsed.value) {
return hasSearchBar.value ? 'h-[calc(100vh-6rem)]' : 'h-[calc(100vh-3rem)]'
}
return hasSearchBar.value ? 'h-[calc(100vh-11rem)]' : 'h-[calc(100vh-8rem)]'
})
const emit = defineEmits<{
'select': [item: MapItem]