@graphcommerce/magento-product-configurable 3.6.41 → 4.0.0

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.
@@ -1,114 +1,46 @@
1
- import { responsiveVal } from '@graphcommerce/next-ui'
2
- import { makeStyles, Theme } from '@material-ui/core'
3
- import React from 'react'
1
+ import { extendableComponent } from '@graphcommerce/next-ui'
2
+ import { Box, SxProps, Theme } from '@mui/material'
4
3
  import { ConfigurableCartItemFragment } from './ConfigurableCartItem.gql'
5
4
 
6
- const useStyles = makeStyles(
7
- (theme: Theme) => ({
8
- optionsList: {
9
- gridArea: 'itemOptions',
10
- cursor: 'default',
11
- marginLeft: 0,
12
- paddingBottom: 4,
13
- },
14
- option: {
15
- color: theme.palette.text.secondary,
16
- textDecoration: 'underline',
17
- marginRight: theme.spacings.xs,
18
- paddingBottom: 1,
19
- display: 'inline',
20
- },
21
- menuPaper: {
22
- minWidth: responsiveVal(200, 560),
23
- maxWidth: 560,
24
- marginTop: -8,
25
- padding: `${theme.spacings.xs} ${theme.spacings.xs}`,
26
- [theme.breakpoints.down('xs')]: {
27
- minWidth: 0,
28
- width: '100%',
29
- maxWidth: `calc(100% - (${theme.page.horizontal}px * 2))`,
30
- margin: '0 auto',
31
- marginTop: '8px',
32
- },
33
- },
34
- menuList: {
35
- padding: 0,
36
- '&:focus': {
37
- outline: 'none',
38
- },
39
- },
40
- menuTitle: {
41
- ...theme.typography.h5,
42
- paddingBottom: theme.spacings.xxs,
43
- marginBottom: theme.spacings.xxs,
44
- borderBottom: `1px solid ${theme.palette.divider}`,
45
- },
46
- menuContent: {
47
- display: 'flex',
48
- },
49
- saveChangesWrap: {
50
- alignSelf: 'center',
51
- },
52
- saveChangesButton: {
53
- padding: '10px 20px',
54
- fontWeight: theme.typography.fontWeightBold,
55
- boxShadow: theme.shadows[3],
56
- },
57
- }),
58
- { name: 'CartItemOptionsList' },
59
- )
5
+ type CartItemOptionsListProps = ConfigurableCartItemFragment & {
6
+ sx?: SxProps<Theme>
7
+ }
60
8
 
61
- type CartItemOptionsListProps = ConfigurableCartItemFragment
9
+ const name = 'ColorSwatchData' as const
10
+ const parts = ['root', 'option'] as const
11
+ const { classes } = extendableComponent(name, parts)
62
12
 
63
13
  export default function OptionsList(props: CartItemOptionsListProps) {
64
- const { configurable_options } = props
65
- const classes = useStyles()
66
- // const [anchorEl, setAnchorEl] = useState<HTMLDivElement>()
67
-
68
- // const handleClick = (event: React.MouseEvent<HTMLDivElement, MouseEvent>) => {
69
- // setAnchorEl(event.currentTarget)
70
- // }
71
-
72
- // const handleClose = () => {
73
- // setAnchorEl(undefined)
74
- // }
75
-
76
- // const handleChange = () => {
77
- // //
78
- // }
14
+ const { configurable_options, sx = [] } = props
79
15
 
80
16
  return (
81
- <>
82
- <div className={classes.optionsList} /* onClick={handleClick}*/>
83
- {configurable_options &&
84
- configurable_options.map((option) => (
85
- <div key={option?.configurable_product_option_uid} className={classes.option}>
86
- {option?.value_label}
87
- </div>
88
- ))}
89
- </div>
90
-
91
- {/* <Menu
92
- anchorEl={anchorEl}
93
- open={!!anchorEl}
94
- onClose={handleClose}
95
- getContentAnchorEl={null} // https://github.com/mui-org/material-ui/issues/7961#issuecomment-326116559
96
- variant='selectedMenu'
97
- anchorPosition={{ top: 6, left: 0 }}
98
- anchorOrigin={{ horizontal: 'left', vertical: 'bottom' }}
99
- classes={{ paper: classes.menuPaper, list: classes.menuList }}
100
- >
101
- <div className={classes.menuTitle}>Edit product</div>
102
- <div className={classes.menuContent}>
103
- <CartItemOptionDropdown label='Color' onChange={handleChange} />
104
- <CartItemOptionDropdown label='Size' onChange={handleChange} />
105
- <div className={classes.saveChangesWrap}>
106
- <Button variant='pill' className={classes.saveChangesButton}>
107
- Save changes
108
- </Button>
109
- </div>
110
- </div>
111
- </Menu> */}
112
- </>
17
+ <Box
18
+ className={classes.root}
19
+ sx={[
20
+ {
21
+ gridArea: 'itemOptions',
22
+ cursor: 'default',
23
+ marginLeft: 0,
24
+ paddingBottom: '4px',
25
+ },
26
+ ...(Array.isArray(sx) ? sx : [sx]),
27
+ ]}
28
+ >
29
+ {configurable_options &&
30
+ configurable_options.map((option) => (
31
+ <Box
32
+ key={option?.configurable_product_option_uid}
33
+ className={classes.option}
34
+ sx={(theme) => ({
35
+ color: 'text.secondary',
36
+ textDecoration: 'underline',
37
+ marginRight: theme.spacings.xs,
38
+ display: 'inline',
39
+ })}
40
+ >
41
+ {option?.value_label}
42
+ </Box>
43
+ ))}
44
+ </Box>
113
45
  )
114
46
  }
