Add mega menu to header
This commit is contained in:
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
Reference in New Issue
Block a user