@faststore/core 3.0.130 → 3.0.132

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 (68) hide show
  1. package/.next/BUILD_ID +1 -1
  2. package/.next/build-manifest.json +29 -29
  3. package/.next/cache/.tsbuildinfo +1 -1
  4. package/.next/cache/config.json +3 -3
  5. package/.next/cache/eslint/.cache_1gneedd +1 -1
  6. package/.next/cache/webpack/client-production/0.pack +0 -0
  7. package/.next/cache/webpack/client-production/index.pack +0 -0
  8. package/.next/cache/webpack/server-production/0.pack +0 -0
  9. package/.next/cache/webpack/server-production/index.pack +0 -0
  10. package/.next/next-minimal-server.js.nft.json +1 -1
  11. package/.next/next-server.js.nft.json +1 -1
  12. package/.next/prerender-manifest.js +1 -1
  13. package/.next/prerender-manifest.json +1 -1
  14. package/.next/react-loadable-manifest.json +2 -2
  15. package/.next/routes-manifest.json +1 -1
  16. package/.next/server/chunks/158.js +1 -1
  17. package/.next/server/chunks/350.js +1 -1
  18. package/.next/server/chunks/371.js +1 -1
  19. package/.next/server/chunks/430.js +1 -1
  20. package/.next/server/chunks/646.js +2 -2
  21. package/.next/server/chunks/671.js +1 -1
  22. package/.next/server/chunks/96.js +1 -1
  23. package/.next/server/functions-config-manifest.json +1 -1
  24. package/.next/server/middleware-build-manifest.js +1 -1
  25. package/.next/server/middleware-react-loadable-manifest.js +1 -1
  26. package/.next/server/pages/api/graphql.js +1 -1
  27. package/.next/server/pages/en-US/404.html +1 -1
  28. package/.next/server/pages/en-US/404.json +1 -1
  29. package/.next/server/pages/en-US/500.html +1 -1
  30. package/.next/server/pages/en-US/500.json +1 -1
  31. package/.next/server/pages/en-US/account.html +1 -1
  32. package/.next/server/pages/en-US/account.json +1 -1
  33. package/.next/server/pages/en-US/checkout.html +1 -1
  34. package/.next/server/pages/en-US/checkout.json +1 -1
  35. package/.next/server/pages/en-US/login.html +1 -1
  36. package/.next/server/pages/en-US/login.json +1 -1
  37. package/.next/server/pages/en-US/s.html +1 -1
  38. package/.next/server/pages/en-US/s.json +1 -1
  39. package/.next/server/pages/en-US.html +2 -2
  40. package/.next/server/pages/en-US.json +1 -1
  41. package/.next/server/pages-manifest.json +1 -1
  42. package/.next/static/{8vzjQghRcsjOlyIq8UHyC → QumIIgsiZ1UZUHbqVzzNR}/_buildManifest.js +1 -1
  43. package/.next/static/chunks/{235-a7d9d6cf81ae08ef.js → 235-973ebc8cf442e7f2.js} +1 -1
  44. package/.next/static/chunks/798.f16a775f53df6766.js +1 -0
  45. package/.next/static/chunks/87-a5c39fc100e83f67.js +1 -0
  46. package/.next/static/chunks/968-1e18abc7032f157c.js +1 -0
  47. package/.next/static/chunks/pages/{_app-ed807a0c1aebeb38.js → _app-cb1c3a94f987c5c8.js} +1 -1
  48. package/.next/static/chunks/{webpack-904b033805de64d4.js → webpack-ca5fe5a8d57b1bda.js} +1 -1
  49. package/.next/trace +94 -94
  50. package/.turbo/turbo-build.log +6 -6
  51. package/.turbo/turbo-test.log +4 -4
  52. package/discovery.config.default.js +1 -0
  53. package/next.config.js +10 -0
  54. package/package.json +9 -9
  55. package/src/components/cms/ViewportObserver.tsx +88 -0
  56. package/src/components/common/Footer/FooterLinks.tsx +6 -4
  57. package/src/components/navigation/Navbar/Navbar.tsx +4 -1
  58. package/src/components/product/ProductGrid/ProductGrid.tsx +82 -21
  59. package/src/components/search/Filter/Filter.tsx +4 -2
  60. package/src/components/sections/RegionBar/RegionBar.tsx +9 -7
  61. package/src/components/ui/Carousel/Carousel.tsx +6 -2
  62. package/src/components/ui/ProductGallery/ProductGallery.tsx +4 -3
  63. package/src/components/ui/ProductGallery/ProductGalleryPage.tsx +10 -2
  64. package/src/sdk/ui/useScreenResize.ts +43 -0
  65. package/.next/static/chunks/758.829db9862d9c4add.js +0 -1
  66. package/.next/static/chunks/87-5709356a398fea13.js +0 -1
  67. package/.next/static/chunks/968-dffabcd6f064d487.js +0 -1
  68. /package/.next/static/{8vzjQghRcsjOlyIq8UHyC → QumIIgsiZ1UZUHbqVzzNR}/_ssgManifest.js +0 -0
