@salesforce/retail-react-app 1.0.0-preview.0

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 (425) hide show
  1. package/.eslintignore +7 -0
  2. package/.eslintrc.js +25 -0
  3. package/.prettierignore +4 -0
  4. package/.prettierrc.yaml +7 -0
  5. package/CHANGELOG.md +173 -0
  6. package/LICENSE +14 -0
  7. package/README.md +48 -0
  8. package/app/assets/svg/account.svg +3 -0
  9. package/app/assets/svg/alert.svg +3 -0
  10. package/app/assets/svg/basket.svg +3 -0
  11. package/app/assets/svg/brand-logo.svg +10 -0
  12. package/app/assets/svg/cc-amex.svg +7 -0
  13. package/app/assets/svg/cc-cvv.svg +8 -0
  14. package/app/assets/svg/cc-discover.svg +14 -0
  15. package/app/assets/svg/cc-mastercard.svg +8 -0
  16. package/app/assets/svg/cc-visa.svg +11 -0
  17. package/app/assets/svg/check-circle.svg +3 -0
  18. package/app/assets/svg/check.svg +3 -0
  19. package/app/assets/svg/chevron-down.svg +3 -0
  20. package/app/assets/svg/chevron-left.svg +3 -0
  21. package/app/assets/svg/chevron-right.svg +3 -0
  22. package/app/assets/svg/chevron-up.svg +3 -0
  23. package/app/assets/svg/close.svg +3 -0
  24. package/app/assets/svg/dashboard.svg +4 -0
  25. package/app/assets/svg/figma-logo.svg +14 -0
  26. package/app/assets/svg/file.svg +3 -0
  27. package/app/assets/svg/filter.svg +3 -0
  28. package/app/assets/svg/flag-ca.svg +5 -0
  29. package/app/assets/svg/flag-cn.svg +19 -0
  30. package/app/assets/svg/flag-fr.svg +19 -0
  31. package/app/assets/svg/flag-gb.svg +16 -0
  32. package/app/assets/svg/flag-it.svg +29 -0
  33. package/app/assets/svg/flag-jp.svg +10 -0
  34. package/app/assets/svg/flag-us.svg +7 -0
  35. package/app/assets/svg/github-logo.svg +40 -0
  36. package/app/assets/svg/hamburger.svg +8 -0
  37. package/app/assets/svg/heart-solid.svg +7 -0
  38. package/app/assets/svg/heart.svg +3 -0
  39. package/app/assets/svg/info.svg +3 -0
  40. package/app/assets/svg/like.svg +4 -0
  41. package/app/assets/svg/location.svg +3 -0
  42. package/app/assets/svg/lock.svg +3 -0
  43. package/app/assets/svg/paypal.svg +19 -0
  44. package/app/assets/svg/plug.svg +3 -0
  45. package/app/assets/svg/plus.svg +3 -0
  46. package/app/assets/svg/receipt.svg +3 -0
  47. package/app/assets/svg/search.svg +8 -0
  48. package/app/assets/svg/signout.svg +3 -0
  49. package/app/assets/svg/social-facebook.svg +3 -0
  50. package/app/assets/svg/social-instagram.svg +3 -0
  51. package/app/assets/svg/social-pinterest.svg +4 -0
  52. package/app/assets/svg/social-twitter.svg +3 -0
  53. package/app/assets/svg/social-youtube.svg +3 -0
  54. package/app/assets/svg/user.svg +3 -0
  55. package/app/assets/svg/visibility-off.svg +5 -0
  56. package/app/assets/svg/visibility.svg +3 -0
  57. package/app/components/_app/index.jsx +401 -0
  58. package/app/components/_app/index.test.js +85 -0
  59. package/app/components/_app/partials/above-header.jsx +10 -0
  60. package/app/components/_app-config/index.jsx +125 -0
  61. package/app/components/_app-config/index.test.js +77 -0
  62. package/app/components/_error/index.jsx +142 -0
  63. package/app/components/_error/index.test.js +25 -0
  64. package/app/components/action-card/index.jsx +75 -0
  65. package/app/components/address-display/index.jsx +30 -0
  66. package/app/components/basic-tile/index.jsx +65 -0
  67. package/app/components/basic-tile/index.test.js +23 -0
  68. package/app/components/breadcrumb/index.jsx +67 -0
  69. package/app/components/breadcrumb/index.test.js +30 -0
  70. package/app/components/confirmation-modal/index.jsx +111 -0
  71. package/app/components/confirmation-modal/index.test.js +98 -0
  72. package/app/components/drawer-menu/index.jsx +405 -0
  73. package/app/components/drawer-menu/index.test.js +33 -0
  74. package/app/components/dynamic-image/index.jsx +56 -0
  75. package/app/components/field/index.jsx +161 -0
  76. package/app/components/footer/index.jsx +269 -0
  77. package/app/components/footer/index.test.js +22 -0
  78. package/app/components/forms/address-fields.jsx +49 -0
  79. package/app/components/forms/credit-card-fields.jsx +149 -0
  80. package/app/components/forms/form-action-buttons.jsx +55 -0
  81. package/app/components/forms/login-fields.jsx +31 -0
  82. package/app/components/forms/password-requirements.jsx +99 -0
  83. package/app/components/forms/post-checkout-registration-fields.jsx +43 -0
  84. package/app/components/forms/profile-fields.jsx +36 -0
  85. package/app/components/forms/promo-code-fields.jsx +43 -0
  86. package/app/components/forms/registration-fields.jsx +42 -0
  87. package/app/components/forms/reset-password-fields.jsx +31 -0
  88. package/app/components/forms/state-province-options.jsx +75 -0
  89. package/app/components/forms/update-password-fields.jsx +49 -0
  90. package/app/components/forms/useAddressFields.jsx +196 -0
  91. package/app/components/forms/useCreditCardFields.jsx +146 -0
  92. package/app/components/forms/useLoginFields.jsx +52 -0
  93. package/app/components/forms/useProfileFields.jsx +95 -0
  94. package/app/components/forms/usePromoCodeFields.jsx +39 -0
  95. package/app/components/forms/useRegistrationFields.jsx +136 -0
  96. package/app/components/forms/useResetPasswordFields.jsx +40 -0
  97. package/app/components/forms/useUpdatePasswordFields.jsx +89 -0
  98. package/app/components/header/index.jsx +290 -0
  99. package/app/components/header/index.test.js +217 -0
  100. package/app/components/hero/index.jsx +84 -0
  101. package/app/components/hero/index.test.js +40 -0
  102. package/app/components/icons/index.jsx +158 -0
  103. package/app/components/icons/index.test.js +20 -0
  104. package/app/components/image-gallery/index.jsx +176 -0
  105. package/app/components/image-gallery/index.test.js +485 -0
  106. package/app/components/item-variant/index.jsx +33 -0
  107. package/app/components/item-variant/item-attributes.jsx +107 -0
  108. package/app/components/item-variant/item-image.jsx +73 -0
  109. package/app/components/item-variant/item-name.jsx +28 -0
  110. package/app/components/item-variant/item-price.jsx +117 -0
  111. package/app/components/link/index.jsx +32 -0
  112. package/app/components/link/index.test.js +72 -0
  113. package/app/components/links-list/index.jsx +89 -0
  114. package/app/components/links-list/index.test.js +62 -0
  115. package/app/components/list-menu/index.jsx +280 -0
  116. package/app/components/list-menu/index.test.js +44 -0
  117. package/app/components/loading-spinner/index.jsx +46 -0
  118. package/app/components/locale-selector/index.jsx +124 -0
  119. package/app/components/locale-selector/index.test.js +37 -0
  120. package/app/components/locale-text/index.jsx +97 -0
  121. package/app/components/locale-text/index.test.js +36 -0
  122. package/app/components/login/index.jsx +96 -0
  123. package/app/components/nested-accordion/index.jsx +185 -0
  124. package/app/components/nested-accordion/index.test.js +98 -0
  125. package/app/components/offline-banner/index.jsx +40 -0
  126. package/app/components/offline-banner/index.test.js +15 -0
  127. package/app/components/offline-boundary/index.jsx +104 -0
  128. package/app/components/offline-boundary/index.test.js +123 -0
  129. package/app/components/order-summary/index.jsx +331 -0
  130. package/app/components/page-action-placeholder/index.jsx +50 -0
  131. package/app/components/pagination/index.jsx +134 -0
  132. package/app/components/pagination/index.test.js +25 -0
  133. package/app/components/product-item/index.jsx +146 -0
  134. package/app/components/product-item/index.test.js +38 -0
  135. package/app/components/product-scroller/index.jsx +172 -0
  136. package/app/components/product-scroller/index.test.js +98 -0
  137. package/app/components/product-tile/index.jsx +195 -0
  138. package/app/components/product-tile/index.test.js +96 -0
  139. package/app/components/product-view/index.jsx +538 -0
  140. package/app/components/product-view/index.test.js +224 -0
  141. package/app/components/product-view-modal/index.jsx +48 -0
  142. package/app/components/product-view-modal/index.test.js +72 -0
  143. package/app/components/promo-code/index.jsx +162 -0
  144. package/app/components/promo-popover/index.jsx +83 -0
  145. package/app/components/quantity-picker/index.jsx +58 -0
  146. package/app/components/radio-card/index.jsx +75 -0
  147. package/app/components/recommended-products/index.jsx +227 -0
  148. package/app/components/register/index.jsx +114 -0
  149. package/app/components/reset-password/index.jsx +87 -0
  150. package/app/components/responsive/index.jsx +29 -0
  151. package/app/components/scroll-to-top/index.jsx +24 -0
  152. package/app/components/scroll-to-top/index.test.js +46 -0
  153. package/app/components/search/index.jsx +279 -0
  154. package/app/components/search/index.test.js +127 -0
  155. package/app/components/search/partials/recent-searches.jsx +76 -0
  156. package/app/components/search/partials/search-suggestions.jsx +45 -0
  157. package/app/components/search/partials/suggestions.jsx +43 -0
  158. package/app/components/section/index.jsx +68 -0
  159. package/app/components/seo/index.jsx +33 -0
  160. package/app/components/social-icons/index.jsx +101 -0
  161. package/app/components/social-icons/index.test.js +30 -0
  162. package/app/components/swatch-group/index.jsx +77 -0
  163. package/app/components/swatch-group/index.test.js +136 -0
  164. package/app/components/swatch-group/swatch.jsx +94 -0
  165. package/app/components/toggle-card/index.jsx +97 -0
  166. package/app/components/with-registration/index.jsx +58 -0
  167. package/app/components/with-registration/index.test.js +85 -0
  168. package/app/constants.js +109 -0
  169. package/app/contexts/index.js +92 -0
  170. package/app/hooks/einstein-mock-data.js +916 -0
  171. package/app/hooks/index.js +17 -0
  172. package/app/hooks/use-add-to-cart-modal.js +344 -0
  173. package/app/hooks/use-add-to-cart-modal.test.js +625 -0
  174. package/app/hooks/use-auth-modal.js +337 -0
  175. package/app/hooks/use-auth-modal.test.js +365 -0
  176. package/app/hooks/use-currency.js +20 -0
  177. package/app/hooks/use-currency.test.js +41 -0
  178. package/app/hooks/use-current-basket.js +39 -0
  179. package/app/hooks/use-current-customer.js +29 -0
  180. package/app/hooks/use-derived-product.js +77 -0
  181. package/app/hooks/use-derived-product.test.js +69 -0
  182. package/app/hooks/use-einstein.js +512 -0
  183. package/app/hooks/use-einstein.test.js +224 -0
  184. package/app/hooks/use-intersection-observer.js +64 -0
  185. package/app/hooks/use-limit-urls.js +31 -0
  186. package/app/hooks/use-limit-urls.test.js +40 -0
  187. package/app/hooks/use-multi-site.js +36 -0
  188. package/app/hooks/use-multi-site.test.js +53 -0
  189. package/app/hooks/use-navigation.js +37 -0
  190. package/app/hooks/use-navigation.test.js +109 -0
  191. package/app/hooks/use-page-urls.js +35 -0
  192. package/app/hooks/use-page-urls.test.js +39 -0
  193. package/app/hooks/use-pdp-search-params.js +16 -0
  194. package/app/hooks/use-pdp-search-params.test.js +52 -0
  195. package/app/hooks/use-previous.js +17 -0
  196. package/app/hooks/use-product-view-modal.js +93 -0
  197. package/app/hooks/use-product-view-modal.test.js +172 -0
  198. package/app/hooks/use-search-params.js +96 -0
  199. package/app/hooks/use-search-params.test.js +91 -0
  200. package/app/hooks/use-sort-urls.js +33 -0
  201. package/app/hooks/use-sort-urls.test.js +42 -0
  202. package/app/hooks/use-toast.js +68 -0
  203. package/app/hooks/use-toast.test.js +58 -0
  204. package/app/hooks/use-variant.js +32 -0
  205. package/app/hooks/use-variant.test.js +81 -0
  206. package/app/hooks/use-variation-attributes.js +138 -0
  207. package/app/hooks/use-variation-attributes.test.js +119 -0
  208. package/app/hooks/use-variation-params.js +31 -0
  209. package/app/hooks/use-variation-params.test.js +73 -0
  210. package/app/hooks/use-wish-list.js +42 -0
  211. package/app/main.jsx +14 -0
  212. package/app/mocks/basket-with-suit.js +146 -0
  213. package/app/mocks/empty-basket.js +39 -0
  214. package/app/mocks/mock-data.js +5632 -0
  215. package/app/mocks/product-set-winter-lookM.js +1224 -0
  216. package/app/mocks/searchResults.js +144 -0
  217. package/app/mocks/variant-750518699578M.js +434 -0
  218. package/app/page-designer/README.md +102 -0
  219. package/app/page-designer/assets/image-tile/index.jsx +51 -0
  220. package/app/page-designer/assets/image-tile/index.test.js +30 -0
  221. package/app/page-designer/assets/image-with-text/index.jsx +140 -0
  222. package/app/page-designer/assets/image-with-text/index.test.js +38 -0
  223. package/app/page-designer/assets/index.js +9 -0
  224. package/app/page-designer/index.js +10 -0
  225. package/app/page-designer/layouts/carousel/index.jsx +222 -0
  226. package/app/page-designer/layouts/carousel/index.test.js +43 -0
  227. package/app/page-designer/layouts/index.js +14 -0
  228. package/app/page-designer/layouts/mobileGrid1r1c/index.jsx +36 -0
  229. package/app/page-designer/layouts/mobileGrid1r1c/index.test.js +35 -0
  230. package/app/page-designer/layouts/mobileGrid2r1c/index.jsx +37 -0
  231. package/app/page-designer/layouts/mobileGrid2r1c/index.test.js +47 -0
  232. package/app/page-designer/layouts/mobileGrid2r2c/index.jsx +37 -0
  233. package/app/page-designer/layouts/mobileGrid2r2c/index.test.js +71 -0
  234. package/app/page-designer/layouts/mobileGrid2r3c/index.jsx +37 -0
  235. package/app/page-designer/layouts/mobileGrid2r3c/index.test.js +95 -0
  236. package/app/page-designer/layouts/mobileGrid3r1c/index.jsx +37 -0
  237. package/app/page-designer/layouts/mobileGrid3r1c/index.test.js +59 -0
  238. package/app/page-designer/layouts/mobileGrid3r2c/index.jsx +37 -0
  239. package/app/page-designer/layouts/mobileGrid3r2c/index.test.js +95 -0
  240. package/app/page-designer/utils.js +14 -0
  241. package/app/pages/account/addresses.jsx +382 -0
  242. package/app/pages/account/addresses.test.js +120 -0
  243. package/app/pages/account/constant.js +57 -0
  244. package/app/pages/account/index.jsx +237 -0
  245. package/app/pages/account/index.test.js +188 -0
  246. package/app/pages/account/order-detail.jsx +397 -0
  247. package/app/pages/account/order-history.jsx +264 -0
  248. package/app/pages/account/orders.jsx +30 -0
  249. package/app/pages/account/orders.test.js +95 -0
  250. package/app/pages/account/profile.jsx +357 -0
  251. package/app/pages/account/wishlist/index.jsx +195 -0
  252. package/app/pages/account/wishlist/index.mock.js +1481 -0
  253. package/app/pages/account/wishlist/index.test.js +56 -0
  254. package/app/pages/account/wishlist/partials/wishlist-primary-action.jsx +170 -0
  255. package/app/pages/account/wishlist/partials/wishlist-primary-action.mock.js +1623 -0
  256. package/app/pages/account/wishlist/partials/wishlist-primary-action.test.js +99 -0
  257. package/app/pages/account/wishlist/partials/wishlist-secondary-button-group.jsx +120 -0
  258. package/app/pages/account/wishlist/partials/wishlist-secondary-button-group.test.js +391 -0
  259. package/app/pages/cart/index.jsx +476 -0
  260. package/app/pages/cart/index.test.js +481 -0
  261. package/app/pages/cart/partials/cart-cta.jsx +46 -0
  262. package/app/pages/cart/partials/cart-secondary-button-group.jsx +135 -0
  263. package/app/pages/cart/partials/cart-secondary-button-group.test.js +103 -0
  264. package/app/pages/cart/partials/cart-skeleton.jsx +93 -0
  265. package/app/pages/cart/partials/cart-title.jsx +27 -0
  266. package/app/pages/cart/partials/empty-cart.jsx +86 -0
  267. package/app/pages/checkout/confirmation.jsx +541 -0
  268. package/app/pages/checkout/confirmation.mock.js +450 -0
  269. package/app/pages/checkout/confirmation.test.js +114 -0
  270. package/app/pages/checkout/index.jsx +169 -0
  271. package/app/pages/checkout/index.test.js +582 -0
  272. package/app/pages/checkout/partials/cc-radio-group.jsx +122 -0
  273. package/app/pages/checkout/partials/checkout-footer.jsx +140 -0
  274. package/app/pages/checkout/partials/checkout-footer.test.js +16 -0
  275. package/app/pages/checkout/partials/checkout-header.jsx +54 -0
  276. package/app/pages/checkout/partials/checkout-header.test.js +16 -0
  277. package/app/pages/checkout/partials/checkout-skeleton.jsx +52 -0
  278. package/app/pages/checkout/partials/contact-info.jsx +251 -0
  279. package/app/pages/checkout/partials/contact-info.test.js +43 -0
  280. package/app/pages/checkout/partials/payment-form.jsx +97 -0
  281. package/app/pages/checkout/partials/payment.jsx +276 -0
  282. package/app/pages/checkout/partials/shipping-address-selection.jsx +377 -0
  283. package/app/pages/checkout/partials/shipping-address.jsx +132 -0
  284. package/app/pages/checkout/partials/shipping-options.jsx +232 -0
  285. package/app/pages/checkout/util/checkout-context.js +94 -0
  286. package/app/pages/home/data.js +134 -0
  287. package/app/pages/home/index.jsx +301 -0
  288. package/app/pages/home/index.test.js +23 -0
  289. package/app/pages/login/index.jsx +123 -0
  290. package/app/pages/login/index.test.js +229 -0
  291. package/app/pages/login-redirect/index.jsx +23 -0
  292. package/app/pages/login-redirect/index.test.js +16 -0
  293. package/app/pages/page-not-found/index.jsx +90 -0
  294. package/app/pages/page-not-found/index.test.js +31 -0
  295. package/app/pages/product-detail/index.jsx +394 -0
  296. package/app/pages/product-detail/index.mock.js +197 -0
  297. package/app/pages/product-detail/index.test.js +162 -0
  298. package/app/pages/product-detail/partials/information-accordion.jsx +121 -0
  299. package/app/pages/product-list/index.jsx +735 -0
  300. package/app/pages/product-list/index.test.js +180 -0
  301. package/app/pages/product-list/partials/above-page-header.jsx +10 -0
  302. package/app/pages/product-list/partials/checkbox-refinements.jsx +41 -0
  303. package/app/pages/product-list/partials/checkbox-refinements.test.js +53 -0
  304. package/app/pages/product-list/partials/color-refinements.jsx +88 -0
  305. package/app/pages/product-list/partials/empty-results.jsx +118 -0
  306. package/app/pages/product-list/partials/link-refinements.jsx +38 -0
  307. package/app/pages/product-list/partials/page-header.jsx +42 -0
  308. package/app/pages/product-list/partials/radio-refinements.jsx +60 -0
  309. package/app/pages/product-list/partials/refinements.jsx +144 -0
  310. package/app/pages/product-list/partials/selected-refinements.jsx +100 -0
  311. package/app/pages/product-list/partials/size-refinements.jsx +55 -0
  312. package/app/pages/registration/index.jsx +87 -0
  313. package/app/pages/registration/index.test.jsx +132 -0
  314. package/app/pages/reset-password/index.jsx +112 -0
  315. package/app/pages/reset-password/index.test.jsx +141 -0
  316. package/app/request-processor.js +118 -0
  317. package/app/request-processor.test.js +23 -0
  318. package/app/routes.jsx +111 -0
  319. package/app/routes.test.js +13 -0
  320. package/app/ssr.js +70 -0
  321. package/app/static/ico/favicon.ico +0 -0
  322. package/app/static/img/global/app-icon-192.png +0 -0
  323. package/app/static/img/global/app-icon-512.png +0 -0
  324. package/app/static/img/global/apple-touch-icon.png +0 -0
  325. package/app/static/img/hero.png +0 -0
  326. package/app/static/manifest.json +19 -0
  327. package/app/static/robots.txt +2 -0
  328. package/app/theme/components/base/accordion.js +21 -0
  329. package/app/theme/components/base/alert.js +17 -0
  330. package/app/theme/components/base/badge.js +25 -0
  331. package/app/theme/components/base/button.js +77 -0
  332. package/app/theme/components/base/checkbox.js +30 -0
  333. package/app/theme/components/base/container.js +17 -0
  334. package/app/theme/components/base/drawer.js +26 -0
  335. package/app/theme/components/base/formLabel.js +13 -0
  336. package/app/theme/components/base/icon.js +13 -0
  337. package/app/theme/components/base/input.js +44 -0
  338. package/app/theme/components/base/modal.js +11 -0
  339. package/app/theme/components/base/popover.js +61 -0
  340. package/app/theme/components/base/radio.js +33 -0
  341. package/app/theme/components/base/select.js +15 -0
  342. package/app/theme/components/base/skeleton.js +12 -0
  343. package/app/theme/components/base/tooltip.js +19 -0
  344. package/app/theme/components/project/_app.js +25 -0
  345. package/app/theme/components/project/breadcrumb.js +25 -0
  346. package/app/theme/components/project/checkout-footer.js +35 -0
  347. package/app/theme/components/project/drawer-menu.js +66 -0
  348. package/app/theme/components/project/footer.js +84 -0
  349. package/app/theme/components/project/header.js +84 -0
  350. package/app/theme/components/project/image-gallery.js +59 -0
  351. package/app/theme/components/project/links-list.js +43 -0
  352. package/app/theme/components/project/list-menu.js +91 -0
  353. package/app/theme/components/project/locale-selector.js +42 -0
  354. package/app/theme/components/project/nested-accordion.js +26 -0
  355. package/app/theme/components/project/offline-banner.js +25 -0
  356. package/app/theme/components/project/pagination.js +22 -0
  357. package/app/theme/components/project/product-tile.js +32 -0
  358. package/app/theme/components/project/social-icons.js +52 -0
  359. package/app/theme/components/project/swatch-group.js +115 -0
  360. package/app/theme/foundations/colors.js +170 -0
  361. package/app/theme/foundations/gradients.js +9 -0
  362. package/app/theme/foundations/layerStyles.js +41 -0
  363. package/app/theme/foundations/shadows.js +9 -0
  364. package/app/theme/foundations/sizes.js +18 -0
  365. package/app/theme/foundations/space.js +9 -0
  366. package/app/theme/foundations/styles.js +21 -0
  367. package/app/theme/index.js +104 -0
  368. package/app/utils/cc-utils.js +112 -0
  369. package/app/utils/cc-utils.test.js +41 -0
  370. package/app/utils/image-groups-utils.js +62 -0
  371. package/app/utils/image-groups-utils.test.js +65 -0
  372. package/app/utils/locale.js +78 -0
  373. package/app/utils/locale.test.js +112 -0
  374. package/app/utils/password-utils.js +21 -0
  375. package/app/utils/phone-utils.js +22 -0
  376. package/app/utils/phone-utils.test.js +15 -0
  377. package/app/utils/product-utils.js +35 -0
  378. package/app/utils/product-utils.test.js +51 -0
  379. package/app/utils/responsive-image.js +198 -0
  380. package/app/utils/responsive-image.test.js +170 -0
  381. package/app/utils/routes-utils.js +111 -0
  382. package/app/utils/routes-utils.test.js +291 -0
  383. package/app/utils/site-utils.js +222 -0
  384. package/app/utils/site-utils.test.js +376 -0
  385. package/app/utils/test-utils.js +257 -0
  386. package/app/utils/url.js +291 -0
  387. package/app/utils/url.test.js +421 -0
  388. package/app/utils/utils.js +201 -0
  389. package/app/utils/utils.test.js +182 -0
  390. package/babel.config.js +7 -0
  391. package/cache-hash-config.json +8 -0
  392. package/config/default.js +64 -0
  393. package/config/mocks/default.js +131 -0
  394. package/config/sites.js +78 -0
  395. package/jest-setup.js +191 -0
  396. package/jest.config.js +50 -0
  397. package/jsconfig.json +13 -0
  398. package/package.json +105 -0
  399. package/scripts/extract-default-messages.js +92 -0
  400. package/tests/lighthouserc.js +37 -0
  401. package/translations/README.md +127 -0
  402. package/translations/compiled/de-DE.json +3212 -0
  403. package/translations/compiled/en-GB.json +3212 -0
  404. package/translations/compiled/en-US.json +3212 -0
  405. package/translations/compiled/en-XA.json +6948 -0
  406. package/translations/compiled/es-MX.json +3216 -0
  407. package/translations/compiled/fr-FR.json +3216 -0
  408. package/translations/compiled/it-IT.json +3188 -0
  409. package/translations/compiled/ja-JP.json +3200 -0
  410. package/translations/compiled/ko-KR.json +3180 -0
  411. package/translations/compiled/pt-BR.json +3220 -0
  412. package/translations/compiled/zh-CN.json +3212 -0
  413. package/translations/compiled/zh-TW.json +3208 -0
  414. package/translations/de-DE.json +1417 -0
  415. package/translations/en-GB.json +1417 -0
  416. package/translations/en-US.json +1417 -0
  417. package/translations/es-MX.json +1417 -0
  418. package/translations/fr-FR.json +1417 -0
  419. package/translations/it-IT.json +1417 -0
  420. package/translations/ja-JP.json +1417 -0
  421. package/translations/ko-KR.json +1417 -0
  422. package/translations/pt-BR.json +1417 -0
  423. package/translations/zh-CN.json +1417 -0
  424. package/translations/zh-TW.json +1417 -0
  425. package/worker/main.js +36 -0
