@graphcommerce/magento-product 8.1.0-canary.3 → 8.1.0-canary.30

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 (60) hide show
  1. package/Api/ProductListItem.graphql +1 -1
  2. package/Api/ProductPageItem.graphql +1 -1
  3. package/CHANGELOG.md +155 -2
  4. package/components/AddProductsToCart/AddProductsToCartButton.tsx +1 -0
  5. package/components/AddProductsToCart/AddProductsToCartForm.tsx +7 -2
  6. package/components/AddProductsToCart/AddProductsToCartSnackbar.tsx +13 -14
  7. package/components/AddProductsToCart/UseAddProductsToCartAction.graphql +1 -1
  8. package/components/AddProductsToCart/findAddedItems.ts +81 -0
  9. package/components/AddProductsToCart/index.ts +3 -0
  10. package/components/AddProductsToCart/useAddProductsToCartAction.ts +6 -3
  11. package/components/AddProductsToCart/useFormAddProductsToCart.ts +1 -2
  12. package/components/JsonLdProduct/JsonLdProduct.graphql +1 -1
  13. package/components/JsonLdProduct/JsonLdProductOffer.graphql +2 -3
  14. package/components/JsonLdProduct/ProductPageJsonLd.tsx +10 -5
  15. package/components/JsonLdProduct/index.ts +1 -0
  16. package/components/ProductAddToCart/ProductAddToCart.tsx +6 -4
  17. package/components/ProductCustomizable/CustomizableAreaOption.tsx +41 -7
  18. package/components/ProductCustomizable/CustomizableDateOption.tsx +60 -7
  19. package/components/ProductCustomizable/CustomizableDropDownOption.tsx +63 -15
  20. package/components/ProductCustomizable/CustomizableFieldOption.tsx +40 -4
  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/ProductFiltersPro.tsx +49 -12
  25. package/components/ProductFiltersPro/ProductFiltersProAggregations.tsx +14 -0
  26. package/components/ProductFiltersPro/ProductFiltersProAllFiltersChip.tsx +10 -10
  27. package/components/ProductFiltersPro/ProductFiltersProAllFiltersSidebar.tsx +23 -9
  28. package/components/ProductFiltersPro/ProductFiltersProCategorySection.tsx +70 -0
  29. package/components/ProductFiltersPro/ProductFiltersProChips.tsx +10 -8
  30. package/components/ProductFiltersPro/ProductFiltersProLayoutSidebar.tsx +15 -7
  31. package/components/ProductFiltersPro/ProductFiltersProLimitSection.tsx +4 -1
  32. package/components/ProductFiltersPro/ProductFiltersProSortChip.tsx +9 -28
  33. package/components/ProductFiltersPro/ProductFiltersProSortDirectionArrow.tsx +15 -0
  34. package/components/ProductFiltersPro/ProductFiltersProSortSection.tsx +12 -32
  35. package/components/ProductFiltersPro/index.ts +6 -0
  36. package/components/ProductFiltersPro/useProductFiltersProSort.tsx +76 -0
  37. package/components/ProductListFiltersContainer/ProductListFiltersContainer.tsx +2 -4
  38. package/components/ProductListItems/CategoryDefault.graphql +5 -0
  39. package/components/ProductListItems/ProductListItemsBase.tsx +1 -1
  40. package/components/ProductListItems/filterTypes.tsx +1 -1
  41. package/components/ProductListItems/filteredProductList.tsx +1 -1
  42. package/components/ProductListItems/productListApplyCategoryDefaults.ts +28 -0
  43. package/components/ProductPageBreadcrumb/ProductPageBreadcrumb.graphql +3 -0
  44. package/components/ProductPageBreadcrumb/ProductPageBreadcrumb.tsx +8 -3
  45. package/components/ProductPageBreadcrumb/ProductPageBreadcrumbs.tsx +40 -0
  46. package/components/ProductPageBreadcrumb/index.ts +1 -0
  47. package/components/ProductPageDescription/ComplexTextValue.graphql +1 -1
  48. package/components/ProductPageGallery/ProductImage.graphql +1 -0
  49. package/components/ProductPageGallery/ProductPageGallery.tsx +14 -8
  50. package/components/ProductPagePrice/ProductPagePrice.graphql +3 -0
  51. package/components/ProductPagePrice/ProductPagePrice.tsx +11 -4
  52. package/components/ProductPagePrice/useCustomizableOptionPrice.ts +85 -0
  53. package/components/ProductShortDescription/ProductShortDescription.tsx +2 -0
  54. package/components/ProductStaticPaths/getProductStaticPaths.ts +2 -3
  55. package/components/ProductStaticPaths/getSitemapPaths.ts +3 -0
  56. package/components/index.ts +2 -0
  57. package/hooks/useProductListLink.ts +10 -5
  58. package/hooks/useProductListLinkReplace.ts +3 -0
  59. package/package.json +14 -13
  60. package/tsconfig.json +1 -1
