@graphcommerce/magento-product 8.1.0-canary.43 → 8.1.0-canary.45

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 (52) hide show
  1. package/Api/ProductListItem.graphql +0 -1
  2. package/CHANGELOG.md +4 -0
  3. package/components/AddProductsToCart/AddProductsToCartButton.tsx +2 -2
  4. package/components/AddProductsToCart/AddProductsToCartFab.tsx +2 -2
  5. package/components/AddProductsToCart/AddProductsToCartForm.tsx +24 -26
  6. package/components/AddProductsToCart/AddProductsToCartSnackbar.tsx +25 -16
  7. package/components/ProductAddToCart/ProductAddToCart.tsx +6 -8
  8. package/components/ProductFiltersPro/PriceSlider.tsx +1 -2
  9. package/components/ProductFiltersPro/ProductFilterEqualChip.tsx +1 -1
  10. package/components/ProductFiltersPro/ProductFilterEqualSection.tsx +2 -2
  11. package/components/ProductFiltersPro/ProductFilterRangeChip.tsx +1 -1
  12. package/components/ProductFiltersPro/ProductFilterRangeSection.tsx +1 -1
  13. package/components/ProductFiltersPro/ProductFiltersPro.tsx +79 -17
  14. package/components/ProductFiltersPro/ProductFiltersProAggregations.tsx +17 -18
  15. package/components/ProductFiltersPro/ProductFiltersProAllFiltersChip.tsx +2 -2
  16. package/components/ProductFiltersPro/ProductFiltersProCategorySection.tsx +99 -39
  17. package/components/ProductFiltersPro/ProductFiltersProClearAll.tsx +4 -16
  18. package/components/ProductFiltersPro/ProductFiltersProLimitSection.tsx +1 -1
  19. package/components/ProductFiltersPro/ProductFiltersProNoResults.tsx +79 -0
  20. package/components/ProductFiltersPro/ProductFiltersProSortChip.tsx +1 -1
  21. package/components/ProductFiltersPro/ProductFiltersProSortSection.tsx +1 -1
  22. package/components/ProductFiltersPro/activeAggregations.ts +5 -9
  23. package/components/ProductFiltersPro/applyAggregationCount.ts +14 -8
  24. package/components/ProductFiltersPro/index.ts +4 -1
  25. package/components/ProductFiltersPro/{useClearAllFiltersHandler.ts → useProductFiltersProClearAllAction.ts} +1 -1
  26. package/components/ProductFiltersPro/useProductFiltersProHasFiltersApplied.ts +21 -0
  27. package/components/ProductList/ProductList.graphql +8 -5
  28. package/components/ProductListCount/ProductListCount.tsx +3 -1
  29. package/components/ProductListFilters/ProductFilters.graphql +7 -2
  30. package/components/ProductListFilters/ProductListFilters.graphql +1 -1
  31. package/components/ProductListItem/ProductDiscountLabel.tsx +2 -3
  32. package/components/ProductListItem/ProductListItem.tsx +3 -3
  33. package/components/ProductListItem/ProductListItemTitleAndPrice.tsx +18 -15
  34. package/components/ProductListItems/ProductListItemsBase.tsx +65 -23
  35. package/components/ProductListItems/filterTypes.tsx +14 -5
  36. package/components/ProductListItems/filteredProductList.tsx +23 -0
  37. package/components/ProductListItems/productListApplyCategoryDefaults.ts +44 -4
  38. package/components/ProductListItems/renderer.tsx +8 -2
  39. package/components/ProductListPagination/ProductListPagination.tsx +3 -1
  40. package/components/ProductListPrice/ProductListPrice.tsx +9 -4
  41. package/components/ProductListSuggestions/ProductListSuggestions.graphql +5 -0
  42. package/components/ProductListSuggestions/ProductListSuggestions.tsx +42 -0
  43. package/components/ProductPageDescription/ProductPageDescription.tsx +1 -1
  44. package/components/ProductPagePrice/ProductPagePrice.graphql +0 -6
  45. package/components/ProductPagePrice/ProductPagePrice.tsx +19 -12
  46. package/components/ProductPagePrice/ProductPagePriceTiers.tsx +4 -3
  47. package/components/ProductWeight/ProductWeight.tsx +12 -9
  48. package/components/index.ts +2 -0
  49. package/hooks/useProductList.ts +123 -0
  50. package/hooks/useProductListLink.ts +6 -3
  51. package/index.ts +1 -0
  52. package/package.json +14 -14
