@graphcommerce/magento-product 8.1.0-canary.8 → 9.0.0-canary.100

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 (94) hide show
  1. package/Api/ProductListItem.graphql +1 -2
  2. package/Api/ProductPageItem.graphql +1 -1
  3. package/CHANGELOG.md +278 -84
  4. package/Config.graphqls +13 -0
  5. package/components/AddProductsToCart/AddProductsToCartButton.tsx +17 -4
  6. package/components/AddProductsToCart/AddProductsToCartFab.tsx +7 -2
  7. package/components/AddProductsToCart/AddProductsToCartForm.tsx +31 -29
  8. package/components/AddProductsToCart/AddProductsToCartSnackbar.tsx +14 -63
  9. package/components/AddProductsToCart/AddProductsToCartSnackbarMessage.tsx +84 -0
  10. package/components/AddProductsToCart/UseAddProductsToCartAction.graphql +1 -1
  11. package/components/AddProductsToCart/findAddedItems.ts +1 -4
  12. package/components/AddProductsToCart/index.ts +1 -0
  13. package/components/AddProductsToCart/useAddProductsToCartAction.ts +2 -1
  14. package/components/AddProductsToCart/useFormAddProductsToCart.ts +1 -2
  15. package/components/JsonLdProduct/JsonLdProduct.graphql +1 -1
  16. package/components/JsonLdProduct/ProductPageJsonLd.tsx +1 -1
  17. package/components/ProductAddToCart/ProductAddToCart.tsx +6 -8
  18. package/components/ProductCustomizable/CustomizableCheckboxOption.tsx +3 -4
  19. package/components/ProductCustomizable/CustomizableMultipleOption.tsx +2 -2
  20. package/components/ProductCustomizable/CustomizableRadioOption.tsx +2 -2
  21. package/components/ProductCustomizable/ProductCustomizable.graphql +1 -1
  22. package/components/ProductCustomizable/index.ts +1 -0
  23. package/components/ProductCustomizable/productCustomizableSelectors.ts +59 -0
  24. package/components/ProductFiltersPro/PriceSlider.tsx +1 -2
  25. package/components/ProductFiltersPro/ProductFilterEqualChip.tsx +4 -5
  26. package/components/ProductFiltersPro/ProductFilterEqualSection.tsx +6 -7
  27. package/components/ProductFiltersPro/ProductFilterRangeChip.tsx +1 -1
  28. package/components/ProductFiltersPro/ProductFilterRangeSection.tsx +1 -1
  29. package/components/ProductFiltersPro/ProductFiltersPro.tsx +103 -19
  30. package/components/ProductFiltersPro/ProductFiltersProAggregations.tsx +41 -20
  31. package/components/ProductFiltersPro/ProductFiltersProAllFiltersChip.tsx +6 -10
  32. package/components/ProductFiltersPro/ProductFiltersProAllFiltersSidebar.tsx +18 -8
  33. package/components/ProductFiltersPro/ProductFiltersProCategorySection.tsx +130 -0
  34. package/components/ProductFiltersPro/ProductFiltersProChips.tsx +10 -8
  35. package/components/ProductFiltersPro/ProductFiltersProClearAll.tsx +4 -16
  36. package/components/ProductFiltersPro/ProductFiltersProLayoutSidebar.tsx +15 -7
  37. package/components/ProductFiltersPro/ProductFiltersProLimitChip.tsx +2 -8
  38. package/components/ProductFiltersPro/ProductFiltersProLimitSection.tsx +7 -10
  39. package/components/ProductFiltersPro/ProductFiltersProNoResults.tsx +79 -0
  40. package/components/ProductFiltersPro/ProductFiltersProSortChip.tsx +5 -7
  41. package/components/ProductFiltersPro/ProductFiltersProSortDirectionArrow.tsx +2 -4
  42. package/components/ProductFiltersPro/ProductFiltersProSortSection.tsx +11 -3
  43. package/components/ProductFiltersPro/activeAggregations.ts +5 -9
  44. package/components/ProductFiltersPro/applyAggregationCount.ts +14 -8
  45. package/components/ProductFiltersPro/index.ts +9 -0
  46. package/components/ProductFiltersPro/{useClearAllFiltersHandler.ts → useProductFiltersProClearAllAction.ts} +1 -1
  47. package/components/ProductFiltersPro/useProductFiltersProHasFiltersApplied.ts +21 -0
  48. package/components/ProductFiltersPro/useProductFiltersProSort.tsx +7 -3
  49. package/components/ProductList/ProductList.graphql +8 -5
  50. package/components/ProductListCount/ProductListCount.tsx +3 -1
  51. package/components/ProductListFilters/ProductFilters.graphql +11 -2
  52. package/components/ProductListFilters/ProductListFilters.graphql +1 -1
  53. package/components/ProductListFilters/ProductListFilters.tsx +13 -19
  54. package/components/ProductListFiltersContainer/ProductListFiltersContainer.tsx +2 -4
  55. package/components/ProductListItem/ProductDiscountLabel.tsx +2 -3
  56. package/components/ProductListItem/ProductListItem.tsx +3 -3
  57. package/components/ProductListItem/ProductListItemTitleAndPrice.tsx +18 -15
  58. package/components/ProductListItems/ProductFilterTypes.graphql +8 -0
  59. package/components/ProductListItems/ProductListItemsBase.tsx +71 -30
  60. package/components/ProductListItems/filterTypes.tsx +14 -7
  61. package/components/ProductListItems/filteredProductList.tsx +44 -17
  62. package/components/ProductListItems/getFilterTypes.ts +33 -4
  63. package/components/ProductListItems/productListApplyCategoryDefaults.ts +50 -4
  64. package/components/ProductListItems/renderer.tsx +8 -2
  65. package/components/ProductListPagination/ProductListPagination.tsx +39 -20
  66. package/components/ProductListPrice/ProductListPrice.tsx +9 -4
  67. package/components/ProductListSuggestions/ProductListSuggestions.graphql +5 -0
  68. package/components/ProductListSuggestions/ProductListSuggestions.tsx +42 -0
  69. package/components/ProductPageBreadcrumb/ProductPageBreadcrumb.graphql +3 -0
  70. package/components/ProductPageBreadcrumb/ProductPageBreadcrumb.tsx +3 -0
  71. package/components/ProductPageBreadcrumb/ProductPageBreadcrumbs.tsx +40 -0
  72. package/components/ProductPageBreadcrumb/index.ts +1 -0
  73. package/components/ProductPageDescription/ComplexTextValue.graphql +1 -1
  74. package/components/ProductPageDescription/ProductPageDescription.tsx +1 -1
  75. package/components/ProductPageGallery/ProductImage.graphql +1 -0
  76. package/components/ProductPageGallery/ProductPageGallery.tsx +14 -8
  77. package/components/ProductPagePrice/ProductPagePrice.graphql +0 -6
  78. package/components/ProductPagePrice/ProductPagePrice.tsx +19 -12
  79. package/components/ProductPagePrice/ProductPagePriceTiers.tsx +4 -3
  80. package/components/ProductPagePrice/useCustomizableOptionPrice.ts +11 -53
  81. package/components/ProductShortDescription/ProductShortDescription.tsx +2 -0
  82. package/components/ProductSpecs/ProductSpecs.graphql +21 -1
  83. package/components/ProductSpecs/ProductSpecs.tsx +5 -11
  84. package/components/ProductSpecs/ProductSpecsAggregations.tsx +34 -0
  85. package/components/ProductSpecs/ProductSpecsCustomAttributes.tsx +45 -0
  86. package/components/ProductSpecs/ProductSpecsTypes.graphql +8 -0
  87. package/components/ProductStaticPaths/getProductStaticPaths.ts +3 -4
  88. package/components/ProductStaticPaths/getSitemapPaths.ts +3 -0
  89. package/components/ProductWeight/ProductWeight.tsx +12 -9
  90. package/components/index.ts +2 -0
  91. package/hooks/useProductList.ts +148 -0
  92. package/hooks/useProductListLink.ts +6 -3
  93. package/index.ts +1 -0
  94. package/package.json +14 -14