@@ -4,6 +4,7 @@ import { Trans } from '@lingui/react'
4
4
  import { Box } from '@mui/material'
5
5
  import { useFormAddProductsToCart } from '../AddProductsToCart'
6
6
  import { OptionTypeRenderer } from './CustomizableAreaOption'
7
+ import { Money } from '@graphcommerce/magento-store'
7
8
 
8
9
  type CustomizableDateOptionProps = React.ComponentProps<
9
10
  OptionTypeRenderer['CustomizableDateOption']
@@ -21,14 +22,56 @@ export function CustomizableDateOption(props: CustomizableDateOptionProps) {
21
22
  title,
22
23
  minDate = new Date('1950-11-12T00:00'),
23
24
  maxDate = new Date('9999-11-12T00:00'),
25
+ dateValue,
26
+ currency,
27
+ productPrice,
24
28
  } = props
25
- const { register, setValue, setError, getFieldState, clearErrors, control } =
26
- useFormAddProductsToCart()
29
+ const {
30
+ register,
31
+ setValue,
32
+ setError,
33
+ getFieldState,
34
+ clearErrors,
35
+ control,
36
+ resetField,
37
+ getValues,
38
+ } = useFormAddProductsToCart()
27
39
 
28
40
  const { invalid } = getFieldState(`cartItems.${index}.entered_options.${optionIndex}.value`)
29
41
 
30
42
  minDate.setSeconds(0, 0)
31
43
  maxDate.setSeconds(0, 0)
