@graphcommerce/magento-product 9.1.0-canary.18 → 9.1.0-canary.19

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 (34) hide show
  1. package/CHANGELOG.md +24 -0
  2. package/components/AddProductsToCart/AddProductsToCartForm.tsx +20 -45
  3. package/components/AddProductsToCart/useFormAddProductsToCart.ts +16 -1
  4. package/components/ProductCustomizable/CustomizableAreaOption.tsx +22 -37
  5. package/components/ProductCustomizable/CustomizableCheckboxOption.tsx +61 -41
  6. package/components/ProductCustomizable/CustomizableDateOption.graphql +1 -0
  7. package/components/ProductCustomizable/CustomizableDateOption.tsx +42 -87
  8. package/components/ProductCustomizable/CustomizableDropDownOption.tsx +13 -5
  9. package/components/ProductCustomizable/CustomizableFieldOption.tsx +22 -40
  10. package/components/ProductCustomizable/CustomizableMultipleOption.tsx +54 -35
  11. package/components/ProductCustomizable/CustomizablePrice.tsx +40 -0
  12. package/components/ProductCustomizable/CustomizableRadioOption.tsx +56 -36
  13. package/components/ProductCustomizable/ProductCustomizable.graphql +1 -0
  14. package/components/ProductCustomizable/ProductCustomizable.tsx +10 -2
  15. package/components/ProductCustomizable/productCustomizableSelectors.ts +5 -7
  16. package/components/ProductListItem/ProductDiscountLabel.tsx +1 -1
  17. package/components/ProductListItem/ProductListItem.tsx +5 -2
  18. package/components/ProductListItem/ProductNewLabel.tsx +36 -0
  19. package/components/ProductListItems/renderer.tsx +1 -1
  20. package/components/ProductPageDescription/ProductPageDescription.tsx +10 -1
  21. package/components/ProductPageGallery/ProductPageGallery.tsx +22 -19
  22. package/components/ProductPageGallery/ProductVideo.graphql +1 -0
  23. package/components/ProductPageGallery/ProductVideo.tsx +169 -0
  24. package/components/ProductPageMeta/ProductPageMeta.graphql +1 -0
  25. package/components/ProductPageMeta/ProductPageMeta.tsx +2 -0
  26. package/components/ProductPagePrice/ProductPagePrice.graphql +1 -10
  27. package/components/ProductPagePrice/ProductPrice.graphql +12 -0
  28. package/components/ProductPagePrice/getProductTierPrice.ts +3 -5
  29. package/components/ProductPagePrice/useCustomizableOptionPrice.ts +38 -29
  30. package/{Api → graphql/fragments}/ProductListItem.graphql +2 -0
  31. package/graphql/index.ts +2 -0
  32. package/index.ts +1 -2
  33. package/package.json +13 -13
  34. /package/{Api → graphql/fragments}/ProductPageItem.graphql +0 -0
@@ -1,65 +1,51 @@
1
- import { TextFieldElement } from '@graphcommerce/ecommerce-ui'
1
+ import { TextFieldElement, useWatch } from '@graphcommerce/ecommerce-ui'
2
2
  import { Money } from '@graphcommerce/magento-store'
3
3
  import { SectionHeader } from '@graphcommerce/next-ui'
4
4
  import { i18n } from '@lingui/core'
5
5
  import { Box } from '@mui/material'
6
6
  import { useFormAddProductsToCart } from '../AddProductsToCart'
7
7
  import type { OptionTypeRenderer } from './CustomizableAreaOption'
8
+ import { CustomizablePrice } from './CustomizablePrice'
8
9
 
9
10
  export type CustomizableFieldOptionProps = React.ComponentProps<
10
11
  OptionTypeRenderer['CustomizableFieldOption']
11
12
  >
12
13
 
