@graphcommerce/magento-product-configurable 4.2.11 → 4.3.1

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 (40) hide show
  1. package/CHANGELOG.md +39 -0
  2. package/ConfigurableOptions/ConfigurableOptions.tsx +1 -1
  3. package/ConfigurableProductAddToCart/ConfigurableProductAddToCart.tsx +3 -4
  4. package/ProductPageConfigurableQueryFragment.graphql +1 -5
  5. package/SwatchList.tsx +3 -2
  6. package/Swatches/types.ts +1 -1
  7. package/components/ConfigurableName/ConfigurableName.tsx +14 -0
  8. package/components/ConfigurableName/index.ts +1 -0
  9. package/components/ConfigurableOptionValue/ConfigurableOptionValue.graphql +7 -0
  10. package/components/ConfigurableOptionValue/ConfigurableOptionValue.tsx +24 -0
  11. package/components/ConfigurableOptionValue/index.ts +2 -0
  12. package/components/ConfigurableOptionValueColor/ConfigurableOptionValueColor.graphql +11 -0
  13. package/components/ConfigurableOptionValueColor/ConfigurableOptionValueColor.tsx +32 -0
  14. package/components/ConfigurableOptionValueColor/index.ts +2 -0
  15. package/components/ConfigurableOptionValueImage/ConfigurableOptionValueImage.graphql +12 -0
  16. package/components/ConfigurableOptionValueImage/ConfigurableOptionValueImage.tsx +44 -0
  17. package/components/ConfigurableOptionValueImage/index.ts +2 -0
  18. package/components/ConfigurableOptionValueText/ConfigurableOptionValueText.graphql +11 -0
  19. package/components/ConfigurableOptionValueText/ConfigurableOptionValueText.tsx +21 -0
  20. package/components/ConfigurableOptionValueText/index.ts +2 -0
  21. package/components/ConfigurablePrice/ConfigurablePrice.tsx +19 -0
  22. package/components/ConfigurablePrice/index.ts +1 -0
  23. package/components/ConfigurableProductOptions/ConfigurableProductOptions.tsx +101 -0
  24. package/components/ConfigurableProductOptions/index.ts +1 -0
  25. package/components/ConfigurableProductPageGallery/ConfigurableProductPageGallery.tsx +20 -0
  26. package/components/ConfigurableProductPageGallery/index.ts +1 -0
  27. package/{ProductListItemConfigurable.graphql → components/ProductListItemConfigurable/ProductListItemConfigurable.graphql} +0 -0
  28. package/{ProductListItemConfigurable.tsx → components/ProductListItemConfigurable/ProductListItemConfigurable.tsx} +2 -6
  29. package/components/ProductListItemConfigurable/index.ts +2 -0
  30. package/components/index.ts +9 -0
  31. package/graphql/ConfigurableOptions.graphql +25 -0
  32. package/graphql/ConfigurableOptionsSelection.graphql +52 -0
  33. package/{ConfigurableProductAddToCart → graphql}/ConfigurableProductAddToCart.graphql +0 -0
  34. package/graphql/GetConfigurableOptionsSelection.graphql +9 -0
  35. package/graphql/index.ts +2 -0
  36. package/hooks/index.ts +1 -0
  37. package/hooks/useConfigurableOptionsSelection.ts +30 -0
  38. package/index.ts +3 -1
  39. package/package.json +10 -10
  40. package/test/addConfigurableProductToCart.ts +2 -2
package/CHANGELOG.md CHANGED
@@ -1,5 +1,44 @@
1
1
  # Change Log
2
2
 
