Skip to content
This repository has been archived by the owner on Oct 31, 2024. It is now read-only.

Commit

Permalink
feat: add MegaMenu component (#593)
Browse files Browse the repository at this point in the history
* feat: add MegaMenu component

* feat(helpers): getCategoryUrl

Co-authored-by: Patryk Tomczyk <13100280+patzick@users.noreply.github.com>
Co-authored-by: martaradziszewska <marta.radziszewskaa@gmail.com>
  • Loading branch information
3 people authored May 7, 2020
1 parent b0ef05e commit f6167d5
Show file tree
Hide file tree
Showing 12 changed files with 229 additions and 45 deletions.
15 changes: 14 additions & 1 deletion api/composables.api.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import { CustomerUpdateEmailParam } from '@shopware-pwa/shopware-6-client';
import { CustomerUpdatePasswordParam } from '@shopware-pwa/shopware-6-client';
import { CustomerUpdateProfileParam } from '@shopware-pwa/shopware-6-client';
import { GuestOrderParams } from '@shopware-pwa/commons/interfaces/request/GuestOrderParams';
import { NavigationElement } from '@shopware-pwa/commons/interfaces/models/content/navigation/Navigation';
import { Order } from '@shopware-pwa/commons/interfaces/models/checkout/order/Order';
import { PaymentMethod } from '@shopware-pwa/commons/interfaces/models/checkout/payment/PaymentMethod';
import { Product } from '@shopware-pwa/commons/interfaces/models/content/product/Product';
Expand Down Expand Up @@ -154,7 +155,19 @@ export interface UseCurrency {
export const useCurrency: () => UseCurrency;

// @alpha (undocumented)
export const useNavigation: () => any;
export interface UseNavigation {
// (undocumented)
fetchNavigationElements: (depth: number) => Promise<void>;
// (undocumented)
fetchRoutes: () => Promise<void>;
// (undocumented)
navigationElements: NavigationElement[];
// (undocumented)
routes: Ref<Readonly<any>>;
}

// @alpha (undocumented)
export const useNavigation: () => UseNavigation;

// @alpha (undocumented)
export interface UseProduct<PRODUCT, SEARCH> {
Expand Down
4 changes: 4 additions & 0 deletions api/helpers.api.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
```ts

import { Category } from '@shopware-pwa/commons/interfaces/models/content/category/Category';
import { CmsPage } from '@shopware-pwa/commons/interfaces/models/content/cms/CmsPage';
import { CmsSection } from '@shopware-pwa/commons/interfaces/models/content/cms/CmsPage';
import { Country } from '@shopware-pwa/commons/interfaces/models/system/country/Country';
Expand Down Expand Up @@ -72,6 +73,9 @@ export function getCategoryAvailableSorting({ sorting, }?: {
sorting?: Sorting;
}): UiCategorySorting[];

// @alpha
export const getCategoryUrl: (category: Partial<Category>) => string;

// @alpha (undocumented)
export function getCmsSections(content: CmsPage): CmsSection[];

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,4 +39,7 @@ export interface Category extends Entity {
parentVersionId: string;
childrenCount: number;
afterCategoryVersionId: string;
route?: {
path?: string;
};
}
21 changes: 21 additions & 0 deletions packages/composables/__tests__/useNavigation.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,9 @@ jest.mock("@shopware-pwa/shopware-6-client");
const mockedGetPage = shopwareClient as jest.Mocked<typeof shopwareClient>;

describe("Composables - useNavigation", () => {
beforeEach(() => {
jest.clearAllMocks();
});
describe("computed", () => {
describe("routes", () => {
it("should get null when routeNames are not fetched", () => {
Expand Down Expand Up @@ -41,5 +44,23 @@ describe("Composables - useNavigation", () => {
expect(routes.value).toHaveLength(3);
});
});
describe("fetchNavigationElements", () => {
it("should fetch navigation elements correcly", async () => {
mockedGetPage.getNavigation.mockResolvedValueOnce({
count: 3,
children: [
{ name: "Clothin", route: { path: "clothing/" } },
{ name: "Sports", route: { path: "sports/" } },
{
name: "Accessories & Others",
route: { path: "accessories-others/" },
},
],
} as any);
const { navigationElements, fetchNavigationElements } = useNavigation();
await fetchNavigationElements(2);
expect(navigationElements).toHaveLength(3);
});
});
});
});
30 changes: 25 additions & 5 deletions packages/composables/src/hooks/useNavigation.ts
Original file line number Diff line number Diff line change
@@ -1,27 +1,47 @@
import Vue from "vue";
import { reactive, computed } from "@vue/composition-api";
import { reactive, computed, Ref } from "@vue/composition-api";
import { getNavigation } from "@shopware-pwa/shopware-6-client";
import { getNavigationRoutes } from "@shopware-pwa/helpers";
import { NavigationElement } from "@shopware-pwa/commons/interfaces/models/content/navigation/Navigation";

/**
* @alpha
*/
export interface UseNavigation {
routes: Ref<Readonly<any>>;
navigationElements: NavigationElement[];
fetchNavigationElements: (depth: number) => Promise<void>;
fetchRoutes: () => Promise<void>;
}

const sharedNavigation = Vue.observable({
routes: null,
navigationElements: [],
} as any);

/**
* @alpha
*/
export const useNavigation = (): any => {
export const useNavigation = (): UseNavigation => {
const localNavigation = reactive(sharedNavigation);
const routes = computed(() => localNavigation.routes);

const fetchRoutes = async (params?: any): Promise<void> => {
const navigation = await getNavigation(params);
if (typeof navigation.children === "undefined") return;
sharedNavigation.routes = getNavigationRoutes(navigation.children);
const { children } = await getNavigation(params);
if (typeof children === "undefined") return;
sharedNavigation.routes = getNavigationRoutes(children);
};

const fetchNavigationElements = async (depth: number) => {
const { children } = await getNavigation({ depth });
localNavigation.navigationElements.length = 0;
localNavigation.navigationElements.push(...children);
};

return {
routes,
navigationElements: localNavigation.navigationElements,
fetchNavigationElements,
fetchRoutes,
};
};
77 changes: 77 additions & 0 deletions packages/default-theme/components/SwMegaMenu.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
<template>
<SfMegaMenu :visible="visible" :title="category.name" class="sw-mega-menu">
<div class="sw-mega-menu__content">
<div
class="sw-mega-menu__content-section"
v-for="subcategory in category.children"
:key="subcategory.name"
>
<nuxt-link
class="sf-header__link"
:to="getCategoryUrl(subcategory)"
>
<SfHeading
:title="subcategory.name"
:subtitle="subcategory.description"
:level="4"
/>
</nuxt-link>
<SfList>
<SfListItem v-for="child in subcategory.children" :key="child.label">
<nuxt-link
class="sf-header__link"
:to="getCategoryUrl(child)"
>
<SfMenuItem :label="child.name" />
</nuxt-link>
</SfListItem>
</SfList>
</div>
</div>
</SfMegaMenu>
</template>

<script>
import { SfMegaMenu, SfMenuItem, SfList, SfHeading } from '@storefront-ui/vue'
import { getCategoryUrl } from '@shopware-pwa/helpers'
export default {
name: 'SwMegaMenu',
components: { SfMegaMenu, SfMenuItem, SfList, SfHeading },
props: {
category: {
type: Object,
default: () => ({}),
},
visible: {
type: Boolean,
default: false,
},
},
setup() {
return { getCategoryUrl }
},
}
</script>

<style lang="scss" scoped>
.sw-mega-menu {
position: absolute;
left: 0;
width: 100%;
top: 100%;
&__content {
flex-wrap: wrap;
display: flex;
max-width: 80vw;
&-section {
padding: 1rem;
display: flex;
flex-direction: column;
justify-content: flex-start;
align-items: flex-start;
}
}
}
</style>
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
<template>
<div class="top-navigation">
<SfOverlay :visible="!!currentCategoryName" />
<SfTopBar class="top-bar desktop-only">
<template #right>
<SwCurrency class="sf-header__currency" />
Expand All @@ -25,21 +26,23 @@
<template #navigation>
<SwPluginSlot name="top-navigation-before" />
<SfHeaderNavigationItem
v-for="{ routeLabel, routePath } in routes"
:key="routeLabel"
v-for="category in navigationElements"
:key="category.name"
class="sf-header__link"
@mouseover="currentCategoryName = category.name"
@mouseleave="currentCategoryName = null"
@keyup.tab="currentCategoryName = category.name"
>
<nuxt-link class="sf-header__link" :to="routePath">
<a
:style="{
display: 'flex',
alignItems: 'center',
height: '100%',
}"
>
{{ routeLabel }}
</a>
<nuxt-link
class="sf-header__link"
:to="getCategoryUrl(category)"
>
{{ category.name }}
</nuxt-link>
<SwMegaMenu
:category="category"
:visible="category.name === currentCategoryName"
/>
</SfHeaderNavigationItem>
<SwPluginSlot name="top-navigation-after" />
</template>
Expand Down Expand Up @@ -68,7 +71,7 @@
:has-badge="isLoggedIn"
@click="userIconClick"
/>
<SfIcon
<SfIcon
v-if="cartIcon"
:icon="cartIcon"
:has-badge="count > 0"
Expand Down Expand Up @@ -97,10 +100,11 @@
<script>
import {
SfHeader,
SfIcon,
SfImage,
SfTopBar,
SfSearchBar,
SfOverlay,
SfTopBar,
SfIcon,
} from '@storefront-ui/vue'
import {
useUser,
Expand All @@ -112,50 +116,57 @@ import {
} from '@shopware-pwa/composables'
import SwLoginModal from '@shopware-pwa/default-theme/components/modals/SwLoginModal'
import SwCurrency from '@shopware-pwa/default-theme/components/SwCurrency'
import SwMegaMenu from '@shopware-pwa/default-theme/components/SwMegaMenu'
import { ref, reactive, onMounted } from '@vue/composition-api'
import { PAGE_ACCOUNT } from '@shopware-pwa/default-theme/helpers/pages'
import { getCategoryUrl } from '@shopware-pwa/helpers'
import SwPluginSlot from 'sw-plugins/SwPluginSlot'
export default {
components: {
SfHeader,
SfIcon,
SwLoginModal,
SfImage,
SfTopBar,
SfSearchBar,
SwMegaMenu,
SfOverlay,
SfTopBar,
SwCurrency,
SwPluginSlot
SfIcon,
SwPluginSlot,
},
setup() {
const { routes, fetchRoutes } = useNavigation()
const { isLoggedIn, logout } = useUser()
const { count } = useCart()
const { toggleSidebar } = useCartSidebar()
const { toggleModal } = useUserLoginModal()
const { search: fulltextSearch } = useProductSearch()
const { fetchNavigationElements, navigationElements } = useNavigation()
const currentCategoryName = ref(null)
onMounted(async () => {
await fetchNavigationElements(3)
})
return {
routes,
fetchRoutes,
count,
toggleModal,
toggleSidebar,
isLoggedIn,
logout,
fulltextSearch,
navigationElements,
getCategoryUrl,
currentCategoryName,
}
},
data() {
return {
navigationElements: [{ name: '' }],
activeSidebar: 'account',
activeIcon: '',
isModalOpen: false,
}
},
async mounted() {
await this.fetchRoutes({ depth: 1 })
},
methods: {
userIconClick() {
if (this.isLoggedIn) this.$router.push(PAGE_ACCOUNT)
Expand All @@ -174,7 +185,7 @@ export default {
--header-navigation-item-margin: 0 1rem 0 0;
margin-bottom: var(--spacer-sm);
.sf-header {
padding: 0 var(--spacer-sm);
// padding: 0 var(--spacer-sm);
&__currency {
position: relative;
margin: 0 var(--spacer-base) 0 var(--spacer-base);
Expand Down Expand Up @@ -213,15 +224,14 @@ export default {
}
&__link {
display: flex;
align-items: center;
height: 100;
}
}
}
}
.top-bar {
padding: 0 var(--spacer-sm);
position: relative;
z-index: 1;
&__location-label {
margin: 0 var(--spacer-sm) 0 0;
}
Expand Down
Loading

1 comment on commit f6167d5

@vercel
Copy link

@vercel vercel bot commented on f6167d5 May 7, 2020

Choose a reason for hiding this comment

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

Please sign in to comment.