@graphcommerce/magento-product 8.1.0-canary.9 → 9.0.0-canary.101
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 +1 -2
- package/Api/ProductPageItem.graphql +1 -1
- package/CHANGELOG.md +278 -84
- package/Config.graphqls +13 -0
- package/components/AddProductsToCart/AddProductsToCartButton.tsx +17 -4
- package/components/AddProductsToCart/AddProductsToCartFab.tsx +7 -2
- package/components/AddProductsToCart/AddProductsToCartForm.tsx +31 -29
- package/components/AddProductsToCart/AddProductsToCartSnackbar.tsx +14 -63
- package/components/AddProductsToCart/AddProductsToCartSnackbarMessage.tsx +84 -0
- package/components/AddProductsToCart/UseAddProductsToCartAction.graphql +1 -1
- package/components/AddProductsToCart/findAddedItems.ts +1 -4
- package/components/AddProductsToCart/index.ts +1 -0
- package/components/AddProductsToCart/useAddProductsToCartAction.ts +2 -1
- package/components/AddProductsToCart/useFormAddProductsToCart.ts +1 -2
- package/components/JsonLdProduct/JsonLdProduct.graphql +1 -1
- package/components/JsonLdProduct/ProductPageJsonLd.tsx +1 -1
- package/components/ProductAddToCart/ProductAddToCart.tsx +6 -8
- package/components/ProductCustomizable/CustomizableCheckboxOption.tsx +3 -4
- package/components/ProductCustomizable/CustomizableMultipleOption.tsx +2 -2
- package/components/ProductCustomizable/CustomizableRadioOption.tsx +2 -2
- package/components/ProductCustomizable/ProductCustomizable.graphql +1 -1
- package/components/ProductCustomizable/index.ts +1 -0
- package/components/ProductCustomizable/productCustomizableSelectors.ts +59 -0
- package/components/ProductFiltersPro/PriceSlider.tsx +1 -2
- package/components/ProductFiltersPro/ProductFilterEqualChip.tsx +4 -5
- package/components/ProductFiltersPro/ProductFilterEqualSection.tsx +6 -7
- package/components/ProductFiltersPro/ProductFilterRangeChip.tsx +1 -1
- package/components/ProductFiltersPro/ProductFilterRangeSection.tsx +1 -1
- package/components/ProductFiltersPro/ProductFiltersPro.tsx +103 -19
- package/components/ProductFiltersPro/ProductFiltersProAggregations.tsx +41 -20
- package/components/ProductFiltersPro/ProductFiltersProAllFiltersChip.tsx +6 -10
- package/components/ProductFiltersPro/ProductFiltersProAllFiltersSidebar.tsx +18 -8
- package/components/ProductFiltersPro/ProductFiltersProCategorySection.tsx +130 -0
- package/components/ProductFiltersPro/ProductFiltersProChips.tsx +10 -8
- package/components/ProductFiltersPro/ProductFiltersProClearAll.tsx +4 -16
- package/components/ProductFiltersPro/ProductFiltersProLayoutSidebar.tsx +15 -7
- package/components/ProductFiltersPro/ProductFiltersProLimitChip.tsx +2 -8
- package/components/ProductFiltersPro/ProductFiltersProLimitSection.tsx +7 -10
- package/components/ProductFiltersPro/ProductFiltersProNoResults.tsx +79 -0
- package/components/ProductFiltersPro/ProductFiltersProSortChip.tsx +5 -7
- package/components/ProductFiltersPro/ProductFiltersProSortDirectionArrow.tsx +2 -4
- package/components/ProductFiltersPro/ProductFiltersProSortSection.tsx +11 -3
- package/components/ProductFiltersPro/activeAggregations.ts +5 -9
- package/components/ProductFiltersPro/applyAggregationCount.ts +14 -8
- package/components/ProductFiltersPro/index.ts +9 -0
- package/components/ProductFiltersPro/{useClearAllFiltersHandler.ts → useProductFiltersProClearAllAction.ts} +1 -1
- package/components/ProductFiltersPro/useProductFiltersProHasFiltersApplied.ts +21 -0
- package/components/ProductFiltersPro/useProductFiltersProSort.tsx +7 -3
- package/components/ProductList/ProductList.graphql +8 -5
- package/components/ProductListCount/ProductListCount.tsx +3 -1
- package/components/ProductListFilters/ProductFilters.graphql +11 -2
- package/components/ProductListFilters/ProductListFilters.graphql +1 -1
- package/components/ProductListFilters/ProductListFilters.tsx +13 -19
- package/components/ProductListFiltersContainer/ProductListFiltersContainer.tsx +2 -4
- package/components/ProductListItem/ProductDiscountLabel.tsx +2 -3
- package/components/ProductListItem/ProductListItem.tsx +3 -3
- package/components/ProductListItem/ProductListItemTitleAndPrice.tsx +18 -15
- package/components/ProductListItems/ProductFilterTypes.graphql +8 -0
- package/components/ProductListItems/ProductListItemsBase.tsx +71 -30
- package/components/ProductListItems/filterTypes.tsx +14 -7
- package/components/ProductListItems/filteredProductList.tsx +44 -17
- package/components/ProductListItems/getFilterTypes.ts +33 -4
- package/components/ProductListItems/productListApplyCategoryDefaults.ts +50 -4
- package/components/ProductListItems/renderer.tsx +8 -2
- package/components/ProductListPagination/ProductListPagination.tsx +39 -20
- package/components/ProductListPrice/ProductListPrice.tsx +9 -4
- package/components/ProductListSuggestions/ProductListSuggestions.graphql +5 -0
- package/components/ProductListSuggestions/ProductListSuggestions.tsx +42 -0
- package/components/ProductPageBreadcrumb/ProductPageBreadcrumb.graphql +3 -0
- package/components/ProductPageBreadcrumb/ProductPageBreadcrumb.tsx +3 -0
- package/components/ProductPageBreadcrumb/ProductPageBreadcrumbs.tsx +40 -0
- package/components/ProductPageBreadcrumb/index.ts +1 -0
- package/components/ProductPageDescription/ComplexTextValue.graphql +1 -1
- package/components/ProductPageDescription/ProductPageDescription.tsx +1 -1
- package/components/ProductPageGallery/ProductImage.graphql +1 -0
- package/components/ProductPageGallery/ProductPageGallery.tsx +14 -8
- package/components/ProductPagePrice/ProductPagePrice.graphql +0 -6
- package/components/ProductPagePrice/ProductPagePrice.tsx +19 -12
- package/components/ProductPagePrice/ProductPagePriceTiers.tsx +4 -3
- package/components/ProductPagePrice/useCustomizableOptionPrice.ts +11 -53
- package/components/ProductShortDescription/ProductShortDescription.tsx +2 -0
- package/components/ProductSpecs/ProductSpecs.graphql +21 -1
- package/components/ProductSpecs/ProductSpecs.tsx +5 -11
- package/components/ProductSpecs/ProductSpecsAggregations.tsx +34 -0
- package/components/ProductSpecs/ProductSpecsCustomAttributes.tsx +45 -0
- package/components/ProductSpecs/ProductSpecsTypes.graphql +8 -0
- package/components/ProductStaticPaths/getProductStaticPaths.ts +1 -1
- package/components/ProductWeight/ProductWeight.tsx +12 -9
- package/components/index.ts +2 -0
- package/hooks/useProductLink.ts +4 -0
- package/hooks/useProductList.ts +148 -0
- package/hooks/useProductListLink.ts +6 -3
- package/index.ts +1 -0
- 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 =
|
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='
|
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'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
|
+
}
|
@@ -1,9 +1,5 @@
|
|
1
|
-
import {
|
2
|
-
|
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 ?
|
11
|
+
src={sortDirection === 'ASC' || sortDirection === null ? iconArrowUp : iconArrowDown}
|
14
12
|
sx={{ display: 'flex' }}
|
15
13
|
/>
|
16
14
|
)
|
@@ -1,16 +1,24 @@
|
|
1
|
-
import {
|
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='
|
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
|
-
|
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
|
}
|
@@ -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
|
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
|
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 =
|
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
|
-
...
|
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,14 @@
|
|
1
|
-
query ProductFilters(
|
2
|
-
|
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
|
import { ChipMenuProps } from '@graphcommerce/next-ui'
|
2
|
-
import { FilterTypes } from '../ProductListItems/
|
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 '
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
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
|
-
|
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
|
-
|
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 {
|
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
|
>
|