@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.
- package/Api/ProductListItem.graphql +0 -1
- package/CHANGELOG.md +4 -0
- package/components/AddProductsToCart/AddProductsToCartButton.tsx +2 -2
- package/components/AddProductsToCart/AddProductsToCartFab.tsx +2 -2
- package/components/AddProductsToCart/AddProductsToCartForm.tsx +24 -26
- package/components/AddProductsToCart/AddProductsToCartSnackbar.tsx +25 -16
- package/components/ProductAddToCart/ProductAddToCart.tsx +6 -8
- package/components/ProductFiltersPro/PriceSlider.tsx +1 -2
- package/components/ProductFiltersPro/ProductFilterEqualChip.tsx +1 -1
- package/components/ProductFiltersPro/ProductFilterEqualSection.tsx +2 -2
- package/components/ProductFiltersPro/ProductFilterRangeChip.tsx +1 -1
- package/components/ProductFiltersPro/ProductFilterRangeSection.tsx +1 -1
- package/components/ProductFiltersPro/ProductFiltersPro.tsx +79 -17
- package/components/ProductFiltersPro/ProductFiltersProAggregations.tsx +17 -18
- package/components/ProductFiltersPro/ProductFiltersProAllFiltersChip.tsx +2 -2
- package/components/ProductFiltersPro/ProductFiltersProCategorySection.tsx +99 -39
- package/components/ProductFiltersPro/ProductFiltersProClearAll.tsx +4 -16
- package/components/ProductFiltersPro/ProductFiltersProLimitSection.tsx +1 -1
- package/components/ProductFiltersPro/ProductFiltersProNoResults.tsx +79 -0
- package/components/ProductFiltersPro/ProductFiltersProSortChip.tsx +1 -1
- package/components/ProductFiltersPro/ProductFiltersProSortSection.tsx +1 -1
- package/components/ProductFiltersPro/activeAggregations.ts +5 -9
- package/components/ProductFiltersPro/applyAggregationCount.ts +14 -8
- package/components/ProductFiltersPro/index.ts +4 -1
- package/components/ProductFiltersPro/{useClearAllFiltersHandler.ts → useProductFiltersProClearAllAction.ts} +1 -1
- package/components/ProductFiltersPro/useProductFiltersProHasFiltersApplied.ts +21 -0
- package/components/ProductList/ProductList.graphql +8 -5
- package/components/ProductListCount/ProductListCount.tsx +3 -1
- package/components/ProductListFilters/ProductFilters.graphql +7 -2
- package/components/ProductListFilters/ProductListFilters.graphql +1 -1
- package/components/ProductListItem/ProductDiscountLabel.tsx +2 -3
- package/components/ProductListItem/ProductListItem.tsx +3 -3
- package/components/ProductListItem/ProductListItemTitleAndPrice.tsx +18 -15
- package/components/ProductListItems/ProductListItemsBase.tsx +65 -23
- package/components/ProductListItems/filterTypes.tsx +14 -5
- package/components/ProductListItems/filteredProductList.tsx +23 -0
- package/components/ProductListItems/productListApplyCategoryDefaults.ts +44 -4
- package/components/ProductListItems/renderer.tsx +8 -2
- package/components/ProductListPagination/ProductListPagination.tsx +3 -1
- package/components/ProductListPrice/ProductListPrice.tsx +9 -4
- package/components/ProductListSuggestions/ProductListSuggestions.graphql +5 -0
- package/components/ProductListSuggestions/ProductListSuggestions.tsx +42 -0
- package/components/ProductPageDescription/ProductPageDescription.tsx +1 -1
- package/components/ProductPagePrice/ProductPagePrice.graphql +0 -6
- package/components/ProductPagePrice/ProductPagePrice.tsx +19 -12
- package/components/ProductPagePrice/ProductPagePriceTiers.tsx +4 -3
- package/components/ProductWeight/ProductWeight.tsx +12 -9
- package/components/index.ts +2 -0
- package/hooks/useProductList.ts +123 -0
- package/hooks/useProductListLink.ts +6 -3
- package/index.ts +1 -0
- package/package.json +14 -14
@@ -1,27 +1,36 @@
|
|
1
|
-
import {
|
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 {
|
19
|
+
import { useProductFiltersPro } from './ProductFiltersPro'
|
13
20
|
|
14
|
-
export type
|
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
|
20
|
-
const { hideTitle, sx } = props
|
21
|
-
const
|
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
|
-
|
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
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
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
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
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
|
-
|
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 {
|
7
|
-
import {
|
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
|
18
|
-
const
|
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
|
|
@@ -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'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'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
|
|
@@ -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
|
-
|
7
|
-
|
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
|
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)
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
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
|
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
|
-
...
|
16
|
-
...
|
17
|
-
...
|
18
|
-
...
|
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(
|
2
|
-
|
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,4 +1,4 @@
|
|
1
|
-
import {
|
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
|
-
{
|
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
|
-
|
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:
|
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={
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
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
|
>
|