@magento/peregrine 11.0.0-beta.1 → 12.0.0-alpha.3

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.
Files changed (133) hide show
  1. package/lib/Apollo/magentoGqlCacheLink.js +59 -0
  2. package/lib/List/item.js +12 -9
  3. package/lib/List/list.js +10 -7
  4. package/lib/Price/price.js +0 -1
  5. package/lib/context/cart.js +9 -10
  6. package/lib/hooks/hook-wrappers/useInformedFieldStateWrapper.js +27 -0
  7. package/lib/hooks/useDelayedTransition.js +127 -0
  8. package/lib/hooks/useDetectScrollWidth.js +77 -0
  9. package/lib/hooks/useInternalLink.js +21 -0
  10. package/lib/hooks/useIntersectionObserver.js +7 -0
  11. package/lib/hooks/useIsInViewport.js +44 -0
  12. package/lib/store/actions/app/actions.js +2 -1
  13. package/lib/store/reducers/app.js +8 -1
  14. package/lib/talons/AccountInformationPage/useAccountInformationPage.js +0 -1
  15. package/lib/talons/Adapter/useAdapter.js +23 -3
  16. package/lib/talons/AddressBookPage/useAddressBookPage.js +0 -10
  17. package/lib/talons/Breadcrumbs/breadcrumbs.gql.js +11 -2
  18. package/lib/talons/Breadcrumbs/useBreadcrumbs.js +19 -5
  19. package/lib/talons/CartPage/GiftCards/giftCardFragments.gql.ee.js +14 -0
  20. package/lib/talons/CartPage/GiftCards/giftCardFragments.gql.js +8 -0
  21. package/lib/talons/CartPage/GiftCards/giftCardQueries.gql.ee.js +73 -0
  22. package/lib/talons/CartPage/GiftCards/useGiftCards.js +25 -16
  23. package/lib/talons/CartPage/PriceAdjustments/CouponCode/couponCode.gql.js +56 -0
  24. package/lib/talons/CartPage/PriceAdjustments/CouponCode/couponCodeFragments.gql.js +10 -0
  25. package/lib/talons/CartPage/PriceAdjustments/{useCouponCode.js → CouponCode/useCouponCode.js} +10 -6
  26. package/lib/talons/CartPage/{GiftOptions → PriceAdjustments/GiftOptions}/client-schema.graphql +0 -0
  27. package/lib/talons/CartPage/PriceAdjustments/GiftOptions/giftOptions.gql.js +21 -0
  28. package/lib/talons/CartPage/{GiftOptions → PriceAdjustments/GiftOptions}/useGiftOptions.js +5 -3
  29. package/lib/talons/CartPage/PriceAdjustments/ShippingMethods/shippingMethods.gql.js +43 -0
  30. package/lib/talons/CartPage/PriceAdjustments/ShippingMethods/shippingMethodsFragments.gql.js +60 -0
  31. package/lib/talons/CartPage/PriceAdjustments/ShippingMethods/useShippingForm.js +8 -9
  32. package/lib/talons/CartPage/PriceAdjustments/ShippingMethods/useShippingMethods.js +7 -5
  33. package/lib/talons/CartPage/PriceAdjustments/ShippingMethods/useShippingRadios.js +1 -1
  34. package/lib/talons/CartPage/PriceSummary/priceSummary.gql.js +16 -0
  35. package/lib/talons/CartPage/PriceSummary/usePriceSummary.js +6 -5
  36. package/lib/talons/CartPage/ProductListing/EditModal/productForm.gql.js +78 -0
  37. package/lib/talons/CartPage/ProductListing/EditModal/productFormFragment.gql.js +47 -0
  38. package/lib/talons/CartPage/ProductListing/EditModal/useProductForm.js +18 -8
  39. package/lib/talons/CartPage/ProductListing/product.gql.js +4 -3
  40. package/lib/talons/CartPage/ProductListing/productListing.gql.ce.js +13 -1
  41. package/lib/talons/CartPage/ProductListing/productListing.gql.ee.js +13 -1
  42. package/lib/talons/CartPage/ProductListing/productListingFragments.gql.js +52 -0
  43. package/lib/talons/CartPage/ProductListing/useProduct.js +22 -18
  44. package/lib/talons/CartPage/ProductListing/useProductListing.js +8 -13
  45. package/lib/talons/CartPage/ProductListing/useQuantity.js +2 -1
  46. package/lib/talons/CartPage/cartPage.gql.js +16 -0
  47. package/lib/talons/CartPage/cartPageFragments.gql.js +21 -0
  48. package/lib/talons/CartPage/useCartPage.js +7 -6
  49. package/lib/talons/CategoryList/categoryList.gql.js +11 -2
  50. package/lib/talons/CategoryList/useCategoryList.js +8 -1
  51. package/lib/talons/CategoryList/useCategoryTile.js +10 -4
  52. package/lib/talons/CategoryTree/categoryTree.gql.js +11 -2
  53. package/lib/talons/CategoryTree/useCategoryTree.js +12 -3
  54. package/lib/talons/CheckoutPage/AddressBook/addressBook.gql.js +34 -0
  55. package/lib/talons/CheckoutPage/AddressBook/addressBookFragments.gql.js +31 -0
  56. package/lib/talons/CheckoutPage/AddressBook/useAddressBook.js +16 -8
  57. package/lib/talons/CheckoutPage/PaymentInformation/braintreeSummary.gql.js +1 -1
  58. package/lib/talons/CheckoutPage/PaymentInformation/editModal.gql.js +16 -0
  59. package/lib/talons/CheckoutPage/PaymentInformation/paymentInformation.gql.js +4 -8
  60. package/lib/talons/CheckoutPage/PaymentInformation/paymentMethods.gql.js +17 -0
  61. package/lib/talons/CheckoutPage/PaymentInformation/useBraintreeSummary.js +1 -1
  62. package/lib/talons/CheckoutPage/PaymentInformation/useEditModal.js +6 -5
  63. package/lib/talons/CheckoutPage/PaymentInformation/usePaymentInformation.js +22 -16
  64. package/lib/talons/CheckoutPage/PaymentInformation/usePaymentMethods.js +6 -3
  65. package/lib/talons/CheckoutPage/ShippingInformation/AddressForm/customerForm.gql.js +55 -0
  66. package/lib/talons/CheckoutPage/ShippingInformation/AddressForm/guestForm.gql.js +44 -0
  67. package/lib/talons/CheckoutPage/ShippingInformation/AddressForm/useCustomerForm.js +11 -14
  68. package/lib/talons/CheckoutPage/ShippingInformation/AddressForm/useGuestForm.js +6 -7
  69. package/lib/talons/CheckoutPage/ShippingInformation/shippingInformation.gql.js +54 -0
  70. package/lib/talons/CheckoutPage/ShippingInformation/shippingInformationFragments.gql.js +25 -0
  71. package/lib/talons/CheckoutPage/ShippingInformation/useShippingInformation.js +11 -6
  72. package/lib/talons/CheckoutPage/ShippingMethod/shippingMethod.gql.js +64 -0
  73. package/lib/talons/CheckoutPage/ShippingMethod/shippingMethodFragments.gql.js +64 -0
  74. package/lib/talons/CheckoutPage/{useShippingMethod.js → ShippingMethod/useShippingMethod.js} +11 -8
  75. package/lib/talons/Cms/cmsPage.gql.js +2 -2
  76. package/lib/talons/Cms/useCmsPage.js +2 -2
  77. package/lib/talons/CommunicationsPage/useCommunicationsPage.js +1 -2
  78. package/lib/talons/CreateAccount/createAccount.gql.js +3 -0
  79. package/lib/talons/CreateAccount/useCreateAccount.js +2 -4
  80. package/lib/talons/CreateAccountPage/useCreateAccountPage.js +38 -9
  81. package/lib/talons/FilterModal/useFilterList.js +17 -3
  82. package/lib/talons/FilterModal/useFilterModal.js +4 -5
  83. package/lib/talons/FilterSidebar/useFilterSidebar.js +4 -5
  84. package/lib/talons/ForgotPasswordPage/useForgotPasswordPage.js +46 -0
  85. package/lib/talons/FormError/useFormError.js +10 -3
  86. package/lib/talons/Gallery/__fixtures__/apolloMocks.js +8 -6
  87. package/lib/talons/Gallery/addToCart.gql.js +17 -0
  88. package/lib/talons/Gallery/gallery.gql.ce.js +4 -3
  89. package/lib/talons/Gallery/gallery.gql.ee.js +4 -3
  90. package/lib/talons/Gallery/useAddToCartButton.js +81 -0
  91. package/lib/talons/Gallery/useGallery.js +4 -7
  92. package/lib/talons/Gallery/useGalleryItem.js +7 -1
  93. package/lib/talons/Header/useCartTrigger.js +14 -7
  94. package/lib/talons/Link/useLink.js +68 -0
  95. package/lib/talons/MagentoRoute/magentoRoute.gql.js +12 -3
  96. package/lib/talons/MagentoRoute/useMagentoRoute.js +115 -30
  97. package/lib/talons/MegaMenu/megaMenu.gql.js +10 -4
  98. package/lib/talons/MegaMenu/useMegaMenu.js +61 -11
  99. package/lib/talons/MegaMenu/useMegaMenuItem.js +61 -0
  100. package/lib/talons/MegaMenu/useSubMenu.js +20 -0
  101. package/lib/talons/MiniCart/ProductList/productListFragments.gql.js +0 -1
  102. package/lib/talons/MiniCart/miniCart.gql.js +4 -3
  103. package/lib/talons/MiniCart/useMiniCart.js +15 -12
  104. package/lib/talons/Navigation/useNavigation.js +6 -1
  105. package/lib/talons/OrderHistoryPage/orderRow.gql.js +0 -1
  106. package/lib/talons/OrderHistoryPage/useOrderHistoryPage.js +2 -14
  107. package/lib/talons/PageLoadingIndicator/usePageLoadingIndicator.js +52 -0
  108. package/lib/talons/Postcode/usePostcode.js +2 -1
  109. package/lib/talons/ProductFullDetail/useProductFullDetail.js +30 -5
  110. package/lib/talons/Region/useRegion.js +12 -10
  111. package/lib/talons/RootComponents/Category/categoryFragments.gql.js +5 -4
  112. package/lib/talons/RootComponents/Product/productDetailFragment.gql.js +7 -0
  113. package/lib/talons/SavedPaymentsPage/useSavedPaymentsPage.js +1 -11
  114. package/lib/talons/SearchBar/useAutocomplete.js +1 -1
  115. package/lib/talons/SearchBar/useSearchField.js +2 -1
  116. package/lib/talons/SearchBar/useSuggestedCategory.js +4 -6
  117. package/lib/talons/SearchPage/searchPage.gql.js +10 -9
  118. package/lib/talons/SignIn/signIn.gql.js +3 -0
  119. package/lib/talons/SignInPage/useSignInPage.js +63 -0
  120. package/lib/talons/Wishlist/AddToListButton/helpers/useSingleWishlist.js +2 -2
  121. package/lib/talons/Wishlist/AddToListButton/useAddToListButton.ee.js +2 -1
  122. package/lib/talons/WishlistPage/useActionMenu.js +39 -5
  123. package/lib/talons/WishlistPage/useWishlist.js +4 -4
  124. package/lib/talons/WishlistPage/useWishlistPage.js +3 -11
  125. package/lib/talons/WishlistPage/wishlist.gql.js +1 -1
  126. package/lib/talons/WishlistPage/wishlistConfig.gql.ce.js +14 -0
  127. package/lib/talons/WishlistPage/wishlistConfig.gql.ee.js +15 -0
  128. package/lib/targets/peregrine-declare.js +0 -4
  129. package/lib/util/deriveErrorMessage.js +12 -6
  130. package/lib/util/images.js +3 -2
  131. package/lib/util/isSupportedProductType.js +5 -0
  132. package/lib/util/magentoRouteData.js +9 -0
  133. package/package.json +8 -8