@@ -1,27 +1,36 @@
1
- import { UseCategoryTreeProps, useCategoryTree } from '@graphcommerce/magento-category'
1
+ import { useWatch } from '@graphcommerce/ecommerce-ui'
2
+ import {
3
+ CategoryTreeItem,
4
+ UseCategoryTreeProps,
5
+ useCategoryTree,
6
+ } from '@graphcommerce/magento-category'
2
7
  import {
3
8
  ActionCard,
4
9
  ActionCardAccordion,
10
+ ActionCardAccordionProps,
5
11
  ActionCardList,
12
+ Button,
6
13
  IconSvg,
7
14
  iconChevronLeft,
8
15
  responsiveVal,
9
16
  } from '@graphcommerce/next-ui'
10
17
  import { Trans } from '@lingui/react'
11
18
  import { Box, SxProps, Theme } from '@mui/material'
12
- import { useRouter } from 'next/router'
19
+ import { useProductFiltersPro } from './ProductFiltersPro'
13
20
 
14
- export type ProductFiltersCategorySectionProps = UseCategoryTreeProps & {
21
+ export type ProductFiltersProCategoryAccordionProps = {
15
22
  hideTitle?: boolean
16
23
  sx?: SxProps<Theme>
17
- }
24
+ categoryTree: CategoryTreeItem[]
25
+ onChange: (uid: CategoryTreeItem) => void | Promise<void>
26
+ } & Pick<ActionCardAccordionProps, 'defaultExpanded'>
18
27
 