@@ -1,41 +1,35 @@
1
- import { RenderType, SectionHeader, ToggleButton, ToggleButtonGroup } from '@graphcommerce/next-ui'
1
+ import {
2
+ RenderType,
3
+ SectionHeader,
4
+ ToggleButton,
5
+ ToggleButtonGroup,
6
+ extendableComponent,
7
+ } from '@graphcommerce/next-ui'
2
8
  import { Controller, FieldErrors, UseControllerProps } from '@graphcommerce/react-hook-form'
3
- import { BaseTextFieldProps, FormHelperText, makeStyles, Theme } from '@material-ui/core'
9
+ import { BaseTextFieldProps, FormHelperText } from '@mui/material'
4
10
  import React from 'react'
5
11
  import { Selected, useConfigurableContext } from '../ConfigurableContext'
6
12
  import { SwatchTypeRenderer, SwatchSize } from '../Swatches'
7
- import ColorSwatchData from '../Swatches/ColorSwatchData'
8
- import ImageSwatchData from '../Swatches/ImageSwatchData'
9
- import TextSwatchData from '../Swatches/TextSwatchData'
13
+ import { ColorSwatchData } from '../Swatches/ColorSwatchData'
14
+ import { ImageSwatchData } from '../Swatches/ImageSwatchData'
15
+ import { TextSwatchData } from '../Swatches/TextSwatchData'
10
16
 