@@ -45,12 +45,12 @@ Warning: Dynamic Content not found for the page: home. Refer to the Dynamic Cont
45
45
  Collecting build traces ...
46
46
 
47
47
  Route (pages) Size First Load JS
48
- ┌ ● / 1.23 kB 142 kB
48
+ ┌ ● / 1.23 kB 143 kB
49
49
  ├ └ css/197e314c5a03eabd.css 740 B
50
50
  ├ /_app 0 B 90.7 kB
51
- ├ ● /[...slug] 2.79 kB 155 kB
51
+ ├ ● /[...slug] 2.79 kB 156 kB
52
52
  ├ └ css/e47f1a002bdcf76f.css 2.38 kB
53
- ├ ● /[slug]/p 11.2 kB 152 kB
53
+ ├ ● /[slug]/p 11.2 kB 153 kB
54
54
  ├ └ css/9b6bba2472d272ec.css 10.5 kB
55
55
  ├ ○ /404 1.28 kB 126 kB
56
56
  ├ ● /500 1.29 kB 126 kB
@@ -60,13 +60,13 @@ Route (pages) Size First Load JS
60
60
  ├ λ /api/health/ready 0 B 90.7 kB
61
61
  ├ λ /api/preview 0 B 90.7 kB
62
62
  ├ ● /checkout 661 B 126 kB
63
- ├ ● /login 1.4 kB 126 kB
63
+ ├ ● /login 1.4 kB 127 kB
64
64
  └ ● /s 2.22 kB 155 kB
65
65
  + First Load JS shared by all 93.8 kB
66
66
  ├ chunks/framework-8e279965036b6169.js 45.4 kB
67
67
  ├ chunks/main-029f1328cfee9686.js 33.1 kB
68
- ├ chunks/pages/_app-ed807a0c1aebeb38.js 9.87 kB
69
- ├ chunks/webpack-904b033805de64d4.js 2.45 kB
68
+ ├ chunks/pages/_app-cb1c3a94f987c5c8.js 9.87 kB
69
+ ├ chunks/webpack-ca5fe5a8d57b1bda.js 2.45 kB
70
70
  └ css/ee0556daedda6306.css 3.07 kB
71
71
 
72
72
  λ (Server) server-side renders at runtime (uses getInitialProps or getServerSideProps)
@@ -1,10 +1,10 @@
1
1
  $ jest
2
- PASS test/server/cms/index.test.ts (33.424 s)
3
- PASS test/utils/multipleTemplates.test.ts (33.735 s)
4
- PASS test/server/index.test.ts (38.039 s)
2
+ PASS test/utils/multipleTemplates.test.ts (35.769 s)
3
+ PASS test/server/cms/index.test.ts (36.47 s)
4
+ PASS test/server/index.test.ts (39.897 s)
5
5
 
6
6
  Test Suites: 3 passed, 3 total
7
7
  Tests: 19 passed, 19 total
8
8
  Snapshots: 0 total
9
- Time: 39.212 s
9
+ Time: 41.135 s
10
10
  Ran all test suites.
@@ -100,5 +100,6 @@ module.exports = {
100
100
  cypressVersion: 12,
101
101
  enableCypressExtension: false,
102
102
  noRobots: false,
103
+ preact: false,
103
104
  },
104
105
  }
package/next.config.js CHANGED
@@ -46,6 +46,16 @@ const nextConfig = {
46
46
  config.optimization.splitChunks.maxInitialRequests = 1
47
47
  }
48
48
 
49
+ if (storeConfig.experimental.preact && !isServer && !dev) {
50
+ Object.assign(config.resolve.alias, {
51
+ react: 'preact/compat',
52
+
53
+ 'react-dom/test-utils': 'preact/test-utils',
54
+
55
+ 'react-dom': 'preact/compat',
56
+ })
57
+ }
58
+
49
59
  return config