13
14
  export function CustomizableFieldOption(props: CustomizableFieldOptionProps) {
14
- const { uid, required, optionIndex, index, title, fieldValue, productPrice, currency } = props
15
- const { control, register, resetField, getValues } = useFormAddProductsToCart()
15
+ const { uid, required, index, title: label, fieldValue, productPrice, currency } = props
16
+ const { control } = useFormAddProductsToCart()
16
17
 
18
+ const name = `cartItems.${index}.entered_options_record.${uid}` as const
17
19
  if (!fieldValue) return null
18
20
 
19
21
  const maxLength = fieldValue.max_characters ?? 0
20
22
  return (
21
23
  <Box>
22
- <SectionHeader labelLeft={title} sx={{ mt: 0 }} />
23
- <input
24
- type='hidden'
25
- {...register(`cartItems.${index}.entered_options.${optionIndex}.uid`)}
26
- value={uid}
24
+ <SectionHeader
25
+ labelLeft={
26
+ <>
27
+ {label} {required && ' *'}
28
+ </>
29
+ }
30
+ sx={{ mt: 0 }}
27
31
  />
28
32
  <TextFieldElement
29
33
  sx={{ width: '100%' }}
30
34
  color='primary'
31
35
  multiline
32
36
  control={control}
33
- name={`cartItems.${index}.entered_options.${optionIndex}.value`}
37
+ name={name}
34
38
  required={Boolean(required)}
35
39
  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
- ),
40
+ endAdornment: (
41
+ <CustomizablePrice
42
+ name={name}
43
+ productPrice={productPrice}
44
+ currency={currency}
45
+ price_type={fieldValue.price_type}
46
+ value={fieldValue.price}
47
+ />
48
+ ),
63
49
  }}
64
50
  rules={{
65
51
  maxLength: {
@@ -75,10 +61,6 @@ export function CustomizableFieldOption(props: CustomizableFieldOptionProps) {
75
61
  maxLength,
76
62
  })
77
63
  }
78
- onChange={(data) => {
79
- if (!data.currentTarget.value)
80
- resetField(`cartItems.${index}.entered_options.${optionIndex}.value`)
81
- }}
82
64
  />
83
65
  </Box>
84
66
  )
@@ -6,20 +6,61 @@ import { i18n } from '@lingui/core'
6
6
  import { Box } from '@mui/material'
7
7
  import { useFormAddProductsToCart } from '../AddProductsToCart'
8
8
  import type { OptionTypeRenderer } from './CustomizableAreaOption'
9
+ import type { CustomizableMultipleOptionFragment } from './CustomizableMultipleOption.gql'
9
10
 
10
11
  export type CustomizableMultipleOptionProps = React.ComponentProps<
11
12
  OptionTypeRenderer['CustomizableMultipleOption']
12
13
  >
13
14
 