@@ -0,0 +1,224 @@
1
+ /*
2
+ * Copyright (c) 2021, salesforce.com, inc.
3
+ * All rights reserved.
4
+ * SPDX-License-Identifier: BSD-3-Clause
5
+ * For full license text, see the LICENSE file in the repo root or https://opensource.org/licenses/BSD-3-Clause
6
+ */
7
+
8
+ import React from 'react'
9
+ import PropTypes from 'prop-types'
10
+ import {fireEvent, screen, waitFor} from '@testing-library/react'
11
+ import mockProductDetail from '@salesforce/retail-react-app/app/mocks/variant-750518699578M'
12
+ import mockProductSet from '@salesforce/retail-react-app/app/mocks/product-set-winter-lookM'
13
+ import ProductView from '@salesforce/retail-react-app/app/components/product-view'
14
+ import {renderWithProviders} from '@salesforce/retail-react-app/app/utils/test-utils'
15
+ import userEvent from '@testing-library/user-event'
16
+ import {useCurrentCustomer} from '@salesforce/retail-react-app/app/hooks/use-current-customer'
17
+
18
+ const MockComponent = (props) => {
19
+ const {data: customer} = useCurrentCustomer()
20
+ return (
21
+ <div>
22
+ <div>customer: {customer?.authType}</div>
23
+ <ProductView {...props} />
24
+ </div>
25
+ )
26
+ }
27
+
28
+ MockComponent.propTypes = {
29
+ product: PropTypes.object,
30
+ addToCart: PropTypes.func,
31
+ addToWishlist: PropTypes.func,
32
+ updateWishlist: PropTypes.func
33
+ }
34
+
35
+ // Set up and clean up
36
+ beforeEach(() => {
37
+ // Since we're testing some navigation logic, we are using a simple Router
38
+ // around our component. We need to initialize the default route/path here.
39
+ window.history.pushState({}, 'Account', '/en/account')
40
+ })
41
+ afterEach(() => {
42
+ jest.resetModules()
43
+ localStorage.clear()
44
+ sessionStorage.clear()
45
+ })
46
+
47
+ test('ProductView Component renders properly', async () => {
48
+ const addToCart = jest.fn()
49
+ await renderWithProviders(<MockComponent product={mockProductDetail} addToCart={addToCart} />)
50
+
51
+ expect(screen.getAllByText(/Black Single Pleat Athletic Fit Wool Suit/i)).toHaveLength(2)
52
+ expect(screen.getAllByText(/299.99/)).toHaveLength(2)
53
+ expect(screen.getAllByText(/Add to cart/i)).toHaveLength(2)
54
+ expect(screen.getAllByRole('radiogroup')).toHaveLength(3)
55
+ expect(screen.getAllByText(/add to cart/i)).toHaveLength(2)
56
+ })
57
+
58
+ test('ProductView Component renders with addToCart event handler', async () => {
59
+ const addToCart = jest.fn()
60
+ await renderWithProviders(<MockComponent product={mockProductDetail} addToCart={addToCart} />)
61
+
62
+ const addToCartButton = screen.getAllByText(/add to cart/i)[0]
63
+ fireEvent.click(addToCartButton)
64
+
65
+ await waitFor(() => {
66
+ expect(addToCart).toHaveBeenCalledTimes(1)
67
+ })
68
+ })
69
+
70
+ test('ProductView Component renders with addToWishList event handler', async () => {
71
+ const addToWishlist = jest.fn()
72
+
73
+ await renderWithProviders(
74
+ <MockComponent product={mockProductDetail} addToWishlist={addToWishlist} />
75
+ )
76
+
77
+ await waitFor(() => {
78
+ expect(screen.getByText(/customer: registered/)).toBeInTheDocument()
79
+ })
80
+
81
+ await waitFor(() => {
82
+ const addToWishListButton = screen.getAllByText(/Add to wishlist/i)[0]
83
+
84
+ fireEvent.click(addToWishListButton)
85
+ expect(addToWishlist).toHaveBeenCalledTimes(1)
86
+ })
87
+ })
88
+
89
+ test('ProductView Component renders with updateWishlist event handler', async () => {
90
+ const updateWishlist = jest.fn()
91
+
92
+ await renderWithProviders(
93
+ <MockComponent product={mockProductDetail} updateWishlist={updateWishlist} />
94
+ )
95
+
96
+ await waitFor(() => {
97
+ expect(screen.getByText(/customer: registered/)).toBeInTheDocument()
98
+ })
99
+
100
+ await waitFor(() => {
101
+ const updateWishlistButton = screen.getAllByText(/Update/i)[0]
102
+
103
+ fireEvent.click(updateWishlistButton)
104
+ expect(updateWishlist).toHaveBeenCalledTimes(1)
105
+ })
106
+ })
107
+
108
+ test('Product View can update quantity', async () => {
109
+ const user = userEvent.setup()
110
+ const addToCart = jest.fn()
111
+ await renderWithProviders(<MockComponent product={mockProductDetail} addToCart={addToCart} />)
112
+
113
+ let quantityBox
114
+ await waitFor(() => {
115
+ quantityBox = screen.getByRole('spinbutton')
116
+ })
117
+
118
+ await waitFor(() => {
119
+ expect(quantityBox).toHaveValue('1')
120
+ })
121
+
122
+ // update item quantity
123
+ await user.type(quantityBox, '{backspace}3')
124
+
125
+ await waitFor(() => {
126
+ expect(quantityBox).toHaveValue('3')
127
+ })
128
+ })
129
+
130
+ test('renders a product set properly - parent item', () => {
131
+ const parent = mockProductSet
132
+ renderWithProviders(
133
+ <MockComponent product={parent} addToCart={() => {}} addToWishlist={() => {}} />
134
+ )
135
+
136
+ // NOTE: there can be duplicates of the same element, due to mobile and desktop views
137
+ // (they're hidden with display:none style)
138
+
139
+ const startingAtLabel = screen.getAllByText(/starting at/i)[0]
140
+ const addSetToCartButton = screen.getAllByRole('button', {name: /add set to cart/i})[0]
141
+ const addSetToWishlistButton = screen.getAllByRole('button', {name: /add set to wishlist/i})[0]
142
+ const variationAttributes = screen.queryAllByRole('radiogroup') // e.g. sizes, colors
143
+ const quantityPicker = screen.queryByRole('spinbutton', {name: /quantity:/i})
144
+
145
+ // What should exist:
146
+ expect(startingAtLabel).toBeInTheDocument()
147
+ expect(addSetToCartButton).toBeInTheDocument()
148
+ expect(addSetToWishlistButton).toBeInTheDocument()
149
+
150
+ // What should _not_ exist:
151
+ expect(variationAttributes).toHaveLength(0)
152
+ expect(quantityPicker).toBeNull()
153
+ })
154
+
155
+ test('renders a product set properly - child item', () => {
156
+ const child = mockProductSet.setProducts[0]
157
+ renderWithProviders(
158
+ <MockComponent product={child} addToCart={() => {}} addToWishlist={() => {}} />
159
+ )
160
+
161
+ // NOTE: there can be duplicates of the same element, due to mobile and desktop views
162
+ // (they're hidden with display:none style)
163
+
164
+ const addToCartButton = screen.getAllByRole('button', {name: /add to cart/i})[0]
165
+ const addToWishlistButton = screen.getAllByRole('button', {name: /add to wishlist/i})[0]
166
+ const variationAttributes = screen.getAllByRole('radiogroup') // e.g. sizes, colors
167
+ const quantityPicker = screen.getByRole('spinbutton', {name: /quantity:/i})
168
+ const startingAtLabels = screen.queryAllByText(/starting at/i)
169
+
170
+ // What should exist:
171
+ expect(addToCartButton).toBeInTheDocument()
172
+ expect(addToWishlistButton).toBeInTheDocument()
173
+ expect(variationAttributes).toHaveLength(2)
174
+ expect(quantityPicker).toBeInTheDocument()
175
+
176
+ // What should _not_ exist:
177
+ expect(startingAtLabels).toHaveLength(0)
178
+ })
179
+
180
+ test('validateOrderability callback is called when adding a set to cart', async () => {
181
+ const user = userEvent.setup()
182
+
183
+ const parent = mockProductSet
184
+ const validateOrderability = jest.fn()
185
+
186
+ renderWithProviders(
187
+ <MockComponent
188
+ product={parent}
189
+ validateOrderability={validateOrderability}
190
+ addToCart={() => {}}
191
+ addToWishlist={() => {}}
192
+ />
193
+ )
194
+
195
+ const button = screen.getByRole('button', {name: /add set to cart/i})
196
+ await user.click(button)
197
+
198
+ await waitFor(() => {
199
+ expect(validateOrderability).toHaveBeenCalledTimes(1)
200
+ })
201
+ })
202
+
203
+ test('onVariantSelected callback is called after successfully selected a variant', async () => {
204
+ const user = userEvent.setup()
205
+
206
+ const onVariantSelected = jest.fn()
207
+ const child = mockProductSet.setProducts[0]
208
+
209
+ renderWithProviders(
210
+ <MockComponent
211
+ product={child}
212
+ onVariantSelected={onVariantSelected}
213
+ addToCart={() => {}}
214
+ addToWishlist={() => {}}
215
+ />
216
+ )
217
+
218
+ const size = screen.getByRole('radio', {name: /xl/i})
219
+ await user.click(size)
220
+
221
+ await waitFor(() => {
222
+ expect(onVariantSelected).toHaveBeenCalledTimes(1)
223
+ })
224
+ })
@@ -0,0 +1,48 @@
1
+ /*
2
+ * Copyright (c) 2021, salesforce.com, inc.
3
+ * All rights reserved.
4
+ * SPDX-License-Identifier: BSD-3-Clause
5
+ * For full license text, see the LICENSE file in the repo root or https://opensource.org/licenses/BSD-3-Clause
6
+ */
7
+
8
+ import React from 'react'
9
+ import PropTypes from 'prop-types'
10
+ import {Modal, ModalBody, ModalCloseButton, ModalContent, ModalOverlay} from '@chakra-ui/react'
11
+ import ProductView from '@salesforce/retail-react-app/app/components/product-view'
12
+ import {useProductViewModal} from '@salesforce/retail-react-app/app/hooks/use-product-view-modal'
13
+
14
+ /**
15
+ * A Modal that contains Product View
16
+ */
17
+ const ProductViewModal = ({product, isOpen, onClose, ...props}) => {
18
+ const productViewModalData = useProductViewModal(product)
19
+ return (
20
+ <Modal size="4xl" isOpen={isOpen} onClose={onClose}>
21
+ <ModalOverlay />
22
+ <ModalContent containerProps={{'data-testid': 'product-view-modal'}}>
23
+ <ModalCloseButton />
24
+ <ModalBody pb={8} bg="white" paddingBottom={6} marginTop={6}>
25
+ <ProductView
26
+ showFullLink={true}
27
+ imageSize="sm"
28
+ product={productViewModalData.product}
29
+ isLoading={productViewModalData.isFetching}
30
+ {...props}
31
+ />
32
+ </ModalBody>
33
+ </ModalContent>
34
+ </Modal>
35
+ )
36
+ }
37
+
38
+ ProductViewModal.propTypes = {
39
+ isOpen: PropTypes.bool.isRequired,
40
+ onOpen: PropTypes.func.isRequired,
41
+ onClose: PropTypes.func.isRequired,
42
+ product: PropTypes.object,
43
+ isLoading: PropTypes.bool,
44
+ actionButtons: PropTypes.node,
45
+ onModalClose: PropTypes.func
46
+ }
47
+
48
+ export default ProductViewModal
@@ -0,0 +1,72 @@
1
+ /*
2
+ * Copyright (c) 2021, salesforce.com, inc.
3
+ * All rights reserved.
4
+ * SPDX-License-Identifier: BSD-3-Clause
5
+ * For full license text, see the LICENSE file in the repo root or https://opensource.org/licenses/BSD-3-Clause
6
+ */
7
+
8
+ import React from 'react'
9
+ import PropTypes from 'prop-types'
10
+ import ProductViewModal from '@salesforce/retail-react-app/app/components/product-view-modal/index'
11
+ import {renderWithProviders} from '@salesforce/retail-react-app/app/utils/test-utils'
12
+ import {fireEvent, screen} from '@testing-library/react'
13
+ import {useDisclosure} from '@chakra-ui/react'
14
+ import mockProductDetail from '@salesforce/retail-react-app/app/mocks/variant-750518699578M'
15
+ import {prependHandlersToServer} from '@salesforce/retail-react-app/jest-setup'
16
+
17
+ const MockComponent = ({updateCart}) => {
18
+ const {isOpen, onOpen, onClose} = useDisclosure()
19
+
20
+ return (
21
+ <div>
22
+ <button onClick={onOpen}>Open Modal</button>
23
+ <ProductViewModal
24
+ updateCart={updateCart}
25
+ onOpen={onOpen}
26
+ onClose={onClose}
27
+ isOpen={isOpen}
28
+ product={mockProductDetail}
29
+ />
30
+ </div>
31
+ )
32
+ }
33
+
34
+ MockComponent.propTypes = {
35
+ updateCart: PropTypes.func
36
+ }
37
+
38
+ beforeEach(() => {
39
+ prependHandlersToServer([
40
+ {
41
+ path: '*/products/:productId',
42
+ res: () => {
43
+ return mockProductDetail
44
+ }
45
+ }
46
+ ])
47
+ })
48
+
49
+ test('renders product view modal by default', () => {
50
+ renderWithProviders(<MockComponent />)
51
+
52
+ // open the modal
53
+ const trigger = screen.getByText(/open modal/i)
54
+ fireEvent.click(trigger)
55
+
56
+ expect(screen.getAllByText(/Black Single Pleat Athletic Fit Wool Suit/i)).toHaveLength(2)
57
+ })
58
+
59
+ test('renders product view modal with handleUpdateCart handler', () => {
60
+ const handleUpdateCart = jest.fn()
61
+ renderWithProviders(<MockComponent updateCart={handleUpdateCart} />)
62
+
63
+ // open the modal
64
+ const trigger = screen.getByText(/open modal/i)
65
+ fireEvent.click(trigger)
66
+
67
+ // click on update
68
+ const updateButton = screen.getAllByText(/Update/)[0]
69
+ fireEvent.click(updateButton)
70
+
71
+ expect(handleUpdateCart).toHaveBeenCalledTimes(1)
72
+ })
@@ -0,0 +1,162 @@
1
+ /*
2
+ * Copyright (c) 2021, salesforce.com, inc.
3
+ * All rights reserved.
4
+ * SPDX-License-Identifier: BSD-3-Clause
5
+ * For full license text, see the LICENSE file in the repo root or https://opensource.org/licenses/BSD-3-Clause
6
+ */
7
+ import React, {useEffect, useState} from 'react'
8
+ import PropTypes from 'prop-types'
9
+ import {FormattedMessage, useIntl} from 'react-intl'
10
+ import {
11
+ Box,
12
+ Button,
13
+ Accordion,
14
+ AccordionButton,
15
+ AccordionItem,
16
+ AccordionPanel,
17
+ useToast
18
+ } from '@chakra-ui/react'
19
+ import {useForm} from 'react-hook-form'
20
+ import {ChevronDownIcon, ChevronUpIcon} from '@salesforce/retail-react-app/app/components/icons'
21
+ import PromoCodeFields from '@salesforce/retail-react-app/app/components/forms/promo-code-fields'
22
+ import {API_ERROR_MESSAGE} from '@salesforce/retail-react-app/app/constants'
23
+ import {useShopperBasketsMutation} from '@salesforce/commerce-sdk-react'
24
+ import {useCurrentBasket} from '@salesforce/retail-react-app/app/hooks/use-current-basket'
25
+
26
+ export const usePromoCode = () => {
27
+ const {formatMessage} = useIntl()
28
+ const {data: basket} = useCurrentBasket()
29
+ const form = useForm()
30
+ const toast = useToast()
31
+
32
+ const applyPromoCodeMutation = useShopperBasketsMutation('addCouponToBasket')
33
+ const removePromoCodeMutation = useShopperBasketsMutation('removeCouponFromBasket')
34
+
35
+ const submitPromoCode = async ({code}) => {
36
+ applyPromoCodeMutation.mutate(
37
+ {
38
+ parameters: {basketId: basket?.basketId},
39
+ body: {
40
+ code
41
+ }
42
+ },
43
+ {
44
+ onSuccess: () => {
45
+ form.reset({code: ''})
46
+ toast({
47
+ title: formatMessage({
48
+ defaultMessage: 'Promotion applied',
49
+ id: 'use_promocode.info.promo_applied'
50
+ }),
51
+ status: 'success',
52
+ position: 'top-right',
53
+ isClosable: true
54
+ })
55
+ },
56
+ onError: () => {
57
+ form.setError('code', {
58
+ type: 'manual',
59
+ message: formatMessage({
60
+ defaultMessage:
61
+ 'Check the code and try again, it may already be applied or the promo has expired.',
62
+ id: 'use_promocode.error.check_the_code'
63
+ })
64
+ })
65
+ }
66
+ }
67
+ )
68
+ }
69
+
70
+ const removePromoCode = async (couponItemId) => {
71
+ removePromoCodeMutation.mutate(
72
+ {
73
+ parameters: {basketId: basket?.basketId, couponItemId}
74
+ },
75
+ {
76
+ onSuccess: () => {
77
+ toast({
78
+ title: formatMessage({
79
+ defaultMessage: 'Promotion removed',
80
+ id: 'use_promocode.info.promo_removed'
81
+ }),
82
+ status: 'success',
83
+ position: 'top-right',
84
+ isClosable: true
85
+ })
86
+ },
87
+ onError: () => {
88
+ toast({
89
+ title: formatMessage(API_ERROR_MESSAGE),
90
+ status: 'error',
91
+ position: 'top-right',
92
+ isClosable: true
93
+ })
94
+ }
95
+ }
96
+ )
97
+ }
98
+
99
+ return {form, submitPromoCode, removePromoCode}
100
+ }
101
+
102
+ export const PromoCode = ({form, submitPromoCode, itemProps}) => {
103
+ const [isOpen, setOpen] = useState()
104
+
105
+ useEffect(() => {
106
+ if (form.formState.isSubmitSuccessful) {
107
+ setOpen(false)
108
+ }
109
+ }, [form.formState.isSubmitSuccessful])
110
+
111
+ return (
112
+ <Accordion allowToggle index={isOpen ? 0 : -1} onChange={() => setOpen(!isOpen)}>
113
+ <AccordionItem {...itemProps}>
114
+ {({isExpanded}) => (
115
+ <>
116
+ <AccordionButton
117
+ as={Button}
118
+ justifyContent="flex-start"
119
+ variant="link"
120
+ fontSize="sm"
121
+ rightIcon={isExpanded ? <ChevronUpIcon /> : <ChevronDownIcon />}
122
+ onClick={() => form.reset()}
123
+ >
124
+ <FormattedMessage
125
+ defaultMessage="Do you have a promo code?"
126
+ id="promocode.accordion.button.have_promocode"
127
+ />
128
+ </AccordionButton>
129
+
130
+ <AccordionPanel px={0} mb={4}>
131
+ <Box
132
+ data-testid="promo-code-form"
133
+ as="form"
134
+ p={4}
135
+ background="white"
136
+ border="1px solid"
137
+ borderColor="gray.100"
138
+ borderRadius="base"
139
+ onSubmit={form.handleSubmit(submitPromoCode)}
140
+ >
141
+ <PromoCodeFields form={form} maxWidth="350px" />
142
+ </Box>
143
+ </AccordionPanel>
144
+ </>
145
+ )}
146
+ </AccordionItem>
147
+ </Accordion>
148
+ )
149
+ }
150
+
151
+ PromoCode.propTypes = {
152
+ /** The form object returned from `usePromoCode` hook */
153
+ form: PropTypes.object.isRequired,
154
+
155
+ /** The submit callback returned from `usePromoCode` hook */
156
+ submitPromoCode: PropTypes.func.isRequired,
157
+
158
+ /** Props applied to inner AccordionItem. Useful for style overrides. */
159
+ itemProps: PropTypes.object
160
+ }
161
+
162
+ export default PromoCode
@@ -0,0 +1,83 @@
1
+ /*
2
+ * Copyright (c) 2021, salesforce.com, inc.
3
+ * All rights reserved.
4
+ * SPDX-License-Identifier: BSD-3-Clause
5
+ * For full license text, see the LICENSE file in the repo root or https://opensource.org/licenses/BSD-3-Clause
6
+ */
7
+ import React from 'react'
8
+ import PropTypes from 'prop-types'
9
+ import {
10
+ Box,
11
+ IconButton,
12
+ Popover,
13
+ PopoverArrow,
14
+ PopoverBody,
15
+ PopoverCloseButton,
16
+ PopoverContent,
17
+ PopoverHeader,
18
+ PopoverTrigger,
19
+ Portal,
20
+ Text
21
+ } from '@chakra-ui/react'
22
+ import {InfoIcon} from '@salesforce/retail-react-app/app/components/icons'
23
+ import {FormattedMessage} from 'react-intl'
24
+
25
+ /**
26
+ * This component renders a small info icon and displays a popover when hovered. It could be adapted
27
+ * to handle any kind of popover if needed, but for now its been set up to be used/shared for displaying
28
+ * promotions applied to products and/or orders on cart, checkout, order confirmation and order history.
29
+ */
30
+ const PromoPopover = ({header, children, ...props}) => {
31
+ return (
32
+ <Box position="relative" {...props}>
33
+ <Popover isLazy placement="top" boundary="scrollParent" trigger="hover" variant="small">
34
+ <PopoverTrigger>
35
+ <IconButton
36
+ icon={
37
+ <InfoIcon
38
+ display="block"
39
+ boxSize="18px"
40
+ mt="-2px"
41
+ ml="-1px"
42
+ color="gray.600"
43
+ />
44
+ }
45
+ display="block"
46
+ size="xs"
47
+ height="14px"
48
+ width="14px"
49
+ minWidth="auto"
50
+ position="relative"
51
+ variant="unstyled"
52
+ />
53
+ </PopoverTrigger>
54
+ <Portal>
55
+ <PopoverContent border="none" borderRadius="base">
56
+ <Box boxShadow="lg" zIndex="-1">
57
+ <PopoverArrow />
58
+ <PopoverCloseButton />
59
+ <PopoverHeader borderBottom="none">
60
+ {header || (
61
+ <Text fontWeight="bold" fontSize="md">
62
+ <FormattedMessage
63
+ defaultMessage="Promotions Applied"
64
+ id="promo_popover.heading.promo_applied"
65
+ />
66
+ </Text>
67
+ )}
68
+ </PopoverHeader>
69
+ <PopoverBody pt={0}>{children}</PopoverBody>
70
+ </Box>
71
+ </PopoverContent>
72
+ </Portal>
73
+ </Popover>
74
+ </Box>
75
+ )
76
+ }
77
+
78
+ PromoPopover.propTypes = {
79
+ header: PropTypes.any,
80
+ children: PropTypes.any
81
+ }
82
+
83
+ export default PromoPopover
@@ -0,0 +1,58 @@
1
+ /*
2
+ * Copyright (c) 2021, salesforce.com, inc.
3
+ * All rights reserved.
4
+ * SPDX-License-Identifier: BSD-3-Clause
5
+ * For full license text, see the LICENSE file in the repo root or https://opensource.org/licenses/BSD-3-Clause
6
+ */
7
+
8
+ import React from 'react'
9
+ import {Button, HStack, Input, useNumberInput} from '@chakra-ui/react'
10
+
11
+ /**
12
+ * This is the mobile implementation of the Chakra NumberInput. This simple component essentially
13
+ * is a helper so we don't have to reuse the hooks every time we need a number input since design dictates
14
+ * we use the moobile variation on all screens.
15
+ *
16
+ * NOTE: We can optionally put global logic we see if in here, and various styling decisions in this single
17
+ * component.
18
+ *
19
+ * @param {*} props
20
+ * @returns
21
+ */
22
+ const QuantityPicker = (props) => {
23
+ const {getInputProps, getIncrementButtonProps, getDecrementButtonProps} = useNumberInput({
24
+ ...props,
25
+ // Defaults
26
+ focusInputOnChange: false,
27
+ onFocus: (e) => {
28
+ // eslint-disable-next-line react/prop-types
29
+ const {onFocus} = props
30
+
31
+ // This is useful for mobile devices, this allows the user to pop open the keyboard and set the
32
+ // new quantity with one click.
33
+ e.target.select()
34
+
35
+ // If there is a `onFocus` property define, call it with the event captured.
36
+ // eslint-disable-next-line react/prop-types
37
+ onFocus && onFocus.call(this, e)
38
+ }
39
+ })
40
+
41
+ const inc = getIncrementButtonProps({variant: 'outline'})
42
+ const dec = getDecrementButtonProps({variant: 'outline'})
43
+ const input = getInputProps({maxWidth: '44px', textAlign: 'center'})
44
+
45
+ return (
46
+ <HStack>
47
+ <Button data-testid="quantity-decrement" {...dec}>
48
+ -
49
+ </Button>
50
+ <Input {...input} />
51
+ <Button data-testid="quantity-increment" {...inc}>
52
+ +
53
+ </Button>
54
+ </HStack>
55
+ )
56
+ }
57
+
58
+ export default QuantityPicker