50
60
  },
51
61
  redirects: storeConfig.redirects,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@faststore/core",
3
- "version": "3.0.130",
3
+ "version": "3.0.132",
4
4
  "license": "MIT",
5
5
  "repository": "vtex/faststore",
6
6
  "browserslist": "supports es6-module and not dead",
@@ -43,12 +43,12 @@
43
43
  "@envelop/graphql-jit": "^8.0.3",
44
44
  "@envelop/parser-cache": "^6.0.2",
45
45
  "@envelop/validation-cache": "^6.0.2",
46
- "@faststore/api": "^3.0.130",
47
- "@faststore/components": "^3.0.116",
48
- "@faststore/graphql-utils": "^3.0.116",
49
- "@faststore/lighthouse": "^3.0.116",
50
- "@faststore/sdk": "^3.0.120",
51
- "@faststore/ui": "^3.0.116",
46
+ "@faststore/api": "^3.0.131",
47
+ "@faststore/components": "^3.0.131",
48
+ "@faststore/graphql-utils": "^3.0.131",
49
+ "@faststore/lighthouse": "^3.0.131",
50
+ "@faststore/sdk": "^3.0.131",
51
+ "@faststore/ui": "^3.0.131",
52
52
  "@graphql-codegen/cli": "5.0.2",
53
53
  "@graphql-codegen/client-preset": "4.2.6",
54
54
  "@graphql-codegen/typescript": "4.0.7",
@@ -87,7 +87,7 @@
87
87
  "devDependencies": {
88
88
  "@cypress/code-coverage": "^3.12.1",
89
89
  "@envelop/testing": "^6.0.0",
90
- "@faststore/eslint-config": "^3.0.116",
90
+ "@faststore/eslint-config": "^3.0.131",
91
91
  "@lhci/cli": "^0.9.0",
92
92
  "@testing-library/cypress": "^10.0.1",
93
93
  "@types/cypress": "^1.1.3",
@@ -128,5 +128,5 @@
128
128
  "node": "18.19.0",
129
129
  "yarn": "1.19.1"
130
130
  },
131
- "gitHead": "397a6ee1653fd5c8b42bd46429be651a95646ccc"
131
+ "gitHead": "a99b6bc07181fbaa67077b5c12df219fa9975505"
132
132
  }
