@riosst100/pwa-marketplace 1.3.1 → 1.3.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/src/componentOverrideMapping.js +5 -0
- package/src/components/ShopByCategory/index.js +2 -0
- package/src/components/ShopByCategory/shopByCategory.js +69 -0
- package/src/components/ShopByCategory/shopByCategory.module.css +58 -0
- package/src/components/ShopByCategory/shopByCategory.shimmer.js +24 -0
- package/src/components/SubCategory/subCategory.js +31 -0
- package/src/overwrites/peregrine/lib/talons/Breadcrumbs/useBreadcrumbs.js +100 -0
- package/src/overwrites/peregrine/lib/talons/MegaMenu/megaMenu.gql.js +70 -0
- package/src/overwrites/peregrine/lib/talons/MegaMenu/useMegaMenu.js +199 -0
- package/src/overwrites/peregrine/lib/talons/RootComponents/Category/categoryContent.gql.js +70 -0
- package/src/overwrites/peregrine/lib/talons/RootComponents/Category/useCategoryContent.js +141 -0
- package/src/overwrites/venia-ui/lib/RootComponents/Category/categoryContent.js +222 -0
- package/src/overwrites/venia-ui/lib/components/Adapter/adapter.js +5 -2
- package/src/overwrites/venia-ui/lib/components/FilterSidebar/filterSidebar.js +157 -0
- package/src/overwrites/venia-ui/lib/components/MegaMenu/megaMenu.js +2 -1
- package/src/overwrites/venia-ui/lib/components/MegaMenu/megaMenuItem.js +1 -0
- package/src/overwrites/venia-ui/lib/components/MegaMenu/shopByColumn.js +75 -0
- package/src/overwrites/venia-ui/lib/components/MegaMenu/shopByColumn.module.css +28 -0
- package/src/overwrites/venia-ui/lib/components/MegaMenu/submenu.js +31 -0
- package/src/overwrites/venia-ui/lib/components/MegaMenu/submenuColumn.js +1 -0
- package/src/overwrites/venia-ui/lib/components/SearchBar/searchField.js +1 -1
- package/src/talons/MegaMenu/megaMenu.gql.js +70 -0
- package/src/talons/ShopByCategory/index.js +1 -0
- package/src/talons/ShopByCategory/shopByCategory.gql.js +38 -0
- package/src/talons/ShopByCategory/useShopByCategory.js +69 -0
- package/src/talons/SubCategory/subCategory.gql.js +15 -0
- package/src/talons/SubCategory/useSubCategory.js +52 -0
package/package.json
CHANGED
|
@@ -13,7 +13,12 @@ module.exports = componentOverrideMapping = {
|
|
|
13
13
|
[`@magento/peregrine/lib/talons/SignIn/signIn.gql.js`]: '@riosst100/pwa-marketplace/src/overwrites/peregrine/lib/talons/SignIn/signIn.gql.js',
|
|
14
14
|
[`@magento/peregrine/lib/talons/SignIn/useSignIn.js`]: '@riosst100/pwa-marketplace/src/overwrites/peregrine/lib/talons/SignIn/useSignIn.js',
|
|
15
15
|
[`@magento/peregrine/lib/talons/AccountMenu/useAccountMenuItems.js`]: '@riosst100/pwa-marketplace/src/overwrites/peregrine/lib/talons/AccountMenu/useAccountMenuItems.js',
|
|
16
|
+
[`@magento/peregrine/lib/talons/MegaMenu/megaMenu.gql.js`]: '@riosst100/pwa-marketplace/src/overwrites/peregrine/lib/talons/MegaMenu/megaMenu.gql.js',
|
|
16
17
|
[`@magento/peregrine/lib/util/deriveErrorMessage.js`]: '@riosst100/pwa-marketplace/src/overwrites/peregrine/lib/util/deriveErrorMessage.js',
|
|
17
18
|
[`@magento/venia-ui/lib/components/MegaMenu/megaMenu.js`]: '@riosst100/pwa-marketplace/src/overwrites/venia-ui/lib/components/MegaMenu/megaMenu.js',
|
|
18
19
|
[`@magento/venia-ui/lib/components/FilterModal/FilterList/filterDefault.js`]: '@riosst100/pwa-marketplace/src/overwrites/venia-ui/lib/components/FilterModal/FilterList/filterDefault.js',
|
|
20
|
+
[`@magento/venia-ui/lib/RootComponents/Category/categoryContent.js`]: '@riosst100/pwa-marketplace/src/overwrites/venia-ui/lib/RootComponents/Category/categoryContent.js',
|
|
21
|
+
[`@magento/peregrine/lib/talons/RootComponents/Category/useCategoryContent.js`]: '@riosst100/pwa-marketplace/src/overwrites/peregrine/lib/talons/RootComponents/Category/useCategoryContent.js',
|
|
22
|
+
[`@magento/peregrine/lib/talons/Breadcrumbs/useBreadcrumbs.js`]: '@riosst100/pwa-marketplace/src/overwrites/peregrine/lib/talons/Breadcrumbs/useBreadcrumbs.js',
|
|
23
|
+
[`@magento/venia-ui/lib/components/FilterSidebar/filterSidebar.js`]: '@riosst100/pwa-marketplace/src/overwrites/venia-ui/lib/components/FilterSidebar/filterSidebar.js',
|
|
19
24
|
};
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
|
|
3
|
+
import { useStyle } from '@magento/venia-ui/lib/classify';
|
|
4
|
+
import defaultClasses from './shopByCategory.module.css';
|
|
5
|
+
import { useShopByCategory } from '@riosst100/pwa-marketplace/src/talons/ShopByCategory';
|
|
6
|
+
import { Link } from 'react-router-dom';
|
|
7
|
+
import resourceUrl from '@magento/peregrine/lib/util/makeUrl';
|
|
8
|
+
|
|
9
|
+
const ShopByCategory = props => {
|
|
10
|
+
const { categoryName, children, parent } = props;
|
|
11
|
+
|
|
12
|
+
const talonProps = useShopByCategory({ children, parent });
|
|
13
|
+
|
|
14
|
+
const {
|
|
15
|
+
childrenNormalizedData,
|
|
16
|
+
parentNormalizedData
|
|
17
|
+
} = talonProps;
|
|
18
|
+
|
|
19
|
+
const classes = useStyle(defaultClasses, props.classes);
|
|
20
|
+
|
|
21
|
+
const childrenList = [];
|
|
22
|
+
|
|
23
|
+
childrenList.push(
|
|
24
|
+
<span><b>{categoryName}</b></span>
|
|
25
|
+
);
|
|
26
|
+
|
|
27
|
+
childrenNormalizedData ? childrenNormalizedData.map(({ text, path }) => {
|
|
28
|
+
childrenList.push(
|
|
29
|
+
<Link
|
|
30
|
+
to={resourceUrl(path)}
|
|
31
|
+
>
|
|
32
|
+
<div>{text}</div>
|
|
33
|
+
</Link>
|
|
34
|
+
)
|
|
35
|
+
}) : ''
|
|
36
|
+
|
|
37
|
+
const parentList = [];
|
|
38
|
+
parentNormalizedData ? parentNormalizedData.map(({ text, path }) => {
|
|
39
|
+
parentList.push(
|
|
40
|
+
<Link
|
|
41
|
+
to={resourceUrl(path)}
|
|
42
|
+
>
|
|
43
|
+
<div>{text}</div>
|
|
44
|
+
</Link>
|
|
45
|
+
)
|
|
46
|
+
}) : ''
|
|
47
|
+
|
|
48
|
+
return (
|
|
49
|
+
<aside
|
|
50
|
+
className={classes.root}
|
|
51
|
+
data-cy="ShopByCategory-root"
|
|
52
|
+
aria-busy="false"
|
|
53
|
+
>
|
|
54
|
+
<br />
|
|
55
|
+
<br />
|
|
56
|
+
{/* <div className={classes.body}> */}
|
|
57
|
+
{/* <ul> */}
|
|
58
|
+
<li>
|
|
59
|
+
<div><b>Shop By Category</b></div>
|
|
60
|
+
{parentList}
|
|
61
|
+
<ul className={classes.blocks}>{childrenList}</ul>
|
|
62
|
+
</li>
|
|
63
|
+
{/* </ul> */}
|
|
64
|
+
{/* </div> */}
|
|
65
|
+
</aside>
|
|
66
|
+
);
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
export default ShopByCategory;
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
.root {
|
|
2
|
+
composes: bg-white from global;
|
|
3
|
+
composes: bottom-0 from global;
|
|
4
|
+
composes: hidden from global;
|
|
5
|
+
composes: max-w-modal from global;
|
|
6
|
+
composes: w-full from global;
|
|
7
|
+
composes: z-foreground from global;
|
|
8
|
+
grid-template-rows: 1fr 7rem;
|
|
9
|
+
|
|
10
|
+
composes: lg_grid from global;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
.body {
|
|
14
|
+
composes: overflow-auto from global;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
.header {
|
|
18
|
+
composes: flex from global;
|
|
19
|
+
composes: justify-between from global;
|
|
20
|
+
composes: pb-0 from global;
|
|
21
|
+
composes: pt-5 from global;
|
|
22
|
+
composes: px-5 from global;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
.headerTitle {
|
|
26
|
+
composes: flex from global;
|
|
27
|
+
composes: items-center from global;
|
|
28
|
+
composes: leading-none from global;
|
|
29
|
+
composes: text-lg from global;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
.action {
|
|
33
|
+
composes: pb-0 from global;
|
|
34
|
+
composes: pt-xs from global;
|
|
35
|
+
composes: px-xs from global;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/* TODO @TW: cannot compose */
|
|
39
|
+
.action button {
|
|
40
|
+
/* composes: text-sm from global; */
|
|
41
|
+
font-size: 0.875rem;
|
|
42
|
+
/* composes: no-underline from global; */
|
|
43
|
+
text-decoration: none;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
.blocks {
|
|
47
|
+
composes: pb-0 from global;
|
|
48
|
+
composes: pt-xs from global;
|
|
49
|
+
composes: px-xs from global;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/* TODO @TW: cannot compose */
|
|
53
|
+
.blocks > li:last-child {
|
|
54
|
+
/* composes: border-b-2 from global; */
|
|
55
|
+
/* composes: border-solid from global; */
|
|
56
|
+
/* composes: border-subtle from global; */
|
|
57
|
+
border-bottom: 2px solid rgb(var(--venia-global-color-border));
|
|
58
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { shape, string } from 'prop-types';
|
|
3
|
+
import { useStyle } from '@magento/venia-ui/lib/classify';
|
|
4
|
+
|
|
5
|
+
import Shimmer from '@magento/venia-ui/lib/components/Shimmer';
|
|
6
|
+
import defaultClasses from './shopByCategory.module.css';
|
|
7
|
+
|
|
8
|
+
const ShopByCategory = props => {
|
|
9
|
+
const classes = useStyle(defaultClasses, props.classes);
|
|
10
|
+
|
|
11
|
+
return (
|
|
12
|
+
<aside className={classes.root} aria-live="polite" aria-busy="true">
|
|
13
|
+
<Shimmer width="95%" height="70vh" style={{ marginBottom: 25 }} />
|
|
14
|
+
</aside>
|
|
15
|
+
);
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
ShopByCategory.propTypes = {
|
|
19
|
+
classes: shape({
|
|
20
|
+
root: string
|
|
21
|
+
})
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
export default ShopByCategory;
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { useSubCategory } from '@riosst100/pwa-marketplace/src/talons/SubCategory/useSubCategory';
|
|
2
|
+
import React from 'react';
|
|
3
|
+
import { Link } from 'react-router-dom';
|
|
4
|
+
import resourceUrl from '@magento/peregrine/lib/util/makeUrl';
|
|
5
|
+
|
|
6
|
+
const SubCategory = props => {
|
|
7
|
+
const { children } = props;
|
|
8
|
+
|
|
9
|
+
const talonProps = useSubCategory({ children });
|
|
10
|
+
|
|
11
|
+
const {
|
|
12
|
+
normalizedData
|
|
13
|
+
} = talonProps;
|
|
14
|
+
|
|
15
|
+
const subCategory = [];
|
|
16
|
+
|
|
17
|
+
normalizedData && normalizedData.map(({ text, path }, index) => {
|
|
18
|
+
subCategory.push(
|
|
19
|
+
<Link
|
|
20
|
+
key={index}
|
|
21
|
+
to={resourceUrl(path)}
|
|
22
|
+
>
|
|
23
|
+
<span className="filterModalOpenButton-filterButton-qRo button-root_lowPriority-Qoh button-root-MFn border-2 border-solid cursor-pointer font-bold inline-flex items-center justify-center leading-tight max-w-full min-w-[10rem] outline-none pointer-events-auto px-sm rounded-full text-center text-sm uppercase disabled_bg-gray-400 disabled_border-gray-400 disabled_opacity-50 disabled_pointer-events-none disabled_text-white focus_shadow-inputFocus bg-transparent border-gray-700 text-gray-700 active_border-gray-800 active_text-gray-800 hover_border-gray-800 hover_text-gray-800 min-w-[6.25rem]" style={{"margin":"10px"}}>{text}</span>
|
|
24
|
+
</Link>
|
|
25
|
+
)
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
return subCategory;
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
export default SubCategory;
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
import { useMemo } from 'react';
|
|
2
|
+
import { useQuery } from '@apollo/client';
|
|
3
|
+
import { useInternalLink } from '@magento/peregrine/lib/hooks/useInternalLink';
|
|
4
|
+
|
|
5
|
+
import mergeOperations from '@magento/peregrine/lib/util/shallowMerge';
|
|
6
|
+
|
|
7
|
+
import DEFAULT_OPERATIONS from '@magento/peregrine/lib/talons/Breadcrumbs/breadcrumbs.gql';
|
|
8
|
+
|
|
9
|
+
// Just incase the data is unsorted, lets sort it.
|
|
10
|
+
|
|
11
|
+
/* CUSTOM: Fix sorting */
|
|
12
|
+
const sortCrumbs = (a, b) => a.category_level - b.category_level;
|
|
13
|
+
|
|
14
|
+
// Generates the path for the category.
|
|
15
|
+
const getPath = (path, suffix) => {
|
|
16
|
+
if (path) {
|
|
17
|
+
return `/${path}${suffix || ''}`;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
// If there is no path this is just a dead link.
|
|
21
|
+
return '#';
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Returns props necessary to render a Breadcrumbs component.
|
|
26
|
+
*
|
|
27
|
+
* @param {object} props
|
|
28
|
+
* @param {object} props.query - the breadcrumb query
|
|
29
|
+
* @param {string} props.categoryId - the id of the category for which to generate breadcrumbs
|
|
30
|
+
* @return {{
|
|
31
|
+
* currentCategory: string,
|
|
32
|
+
* currentCategoryPath: string,
|
|
33
|
+
* isLoading: boolean,
|
|
34
|
+
* normalizedData: array,
|
|
35
|
+
* handleClick: function
|
|
36
|
+
* }}
|
|
37
|
+
*/
|
|
38
|
+
export const useBreadcrumbs = props => {
|
|
39
|
+
const { categoryId } = props;
|
|
40
|
+
|
|
41
|
+
const operations = mergeOperations(DEFAULT_OPERATIONS, props.operations);
|
|
42
|
+
const { getBreadcrumbsQuery, getStoreConfigQuery } = operations;
|
|
43
|
+
|
|
44
|
+
const { data, loading, error } = useQuery(getBreadcrumbsQuery, {
|
|
45
|
+
variables: { category_id: categoryId },
|
|
46
|
+
fetchPolicy: 'cache-and-network',
|
|
47
|
+
nextFetchPolicy: 'cache-first'
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
const { data: storeConfigData } = useQuery(getStoreConfigQuery, {
|
|
51
|
+
fetchPolicy: 'cache-and-network'
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
const categoryUrlSuffix = useMemo(() => {
|
|
55
|
+
if (storeConfigData) {
|
|
56
|
+
return storeConfigData.storeConfig.category_url_suffix;
|
|
57
|
+
}
|
|
58
|
+
}, [storeConfigData]);
|
|
59
|
+
|
|
60
|
+
// When we have breadcrumb data sort and normalize it for easy rendering.
|
|
61
|
+
const normalizedData = useMemo(() => {
|
|
62
|
+
if (!loading && data && data.categories.items.length) {
|
|
63
|
+
const breadcrumbData = data.categories.items[0].breadcrumbs;
|
|
64
|
+
|
|
65
|
+
return (
|
|
66
|
+
breadcrumbData &&
|
|
67
|
+
breadcrumbData
|
|
68
|
+
.map(category => ({
|
|
69
|
+
category_level: category.category_level,
|
|
70
|
+
text: category.category_name,
|
|
71
|
+
path: getPath(
|
|
72
|
+
category.category_url_path,
|
|
73
|
+
categoryUrlSuffix
|
|
74
|
+
)
|
|
75
|
+
}))
|
|
76
|
+
.sort(sortCrumbs)
|
|
77
|
+
);
|
|
78
|
+
}
|
|
79
|
+
}, [categoryUrlSuffix, data, loading]);
|
|
80
|
+
|
|
81
|
+
const { setShimmerType } = useInternalLink('category');
|
|
82
|
+
|
|
83
|
+
return {
|
|
84
|
+
currentCategory:
|
|
85
|
+
(data &&
|
|
86
|
+
data.categories.items.length &&
|
|
87
|
+
data.categories.items[0].name) ||
|
|
88
|
+
'',
|
|
89
|
+
currentCategoryPath:
|
|
90
|
+
(data &&
|
|
91
|
+
data.categories.items.length &&
|
|
92
|
+
`${data.categories.items[0].url_path}${categoryUrlSuffix ||
|
|
93
|
+
''}`) ||
|
|
94
|
+
'#',
|
|
95
|
+
isLoading: loading,
|
|
96
|
+
hasError: !!error,
|
|
97
|
+
normalizedData: normalizedData || [],
|
|
98
|
+
handleClick: setShimmerType
|
|
99
|
+
};
|
|
100
|
+
};
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import { gql } from '@apollo/client';
|
|
2
|
+
export const GET_STORE_CONFIG_DATA = gql`
|
|
3
|
+
query GetStoreConfigForMegaMenu {
|
|
4
|
+
# eslint-disable-next-line @graphql-eslint/require-id-when-available
|
|
5
|
+
storeConfig {
|
|
6
|
+
store_code
|
|
7
|
+
category_url_suffix
|
|
8
|
+
}
|
|
9
|
+
}
|
|
10
|
+
`;
|
|
11
|
+
|
|
12
|
+
export const GET_MEGA_MENU = gql`
|
|
13
|
+
query getMegaMenu {
|
|
14
|
+
# eslint-disable-next-line @graphql-eslint/require-id-when-available
|
|
15
|
+
categoryList {
|
|
16
|
+
uid
|
|
17
|
+
name
|
|
18
|
+
# eslint-disable-next-line @graphql-eslint/require-id-when-available
|
|
19
|
+
children {
|
|
20
|
+
uid
|
|
21
|
+
include_in_menu
|
|
22
|
+
name
|
|
23
|
+
position
|
|
24
|
+
url_path
|
|
25
|
+
shop_by {
|
|
26
|
+
name
|
|
27
|
+
items {
|
|
28
|
+
name
|
|
29
|
+
url_path
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
# eslint-disable-next-line @graphql-eslint/require-id-when-available
|
|
33
|
+
children {
|
|
34
|
+
uid
|
|
35
|
+
include_in_menu
|
|
36
|
+
name
|
|
37
|
+
position
|
|
38
|
+
url_path
|
|
39
|
+
shop_by {
|
|
40
|
+
name
|
|
41
|
+
items {
|
|
42
|
+
name
|
|
43
|
+
url_path
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
# eslint-disable-next-line @graphql-eslint/require-id-when-available
|
|
47
|
+
children {
|
|
48
|
+
uid
|
|
49
|
+
include_in_menu
|
|
50
|
+
name
|
|
51
|
+
position
|
|
52
|
+
url_path
|
|
53
|
+
shop_by {
|
|
54
|
+
name
|
|
55
|
+
items {
|
|
56
|
+
name
|
|
57
|
+
url_path
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
`;
|
|
66
|
+
|
|
67
|
+
export default {
|
|
68
|
+
getMegaMenuQuery: GET_MEGA_MENU,
|
|
69
|
+
getStoreConfigQuery: GET_STORE_CONFIG_DATA
|
|
70
|
+
};
|
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
import { useMemo, useState, useCallback, useEffect } from 'react';
|
|
2
|
+
import { useLocation } from 'react-router-dom';
|
|
3
|
+
import { useInternalLink } from '../../hooks/useInternalLink';
|
|
4
|
+
|
|
5
|
+
import { useQuery } from '@apollo/client';
|
|
6
|
+
import { useEventListener } from '../../hooks/useEventListener';
|
|
7
|
+
|
|
8
|
+
import mergeOperations from '../../util/shallowMerge';
|
|
9
|
+
import DEFAULT_OPERATIONS from './megaMenu.gql';
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* The useMegaMenu talon complements the MegaMenu component.
|
|
13
|
+
*
|
|
14
|
+
* @param {Object} props
|
|
15
|
+
* @param {*} props.operations GraphQL operations used by talons
|
|
16
|
+
* @param {React.RefObject} props.mainNavRef Reference to main navigation DOM node
|
|
17
|
+
*
|
|
18
|
+
* @return {MegaMenuTalonProps}
|
|
19
|
+
*/
|
|
20
|
+
export const useMegaMenu = (props = {}) => {
|
|
21
|
+
const operations = mergeOperations(DEFAULT_OPERATIONS, props.operations);
|
|
22
|
+
const { getMegaMenuQuery, getStoreConfigQuery } = operations;
|
|
23
|
+
|
|
24
|
+
const location = useLocation();
|
|
25
|
+
const [activeCategoryId, setActiveCategoryId] = useState(null);
|
|
26
|
+
const [subMenuState, setSubMenuState] = useState(false);
|
|
27
|
+
const [disableFocus, setDisableFocus] = useState(false);
|
|
28
|
+
|
|
29
|
+
const { data: storeConfigData } = useQuery(getStoreConfigQuery, {
|
|
30
|
+
fetchPolicy: 'cache-and-network'
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
const { data } = useQuery(getMegaMenuQuery, {
|
|
34
|
+
fetchPolicy: 'cache-and-network',
|
|
35
|
+
nextFetchPolicy: 'cache-first'
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
const categoryUrlSuffix = useMemo(() => {
|
|
39
|
+
if (storeConfigData) {
|
|
40
|
+
return storeConfigData.storeConfig.category_url_suffix;
|
|
41
|
+
}
|
|
42
|
+
}, [storeConfigData]);
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Check if category should be visible on the storefront.
|
|
46
|
+
*
|
|
47
|
+
* @param {MegaMenuCategory} category
|
|
48
|
+
* @returns {boolean}
|
|
49
|
+
*/
|
|
50
|
+
const shouldRenderMegaMenuItem = category => {
|
|
51
|
+
return !!category.include_in_menu;
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Check if category is the active category based on the current location.
|
|
56
|
+
*
|
|
57
|
+
* @param {MegaMenuCategory} category
|
|
58
|
+
* @returns {boolean}
|
|
59
|
+
*/
|
|
60
|
+
|
|
61
|
+
const isActive = useCallback(
|
|
62
|
+
({ url_path }) => {
|
|
63
|
+
if (!url_path) return false;
|
|
64
|
+
|
|
65
|
+
const categoryUrlPath = `/${url_path}${categoryUrlSuffix || ''}`;
|
|
66
|
+
|
|
67
|
+
return location.pathname === categoryUrlPath;
|
|
68
|
+
},
|
|
69
|
+
[location.pathname, categoryUrlSuffix]
|
|
70
|
+
);
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Recursively map data returned by GraphQL query.
|
|
74
|
+
*
|
|
75
|
+
* @param {MegaMenuCategory} category
|
|
76
|
+
* @param {array} - path from the given category to the first level category
|
|
77
|
+
* @param {boolean} isRoot - describes is category a root category
|
|
78
|
+
* @return {MegaMenuCategory}
|
|
79
|
+
*/
|
|
80
|
+
const processData = useCallback(
|
|
81
|
+
(category, path = [], isRoot = true) => {
|
|
82
|
+
if (!category) {
|
|
83
|
+
return;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
const megaMenuCategory = Object.assign({}, category);
|
|
87
|
+
|
|
88
|
+
if (!isRoot) {
|
|
89
|
+
megaMenuCategory.path = [...path, category.uid];
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
megaMenuCategory.isActive = isActive(megaMenuCategory);
|
|
93
|
+
|
|
94
|
+
if (megaMenuCategory.children) {
|
|
95
|
+
megaMenuCategory.children = [...megaMenuCategory.children]
|
|
96
|
+
.filter(category => shouldRenderMegaMenuItem(category))
|
|
97
|
+
.sort((a, b) => (a.position > b.position ? 1 : -1))
|
|
98
|
+
.map(child =>
|
|
99
|
+
processData(child, megaMenuCategory.path, false)
|
|
100
|
+
);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
return megaMenuCategory;
|
|
104
|
+
},
|
|
105
|
+
[isActive]
|
|
106
|
+
);
|
|
107
|
+
|
|
108
|
+
const megaMenuData = useMemo(() => {
|
|
109
|
+
return data ? processData(data.categoryList[0]) : {};
|
|
110
|
+
}, [data, processData]);
|
|
111
|
+
|
|
112
|
+
const findActiveCategory = useCallback(
|
|
113
|
+
(pathname, category) => {
|
|
114
|
+
if (isActive(category)) {
|
|
115
|
+
return category;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
if (category.children) {
|
|
119
|
+
return category.children.find(category =>
|
|
120
|
+
findActiveCategory(pathname, category)
|
|
121
|
+
);
|
|
122
|
+
}
|
|
123
|
+
},
|
|
124
|
+
[isActive]
|
|
125
|
+
);
|
|
126
|
+
|
|
127
|
+
const handleClickOutside = e => {
|
|
128
|
+
if (!props.mainNavRef.current.contains(e.target)) {
|
|
129
|
+
setSubMenuState(false);
|
|
130
|
+
setDisableFocus(true);
|
|
131
|
+
}
|
|
132
|
+
};
|
|
133
|
+
|
|
134
|
+
useEventListener(globalThis, 'keydown', handleClickOutside);
|
|
135
|
+
|
|
136
|
+
const handleSubMenuFocus = useCallback(() => {
|
|
137
|
+
setSubMenuState(true);
|
|
138
|
+
}, [setSubMenuState]);
|
|
139
|
+
|
|
140
|
+
useEffect(() => {
|
|
141
|
+
const activeCategory = findActiveCategory(
|
|
142
|
+
location.pathname,
|
|
143
|
+
megaMenuData
|
|
144
|
+
);
|
|
145
|
+
|
|
146
|
+
if (activeCategory) {
|
|
147
|
+
setActiveCategoryId(activeCategory.path[0]);
|
|
148
|
+
} else {
|
|
149
|
+
setActiveCategoryId(null);
|
|
150
|
+
}
|
|
151
|
+
}, [findActiveCategory, location.pathname, megaMenuData]);
|
|
152
|
+
|
|
153
|
+
/**
|
|
154
|
+
* Sets next root component to show proper loading effect
|
|
155
|
+
*
|
|
156
|
+
* @returns {void}
|
|
157
|
+
*/
|
|
158
|
+
const { setShimmerType } = useInternalLink('category');
|
|
159
|
+
|
|
160
|
+
return {
|
|
161
|
+
megaMenuData,
|
|
162
|
+
activeCategoryId,
|
|
163
|
+
categoryUrlSuffix,
|
|
164
|
+
handleClickOutside,
|
|
165
|
+
subMenuState,
|
|
166
|
+
disableFocus,
|
|
167
|
+
handleSubMenuFocus,
|
|
168
|
+
handleNavigate: setShimmerType
|
|
169
|
+
};
|
|
170
|
+
};
|
|
171
|
+
|
|
172
|
+
/** JSDocs type definitions */
|
|
173
|
+
|
|
174
|
+
/**
|
|
175
|
+
* @typedef {Object} MegaMenuTalonProps
|
|
176
|
+
*
|
|
177
|
+
* @property {MegaMenuCategory} megaMenuData - The Object with categories contains only categories
|
|
178
|
+
* with the include_in_menu = 1 flag. The categories are sorted
|
|
179
|
+
* based on the field position.
|
|
180
|
+
* @property {String} activeCategoryId returns the currently selected category uid.
|
|
181
|
+
* @property {String} categoryUrlSuffix store's category url suffix to construct category URL
|
|
182
|
+
* @property {Function} handleClickOutside function to handle mouse/key events.
|
|
183
|
+
* @property {Boolean} subMenuState maintaining sub-menu open/close state
|
|
184
|
+
* @property {Boolean} disableFocus state to disable focus
|
|
185
|
+
* @property {Function} handleSubMenuFocus toggle function to handle sub-menu focus
|
|
186
|
+
* @property {function} handleNavigate - callback to fire on link click
|
|
187
|
+
*/
|
|
188
|
+
|
|
189
|
+
/**
|
|
190
|
+
* Object type returned by the {@link useMegaMenu} talon.
|
|
191
|
+
* @typedef {Object} MegaMenuCategory
|
|
192
|
+
*
|
|
193
|
+
* @property {String} uid - uid of the category
|
|
194
|
+
* @property {int} include_in_menu - describes if category should be included in menu
|
|
195
|
+
* @property {String} name - name of the category
|
|
196
|
+
* @property {int} position - value used for sorting
|
|
197
|
+
* @property {String} url_path - URL path for a category
|
|
198
|
+
* @property {MegaMenuCategory} children - child category
|
|
199
|
+
*/
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import { gql } from '@apollo/client';
|
|
2
|
+
|
|
3
|
+
export const GET_PRODUCT_FILTERS_BY_CATEGORY = gql`
|
|
4
|
+
query getProductFiltersByCategory(
|
|
5
|
+
$categoryIdFilter: FilterEqualTypeInput!
|
|
6
|
+
) {
|
|
7
|
+
products(filter: { category_uid: $categoryIdFilter }) {
|
|
8
|
+
aggregations {
|
|
9
|
+
label
|
|
10
|
+
count
|
|
11
|
+
attribute_code
|
|
12
|
+
options {
|
|
13
|
+
label
|
|
14
|
+
value
|
|
15
|
+
}
|
|
16
|
+
position
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
`;
|
|
21
|
+
|
|
22
|
+
export const GET_CATEGORY_CONTENT = gql`
|
|
23
|
+
query getCategoryData($id: String!) {
|
|
24
|
+
categories(filters: { category_uid: { in: [$id] } }) {
|
|
25
|
+
# eslint-disable-next-line @graphql-eslint/require-id-when-available
|
|
26
|
+
items {
|
|
27
|
+
uid
|
|
28
|
+
name
|
|
29
|
+
description
|
|
30
|
+
url_key
|
|
31
|
+
url_path
|
|
32
|
+
parent {
|
|
33
|
+
uid
|
|
34
|
+
name
|
|
35
|
+
position
|
|
36
|
+
url_path
|
|
37
|
+
# eslint-disable-next-line @graphql-eslint/require-id-when-available
|
|
38
|
+
}
|
|
39
|
+
children {
|
|
40
|
+
uid
|
|
41
|
+
name
|
|
42
|
+
position
|
|
43
|
+
url_path
|
|
44
|
+
# eslint-disable-next-line @graphql-eslint/require-id-when-available
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
`;
|
|
50
|
+
|
|
51
|
+
export const GET_CATEGORY_AVAILABLE_SORT_METHODS = gql`
|
|
52
|
+
query getCategoryAvailableSortMethods(
|
|
53
|
+
$categoryIdFilter: FilterEqualTypeInput!
|
|
54
|
+
) {
|
|
55
|
+
products(filter: { category_uid: $categoryIdFilter }) {
|
|
56
|
+
sort_fields {
|
|
57
|
+
options {
|
|
58
|
+
label
|
|
59
|
+
value
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
`;
|
|
65
|
+
|
|
66
|
+
export default {
|
|
67
|
+
getCategoryContentQuery: GET_CATEGORY_CONTENT,
|
|
68
|
+
getProductFiltersByCategoryQuery: GET_PRODUCT_FILTERS_BY_CATEGORY,
|
|
69
|
+
getCategoryAvailableSortMethodsQuery: GET_CATEGORY_AVAILABLE_SORT_METHODS
|
|
70
|
+
};
|