19
- export function ProductFiltersProCategorySection(props: ProductFiltersCategorySectionProps) {
20
- const { hideTitle, sx } = props
21
- const router = useRouter()
22
- const categoryTree = useCategoryTree(props)
28
+ export function ProductFiltersProCategoryAccordion(props: ProductFiltersProCategoryAccordionProps) {
29
+ const { hideTitle, sx, categoryTree, onChange, defaultExpanded } = props
30
+ const { form } = useProductFiltersPro()
23
31
 
24
- if (!categoryTree) return null
32
+ const name = `filters.category_uid.in` as const
33
+ const currentFilter = useWatch({ control: form.control, name })
25
34
 
26
35
  return (
27
36
  <ActionCardAccordion
@@ -30,41 +39,92 @@ export function ProductFiltersProCategorySection(props: ProductFiltersCategorySe
30
39
  sx,
31
40
  ...(Array.isArray(sx) ? sx : [sx]),
32
41
  ]}
33
- defaultExpanded
42
+ defaultExpanded={defaultExpanded}
34
43
  summary={<Trans id='Categories' />}
44
+ right={
45
+ currentFilter && currentFilter.length > 0 ? (
46
+ <Button
47
+ color='primary'
48
+ onClick={(e) => {
49
+ e.stopPropagation()
50
+ form.setValue(name, null)
51
+ }}
52
+ >
53
+ <Trans id='Clear' />
54
+ </Button>
55
+ ) : undefined
56
+ }
35
57
  details={
36
- <ActionCardList value='cat' variant='default'>
37
- {categoryTree.map((item) => (
38
- <ActionCard
39
- {...item}
40
- title={
41
- item.isBack ? (
42
- <Box sx={{ display: 'flex', alignItems: 'center' }}>
43
- <IconSvg src={iconChevronLeft} size='medium' />
44
- {item.title}
58
+ <ActionCardList
59
+ size='responsive'
60
+ variant='default'
61
+ sx={{ mb: 2 }}
62
+ value={form.getValues('url')}
63
+ onChange={async (e, value) => {
64
+ const item = categoryTree.find((i) => i.value === value)
65
+ if (!item) return
66
+ await onChange(item)
67
+ }}
68
+ >
69
+ {categoryTree.map((item) => {
70
+ const indent = item.isBack ? 0 : item.indent + 1
71
+ return (
72
+ <ActionCard
73
+ key={item.value}
74
+ {...item}
75
+ size='responsive'
76
+ title={
77
+ <Box sx={{ display: 'flex', alignItems: 'center', gap: 1 }}>
78
+ <Box sx={{ marginRight: 1 }}>
79
+ {item.isBack ? (
80
+ <Box sx={{ display: 'flex', alignItems: 'center' }}>
81
+ <IconSvg src={iconChevronLeft} size='medium' />
82
+ {item.title}
83
+ </Box>
84
+ ) : (
85
+ item.title
86
+ )}
87
+ </Box>
88
+ {item.count !== null && (
89
+ <Box sx={{ typography: 'caption', color: 'text.disabled' }}>
90
+ ({item.count})
91
+ </Box>
92
+ )}
45
93
  </Box>
46
- ) : (
47
- item.title
48
- )
49
- }
50
- sx={[
51
- item.isBack ? {} : {},
52
- {
53
- '&.sizeSmall': { pl: responsiveVal(8 * item.indent, 12 * item.indent) },
54
- '&.sizeMedium': { pl: responsiveVal(10 * item.indent, 14 * item.indent) },
55
- '&.sizeLarge': { pl: responsiveVal(12 * item.indent, 16 * item.indent) },
56
- '&.sizeResponsive': { pl: responsiveVal(8 * item.indent, 16 * item.indent) },
57
- },
58
- ]}
59
- value={item.href}
60
- key={item.href}
61
- selected={item.selected}
62
- onClick={() => router.push(item.href)}
63
- />
64
- ))}
94
+ }
95
+ sx={{
96
+ '&.sizeSmall': { pl: responsiveVal(8 * indent, 12 * indent) },
97
+ '&.sizeMedium': { pl: responsiveVal(10 * indent, 14 * indent) },
98
+ '&.sizeLarge': { pl: responsiveVal(12 * indent, 16 * indent) },
99
+ '&.sizeResponsive': { pl: responsiveVal(8 * indent, 16 * indent) },
100
+ '& .ActionCard-title.selected': { fontWeight: 'bold' },
101
+ }}
102
+ />
103
+ )
104
+ })}
65
105
  </ActionCardList>
66
106
  }
67
- right={undefined}
107
+ />
108
+ )
109
+ }
110
+
111
+ export type ProductFiltersCategorySectionProps = UseCategoryTreeProps &
112
+ Omit<ProductFiltersProCategoryAccordionProps, 'categoryTree' | 'onChange'>
113
+
114
+ export function ProductFiltersProCategorySection(props: ProductFiltersCategorySectionProps) {
115
+ const categoryTree = useCategoryTree(props)
116
+ const { form, submit } = useProductFiltersPro()
117
+
118
+ if (!categoryTree) return null
119
+ return (
120
+ <ProductFiltersProCategoryAccordion
121
+ categoryTree={categoryTree}
122
+ {...props}
123
+ onChange={async (item) => {
124
+ form.setValue('url', item.value)
125
+ form.setValue('filters', { category_uid: { in: [item?.uid] } })
126
+ await submit()
127
+ }}
68
128
  />
69
129
  )
70
130
  }
@@ -1,11 +1,9 @@
1
1
  import { Button } from '@graphcommerce/next-ui'
2
2
  import { Trans } from '@lingui/react'
3
3
  import { SxProps, Theme } from '@mui/material'
4
- import { useProductFiltersPro } from './ProductFiltersPro'
5
4
  import { ProductFiltersProAggregationsProps } from './ProductFiltersProAggregations'
