Add mega menu to header

This commit is contained in:
marzban-dev
2025-10-24 11:22:48 +03:30
parent b8c15229d5
commit 453f3d1de2
2 changed files with 167 additions and 2 deletions
+29 -2
View File
@@ -3,6 +3,7 @@
import useGetAccount from "~/composables/api/account/useGetAccount";
import { useAuth } from "~/composables/api/auth/useAuth";
import useGetCategories from "~/composables/api/categories/useGetCategories";
import useGetCartOrders from "~/composables/api/orders/useGetCartOrders";
import { NAV_LINKS } from "~/constants";
@@ -11,16 +12,35 @@ import { NAV_LINKS } from "~/constants";
const route = useRoute();
const { token } = useAuth();
const isSideDrawerOpen = ref(false);
const isCategoriesMenuOpen = ref(false);
// queries
const { data: account } = useGetAccount();
const { data: cart } = useGetCartOrders();
const { data: categories } = useGetCategories();
// computed
const isHomePage = computed(() => route.path === "/");
const megaMenuCategories = computed(() => {
if (categories.value) {
return categories.value.map((item) => {
return {
title: item.name,
subitems: item.subcategorys.map((item) => {
return {
title: item.name,
link: item.slug,
};
}),
};
});
}
return [];
});
</script>
<template>
@@ -114,7 +134,9 @@ const isHomePage = computed(() => route.path === "/");
:key="index"
:to="link.path"
class="underline-offset-[10px]"
activeClass="underline"
:activeClass="isCategoriesMenuOpen && link.title !== 'دسته بندی ها' ? 'underline' : ''"
:class="isCategoriesMenuOpen ? 'underline' : ''"
@mouseenter="link.title === 'دسته بندی ها' ? (isCategoriesMenuOpen = true) : undefined"
>
{{ link.title }}
</NuxtLink>
@@ -141,4 +163,9 @@ const isHomePage = computed(() => route.path === "/");
</header>
<SideDrawer v-model="isSideDrawerOpen" />
<MegaMenu
:items="megaMenuCategories"
v-model:isOpen="isCategoriesMenuOpen"
/>
</template>
+138
View File
@@ -0,0 +1,138 @@
<script lang="ts" setup>
// types
type Props = {
isOpen: boolean;
items: {
title: string;
subitems: {
title: string;
link: string;
}[];
}[];
};
// props
const props = defineProps<Props>();
const { items, isOpen } = toRefs(props);
// emits
const emit = defineEmits(["update:isOpen"]);
// states
const selectedItem = ref(0);
const selectTabTimer = ref<NodeJS.Timeout | null>(null);
const isScrollLocked = useScrollLock(window);
const navbarEl = ref<HTMLElement | null>(null);
const { height: navbarHeight } = useElementSize(navbarEl);
// computed
const selectedItemSubItems = computed(() => {
if (items.value.length > 0) {
return items.value[selectedItem.value].subitems;
}
return [];
});
// methods
const menuTabMouseEnter = (index: number) => {
if (selectTabTimer.value) clearTimeout(selectTabTimer.value);
selectTabTimer.value = setTimeout(() => {
selectedItem.value = index;
}, 150);
};
const menuTabMouseLeave = () => {
if (selectTabTimer.value) clearTimeout(selectTabTimer.value);
selectTabTimer.value = null;
};
// watches
watch(
isOpen,
(newValue) => {
isScrollLocked.value = newValue;
if (!newValue) {
selectedItem.value = 0;
}
},
{
immediate: true,
}
);
// lifecycle
onMounted(() => {
navbarEl.value = document.querySelector("#header-navbar");
});
</script>
<template>
<Transition
name="fade"
mode="out-in"
>
<div
v-if="isOpen"
:style="{ marginTop: `${navbarHeight}px` }"
class="fixed right-0 top-0 w-full z-999 bg-black/50 h-svh border-t border-slate-200"
>
<div
class="flex h-200 bg-white"
@mouseleave="$emit('update:isOpen', false)"
>
<div class="bg-slate-100 p-4 flex flex-col overflow-y-auto mask-b-from-90% pb-8">
<button
v-for="(item, index) in items"
:key="item.title"
class="text-black transition-all p-4 rounded-xl cursor-default"
:class="index === selectedItem ? 'bg-blue-100 text-blue-500' : 'bg-slate-100'"
:id="`mega-menu-tab-${index}`"
@mouseenter="menuTabMouseEnter(index)"
@click="selectedItem = index"
@mouseleave="menuTabMouseLeave"
>
{{ item.title }}
</button>
</div>
<div class="overflow-y-auto w-full mask-b-from-90% pb-8">
<Transition
name="fade"
mode="out-in"
:duration="150"
>
<div
:key="selectedItem"
class="grid grid-cols-4 p-4 text-back w-full h-fit"
:style="{
// gridTemplateRows: 'repeat(5, auto)',
// justifyContent: 'center',
}"
>
<NuxtLink
v-for="item in selectedItemSubItems"
:to="'https://google.com'"
class="p-4 whitespace-nowrap h-fit text-slate-500 flex items-center justify-between hover:text-blue-500 hover:-translate-x-1.5 transition-all"
>
<span class="truncate w-[90%]">
{{ item.title }}
</span>
</NuxtLink>
</div>
</Transition>
</div>
</div>
</div>
</Transition>
</template>