@@ -0,0 +1,88 @@
1
+ import type { PropsWithChildren } from 'react'
2
+ import { useCallback, useEffect, useRef, useState } from 'react'
3
+
4
+ // Mobile height to prevent sections outside the viewport from being rendered initially.
5
+ // We are using the Moto G Power device measurement as a reference, as used by PageSpeed Insights.
6
+ const VIEWPORT_SIZE = 823
7
+
8
+ type ViewportObserverProps = {
9
+ /**
10
+ * Identify the store section
11
+ */
12
+ sectionName?: string
13
+ /**
14
+ * Debug/test purposes: enables visual debugging to identify the visibility of the section.
15
+ */
16
+ debug?: boolean
17
+ } & IntersectionObserverInit
18
+
19
+ function ViewportObserver({
20
+ sectionName = '',
21
+ threshold = 0,
22
+ root = null,
23
+ rootMargin,
24
+ children,
25
+ debug = false,
26
+ }: PropsWithChildren<ViewportObserverProps>) {
27
+ const [isVisible, setVisible] = useState(false)
28
+ const ref = useRef<HTMLDivElement | null>(null)
29
+
30
+ const observerCallback = useCallback(
31
+ ([entry]: IntersectionObserverEntry[], obs: IntersectionObserver) => {
32
+ if (entry.isIntersecting) {
33
+ if (debug) {
34
+ console.log(`section '${sectionName}' VISIBLE`)
35
+ document.body.style.border = '2px solid green'
36
+ }
37
+ setVisible(true)
38
+ if (ref.current) {
39
+ obs.unobserve(ref.current)
40
+ }
41
+ } else {
42
+ setVisible(false)
43
+ if (debug) {
44
+ console.log(`section '${sectionName}' NOT VISIBLE`)
45
+ document.body.style.border = '2px solid red'
46
+ document.body.style.height = `${VIEWPORT_SIZE}px`
47
+ document.body.style.boxSizing = 'border-box'
48
+ }
49
+ }
50
+ },
51
+ [debug, sectionName]
52
+ )
53
+
54
+ useEffect(() => {
55
+ const observer = new IntersectionObserver(observerCallback, {
56
+ root,
57
+ rootMargin,
58
+ threshold,
59
+ })
60
+
61
+ if (ref.current) {
62
+ observer.observe(ref.current)
63
+ }
64
+
65
+ return () => observer.disconnect()
66
+ }, [observerCallback, root, rootMargin, threshold])
67
+
68
+ return (
69
+ <>
70
+ {!isVisible && (
71
+ <div
72
+ data-store-section-name={sectionName}
73
+ ref={ref}
74
+ style={{
75
+ border: debug ? '2px solid red' : undefined,
76
+ backgroundColor: debug ? 'red' : undefined,
77
+ height: VIEWPORT_SIZE, // required to make sections out of the viewport to be rendered on demand
78
+ width: '100%',
79
+ }}
80
+ ></div>
81
+ )}
82
+
83
+ {isVisible && children}
84
+ </>
85
+ )
86
+ }
87
+
88
+ export default ViewportObserver
@@ -8,6 +8,7 @@ import {
8
8
  import { useState } from 'react'
9
9
 
10
10
  import Link from 'src/components/ui/Link'
11
+ import useScreenResize from 'src/sdk/ui/useScreenResize'
11
12
 
12
13
  type Item = {
13
14
  url: string
@@ -38,6 +39,7 @@ export interface FooterLinksProps {
38
39
  }
39
40
 
40
41
  function FooterLinks({ links }: FooterLinksProps) {
42
+ const { isDesktop } = useScreenResize()
41
43
  const [indicesExpanded, setIndicesExpanded] = useState<Set<number>>(
42
44
  new Set([])
43
45
  )
@@ -53,7 +55,7 @@ function FooterLinks({ links }: FooterLinksProps) {
53
55
 
54
56
  return (
55
57
  <section data-fs-footer data-fs-footer-links>
56
- <div className="display-mobile">
58
+ {!isDesktop && (
57
59
  <UIAccordion indices={indicesExpanded} onChange={onChange}>
58
60
  {links.map(({ sectionTitle, items }) => (
59
61
  <UIAccordionItem key={sectionTitle}>
@@ -64,9 +66,9 @@ function FooterLinks({ links }: FooterLinksProps) {
64
66
  </UIAccordionItem>
65
67
  ))}
66
68
  </UIAccordion>
67
- </div>
69
+ )}
68
70
 
69
- <div className="hidden-mobile">
71
+ {isDesktop && (
70
72
  <nav data-fs-footer-links-columns aria-label="Footer Links Navigation">
71
73
  {links.map(({ sectionTitle, items }) => (
72
74
  <div key={sectionTitle}>
@@ -75,7 +77,7 @@ function FooterLinks({ links }: FooterLinksProps) {
75
77
  </div>
76
78
  ))}
77
79
  </nav>
78
- </div>
80
+ )}
79
81
  </section>
80
82
  )
81
83
  }
@@ -12,6 +12,7 @@ import Link from 'src/components/ui/Link'
12
12
  import { useOverrideComponents } from 'src/sdk/overrides/OverrideContext'
13
13
 
14
14
  import type { NavbarProps as SectionNavbarProps } from '../../sections/Navbar'
15
+ import useScreenResize from 'src/sdk/ui/useScreenResize'
15
16
 
16
17
  export interface NavbarProps {
17
18
  /**
@@ -78,6 +79,8 @@ function Navbar({
78
79
  } = useOverrideComponents<'Navbar'>()
79
80
  const scrollDirection = useScrollDirection()
80
81
  const { openNavbar, navbar: displayNavbar } = useUI()
82
+ const { isDesktop } = useScreenResize()
83
+
81
84
  const searchMobileRef = useRef<SearchInputRef>(null)
82
85
  const [searchExpanded, setSearchExpanded] = useState(false)
83
86
 
@@ -157,7 +160,7 @@ function Navbar({
157
160
  </NavbarRow.Component>
158
161
  </NavbarHeader.Component>
159
162
 
160
- <NavbarLinks links={links} region={region} className="hidden-mobile" />
163
+ {isDesktop && <NavbarLinks links={links} region={region} />}
161
164
 
162
165
  {displayNavbar && (
163
166
  <NavbarSlider
@@ -7,6 +7,7 @@ import ProductGridSkeleton from 'src/components/skeletons/ProductGridSkeleton'
7
7
  import { ProductCardProps } from '../ProductCard'
8
8
 
9
9
  import { memo } from 'react'
10
+ import ViewportObserver from 'src/components/cms/ViewportObserver'
10
11
  import { useOverrideComponents } from 'src/sdk/overrides/OverrideContext'
11
12
 
12
13
  interface Props {
@@ -26,6 +27,10 @@ interface Props {
26
27
  ProductCardProps,
27
28
  'showDiscountBadge' | 'bordered' | 'taxesConfiguration'
28
29
  >
30
+ /**
31
+ * Identify the number of firstPage
32
+ */
33
+ firstPage?: number
29
34
  }
30
35
 
31
36
  function ProductGrid({
@@ -33,38 +38,94 @@ function ProductGrid({
33
38
  page,
34
39
  pageSize,
35
40
  productCard: { showDiscountBadge, bordered, taxesConfiguration } = {},
41
+ firstPage,
36
42
  }: Props) {
37
43
  const { __experimentalProductCard: ProductCard } =
38
44
  useOverrideComponents<'ProductGallery'>()
39
45
  const aspectRatio = 1
40
46
 
47
+ // TODO: Check if is also isMobile
48
+ const isFirstPage = firstPage === page
49
+
41
50
  return (
42
51
  <ProductGridSkeleton
43
52
  aspectRatio={aspectRatio}
44
53
  loading={products.length === 0}
45
54
  >
46
55
  <UIProductGrid>
47
- {products.map(({ node: product }, idx) => (
48
- <UIProductGridItem key={`${product.id}`}>
49
- <ProductCard.Component
50
- aspectRatio={aspectRatio}
51
- imgProps={{
52
- width: 150,
53
- height: 150,
54
- sizes: '30vw',
55
- loading: idx === 0 ? 'eager' : 'lazy',
56
- }}
57
- {...ProductCard.props}
58
- bordered={bordered ?? ProductCard.props.bordered}
59
- showDiscountBadge={
60
- showDiscountBadge ?? ProductCard.props.showDiscountBadge
61
- }
62
- product={product}
63
- index={pageSize * page + idx + 1}
64
- taxesConfiguration={taxesConfiguration}
65
- />
66
- </UIProductGridItem>
67
- ))}
56
+ {isFirstPage ? (
57
+ <>
58
+ {products.slice(0, 2).map(({ node: product }, idx) => (
59
+ <UIProductGridItem key={`${product.id}`}>
60
+ <ProductCard.Component
61
+ aspectRatio={aspectRatio}
62
+ imgProps={{
63
+ width: 150,
64
+ height: 150,
65
+ sizes: '30vw',
66
+ loading: 'eager',
67
+ }}
68
+ {...ProductCard.props}
69
+ bordered={bordered ?? ProductCard.props.bordered}
70
+ showDiscountBadge={
71
+ showDiscountBadge ?? ProductCard.props.showDiscountBadge
72
+ }
73
+ product={product}
74
+ index={pageSize * page + idx + 1}
75
+ taxesConfiguration={taxesConfiguration}
76
+ />
77
+ </UIProductGridItem>
78
+ ))}
79
+ <></>
80
+ <ViewportObserver sectionName="UIProductGrid-out-viewport">
81
+ {products.slice(2).map(({ node: product }, idx) => (
82
+ <UIProductGridItem key={`${product.id}`}>
83
+ <ProductCard.Component
84
+ aspectRatio={aspectRatio}
85
+ imgProps={{
86
+ width: 150,
87
+ height: 150,
88
+ sizes: '30vw',
89
+ loading: 'lazy',
90
+ }}
91
+ {...ProductCard.props}
92
+ bordered={bordered ?? ProductCard.props.bordered}
93
+ showDiscountBadge={
94
+ showDiscountBadge ?? ProductCard.props.showDiscountBadge
95
+ }
96
+ product={product}
97
+ index={pageSize * page + idx + 1}
98
+ taxesConfiguration={taxesConfiguration}
99
+ />
100
+ </UIProductGridItem>
101
+ ))}
102
+ </ViewportObserver>
103
+ </>
104
+ ) : (
105
+ <>
106
+ {products.map(({ node: product }, idx) => (
107
+ <UIProductGridItem key={`${product.id}`}>
108
+ <ProductCard.Component
109
+ aspectRatio={aspectRatio}
110
+ imgProps={{
111
+ width: 150,
112
+ height: 150,
113
+ sizes: '30vw',
114
+ loading: idx === 0 ? 'eager' : 'lazy',
115
+ }}
116
+ {...ProductCard.props}
117
+ bordered={bordered ?? ProductCard.props.bordered}
118
+ showDiscountBadge={
119
+ showDiscountBadge ?? ProductCard.props.showDiscountBadge
120
+ }
121
+ product={product}
122
+ index={pageSize * page + idx + 1}
123
+ taxesConfiguration={taxesConfiguration}
124
+ />
125
+ </UIProductGridItem>
126
+ ))}
127
+ </>
128
+ )}
68
129
  </UIProductGrid>
69
130
  </ProductGridSkeleton>
70
131
  )
@@ -6,6 +6,7 @@ import { gql } from '@generated'
6
6
  import { ProductGalleryProps } from 'src/components/ui/ProductGallery/ProductGallery'
7
7
  import { useOverrideComponents } from 'src/sdk/overrides/OverrideContext'
8
8
  import { useFilter } from 'src/sdk/search/useFilter'
9
+ import useScreenResize from 'src/sdk/ui/useScreenResize'
9
10
 
10
11
  interface Props {
11
12
  /**
@@ -35,17 +36,18 @@ function Filter({
35
36
 
36
37
  const filter = useFilter(allFacets)
37
38
  const { filter: displayFilter } = useUI()
39
+ const { isDesktop } = useScreenResize()
38
40
 
39
41
  return (
40
42
  <>
41
- <div className="hidden-mobile">
43
+ {isDesktop && (
42
44
  <FilterDesktop.Component
43
45
  {...FilterDesktop.props}
44
46
  {...filter}
45
47
  testId={testId}
46
48
  title={filterCmsData?.title}
47
49
  />
48
- </div>
50
+ )}
49
51
 
50
52
  {displayFilter && (
51
53
  <Suspense fallback={null}>
@@ -3,6 +3,7 @@ import Section from '../Section/Section'
3
3
  import styles from './section.module.scss'
4
4
  import { RegionBarDefaultComponents } from './DefaultComponents'
5
5
  import { getOverridableSection } from '../../..//sdk/overrides/getOverriddenSection'
6
+ import useScreenResize from 'src/sdk/ui/useScreenResize'
6
7
 
7
8
  type RegionBarSectionProps = {
8
9
  /**
@@ -27,14 +28,15 @@ type RegionBarSectionProps = {
27
28
  buttonIcon?: RegionBarProps['buttonIcon']
28
29
  }
29
30
 
30
- function RegionBarSection({
31
- className = 'display-mobile',
32
- ...otherProps
33
- }: RegionBarSectionProps) {
31
+ function RegionBarSection({ ...otherProps }: RegionBarSectionProps) {
32
+ const { isMobile } = useScreenResize()
33
+
34
34
  return (
35
- <Section className={`${styles.section} section-region-bar ${className}`}>
36
- <RegionBar {...otherProps} />
37
- </Section>
35
+ isMobile && (
36
+ <Section className={`${styles.section} section-region-bar`}>
37
+ <RegionBar {...otherProps} />
38
+ </Section>
39
+ )
38
40
  )
39
41
  }
40
42
 
@@ -2,6 +2,8 @@ import type { PropsWithChildren } from 'react'
2
2
  import { Carousel as UICarousel } from '@faststore/ui'
3
3
  import type { CarouselProps as UICarouselProps } from '@faststore/ui'
4
4
 
5
+ import useScreenResize from 'src/sdk/ui/useScreenResize'
6
+
5
7
  export type CarouselProps = {
6
8
  id?: string
7
9
  testId?: string
@@ -21,7 +23,9 @@ function Carousel({
21
23
  variant = 'scroll',
22
24
  infiniteMode = false,
23
25
  }: PropsWithChildren<CarouselProps>) {
24
- const isMobile = window.innerWidth <= 768
26
+ const { loading, isTablet, isMobile } = useScreenResize()
27
+
28
+ if (loading) return null
25
29
 
26
30
  return (
27
31
  <UICarousel
@@ -29,7 +33,7 @@ function Carousel({
29
33
  testId={testId}
30
34
  variant={variant}
31
35
  infiniteMode={infiniteMode}
32
- itemsPerPage={isMobile ? 1.6 : itemsPerPage}
36
+ itemsPerPage={isTablet || isMobile ? 1.6 : itemsPerPage}
33
37
  >
34
38
  {children}
35
39
  </UICarousel>
@@ -12,15 +12,15 @@ import ProductGridSkeleton from 'src/components/skeletons/ProductGridSkeleton'
12
12
  import { ProductCardProps } from 'src/components/product/ProductCard'
13
13
  import { FilterSliderProps } from 'src/components/search/Filter/FilterSlider'
14
14
  import { SortProps } from 'src/components/search/Sort/Sort'
15
- import { useDelayedFacets } from 'src/sdk/search/useDelayedFacets'
16
- import { useDelayedPagination } from 'src/sdk/search/useDelayedPagination'
15
+ import { useOverrideComponents } from 'src/sdk/overrides/OverrideContext'
17
16
  import {
18
17
  PLPContext,
19
18
  SearchPageContext,
20
19
  usePage,
21
20
  } from 'src/sdk/overrides/PageProvider'
22
21
  import { useProductsPrefetch } from 'src/sdk/product/useProductsPrefetch'
23
- import { useOverrideComponents } from 'src/sdk/overrides/OverrideContext'
22
+ import { useDelayedFacets } from 'src/sdk/search/useDelayedFacets'
23
+ import { useDelayedPagination } from 'src/sdk/search/useDelayedPagination'
24
24
 
25
25
  const ProductGalleryPage = lazy(() => import('./ProductGalleryPage'))
26
26
  const GalleryPageSkeleton = <ProductGridSkeleton loading />
@@ -237,6 +237,7 @@ function ProductGallery({
237
237
  title={title}
238
238
  productCard={productCard}
239
239
  itemsPerPage={itemsPerPage}
240
+ firstPage={pages[0]}
240
241
  />
241
242
  ))}
242
243
  </Suspense>
@@ -1,8 +1,8 @@
1
1
  import ProductGrid from 'src/components/product/ProductGrid'
2
2
  import Sentinel from 'src/sdk/search/Sentinel'
3
3
 
4
- import { ProductCardProps } from 'src/components/product/ProductCard'
5
4
  import { memo } from 'react'
5
+ import { ProductCardProps } from 'src/components/product/ProductCard'
6
6
  import { useGalleryPage } from 'src/sdk/product/usePageProductsQuery'
7
7
 
8
8
  interface Props {
@@ -13,9 +13,16 @@ interface Props {
13
13
  'showDiscountBadge' | 'bordered' | 'taxesConfiguration'
14
14
  >
15
15
  itemsPerPage: number
16
+ firstPage: number
16
17
  }
17
18
 
18
- function ProductGalleryPage({ page, title, productCard, itemsPerPage }: Props) {
19
+ function ProductGalleryPage({
20
+ page,
21
+ title,
22
+ productCard,
23
+ itemsPerPage,
24
+ firstPage,
25
+ }: Props) {
19
26
  const { data } = useGalleryPage(page)
20
27
 
21
28
  const products = data?.search?.products?.edges ?? []
@@ -33,6 +40,7 @@ function ProductGalleryPage({ page, title, productCard, itemsPerPage }: Props) {
33
40
  page={page}
34
41
  pageSize={itemsPerPage}
35
42
  productCard={productCard}
43
+ firstPage={firstPage}
36
44
  />
37
45
  </>
38
46
  )
@@ -0,0 +1,43 @@
1
+ import { useEffect, useState } from 'react'
2
+
3
+ // We are using the Moto G Power device measurement as a reference (mobile), as used by PageSpeed Insights.
4
+ const MAX_MOBILE_WIDTH = 420
5
+ const MAX_TABLET_WIDTH = 768
6
+ const MIN_NOTEBOOK_WIDTH = 1280
7
+
8
+ function useScreenResize() {
9
+ const [isMobile, setIsMobile] = useState(undefined)
10
+ const [isTablet, setIsTablet] = useState(undefined)
11
+ const [isDesktop, setIsDesktop] = useState(undefined)
12
+
13
+ useEffect(() => {
14
+ const handleResize = () => {
15
+ setIsMobile(window.innerWidth <= MAX_MOBILE_WIDTH)
16
+ setIsTablet(
17
+ window.innerWidth > MAX_MOBILE_WIDTH &&
18
+ window.innerWidth <= MAX_TABLET_WIDTH
19
+ )
20
+ setIsDesktop(window.innerWidth >= MIN_NOTEBOOK_WIDTH)
21
+ }
22
+
23
+ handleResize()
24
+
25
+ window.addEventListener('resize', handleResize)
26
+
27
+ return () => {
28
+ window.removeEventListener('resize', handleResize)
29
+ }
30
+ }, [])
31
+
32
+ return {
33
+ isMobile,
34
+ isTablet,
35
+ isDesktop,
36
+ loading:
37
+ isMobile === undefined ||
38
+ isTablet === undefined ||
39
+ isDesktop === undefined,
40
+ }
41
+ }
42
+
43
+ export default useScreenResize
@@ -1 +0,0 @@
1
- "use strict";(self.webpackChunk_N_E=self.webpackChunk_N_E||[]).push([[758],{5758:function(e,r,t){t.r(r),t.d(r,{default:function(){return j}});var n=t(9499),o=t(2784);let a=(0,o.forwardRef)(function({testId:e="fs-product-grid",children:r,...t},n){return o.createElement("ul",{ref:n,"data-fs-product-grid":!0,...t},r)}),c=(0,o.forwardRef)(function({testId:e="fs-product-grid-item",children:r,...t},n){return o.createElement("li",{ref:n,"data-fs-product-grid-item":!0,...t},r)});var i=t(4548),s=t(4329),u=t(2322);function ownKeys(e,r){var t=Object.keys(e);if(Object.getOwnPropertySymbols){var n=Object.getOwnPropertySymbols(e);r&&(n=n.filter(function(r){return Object.getOwnPropertyDescriptor(e,r).enumerable})),t.push.apply(t,n)}return t}function _objectSpread(e){for(var r=1;r<arguments.length;r++){var t=null!=arguments[r]?arguments[r]:{};r%2?ownKeys(Object(t),!0).forEach(function(r){(0,n.Z)(e,r,t[r])}):Object.getOwnPropertyDescriptors?Object.defineProperties(e,Object.getOwnPropertyDescriptors(t)):ownKeys(Object(t)).forEach(function(r){Object.defineProperty(e,r,Object.getOwnPropertyDescriptor(t,r))})}return e}var d=(0,o.memo)(function(e){var{products:r,page:t,pageSize:n,productCard:{showDiscountBadge:o,bordered:d,taxesConfiguration:l}={}}=e,{__experimentalProductCard:p}=(0,s.r3)();return(0,u.jsx)(i.Z,{aspectRatio:1,loading:0===r.length,children:(0,u.jsx)(a,{children:r.map((e,r)=>{var{node:a}=e;return(0,u.jsx)(c,{children:(0,u.jsx)(p.Component,_objectSpread(_objectSpread({aspectRatio:1,imgProps:{width:150,height:150,sizes:"30vw",loading:0===r?"eager":"lazy"}},p.props),{},{bordered:null!=d?d:p.props.bordered,showDiscountBadge:null!=o?o:p.props.showDiscountBadge,product:a,index:n*t+r+1,taxesConfiguration:l}))},"".concat(a.id))})})})}),l=t(9029),p=t(7704),f=t(1163),g=t(6414),replacePagination=(e,r)=>{var t=new URL(window.location.href);t.searchParams.get("page")!==e&&(t.searchParams.set("page",e),r.replace(t,void 0,{shallow:!0,scroll:!1}))},search_Sentinel=function(e){var{page:r,pageSize:t,products:n,title:a}=e,c=(0,o.useRef)(!1),{ref:i,inView:s}=(0,p.YD)(),{pages:d}=(0,l.R)(),h=(0,f.useRouter)(),{sendViewItemListEvent:j}=(0,g.m)({products:n,title:a,page:r,pageSize:t});return(0,o.useEffect)(()=>{s&&d.length>1&&replacePagination(r.toString(),h),s&&!c.current&&n.length&&(j(),c.current=!0)},[d.length,s,r,h,j,n.length]),(0,u.jsx)("div",{ref:i})},h=t(7171),j=(0,o.memo)(function(e){var r,t,n,{page:o,title:a,productCard:c,itemsPerPage:i}=e,{data:s}=(0,h.__)(o),l=null!==(r=null==s?void 0:null===(t=s.search)||void 0===t?void 0:null===(n=t.products)||void 0===n?void 0:n.edges)&&void 0!==r?r:[];return(0,u.jsxs)(u.Fragment,{children:[(0,u.jsx)(search_Sentinel,{products:l,page:o,pageSize:i,title:a}),(0,u.jsx)(d,{products:l,page:o,pageSize:i,productCard:c})]})})}}]);