Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Next: Mega menu for desktop/mobile #5685

Closed
wants to merge 41 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
41 commits
Select commit Hold shift + click to select a range
2e5c4f4
fix: from cr and update customQuery
lukaszjedrasik Mar 18, 2021
73a20f8
chore: removed unnecessary fragment from query
lukaszjedrasik Mar 18, 2021
2c7d4b3
chore: fixes
lukaszjedrasik Mar 19, 2021
f0d28b7
chore: added changelog
lukaszjedrasik Mar 22, 2021
2fbdf93
refactor
lukaszjedrasik Mar 22, 2021
462c06d
chore: update branch
lukaszjedrasik Mar 24, 2021
b810e5c
chore: megaMenu for mobile
lukaszjedrasik Mar 26, 2021
67a2253
fix: dom exception
lukaszjedrasik Mar 26, 2021
5cd82d3
refactor
lukaszjedrasik Mar 29, 2021
8d05e3b
fix: category banner on mobile
lukaszjedrasik Mar 29, 2021
a63f8e9
fix: category banner height
lukaszjedrasik Mar 29, 2021
493eb34
chore: ui fixes
lukaszjedrasik Mar 29, 2021
c7a46f3
chore: added class to loader
lukaszjedrasik Mar 29, 2021
9f65f3d
fix: naming, banners, transition
lukaszjedrasik Mar 30, 2021
d90aa69
fix: fade naming
lukaszjedrasik Mar 30, 2021
b7afe44
fix: loader
lukaszjedrasik Mar 30, 2021
f2ca1ac
chore: svg instead SfLoader
lukaszjedrasik Mar 30, 2021
5093253
chore: revert svg instead SfLoader
lukaszjedrasik Mar 31, 2021
dce3291
chore: better banners loading
lukaszjedrasik Mar 31, 2021
e2afaa6
chore: adjustment HeaderNav
lukaszjedrasik Mar 31, 2021
c2b5f38
fix: computed instead function
lukaszjedrasik Apr 8, 2021
1636fbd
chore: adjust to next
lukaszjedrasik Apr 9, 2021
7743e23
chore: add function instead copy code
lukaszjedrasik Apr 13, 2021
3147ce9
chore: fixes
lukaszjedrasik Apr 14, 2021
532e2ac
chore: fixes
lukaszjedrasik Apr 14, 2021
a131c61
chore: adjust boilerplate
lukaszjedrasik Apr 14, 2021
aec6269
fix: transition
lukaszjedrasik Apr 14, 2021
f8263d2
chore: adjust to next
lukaszjedrasik Apr 15, 2021
c84bcb9
chore: missing spinner
lukaszjedrasik Apr 23, 2021
7a7a878
chore: update
lukaszjedrasik Apr 23, 2021
babfefe
fix: loader visibility on mobile
lukaszjedrasik Apr 26, 2021
e573905
chore: update changelog
lukaszjedrasik Apr 26, 2021
c3f5160
chore: update changelog
lukaszjedrasik Apr 26, 2021
4872662
chore: duplicates refactor
lukaszjedrasik Apr 26, 2021
43cbe0a
chore: hasChildren as a computed
lukaszjedrasik Apr 26, 2021
447174e
fix: cr suggestions
lukaszjedrasik Apr 29, 2021
4596e11
chore: rename NewCatBanners -> CatBanners
lukaszjedrasik Apr 29, 2021
4bfef21
fix loader position
lukaszjedrasik May 10, 2021
46526dc
fix routing
lukaszjedrasik May 10, 2021
6587eb4
chore: branch update
lukaszjedrasik May 14, 2021
f5678d3
update branch
lukaszjedrasik Jun 24, 2021
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
89 changes: 76 additions & 13 deletions packages/boilerplate/api-client/src/api/getCategory/index.ts
Expand Up @@ -5,70 +5,133 @@ export default async function getCategory(context, params, customQuery?: CustomQ
return Promise.resolve([
{
id: 1,
name: 'New',
slug: 'new',
childCount: 2,
children: [
{
id: 15,
name: 'Women',
slug: 'new-women',
childCount: 2,
children: [
{
id: 16,
name: 'Clothing',
slug: 'new-women-clothing',
childCount: 0,
children: []
},
{
id: 17,
name: 'Shoes',
slug: 'new-women-shoes',
childCount: 0,
children: []
}
]
},
{
id: 11,
name: 'Men',
slug: 'new-men',
childCount: 1,
children: [
{
id: 18,
name: 'Clothing',
slug: 'new-men-clothing',
childCount: 0,
children: []
},
{
id: 19,
name: 'Shoes',
slug: 'new-men-shoes',
childCount: 0,
children: []
}
]
}
]
},
{
id: 2,
name: 'Women',
slug: 'women',
items: [
childCount: 2,
children: [
{
id: 4,
name: 'Women jackets',
slug: 'women-jackets',
items: [
childCount: 2,
children: [
{
id: 9,
name: 'Winter jackets',
slug: 'winter-jackets',
items: []
childCount: 0,
children: []
},
{
id: 10,
name: 'Autumn jackets',
slug: 'autmun-jackets',
items: []
childCount: 0,
children: []
}
]
},
{
id: 5,
name: 'Skirts',
slug: 'skirts',
items: []
childCount: 0,
children: []
}
]
},
{
id: 2,
id: 3,
name: 'Men',
slug: 'men',
items: [
childCount: 1,
children: [
{
id: 6,
name: 'Men T-shirts',
slug: 'men-tshirts',
items: []
childCount: 0,
children: []
}
]
},
{
id: 3,
id: 4,
name: 'Kids',
slug: 'kids',
items: [
childCount: 1,
children: [
{
id: 7,
name: 'Toys',
slug: 'toys',
items: [
childCount: 2,
children: [
{
id: 8,
name: 'Toy Cars',
slug: 'toy-cars',
items: []
childCount: 0,
children: []
},
{
id: 8,
name: 'Dolls',
slug: 'dolls',
items: []
childCount: 0,
children: []
}
]
}
Expand Down
Binary file not shown.
Binary file not shown.
223 changes: 223 additions & 0 deletions packages/commercetools/theme/components/Header/HeaderNav.vue
@@ -0,0 +1,223 @@
<template>
<div>
<SfHeaderNavigation v-if="!isMobile">
<SfHeaderNavigationItem
v-for="category in categories"
:key="category.id"
:label="category.name"
@mouseenter="() => handleMouseEnter(category.slug)"
@mouseleave="() => handleMouseLeave()"
@click="handleMouseLeave()"
:link="localePath(`/c/${category.slug}`)"
v-e2e="category.name === 'Women' ? 'app-header-url_women' : category.name === 'Men' ? 'app-header-url_women' : ''"
>
<SfMegaMenu
is-absolute
:visible="currentCatSlug === category.slug"
:title="category.name"
@close="currentCatSlug = ''"
v-if="hasChildren"
>
<SfMegaMenuColumn
v-for="subCategory in activeCategory[0].children"
:key="subCategory.id"
:title="subCategory.name"
>
<template #title="{ title, changeActive }">
<SfMenuItem
:label="title"
class="sf-mega-menu-column__header"
@click="$router.push(`/c/${currentCatSlug}/${subCategory.slug}`)"
/>
</template>
<SfLoader :loading="subCategoriesLoading">
<SfList v-if="!subCategoriesLoading">
<SfListItem
v-for="subCategoryChild in subCategory.children"
:key="subCategoryChild.id"
>
<SfMenuItem :label="subCategoryChild.name" :link="localePath(`/c/${currentCatSlug}/${subCategoryChild.slug}`)">
<SfLink>
{{ subCategoryChild.name }}
</SfLink>
</SfMenuItem>
</SfListItem>
</SfList>
</SfLoader>
</SfMegaMenuColumn>
<CatBanners v-if="!subCategoriesLoading && hasBanners" />
</SfMegaMenu>
</SfHeaderNavigationItem>
</SfHeaderNavigation>
<transition name="sf-fade" mode="out-in">
<SfMegaMenu
v-if="isMobile && isMobileMenuOpen"
visible
@close="toggleMobileMenu"
class="mobile-menu"
>
<SfMegaMenuColumn
v-for="category in categories"
:key="category.id"
:title="category.name"
>
<template #title="{ title, changeActive }">
<SfMenuItem
:label="title"
class="sf-mega-menu-column__header"
@click="changeActive(title); handleClickCategoryLvl(category.slug, 1)"
/>
</template>
<SfLoader :loading="subCategoriesLoading">
<div v-if="!subCategoriesLoading">
<SfList v-if="hasChildren">
<SfListItem
v-for="subCategory in activeCategory[0].children"
:key="subCategory.id"
>
<SfMenuItem
:label="subCategory.name"
@click.native="handleClickCategoryLvl(subCategory.slug, 2)"
>
<SfLink>
{{ subCategory.name }}
</SfLink>
</SfMenuItem>
</SfListItem>
</SfList>
</div>
</SfLoader>
<CatBanners v-if="!subCategoriesLoading && hasBanners" />
</SfMegaMenuColumn>
</SfMegaMenu>
</transition>
</div>
</template>

<script>
import { SfMegaMenu, SfMenuItem, SfList, SfBanner, SfLoader } from '@storefront-ui/vue';
import { useCategory } from '@vue-storefront/commercetools';
import { useUiState } from '~/composables';
import { onSSR } from '@vue-storefront/core';
import { ref, computed } from '@vue/composition-api';
import debounce from 'lodash.debounce';

export default {
name: 'HeaderNav',
components: {
SfMegaMenu,
SfMenuItem,
SfList,
SfBanner,
SfLoader,
CatBanners: () => import('./CatBanners')
},
props: {
isMobile: {
type: Boolean,
default: false
}
},
setup (_, { emit, root }) {
const { categories, search } = useCategory('menu-categories');
const { categories: subCategories, search: subCategoriesSearch, loading: subCategoriesLoading } = useCategory('menu-subCategories');
const { toggleMobileMenu, isMobileMenuOpen } = useUiState();
const currentCatSlug = ref('');
const activeCategory = ref(null);
const fetchedSubCategories = ref({});
const categoriesWithBanners = ref([
{ slug: 'new' }
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

hardcoded category?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, because we don't have any external source of information about the categories in which banners should be visible. Eventually, we can remove banners. WDYT?

]);

const getCurrentCat = (source, slug) => source.find(src => src.slug === slug);

const hasChildren = computed(() => activeCategory.value && activeCategory.value[0] && activeCategory.value[0].children);
const hasBanners = computed(() => getCurrentCat(categoriesWithBanners.value, currentCatSlug.value));

const fetchSubCategories = async slug => {
await subCategoriesSearch({ slug });
fetchedSubCategories.value = {
...fetchedSubCategories.value,
[slug]: subCategories.value
};
};

const handleMouseEnter = debounce(async slug => {
currentCatSlug.value = slug;
const { childCount } = getCurrentCat(categories.value, slug);
emit('setOverlay', Boolean(childCount));

if (!fetchedSubCategories.value[slug] && Boolean(childCount)) {
await fetchSubCategories(slug);
}
activeCategory.value = fetchedSubCategories.value[slug];
}, 200);

const handleMouseLeave = debounce(() => {
emit('setOverlay', false);
currentCatSlug.value = '';
}, 200);

const getSubCategories = async (slug, childCount) => {
if (!childCount) {
root.$router.push(`/c/${slug}`);
toggleMobileMenu();
}
if (!fetchedSubCategories.value[slug]) {
await fetchSubCategories(slug);
}
activeCategory.value = fetchedSubCategories.value[slug];
};

const handleClickCategoryLvl = async (slug, lvl) => {
currentCatSlug.value = slug;
const catElements = lvl === 1 ? categories.value : activeCategory.value[0].children;
const { childCount } = getCurrentCat(catElements, slug);
await getSubCategories(slug, childCount);
};

onSSR(async () => {
await search({ customQuery: { categories: 'megamenu-categories-query' } });
});

return {
categories,
activeCategory,
subCategories,
currentCatSlug,
subCategoriesLoading,
handleMouseEnter,
handleMouseLeave,
handleClickCategoryLvl,
hasBanners,
hasChildren,
toggleMobileMenu,
isMobileMenuOpen
};
}
};
</script>

<style lang='scss'>
.sf-mega-menu__bar.sf-bar {
display: flex;
@include for-desktop {
display: none;
}
}
.sf-mega-menu.mobile-menu {
position: absolute;
overflow-y: auto;
top: 0;
z-index: 1;
width: 100%;
--mega-menu-aside-menu-height: calc(100vh - var(--bottom-navigation-height) - var(--bar-height));
}
.sf-loader__spinner {
margin-bottom: var(--bar-height, 3.125rem);
@include for-mobile {
margin-top: var(--bar-height, 3.125rem);
margin-bottom: 0;
}
}
</style>
5 changes: 5 additions & 0 deletions packages/commercetools/theme/middleware.config.js
@@ -1,3 +1,5 @@
const { megaMenuCategoriesQuery } = require('./queries');

module.exports = {
integrations: {
ct: {
Expand All @@ -24,6 +26,9 @@ module.exports = {
},
currency: 'USD',
country: 'US'
},
customQueries: {
'megamenu-categories-query': megaMenuCategoriesQuery
}
}
}
Expand Down