@graphcommerce/ecommerce-ui 9.0.4-canary.0 → 9.0.4-canary.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.
package/CHANGELOG.md CHANGED
@@ -1,18 +1,12 @@
1
1
  # @graphcommerce/ecommerce-ui
2
2
 
3
- ## 9.0.4-canary.0
4
-
5
- ## 9.0.3
6
-
7
- ## 9.0.3-canary.0
3
+ ## 9.0.4-canary.1
8
4
 
9
- ## 9.0.2
10
-
11
- ## 9.0.2-canary.0
5
+ ### Patch Changes
12
6
 
13
- ## 9.0.1
7
+ - [#2470](https://github.com/graphcommerce-org/graphcommerce/pull/2470) [`910e6aa`](https://github.com/graphcommerce-org/graphcommerce/commit/910e6aab024a925bb042cf17968b2ab826f97d88) - Refactor the FormComponents for better TypeScript checking performance. ([@paales](https://github.com/paales))
14
8
 
15
- ## 9.0.1-canary.1
9
+ ## 9.0.4-canary.0
16
10
 
17
11
  ## 9.0.0
18
12
 
@@ -1,5 +1,5 @@
1
1
  import type { ButtonProps } from '@graphcommerce/next-ui'
2
- import { Button, IconSvg, iconChevronRight } from '@graphcommerce/next-ui'
2
+ import { Button, iconChevronRight, IconSvg } from '@graphcommerce/next-ui'
3
3
  import type { ComposedSubmitRenderComponentProps } from '@graphcommerce/react-hook-form'
4
4
  import { forwardRef } from 'react'
5
5
 
@@ -1,5 +1,5 @@
1
1
  import type { LinkOrButtonProps } from '@graphcommerce/next-ui'
2
- import { IconSvg, LinkOrButton, iconChevronRight } from '@graphcommerce/next-ui'
2
+ import { iconChevronRight, IconSvg, LinkOrButton } from '@graphcommerce/next-ui'
3
3
  import type { ComposedSubmitRenderComponentProps } from '@graphcommerce/react-hook-form'
4
4
  import { forwardRef } from 'react'
5
5
 
@@ -1,4 +1,4 @@
1
- import type { FieldValues, UseControllerProps } from '@graphcommerce/react-hook-form'
1
+ import type { FieldValues } from '@graphcommerce/react-hook-form'
2
2
  import { useController } from '@graphcommerce/react-hook-form'
3
3
  import { i18n } from '@lingui/core'
4
4
  import type { CheckboxProps } from '@mui/material'
@@ -11,27 +11,33 @@ import {
11
11
  FormLabel,
12
12
  useTheme,
13
13
  } from '@mui/material'
14
+ import type { BaseControllerProps } from './types'
14
15
 
15
- export type CheckboxButtonGroupProps<T extends FieldValues> = {
16
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
17
- options: { id: string | number; label: string }[] | any[]
16
+ type OptionBase = { id: string | number; label: string | number }
17
+
18
+ type AdditionalProps<TOption extends OptionBase = OptionBase> = {
19
+ options: TOption[]
18
20
  helperText?: string
19
21
  required?: boolean
20
22
  label?: string
21
- labelKey?: string
22
- valueKey?: string
23
- // eslint-disable-next-line @typescript-eslint/ban-types
24
- onChange?: Function
25
- returnObject?: boolean
23
+ labelKey?: keyof TOption
24
+ valueKey?: keyof TOption
25
+ onChange?: (value: (string | number)[]) => void
26
26
  disabled?: boolean
27
27
  row?: boolean
28
28
  checkboxColor?: CheckboxProps['color']
29
- } & UseControllerProps<T>
29
+ }
30
30
 
31
- /** @public */
32
- export function CheckboxButtonGroup<TFieldValues extends FieldValues>(
31
+ export type CheckboxButtonGroupProps<
32
+ TFieldValues extends FieldValues = FieldValues,
33
+ TOption extends OptionBase = OptionBase,
34
+ > = BaseControllerProps<TFieldValues> & AdditionalProps<TOption>
35
+
36
+ type CheckboxButtonGroupComponent = <TFieldValues extends FieldValues>(
33
37
  props: CheckboxButtonGroupProps<TFieldValues>,
34
- ): JSX.Element {
38
+ ) => React.ReactNode
39
+
40
+ function CheckboxButtonGroupBase(props: CheckboxButtonGroupProps) {
35
41
  const {
36
42
  helperText,
37
43
  options,
@@ -40,7 +46,6 @@ export function CheckboxButtonGroup<TFieldValues extends FieldValues>(
40
46
  required,
41
47
  labelKey = 'label',
42
48
  valueKey = 'id',
43
- returnObject,
44
49
  disabled,
45
50
  row,
46
51
  control,
@@ -65,57 +70,37 @@ export function CheckboxButtonGroup<TFieldValues extends FieldValues>(
65
70
 
66
71
  const parsedHelperText = error ? error.message : helperText
67
72
 
68
- const handleChange = (index: number | string) => {
69
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
70
- const newArray: (string | number)[] | any[] = [...value]
71
- const exists =
72
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
73
- value.findIndex((i: any) => (returnObject ? i[valueKey] === index : i === index)) === -1
74
- if (exists) {
75
- // eslint-disable-next-line @typescript-eslint/no-unsafe-argument
76
- newArray.push(returnObject ? options.find((i) => i[valueKey] === index) : index)
73
+ const handleChange = (optionKey: string | number) => {
74
+ const currentValue = value as (string | number)[]
75
+ const newArray = [...currentValue]
76
+
77
+ const index = currentValue.indexOf(optionKey)
78
+ if (index === -1) {
79
+ newArray.push(optionKey)
77
80
  } else {
78
- newArray.splice(
79
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
80
- value.findIndex((i: any) => (returnObject ? i[valueKey] === index : i === index)),
81
- 1,
82
- )
81
+ newArray.splice(index, 1)
83
82
  }
84
- // setValue(name, newArray, { shouldValidate: true })
83
+
85
84
  onChange(newArray)
86
- if (typeof rest.onChange === 'function') {
87
- rest.onChange(newArray)
88
- }
85
+ rest.onChange?.(newArray)
89
86
  }
90
87
 
91
88
  return (
92
89
  <FormControl error={invalid} required={required}>
93
90
  {label && <FormLabel error={invalid}>{label}</FormLabel>}
94
91
  <FormGroup row={row}>
95
- {options.map((option: any) => {
92
+ {options.map((option) => {
96
93
  const optionKey = option[valueKey]
97
- if (!optionKey) {
98
- console.error(
99
- `CheckboxButtonGroup: valueKey ${valueKey} does not exist on option`,
100
- option,
101
- )
102
- }
103
- const isChecked =
104
- value.findIndex((item) =>
105
- returnObject ? item[valueKey] === optionKey : item === optionKey,
106
- ) !== -1
94
+ const isChecked = (value as (string | number)[]).includes(optionKey)
107
95
  return (
108
96
  <FormControlLabel
109
97
  control={
110
98
  <Checkbox
111
- sx={{
112
- color: invalid ? theme.palette.error.main : undefined,
113
- }}
99
+ sx={{ color: invalid ? theme.palette.error.main : undefined }}
114
100
  color={checkboxColor || 'primary'}
115
101
  value={optionKey}
116
102
  checked={isChecked}
117
103
  disabled={disabled}
118
- // eslint-disable-next-line @typescript-eslint/no-unsafe-argument
119
104
  onChange={() => handleChange(optionKey)}
120
105
  />
121
106
  }
@@ -129,3 +114,6 @@ export function CheckboxButtonGroup<TFieldValues extends FieldValues>(
129
114
  </FormControl>
130
115
  )
131
116
  }
117
+
118
+ /** @public */
119
+ export const CheckboxButtonGroup = CheckboxButtonGroupBase as CheckboxButtonGroupComponent
@@ -1,4 +1,4 @@
1
- import type { ControllerProps, FieldValues } from '@graphcommerce/react-hook-form'
1
+ import type { FieldValues } from '@graphcommerce/react-hook-form'
2
2
  import { useController } from '@graphcommerce/react-hook-form'
3
3
  import { i18n } from '@lingui/core'
4
4
  import type {
@@ -16,18 +16,23 @@ import {
16
16
  FormHelperText,
17
17
  useForkRef,
18
18
  } from '@mui/material'
19
+ import type { FieldElementProps } from './types'
19
20
 
20
- export type CheckboxElementProps<T extends FieldValues> = Omit<CheckboxProps, 'name'> & {
21
+ type AdditionalProps = {
21
22
  label?: FormControlLabelProps['label']
22
23
  helperText?: string
23
24
  sx?: SxProps<Theme>
24
25
  formControl?: Omit<FormControlProps<'div'>, 'required' | 'error'>
25
- } & Omit<ControllerProps<T>, 'render'>
26
+ }
26
27
 
27
- /** @public */
28
- export function CheckboxElement<TFieldValues extends FieldValues>(
28
+ export type CheckboxElementProps<TFieldValues extends FieldValues = FieldValues> =
29
+ FieldElementProps<TFieldValues, CheckboxProps> & AdditionalProps
30
+
31
+ type CheckboxElementComponent = <TFieldValues extends FieldValues>(
29
32
  props: CheckboxElementProps<TFieldValues>,
30
- ): JSX.Element {
33
+ ) => React.ReactNode
34
+
35
+ function CheckboxElementBase(props: CheckboxElementProps): JSX.Element {
31
36
  const {
32
37
  name,
33
38
  rules = {},
@@ -48,7 +53,7 @@ export function CheckboxElement<TFieldValues extends FieldValues>(
48
53
  }
49
54
 
50
55
  const {
51
- field: { value, onChange, ref, ...field },
56
+ field: { value, onChange, ref, onBlur },
52
57
  fieldState: { invalid, error },
53
58
  } = useController({
54
59
  name,
@@ -69,7 +74,9 @@ export function CheckboxElement<TFieldValues extends FieldValues>(
69
74
  control={
70
75
  <Checkbox
71
76
  {...rest}
72
- {...field}
77
+ onBlur={onBlur}
78
+ disabled={disabled}
79
+ name={name}
73
80
  inputRef={useForkRef(ref, rest.inputRef)}
74
81
  color={rest.color || 'primary'}
75
82
  sx={{
@@ -87,3 +94,6 @@ export function CheckboxElement<TFieldValues extends FieldValues>(
87
94
  </FormControl>
88
95
  )
89
96
  }
97
+
98
+ /** @public */
99
+ export const CheckboxElement = CheckboxElementBase as CheckboxElementComponent
@@ -1,24 +1,21 @@
1
- /* eslint-disable no-nested-ternary */
2
- import { FieldValues, UseControllerProps, useController } from '@graphcommerce/react-hook-form'
1
+ import type { FieldValues } from '@graphcommerce/react-hook-form'
2
+ import { useController } from '@graphcommerce/react-hook-form'
3
3
  import { i18n } from '@lingui/core'
4
- import { InputBase, InputBaseProps } from '@mui/material'
4
+ import type { InputBaseProps } from '@mui/material'
5
+ import { InputBase } from '@mui/material'
5
6
  import React from 'react'
7
+ import type { FieldElementProps } from './types'
6
8
 
7
- export type InputBaseElementProps<T extends FieldValues = FieldValues> = Omit<
8
- InputBaseProps,
9
- 'name' | 'defaultValue'
10
- > & {
11
- showValid?: boolean
12
- } & UseControllerProps<T>
9
+ export type InputBaseElementProps<TFieldValues extends FieldValues = FieldValues> =
10
+ FieldElementProps<TFieldValues, InputBaseProps>
13
11
 
14
12
  type InputBaseElementComponent = <TFieldValues extends FieldValues>(
15
13
  props: InputBaseElementProps<TFieldValues> & { ref?: React.Ref<HTMLInputElement> },
16
- ) => JSX.Element
14
+ ) => React.ReactNode
17
15
 
18
- export const InputBaseElement = React.forwardRef<
19
- HTMLInputElement,
20
- InputBaseElementProps<FieldValues>
21
- >((props: InputBaseElementProps<FieldValues>, ref: React.Ref<HTMLInputElement>): JSX.Element => {
16
+ function InputBaseElementBase(
17
+ props: InputBaseElementProps & { ref?: React.Ref<HTMLInputElement> },
18
+ ): JSX.Element {
22
19
  const {
23
20
  type,
24
21
  required,
@@ -27,19 +24,26 @@ export const InputBaseElement = React.forwardRef<
27
24
  defaultValue,
28
25
  rules = {},
29
26
  shouldUnregister,
30
- showValid,
31
27
  disabled,
28
+ ref,
32
29
  ...rest
33
30
  } = props
34
31
 
35
- if (required && !rules?.required) {
32
+ if (required && !rules.required) {
36
33
  rules.required = i18n._(/* i18n */ 'This field is required')
37
34
  }
38
35
 
39
36
  const {
40
37
  field,
41
38
  fieldState: { error },
42
- } = useController({ name, control, rules, defaultValue, shouldUnregister, disabled })
39
+ } = useController({
40
+ name,
41
+ control,
42
+ rules,
43
+ defaultValue,
44
+ shouldUnregister,
45
+ disabled,
46
+ })
43
47
 
44
48
  return (
45
49
  <InputBase
@@ -51,4 +55,9 @@ export const InputBaseElement = React.forwardRef<
51
55
  error={Boolean(error) || rest.error}
52
56
  />
53
57
  )
54
- }) as InputBaseElementComponent
58
+ }
59
+
60
+ /** @public */
61
+ export const InputBaseElement = React.forwardRef<HTMLInputElement, InputBaseElementProps>(
62
+ (props, ref) => InputBaseElementBase({ ...props, ref }),
63
+ ) as InputBaseElementComponent
@@ -5,20 +5,25 @@ import {
5
5
  IconSvg,
6
6
  responsiveVal,
7
7
  } from '@graphcommerce/next-ui'
8
- import type { ControllerProps, FieldValues } from '@graphcommerce/react-hook-form'
9
- import { Controller, useController } from '@graphcommerce/react-hook-form'
8
+ import type { FieldValues } from '@graphcommerce/react-hook-form'
9
+ import { useController } from '@graphcommerce/react-hook-form'
10
10
  import { i18n } from '@lingui/core'
11
11
  import type { IconButtonProps, SxProps, TextFieldProps, Theme } from '@mui/material'
12
12
  import { Fab, TextField, useForkRef } from '@mui/material'
13
+ import type { FieldElementProps } from './types'
13
14
 
14
- export type NumberFieldElementProps<T extends FieldValues = FieldValues> = Omit<
15
- TextFieldProps,
16
- 'type' | 'defaultValue'
17
- > & {
15
+ type AdditionalProps = {
18
16
  DownProps?: IconButtonProps
19
17
  UpProps?: IconButtonProps
20
18
  sx?: SxProps<Theme>
21
- } & Omit<ControllerProps<T>, 'render'>
19
+ }
20
+
21
+ export type NumberFieldElementProps<TFieldValues extends FieldValues = FieldValues> =
22
+ FieldElementProps<TFieldValues, Omit<TextFieldProps, 'type'>> & AdditionalProps
23
+
24
+ type NumberFieldElementComponent = <TFieldValues extends FieldValues>(
25
+ props: NumberFieldElementProps<TFieldValues>,
26
+ ) => React.ReactNode
22
27
 
23
28
  type OwnerState = { size?: 'small' | 'medium' }
24
29
  const componentName = 'TextInputNumber'
@@ -29,7 +34,7 @@ const { withState } = extendableComponent<OwnerState, typeof componentName, type
29
34
  )
30
35
 
31
36
  /** @public */
32
- export function NumberFieldElement<T extends FieldValues>(props: NumberFieldElementProps<T>) {
37
+ function NumberFieldElementBase(props: NumberFieldElementProps) {
33
38
  const {
34
39
  DownProps = {},
35
40
  UpProps = {},
@@ -182,3 +187,5 @@ export function NumberFieldElement<T extends FieldValues>(props: NumberFieldElem
182
187
  />
183
188
  )
184
189
  }
190
+
191
+ export const NumberFieldElement = NumberFieldElementBase as NumberFieldElementComponent
@@ -1,4 +1,4 @@
1
- import type { FieldValues, UseControllerProps } from '@graphcommerce/react-hook-form'
1
+ import type { FieldValues } from '@graphcommerce/react-hook-form'
2
2
  import { useController } from '@graphcommerce/react-hook-form'
3
3
  import { i18n } from '@lingui/core'
4
4
  import {
@@ -11,27 +11,33 @@ import {
11
11
  useTheme,
12
12
  } from '@mui/material'
13
13
  import type { ChangeEvent } from 'react'
14
+ import type { BaseControllerProps } from './types'
14
15
 
15
- export type RadioButtonGroupProps<T extends FieldValues> = {
16
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
17
- options: { label: string; id: string | number }[] | any[]
16
+ type OptionBase = { id: string | number; label: string | number }
17
+
18
+ type AdditionalProps<TOption extends OptionBase = OptionBase> = {
19
+ options: TOption[]
18
20
  helperText?: string
19
21
  required?: boolean
20
22
  label?: string
21
- labelKey?: string
22
- valueKey?: string
23
- type?: 'number' | 'string'
24
- emptyOptionLabel?: 'string'
25
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
26
- onChange?: (value: any) => void
27
- returnObject?: boolean
23
+ labelKey?: keyof TOption
24
+ valueKey?: keyof TOption
25
+ onChange?: (value: string | number) => void
26
+ disabled?: boolean
28
27
  row?: boolean
29
- } & UseControllerProps<T>
28
+ emptyOptionLabel?: string
29
+ }
30
30
 
31
- /** @public */
32
- export function RadioButtonGroup<TFieldValues extends FieldValues>(
31
+ export type RadioButtonGroupProps<
32
+ TFieldValues extends FieldValues = FieldValues,
33
+ TOption extends OptionBase = OptionBase,
34
+ > = BaseControllerProps<TFieldValues> & AdditionalProps<TOption>
35
+
36
+ type RadioButtonGroupComponent = <TFieldValues extends FieldValues>(
33
37
  props: RadioButtonGroupProps<TFieldValues>,
34
- ): JSX.Element {
38
+ ) => React.ReactNode
39
+
40
+ function RadioButtonGroupBase(props: RadioButtonGroupProps): JSX.Element {
35
41
  const {
36
42
  helperText,
37
43
  options,
@@ -41,7 +47,6 @@ export function RadioButtonGroup<TFieldValues extends FieldValues>(
41
47
  valueKey = 'id',
42
48
  required,
43
49
  emptyOptionLabel,
44
- returnObject,
45
50
  row,
46
51
  control,
47
52
  defaultValue,
@@ -65,26 +70,16 @@ export function RadioButtonGroup<TFieldValues extends FieldValues>(
65
70
 
66
71
  const parsedHelperText = error ? error.message : helperText
67
72
 
68
- const onRadioChange = (event: ChangeEvent<HTMLInputElement>) => {
69
- const radioValue = (event.target as HTMLInputElement).value
70
- const returnValue = returnObject
71
- ? options.find((items) => items[valueKey] === radioValue)
72
- : radioValue
73
- // setValue(name, returnValue, { shouldValidate: true })
74
- onChange(returnValue)
75
- if (typeof rest.onChange === 'function') {
76
- rest.onChange(returnValue)
77
- }
73
+ const handleChange = (event: ChangeEvent<HTMLInputElement>) => {
74
+ const radioValue = event.target.value
75
+ onChange(radioValue)
76
+ rest.onChange?.(radioValue)
78
77
  }
79
78
 
80
79
  return (
81
- <FormControl error={invalid}>
82
- {label && (
83
- <FormLabel required={required} error={invalid}>
84
- {label}
85
- </FormLabel>
86
- )}
87
- <RadioGroup onChange={onRadioChange} name={name} row={row} value={value || ''}>
80
+ <FormControl error={invalid} required={required}>
81
+ {label && <FormLabel error={invalid}>{label}</FormLabel>}
82
+ <RadioGroup onChange={handleChange} name={name} row={row} value={value ?? ''}>
88
83
  {emptyOptionLabel && (
89
84
  <FormControlLabel
90
85
  control={
@@ -101,15 +96,6 @@ export function RadioButtonGroup<TFieldValues extends FieldValues>(
101
96
  )}
102
97
  {options.map((option) => {
103
98
  const optionKey = option[valueKey]
104
- if (!optionKey) {
105
- console.error(
106
- `CheckboxButtonGroup: valueKey ${valueKey} does not exist on option`,
107
- option,
108
- )
109
- }
110
- const isChecked = !!(
111
- value && (returnObject ? value[valueKey] === optionKey : value === optionKey)
112
- )
113
99
  return (
114
100
  <FormControlLabel
115
101
  control={
@@ -117,7 +103,7 @@ export function RadioButtonGroup<TFieldValues extends FieldValues>(
117
103
  sx={{
118
104
  color: invalid ? theme.palette.error.main : undefined,
119
105
  }}
120
- checked={isChecked}
106
+ checked={value === optionKey}
121
107
  />
122
108
  }
123
109
  value={optionKey}
@@ -131,3 +117,6 @@ export function RadioButtonGroup<TFieldValues extends FieldValues>(
131
117
  </FormControl>
132
118
  )
133
119
  }
120
+
121
+ /** @public */
122
+ export const RadioButtonGroup = RadioButtonGroupBase as RadioButtonGroupComponent
@@ -1,87 +1,31 @@
1
- import { InputCheckmark } from '@graphcommerce/next-ui'
2
- import type { ControllerProps, FieldValues } from '@graphcommerce/react-hook-form'
3
- import { useController } from '@graphcommerce/react-hook-form'
4
- import { i18n } from '@lingui/core'
1
+ import type { FieldValues } from '@graphcommerce/react-hook-form'
5
2
  import type { TextFieldProps } from '@mui/material'
6
- import { MenuItem, TextField, useForkRef } from '@mui/material'
3
+ import { MenuItem } from '@mui/material'
4
+ import { TextFieldElement, type TextFieldElementProps } from './TextFieldElement'
7
5
 
8
6
  type OptionBase = { id: string | number; label: string | number }
9
7
 
10
- export type SelectElementProps<T extends FieldValues, O extends OptionBase> = Omit<
11
- TextFieldProps,
12
- 'name' | 'type' | 'onChange' | 'defaultValue'
13
- > & {
14
- options?: O[]
15
- type?: 'string' | 'number'
16
- onChange?: (value: string | number) => void
17
- showValid?: boolean
18
- } & Omit<ControllerProps<T>, 'render'>
8
+ type AdditionalProps<O extends OptionBase = OptionBase> = { options: O[] }
19
9
 
20
- /** @public */
21
- export function SelectElement<TFieldValues extends FieldValues, O extends OptionBase>({
22
- name,
23
- required,
24
- options = [],
25
- type,
26
- control,
27
- defaultValue,
28
- rules = {},
29
- showValid,
30
- disabled,
31
- shouldUnregister,
32
- ...rest
33
- }: SelectElementProps<TFieldValues, O>): JSX.Element {
34
- const isNativeSelect = !!rest.SelectProps?.native
35
- const ChildComponent = isNativeSelect ? 'option' : MenuItem
10
+ export type SelectElementProps<
11
+ TFieldValues extends FieldValues = FieldValues,
12
+ TOption extends OptionBase = OptionBase,
13
+ > = Omit<TextFieldElementProps<TFieldValues>, 'select'> & AdditionalProps<TOption>
36
14
 
37
- if (required && !rules.required) {
38
- rules.required = i18n._(/* i18n */ 'This field is required')
39
- }
15
+ type SelectElementComponent = <TFieldValues extends FieldValues>(
16
+ props: SelectElementProps<TFieldValues>,
17
+ ) => React.ReactNode
40
18
 
41
- const {
42
- field: { onChange, value, ref, ...field },
43
- fieldState: { invalid, error },
44
- } = useController({
45
- name,
46
- rules,
47
- control,
48
- defaultValue,
49
- disabled,
50
- shouldUnregister,
51
- })
19
+ /** @public */
20
+ function SelectElementBase(props: SelectElementProps): JSX.Element {
21
+ const { options } = props as AdditionalProps
22
+ const { SelectProps } = props as TextFieldProps
52
23
 
53
- // handle shrink on number input fields
54
- if (type === 'number' && typeof value !== 'undefined') {
55
- rest.InputLabelProps = rest.InputLabelProps || {}
56
- rest.InputLabelProps.shrink = true
57
- }
24
+ const isNativeSelect = !!SelectProps?.native
25
+ const ChildComponent = isNativeSelect ? 'option' : MenuItem
58
26
 
59
27
  return (
60
- <TextField
61
- {...rest}
62
- value={value ?? ''}
63
- {...field}
64
- inputRef={useForkRef(ref, rest.inputRef)}
65
- onChange={(event) => {
66
- let item: number | string | O | undefined = event.target.value
67
- if (type === 'number') item = Number(item)
68
- rest.onChange?.(item)
69
- onChange(item)
70
- }}
71
- select
72
- required={required}
73
- error={invalid}
74
- helperText={error ? error.message : rest.helperText}
75
- InputProps={{
76
- ...rest.InputProps,
77
- endAdornment:
78
- showValid && value && !error ? (
79
- <InputCheckmark show={!error} />
80
- ) : (
81
- rest.InputProps?.endAdornment
82
- ),
83
- }}
84
- >
28
+ <TextFieldElement {...props} select>
85
29
  {/* eslint-disable-next-line jsx-a11y/control-has-associated-label */}
86
30
  {isNativeSelect && <option />}
87
31
  {options.map((item) => (
@@ -89,6 +33,8 @@ export function SelectElement<TFieldValues extends FieldValues, O extends Option
89
33
  {item.label}
90
34
  </ChildComponent>
91
35
  ))}
92
- </TextField>
36
+ </TextFieldElement>
93
37
  )
94
38
  }
39
+
40
+ export const SelectElement = SelectElementBase as SelectElementComponent
@@ -1,28 +1,41 @@
1
- import type { ControllerProps, FieldValues } from '@graphcommerce/react-hook-form'
1
+ import type { FieldValues } from '@graphcommerce/react-hook-form'
2
2
  import { useController } from '@graphcommerce/react-hook-form'
3
3
  import { i18n } from '@lingui/core'
4
4
  import type { FormControlProps, SliderProps } from '@mui/material'
5
5
  import { FormControl, FormHelperText, FormLabel, Slider } from '@mui/material'
6
+ import React from 'react'
7
+ import type { FieldElementProps } from './types'
6
8
 
7
- export type SliderElementProps<T extends FieldValues> = Omit<SliderProps, 'control'> & {
9
+ type AdditionalProps = {
8
10
  label?: string
9
11
  required?: boolean
10
12
  formControlProps?: FormControlProps
11
- } & Omit<ControllerProps<T>, 'render'>
13
+ }
14
+
15
+ export type SliderElementProps<TFieldValues extends FieldValues = FieldValues> = FieldElementProps<
16
+ TFieldValues,
17
+ Omit<SliderProps, 'name'>
18
+ > &
19
+ AdditionalProps
20
+
21
+ type SliderElementComponent = <TFieldValues extends FieldValues>(
22
+ props: SliderElementProps<TFieldValues>,
23
+ ) => React.ReactNode
24
+
25
+ function SliderElementBase(props: SliderElementProps): JSX.Element {
26
+ const {
27
+ name,
28
+ control,
29
+ label,
30
+ rules = {},
31
+ required,
32
+ formControlProps,
33
+ defaultValue,
34
+ disabled,
35
+ shouldUnregister,
36
+ ...other
37
+ } = props
12
38
 
13
- /** @public */
14
- export function SliderElement<TFieldValues extends FieldValues>({
15
- name,
16
- control,
17
- label,
18
- rules = {},
19
- required,
20
- formControlProps,
21
- defaultValue,
22
- disabled,
23
- shouldUnregister,
24
- ...other
25
- }: SliderElementProps<TFieldValues>) {
26
39
  if (required && !rules.required) {
27
40
  rules.required = i18n._(/* i18n */ 'This field is required')
28
41
  }
@@ -53,3 +66,6 @@ export function SliderElement<TFieldValues extends FieldValues>({
53
66
  </FormControl>
54
67
  )
55
68
  }
69
+
70
+ /** @public */
71
+ export const SliderElement = SliderElementBase as SliderElementComponent
@@ -1,22 +1,25 @@
1
- import type { ControllerProps, FieldValues } from '@graphcommerce/react-hook-form'
1
+ import type { FieldValues } from '@graphcommerce/react-hook-form'
2
2
  import { useController } from '@graphcommerce/react-hook-form'
3
- import type { FormControlLabelProps } from '@mui/material'
3
+ import type { FormControlLabelProps, SwitchProps } from '@mui/material'
4
4
  import { FormControlLabel, Switch } from '@mui/material'
5
+ import React from 'react'
6
+ import type { FieldElementProps } from './types'
5
7
 
6
- type IProps = Omit<FormControlLabelProps, 'control'>
8
+ type AdditionalProps = Omit<FormControlLabelProps, 'control'>
7
9
 
8
- export type SwitchElementProps<T extends FieldValues> = IProps & Omit<ControllerProps<T>, 'render'>
10
+ export type SwitchElementProps<TFieldValues extends FieldValues = FieldValues> = FieldElementProps<
11
+ TFieldValues,
12
+ SwitchProps
13
+ > &
14
+ AdditionalProps
15
+
16
+ type SwitchElementComponent = <TFieldValues extends FieldValues>(
17
+ props: SwitchElementProps<TFieldValues>,
18
+ ) => React.ReactNode
19
+
20
+ function SwitchElementBase(props: SwitchElementProps): JSX.Element {
21
+ const { name, control, defaultValue, disabled, shouldUnregister, rules, ...other } = props
9
22
 
10
- /** @public */
11
- export function SwitchElement<TFieldValues extends FieldValues>({
12
- name,
13
- control,
14
- defaultValue,
15
- disabled,
16
- shouldUnregister,
17
- rules,
18
- ...other
19
- }: SwitchElementProps<TFieldValues>) {
20
23
  const { field } = useController({
21
24
  name,
22
25
  control,
@@ -28,3 +31,6 @@ export function SwitchElement<TFieldValues extends FieldValues>({
28
31
 
29
32
  return <FormControlLabel control={<Switch {...field} checked={!!field.value} />} {...other} />
30
33
  }
34
+
35
+ /** @public */
36
+ export const SwitchElement = SwitchElementBase as SwitchElementComponent
@@ -1,32 +1,35 @@
1
1
  /* eslint-disable no-nested-ternary */
2
2
  import { InputCheckmark } from '@graphcommerce/next-ui'
3
- import type { FieldValues, UseControllerProps } from '@graphcommerce/react-hook-form'
3
+ import type { FieldValues } from '@graphcommerce/react-hook-form'
4
4
  import { emailPattern, useController } from '@graphcommerce/react-hook-form'
5
5
  import { i18n } from '@lingui/core'
6
6
  import type { TextFieldProps } from '@mui/material'
7
7
  import { TextField, useForkRef } from '@mui/material'
8
8
  import React, { useState } from 'react'
9
+ import type { BaseControllerProps, FieldElementProps } from './types'
9
10
 
10
- export type TextFieldElementProps<T extends FieldValues = FieldValues> = Omit<
11
- TextFieldProps,
12
- 'name' | 'defaultValue'
13
- > & {
14
- showValid?: boolean
15
- } & UseControllerProps<T>
11
+ type ShowValidProps = { showValid?: boolean }
12
+
13
+ export type TextFieldElementProps<TFieldValues extends FieldValues = FieldValues> =
14
+ FieldElementProps<TFieldValues, TextFieldProps> & ShowValidProps
15
+
16
+ type TextFieldElementComponent = <TFieldValues extends FieldValues>(
17
+ props: TextFieldElementProps<TFieldValues>,
18
+ ) => React.ReactNode
16
19
 
17
20
  /** @public */
18
- export function TextFieldElement<TFieldValues extends FieldValues>({
19
- type,
20
- required,
21
- name,
22
- control,
23
- defaultValue,
24
- rules = {},
25
- shouldUnregister,
26
- showValid,
27
- disabled,
28
- ...rest
29
- }: TextFieldElementProps<TFieldValues>): JSX.Element {
21
+ function TextFieldElementBase(props: TextFieldElementProps): JSX.Element {
22
+ const {
23
+ name,
24
+ control,
25
+ defaultValue,
26
+ rules = {},
27
+ shouldUnregister,
28
+ disabled,
29
+ } = props as BaseControllerProps
30
+ const { showValid } = props as ShowValidProps
31
+ const { type, required, ...rest } = props as TextFieldProps
32
+
30
33
  if (required && !rules.required) {
31
34
  rules.required = i18n._(/* i18n */ 'This field is required')
32
35
  }
@@ -39,7 +42,7 @@ export function TextFieldElement<TFieldValues extends FieldValues>({
39
42
  }
40
43
 
41
44
  const {
42
- field: { onChange, ref, value = '', ...field },
45
+ field: { onChange, ref, value = '', onBlur },
43
46
  fieldState: { error },
44
47
  } = useController({ name, control, rules, defaultValue, shouldUnregister, disabled })
45
48
 
@@ -58,7 +61,9 @@ export function TextFieldElement<TFieldValues extends FieldValues>({
58
61
  return (
59
62
  <TextField
60
63
  {...rest}
61
- {...field}
64
+ onBlur={onBlur}
65
+ name={name}
66
+ disabled={disabled}
62
67
  value={value}
63
68
  inputProps={{ ...rest.inputProps, onAnimationStart }}
64
69
  onChange={(ev) => {
@@ -83,3 +88,5 @@ export function TextFieldElement<TFieldValues extends FieldValues>({
83
88
  />
84
89
  )
85
90
  }
91
+
92
+ export const TextFieldElement = TextFieldElementBase as TextFieldElementComponent
@@ -2,7 +2,6 @@ export * from './ActionCardListForm'
2
2
  export * from './CheckboxButtonGroup'
3
3
  export * from './CheckboxElement'
4
4
  export * from './EmailElement'
5
- export * from './MultiSelectElement'
6
5
  export * from './NumberFieldElement'
7
6
  export * from './PasswordElement'
8
7
  export * from './PasswordRepeatElement'
@@ -0,0 +1,26 @@
1
+ import type {
2
+ Control,
3
+ FieldPath,
4
+ FieldPathValue,
5
+ FieldValues,
6
+ RegisterOptions,
7
+ } from '@graphcommerce/react-hook-form'
8
+
9
+ export type BaseControllerProps<TFieldValues extends FieldValues = FieldValues> = {
10
+ name: FieldPath<TFieldValues>
11
+ control?: Control<TFieldValues>
12
+ rules?: Omit<
13
+ RegisterOptions<NoInfer<TFieldValues>, FieldPath<TFieldValues>>,
14
+ 'valueAsNumber' | 'valueAsDate' | 'setValueAs' | 'disabled'
15
+ >
16
+ shouldUnregister?: boolean
17
+ defaultValue?: FieldPathValue<NoInfer<TFieldValues>, FieldPath<TFieldValues>>
18
+ disabled?: boolean
19
+ }
20
+
21
+ type BaseControllerPropsKeys = keyof BaseControllerProps
22
+
23
+ export type FieldElementProps<
24
+ TFieldValues extends FieldValues,
25
+ BaseTypes = Record<string, unknown>,
26
+ > = BaseControllerProps<TFieldValues> & Omit<BaseTypes, BaseControllerPropsKeys>
@@ -1,4 +1,4 @@
1
- import { Tooltip, styled, tooltipClasses } from '@mui/material'
1
+ import { styled, Tooltip, tooltipClasses } from '@mui/material'
2
2
 
3
3
  export const LightTooltip = styled<typeof Tooltip>(({ className, ...props }) => (
4
4
  <Tooltip {...props} classes={{ popper: className }} />
@@ -1,20 +1,6 @@
1
- import type { PreviewData } from '@graphcommerce/graphql'
2
- import {
3
- iconChevronRight,
4
- iconClose,
5
- iconContrast,
6
- iconInfo,
7
- iconRefresh,
8
- IconSvg,
9
- MessageSnackbar,
10
- } from '@graphcommerce/next-ui'
11
- import { FormAutoSubmit, FormPersist, FormProvider, useForm } from '@graphcommerce/react-hook-form'
12
- import { Box, IconButton } from '@mui/material'
13
1
  import { useRouter } from 'next/router'
14
- import { TextFieldElement } from '../FormComponents'
15
- import { LightTooltip } from './LightTooltip'
16
- import { PreviewModeActions } from './PreviewModeActions'
17
- import { PreviewModeToolbar } from './PreviewModeToolbar'
2
+ import { PreviewModeDisabled } from './PreviewModeDisabled'
3
+ import { PreviewModeEnabled } from './PreviewModeEnabled'
18
4
 
19
5
  function getPreviewUrl() {
20
6
  const url = new URL(window.location.href)
@@ -23,128 +9,6 @@ function getPreviewUrl() {
23
9
  return url
24
10
  }
25
11
 
26
- function PreviewModeEnabled() {
27
- const form = useForm<{ secret: string; previewData: PreviewData }>({})
28
-
29
- const submit = form.handleSubmit((formValues) => {
30
- const url = getPreviewUrl()
31
- url.searchParams.append('action', 'update')
32
-
33
- Object.entries(formValues).forEach(([key, value]) => {
34
- url.searchParams.append(key, JSON.stringify(value))
35
- })
36
-
37
- window.location.href = url.toString()
38
- })
39
-
40
- const exitHandler = form.handleSubmit(() => {
41
- const url = getPreviewUrl()
42
- url.searchParams.append('action', 'exit')
43
-
44
- window.location.href = url.toString()
45
- })
46
-
47
- const revalidateHandler = form.handleSubmit((formValues) => {
48
- const url = getPreviewUrl()
49
- Object.entries(formValues).forEach(([key, value]) => {
50
- // eslint-disable-next-line @typescript-eslint/no-base-to-string
51
- url.searchParams.append(key, `${value}`)
52
- })
53
- url.searchParams.append('action', 'revalidate')
54
- window.location.href = url.toString()
55
- })
56
-
57
- return (
58
- <FormProvider {...form}>
59
- <MessageSnackbar
60
- variant='pill'
61
- severity='warning'
62
- disableBackdropClick
63
- disableClose
64
- open
65
- icon={iconContrast}
66
- onClose={() => {}}
67
- action={
68
- <>
69
- <PreviewModeActions />
70
- <LightTooltip title='Revalidate / Regenerate Page' placement='top'>
71
- <IconButton color='secondary' type='submit' onClick={revalidateHandler}>
72
- <IconSvg src={iconRefresh} />
73
- </IconButton>
74
- </LightTooltip>
75
- <LightTooltip title='Stop preview mode' placement='top'>
76
- <IconButton color='secondary' type='submit' onClick={exitHandler}>
77
- <IconSvg src={iconClose} />
78
- </IconButton>
79
- </LightTooltip>
80
- </>
81
- }
82
- >
83
- <Box sx={{ display: 'grid', gridAutoFlow: 'column', placeItems: 'center', gap: 4 }}>
84
- <Box sx={{ display: 'flex', placeItems: 'center' }}>
85
- Preview Mode{' '}
86
- <LightTooltip title='You are currently viewing the website in Preview Mode (caches are disabled).'>
87
- <IconButton size='small'>
88
- <IconSvg src={iconInfo} />
89
- </IconButton>
90
- </LightTooltip>
91
- </Box>
92
- <PreviewModeToolbar />
93
- </Box>
94
- </MessageSnackbar>
95
- <FormPersist form={form} name='PreviewModePreviewData' />
96
- <FormAutoSubmit control={form.control} submit={submit} />
97
- </FormProvider>
98
- )
99
- }
100
-
101
- function PreviewModeDisabled() {
102
- const form = useForm<{ secret: string }>({
103
- defaultValues: {
104
- secret:
105
- process.env.NODE_ENV === 'development'
106
- ? (import.meta.graphCommerce.previewSecret ?? '')
107
- : '',
108
- },
109
- })
110
-
111
- const submit = form.handleSubmit((formValues) => {
112
- const url = getPreviewUrl()
113
- url.searchParams.append('action', 'enable')
114
-
115
- Object.entries(formValues).forEach(([key, value]) => {
116
- url.searchParams.append(key, typeof value === 'string' ? value : JSON.stringify(value))
117
- })
118
-
119
- window.location.href = url.toString()
120
- })
121
-
122
- return (
123
- <FormProvider {...form}>
124
- <MessageSnackbar
125
- variant='pill'
126
- severity='warning'
127
- disableBackdropClick
128
- disableClose
129
- open
130
- icon={iconContrast}
131
- onClose={() => {}}
132
- action={
133
- <IconButton color='secondary' type='submit' onClick={submit}>
134
- <IconSvg src={iconChevronRight} />
135
- </IconButton>
136
- }
137
- >
138
- <Box sx={{ display: 'grid', gridAutoFlow: 'column', placeItems: 'center', gap: 4 }}>
139
- <Box sx={{ display: 'flex', placeItems: 'center' }}>Preview Mode</Box>
140
- <TextFieldElement control={form.control} name='secret' label='Secret' />
141
- </Box>
142
- </MessageSnackbar>
143
- <FormPersist form={form} name='PreviewModePreviewData' />
144
- </FormProvider>
145
- )
146
- }
147
-
148
12
  export function PreviewMode() {
149
13
  const router = useRouter()
150
14
 
@@ -0,0 +1,56 @@
1
+ import { iconChevronRight, iconContrast, IconSvg, MessageSnackbar } from '@graphcommerce/next-ui'
2
+ import { FormPersist, FormProvider, useForm } from '@graphcommerce/react-hook-form'
3
+ import { Box, IconButton } from '@mui/material'
4
+ import { TextFieldElement } from '../FormComponents'
5
+
6
+ function getPreviewUrl() {
7
+ const url = new URL(window.location.href)
8
+ url.pathname = '/api/preview'
9
+ ;[...url.searchParams.entries()].forEach(([key]) => url.searchParams.delete(key))
10
+ return url
11
+ }
12
+
13
+ const secret =
14
+ process.env.NODE_ENV === 'development' ? (import.meta.graphCommerce.previewSecret ?? '') : ''
15
+
16
+ type FormValues = { secret: string }
17
+
18
+ export function PreviewModeDisabled() {
19
+ const form = useForm<FormValues>({ defaultValues: { secret } })
20
+
21
+ const submit = form.handleSubmit((formValues) => {
22
+ const url = getPreviewUrl()
23
+ url.searchParams.append('action', 'enable')
24
+
25
+ Object.entries(formValues).forEach(([key, value]) => {
26
+ url.searchParams.append(key, typeof value === 'string' ? value : JSON.stringify(value))
27
+ })
28
+
29
+ window.location.href = url.toString()
30
+ })
31
+
32
+ return (
33
+ <FormProvider<FormValues> {...form}>
34
+ <MessageSnackbar
35
+ variant='pill'
36
+ severity='warning'
37
+ disableBackdropClick
38
+ disableClose
39
+ open
40
+ icon={iconContrast}
41
+ onClose={() => {}}
42
+ action={
43
+ <IconButton color='secondary' type='submit' onClick={submit}>
44
+ <IconSvg src={iconChevronRight} />
45
+ </IconButton>
46
+ }
47
+ >
48
+ <Box sx={{ display: 'grid', gridAutoFlow: 'column', placeItems: 'center', gap: 4 }}>
49
+ <Box sx={{ display: 'flex', placeItems: 'center' }}>Preview Mode</Box>
50
+ <TextFieldElement control={form.control} name='secret' label='Secret' />
51
+ </Box>
52
+ </MessageSnackbar>
53
+ <FormPersist form={form} name='PreviewModePreviewData' />
54
+ </FormProvider>
55
+ )
56
+ }
@@ -0,0 +1,98 @@
1
+ import type { PreviewData } from '@graphcommerce/graphql'
2
+ import {
3
+ iconClose,
4
+ iconContrast,
5
+ iconInfo,
6
+ iconRefresh,
7
+ IconSvg,
8
+ MessageSnackbar,
9
+ } from '@graphcommerce/next-ui'
10
+ import { FormAutoSubmit, FormPersist, FormProvider, useForm } from '@graphcommerce/react-hook-form'
11
+ import { Box, IconButton } from '@mui/material'
12
+ import { LightTooltip } from './LightTooltip'
13
+ import { PreviewModeActions } from './PreviewModeActions'
14
+ import { PreviewModeToolbar } from './PreviewModeToolbar'
15
+
16
+ function getPreviewUrl() {
17
+ const url = new URL(window.location.href)
18
+ url.pathname = '/api/preview'
19
+ ;[...url.searchParams.entries()].forEach(([key]) => url.searchParams.delete(key))
20
+ return url
21
+ }
22
+
23
+ type FormValues = { secret: string; previewData: PreviewData }
24
+
25
+ export function PreviewModeEnabled() {
26
+ const form = useForm<FormValues>({})
27
+
28
+ const submit = form.handleSubmit((formValues) => {
29
+ const url = getPreviewUrl()
30
+ url.searchParams.append('action', 'update')
31
+
32
+ Object.entries(formValues).forEach(([key, value]) => {
33
+ url.searchParams.append(key, JSON.stringify(value))
34
+ })
35
+
36
+ window.location.href = url.toString()
37
+ })
38
+
39
+ const exitHandler = form.handleSubmit(() => {
40
+ const url = getPreviewUrl()
41
+ url.searchParams.append('action', 'exit')
42
+
43
+ window.location.href = url.toString()
44
+ })
45
+
46
+ const revalidateHandler = form.handleSubmit((formValues) => {
47
+ const url = getPreviewUrl()
48
+ Object.entries(formValues).forEach(([key, value]) => {
49
+ // eslint-disable-next-line @typescript-eslint/no-base-to-string
50
+ url.searchParams.append(key, `${value}`)
51
+ })
52
+ url.searchParams.append('action', 'revalidate')
53
+ window.location.href = url.toString()
54
+ })
55
+
56
+ return (
57
+ <FormProvider<FormValues> {...form}>
58
+ <MessageSnackbar
59
+ variant='pill'
60
+ severity='warning'
61
+ disableBackdropClick
62
+ disableClose
63
+ open
64
+ icon={iconContrast}
65
+ onClose={() => {}}
66
+ action={
67
+ <>
68
+ <PreviewModeActions />
69
+ <LightTooltip title='Revalidate / Regenerate Page' placement='top'>
70
+ <IconButton color='secondary' type='submit' onClick={revalidateHandler}>
71
+ <IconSvg src={iconRefresh} />
72
+ </IconButton>
73
+ </LightTooltip>
74
+ <LightTooltip title='Stop preview mode' placement='top'>
75
+ <IconButton color='secondary' type='submit' onClick={exitHandler}>
76
+ <IconSvg src={iconClose} />
77
+ </IconButton>
78
+ </LightTooltip>
79
+ </>
80
+ }
81
+ >
82
+ <Box sx={{ display: 'grid', gridAutoFlow: 'column', placeItems: 'center', gap: 4 }}>
83
+ <Box sx={{ display: 'flex', placeItems: 'center' }}>
84
+ Preview Mode{' '}
85
+ <LightTooltip title='You are currently viewing the website in Preview Mode (caches are disabled).'>
86
+ <IconButton size='small'>
87
+ <IconSvg src={iconInfo} />
88
+ </IconButton>
89
+ </LightTooltip>
90
+ </Box>
91
+ <PreviewModeToolbar />
92
+ </Box>
93
+ </MessageSnackbar>
94
+ <FormPersist form={form} name='PreviewModePreviewData' />
95
+ <FormAutoSubmit control={form.control} submit={submit} />
96
+ </FormProvider>
97
+ )
98
+ }
@@ -1,5 +1,5 @@
1
1
  import type { PreviewData } from '@graphcommerce/graphql'
2
2
 
3
3
  export function previewModeDefaults(): PreviewData {
4
- return {}
4
+ return {} as PreviewData
5
5
  }
package/package.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "name": "@graphcommerce/ecommerce-ui",
3
3
  "homepage": "https://www.graphcommerce.org/",
4
4
  "repository": "github:graphcommerce-org/graphcommerce",
5
- "version": "9.0.4-canary.0",
5
+ "version": "9.0.4-canary.1",
6
6
  "sideEffects": false,
7
7
  "prettier": "@graphcommerce/prettier-config-pwa",
8
8
  "eslintConfig": {
@@ -12,12 +12,12 @@
12
12
  }
13
13
  },
14
14
  "peerDependencies": {
15
- "@graphcommerce/eslint-config-pwa": "^9.0.4-canary.0",
16
- "@graphcommerce/graphql": "^9.0.4-canary.0",
17
- "@graphcommerce/next-ui": "^9.0.4-canary.0",
18
- "@graphcommerce/prettier-config-pwa": "^9.0.4-canary.0",
19
- "@graphcommerce/react-hook-form": "^9.0.4-canary.0",
20
- "@graphcommerce/typescript-config-pwa": "^9.0.4-canary.0",
15
+ "@graphcommerce/eslint-config-pwa": "^9.0.4-canary.1",
16
+ "@graphcommerce/graphql": "^9.0.4-canary.1",
17
+ "@graphcommerce/next-ui": "^9.0.4-canary.1",
18
+ "@graphcommerce/prettier-config-pwa": "^9.0.4-canary.1",
19
+ "@graphcommerce/react-hook-form": "^9.0.4-canary.1",
20
+ "@graphcommerce/typescript-config-pwa": "^9.0.4-canary.1",
21
21
  "@lingui/core": "^4.2.1",
22
22
  "@lingui/macro": "^4.2.1",
23
23
  "@lingui/react": "^4.2.1",