44
+ if (!dateValue) return null
45
+
46
+ const price =
47
+ dateValue.price === 0
48
+ ? null
49
+ : dateValue.price && (
50
+ <Box
51
+ sx={{
52
+ display: 'flex',
53
+ typography: 'body1',
54
+ '&.sizeMedium': { typographty: 'subtitle1' },
55
+ '&.sizeLarge': { typography: 'h6' },
56
+ color:
57
+ dateValue.uid ===
58
+ getValues(`cartItems.${index}.entered_options.${optionIndex}.value`)
59
+ ? 'text.primary'
60
+ : 'text.secondary',
61
+ }}
62
+ >
63
+ {/* Change fontFamily so the + is properly outlined */}
64
+ <span style={{ fontFamily: 'arial' }}>+{'\u00A0'}</span>
65
+ <Money
66
+ value={
67
+ dateValue.price_type === 'PERCENT'
68
+ ? productPrice * (dateValue.price / 100)
69
+ : dateValue.price
70
+ }
71
+ currency={currency}
72
+ />
73
+ </Box>
74
+ )
32
75
  return (
33
76
  <Box>
34
77
  <input
@@ -41,12 +84,20 @@ export function CustomizableDateOption(props: CustomizableDateOptionProps) {
41
84
  <TextFieldElement
42
85
  control={control}
43
86
  name={`cartItems.${index}.entered_options.${optionIndex}.value`}
44
- sx={{ width: '100%' }}
87
+ sx={{
88
+ width: '100%',
89
+ '& input[type="datetime-local"]::-webkit-calendar-picker-indicator': {
90
+ filter: (theme) => (theme.palette.mode === 'dark' ? 'invert(100%)' : 'none'),
91
+ mr: '10px',
92
+ },
93
+ }}
94
+ defaultValue=''
45
95
  required={!!required}
46
96
  error={invalid}
47
97
  helperText={invalid ? <Trans id='Invalid date' /> : ''}
48
98
  type='datetime-local'
49
99
  InputProps={{
100
+ endAdornment: price,
50
101
  inputProps: {
51
102
  min: minDate.toISOString().replace(/:00.000Z/, ''),
52
103
  max: maxDate.toISOString().replace(/:00.000Z/, ''),
@@ -61,10 +112,12 @@ export function CustomizableDateOption(props: CustomizableDateOptionProps) {
61
112
  } else {
62
113
  clearErrors(`cartItems.${index}.entered_options.${optionIndex}.value`)
63
114
  }
64
- setValue(
65
- `cartItems.${index}.entered_options.${optionIndex}.value`,
66
- `${data.currentTarget.value.replace('T', ' ')}:00`,
67
- )
115
+ if (data.currentTarget.value)
116
+ setValue(
117
+ `cartItems.${index}.entered_options.${optionIndex}.value`,
118
+ `${data.currentTarget.value.replace('T', ' ')}:00`,
119
+ )
120
+ else resetField(`cartItems.${index}.entered_options.${optionIndex}.value`)
68
121
  }}
69
122
  />
70
123
  </Box>
@@ -1,33 +1,81 @@
1
- import { SelectElement } from '@graphcommerce/ecommerce-ui'
1
+ import { SelectElement, useController } from '@graphcommerce/ecommerce-ui'
2
2
  import { SectionHeader, filterNonNullableKeys } from '@graphcommerce/next-ui'
3
- import { Box } from '@mui/material'
3
+ import { Box, ListItemText, MenuItem, TextField, Typography } from '@mui/material'
4
4
  import { useFormAddProductsToCart } from '../AddProductsToCart'
5
5
  import { OptionTypeRenderer } from './CustomizableAreaOption'
6
+ import { Money } from '@graphcommerce/magento-store'
6
7
 
7
8
  type CustomizableDropDownOptionProps = React.ComponentProps<
8
9
  OptionTypeRenderer['CustomizableDropDownOption']
9
10
  >
10
11
 
11
12
  export function CustomizableDropDownOption(props: CustomizableDropDownOptionProps) {
12
- const { uid, required, index, title, dropdownValue } = props
13
- const { control } = useFormAddProductsToCart()
13
+ const { uid, required, index, title, dropdownValue, productPrice, currency } = props
14
+ const { control, getValues } = useFormAddProductsToCart()
15
+
16
+ const {
17
+ field: { onChange, value, ref, ...field },
18
+ fieldState: { invalid, error },
19
+ } = useController({
20
+ name: `cartItems.${index}.customizable_options.${uid}`,
21
+ rules: {
22
+ required: Boolean(required),
23
+ },
24
+ control,
25
+ defaultValue: '',
26
+ })
14
27
 
15
28
  return (
16
29
  <Box>
17
30
  <SectionHeader labelLeft={title} sx={{ mt: 0 }} />
18
- <SelectElement
19
- sx={{ width: '100%' }}
31
+
32
+ <TextField
33
+ sx={{
34
+ width: '100%',
35
+ '& .MuiSelect-select': {
36
+ display: 'flex',
37
+ justifyContent: 'space-between',
38
+ alignItems: 'center',
39
+ },
40
+ }}
20
41
  color='primary'
21
- control={control}
22
- name={`cartItems.${index}.customizable_options.${uid}`}
23
- label={title}
42
+ value={value ?? ''}
43
+ {...field}
44
+ inputRef={ref}
45
+ onChange={(event) => onChange(event.target.value)}
46
+ select
24
47
  required={Boolean(required)}
25
- defaultValue=''
26
- options={filterNonNullableKeys(dropdownValue, ['title']).map((option) => ({
27
- id: option.uid,
28
- label: option.title,
29
- }))}
30
- />
48
+ error={invalid}
49
+ helperText={error?.message}
50
+ >
51
+ {filterNonNullableKeys(dropdownValue, ['title']).map((option) => (
52
+ <MenuItem key={option.uid} value={option.uid}>
53
+ <Box>{option.title}</Box>
54
+
55
+ {option.price ? (
56
+ <Box
57
+ sx={{
58
+ // display: 'flex',
59
+ typography: 'body1',
60
+ '&.sizeMedium': { typographty: 'subtitle1' },
61
+ '&.sizeLarge': { typography: 'h6' },
62
+ color: option.uid === value ? 'text.primary' : 'text.secondary',
63
+ }}
64
+ >
65
+ <span style={{ fontFamily: 'arial', paddingTop: '1px' }}>+&nbsp;</span>
66
+ <Money
67
+ value={
68
+ option.price_type === 'PERCENT'
69
+ ? productPrice * (option.price / 100)
70
+ : option.price
71
+ }
72
+ currency={currency}
73
+ />
74
+ </Box>
75
+ ) : null}
76
+ </MenuItem>
77
+ ))}
78
+ </TextField>
31
79
  </Box>
32
80
  )
33
81
  }
@@ -1,3 +1,4 @@
1
+ import { Money } from '@graphcommerce/magento-store'
1
2
  import { TextFieldElement } from '@graphcommerce/ecommerce-ui'
2
3
  import { SectionHeader } from '@graphcommerce/next-ui'
3
4
  import { i18n } from '@lingui/core'
@@ -10,10 +11,12 @@ type CustomizableFieldOptionProps = React.ComponentProps<
10
11
  >
11
12
 
12
13
  export function CustomizableFieldOption(props: CustomizableFieldOptionProps) {
13
- const { uid, required, optionIndex, index, title, fieldValue } = props
14
- const { control, register } = useFormAddProductsToCart()
14
+ const { uid, required, optionIndex, index, title, fieldValue, productPrice, currency } = props
15
+ const { control, register, resetField, getValues } = useFormAddProductsToCart()
15
16
 
16
- const maxLength = fieldValue?.max_characters ?? 0
17
+ if (!fieldValue) return null
18
+
19
+ const maxLength = fieldValue.max_characters ?? 0
17
20
  return (
18
21
  <Box>
19
22
  <SectionHeader labelLeft={title} sx={{ mt: 0 }} />
@@ -29,7 +32,36 @@ export function CustomizableFieldOption(props: CustomizableFieldOptionProps) {
29
32
  control={control}
30
33
  name={`cartItems.${index}.entered_options.${optionIndex}.value`}
31
34
  required={Boolean(required)}
32
- validation={{
35
+ InputProps={{
36
+ endAdornment:
37
+ fieldValue.price === 0
38
+ ? null
39
+ : fieldValue.price && (
40
+ <Box
41
+ sx={{
42
+ display: 'flex',
43
+ typography: 'body1',
44
+ '&.sizeMedium': { typographty: 'subtitle1' },
45
+ '&.sizeLarge': { typography: 'h6' },
46
+ color: getValues(`cartItems.${index}.entered_options.${optionIndex}.value`)
47
+ ? 'text.primary'
48
+ : 'text.secondary',
49
+ }}
50
+ >
51
+ {/* Change fontFamily so the + is properly outlined */}
52
+ <span style={{ fontFamily: 'arial', paddingTop: '1px' }}>+{'\u00A0'}</span>
53
+ <Money
54
+ value={
55
+ fieldValue.price_type === 'PERCENT'
56
+ ? productPrice * (fieldValue.price / 100)
57
+ : fieldValue.price
58
+ }
59
+ currency={currency}
60
+ />
61
+ </Box>
62
+ ),
63
+ }}
64
+ rules={{
33
65
  maxLength: {
34
66
  value: maxLength,
35
67
  message: i18n._(/* i18n*/ 'There is a maximum of ‘{maxLength}’ characters', {
@@ -43,6 +75,10 @@ export function CustomizableFieldOption(props: CustomizableFieldOptionProps) {
43
75
  maxLength,
44
76
  })
45
77
  }
78
+ onChange={(data) => {
79
+ if (!data.currentTarget.value)
80
+ resetField(`cartItems.${index}.entered_options.${optionIndex}.value`)
81
+ }}
46
82
  />
47
83
  </Box>
48
84
  )
@@ -1,4 +1,4 @@
1
- fragment ProductCustomizable on CustomizableProductInterface @injectable {
1
+ fragment ProductCustomizable on CustomizableProductInterface {
2
2
  options {
3
3
  uid
4
4
  __typename
@@ -1 +1,2 @@
1
1
  export * from './ProductCustomizable'
2
+ export * from './productCustomizableSelectors'
@@ -0,0 +1,59 @@
1
+ import type { PriceTypeEnum } from '@graphcommerce/graphql-mesh'
2
+ import type { Simplify } from 'type-fest'
3
+ import type { CustomizableAreaOptionFragment } from './CustomizableAreaOption.gql'
4
+ import type { CustomizableCheckboxOptionFragment } from './CustomizableCheckboxOption.gql'
5
+ import type { CustomizableDateOptionFragment } from './CustomizableDateOption.gql'
6
+ import type { CustomizableDropDownOptionFragment } from './CustomizableDropDownOption.gql'
7
+ import type { CustomizableFieldOptionFragment } from './CustomizableFieldOption.gql'
8
+ import type { CustomizableFileOptionFragment } from './CustomizableFileOption.gql'
9
+ import type { CustomizableMultipleOptionFragment } from './CustomizableMultipleOption.gql'
10
+ import type { CustomizableRadioOptionFragment } from './CustomizableRadioOption.gql'
11
+ import type { ProductCustomizable_SimpleProduct_Fragment } from './ProductCustomizable.gql'
12
+
13
+ export type CustomizableProductOptionBase =
14
+ | {
15
+ price?: number | null | undefined
16
+ price_type?: PriceTypeEnum | null | undefined
17
+ uid?: string | null | undefined
18
+ }
19
+ | undefined
20
+ | null
21
+
22
+ export type AnyOption = NonNullable<
23
+ NonNullable<ProductCustomizable_SimpleProduct_Fragment['options']>[number]
24
+ >
25
+
26
+ export type OptionValueSelector = {
27
+ [T in AnyOption as T['__typename']]: (
28
+ option: T,
29
+ ) => CustomizableProductOptionBase | CustomizableProductOptionBase[]
30
+ }
31
+
32
+ type MissingOptionValueSelectors = Omit<
33
+ OptionValueSelector,
34
+ keyof typeof productCustomizableSelectors
35
+ >
36
+ type DefinedOptionValueSelectors = Partial<
37
+ Pick<OptionValueSelector, keyof typeof productCustomizableSelectors>
38
+ >
39
+
40
+ type Selectors = Simplify<
41
+ keyof MissingOptionValueSelectors extends never
42
+ ? (MissingOptionValueSelectors & DefinedOptionValueSelectors) | undefined
43
+ : MissingOptionValueSelectors & DefinedOptionValueSelectors
44
+ >
45
+
46
+ export const productCustomizableSelectors = {
47
+ CustomizableAreaOption: (o: CustomizableAreaOptionFragment) => o.areaValue,
48
+ CustomizableCheckboxOption: (o: CustomizableCheckboxOptionFragment) => o.checkboxValue,
49
+ CustomizableFileOption: (o: CustomizableFileOptionFragment) => o.fileValue,
50
+ CustomizableDateOption: (o: CustomizableDateOptionFragment) => o.dateValue,
51
+ CustomizableDropDownOption: (o: CustomizableDropDownOptionFragment) => o.dropdownValue,
52
+ CustomizableFieldOption: (o: CustomizableFieldOptionFragment) => o.fieldValue,
53
+ CustomizableMultipleOption: (o: CustomizableMultipleOptionFragment) => o.multipleValue,
54
+ CustomizableRadioOption: (o: CustomizableRadioOptionFragment) => o.radioValue,
55
+ }
56
+
57
+ export type SelectorsProp = keyof MissingOptionValueSelectors extends never
58
+ ? { selectors?: Selectors }
59
+ : { selectors: Selectors }
@@ -1,14 +1,15 @@
1
- import { useForm, UseFormProps, UseFormReturn } from '@graphcommerce/ecommerce-ui'
2
- import { useMemoObject } from '@graphcommerce/next-ui'
3
- import { useEventCallback } from '@mui/material'
4
- import React, { BaseSyntheticEvent, createContext, useContext, useMemo } from 'react'
5
- import { useProductListLinkReplace } from '../../hooks/useProductListLinkReplace'
1
+ import { FormAutoSubmit, useForm, UseFormProps, UseFormReturn } from '@graphcommerce/ecommerce-ui'
2
+ import { useMatchMediaMotionValue, useMemoObject } from '@graphcommerce/next-ui'
3
+ import { Theme, useEventCallback, useMediaQuery, useTheme } from '@mui/material'
4
+ import { m, useTransform } from 'framer-motion'
5
+ import { useRouter } from 'next/router'
6
+ import React, { BaseSyntheticEvent, createContext, useContext, useMemo, useRef } from 'react'
7
+ import { productListLinkFromFilter } from '../../hooks/useProductListLink'
6
8
  import { ProductListFiltersFragment } from '../ProductListFilters/ProductListFilters.gql'
7
9
  import {
8
10
  ProductFilterParams,
9
11
  ProductListParams,
10
12
  toFilterParams,
11
- toProductListParams,
12
13
  } from '../ProductListItems/filterTypes'
13
14
 
14
15
  type DataProps = {
@@ -42,19 +43,54 @@ export type FilterFormProviderProps = Omit<
42
43
  > & {
43
44
  children: React.ReactNode
44
45
  params: ProductListParams
46
+ /**
47
+ * Whether the filter should scroll to the products list and whether to submit the form on change.
48
+ */
49
+ autoSubmitMd?: boolean
45
50
  } & DataProps
46
51
 
52
+ function AutoSubmitSidebarDesktop() {
53
+ const { form, submit } = useProductFiltersPro()
54
+
55
+ // We only need to auto-submit when the layout is not sidebar and we're viewing on desktop
56
+ const autoSubmitDisabled = useMediaQuery<Theme>((t) => t.breakpoints.down('md'), {
57
+ defaultMatches: false,
58
+ })
59
+
60
+ return <FormAutoSubmit control={form.control} disabled={autoSubmitDisabled} submit={submit} />
61
+ }
62
+
47
63
  export function ProductFiltersPro(props: FilterFormProviderProps) {
48
- const { children, params, aggregations, appliedAggregations, filterTypes, ...formProps } = props
64
+ const {
65
+ children,
66
+ params,
67
+ aggregations,
68
+ appliedAggregations,
69
+ filterTypes,
70
+ autoSubmitMd = false,
71
+ ...formProps
72
+ } = props
49
73
 
50
74
  const defaultValues = useMemoObject(toFilterParams(params))
51
75
  const form = useForm<ProductFilterParams>({ defaultValues, ...formProps })
76
+ const ref = useRef<HTMLFormElement>(null)
77
+
78
+ const router = useRouter()
79
+ const theme = useTheme()
80
+ const isDesktop = useMatchMediaMotionValue('up', 'md')
81
+ const scrollMarginTop = useTransform(() => (isDesktop.get() ? 0 : theme.appShell.headerHeightSm))
82
+ const scroll = useTransform(() => !autoSubmitMd || isDesktop.get())
52
83
 
53
- const push = useProductListLinkReplace({ scroll: false })
54
84
  const submit = useEventCallback(
55
- form.handleSubmit(async (formValues) =>
56
- push({ ...toProductListParams(formValues), currentPage: 1 }),
57
- ),
85
+ form.handleSubmit(async (formValues) => {
86
+ const path = productListLinkFromFilter({ ...formValues, currentPage: 1 })
87
+ if (router.asPath === path) return false
88
+
89
+ const opts = { scroll: scroll.get() }
90
+ return (router.query.url ?? []).includes('q')
91
+ ? router.replace(path, path, opts)
92
+ : router.push(path, path, opts)
93
+ }),
58
94
  )
59
95
 
60
96
  const filterFormContext: FilterFormContextProps = useMemo(
@@ -71,7 +107,8 @@ export function ProductFiltersPro(props: FilterFormProviderProps) {
71
107
 
72
108
  return (
73
109
  <FilterFormContext.Provider value={filterFormContext}>
74
- <form noValidate onSubmit={submit} id='products' />
110
+ <m.form ref={ref} noValidate onSubmit={submit} id='products' style={{ scrollMarginTop }} />
111
+ {autoSubmitMd && <AutoSubmitSidebarDesktop />}
75
112
  {children}
76
113
  </FilterFormContext.Provider>
77
114
  )
@@ -1,4 +1,8 @@
1
1
  import { ProductListFiltersFragment } from '../ProductListFilters/ProductListFilters.gql'
2
+ import { ProductFilterEqualChip } from './ProductFilterEqualChip'
3
+ import { ProductFilterEqualSection } from './ProductFilterEqualSection'
4
+ import { ProductFilterRangeChip } from './ProductFilterRangeChip'
5
+ import { ProductFilterRangeSection } from './ProductFilterRangeSection'
2
6
  import { useProductFiltersPro } from './ProductFiltersPro'
3
7
  import { excludeCategory } from './activeAggregations'
4
8
  import { applyAggregationCount } from './applyAggregationCount'
@@ -13,6 +17,16 @@ export type ProductFiltersProAggregationsProps = {
13
17
  renderer?: FilterRenderer
14
18
  }
15
19
 
20
+ export const productFiltersProSectionRenderer = {
21
+ FilterRangeTypeInput: ProductFilterRangeSection,
22
+ FilterEqualTypeInput: ProductFilterEqualSection,
23
+ }
24
+
25
+ export const productFiltersProChipRenderer = {
26
+ FilterEqualTypeInput: ProductFilterEqualChip,
27
+ FilterRangeTypeInput: ProductFilterRangeChip,
28
+ }
29
+
16
30
  export function ProductFiltersProAggregations(props: ProductFiltersProAggregationsProps) {
17
31
  const { renderer } = props
18
32
  const { params, aggregations, appliedAggregations, filterTypes } = useProductFiltersPro()
@@ -1,11 +1,10 @@
1
1
  import { ChipOverlayOrPopper, ChipOverlayOrPopperProps } from '@graphcommerce/next-ui'
2
2
  import { Trans } from '@lingui/react'
3
- import { ProductFilterEqualSection } from './ProductFilterEqualSection'
4
- import { ProductFilterRangeSection } from './ProductFilterRangeSection'
5
3
  import { useProductFiltersPro } from './ProductFiltersPro'
6
4
  import {
7
5
  ProductFiltersProAggregations,
8
6
  ProductFiltersProAggregationsProps,
7
+ productFiltersProSectionRenderer,
9
8
  } from './ProductFiltersProAggregations'
10
9
  import { ProductFiltersProLimitSection } from './ProductFiltersProLimitSection'
11
10
  import {
@@ -23,13 +22,8 @@ export type ProductFiltersProAllFiltersChipProps = ProductFiltersProAggregations
23
22
  'label' | 'selected' | 'selectedLabel' | 'onApply' | 'onReset' | 'onClose' | 'children'
24
23
  >
25
24
 
26
- const defaultRenderer = {
27
- FilterRangeTypeInput: ProductFilterRangeSection,
28
- FilterEqualTypeInput: ProductFilterEqualSection,
29
- }
30
-
31
25
  export function ProductFiltersProAllFiltersChip(props: ProductFiltersProAllFiltersChipProps) {
32
- const { sort_fields, total_count, renderer, ...rest } = props
26
+ const { sort_fields, total_count, renderer, category, ...rest } = props
33
27
 
34
28
  const { submit, params, aggregations, appliedAggregations } = useProductFiltersPro()
35
29
  const { sort } = params
@@ -59,9 +53,15 @@ export function ProductFiltersProAllFiltersChip(props: ProductFiltersProAllFilte
59
53
  >
60
54
  {() => (
61
55
  <>
62
- <ProductFiltersProSortSection sort_fields={sort_fields} total_count={total_count} />
56
+ <ProductFiltersProSortSection
57
+ sort_fields={sort_fields}
58
+ total_count={total_count}
59
+ category={category}
60
+ />
63
61
  <ProductFiltersProLimitSection />
64
- <ProductFiltersProAggregations renderer={{ ...defaultRenderer, ...renderer }} />
62
+ <ProductFiltersProAggregations
63
+ renderer={{ ...productFiltersProSectionRenderer, ...renderer }}
64
+ />
65
65
  </>
66
66
  )}
67
67
  </ChipOverlayOrPopper>
@@ -4,7 +4,12 @@ import { ProductFilterRangeSection } from './ProductFilterRangeSection'
4
4
  import {
5
5
  ProductFiltersProAggregations,
6
6
  ProductFiltersProAggregationsProps,
7
+ productFiltersProSectionRenderer,
7
8
  } from './ProductFiltersProAggregations'
9
+ import {
10
+ ProductFiltersCategorySectionProps,
11
+ ProductFiltersProCategorySection,
12
+ } from './ProductFiltersProCategorySection'
8
13
  import { ProductFiltersProLimitSection } from './ProductFiltersProLimitSection'
9
14
  import {
10
15
  ProductFiltersProSortSection,
@@ -12,21 +17,30 @@ import {
12
17
  } from './ProductFiltersProSortSection'
13
18
 
14
19
  export type ProductFiltersProAllFiltersSidebarProps = ProductFiltersProAggregationsProps &
15
- ProductFiltersProSortSectionProps & { sx?: SxProps<Theme> }
16
-
17
- const defaultRenderer = {
18
- FilterRangeTypeInput: ProductFilterRangeSection,
19
- FilterEqualTypeInput: ProductFilterEqualSection,
20
- }
20
+ ProductFiltersProSortSectionProps &
21
+ ProductFiltersCategorySectionProps & { sx?: SxProps<Theme> }
21
22
 
23
+ /**
24
+ * @deprecated Not used anymore
25
+ *
26
+ * @param props
27
+ * @returns
28
+ */
22
29
  export function ProductFiltersProAllFiltersSidebar(props: ProductFiltersProAllFiltersSidebarProps) {
23
- const { sort_fields, total_count, renderer, sx = [] } = props
30
+ const { sort_fields, total_count, renderer, sx = [], category, params } = props
24
31
 
25
32
  return (
26
33
  <Box sx={[{ display: { xs: 'none', md: 'grid' } }, ...(Array.isArray(sx) ? sx : [sx])]}>
27
- <ProductFiltersProSortSection sort_fields={sort_fields} total_count={total_count} />
34
+ <ProductFiltersProCategorySection category={category} params={params} />
35
+ <ProductFiltersProSortSection
36
+ sort_fields={sort_fields}
37
+ total_count={total_count}
38
+ category={category}
39
+ />
28
40
  <ProductFiltersProLimitSection />
29
- <ProductFiltersProAggregations renderer={{ ...defaultRenderer, ...renderer }} />
41
+ <ProductFiltersProAggregations
42
+ renderer={{ ...productFiltersProSectionRenderer, ...renderer }}
43
+ />
30
44
  </Box>
31
45
  )
32
46
  }
@@ -0,0 +1,70 @@
1
+ import { UseCategoryTreeProps, useCategoryTree } from '@graphcommerce/magento-category'
2
+ import {
3
+ ActionCard,
4
+ ActionCardAccordion,
5
+ ActionCardList,
6
+ IconSvg,
7
+ iconChevronLeft,
8
+ responsiveVal,
9
+ } from '@graphcommerce/next-ui'
10
+ import { Trans } from '@lingui/react'
11
+ import { Box, SxProps, Theme } from '@mui/material'
12
+ import { useRouter } from 'next/router'
13
+
14
+ export type ProductFiltersCategorySectionProps = UseCategoryTreeProps & {
15
+ hideTitle?: boolean
16
+ sx?: SxProps<Theme>
17
+ }
18
+
19
+ export function ProductFiltersProCategorySection(props: ProductFiltersCategorySectionProps) {
20
+ const { hideTitle, sx } = props
21
+ const router = useRouter()
22
+ const categoryTree = useCategoryTree(props)
23
+
24
+ if (!categoryTree) return null
25
+
26
+ return (
27
+ <ActionCardAccordion
28
+ sx={[
29
+ hideTitle ? { '& .MuiAccordionSummary-root': { display: 'none' } } : {},
30
+ sx,
31
+ ...(Array.isArray(sx) ? sx : [sx]),
32
+ ]}
33
+ defaultExpanded
34
+ summary={<Trans id='Categories' />}
35
+ 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}
45
+ </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
+ ))}
65
+ </ActionCardList>
66
+ }
67
+ right={undefined}
68
+ />
69
+ )
70
+ }
@@ -1,16 +1,18 @@
1
- import { ProductFilterEqualChip } from './ProductFilterEqualChip'
2
- import { ProductFilterRangeChip } from './ProductFilterRangeChip'
3
1
  import {
4
2
  ProductFiltersProAggregations,
5
3
  ProductFiltersProAggregationsProps,
4
+ productFiltersProChipRenderer,
6
5
  } from './ProductFiltersProAggregations'
7
6
 
8
- const defaultRenderer = {
9
- FilterEqualTypeInput: ProductFilterEqualChip,
10
- FilterRangeTypeInput: ProductFilterRangeChip,
11
- }
12
-
7
+ /**
8
+ * @deprecated Not used anymore, use `<ProductFiltersProAggregations renderer={productFiltersProChipRenderer}/>`
9
+ */
13
10
  export function ProductFiltersProFilterChips(props: ProductFiltersProAggregationsProps) {
14
11
  const { renderer } = props
15
- return <ProductFiltersProAggregations {...props} renderer={{ ...defaultRenderer, ...renderer }} />
12
+ return (
13
+ <ProductFiltersProAggregations
14
+ {...props}
15
+ renderer={{ ...productFiltersProChipRenderer, ...renderer }}
16
+ />
17
+ )
16
18
  }