@@ -10,14 +10,11 @@ export const useGallery = (props = {}) => {
10
10
 
11
11
  useCustomerWishlistSkus();
12
12
 
13
- const { data: wishlistConfigData } = useQuery(
14
- operations.getWishlistConfigQuery,
15
- { fetchPolicy: 'cache-and-network' }
16
- );
13
+ const { data: storeConfigData } = useQuery(operations.getStoreConfigQuery, {
14
+ fetchPolicy: 'cache-and-network'
15
+ });
17
16
 
18
- const storeConfig = wishlistConfigData
19
- ? wishlistConfigData.storeConfig
20
- : null;
17
+ const storeConfig = storeConfigData ? storeConfigData.storeConfig : null;
21
18
 
22
19
  return {
23
20
  storeConfig
@@ -1,6 +1,12 @@
1
+ import { isSupportedProductType as isSupported } from '@magento/peregrine/lib/util/isSupportedProductType';
2
+
1
3
  export const useGalleryItem = (props = {}) => {
2
4
  const { item, storeConfig } = props;
3
5
 
6
+ const productType = item ? item.__typename : null;
7
+
8
+ const isSupportedProductType = isSupported(productType);
9
+
4
10
  const wishlistButtonProps =
5
11
  storeConfig && storeConfig.magento_wishlist_general_is_enabled === '1'
6
12
  ? {
@@ -12,5 +18,5 @@ export const useGalleryItem = (props = {}) => {
12
18
  }
13
19
  : null;
14
20
 
15
- return { ...props, wishlistButtonProps };
21
+ return { ...props, wishlistButtonProps, isSupportedProductType };
16
22
  };
@@ -1,6 +1,6 @@
1
- import { useCallback } from 'react';
1
+ import { useCallback, useState, useEffect } from 'react';
2
2
  import { useQuery } from '@apollo/client';
3
- import { useHistory } from 'react-router-dom';
3
+ import { useHistory, useLocation } from 'react-router-dom';
4
4
 
5
5
  import { useCartContext } from '@magento/peregrine/lib/context/cart';
6
6
  import { useDropdown } from '@magento/peregrine/lib/hooks/useDropdown';
@@ -30,13 +30,18 @@ export const useCartTrigger = props => {
30
30
  } = props;
31
31
 
32
32
  const [{ cartId }] = useCartContext();
33
+ const history = useHistory();
34
+ const location = useLocation();
35
+ const [isHidden, setIsHidden] = useState(() =>
36
+ DENIED_MINI_CART_ROUTES.includes(location.pathname)
37
+ );
38
+
33
39
  const {
34
40
  elementRef: miniCartRef,
35
41
  expanded: miniCartIsOpen,
36
42
  setExpanded: setMiniCartIsOpen,
37
43
  triggerRef: miniCartTriggerRef
38
44
  } = useDropdown();
39
- const history = useHistory();
40
45
 
41
46
  const { data } = useQuery(getItemCountQuery, {
42
47
  fetchPolicy: 'cache-and-network',
@@ -46,10 +51,8 @@ export const useCartTrigger = props => {
46
51
  },
47
52
  skip: !cartId
48
53
  });
54
+
49
55
  const itemCount = data ? data.cart.total_quantity : 0;
50
- const hideCartTrigger = DENIED_MINI_CART_ROUTES.includes(
51
- history.location.pathname
52
- );
53
56
 
54
57
  const handleTriggerClick = useCallback(() => {
55
58
  // Open the mini cart.
@@ -61,13 +64,17 @@ export const useCartTrigger = props => {
61
64
  history.push('/cart');
62
65
  }, [history]);
63
66
 
67
+ useEffect(() => {
68
+ setIsHidden(DENIED_MINI_CART_ROUTES.includes(location.pathname));
69
+ }, [location]);
70
+
64
71
  return {
65
72
  handleLinkClick,
66
73
  handleTriggerClick,
67
74
  itemCount,
68
75
  miniCartIsOpen,
69
76
  miniCartRef,
70
- hideCartTrigger,
77
+ hideCartTrigger: isHidden,
71
78
  setMiniCartIsOpen,
72
79
  miniCartTriggerRef
73
80
  };
@@ -0,0 +1,68 @@
1
+ import { useEffect, useRef } from 'react';
2
+ import { useLazyQuery } from '@apollo/client';
3
+ import useIntersectionObserver from '../../hooks/useIntersectionObserver';
4
+ import resourceUrl from '../../util/makeUrl';
5
+ import mergeOperations from '../../util/shallowMerge';
6
+ import DEFAULT_OPERATIONS from '../MagentoRoute/magentoRoute.gql';
7
+
8
+ const useLink = (props, passedOperations = {}) => {
9
+ const { prefetchType: shouldPrefetch, innerRef: originalRef, to } = props;
10
+ const operations = shouldPrefetch
11
+ ? mergeOperations(DEFAULT_OPERATIONS, passedOperations)
12
+ : {};
13
+
14
+ const intersectionObserver = useIntersectionObserver();
15
+ const { resolveUrlQuery } = operations;
16
+ const generatedRef = useRef();
17
+ const elementRef =
18
+ originalRef || !shouldPrefetch ? originalRef : generatedRef;
19
+ const [runQuery, { called: pageTypeCalled }] = useLazyQuery(
20
+ resolveUrlQuery
21
+ );
22
+ const linkPath = shouldPrefetch ? resourceUrl(to) : null;
23
+
24
+ useEffect(() => {
25
+ if (
26
+ !shouldPrefetch ||
27
+ pageTypeCalled ||
28
+ !runQuery ||
29
+ !elementRef.current ||
30
+ !intersectionObserver
31
+ ) {
32
+ return;
33
+ }
34
+
35
+ const htmlElement = elementRef.current;
36
+
37
+ const onIntersection = entries => {
38
+ if (entries.some(entry => entry.isIntersecting)) {
39
+ observer.unobserve(htmlElement);
40
+
41
+ runQuery({
42
+ variables: { url: linkPath }
43
+ });
44
+ }
45
+ };
46
+ const observer = new intersectionObserver(onIntersection);
47
+ observer.observe(htmlElement);
48
+
49
+ return () => {
50
+ if (htmlElement) {
51
+ observer.unobserve(htmlElement);
52
+ }
53
+ };
54
+ }, [
55
+ shouldPrefetch,
56
+ elementRef,
57
+ pageTypeCalled,
58
+ linkPath,
59
+ intersectionObserver,
60
+ runQuery
61
+ ]);
62
+
63
+ return {
64
+ ref: elementRef
65
+ };
66
+ };
67
+
68
+ export default useLink;
@@ -2,11 +2,20 @@ import { gql } from '@apollo/client';
2
2
 
3
3
  export const RESOLVE_URL = gql`
4
4
  query ResolveURL($url: String!) {
5
- urlResolver(url: $url) {
6
- id
5
+ route(url: $url) {
7
6
  relative_url
8
- redirectCode
7
+ redirect_code
9
8
  type
9
+ ... on CmsPage {
10
+ identifier
11
+ }
12
+ ... on ProductInterface {
13
+ id
14
+ __typename
15
+ }
16
+ ... on CategoryInterface {
17
+ id
18
+ }
10
19
  }
11
20
  }
12
21
  `;
@@ -1,12 +1,24 @@
1
- import { useCallback, useEffect } from 'react';
1
+ import { useCallback, useEffect, useRef } from 'react';
2
2
  import { useHistory, useLocation } from 'react-router-dom';
3
- import { useQuery } from '@apollo/client';
4
- import { useRootComponents } from '@magento/peregrine/lib/context/rootComponents';
5
- import mergeOperations from '@magento/peregrine/lib/util/shallowMerge';
3
+ import { useLazyQuery } from '@apollo/client';
4
+ import { useRootComponents } from '../../context/rootComponents';
5
+ import mergeOperations from '../../util/shallowMerge';
6
+ import { getComponentData } from '../../util/magentoRouteData';
7
+ import { useAppContext } from '../../context/app';
6
8
 
7
9
  import { getRootComponent, isRedirect } from './helpers';
8
10
  import DEFAULT_OPERATIONS from './magentoRoute.gql';
9
11
 
12
+ const getInlinedPageData = () => {
13
+ return globalThis.INLINED_PAGE_TYPE && globalThis.INLINED_PAGE_TYPE.type
14
+ ? globalThis.INLINED_PAGE_TYPE
15
+ : null;
16
+ };
17
+
18
+ const resetInlinedPageData = () => {
19
+ globalThis.INLINED_PAGE_TYPE = false;
20
+ };
21
+
10
22
  export const useMagentoRoute = (props = {}) => {
11
23
  const operations = mergeOperations(DEFAULT_OPERATIONS, props.operations);
12
24
  const { resolveUrlQuery } = operations;
@@ -14,6 +26,15 @@ export const useMagentoRoute = (props = {}) => {
14
26
  const { pathname } = useLocation();
15
27
  const [componentMap, setComponentMap] = useRootComponents();
16
28
 
29
+ const initialized = useRef(false);
30
+ const fetchedPathname = useRef(null);
31
+ const fetching = useRef(false);
32
+
33
+ const [appState, appApi] = useAppContext();
34
+ const { actions: appActions } = appApi;
35
+ const { nextRootComponent } = appState;
36
+ const { setNextRootComponent, setPageLoading } = appActions;
37
+
17
38
  const setComponent = useCallback(
18
39
  (key, value) => {
19
40
  setComponentMap(prevMap => new Map(prevMap).set(key, value));
@@ -21,23 +42,55 @@ export const useMagentoRoute = (props = {}) => {
21
42
  [setComponentMap]
22
43
  );
23
44
 
24
- const queryResult = useQuery(resolveUrlQuery, {
25
- fetchPolicy: 'cache-and-network',
26
- nextFetchPolicy: 'cache-first',
27
- variables: { url: pathname }
45
+ const component = componentMap.get(pathname);
46
+
47
+ const [runQuery, queryResult] = useLazyQuery(resolveUrlQuery, {
48
+ onCompleted: async ({ route }) => {
49
+ fetching.current = false;
50
+ if (!component) {
51
+ const { type, ...routeData } = route || {};
52
+ try {
53
+ const rootComponent = await getRootComponent(type);
54
+ setComponent(pathname, {
55
+ component: rootComponent,
56
+ ...getComponentData(routeData),
57
+ type
58
+ });
59
+ } catch (error) {
60
+ setComponent(pathname, error);
61
+ }
62
+ }
63
+ },
64
+ onError: () => {
65
+ fetching.current = false;
66
+ }
28
67
  });
29
68
 
69
+ useEffect(() => {
70
+ if (initialized.current || !getInlinedPageData()) {
71
+ fetching.current = true;
72
+ runQuery({
73
+ fetchPolicy: 'cache-and-network',
74
+ nextFetchPolicy: 'cache-first',
75
+ variables: { url: pathname }
76
+ });
77
+ fetchedPathname.current = pathname;
78
+ }
79
+ }, [initialized, pathname]); // eslint-disable-line react-hooks/exhaustive-deps
80
+
30
81
  // destructure the query result
31
- const { data, error, loading } = queryResult;
32
- const { urlResolver } = data || {};
33
- const { id, redirectCode, relative_url, type } = urlResolver || {};
82
+ const { data, error } = queryResult;
83
+ const { route } = data || {};
84
+ const { id, redirect_code, relative_url, type } = route || {};
34
85
 
35
86
  // evaluate both results and determine the response type
36
- const component = componentMap.get(pathname);
37
- const empty = !urlResolver || !type || id < 1;
38
- const redirect = isRedirect(redirectCode);
87
+ const empty = !route || !type || id < 1;
88
+ const redirect = isRedirect(redirect_code);
39
89
  const fetchError = component instanceof Error && component;
40
90
  const routeError = fetchError || error;
91
+ const isInitialized = initialized.current || !getInlinedPageData();
92
+
93
+ let showPageLoader = false;
41
94
  let routeData;
42
95
 
43
96
  if (component && !fetchError) {
@@ -48,32 +101,53 @@ export const useMagentoRoute = (props = {}) => {
48
101
  routeData = { hasError: true, routeError };
49
102
  } else if (redirect) {
50
103
  // REDIRECT
51
- routeData = { isRedirect: true, relativeUrl: relative_url };
52
- } else if (empty && !loading) {
104
+ routeData = {
105
+ isRedirect: true,
106
+ relativeUrl: relative_url.startsWith('/')
107
+ ? relative_url
108
+ : '/' + relative_url
109
+ };
110
+ } else if (
111
+ empty &&
112
+ fetchedPathname.current === pathname &&
113
+ !fetching.current
114
+ ) {
53
115
  // NOT FOUND
54
116
  routeData = { isNotFound: true };
117
+ } else if (nextRootComponent) {
118
+ // LOADING with full page shimmer
119
+ showPageLoader = true;
120
+ routeData = { isLoading: true, shimmer: nextRootComponent };
55
121
  } else {
56
122
  // LOADING
57
- routeData = { isLoading: true };
123
+ const isInitialLoad = !isInitialized;
124
+ routeData = { isLoading: true, initial: isInitialLoad };
58
125
  }
59
126
 
60
- // fetch a component if necessary
61
127
  useEffect(() => {
62
128
  (async () => {
63
- // don't fetch if we don't have data yet
64
- if (loading || empty) return;
65
-
66
- // don't fetch more than once
67
- if (component) return;
68
-
69
- try {
70
- const component = await getRootComponent(type);
71
- setComponent(pathname, { component, id, type });
72
- } catch (error) {
73
- setComponent(pathname, error);
129
+ const inlinedData = getInlinedPageData();
130
+ if (inlinedData) {
131
+ try {
132
+ const componentType = inlinedData.type;
133
+ const rootComponent = await getRootComponent(componentType);
134
+ setComponent(pathname, {
135
+ component: rootComponent,
136
+ type: componentType,
137
+ ...getComponentData(inlinedData)
138
+ });
139
+ } catch (error) {
140
+ setComponent(pathname, error);
141
+ }
74
142
  }
143
+ initialized.current = true;
75
144
  })();
76
- }, [component, empty, id, loading, pathname, setComponent, type]);
145
+
146
+ return () => {
147
+ // Unmount
148
+ resetInlinedPageData();
149
+ };
150
+ }, []); // eslint-disable-line react-hooks/exhaustive-deps
77
151
 
78
152
  // perform a redirect if necesssary
79
153
  useEffect(() => {
@@ -82,5 +156,16 @@ export const useMagentoRoute = (props = {}) => {
82
156
  }
83
157
  }, [pathname, replace, routeData]);
84
158
 
159
+ useEffect(() => {
160
+ if (component) {
161
+ // Reset loading shimmer whenever component resolves
162
+ setNextRootComponent(null);
163
+ }
164
+ }, [component, pathname, setNextRootComponent]);
165
+
166
+ useEffect(() => {
167
+ setPageLoading(showPageLoader);
168
+ }, [showPageLoader, setPageLoading]);
169
+
85
170
  return routeData;
86
171
  };
@@ -1,4 +1,12 @@
1
1
  import { gql } from '@apollo/client';
2
+ export const GET_STORE_CONFIG_DATA = gql`
3
+ query GetStoreConfigForMegaMenu {
4
+ storeConfig {
5
+ id
6
+ category_url_suffix
7
+ }
8
+ }
9
+ `;
2
10
 
3
11
  export const GET_MEGA_MENU = gql`
4
12
  query getMegaMenu {
@@ -11,21 +19,18 @@ export const GET_MEGA_MENU = gql`
11
19
  name
12
20
  position
13
21
  url_path
14
- url_suffix
15
22
  children {
16
23
  id
17
24
  include_in_menu
18
25
  name
19
26
  position
20
27
  url_path
21
- url_suffix
22
28
  children {
23
29
  id
24
30
  include_in_menu
25
31
  name
26
32
  position
27
33
  url_path
28
- url_suffix
29
34
  }
30
35
  }
31
36
  }
@@ -34,5 +39,6 @@ export const GET_MEGA_MENU = gql`
34
39
  `;
35
40
 
36
41
  export default {
37
- getMegaMenuQuery: GET_MEGA_MENU
42
+ getMegaMenuQuery: GET_MEGA_MENU,
43
+ getStoreConfigQuery: GET_STORE_CONFIG_DATA
38
44
  };
@@ -1,29 +1,46 @@
1
- import mergeOperations from '../../util/shallowMerge';
2
- import DEFAULT_OPERATIONS from './megaMenu.gql';
3
- import { useQuery } from '@apollo/client';
4
1
  import { useMemo, useState, useCallback, useEffect } from 'react';
5
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';
6
10
 
7
11
  /**
8
12
  * The useMegaMenu talon complements the MegaMenu component.
9
13
  *
10
14
  * @param {Object} props
11
15
  * @param {*} props.operations GraphQL operations used by talons
16
+ * @param {React.RefObject} props.mainNavRef Reference to main navigation DOM node
12
17
  *
13
18
  * @return {MegaMenuTalonProps}
14
19
  */
15
20
  export const useMegaMenu = (props = {}) => {
16
21
  const operations = mergeOperations(DEFAULT_OPERATIONS, props.operations);
17
- const { getMegaMenuQuery } = operations;
22
+ const { getMegaMenuQuery, getStoreConfigQuery } = operations;
18
23
 
19
24
  const location = useLocation();
20
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
+ });
21
32
 
22
33
  const { data } = useQuery(getMegaMenuQuery, {
23
34
  fetchPolicy: 'cache-and-network',
24
35
  nextFetchPolicy: 'cache-first'
25
36
  });
26
37
 
38
+ const categoryUrlSuffix = useMemo(() => {
39
+ if (storeConfigData) {
40
+ return storeConfigData.storeConfig.category_url_suffix;
41
+ }
42
+ }, [storeConfigData]);
43
+
27
44
  /**
28
45
  * Check if category should be visible on the storefront.
29
46
  *
@@ -40,15 +57,16 @@ export const useMegaMenu = (props = {}) => {
40
57
  * @param {MegaMenuCategory} category
41
58
  * @returns {boolean}
42
59
  */
60
+
43
61
  const isActive = useCallback(
44
- ({ url_path, url_suffix }) => {
62
+ ({ url_path }) => {
45
63
  if (!url_path) return false;
46
64
 
47
- const categoryUrlPath = `/${url_path}${url_suffix || ''}`;
65
+ const categoryUrlPath = `/${url_path}${categoryUrlSuffix || ''}`;
48
66
 
49
67
  return location.pathname === categoryUrlPath;
50
68
  },
51
- [location.pathname]
69
+ [location.pathname, categoryUrlSuffix]
52
70
  );
53
71
 
54
72
  /**
@@ -106,6 +124,21 @@ export const useMegaMenu = (props = {}) => {
106
124
  [isActive]
107
125
  );
108
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, 'mousedown', handleClickOutside);
135
+ useEventListener(globalThis, 'mouseout', handleClickOutside);
136
+ useEventListener(globalThis, 'keydown', handleClickOutside);
137
+
138
+ const handleSubMenuFocus = useCallback(() => {
139
+ setSubMenuState(true);
140
+ }, [setSubMenuState]);
141
+
109
142
  useEffect(() => {
110
143
  const activeCategory = findActiveCategory(
111
144
  location.pathname,
@@ -119,9 +152,22 @@ export const useMegaMenu = (props = {}) => {
119
152
  }
120
153
  }, [findActiveCategory, location.pathname, megaMenuData]);
121
154
 
155
+ /**
156
+ * Sets next root component to show proper loading effect
157
+ *
158
+ * @returns {void}
159
+ */
160
+ const { setShimmerType } = useInternalLink('category');
161
+
122
162
  return {
123
163
  megaMenuData,
124
- activeCategoryId
164
+ activeCategoryId,
165
+ categoryUrlSuffix,
166
+ handleClickOutside,
167
+ subMenuState,
168
+ disableFocus,
169
+ handleSubMenuFocus,
170
+ handleNavigate: setShimmerType
125
171
  };
126
172
  };
127
173
 
@@ -133,8 +179,13 @@ export const useMegaMenu = (props = {}) => {
133
179
  * @property {MegaMenuCategory} megaMenuData - The Object with categories contains only categories
134
180
  * with the include_in_menu = 1 flag. The categories are sorted
135
181
  * based on the field position.
136
- * @property {int} loading whether the regions are loading
137
- *
182
+ * @property {int} activeCategoryId returns the currently selected category id.
183
+ * @property {String} categoryUrlSuffix store's category url suffix to construct category URL
184
+ * @property {Function} handleClickOutside function to handle mouse/key events.
185
+ * @property {Boolean} subMenuState maintaining sub-menu open/close state
186
+ * @property {Boolean} disableFocus state to disable focus
187
+ * @property {Function} handleSubMenuFocus toggle function to handle sub-menu focus
188
+ * @property {function} handleNavigate - callback to fire on link click
138
189
  */
139
190
 
140
191
  /**
@@ -146,6 +197,5 @@ export const useMegaMenu = (props = {}) => {
146
197
  * @property {String} name - name of the category
147
198
  * @property {int} position - value used for sorting
148
199
  * @property {String} url_path - URL path for a category
149
- * @property {String} url_suffix - URL Suffix for the category
150
200
  * @property {MegaMenuCategory} children - child category
151
201
  */
@@ -0,0 +1,61 @@
1
+ import { useCallback, useMemo, useState } from 'react';
2
+
3
+ export const useMegaMenuItem = props => {
4
+ const { category, activeCategoryId, subMenuState, disableFocus } = props;
5
+
6
+ const [isFocused, setIsFocused] = useState(false);
7
+ const isActive = category.id === activeCategoryId;
8
+
9
+ const handleCloseSubMenu = useCallback(() => {
10
+ setIsFocused(false);
11
+ }, [setIsFocused]);
12
+
13
+ const isMenuActive = useMemo(() => {
14
+ if (!isFocused) {
15
+ return false;
16
+ }
17
+ if (subMenuState) {
18
+ return true;
19
+ } else if (disableFocus) {
20
+ setIsFocused(false);
21
+ }
22
+ return false;
23
+ }, [isFocused, subMenuState, disableFocus]);
24
+
25
+ const handleKeyDown = useCallback(
26
+ event => {
27
+ const { key: pressedKey, shiftKey } = event;
28
+
29
+ // checking down arrow and spacebar
30
+ if (pressedKey === 'ArrowDown' || pressedKey === ' ') {
31
+ event.preventDefault();
32
+ if (category.children.length) {
33
+ setIsFocused(true);
34
+ } else {
35
+ setIsFocused(false);
36
+ }
37
+
38
+ return;
39
+ }
40
+
41
+ //checking up arrow or escape
42
+ if (pressedKey === 'ArrowUp' || pressedKey === 'Escape') {
43
+ setIsFocused(false);
44
+ }
45
+
46
+ //checking Tab with Shift
47
+ if (shiftKey && pressedKey === 'Tab') {
48
+ setIsFocused(false);
49
+ }
50
+ },
51
+ [category.children.length]
52
+ );
53
+
54
+ return {
55
+ isFocused,
56
+ isActive,
57
+ handleCloseSubMenu,
58
+ isMenuActive,
59
+ handleKeyDown
60
+ };
61
+ };
@@ -0,0 +1,20 @@
1
+ import { useKeyboard } from 'react-aria';
2
+
3
+ export const useSubMenu = props => {
4
+ const { isFocused, subMenuState, handleCloseSubMenu } = props;
5
+
6
+ const { keyboardProps } = useKeyboard({
7
+ onKeyDown: e => {
8
+ //checking for Tab without Shift
9
+ if (!e.shiftKey && e.key === 'Tab') {
10
+ e.target.addEventListener('blur', handleCloseSubMenu);
11
+ } else {
12
+ e.target.removeEventListener('blur', handleCloseSubMenu);
13
+ }
14
+ }
15
+ });
16
+
17
+ const isSubMenuActive = isFocused && subMenuState;
18
+
19
+ return { keyboardProps, isSubMenuActive };
20
+ };
@@ -9,7 +9,6 @@ export const ProductListFragment = gql`
9
9
  id
10
10
  name
11
11
  url_key
12
- url_suffix
13
12
  thumbnail {
14
13
  url
15
14
  }
@@ -1,14 +1,15 @@
1
1
  import { gql } from '@apollo/client';
2
2
 
3
- export const GET_CONFIGURABLE_THUMBNAIL_SOURCE = gql`
4
- query getConfigurableThumbnailSource {
3
+ export const GET_STORE_CONFIG_DATA = gql`
4
+ query getStoreConfigForMiniCart {
5
5
  storeConfig {
6
6
  id
7
+ product_url_suffix
7
8
  configurable_thumbnail_source
8
9
  }
9
10
  }
10
11
  `;
11
12
 
12
13
  export default {
13
- getConfigurableThumbnailSource: GET_CONFIGURABLE_THUMBNAIL_SOURCE
14
+ getStoreConfigQuery: GET_STORE_CONFIG_DATA
14
15
  };