3
+ ## 4.3.1
4
+
5
+ ### Patch Changes
6
+
7
+ - Updated dependencies [[`48e6522bb`](https://github.com/graphcommerce-org/graphcommerce/commit/48e6522bb9424d4bd77fd77c68065f5625f3ec8d), [`75ae24a93`](https://github.com/graphcommerce-org/graphcommerce/commit/75ae24a93bd74e3b9b7efda21ec7ba6fbe9a3a75), [`37b1980a0`](https://github.com/graphcommerce-org/graphcommerce/commit/37b1980a04a4a3d77663b404ae83539620cf65b9)]:
8
+ - @graphcommerce/magento-product@4.6.1
9
+ - @graphcommerce/react-hook-form@3.3.4
10
+ - @graphcommerce/magento-cart-items@3.1.12
11
+ - @graphcommerce/magento-category@4.5.12
12
+ - @graphcommerce/magento-product-simple@4.1.1
13
+ - @graphcommerce/magento-cart@4.8.5
14
+ - @graphcommerce/magento-customer@4.11.5
15
+
16
+ ## 4.3.0
17
+
18
+ ### Minor Changes
19
+
20
+ - [#1642](https://github.com/graphcommerce-org/graphcommerce/pull/1642) [`ad63ebf4e`](https://github.com/graphcommerce-org/graphcommerce/commit/ad63ebf4e33bfb0e5c9e5e68ab69b14775f3f8a8) Thanks [@paales](https://github.com/paales)! - Introduced `<AddProductsToCartForm/>`, which is allows for adding all product types to the cart with a single react-hook-form form.
21
+
22
+ Which allows you to fully compose the form on the product page without having to modify the page.
23
+
24
+ ### Patch Changes
25
+
26
+ - [#1642](https://github.com/graphcommerce-org/graphcommerce/pull/1642) [`87c897cda`](https://github.com/graphcommerce-org/graphcommerce/commit/87c897cda1934f072887d5302b7b7ef5ecccd1c0) Thanks [@paales](https://github.com/paales)! - Fix issue where configurable products couldn’t properly be configured
27
+
28
+ * [#1642](https://github.com/graphcommerce-org/graphcommerce/pull/1642) [`b6bf2c941`](https://github.com/graphcommerce-org/graphcommerce/commit/b6bf2c94197ddacbf8f1fc0d352cd0d46e096f30) Thanks [@paales](https://github.com/paales)! - Remove unused ProductCustomizable on the old product pages
29
+
30
+ - [#1642](https://github.com/graphcommerce-org/graphcommerce/pull/1642) [`b91b9eb1f`](https://github.com/graphcommerce-org/graphcommerce/commit/b91b9eb1f3f9c2740fcbe03d8047f23941b10dcc) Thanks [@paales](https://github.com/paales)! - Remove the Actions callback from Configurable product list item
31
+
32
+ - Updated dependencies [[`ad63ebf4e`](https://github.com/graphcommerce-org/graphcommerce/commit/ad63ebf4e33bfb0e5c9e5e68ab69b14775f3f8a8), [`b6bf2c941`](https://github.com/graphcommerce-org/graphcommerce/commit/b6bf2c94197ddacbf8f1fc0d352cd0d46e096f30)]:
33
+ - @graphcommerce/magento-product@4.6.0
34
+ - @graphcommerce/magento-product-simple@4.1.0
35
+ - @graphcommerce/magento-store@4.3.0
36
+ - @graphcommerce/next-ui@4.27.0
37
+ - @graphcommerce/magento-cart@4.8.4
38
+ - @graphcommerce/magento-customer@4.11.4
39
+ - @graphcommerce/magento-cart-items@3.1.11
40
+ - @graphcommerce/magento-category@4.5.11
41
+
3
42
  ## 4.2.11
4
43
 
5
44
  ### Patch Changes
@@ -1,3 +1,4 @@
1
+ import { Controller, FieldErrors, UseControllerProps } from '@graphcommerce/ecommerce-ui'
1
2
  import {
2
3
  RenderType,
3
4
  SectionHeader,
@@ -5,7 +6,6 @@ import {
5
6
  ToggleButtonGroup,
6
7
  extendableComponent,
7
8
  } from '@graphcommerce/next-ui'
8
- import { Controller, FieldErrors, UseControllerProps } from '@graphcommerce/react-hook-form'
9
9
  import { BaseTextFieldProps, FormHelperText, SxProps } from '@mui/material'
10
10
  import React from 'react'
11
11
  import { Selected, useConfigurableContext } from '../ConfigurableContext/ConfigurableContext'
@@ -21,7 +21,7 @@ import {
21
21
  import {
22
22
  ConfigurableProductAddToCartDocument,
23
23
  ConfigurableProductAddToCartMutationVariables,
24
- } from './ConfigurableProductAddToCart.gql'
24
+ } from '../graphql/ConfigurableProductAddToCart.gql'
25
25
 
26
26
  type ConfigurableProductAddToCartProps = {
27
27
  variables: Omit<ConfigurableProductAddToCartMutationVariables, 'cartId' | 'selectedOptions'>
@@ -52,14 +52,13 @@ export function ConfigurableProductAddToCart(props: ConfigurableProductAddToCart
52
52
  ...buttonProps
53
53
  } = props
54
54
 
55
- const { getUids, getVariants, selection } = useConfigurableContext(variables.sku)
55
+ const { getVariants, selection } = useConfigurableContext(variables.sku)
56
56
 
57
57
  const form = useFormGqlMutationCart(ConfigurableProductAddToCartDocument, {
58
58
  defaultValues: { ...variables },
59
59
  onBeforeSubmit: ({ selectedOptions, ...vars }) => ({
60
60
  ...vars,
61
- // todo: selectedOptions should contain the correct values directly
62
- selectedOptions: getUids(selectedOptions?.[0] as unknown as Selected),
61
+ selectedOptions: Object.values(selectedOptions),
63
62
  }),
64
63
  })
65
64
 
@@ -4,11 +4,7 @@ fragment ProductPageConfigurableQueryFragment on Query {
4
4
  items {
5
5
  __typename
6
6
  uid
7
- ... on ConfigurableProduct {
8
- ...ProductWeight
9
- ...ProductCustomizable
10
- ...ConfigurableProductForm
11
- }
7
+ ...ConfigurableProductForm
12
8
  }
13
9
  }
14
10
  }
package/SwatchList.tsx CHANGED
@@ -1,15 +1,15 @@
1
1
  import type { Maybe } from '@graphcommerce/graphql-mesh'
2
2
  import { RenderType } from '@graphcommerce/next-ui'
3
3
  import React from 'react'
4
- import type { ProductListItemConfigurableFragment } from './ProductListItemConfigurable.gql'
5
4
  import { ColorSwatchData } from './Swatches/ColorSwatchData'
6
5
  import { ImageSwatchData } from './Swatches/ImageSwatchData'
7
6
  import { TextSwatchData } from './Swatches/TextSwatchData'
8
7
  import { SwatchSize, SwatchTypeRenderer } from './Swatches/types'
8
+ import { ProductListItemConfigurableFragment } from './components/ProductListItemConfigurable/ProductListItemConfigurable.gql'
9
9
 
10
10
  type SwatchListProps = {
11
11
  attributes: string[]
12
- configurable_options: Maybe<ProductListItemConfigurableFragment['configurable_options']>
12
+ configurable_options?: Maybe<ProductListItemConfigurableFragment['configurable_options']>
13
13
  }
14
14
 
15
15
  const renderer: SwatchTypeRenderer = {
@@ -34,6 +34,7 @@ export function SwatchList({ attributes, configurable_options }: SwatchListProps
34
34
  renderer={renderer}
35
35
  {...val}
36
36
  {...val.swatch_data}
37
+ __typename={val?.swatch_data?.__typename ?? 'TextSwatchData'}
37
38
  size={'small' as SwatchSize}
38
39
  />
39
40
  ) : null,
package/Swatches/types.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  import { MoneyProps } from '@graphcommerce/magento-store'
2
2
  import { TypeRenderer } from '@graphcommerce/next-ui'
3
- import { ProductListItemConfigurableFragment } from '../ProductListItemConfigurable.gql'
3
+ import { ProductListItemConfigurableFragment } from '../components/ProductListItemConfigurable/ProductListItemConfigurable.gql'
4
4
  import { SwatchDataFragment } from './SwatchData.gql'
5
5
 
6
6
  type ConfigurableOption = NonNullable<
@@ -0,0 +1,14 @@
1
+ import { ConfigurableOptionsFragment } from '../../graphql/ConfigurableOptions.gql'
2
+ import { useConfigurableOptionsSelection } from '../../hooks/useConfigurableOptionsSelection'
3
+
4
+ type ConfigurableNameProps = {
5
+ product: ConfigurableOptionsFragment
6
+ index?: number
7
+ }
8
+
9
+ export function ConfigurableName(props: ConfigurableNameProps) {
10
+ const { product, index = 0 } = props
11
+ const { configured } = useConfigurableOptionsSelection({ url_key: product.url_key, index })
12
+
13
+ return <>{configured?.configurable_product_options_selection?.variant?.name ?? product.name}</>
14
+ }
@@ -0,0 +1 @@
1
+ export * from './ConfigurableName'
@@ -0,0 +1,7 @@
1
+ fragment ConfigurableOptionValue on ConfigurableProductOptionsValues {
2
+ __typename
3
+ uid
4
+ ...ConfigurableOptionValueColor
5
+ ...ConfigurableOptionValueText
6
+ ...ConfigurableOptionValueImage
7
+ }
@@ -0,0 +1,24 @@
1
+ import { ActionCardItemRenderProps, RenderType } from '@graphcommerce/next-ui'
2
+ import { ConfigurableOptionValueColor } from '../ConfigurableOptionValueColor'
3
+ import { ConfigurableOptionValueImage } from '../ConfigurableOptionValueImage/ConfigurableOptionValueImage'
4
+ import { ConfigurableOptionValueText } from '../ConfigurableOptionValueText/ConfigurableOptionValueText'
5
+ import { ConfigurableOptionValueFragment } from './ConfigurableOptionValue.gql'
6
+
7
+ export type ConfigurabeOptionValueProps = ActionCardItemRenderProps<ConfigurableOptionValueFragment>
8
+
9
+ export function ConfigurableOptionValue(props: ConfigurabeOptionValueProps) {
10
+ const { swatch_data } = props
11
+
12
+ return (
13
+ <RenderType
14
+ {...props}
15
+ __typename={swatch_data?.__typename ?? 'TextSwatchData'}
16
+ description={swatch_data?.value ?? ''}
17
+ renderer={{
18
+ ColorSwatchData: ConfigurableOptionValueColor,
19
+ ImageSwatchData: ConfigurableOptionValueImage,
20
+ TextSwatchData: ConfigurableOptionValueText,
21
+ }}
22
+ />
23
+ )
24
+ }
@@ -0,0 +1,2 @@
1
+ export * from './ConfigurableOptionValue'
2
+ export * from './ConfigurableOptionValue.gql'
@@ -0,0 +1,11 @@
1
+ fragment ConfigurableOptionValueColor on ConfigurableProductOptionsValues {
2
+ uid
3
+ use_default_value
4
+ store_label
5
+ swatch_data {
6
+ __typename
7
+ ... on ColorSwatchData {
8
+ value
9
+ }
10
+ }
11
+ }
@@ -0,0 +1,32 @@
1
+ import { ActionCard, ActionCardItemRenderProps } from '@graphcommerce/next-ui'
2
+ import { Box } from '@mui/material'
3
+ import { swatchSizes } from '../ConfigurableOptionValueImage'
4
+ import { ConfigurableOptionValueColorFragment } from './ConfigurableOptionValueColor.gql'
5
+
6
+ export type ConfigurableOptionValueColorProps =
7
+ ActionCardItemRenderProps<ConfigurableOptionValueColorFragment>
8
+
9
+ export function ConfigurableOptionValueColor(props: ConfigurableOptionValueColorProps) {
10
+ const { swatch_data, store_label, size = 'large' } = props
11
+
12
+ if (swatch_data?.__typename !== 'ColorSwatchData')
13
+ throw Error(`ConfigurableOptionValueColor can not render a ${swatch_data?.__typename}`)
14
+
15
+ return (
16
+ <ActionCard
17
+ {...props}
18
+ image={
19
+ <Box
20
+ sx={{
21
+ width: swatchSizes[size],
22
+ height: swatchSizes[size],
23
+ backgroundColor: swatch_data.value,
24
+ borderRadius: '50%',
25
+ }}
26
+ />
27
+ }
28
+ title={store_label}
29
+ size={size}
30
+ />
31
+ )
32
+ }
@@ -0,0 +1,2 @@
1
+ export * from './ConfigurableOptionValueColor'
2
+ export * from './ConfigurableOptionValueColor.gql'
@@ -0,0 +1,12 @@
1
+ fragment ConfigurableOptionValueImage on ConfigurableProductOptionsValues {
2
+ uid
3
+ use_default_value
4
+ store_label
5
+ swatch_data {
6
+ __typename
7
+ ... on ImageSwatchData {
8
+ thumbnail
9
+ value
10
+ }
11
+ }
12
+ }
@@ -0,0 +1,44 @@
1
+ import { Image } from '@graphcommerce/image'
2
+ import { ActionCardItemRenderProps, ActionCard, responsiveVal } from '@graphcommerce/next-ui'
3
+ import { ConfigurableOptionValueImageFragment } from './ConfigurableOptionValueImage.gql'
4
+
5
+ export type ConfigurableOptionValueImageProps =
6
+ ActionCardItemRenderProps<ConfigurableOptionValueImageFragment>
7
+
8
+ export const swatchSizes = {
9
+ small: responsiveVal(30, 40),
10
+ medium: responsiveVal(30, 50),
11
+ large: responsiveVal(50, 80),
12
+ }
13
+
14
+ export function ConfigurableOptionValueImage(props: ConfigurableOptionValueImageProps) {
15
+ const {
16
+ swatch_data,
17
+ store_label,
18
+ uid,
19
+ use_default_value,
20
+ size = 'large',
21
+ ...actionCardProps
22
+ } = props
23
+
24
+ if (swatch_data?.__typename !== 'ImageSwatchData')
25
+ throw Error(`ConfigurableOptionValueImage can not render a ${swatch_data?.__typename}`)
26
+
27
+ const image = swatch_data.thumbnail && (
28
+ <Image
29
+ src={swatch_data.thumbnail ?? ''}
30
+ layout='fill'
31
+ alt={swatch_data.value ?? ''}
32
+ sizes={swatchSizes[size]}
33
+ sx={{
34
+ display: 'block',
35
+ borderRadius: '50%',
36
+ width: swatchSizes[size],
37
+ height: swatchSizes[size],
38
+ objectFit: 'cover',
39
+ }}
40
+ />
41
+ )
42
+
43
+ return <ActionCard {...actionCardProps} image={image} title={store_label} size={size} />
44
+ }
@@ -0,0 +1,2 @@
1
+ export * from './ConfigurableOptionValueImage'
2
+ export * from './ConfigurableOptionValueImage.gql'
@@ -0,0 +1,11 @@
1
+ fragment ConfigurableOptionValueText on ConfigurableProductOptionsValues {
2
+ uid
3
+ use_default_value
4
+ store_label
5
+ swatch_data {
6
+ __typename
7
+ ... on TextSwatchData {
8
+ value
9
+ }
10
+ }
11
+ }
@@ -0,0 +1,21 @@
1
+ import { ActionCard, ActionCardItemRenderProps } from '@graphcommerce/next-ui'
2
+ import { ConfigurableOptionValueTextFragment } from './ConfigurableOptionValueText.gql'
3
+
4
+ export type ConfigurableOptionValueTextProps =
5
+ ActionCardItemRenderProps<ConfigurableOptionValueTextFragment>
6
+
7
+ export function ConfigurableOptionValueText(props: ConfigurableOptionValueTextProps) {
8
+ const { swatch_data, store_label, uid, use_default_value, size, ...actionCardProps } = props
9
+
10
+ if (swatch_data?.__typename !== 'TextSwatchData')
11
+ throw Error(`ConfigurableOptionValueText can not render a ${swatch_data?.__typename}`)
12
+
13
+ return (
14
+ <ActionCard
15
+ {...actionCardProps}
16
+ size={size}
17
+ title={swatch_data?.value ?? store_label}
18
+ details={size !== 'small' && store_label}
19
+ />
20
+ )
21
+ }
@@ -0,0 +1,2 @@
1
+ export * from './ConfigurableOptionValueText'
2
+ export * from './ConfigurableOptionValueText.gql'
@@ -0,0 +1,19 @@
1
+ import { Money } from '@graphcommerce/magento-store'
2
+ import { ConfigurableOptionsFragment } from '../../graphql/ConfigurableOptions.gql'
3
+ import { useConfigurableOptionsSelection } from '../../hooks/useConfigurableOptionsSelection'
4
+
5
+ type ConfigurablePriceProps = {
6
+ product: ConfigurableOptionsFragment
7
+ index?: number
8
+ }
9
+
10
+ export function ConfigurablePrice(props: ConfigurablePriceProps) {
11
+ const { product, index = 0 } = props
12
+ const { configured } = useConfigurableOptionsSelection({ url_key: product.url_key, index })
13
+
14
+ const regular_price =
15
+ configured?.configurable_product_options_selection?.variant?.price_range.minimum_price
16
+ .final_price ?? product.price_range?.minimum_price.final_price
17
+
18
+ return <Money {...regular_price} />
19
+ }
@@ -0,0 +1 @@
1
+ export * from './ConfigurablePrice'
@@ -0,0 +1,101 @@
1
+ import { useFormAddProductsToCart } from '@graphcommerce/magento-product'
2
+ import { SectionHeader, filterNonNullableKeys, ActionCardListProps } from '@graphcommerce/next-ui'
3
+ import {
4
+ ActionCardItemBase,
5
+ ActionCardListForm,
6
+ } from '@graphcommerce/next-ui/ActionCard/ActionCardListForm'
7
+ import { i18n } from '@lingui/core'
8
+ import { Alert, Box, SxProps, Theme } from '@mui/material'
9
+ import { useRouter } from 'next/router'
10
+ import React, { useEffect, useMemo } from 'react'
11
+ import { ConfigurableOptionsFragment } from '../../graphql/ConfigurableOptions.gql'
12
+ import { useConfigurableOptionsSelection } from '../../hooks'
13
+ import { ConfigurableOptionValue } from '../ConfigurableOptionValue/ConfigurableOptionValue'
14
+ import { ConfigurableOptionValueFragment } from '../ConfigurableOptionValue/ConfigurableOptionValue.gql'
15
+
16
+ export type ConfigurableProductOptionsProps = {
17
+ optionEndLabels?: Record<string, React.ReactNode>
18
+ sx?: SxProps<Theme>
19
+ render?: typeof ConfigurableOptionValue
20
+ product: ConfigurableOptionsFragment
21
+ index?: number
22
+ } & Pick<ActionCardListProps, 'color' | 'variant' | 'size' | 'layout' | 'collapse'>
23
+
24
+ export function ConfigurableProductOptions(props: ConfigurableProductOptionsProps) {
25
+ const {
26
+ optionEndLabels,
27
+ sx,
28
+ render = ConfigurableOptionValue,
29
+ product,
30
+ index = 0,
31
+ ...other
32
+ } = props
33
+ const { control, setError, clearErrors } = useFormAddProductsToCart()
34
+ const { locale } = useRouter()
35
+
36
+ const options = useMemo(
37
+ () =>
38
+ filterNonNullableKeys(product.configurable_options, ['attribute_code', 'label']).map(
39
+ (option) => ({
40
+ ...option,
41
+ values: filterNonNullableKeys(option.values, ['uid', 'swatch_data']).map((ov) => ({
42
+ value: ov.uid,
43
+ ...ov,
44
+ })),
45
+ }),
46
+ ),
47
+ [product.configurable_options],
48
+ )
49
+
50
+ const { configured } = useConfigurableOptionsSelection({ url_key: product.url_key, index })
51
+ const unavailable =
52
+ configured &&
53
+ (configured?.configurable_product_options_selection?.options_available_for_selection ?? [])
54
+ .length === 0
55
+
56
+ const allLabels = useMemo(() => {
57
+ const formatter = new Intl.ListFormat(locale, { style: 'long', type: 'conjunction' })
58
+ return formatter.format(options.map((o) => o.label))
59
+ }, [locale, options])
60
+
61
+ useEffect(() => {
62
+ if (unavailable) {
63
+ setError(`cartItems.${index}.sku`, {
64
+ message: i18n._(/* i18n */ 'Product not available in {allLabels}', { allLabels }),
65
+ })
66
+ }
67
+ if (!unavailable) clearErrors(`cartItems.${index}.sku`)
68
+ }, [allLabels, clearErrors, index, setError, unavailable])
69
+
70
+ return (
71
+ <Box sx={(theme) => ({ display: 'grid', rowGap: theme.spacings.sm })}>
72
+ {options.map((option, idx) => {
73
+ const { values, label } = option
74
+ const fieldName = `cartItems.${index}.selected_options.${idx}` as const
75
+
76
+ return (
77
+ <Box key={fieldName} sx={sx}>
78
+ <SectionHeader
79
+ labelLeft={label}
80
+ labelRight={optionEndLabels?.[option?.attribute_code ?? '']}
81
+ sx={{ mt: 0 }}
82
+ />
83
+
84
+ <ActionCardListForm<ActionCardItemBase & ConfigurableOptionValueFragment>
85
+ layout='grid'
86
+ {...other}
87
+ name={fieldName}
88
+ control={control}
89
+ required
90
+ items={values}
91
+ render={render}
92
+ rules={{
93
+ required: i18n._(/* i18n*/ 'Please select a value for ‘{label}’', { label }),
94
+ }}
95
+ />
96
+ </Box>
97
+ )
98
+ })}
99
+ </Box>
100
+ )
101
+ }
@@ -0,0 +1 @@
1
+ export * from './ConfigurableProductOptions'
@@ -0,0 +1,20 @@
1
+ import { ProductPageGallery, ProductPageGalleryProps } from '@graphcommerce/magento-product'
2
+ import { useConfigurableOptionsSelection } from '../../hooks/useConfigurableOptionsSelection'
3
+
4
+ type ConfigurableProductPageGalleryProps = ProductPageGalleryProps & {
5
+ url_key?: string | null
6
+ index?: number
7
+ }
8
+
9
+ /** Compatible with any product type, but will switch the image when used for a ConfigurableProduct */
10
+ export function ConfigurableProductPageGallery(props: ConfigurableProductPageGalleryProps) {
11
+ const { media_gallery, url_key, index = 0 } = props
12
+
13
+ const { configured } = useConfigurableOptionsSelection({ url_key, index })
14
+ const media =
15
+ (configured?.configurable_product_options_selection?.media_gallery?.length ?? 0) > 0
16
+ ? configured?.configurable_product_options_selection?.media_gallery
17
+ : media_gallery
18
+
19
+ return <ProductPageGallery {...props} media_gallery={media} />
20
+ }
@@ -0,0 +1 @@
1
+ export * from './ConfigurableProductPageGallery'
@@ -2,12 +2,11 @@ import {
2
2
  ProductListItem,
3
3
  OverlayAreaKeys,
4
4
  ProductListItemProps,
5
- isFilterTypeEqual,
6
5
  useProductListParamsContext,
6
+ isFilterTypeEqual,
7
7
  } from '@graphcommerce/magento-product'
8
- import React from 'react'
8
+ import { SwatchList } from '../../SwatchList'
9
9
  import { ProductListItemConfigurableFragment } from './ProductListItemConfigurable.gql'
10
- import { SwatchList } from './SwatchList'
11
10
 
12
11
  export type ProductListItemConfigurableActionProps = ProductListItemConfigurableFragment & {
13
12
  variant?: NonNullable<ProductListItemConfigurableFragment['variants']>[0]
@@ -15,13 +14,11 @@ export type ProductListItemConfigurableActionProps = ProductListItemConfigurable
15
14
 
16
15
  export type ProdustListItemConfigurableProps = ProductListItemConfigurableFragment &
17
16
  ProductListItemProps & {
18
- Actions?: React.VFC<ProductListItemConfigurableActionProps>
19
17
  swatchLocations?: Record<OverlayAreaKeys, string[]>
20
18
  }
21
19
 
22
20
  export function ProductListItemConfigurable(props: ProdustListItemConfigurableProps) {
23
21
  const {
24
- Actions,
25
22
  variants,
26
23
  configurable_options,
27
24
  children,
@@ -103,7 +100,6 @@ export function ProductListItemConfigurable(props: ProdustListItemConfigurablePr
103
100
  </>
104
101
  }
105
102
  >
106
- {Actions && <Actions {...configurableProduct} variant={matchingVariants?.[0]} />}
107
103
  {children}
108
104
  </ProductListItem>
109
105
  )
@@ -0,0 +1,2 @@
1
+ export * from './ProductListItemConfigurable'
2
+ export * from './ProductListItemConfigurable.gql'
@@ -0,0 +1,9 @@
1
+ export * from './ConfigurableOptionValue'
2
+ export * from './ConfigurableOptionValueColor'
3
+ export * from './ConfigurableOptionValueImage'
4
+ export * from './ConfigurableOptionValueText'
5
+ export * from './ConfigurablePrice'
6
+ export * from './ConfigurableProductOptions'
7
+ export * from './ConfigurableProductPageGallery'
8
+ export * from './ProductListItemConfigurable'
9
+ export * from './ConfigurableName'
@@ -0,0 +1,25 @@
1
+ fragment ConfigurableOptions on ConfigurableProduct {
2
+ __typename
3
+ uid
4
+ sku
5
+ name
6
+ url_key
7
+ price_range {
8
+ minimum_price {
9
+ final_price {
10
+ currency
11
+ value
12
+ }
13
+ }
14
+ }
15
+ configurable_options {
16
+ attribute_code
17
+ uid
18
+ label
19
+ position
20
+ use_default
21
+ values {
22
+ ...ConfigurableOptionValue
23
+ }
24
+ }
25
+ }
@@ -0,0 +1,52 @@
1
+ fragment ConfigurableOptionsSelection on ConfigurableProduct @injectable {
2
+ configurable_product_options_selection(configurableOptionValueUids: $selectedOptions) {
3
+ configurable_options {
4
+ attribute_code
5
+ label
6
+ values {
7
+ is_available
8
+ is_use_default
9
+ label
10
+ swatch {
11
+ value
12
+ ... on ImageSwatchData {
13
+ thumbnail
14
+ }
15
+ }
16
+ uid
17
+ }
18
+ }
19
+ options_available_for_selection {
20
+ attribute_code
21
+ option_value_uids
22
+ }
23
+ media_gallery {
24
+ __typename
25
+ label
26
+ position
27
+ disabled
28
+ ...ProductImage
29
+ ...ProductVideo
30
+ }
31
+ variant {
32
+ uid
33
+ name
34
+ thumbnail {
35
+ disabled
36
+ label
37
+ position
38
+ url
39
+ }
40
+ price_range {
41
+ minimum_price {
42
+ final_price {
43
+ ...Money
44
+ }
45
+ regular_price {
46
+ ...Money
47
+ }
48
+ }
49
+ }
50
+ }
51
+ }
52
+ }
@@ -0,0 +1,9 @@
1
+ query GetConfigurableOptionsSelection($urlKey: String!, $selectedOptions: [ID!] = []) {
2
+ products(filter: { url_key: { eq: $urlKey } }) {
3
+ items {
4
+ __typename
5
+ uid
6
+ ...ConfigurableOptionsSelection
7
+ }
8
+ }
9
+ }
@@ -0,0 +1,2 @@
1
+ export * from './ConfigurableOptionsSelection.gql'
2
+ export * from './GetConfigurableOptionsSelection.gql'
package/hooks/index.ts ADDED
@@ -0,0 +1 @@
1
+ export * from './useConfigurableOptionsSelection'
@@ -0,0 +1,30 @@
1
+ import { useQuery } from '@graphcommerce/graphql'
2
+ import { useFormAddProductsToCart } from '@graphcommerce/magento-product'
3
+ import { findByTypename, nonNullable } from '@graphcommerce/next-ui'
4
+ import { GetConfigurableOptionsSelectionDocument } from '../graphql/GetConfigurableOptionsSelection.gql'
5
+
6
+ export function useConfigurableOptionsSelection({
7
+ url_key,
8
+ index = 0,
9
+ }: {
10
+ url_key?: string | null
11
+ index: number
12
+ }) {
13
+ const { watch } = useFormAddProductsToCart()
14
+
15
+ const selectedOptions = (watch(`cartItems.${index}.selected_options`) ?? [])
16
+ .filter(nonNullable)
17
+ .filter(Boolean)
18
+
19
+ const cpc = useQuery(GetConfigurableOptionsSelectionDocument, {
20
+ variables: { urlKey: url_key ?? '', selectedOptions },
21
+ skip: !url_key || !selectedOptions.length,
22
+ ssr: false,
23
+ })
24
+
25
+ const configured = findByTypename(
26
+ cpc.data?.products?.items ?? cpc.previousData?.products?.items,
27
+ 'ConfigurableProduct',
28
+ )
29
+ return { ...cpc, configured }
30
+ }
package/index.ts CHANGED
@@ -1,5 +1,7 @@
1
+ export * from './components'
1
2
  export * from './ConfigurableCartItem/ConfigurableCartItem'
2
3
  export * from './ConfigurableContext/ConfigurableContext'
3
4
  export * from './ConfigurableProductAddToCart/ConfigurableProductAddToCart'
4
5
  export * from './ConfigurableProductPage.gql'
5
- export * from './ProductListItemConfigurable'
6
+ export * from './graphql'
7
+ export * from './hooks'
package/package.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "name": "@graphcommerce/magento-product-configurable",
3
3
  "homepage": "https://www.graphcommerce.org/",
4
4
  "repository": "github:graphcommerce-org/graphcommerce",
5
- "version": "4.2.11",
5
+ "version": "4.3.1",
6
6
  "sideEffects": false,
7
7
  "prettier": "@graphcommerce/prettier-config-pwa",
8
8
  "eslintConfig": {
@@ -22,15 +22,15 @@
22
22
  "@graphcommerce/graphql": "3.4.8",
23
23
  "@graphcommerce/graphql-mesh": "4.2.0",
24
24
  "@graphcommerce/image": "3.1.9",
25
- "@graphcommerce/magento-cart": "4.8.3",
26
- "@graphcommerce/magento-cart-items": "3.1.10",
27
- "@graphcommerce/magento-category": "4.5.10",
28
- "@graphcommerce/magento-customer": "4.11.3",
29
- "@graphcommerce/magento-product": "4.5.10",
30
- "@graphcommerce/magento-product-simple": "4.0.58",
31
- "@graphcommerce/magento-store": "4.2.35",
32
- "@graphcommerce/next-ui": "4.26.0",
33
- "@graphcommerce/react-hook-form": "3.3.3"
25
+ "@graphcommerce/magento-cart": "4.8.5",
26
+ "@graphcommerce/magento-cart-items": "3.1.12",
27
+ "@graphcommerce/magento-category": "4.5.12",
28
+ "@graphcommerce/magento-customer": "4.11.5",
29
+ "@graphcommerce/magento-product": "4.6.1",
30
+ "@graphcommerce/magento-product-simple": "4.1.1",
31
+ "@graphcommerce/magento-store": "4.3.0",
32
+ "@graphcommerce/next-ui": "4.27.0",
33
+ "@graphcommerce/react-hook-form": "3.3.4"
34
34
  },
35
35
  "peerDependencies": {
36
36
  "@lingui/react": "^3.13.2",
@@ -1,7 +1,7 @@
1
1
  import { waitForGraphQlResponse } from '@graphcommerce/graphql/test/apolloClient.fixture'
2
2
  import { CreateEmptyCartDocument } from '@graphcommerce/magento-cart/hooks/CreateEmptyCart.gql'
3
+ import { ProductAddToCartDocument } from '@graphcommerce/magento-product/components'
3
4
  import { Page, expect } from '@playwright/test'
4
- import { ConfigurableProductAddToCartDocument } from '../ConfigurableProductAddToCart/ConfigurableProductAddToCart.gql'
5
5
 
6
6
  export async function addConfigurableProductToCart(page: Page, productUrl: string) {
7
7
  await page.goto(productUrl)
@@ -18,7 +18,7 @@ export async function addConfigurableProductToCart(page: Page, productUrl: strin
18
18
  expect(createCart.errors).toBeUndefined()
19
19
  expect(createCart.data?.createEmptyCart).toBeDefined()
20
20
 
21
- const addToCart = await waitForGraphQlResponse(page, ConfigurableProductAddToCartDocument)
21
+ const addToCart = await waitForGraphQlResponse(page, ProductAddToCartDocument)
22
22
  expect(addToCart.errors).toBeUndefined()
23
23
  expect(addToCart.data?.addProductsToCart?.user_errors.length).toBe(0)
24
24
  }