11
- export type ConfigurableOptionsProps = {
17
+ export type ConfigurableOptionsInputProps = {
12
18
  sku: string
13
19
  errors?: FieldErrors
20
+ size?: SwatchSize
14
21
  } & UseControllerProps<any> &
15
22
  Pick<BaseTextFieldProps, 'FormHelperTextProps' | 'helperText'> & {
16
23
  optionEndLabels?: Record<string, React.ReactNode>
17
24
  }
18
25
 
19
- export const useStyles = makeStyles(
20
- (theme: Theme) => ({
21
- toggleButtonGroup: {
22
- display: 'grid',
23
- gridTemplateColumns: 'repeat(2, 1fr)',
24
- gap: theme.spacings.xs,
25
- },
26
- button: {
27
- minHeight: theme.spacings.lg,
28
- },
29
- helperText: {
30
- position: 'absolute',
31
- },
32
- }),
33
- { name: 'ConfigurableOptions' },
34
- )
35
-
36
26
  const renderer: SwatchTypeRenderer = { TextSwatchData, ImageSwatchData, ColorSwatchData }
37
27
 
38
- export default function ConfigurableOptionsInput(props: ConfigurableOptionsProps) {
28
+ const compName = 'ConfigurableOptionsInput' as const
29
+ const parts = ['buttonGroup', 'button', 'helperText'] as const
30
+ const { classes } = extendableComponent(compName, parts)
31
+
32
+ export default function ConfigurableOptionsInput(props: ConfigurableOptionsInputProps) {
39
33
  const {
40
34
  sku,
41
35
  FormHelperTextProps,
@@ -44,11 +38,11 @@ export default function ConfigurableOptionsInput(props: ConfigurableOptionsProps
44
38
  errors,
45
39
  helperText,
46
40
  optionEndLabels,
41
+ size = 'large',
47
42
  ...controlProps
48
43
  } = props
49
44
 
50
45
  const { options, selection, select, getVariants } = useConfigurableContext(sku)
51
- const classes = useStyles()
52
46
 
53
47
  return (
54
48
  <>
@@ -77,7 +71,6 @@ export default function ConfigurableOptionsInput(props: ConfigurableOptionsProps
77
71
  defaultValue={selection[attribute_code] ?? ''}
78
72
  required
79
73
  exclusive
80
- minWidth={100}
81
74
  onChange={(_, val: string | number) => {
82
75
  onChange(val)
83
76
  select((prev) => ({ ...prev, [attribute_code]: val } as Selected))
@@ -85,7 +78,8 @@ export default function ConfigurableOptionsInput(props: ConfigurableOptionsProps
85
78
  ref={ref}
86
79
  onBlur={onBlur}
87
80
  value={value}
88
- classes={{ root: classes.toggleButtonGroup }}
81
+ className={classes.buttonGroup}
82
+ size={size}
89
83
  >
90
84
  {option?.values?.map((val) => {
91
85
  if (!val?.uid || !option.attribute_code) return null
@@ -110,20 +104,28 @@ export default function ConfigurableOptionsInput(props: ConfigurableOptionsProps
110
104
  name={inputName}
111
105
  className={classes.button}
112
106
  disabled={!itemVariant}
107
+ size={size}
113
108
  >
114
109
  <RenderType
115
110
  renderer={renderer}
116
111
  {...val}
117
112
  {...swatch_data}
118
113
  price={itemVariant?.product?.price_range.minimum_price.final_price}
119
- size={'large' as SwatchSize}
114
+ size={size}
120
115
  />
121
116
  </ToggleButton>
122
117
  )
123
118
  })}
124
119
  </ToggleButtonGroup>
125
120
  {error && (
126
- <FormHelperText error {...FormHelperTextProps} className={classes.helperText}>
121
+ <FormHelperText
122
+ error
123
+ {...FormHelperTextProps}
124
+ className={classes.helperText}
125
+ sx={{
126
+ position: 'absolute',
127
+ }}
128
+ >
127
129
  {`${option.label} is ${errorHelperText?.type}`}
128
130
  </FormHelperText>
129
131
  )}
@@ -3,19 +3,20 @@ import { Money } from '@graphcommerce/magento-store'
3
3
  import {
4
4
  AnimatedRow,
5
5
  Button,
6
+ extendableComponent,
6
7
  iconChevronRight,
7
8
  MessageSnackbar,
8
- SvgImageSimple,
9
+ SvgIcon,
9
10
  TextInputNumber,
10
11
  } from '@graphcommerce/next-ui'
11
- import { Divider, makeStyles, Theme, Typography } from '@material-ui/core'
12
- import { Alert } from '@material-ui/lab'
12
+ import { Trans } from '@lingui/macro'
13
+ import { Divider, Typography, Alert, Box } from '@mui/material'
13
14
  import { AnimatePresence } from 'framer-motion'
14
15
  import PageLink from 'next/link'
15
16
  import React from 'react'
16
17
  import { Selected, useConfigurableContext } from '../ConfigurableContext'
17
18
  import cheapestVariant from '../ConfigurableContext/cheapestVariant'
18
- import ConfigurableOptionsInput from '../ConfigurableOptions'
19
+ import ConfigurableOptionsInput, { ConfigurableOptionsInputProps } from '../ConfigurableOptions'
19
20
  import {
20
21
  ConfigurableProductAddToCartDocument,
21
22
  ConfigurableProductAddToCartMutationVariables,
@@ -26,35 +27,19 @@ type ConfigurableProductAddToCartProps = {
26
27
  name: string
27
28
  optionEndLabels?: Record<string, React.ReactNode>
28
29
  children?: React.ReactNode
30
+ optionsProps?: Omit<
31
+ ConfigurableOptionsInputProps,
32
+ 'name' | 'sku' | 'control' | 'rules' | 'errors' | 'optionEndLabels'
33
+ >
29
34
  }
30
35
 
31
- const useStyles = makeStyles(
32
- (theme: Theme) => ({
33
- form: {
34
- width: '100%',
35
- },
36
- button: {
37
- marginTop: theme.spacings.sm,
38
- marginBottom: theme.spacings.sm,
39
- width: '100%',
40
- },
41
- finalPrice: {
42
- marginTop: theme.spacings.sm,
43
- },
44
- quantity: {
45
- marginTop: theme.spacings.sm,
46
- },
47
- divider: {
48
- margin: `${theme.spacings.sm} 0`,
49
- },
50
- }),
51
- { name: 'ConfigurableProductAddToCart' },
52
- )
36
+ const compName = 'ConfigurableOptionsInput' as const
37
+ const parts = ['form', 'button', 'finalPrice', 'quantity', 'divider'] as const
38
+ const { classes } = extendableComponent(compName, parts)
53
39
 
54
40
  export default function ConfigurableProductAddToCart(props: ConfigurableProductAddToCartProps) {
55
- const { name, children, variables, optionEndLabels, ...buttonProps } = props
41
+ const { name, children, variables, optionEndLabels, optionsProps, ...buttonProps } = props
56
42
  const { getUids, getVariants, selection } = useConfigurableContext(variables.sku)
57
- const classes = useStyles()
58
43
 
59
44
  const form = useFormGqlMutationCart(ConfigurableProductAddToCartDocument, {
60
45
  defaultValues: { ...variables },
@@ -69,8 +54,14 @@ export default function ConfigurableProductAddToCart(props: ConfigurableProductA
69
54
  const submitHandler = handleSubmit(() => {})
70
55
 
71
56
  return (
72
- <form onSubmit={submitHandler} noValidate className={classes.form}>
73
- <Divider className={classes.divider} />
57
+ <Box
58
+ component='form'
59
+ onSubmit={submitHandler}
60
+ noValidate
61
+ className={classes.form}
62
+ sx={{ width: '100%' }}
63
+ >
64
+ <Divider className={classes.divider} sx={(theme) => ({ margin: `${theme.spacings.sm} 0` })} />
74
65
  <ConfigurableOptionsInput
75
66
  name='selectedOptions'
76
67
  sku={variables.sku}
@@ -78,6 +69,7 @@ export default function ConfigurableProductAddToCart(props: ConfigurableProductA
78
69
  rules={{ required: required.selectedOptions }}
79
70
  errors={formState.errors.selectedOptions}
80
71
  optionEndLabels={optionEndLabels}
72
+ {...optionsProps}
81
73
  />
82
74
  <TextInputNumber
83
75
  variant='outlined'
@@ -89,9 +81,15 @@ export default function ConfigurableProductAddToCart(props: ConfigurableProductA
89
81
  // disabled={loading}
90
82
  size='small'
91
83
  className={classes.quantity}
84
+ sx={(theme) => ({ marginTop: theme.spacings.sm })}
92
85
  />
93
- <Divider className={classes.divider} />
94
- <Typography component='div' variant='h3' className={classes.finalPrice}>
86
+ <Divider className={classes.divider} sx={(theme) => ({ margin: `${theme.spacings.sm} 0` })} />
87
+ <Typography
88
+ component='div'
89
+ variant='h3'
90
+ className={classes.finalPrice}
91
+ sx={(theme) => ({ marginTop: theme.spacings.sm })}
92
+ >
95
93
  <Money
96
94
  {...cheapestVariant(getVariants(selection))?.product?.price_range.minimum_price
97
95
  .final_price}
@@ -104,8 +102,13 @@ export default function ConfigurableProductAddToCart(props: ConfigurableProductA
104
102
  color='primary'
105
103
  variant='pill'
106
104
  size='large'
107
- classes={{ root: classes.button }}
105
+ className={classes.button}
108
106
  {...buttonProps}
107
+ sx={(theme) => ({
108
+ marginTop: theme.spacings.sm,
109
+ marginBottom: theme.spacings.sm,
110
+ width: '100%',
111
+ })}
109
112
  >
110
113
  Add to Cart
111
114
  </Button>
@@ -134,17 +137,17 @@ export default function ConfigurableProductAddToCart(props: ConfigurableProductA
134
137
  size='medium'
135
138
  variant='pill'
136
139
  color='secondary'
137
- endIcon={<SvgImageSimple src={iconChevronRight} inverted />}
140
+ endIcon={<SvgIcon src={iconChevronRight} />}
138
141
  >
139
142
  View shopping cart
140
143
  </Button>
141
144
  </PageLink>
142
145
  }
143
146
  >
144
- <>
147
+ <Trans>
145
148
  <strong>{name}</strong>&nbsp;has been added to your shopping cart!
146
- </>
149
+ </Trans>
147
150
  </MessageSnackbar>
148
- </form>
151
+ </Box>
149
152
  )
150
153
  }
package/SwatchList.tsx CHANGED
@@ -3,9 +3,9 @@ import { RenderType } from '@graphcommerce/next-ui'
3
3
  import React from 'react'
4
4
  import { ProductListItemConfigurableFragment } from './ProductListItemConfigurable.gql'
5
5
  import { SwatchSize, SwatchTypeRenderer } from './Swatches'
6
- import ColorSwatchData from './Swatches/ColorSwatchData'
7
- import ImageSwatchData from './Swatches/ImageSwatchData'
8
- import TextSwatchData from './Swatches/TextSwatchData'
6
+ import { ColorSwatchData } from './Swatches/ColorSwatchData'
7
+ import { ImageSwatchData } from './Swatches/ImageSwatchData'
8
+ import { TextSwatchData } from './Swatches/TextSwatchData'
9
9
 
10
10
  type SwatchListProps = {
11
11
  attributes: string[]
@@ -1,42 +1,46 @@
1
- import { UseStyles, responsiveVal } from '@graphcommerce/next-ui'
2
- import { makeStyles } from '@material-ui/core'
3
- import clsx from 'clsx'
1
+ import { responsiveVal, extendableComponent } from '@graphcommerce/next-ui'
2
+ import { Box, SxProps, Theme } from '@mui/material'
4
3
  import { ColorSwatchDataFragment } from './ColorSwatchData.gql'
5
4
  import { SwatchDataProps } from '.'
6
5
 
7
- export const useStyles = makeStyles(
8
- {
9
- root: {
10
- margin: '0 auto',
11
- height: responsiveVal(22, 30),
12
- width: responsiveVal(22, 30),
13
- borderRadius: '50%',
14
- },
15
- sizeSmall: {
16
- height: responsiveVal(8, 12),
17
- width: responsiveVal(8, 12),
18
- marginTop: responsiveVal(2, 4),
19
- },
20
- },
21
- { name: 'ColorSwatchData' },
22
- )
6
+ type ColorSwatchDataProps = ColorSwatchDataFragment &
7
+ SwatchDataProps & {
8
+ sx?: SxProps<Theme>
9
+ }
23
10
 
24
- type ColorSwatchDataProps = ColorSwatchDataFragment & SwatchDataProps & UseStyles<typeof useStyles>
11
+ type OwnerState = Pick<SwatchDataProps, 'size'>
12
+ const name = 'ColorSwatchData' as const
13
+ const parts = ['root', 'color', 'label'] as const
14
+ const { withState } = extendableComponent<OwnerState, typeof name, typeof parts>(name, parts)
25
15
 
26
- export default function ColorSwatchData(props: ColorSwatchDataProps) {
27
- const classes = useStyles(props)
28
- const { value, store_label, size } = props
16
+ export function ColorSwatchData(props: ColorSwatchDataProps) {
17
+ const { value, store_label, size, sx } = props
18
+ const classes = withState({ size })
29
19
 
30
20
  return (
31
- <div>
32
- <div
33
- className={clsx({
34
- [classes.root]: true,
35
- [classes.sizeSmall]: size === 'small',
36
- })}
21
+ <Box className={classes.root} sx={sx}>
22
+ <Box
23
+ className={classes.color}
37
24
  style={{ backgroundColor: value ?? undefined }}
25
+ sx={[
26
+ {
27
+ margin: '0 auto',
28
+ height: responsiveVal(22, 30),
29
+ width: responsiveVal(22, 30),
30
+ borderRadius: '50%',
31
+ '&.sizeSmall': {
32
+ height: responsiveVal(8, 12),
33
+ width: responsiveVal(8, 12),
34
+ marginTop: responsiveVal(2, 4),
35
+ },
36
+ },
37
+ ]}
38
38
  />
39
- <span>{size !== 'small' && store_label}</span>
40
- </div>
39
+ {size !== 'small' && (
40
+ <Box component='span' className={classes.label}>
41
+ {store_label}
42
+ </Box>
43
+ )}
44
+ </Box>
41
45
  )
42
46
  }
@@ -1,49 +1,52 @@
1
1
  import { Image } from '@graphcommerce/image'
2
- import { UseStyles, responsiveVal } from '@graphcommerce/next-ui'
3
- import { makeStyles, Theme } from '@material-ui/core'
4
- import clsx from 'clsx'
5
- import React from 'react'
2
+ import { responsiveVal, extendableComponent } from '@graphcommerce/next-ui'
3
+ import { Box, SxProps, Theme } from '@mui/material'
6
4
  import { ImageSwatchDataFragment } from './ImageSwatchData.gql'
7
5
  import { SwatchDataProps } from '.'
8
6
 
9
- export const useStyles = makeStyles(
10
- (theme: Theme) => ({
11
- root: {
12
- height: responsiveVal(40, 80),
13
- width: responsiveVal(40, 80),
14
- border: `3px solid ${theme.palette.divider}`,
15
- boxSizing: 'border-box',
16
- borderRadius: '50%',
17
- objectFit: 'cover',
18
- },
19
- sizeSmall: {
20
- width: 20,
21
- height: 20,
22
- },
23
- }),
24
- { name: 'ImageSwatchData' },
25
- )
26
- type ImageSwatchDataProps = ImageSwatchDataFragment & SwatchDataProps & UseStyles<typeof useStyles>
7
+ type ImageSwatchDataProps = ImageSwatchDataFragment & SwatchDataProps & { sx?: SxProps<Theme> }
8
+
9
+ type OwnerState = Pick<SwatchDataProps, 'size'>
10
+ const name = 'ColorSwatchData' as const
11
+ const parts = ['root', 'image', 'label'] as const
12
+ const { withState } = extendableComponent<OwnerState, typeof name, typeof parts>(name, parts)
13
+
14
+ export function ImageSwatchData(props: ImageSwatchDataProps) {
15
+ const { value, thumbnail, store_label, size, sx = [] } = props
16
+
17
+ const classes = withState({ size })
27
18
 
28
- export default function ImageSwatchData(props: ImageSwatchDataProps) {
29
- const classes = useStyles(props)
30
- const { value, thumbnail, store_label, size } = props
31
19
  return (
32
- <>
20
+ <Box
21
+ sx={[
22
+ (theme) => ({
23
+ '& .image': {
24
+ height: responsiveVal(40, 80),
25
+ width: responsiveVal(40, 80),
26
+ border: `3px solid ${theme.palette.divider}`,
27
+ boxSizing: 'border-box',
28
+ borderRadius: '50%',
29
+ objectFit: 'cover',
30
+ },
31
+ '&.small .image': {
32
+ width: 20,
33
+ height: 20,
34
+ },
35
+ }),
36
+ ...(Array.isArray(sx) ? sx : [sx]),
37
+ ]}
38
+ >
33
39
  {thumbnail ? (
34
40
  <Image
35
41
  src={thumbnail}
36
- width={classes.sizeSmall ? 20 : 40}
37
- height={classes.sizeSmall ? 20 : 40}
42
+ width={size === 'small' ? 20 : 40}
43
+ height={size === 'small' ? 20 : 40}
38
44
  alt={value ?? ''}
39
- className={clsx({
40
- [classes.root]: true,
41
- [classes.sizeSmall]: size === 'small',
42
- })}
45
+ className={classes.image}
43
46
  />
44
47
  ) : (
45
- <div>{store_label}</div>
48
+ <div className={classes.image}>{store_label}</div>
46
49
  )}
47
- </>
50
+ </Box>
48
51
  )
49
52
  }