6
- import { activeAggregations } from './activeAggregations'
7
- import { applyAggregationCount } from './applyAggregationCount'
8
- import { useClearAllFiltersAction } from './useClearAllFiltersHandler'
5
+ import { useProductFiltersProClearAllAction } from './useProductFiltersProClearAllAction'
6
+ import { useProductFilterProHasFiltersApplied } from './useProductFiltersProHasFiltersApplied'
9
7
 
10
8
  type AllFiltersSidebar = ProductFiltersProAggregationsProps & {
11
9
  sx?: SxProps<Theme>
@@ -14,18 +12,8 @@ type AllFiltersSidebar = ProductFiltersProAggregationsProps & {
14
12
  export function ProductFiltersProClearAll(props: AllFiltersSidebar) {
15
13
  const { sx = [] } = props
16
14
 
17
- const { params, aggregations, appliedAggregations } = useProductFiltersPro()
18
- const { sort } = params
19
-
20
- const clearAll = useClearAllFiltersAction()
21
-
22
- const activeFilters = activeAggregations(
23
- applyAggregationCount(aggregations, appliedAggregations, params),
24
- params,
25
- ).map(({ label }) => label)
26
-
27
- const allFilters = [...activeFilters, sort].filter(Boolean)
28
- const hasFilters = allFilters.length > 0
15
+ const clearAll = useProductFiltersProClearAllAction()
16
+ const hasFilters = useProductFilterProHasFiltersApplied()
29
17
 
30
18
  if (!hasFilters) return null
31
19
 
@@ -51,7 +51,7 @@ export function ProductFiltersProLimitSection(props: ProductFiltersProLimitSecti
51
51
  control={control}
52
52
  layout='list'
53
53
  variant='default'
54
- size='medium'
54
+ size='responsive'
55
55
  items={options}
56
56
  />
57
57
  }
@@ -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
+ }
@@ -15,7 +15,7 @@ export type ProductListActionSortProps = UseProductFiltersProSortProps &
15
15
  >
16
16
 
17
17
  export function ProductFiltersProSortChip(props: ProductListActionSortProps) {
18
- const { sort_fields, chipProps, category, ...rest } = props
18
+ const { sort_fields, total_count, chipProps, category, ...rest } = props
19
19
  const { submit, form } = useProductFiltersPro()
20
20
  const { options, showReset, selected, selectedLabel } = useProductFiltersProSort(props)
21
21
 
@@ -24,7 +24,7 @@ export function ProductFiltersProSortSection(props: ProductFiltersProSortSection
24
24
  name='sort'
25
25
  layout='list'
26
26
  variant='default'
27
- size='medium'
27
+ size='responsive'
28
28
  render={ActionCard}
29
29
  items={options}
30
30
  />
@@ -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
  }
@@ -3,6 +3,7 @@ export * from './ProductFilterEqualSection'
3
3
  export * from './ProductFilterRangeChip'
4
4
  export * from './ProductFilterRangeSection'
5
5
  export * from './ProductFiltersPro'
6
+ export * from './ProductFiltersProAggregations'
6
7
  export * from './ProductFiltersProAllFiltersChip'
7
8
  export * from './ProductFiltersProAllFiltersSidebar'
8
9
  export * from './ProductFiltersProCategorySection'
@@ -13,5 +14,7 @@ export * from './ProductFiltersProLimitChip'
13
14
  export * from './ProductFiltersProLimitSection'
14
15
  export * from './ProductFiltersProSortChip'
15
16
  export * from './ProductFiltersProSortDirectionArrow'
16
- export * from './ProductFiltersProAggregations'
17
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
+ }
@@ -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,10 @@
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
+ ) {
6
+ filters: products(filter: $filters, currentPage: 1, pageSize: 1, search: $search)
7
+ @inContext(context: $context) {
3
8
  ...ProductListFilters
4
9
  }
5
10
  }
@@ -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,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
  >