15
+ type MultipleActionCardProps = Pick<CustomizableMultipleOptionProps, 'productPrice' | 'currency'> &
16
+ Pick<ActionCardProps, 'value' | 'selected'> & {
17
+ option: NonNullable<NonNullable<CustomizableMultipleOptionFragment['multipleValue']>[number]>
18
+ }
19
+
20
+ function CustomizableMultipleActionCard(props: MultipleActionCardProps) {
21
+ const { productPrice, currency, option, selected, ...rest } = props
22
+ const { title, price, price_type } = option
23
+
24
+ return (
25
+ <ActionCard
26
+ {...rest}
27
+ selected={selected}
28
+ title={title}
29
+ price={
30
+ price === 0
31
+ ? null
32
+ : price && (
33
+ <Box
34
+ sx={{
35
+ color: selected ? 'text.primary' : 'text.secondary',
36
+ }}
37
+ >
38
+ <span style={{ fontFamily: 'arial' }}>{'+ '}</span>
39
+ <Money
40
+ value={price_type === 'PERCENT' ? productPrice * (price / 100) : price}
41
+ currency={currency}
42
+ />
43
+ </Box>
44
+ )
45
+ }
46
+ />
47
+ )
48
+ }
49
+
14
50
  export function CustomizableMultipleOption(props: CustomizableMultipleOptionProps) {
15
51
  const { uid, required, index, title: label, multipleValue, currency, productPrice } = props
16
- const { control, getValues } = useFormAddProductsToCart()
17
-
18
- const allSelected = getValues(`cartItems.${index}.customizable_options.${uid}`) || []
52
+ const { control } = useFormAddProductsToCart()
19
53
 
20
54
  return (
21
55
  <Box>
22
- <SectionHeader labelLeft={label} sx={{ mt: 0 }} />
56
+ <SectionHeader
57
+ labelLeft={
58
+ <>
59
+ {label} {required && ' *'}
60
+ </>
61
+ }
62
+ sx={{ mt: 0 }}
63
+ />
23
64
  <ActionCardListForm
24
65
  sx={(theme) => ({
25
66
  mt: theme.spacings.xxs,
@@ -31,37 +72,15 @@ export function CustomizableMultipleOption(props: CustomizableMultipleOptionProp
31
72
  : false,
32
73
  }}
33
74
  control={control}
34
- render={ActionCard}
35
- name={`cartItems.${index}.customizable_options.${uid}`}
36
- items={filterNonNullableKeys(multipleValue, ['title']).map(
37
- (multipleVal) =>
38
- ({
39
- value: multipleVal.uid,
40
- title: multipleVal.title,
41
- price:
42
- multipleVal.price === 0
43
- ? null
44
- : multipleVal.price && (
45
- <Box
46
- sx={{
47
- color: allSelected.includes(multipleVal.uid)
48
- ? 'text.primary '
49
- : 'text.secondary',
50
- }}
51
- >
52
- <span style={{ fontFamily: 'arial' }}>{'+ '}</span>
53
- <Money
54
- value={
55
- multipleVal.price_type === 'PERCENT'
56
- ? productPrice * (multipleVal.price / 100)
57
- : multipleVal.price
58
- }
59
- currency={currency}
60
- />
61
- </Box>
62
- ),
63
- }) satisfies ActionCardProps,
64
- )}
75
+ render={CustomizableMultipleActionCard}
76
+ name={`cartItems.${index}.selected_options_record.${uid}`}
77
+ items={filterNonNullableKeys(multipleValue, ['title']).map((multipleVal) => ({
78
+ productPrice,
79
+ currency,
80
+ title: multipleVal.title ?? '',
81
+ value: multipleVal.uid,
82
+ option: multipleVal,
83
+ }))}
65
84
  />
66
85
  </Box>
67
86
  )
@@ -0,0 +1,40 @@
1
+ import type { Control, FieldName, FieldPath } from '@graphcommerce/ecommerce-ui'
2
+ import { useWatch, type FieldValues } from '@graphcommerce/ecommerce-ui'
3
+ import type { PriceTypeEnum } from '@graphcommerce/graphql-mesh'
4
+ import { Money, type MoneyFragment } from '@graphcommerce/magento-store'
5
+ import { Box } from '@mui/material'
6
+ import { useFormAddProductsToCart, type AddProductsToCartFields } from '../AddProductsToCart'
7
+
8
+ type CustomizablePriceProps = {
9
+ price_type: PriceTypeEnum | null | undefined
10
+ productPrice: number
11
+ name: FieldPath<AddProductsToCartFields>
12
+ } & MoneyFragment
13
+
14
+ export function CustomizablePrice(props: CustomizablePriceProps) {
15
+ const { name, value, currency, price_type, productPrice } = props
16
+
17
+ const { control } = useFormAddProductsToCart()
18
+ const optionValue = !!useWatch({ control, name })
19
+
20
+ if (!value) return null
21
+
22
+ return (
23
+ <Box
24
+ sx={{
25
+ display: 'flex',
26
+ typography: 'body1',
27
+ '&.sizeMedium': { typographty: 'subtitle1' },
28
+ '&.sizeLarge': { typography: 'h6' },
29
+ color: optionValue ? 'text.primary' : 'text.secondary',
30
+ }}
31
+ >
32
+ {/* Change fontFamily so the + is properly outlined */}
33
+ <span style={{ fontFamily: 'arial' }}>+{'\u00A0'}</span>
34
+ <Money
35
+ value={price_type === 'PERCENT' ? productPrice * (value / 100) : value}
36
+ currency={currency}
37
+ />
38
+ </Box>
39
+ )
40
+ }
@@ -6,63 +6,83 @@ import { i18n } from '@lingui/core'
6
6
  import { Box } from '@mui/material'
7
7
  import { useFormAddProductsToCart } from '../AddProductsToCart'
8
8
  import type { OptionTypeRenderer } from './CustomizableAreaOption'
9
+ import type { CustomizableRadioOptionFragment } from './CustomizableRadioOption.gql'
9
10
 
10
11
  export type CustomizableRadioOptionProps = React.ComponentProps<
11
12
  OptionTypeRenderer['CustomizableRadioOption']
12
13
  >
13
14
 
15
+ type RadioActionCardProps = Pick<CustomizableRadioOptionProps, 'productPrice' | 'currency'> &
16
+ Pick<ActionCardProps, 'value' | 'selected'> & {
17
+ option: NonNullable<NonNullable<CustomizableRadioOptionFragment['radioValue']>[number]>
18
+ }
19
+
20
+ function CustomizableRadioActionCard(props: RadioActionCardProps) {
21
+ const { productPrice, currency, value, option, selected, ...rest } = props
22
+ const { title, price, price_type } = option
23
+
24
+ return (
25
+ <ActionCard
26
+ {...rest}
27
+ value={value}
28
+ title={title}
29
+ selected={selected}
30
+ price={
31
+ price === 0
32
+ ? null
33
+ : price && (
34
+ <Box
35
+ sx={{
36
+ color: selected ? 'text.primary' : 'text.secondary',
37
+ }}
38
+ >
39
+ {/* Change fontFamily so the + is properly outlined */}
40
+ <span style={{ fontFamily: 'arial' }}>{'+ '}</span>
41
+ <Money
42
+ value={price_type === 'PERCENT' ? productPrice * (price / 100) : price}
43
+ currency={currency}
44
+ />
45
+ </Box>
46
+ )
47
+ }
48
+ />
49
+ )
50
+ }
51
+
14
52
  export function CustomizableRadioOption(props: CustomizableRadioOptionProps) {
15
53
  const { uid, required, index, title: label, radioValue, currency, productPrice } = props
16
- const { control, getValues } = useFormAddProductsToCart()
17
-
18
- const allSelected = getValues(`cartItems.${index}.customizable_options.${uid}`) || []
54
+ const { control } = useFormAddProductsToCart()
19
55
 
20
56
  return (
21
57
  <Box>
22
- <SectionHeader labelLeft={label} sx={{ mt: 0 }} />
58
+ <SectionHeader
59
+ labelLeft={
60
+ <>
61
+ {label} {required && ' *'}
62
+ </>
63
+ }
64
+ sx={{ mt: 0 }}
65
+ />
23
66
  <ActionCardListForm
24
67
  sx={(theme) => ({
25
68
  mt: theme.spacings.xxs,
26
69
  })}
27
70
  layout='stack'
28
71
  control={control}
29
- render={ActionCard}
30
- name={`cartItems.${index}.customizable_options.${uid}`}
72
+ render={CustomizableRadioActionCard}
73
+ name={`cartItems.${index}.selected_options_record.${uid}`}
31
74
  rules={{
32
75
  required: required
33
76
  ? i18n._(/* i18n*/ 'Please select a value for ‘{label}’', { label })
34
77
  : false,
35
78
  }}
36
- items={filterNonNullableKeys(radioValue, ['title']).map(
37
- (radioVal) =>
38
- ({
39
- value: radioVal.uid,
40
- title: radioVal.title,
41
- price:
42
- radioVal.price === 0
43
- ? null
44
- : radioVal.price && (
45
- <Box
46
- sx={{
47
- color: allSelected.includes(radioVal.uid)
48
- ? 'text.primary'
49
- : 'text.secondary',
50
- }}
51
- >
52
- {/* Change fontFamily so the + is properly outlined */}
53
- <span style={{ fontFamily: 'arial' }}>{'+ '}</span>
54
- <Money
55
- value={
56
- radioVal.price_type === 'PERCENT'
57
- ? productPrice * (radioVal.price / 100)
58
- : radioVal.price
59
- }
60
- currency={currency}
61
- />
62
- </Box>
63
- ),
64
- }) satisfies ActionCardProps,
65
- )}
79
+ items={filterNonNullableKeys(radioValue, ['title']).map((radioVal) => ({
80
+ productPrice,
81
+ currency,
82
+ title: radioVal.title ?? '',
83
+ value: radioVal.uid,
84
+ option: radioVal,
85
+ }))}
66
86
  />
67
87
  </Box>
68
88
  )
@@ -2,6 +2,7 @@ fragment ProductCustomizable on CustomizableProductInterface {
2
2
  options {
3
3
  uid
4
4
  __typename
5
+ sort_order
5
6
  ...CustomizableOption
6
7
  ...CustomizableAreaOption
7
8
  ...CustomizableCheckboxOption
@@ -1,4 +1,5 @@
1
1
  import { filterNonNullableKeys, RenderType } from '@graphcommerce/next-ui'
2
+ import { useMemo } from 'react'
2
3
  import type { AddToCartItemSelector } from '../AddProductsToCart'
3
4
  import type { ProductPagePriceFragment } from '../ProductPagePrice'
4
5
  import type { OptionTypeRenderer } from './CustomizableAreaOption'
@@ -42,14 +43,21 @@ export type ProductCustomizableProps = AddToCartItemSelector & {
42
43
  export function ProductCustomizable(props: ProductCustomizableProps) {
43
44
  const { product, renderer, index = 0 } = props
44
45
 
46
+ const options = useMemo(
47
+ () =>
48
+ filterNonNullableKeys(product.options, ['sort_order']).sort(
49
+ (a, b) => a.sort_order - b.sort_order,
50
+ ),
51
+ [product.options],
52
+ )
53
+
45
54
  return (
46
55
  <>
47
- {filterNonNullableKeys(product.options, ['sort_order']).map((option) => (
56
+ {options.map((option) => (
48
57
  <RenderType
49
58
  key={option.uid}
50
59
  renderer={{ ...defaultRenderer, ...renderer }}
51
60
  {...option}
52
- optionIndex={option.sort_order + 100}
53
61
  index={index}
54
62
  currency={product.price_range.minimum_price.final_price.currency}
55
63
  productPrice={product.price_range.minimum_price.final_price.value}
@@ -24,9 +24,7 @@ export type AnyOption = NonNullable<
24
24
  >
25
25
 
26
26
  export type OptionValueSelector = {
27
- [T in AnyOption as T['__typename']]: (
28
- option: T,
29
- ) => CustomizableProductOptionBase | CustomizableProductOptionBase[]
27
+ [T in AnyOption as T['__typename']]: (option: AnyOption) => CustomizableProductOptionBase[]
30
28
  }
31
29
 
32
30
  type MissingOptionValueSelectors = Omit<
@@ -44,12 +42,12 @@ type Selectors = Simplify<
44
42
  >
45
43
 
46
44
  export const productCustomizableSelectors = {
47
- CustomizableAreaOption: (o: CustomizableAreaOptionFragment) => o.areaValue,
45
+ CustomizableAreaOption: (o: CustomizableAreaOptionFragment) => [o.areaValue],
48
46
  CustomizableCheckboxOption: (o: CustomizableCheckboxOptionFragment) => o.checkboxValue,
49
- CustomizableFileOption: (o: CustomizableFileOptionFragment) => o.fileValue,
50
- CustomizableDateOption: (o: CustomizableDateOptionFragment) => o.dateValue,
47
+ CustomizableFileOption: (o: CustomizableFileOptionFragment) => [o.fileValue],
48
+ CustomizableDateOption: (o: CustomizableDateOptionFragment) => [o.dateValue],
51
49
  CustomizableDropDownOption: (o: CustomizableDropDownOptionFragment) => o.dropdownValue,
52
- CustomizableFieldOption: (o: CustomizableFieldOptionFragment) => o.fieldValue,
50
+ CustomizableFieldOption: (o: CustomizableFieldOptionFragment) => [o.fieldValue],
53
51
  CustomizableMultipleOption: (o: CustomizableMultipleOptionFragment) => o.multipleValue,
54
52
  CustomizableRadioOption: (o: CustomizableRadioOptionFragment) => o.radioValue,
55
53
  }
@@ -1,7 +1,7 @@
1
1
  import { PercentFormat } from '@graphcommerce/next-ui'
2
2
  import type { BoxProps } from '@mui/material'
3
3
  import { Box } from '@mui/material'
4
- import type { ProductListItemFragment } from '../../Api/ProductListItem.gql'
4
+ import type { ProductListItemFragment } from '../../graphql'
5
5
 
6
6
  export type ProductDiscountLabelProps = Pick<ProductListItemFragment, 'price_range'> &
7
7
  Omit<BoxProps, 'children'>
@@ -1,9 +1,9 @@
1
1
  import type { ImageProps } from '@graphcommerce/image'
2
2
  import { extendableComponent } from '@graphcommerce/next-ui'
3
3
  import type { SxProps, Theme } from '@mui/material'
4
- import { Skeleton, useEventCallback } from '@mui/material'
4
+ import { Skeleton } from '@mui/material'
5
5
  import React from 'react'
6
- import type { ProductListItemFragment } from '../../Api/ProductListItem.gql'
6
+ import type { ProductListItemFragment } from '../../graphql'
7
7
  import { productLink } from '../../hooks/useProductLink'
8
8
  import { ProductListPrice } from '../ProductListPrice/ProductListPrice'
9
9
  import { ProductDiscountLabel } from './ProductDiscountLabel'
@@ -18,6 +18,7 @@ import type { ProductListItemLinkOrDivProps } from './ProductListItemLinkOrDiv'
18
18
  import { ProductListItemLinkOrDiv } from './ProductListItemLinkOrDiv'
19
19
  import type { ProductListItemTitleAndPriceProps } from './ProductListItemTitleAndPrice'
20
20
  import { ProductListItemTitleAndPrice } from './ProductListItemTitleAndPrice'
21
+ import { ProductNewLabel } from './ProductNewLabel'
21
22
 
22
23
  const { classes, selectors } = extendableComponent('ProductListItem', [
23
24
  'root',
@@ -35,6 +36,7 @@ const { classes, selectors } = extendableComponent('ProductListItem', [
35
36
  'placeholder',
36
37
  'image',
37
38
  'discount',
39
+ 'new',
38
40
  ] as const)
39
41
 
40
42
  type StyleProps = {
@@ -123,6 +125,7 @@ export function ProductListItemReal(props: ProductProps) {
123
125
  topLeft={
124
126
  <>
125
127
  <ProductDiscountLabel className={classes.discount} price_range={price_range} />
128
+ <ProductNewLabel className={classes.new} product={props} />
126
129
  {topLeft}
127
130
  </>
128
131
  }
@@ -0,0 +1,36 @@
1
+ import { toDate } from '@graphcommerce/next-ui/Intl/DateTimeFormat/toDate'
2
+ import { Box, type BoxProps } from '@mui/material'
3
+ import type { ProductListItemFragment } from '../../graphql'
4
+
5
+ export type ProductNewLabelProps = {
6
+ product: Pick<ProductListItemFragment, 'new_from_date' | 'new_to_date'>
7
+ } & Omit<BoxProps, 'children'>
8
+
9
+ export function ProductNewLabel(props: ProductNewLabelProps) {
10
+ const { product, ...boxProps } = props
11
+ const { new_from_date, new_to_date } = product
12
+
13
+ const from = toDate(new_from_date)
14
+ const to = toDate(new_to_date)
15
+ const now = new Date()
16
+ if (!from || from > now) return null
17
+ if (to && to < now) return null
18
+
19
+ return (
20
+ <Box
21
+ {...boxProps}
22
+ sx={{
23
+ typography: 'caption',
24
+ bgcolor: 'text.primary',
25
+ fontWeight: 'fontWeightBold',
26
+ border: 1,
27
+ borderColor: 'divider',
28
+ padding: '0px 6px',
29
+ color: 'background.default',
30
+ display: 'inline-block',
31
+ }}
32
+ >
33
+ New
34
+ </Box>
35
+ )
36
+ }
@@ -1,5 +1,5 @@
1
1
  import type { TypeRenderer } from '@graphcommerce/next-ui'
2
- import type { ProductListItemFragment } from '../../Api/ProductListItem.gql'
2
+ import type { ProductListItemFragment } from '../../graphql'
3
3
  import { ProductListItem } from '../ProductListItem/ProductListItem'
4
4
 
5
5
  type SkeletonType = { __typename: 'Skeleton'; uid: string }
@@ -8,6 +8,7 @@ import {
8
8
  import type { SxProps, Theme } from '@mui/material'
9
9
  import { Box, Typography } from '@mui/material'
10
10
  import type { Variant } from '@mui/material/styles/createTypography'
11
+ import type { ProductListItemRenderer } from '../ProductListItems/renderer'
11
12
  import { ProductPageName } from '../ProductPageName'
12
13
  import type { ProductPageDescriptionFragment } from './ProductPageDescription.gql'
13
14
 
@@ -15,6 +16,7 @@ export type ProductPageDescriptionProps = Omit<ColumnTwoWithTopProps, 'top' | 'l
15
16
  sx?: SxProps<Theme>
16
17
  fontSize?: 'responsive' | Variant
17
18
  product: ProductPageDescriptionFragment
19
+ productListRenderer: ProductListItemRenderer
18
20
  }
19
21
 
20
22
  const componentName = 'ProductPageDescription'
@@ -23,7 +25,14 @@ const parts = ['root', 'description'] as const
23
25
  const { classes } = extendableComponent(componentName, parts)
24
26
 
25
27
  export function ProductPageDescription(props: ProductPageDescriptionProps) {
26
- const { product, right, fontSize = 'subtitle1', maxWidth = 'lg', sx = [] } = props
28
+ const {
29
+ product,
30
+ right,
31
+ fontSize = 'subtitle1',
32
+ maxWidth = 'lg',
33
+ sx = [],
34
+ productListRenderer,
35
+ } = props
27
36
 
28
37
  return (
29
38
  <LazyHydrate height={500}>
@@ -1,6 +1,8 @@
1
+ import type { MotionImageAspectProps } from '@graphcommerce/framer-scroller/components/MotionImageAspect'
1
2
  import type { SidebarGalleryProps, TypeRenderer } from '@graphcommerce/next-ui'
2
- import { nonNullable, SidebarGallery } from '@graphcommerce/next-ui'
3
+ import { filterNonNullableKeys, nonNullable, SidebarGallery } from '@graphcommerce/next-ui'
3
4
  import type { ProductPageGalleryFragment } from './ProductPageGallery.gql'
5
+ import { ProductVideo, type ProductVideoProps } from './ProductVideo'
4
6
 
5
7
  export type ProductPageGalleryRenderers = TypeRenderer<
6
8
  NonNullable<NonNullable<ProductPageGalleryFragment['media_gallery']>[0]>
@@ -15,24 +17,25 @@ export function ProductPageGallery(props: ProductPageGalleryProps) {
15
17
  const { product, children, aspectRatio: [width, height] = [1532, 1678], ...sidebarProps } = props
16
18
  const { media_gallery } = product
17
19
 
18
- const images =
19
- media_gallery
20
- ?.filter(nonNullable)
21
- .filter((p) => p.disabled !== true)
22
- .sort((a, b) => (a.position ?? 0) - (b.position ?? 0))
23
- .map((item) =>
24
- item.__typename === 'ProductImage'
25
- ? {
26
- src: item.url ?? '',
27
- alt: item.label || undefined,
28
- width,
29
- height,
30
- }
31
- : {
32
- src: '',
33
- alt: `{${item.__typename} not yet supported}`,
34
- },
35
- ) ?? []
20
+ const images = filterNonNullableKeys(media_gallery)
21
+ .filter((p) => p.disabled !== true)
22
+ .sort((a, b) => (a.position ?? 0) - (b.position ?? 0))
23
+ .map<MotionImageAspectProps<ProductVideoProps>>((item) => {
24
+ const src = item.url ?? ''
25
+ const alt = item.label || undefined
26
+ if (item.__typename === 'ProductImage') return { src, alt, width, height }
27
+ return {
28
+ src,
29
+ alt,
30
+ width: 1280,
31
+ height: 720,
32
+ sx: { objectFit: 'cover' },
33
+ Additional: ProductVideo,
34
+ slotProps: {
35
+ additional: { video: item, width: 1280, height: 720 },
36
+ },
37
+ }
38
+ })
36
39
 
37
40
  return (
38
41
  <SidebarGallery
@@ -1,4 +1,5 @@
1
1
  fragment ProductVideo on ProductVideo {
2
+ url
2
3
  video_content {
3
4
  media_type
4
5
  video_description