@@ -1,21 +1,17 @@
1
- import { useWatch } from '@graphcommerce/ecommerce-ui'
1
+ import { ActionCardItemBase, ActionCardListForm, useWatch } from '@graphcommerce/ecommerce-ui'
2
2
  import { useQuery } from '@graphcommerce/graphql'
3
3
  import { StoreConfigDocument } from '@graphcommerce/magento-store'
4
- import {
5
- ActionCard,
6
- ActionCardAccordion,
7
- ActionCardItemBase,
8
- ActionCardListForm,
9
- Button,
10
- } from '@graphcommerce/next-ui'
4
+ import { ActionCard, ActionCardAccordion, Button } from '@graphcommerce/next-ui'
11
5
  import { Trans } from '@lingui/react'
6
+ import { SxProps, Theme } from '@mui/material'
12
7
  import { useMemo } from 'react'
13
8
  import { useProductFiltersPro } from './ProductFiltersPro'
14
9
 
15
- export type ProductFiltersProLimitSectionProps = Record<string, unknown>
10
+ export type ProductFiltersProLimitSectionProps = { sx?: SxProps<Theme> }
16
11
 
17
12
  // eslint-disable-next-line @typescript-eslint/no-unused-vars
18
13
  export function ProductFiltersProLimitSection(props: ProductFiltersProLimitSectionProps) {
14
+ const { sx } = props
19
15
  const { form } = useProductFiltersPro()
20
16
  const { control } = form
21
17
  const activePageSize = useWatch({ control, name: 'pageSize' })
@@ -38,6 +34,7 @@ export function ProductFiltersProLimitSection(props: ProductFiltersProLimitSecti
38
34
 
39
35
  return (
40
36
  <ActionCardAccordion
37
+ sx={sx}
41
38
  defaultExpanded={!!activePageSize}
42
39
  summary={<Trans id='Per page' />}
43
40
  details={
@@ -48,7 +45,7 @@ export function ProductFiltersProLimitSection(props: ProductFiltersProLimitSecti
48
45
  control={control}
49
46
  layout='list'
50
47
  variant='default'
51
- size='medium'
48
+ size='responsive'
52
49
  items={options}
53
50
  />
54
51
  }
@@ -0,0 +1,79 @@
1
+ import { extendableComponent } from '@graphcommerce/next-ui'
2
+ import { Trans } from '@lingui/macro'
3
+ import { Box, Link, SxProps, Theme, Typography } from '@mui/material'
4
+ import { useProductFiltersProClearAllAction } from './useProductFiltersProClearAllAction'
5
+ import { useProductFilterProHasFiltersApplied } from './useProductFiltersProHasFiltersApplied'
6
+
7
+ export type ProductFitlersProNoResultProps = { search?: string | null; sx?: SxProps<Theme> }
8
+
9
+ const name = 'ProductFiltersProNoResults' as const
10
+ const parts = ['root'] as const
11
+ const { classes } = extendableComponent(name, parts)
12
+
13
+ export function ProductFiltersProNoResults(props: ProductFitlersProNoResultProps) {
14
+ const { search, sx = [] } = props
15
+
16
+ const term = search ? `'${search}'` : ''
17
+
18
+ const clearAll = useProductFiltersProClearAllAction()
19
+ const hasFilters = useProductFilterProHasFiltersApplied()
20
+
21
+ return (
22
+ <Box
23
+ className={classes.root}
24
+ sx={[
25
+ (theme) => ({
26
+ marginTop: theme.spacings.md,
27
+ marginBottom: theme.spacings.sm,
28
+ textAlign: 'center',
29
+ }),
30
+ ...(Array.isArray(sx) ? sx : [sx]),
31
+ ]}
32
+ >
33
+ {term ? (
34
+ <>
35
+ <Typography variant='h5' align='center'>
36
+ <Trans>We couldn&apos;t find any results for {term}</Trans>
37
+ </Typography>
38
+ <p>
39
+ {hasFilters ? (
40
+ <Trans>
41
+ Try a different search or{' '}
42
+ <Link
43
+ href='#'
44
+ onClick={(e) => {
45
+ e.preventDefault()
46
+ return clearAll()
47
+ }}
48
+ >
49
+ clear current filters
50
+ </Link>
51
+ </Trans>
52
+ ) : (
53
+ <Trans>Try a different search</Trans>
54
+ )}
55
+ </p>
56
+ </>
57
+ ) : (
58
+ <>
59
+ <Typography variant='h5' align='center'>
60
+ <Trans>We couldn&apos;t find any results</Trans>
61
+ </Typography>
62
+ {hasFilters && (
63
+ <p>
64
+ <Link
65
+ href='#'
66
+ onClick={(e) => {
67
+ e.preventDefault()
68
+ return clearAll()
69
+ }}
70
+ >
71
+ <Trans>Clear current filters</Trans>
72
+ </Link>
73
+ </p>
74
+ )}
75
+ </>
76
+ )}
77
+ </Box>
78
+ )
79
+ }
@@ -1,9 +1,5 @@
1
- import {
2
- ActionCard,
3
- ActionCardListForm,
4
- ChipOverlayOrPopper,
5
- ChipOverlayOrPopperProps,
6
- } from '@graphcommerce/next-ui'
1
+ import { ActionCardListForm } from '@graphcommerce/ecommerce-ui'
2
+ import { ActionCard, ChipOverlayOrPopper, ChipOverlayOrPopperProps } from '@graphcommerce/next-ui'
7
3
  import { Trans } from '@lingui/react'
8
4
  import { useProductFiltersPro } from './ProductFiltersPro'
9
5
  import { UseProductFiltersProSortProps, useProductFiltersProSort } from './useProductFiltersProSort'
@@ -15,10 +11,12 @@ export type ProductListActionSortProps = UseProductFiltersProSortProps &
15
11
  >
16
12
 
17
13
  export function ProductFiltersProSortChip(props: ProductListActionSortProps) {
18
- const { sort_fields, chipProps, category, ...rest } = props
14
+ const { sort_fields, total_count, chipProps, category, ...rest } = props
19
15
  const { submit, form } = useProductFiltersPro()
20
16
  const { options, showReset, selected, selectedLabel } = useProductFiltersProSort(props)
21
17
 
18
+ if ((options.length ?? 0) <= 1) return null
19
+
22
20
  return (
23
21
  <ChipOverlayOrPopper
24
22
  {...rest}
@@ -1,7 +1,5 @@
1
1
  import { SortEnum } from '@graphcommerce/graphql-mesh'
2
- import { IconSvg } from '@graphcommerce/next-ui'
3
- import * as IconArrowDown from '@graphcommerce/next-ui/icons/arrow-down.svg'
4
- import * as IconArrowUp from '@graphcommerce/next-ui/icons/arrow-up.svg'
2
+ import { IconSvg, iconArrowDown, iconArrowUp } from '@graphcommerce/next-ui'
5
3
 
6
4
  type Props = {
7
5
  sortDirection: SortEnum | null
@@ -10,7 +8,7 @@ type Props = {
10
8
  export function ProductFiltersProSortDirectionArrow({ sortDirection }: Props) {
11
9
  return (
12
10
  <IconSvg
13
- src={sortDirection === 'ASC' || sortDirection === null ? IconArrowUp : IconArrowDown}
11
+ src={sortDirection === 'ASC' || sortDirection === null ? iconArrowUp : iconArrowDown}
14
12
  sx={{ display: 'flex' }}
15
13
  />
16
14
  )
@@ -1,16 +1,24 @@
1
- import { ActionCard, ActionCardAccordion, ActionCardListForm, Button } from '@graphcommerce/next-ui'
1
+ import { ActionCardListForm } from '@graphcommerce/ecommerce-ui'
2
+ import { ActionCard, ActionCardAccordion, Button } from '@graphcommerce/next-ui'
2
3
  import { Trans } from '@lingui/react'
4
+ import { SxProps, Theme } from '@mui/material'
3
5
  import { useProductFiltersPro } from './ProductFiltersPro'
4
6
  import { UseProductFiltersProSortProps, useProductFiltersProSort } from './useProductFiltersProSort'
5
7
 
6
- export type ProductFiltersProSortSectionProps = UseProductFiltersProSortProps
8
+ export type ProductFiltersProSortSectionProps = UseProductFiltersProSortProps & {
9
+ sx?: SxProps<Theme>
10
+ }
7
11
 
8
12
  export function ProductFiltersProSortSection(props: ProductFiltersProSortSectionProps) {
13
+ const { sx } = props
9
14
  const { form } = useProductFiltersPro()
10
15
  const { options, showReset, selected } = useProductFiltersProSort(props)
11
16
 
17
+ if ((options.length ?? 0) <= 1) return null
18
+
12
19
  return (
13
20
  <ActionCardAccordion
21
+ sx={sx}
14
22
  defaultExpanded={selected}
15
23
  summary={<Trans id='Sort By' />}
16
24
  details={
@@ -19,7 +27,7 @@ export function ProductFiltersProSortSection(props: ProductFiltersProSortSection
19
27
  name='sort'
20
28
  layout='list'
21
29
  variant='default'
22
- size='medium'
30
+ size='responsive'
23
31
  render={ActionCard}
24
32
  items={options}
25
33
  />
@@ -2,21 +2,17 @@ import { filterNonNullableKeys } from '@graphcommerce/next-ui'
2
2
  import { ProductListFiltersFragment } from '../ProductListFilters/ProductListFilters.gql'
3
3
  import { ProductFilterParams } from '../ProductListItems/filterTypes'
4
4
 
5
- export function excludeCategory(
6
- aggregations: ProductListFiltersFragment['aggregations'],
7
- params: ProductFilterParams,
8
- ) {
9
- return filterNonNullableKeys(aggregations).filter(({ attribute_code }) => {
10
- if (params.search !== null) return true
11
- return attribute_code !== 'category_id' && attribute_code !== 'category_uid'
12
- })
5
+ export function excludeCategory(aggregations: ProductListFiltersFragment['aggregations']) {
6
+ return filterNonNullableKeys(aggregations).filter(
7
+ ({ attribute_code }) => attribute_code !== 'category_id' && attribute_code !== 'category_uid',
8
+ )
13
9
  }
14
10
 
15
11
  export function activeAggregations(
16
12
  aggregations: ProductListFiltersFragment['aggregations'],
17
13
  params: ProductFilterParams,
18
14
  ) {
19
- return excludeCategory(aggregations, params).filter(
15
+ return excludeCategory(aggregations).filter(
20
16
  ({ attribute_code }) =>
21
17
  params.filters[attribute_code]?.from ||
22
18
  params.filters[attribute_code]?.to ||
@@ -27,14 +27,20 @@ export function applyAggregationCount(
27
27
 
28
28
  return {
29
29
  ...aggregation,
30
- options: filterNonNullableKeys(aggregation?.options)?.map((option) => {
31
- if (applied && filterCount === 1) return option
32
- if (applied && filterCount > 1) return { ...option, count: null }
33
- return {
34
- ...option,
35
- count: appliedAggregation?.options?.find((o) => o?.value === option?.value)?.count ?? 0,
36
- }
37
- }),
30
+ options: filterNonNullableKeys(aggregation?.options)
31
+ ?.map((option) => {
32
+ if (applied && filterCount === 1) return option
33
+ if (applied && filterCount > 1) return { ...option, count: null }
34
+ return {
35
+ ...option,
36
+ count: appliedAggregation?.options?.find((o) => o?.value === option?.value)?.count ?? 0,
37
+ }
38
+ })
39
+ .sort((a, b) => {
40
+ if (a.count === 0) return 1
41
+ if (b.count === 0) return -1
42
+ return 0
43
+ }),
38
44
  }
39
45
  })
40
46
  }
@@ -1,11 +1,20 @@
1
1
  export * from './ProductFilterEqualChip'
2
+ export * from './ProductFilterEqualSection'
2
3
  export * from './ProductFilterRangeChip'
4
+ export * from './ProductFilterRangeSection'
3
5
  export * from './ProductFiltersPro'
6
+ export * from './ProductFiltersProAggregations'
4
7
  export * from './ProductFiltersProAllFiltersChip'
5
8
  export * from './ProductFiltersProAllFiltersSidebar'
9
+ export * from './ProductFiltersProCategorySection'
6
10
  export * from './ProductFiltersProChips'
7
11
  export * from './ProductFiltersProClearAll'
8
12
  export * from './ProductFiltersProLayoutSidebar'
9
13
  export * from './ProductFiltersProLimitChip'
10
14
  export * from './ProductFiltersProLimitSection'
11
15
  export * from './ProductFiltersProSortChip'
16
+ export * from './ProductFiltersProSortDirectionArrow'
17
+ export * from './ProductFiltersProSortSection'
18
+ export * from './useProductFiltersProClearAllAction'
19
+ export * from './useProductFiltersProHasFiltersApplied'
20
+ export * from './ProductFiltersProNoResults'
@@ -2,7 +2,7 @@ import { useCallback } from 'react'
2
2
  import { ProductFilterParams } from '../ProductListItems/filterTypes'
3
3
  import { useProductFiltersPro } from './ProductFiltersPro'
4
4
 
5
- export function useClearAllFiltersAction() {
5
+ export function useProductFiltersProClearAllAction() {
6
6
  const { form, submit } = useProductFiltersPro()
7
7
  const { reset, getValues } = form
8
8
 
@@ -0,0 +1,21 @@
1
+ import { useMemo } from 'react'
2
+ import { useProductFiltersPro } from './ProductFiltersPro'
3
+ import { activeAggregations } from './activeAggregations'
4
+ import { applyAggregationCount } from './applyAggregationCount'
5
+
6
+ export function useProductFilterProHasFiltersApplied() {
7
+ const { params, aggregations, appliedAggregations } = useProductFiltersPro()
8
+ const { sort } = params
9
+
10
+ const hasFilters = useMemo(() => {
11
+ const activeFilters = activeAggregations(
12
+ applyAggregationCount(aggregations, appliedAggregations, params),
13
+ params,
14
+ ).map(({ label }) => label)
15
+
16
+ const allFilters = [...activeFilters, sort].filter(Boolean)
17
+ return allFilters.length > 0
18
+ }, [aggregations, appliedAggregations, params, sort])
19
+
20
+ return hasFilters
21
+ }
@@ -27,7 +27,7 @@ export function useProductFiltersProSort(props: ProductListActionSortProps) {
27
27
  () =>
28
28
  filterNonNullableKeys(sort_fields?.options).map((o) =>
29
29
  !category?.uid && o.value === 'position'
30
- ? { value: 'relevance', label: i18n._('Relevance') }
30
+ ? { value: 'relevance', label: i18n._(/* i18n*/ 'Relevance') }
31
31
  : o,
32
32
  ),
33
33
  [category?.uid, sort_fields?.options],
@@ -36,12 +36,16 @@ export function useProductFiltersProSort(props: ProductListActionSortProps) {
36
36
 
37
37
  const conf = useQuery(StoreConfigDocument).data?.storeConfig
38
38
  const defaultSortBy = (
39
- category ? category.default_sort_by ?? conf?.catalog_default_sort_by ?? 'position' : 'relevance'
39
+ category
40
+ ? (category.default_sort_by ?? conf?.catalog_default_sort_by ?? 'position')
41
+ : 'relevance'
40
42
  ) as ProductFilterParams['sort']
41
43
 
42
44
  const formSort = useWatch({ control, name: 'sort' })
43
45
  const formDirection = useWatch({ control, name: 'dir' })
44
- const showReset = Boolean(formSort !== defaultSortBy || formDirection === 'DESC')
46
+ const showReset =
47
+ (formDirection !== null || formSort !== null) &&
48
+ Boolean(formSort !== defaultSortBy || formDirection === 'DESC')
45
49
  const selected = Boolean(params.sort && (params.sort !== defaultSortBy || params.dir === 'DESC'))
46
50
 
47
51
  const options = useMemo(
@@ -4,6 +4,8 @@ query ProductList(
4
4
  $filters: ProductAttributeFilterInput = {}
5
5
  $sort: ProductAttributeSortInput = {}
6
6
  $search: String = ""
7
+ $context: InContextInput = { loggedIn: false }
8
+ $onlyItems: Boolean = false
7
9
  ) {
8
10
  products(
9
11
  pageSize: $pageSize
@@ -11,11 +13,12 @@ query ProductList(
11
13
  filter: $filters
12
14
  sort: $sort
13
15
  search: $search
14
- ) {
15
- ...ProductListFilters
16
- ...ProductListCount
17
- ...ProductListPagination
18
- ...ProductListSort
16
+ ) @inContext(context: $context) {
17
+ ...ProductListSuggestions @skip(if: $onlyItems)
18
+ ...ProductListFilters @skip(if: $onlyItems)
19
+ ...ProductListCount @skip(if: $onlyItems)
20
+ ...ProductListPagination @skip(if: $onlyItems)
21
+ ...ProductListSort @skip(if: $onlyItems)
19
22
  ...ProductListItems
20
23
  }
21
24
  }
@@ -11,10 +11,11 @@ const { classes, selectors } = extendableComponent('ProductListCount', [
11
11
 
12
12
  export type ProductCountProps = ProductListCountFragment & {
13
13
  sx?: SxProps<Theme>
14
+ children?: React.ReactNode
14
15
  }
15
16
 
16
17
  export function ProductListCount(props: ProductCountProps) {
17
- const { total_count, sx = [] } = props
18
+ const { total_count, children, sx = [] } = props
18
19
 
19
20
  return (
20
21
  <Box
@@ -41,6 +42,7 @@ export function ProductListCount(props: ProductCountProps) {
41
42
  className={classes.count}
42
43
  sx={{ lineHeight: 0 }}
43
44
  >
45
+ {children ? <> {children} </> : null}
44
46
  {total_count === 0 && <Trans id='no products' />}
45
47
  {total_count === 1 && <Trans id='one product' />}
46
48
  {(total_count ?? 0) > 1 && <Trans id='{total_count} products' values={{ total_count }} />}
@@ -1,5 +1,14 @@
1
- query ProductFilters($filters: ProductAttributeFilterInput = {}, $search: String) {
2
- filters: products(filter: $filters, currentPage: 1, pageSize: 1, search: $search) {
1
+ query ProductFilters(
2
+ $filters: ProductAttributeFilterInput = {}
3
+ $search: String
4
+ $context: InContextInput
5
+ $pageSize: Int = 1
6
+ ) {
7
+ filters: products(filter: $filters, currentPage: 1, pageSize: $pageSize, search: $search)
8
+ @inContext(context: $context) {
9
+ page_info {
10
+ total_pages
11
+ }
3
12
  ...ProductListFilters
4
13
  }
5
14
  }
@@ -1,5 +1,5 @@
1
1
  fragment ProductListFilters on Products {
2
- aggregations {
2
+ aggregations(filter: { category: { includeDirectChildrenOnly: true } }) {
3
3
  __typename
4
4
  label
5
5
  attribute_code
@@ -1,5 +1,5 @@
1
1
  import { ChipMenuProps } from '@graphcommerce/next-ui'
2
- import { FilterTypes } from '../ProductListItems/filterTypes'
2
+ import { FilterTypes } from '../ProductListItems/getFilterTypes'
3
3
  import { FilterCheckboxType } from './FilterCheckboxType'
4
4
  import { FilterEqualType } from './FilterEqualType'
5
5
  import { FilterRangeType } from './FilterRangeType'
@@ -23,22 +23,16 @@ export function ProductListFilters(props: ProductFiltersProps) {
23
23
  return null
24
24
 
25
25
  switch (filterTypes[aggregation.attribute_code]) {
26
- case 'FilterEqualTypeInput':
27
- if (
28
- aggregation.options?.[0]?.label === '0' ||
29
- aggregation.options?.[1]?.label === '0' ||
30
- aggregation.options?.[0]?.label === '1' ||
31
- aggregation.options?.[1]?.label === '1'
32
- ) {
33
- return (
34
- <FilterCheckboxType
35
- key={aggregation.attribute_code}
36
- {...aggregation}
37
- {...chipMenuProps}
38
- />
39
- )
40
- }
41
-
26
+ case 'BOOLEAN':
27
+ return (
28
+ <FilterCheckboxType
29
+ key={aggregation.attribute_code}
30
+ {...aggregation}
31
+ {...chipMenuProps}
32
+ />
33
+ )
34
+ case 'SELECT':
35
+ case 'MULTISELECT':
42
36
  return (
43
37
  <FilterEqualType
44
38
  key={aggregation.attribute_code}
@@ -46,8 +40,7 @@ export function ProductListFilters(props: ProductFiltersProps) {
46
40
  {...chipMenuProps}
47
41
  />
48
42
  )
49
-
50
- case 'FilterRangeTypeInput':
43
+ case 'PRICE':
51
44
  return (
52
45
  <FilterRangeType
53
46
  key={aggregation.attribute_code}
@@ -56,6 +49,7 @@ export function ProductListFilters(props: ProductFiltersProps) {
56
49
  />
57
50
  )
58
51
  }
52
+
59
53
  // console.log(
60
54
  // 'Filter not recognized',
61
55
  // aggregation.attribute_code,
@@ -97,7 +97,7 @@ export function ProductListFiltersContainer(props: ProductListFiltersContainerPr
97
97
  top: theme.page.vertical,
98
98
  zIndex: 9,
99
99
  margin: '0 auto',
100
- maxWidth: `calc(100% - 96px - ${theme.spacings.sm} * 2)`,
100
+
101
101
  [theme.breakpoints.down('md')]: {
102
102
  textAlign: 'center',
103
103
  maxWidth: 'unset',
@@ -136,8 +136,7 @@ export function ProductListFiltersContainer(props: ProductListFiltersContainerPr
136
136
  className={classes.scroller}
137
137
  hideScrollbar
138
138
  sx={(theme) => ({
139
- paddingLeft: theme.page.horizontal,
140
- paddingRight: theme.page.horizontal,
139
+ px: theme.page.horizontal,
141
140
  paddingBottom: '1px',
142
141
  [theme.breakpoints.up('md')]: {
143
142
  borderRadius: '99em',
@@ -145,7 +144,6 @@ export function ProductListFiltersContainer(props: ProductListFiltersContainerPr
145
144
  paddingRight: '8px',
146
145
  },
147
146
  py: '5px',
148
-
149
147
  columnGap: '6px',
150
148
  gridAutoColumns: 'min-content',
151
149
  })}
@@ -1,4 +1,4 @@
1
- import { useNumberFormat } from '@graphcommerce/next-ui'
1
+ import { PercentFormat } from '@graphcommerce/next-ui'
2
2
  import { Box, BoxProps } from '@mui/material'
3
3
  import { ProductListItemFragment } from '../../Api/ProductListItem.gql'
4
4
 
@@ -7,7 +7,6 @@ export type ProductDiscountLabelProps = Pick<ProductListItemFragment, 'price_ran
7
7
 
8
8
  export function ProductDiscountLabel(props: ProductDiscountLabelProps) {
9
9
  const { price_range, ...boxProps } = props
10
- const formatter = useNumberFormat({ style: 'percent', maximumFractionDigits: 1 })
11
10
  const discount = Math.floor(price_range.minimum_price.discount?.percent_off ?? 0)
12
11
 
13
12
  return (
@@ -26,7 +25,7 @@ export function ProductDiscountLabel(props: ProductDiscountLabelProps) {
26
25
  }}
27
26
  {...boxProps}
28
27
  >
29
- {formatter.format(discount / -100)}
28
+ <PercentFormat>{discount / 100}</PercentFormat>
30
29
  </Box>
31
30
  )}
32
31
  </>
@@ -85,8 +85,8 @@ export function ProductListItemReal(props: ProductProps) {
85
85
  onClick,
86
86
  } = props
87
87
 
88
- const handleClick = useEventCallback(
89
- (e: React.MouseEvent<HTMLAnchorElement>) => onClick?.(e, props),
88
+ const handleClick = useEventCallback((e: React.MouseEvent<HTMLAnchorElement>) =>
89
+ onClick?.(e, props),
90
90
  )
91
91
 
92
92
  return (
@@ -140,7 +140,7 @@ export function ProductListItemReal(props: ProductProps) {
140
140
  )
141
141
  }
142
142
 
143
- export function ProductListItemSkeleton(props: SkeletonProps) {
143
+ export function ProductListItemSkeleton(props: BaseProps) {
144
144
  const { children, imageOnly = false, aspectRatio, titleComponent = 'h2', sx = [] } = props
145
145
 
146
146
  return (
@@ -1,4 +1,4 @@
1
- import { Box, Typography } from '@mui/material'
1
+ import { Box, SxProps, Theme, Typography } from '@mui/material'
2
2
  import { productListPrice } from '../ProductListPrice'
3
3
 
4
4
  export type ProductListItemTitleAndPriceProps = {
@@ -7,26 +7,30 @@ export type ProductListItemTitleAndPriceProps = {
7
7
  subTitle?: React.ReactNode
8
8
  children: React.ReactNode
9
9
  classes: { titleContainer: string; title: string; subtitle: string }
10
+ sx?: SxProps<Theme>
10
11
  }
11
12
 
12
13
  export function ProductListItemTitleAndPrice(props: ProductListItemTitleAndPriceProps) {
13
- const { titleComponent = 'h2', classes, children, subTitle, title } = props
14
+ const { titleComponent = 'h2', classes, children, subTitle, title, sx } = props
14
15
 
15
16
  return (
16
17
  <Box
17
18
  className={classes.titleContainer}
18
- sx={(theme) => ({
19
- display: 'grid',
20
- alignItems: 'baseline',
21
- marginTop: theme.spacings.xs,
22
- columnGap: 1,
23
- gridTemplateAreas: {
24
- xs: `"title title" "subtitle price"`,
25
- md: `"title subtitle price"`,
26
- },
27
- gridTemplateColumns: { xs: 'unset', md: 'auto auto 1fr' },
28
- justifyContent: 'space-between',
29
- })}
19
+ sx={[
20
+ (theme) => ({
21
+ display: 'grid',
22
+ alignItems: 'baseline',
23
+ marginTop: theme.spacings.xs,
24
+ columnGap: 1,
25
+ gridTemplateAreas: {
26
+ xs: `"title title" "subtitle price"`,
27
+ md: `"title subtitle price"`,
28
+ },
29
+ gridTemplateColumns: { xs: 'unset', md: 'auto auto 1fr' },
30
+ justifyContent: 'space-between',
31
+ }),
32
+ ...(Array.isArray(sx) ? sx : [sx]),
33
+ ]}
30
34
  >
31
35
  <Typography
32
36
  component={titleComponent}
@@ -37,7 +41,6 @@ export function ProductListItemTitleAndPrice(props: ProductListItemTitleAndPrice
37
41
  overflowWrap: 'break-word',
38
42
  maxWidth: '100%',
39
43
  gridArea: 'title',
40
- fontWeight: 'fontWeightBold',
41
44
  }}
42
45
  className={classes.title}
43
46
  >
@@ -0,0 +1,8 @@
1
+ query ProductFilterTypes($filters: AttributeFilterInput!) {
2
+ attributesList(entityType: CATALOG_PRODUCT, filters: $filters) {
3
+ items {
4
+ code
5
+ frontend_input
6
+ }
7
+ }
8
+ }