@graphcommerce/ecommerce-ui 8.1.0-canary.3 → 8.1.0-canary.6

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,13 +1,98 @@
1
1
  # @graphcommerce/ecommerce-ui
2
2
 
3
- ## 8.1.0-canary.3
3
+ ## 8.1.0-canary.6
4
+
5
+ ## 8.1.0-canary.5
6
+
7
+ ## 8.0.6-canary.4
8
+
9
+ ## 8.0.6-canary.3
10
+
11
+ ## 8.0.6-canary.2
12
+
13
+ ### Patch Changes
14
+
15
+ - [#2234](https://github.com/graphcommerce-org/graphcommerce/pull/2234) [`0767bc4`](https://github.com/graphcommerce-org/graphcommerce/commit/0767bc40f7b596209f24ca4e745ff0441f3275c9) - Upgrade input components to no longer use muiRegister, which improves INP scores
16
+ ([@FrankHarland](https://github.com/FrankHarland))
17
+
18
+ - [#2234](https://github.com/graphcommerce-org/graphcommerce/pull/2234) [`43bd04a`](https://github.com/graphcommerce-org/graphcommerce/commit/43bd04a777c5800cc7e01bee1e123a5aad82f194) - Make sure the TextFieldElement doesn’t give a uncontrolled to controlled warning.
19
+ Convert SelectElement to useController instead of a separate Controller component.
20
+ Make sure the original endAdornment is always shown only until the value is valid ([@FrankHarland](https://github.com/FrankHarland))
21
+
22
+ - [#2234](https://github.com/graphcommerce-org/graphcommerce/pull/2234) [`d4e693d`](https://github.com/graphcommerce-org/graphcommerce/commit/d4e693d553198c9a1ef398d000ca23d209e6c2ba) - The `<WaitForQueries/>` component now uses the useIsSSR hook which prevents loading spinners when navigating on the client, which make all account/cart/checkout pages faster.
23
+ ([@FrankHarland](https://github.com/FrankHarland))
24
+
25
+ ## 8.0.6-canary.1
26
+
27
+ ## 8.0.6-canary.0
28
+
29
+ ## 8.0.5
30
+
31
+ ## 8.0.5-canary.10
32
+
33
+ ## 8.0.5-canary.9
34
+
35
+ ## 8.0.5-canary.8
36
+
37
+ ## 8.0.5-canary.7
38
+
39
+ ## 8.0.5-canary.6
40
+
41
+ ## 8.0.5-canary.5
42
+
43
+ ## 8.0.5-canary.4
44
+
45
+ ## 8.0.5-canary.3
46
+
47
+ ## 8.0.5-canary.2
48
+
49
+ ## 8.0.5-canary.1
50
+
51
+ ## 8.0.5-canary.0
52
+
53
+ ## 8.0.4
54
+
55
+ ## 8.0.4-canary.1
56
+
57
+ ## 8.0.4-canary.0
58
+
59
+ ## 8.0.3
4
60
 
5
61
  ### Patch Changes
6
62
 
63
+ - [#2212](https://github.com/graphcommerce-org/graphcommerce/pull/2212) [`e12d1dc`](https://github.com/graphcommerce-org/graphcommerce/commit/e12d1dc201bf7b23a996bd58a256a117b91a9334) - Rename validation to rules for all Form field components and deprecate validation
64
+ ([@paales](https://github.com/paales))
65
+
66
+ - [#2203](https://github.com/graphcommerce-org/graphcommerce/pull/2203) [`7ef7dc7`](https://github.com/graphcommerce-org/graphcommerce/commit/7ef7dc7631f61a2feba67a531a210df9c22fed4b) - CheckboxElement, MultiSelectElement, NumberFieldElement, SelectElement, SliderElement and TextFieldElement have their inputRef passed, allowing focus to be set by the form.
67
+ ([@Jessevdpoel](https://github.com/Jessevdpoel))
68
+
7
69
  - [#2205](https://github.com/graphcommerce-org/graphcommerce/pull/2205) [`eb14696`](https://github.com/graphcommerce-org/graphcommerce/commit/eb14696fc65e084a06790c88a8218fb3003f7c2c) - `<WaitForQueries/>` will default to loading, restoring the previous behavior. This might introduce , this might introduce an additional spinner but prevents a flash where it is shown that there is no cart
8
70
  ([@paales](https://github.com/paales))
9
71
 
10
- ## 8.1.0-canary.2
72
+ ## 8.0.3-canary.6
73
+
74
+ ## 8.0.3-canary.5
75
+
76
+ ### Patch Changes
77
+
78
+ - [#2212](https://github.com/graphcommerce-org/graphcommerce/pull/2212) [`e12d1dc`](https://github.com/graphcommerce-org/graphcommerce/commit/e12d1dc201bf7b23a996bd58a256a117b91a9334) - Rename validation to rules for all Form field components and deprecate validation
79
+ ([@paales](https://github.com/paales))
80
+
81
+ ## 8.0.3-canary.4
82
+
83
+ ### Patch Changes
84
+
85
+ - [#2203](https://github.com/graphcommerce-org/graphcommerce/pull/2203) [`7ef7dc7`](https://github.com/graphcommerce-org/graphcommerce/commit/7ef7dc7631f61a2feba67a531a210df9c22fed4b) - CheckboxElement, MultiSelectElement, NumberFieldElement, SelectElement, SliderElement and TextFieldElement have their inputRef passed, allowing focus to be set by the form.
86
+ ([@Jessevdpoel](https://github.com/Jessevdpoel))
87
+
88
+ ## 8.0.3-canary.3
89
+
90
+ ## 8.0.3-canary.2
91
+
92
+ ### Patch Changes
93
+
94
+ - [#2205](https://github.com/graphcommerce-org/graphcommerce/pull/2205) [`eb14696`](https://github.com/graphcommerce-org/graphcommerce/commit/eb14696fc65e084a06790c88a8218fb3003f7c2c) - `<WaitForQueries/>` will default to loading, restoring the previous behavior. This might introduce , this might introduce an additional spinner but prevents a flash where it is shown that there is no cart
95
+ ([@paales](https://github.com/paales))
11
96
 
12
97
  ## 8.0.3-canary.1
13
98
 
@@ -21,6 +21,7 @@ export type CheckboxButtonGroupProps<T extends FieldValues> = {
21
21
  options: { id: string | number; label: string }[] | any[]
22
22
  helperText?: string
23
23
  required?: boolean
24
+ /** @deprecated Form value parsing should happen in the handleSubmit function of the form */
24
25
  parseError?: (error: FieldError) => string
25
26
  label?: string
26
27
  labelKey?: string
@@ -19,6 +19,7 @@ import {
19
19
  } from '@mui/material'
20
20
 
21
21
  export type CheckboxElementProps<T extends FieldValues> = Omit<CheckboxProps, 'name'> & {
22
+ /** @deprecated Form value parsing should happen in the handleSubmit function of the form */
22
23
  parseError?: (error: FieldError) => string
23
24
  label?: FormControlLabelProps['label']
24
25
  helperText?: string
@@ -47,7 +48,8 @@ export function CheckboxElement<TFieldValues extends FieldValues>({
47
48
  name={name}
48
49
  rules={rules}
49
50
  control={control}
50
- render={({ field: { value, onChange }, fieldState: { invalid, error } }) => {
51
+ render={({ field: { value, onChange, ref, ...field }, fieldState: { invalid, error } }) => {
52
+ // eslint-disable-next-line no-nested-ternary
51
53
  const parsedHelperText = error
52
54
  ? typeof parseError === 'function'
53
55
  ? parseError(error)
@@ -61,6 +63,8 @@ export function CheckboxElement<TFieldValues extends FieldValues>({
61
63
  control={
62
64
  <Checkbox
63
65
  {...rest}
66
+ {...field}
67
+ inputRef={ref}
64
68
  color={rest.color || 'primary'}
65
69
  sx={{
66
70
  ...(Array.isArray(sx) ? sx : [sx]),
@@ -68,9 +72,7 @@ export function CheckboxElement<TFieldValues extends FieldValues>({
68
72
  }}
69
73
  value={value}
70
74
  checked={!!value}
71
- onChange={() => {
72
- onChange(!value)
73
- }}
75
+ onChange={() => onChange(!value)}
74
76
  />
75
77
  }
76
78
  />
@@ -27,6 +27,7 @@ export type MultiSelectElementProps<T extends FieldValues> = Omit<SelectProps, '
27
27
  itemValue?: string
28
28
  itemLabel?: string
29
29
  required?: boolean
30
+ /** @deprecated Form value parsing should happen in the handleSubmit function of the form */
30
31
  parseError?: (error: FieldError) => string
31
32
  minWidth?: number
32
33
  menuMaxHeight?: number
@@ -72,7 +73,7 @@ export function MultiSelectElement<TFieldValues extends FieldValues>(
72
73
  name={name}
73
74
  rules={rules}
74
75
  control={control}
75
- render={({ field: { value, onChange, onBlur }, fieldState: { invalid, error } }) => {
76
+ render={({ field: { value, onChange, ...field }, fieldState: { invalid, error } }) => {
76
77
  helperText = error
77
78
  ? typeof parseError === 'function'
78
79
  ? parseError(error)
@@ -102,6 +103,7 @@ export function MultiSelectElement<TFieldValues extends FieldValues>(
102
103
  )}
103
104
  <Select
104
105
  {...rest}
106
+ {...field}
105
107
  id={rest.id || `select-multi-select-${name}`}
106
108
  multiple
107
109
  label={label || undefined}
@@ -109,7 +111,6 @@ export function MultiSelectElement<TFieldValues extends FieldValues>(
109
111
  value={value || []}
110
112
  required={required}
111
113
  onChange={onChange}
112
- onBlur={onBlur}
113
114
  MenuProps={{
114
115
  ...rest.MenuProps,
115
116
  PaperProps: {
@@ -126,23 +127,23 @@ export function MultiSelectElement<TFieldValues extends FieldValues>(
126
127
  typeof rest.renderValue === 'function'
127
128
  ? rest.renderValue
128
129
  : showChips
129
- ? (selected) => (
130
- <div style={{ display: 'flex', flexWrap: 'wrap' }}>
131
- {((selected as any[]) || []).map((selectedValue) => (
132
- <Chip
133
- key={selectedValue}
134
- label={selectedValue}
135
- style={{ display: 'flex', flexWrap: 'wrap' }}
136
- onDelete={() => {
137
- onChange(value.filter((i: any) => i !== selectedValue))
138
- // setValue(name, formValue.filter((i: any) => i !== value), { shouldValidate: true })
139
- }}
140
- deleteIcon={<IconSvg src={iconClose} />}
141
- />
142
- ))}
143
- </div>
144
- )
145
- : (selected) => (Array.isArray(selected) ? selected.join(', ') : '')
130
+ ? (selected) => (
131
+ <div style={{ display: 'flex', flexWrap: 'wrap' }}>
132
+ {((selected as any[]) || []).map((selectedValue) => (
133
+ <Chip
134
+ key={selectedValue}
135
+ label={selectedValue}
136
+ style={{ display: 'flex', flexWrap: 'wrap' }}
137
+ onDelete={() => {
138
+ onChange(value.filter((i: any) => i !== selectedValue))
139
+ // setValue(name, formValue.filter((i: any) => i !== value), { shouldValidate: true })
140
+ }}
141
+ deleteIcon={<IconSvg src={iconClose} />}
142
+ />
143
+ ))}
144
+ </div>
145
+ )
146
+ : (selected) => (Array.isArray(selected) ? selected.join(', ') : '')
146
147
  }
147
148
  >
148
149
  {options.map((item) => {
@@ -5,7 +5,12 @@ import {
5
5
  IconSvg,
6
6
  responsiveVal,
7
7
  } from '@graphcommerce/next-ui'
8
- import { Controller, ControllerProps, FieldValues } from '@graphcommerce/react-hook-form'
8
+ import {
9
+ Controller,
10
+ ControllerProps,
11
+ FieldValues,
12
+ useController,
13
+ } from '@graphcommerce/react-hook-form'
9
14
  import { i18n } from '@lingui/core'
10
15
  import { IconButtonProps, SxProps, Theme, TextField, TextFieldProps, Fab } from '@mui/material'
11
16
 
@@ -48,117 +53,117 @@ export function NumberFieldElement<T extends FieldValues>(props: NumberFieldElem
48
53
  rules.required = i18n._(/* i18n */ 'This field is required')
49
54
  }
50
55
 
51
- return (
52
- <Controller
53
- name={name}
54
- control={control}
55
- rules={rules}
56
- defaultValue={defaultValue}
57
- render={({ field: { value, onChange, onBlur }, fieldState: { invalid, error } }) => {
58
- const valueAsNumber = value ? parseFloat(value) : 0
56
+ const {
57
+ field: { value, onChange, ref, ...field },
58
+ fieldState: { invalid, error },
59
+ } = useController({
60
+ name,
61
+ control,
62
+ rules,
63
+ defaultValue,
64
+ })
59
65
 
60
- return (
61
- <TextField
62
- {...textFieldProps}
63
- variant={variant}
64
- name={name}
65
- value={value ?? ''}
66
- onChange={(ev) => {
67
- const newValue = (ev.target as HTMLInputElement).valueAsNumber
68
- onChange(Number.isNaN(newValue) ? '' : newValue)
69
- textFieldProps.onChange?.(ev)
66
+ const valueAsNumber = value ? parseFloat(value) : 0
67
+
68
+ return (
69
+ <TextField
70
+ {...textFieldProps}
71
+ {...field}
72
+ inputRef={ref}
73
+ value={value ?? ''}
74
+ onChange={(ev) => {
75
+ const newValue = (ev.target as HTMLInputElement).valueAsNumber
76
+ onChange(Number.isNaN(newValue) ? '' : newValue)
77
+ textFieldProps.onChange?.(ev)
78
+ }}
79
+ variant={variant}
80
+ required={required}
81
+ error={invalid}
82
+ helperText={error ? error.message : textFieldProps.helperText}
83
+ size={size}
84
+ type='number'
85
+ className={`${textFieldProps.className ?? ''} ${classes.quantity}`}
86
+ sx={[
87
+ {
88
+ width: responsiveVal(90, 120),
89
+ },
90
+ {
91
+ '& input[type=number]': {
92
+ MozAppearance: 'textfield',
93
+ },
94
+ '& .MuiOutlinedInput-root': {
95
+ px: '2px',
96
+ display: 'grid',
97
+ gridTemplateColumns: '1fr auto 1fr',
98
+ },
99
+ },
100
+ variant === 'standard' && {
101
+ '& .MuiOutlinedInput-input': {
102
+ padding: 0,
103
+ },
104
+ '& .MuiOutlinedInput-notchedOutline': {
105
+ display: 'none',
106
+ },
107
+ },
108
+ ...(Array.isArray(sx) ? sx : [sx]),
109
+ ]}
110
+ autoComplete='off'
111
+ InputProps={{
112
+ ...textFieldProps.InputProps,
113
+ startAdornment: (
114
+ <Fab
115
+ aria-label={i18n._(/* i18n */ 'Decrease')}
116
+ size='smaller'
117
+ onClick={() => {
118
+ if ((valueAsNumber || Infinity) <= inputProps.min) return
119
+ onChange(value - 1)
70
120
  }}
71
- onBlur={onBlur}
72
- required={required}
73
- error={invalid}
74
- helperText={error ? error.message : textFieldProps.helperText}
75
- size={size}
76
- type='number'
77
- className={`${textFieldProps.className ?? ''} ${classes.quantity}`}
78
- sx={[
79
- {
80
- width: responsiveVal(90, 120),
81
- },
82
- {
83
- '& input[type=number]': {
84
- MozAppearance: 'textfield',
85
- },
86
- '& .MuiOutlinedInput-root': {
87
- px: '2px',
88
- display: 'grid',
89
- gridTemplateColumns: '1fr auto 1fr',
90
- },
91
- },
92
- variant === 'standard' && {
93
- '& .MuiOutlinedInput-input': {
94
- padding: 0,
95
- },
96
- '& .MuiOutlinedInput-notchedOutline': {
97
- display: 'none',
98
- },
99
- },
100
- ...(Array.isArray(sx) ? sx : [sx]),
101
- ]}
102
- autoComplete='off'
103
- InputProps={{
104
- ...textFieldProps.InputProps,
105
- startAdornment: (
106
- <Fab
107
- aria-label={i18n._(/* i18n */ 'Decrease')}
108
- size='smaller'
109
- onClick={() => {
110
- if ((valueAsNumber || Infinity) <= inputProps.min) return
111
- onChange(value - 1)
112
- }}
113
- sx={{
114
- boxShadow: variant === 'standard' ? 4 : 0,
115
- minHeight: '30px',
116
- minWidth: '30px',
117
- }}
118
- tabIndex={-1}
119
- color='inherit'
120
- {...DownProps}
121
- className={`${classes.button} ${DownProps.className ?? ''}`}
122
- >
123
- {DownProps.children ?? <IconSvg src={iconMin} size='small' />}
124
- </Fab>
125
- ),
126
- endAdornment: (
127
- <Fab
128
- aria-label={i18n._(/* i18n */ 'Increase')}
129
- size='smaller'
130
- onClick={() => {
131
- if (valueAsNumber >= (inputProps.max ?? Infinity)) return
132
- onChange(valueAsNumber + 1)
133
- }}
134
- sx={{
135
- boxShadow: variant === 'standard' ? 4 : 0,
136
- minHeight: '30px',
137
- minWidth: '30px',
138
- }}
139
- tabIndex={-1}
140
- color='inherit'
141
- {...UpProps}
142
- className={`${classes.button} ${UpProps.className ?? ''}`}
143
- >
144
- {UpProps.children ?? <IconSvg src={iconPlus} size='small' />}
145
- </Fab>
146
- ),
121
+ sx={{
122
+ boxShadow: variant === 'standard' ? 4 : 0,
123
+ minHeight: '30px',
124
+ minWidth: '30px',
147
125
  }}
148
- inputProps={{
149
- 'aria-label': i18n._(/* i18n */ 'Number'),
150
- ...inputProps,
151
- className: `${inputProps?.className ?? ''} ${classes.quantityInput}`,
152
- sx: {
153
- typography: 'body1',
154
- textAlign: 'center',
155
- '&::-webkit-inner-spin-button,&::-webkit-outer-spin-button': {
156
- appearance: 'none',
157
- },
158
- },
126
+ tabIndex={-1}
127
+ color='inherit'
128
+ {...DownProps}
129
+ className={`${classes.button} ${DownProps.className ?? ''}`}
130
+ >
131
+ {DownProps.children ?? <IconSvg src={iconMin} size='small' />}
132
+ </Fab>
133
+ ),
134
+ endAdornment: (
135
+ <Fab
136
+ aria-label={i18n._(/* i18n */ 'Increase')}
137
+ size='smaller'
138
+ onClick={() => {
139
+ if (valueAsNumber >= (inputProps.max ?? Infinity)) return
140
+ onChange(valueAsNumber + 1)
159
141
  }}
160
- />
161
- )
142
+ sx={{
143
+ boxShadow: variant === 'standard' ? 4 : 0,
144
+ minHeight: '30px',
145
+ minWidth: '30px',
146
+ }}
147
+ tabIndex={-1}
148
+ color='inherit'
149
+ {...UpProps}
150
+ className={`${classes.button} ${UpProps.className ?? ''}`}
151
+ >
152
+ {UpProps.children ?? <IconSvg src={iconPlus} size='small' />}
153
+ </Fab>
154
+ ),
155
+ }}
156
+ inputProps={{
157
+ 'aria-label': i18n._(/* i18n */ 'Number'),
158
+ ...inputProps,
159
+ className: `${inputProps?.className ?? ''} ${classes.quantityInput}`,
160
+ sx: {
161
+ typography: 'body1',
162
+ textAlign: 'center',
163
+ '&::-webkit-inner-spin-button,&::-webkit-outer-spin-button': {
164
+ appearance: 'none',
165
+ },
166
+ },
162
167
  }}
163
168
  />
164
169
  )
@@ -14,7 +14,7 @@ export function PasswordRepeatElement<TFieldValues extends FieldValues>({
14
14
  return (
15
15
  <PasswordElement
16
16
  {...rest}
17
- validation={{
17
+ rules={{
18
18
  validate: (value: string) =>
19
19
  value === pwValue || i18n._(/* i18n */ "Passwords don't match"),
20
20
  }}
@@ -20,6 +20,7 @@ export type RadioButtonGroupProps<T extends FieldValues> = {
20
20
  options: { label: string; id: string | number }[] | any[]
21
21
  helperText?: string
22
22
  required?: boolean
23
+ /** @deprecated Form value parsing should happen in the handleSubmit function of the form */
23
24
  parseError?: (error: FieldError) => string
24
25
  label?: string
25
26
  labelKey?: string
@@ -1,4 +1,10 @@
1
- import { Controller, ControllerProps, FieldValues } from '@graphcommerce/react-hook-form'
1
+ import { InputCheckmark } from '@graphcommerce/next-ui'
2
+ import {
3
+ Controller,
4
+ ControllerProps,
5
+ FieldValues,
6
+ useController,
7
+ } from '@graphcommerce/react-hook-form'
2
8
  import { i18n } from '@lingui/core'
3
9
  import { MenuItem, TextField, TextFieldProps } from '@mui/material'
4
10
 
@@ -8,10 +14,12 @@ export type SelectElementProps<T extends FieldValues, O extends OptionBase> = Om
8
14
  TextFieldProps,
9
15
  'name' | 'type' | 'onChange' | 'defaultValue'
10
16
  > & {
17
+ /** @deprecated Please use the rules props instead */
11
18
  validation?: ControllerProps<T>['rules']
12
19
  options?: O[]
13
20
  type?: 'string' | 'number'
14
21
  onChange?: (value: string | number) => void
22
+ showValid?: boolean
15
23
  } & Omit<ControllerProps<T>, 'render'>
16
24
 
17
25
  export function SelectElement<TFieldValues extends FieldValues, O extends OptionBase>({
@@ -19,57 +27,72 @@ export function SelectElement<TFieldValues extends FieldValues, O extends Option
19
27
  required,
20
28
  options = [],
21
29
  type,
22
- validation = {},
30
+ validation,
23
31
  control,
24
32
  defaultValue,
33
+ rules = validation ?? {},
34
+ showValid,
35
+ disabled,
36
+ shouldUnregister,
25
37
  ...rest
26
38
  }: SelectElementProps<TFieldValues, O>): JSX.Element {
27
39
  const isNativeSelect = !!rest.SelectProps?.native
28
40
  const ChildComponent = isNativeSelect ? 'option' : MenuItem
29
41
 
30
- if (required && !validation.required) {
31
- validation.required = i18n._(/* i18n */ 'This field is required')
42
+ if (required && !rules.required) {
43
+ rules.required = i18n._(/* i18n */ 'This field is required')
32
44
  }
33
45
 
34
- return (
35
- <Controller
36
- name={name}
37
- rules={validation}
38
- control={control}
39
- defaultValue={defaultValue}
40
- render={({ field: { onBlur, onChange, value }, fieldState: { invalid, error } }) => {
41
- // handle shrink on number input fields
42
- if (type === 'number' && typeof value !== 'undefined') {
43
- rest.InputLabelProps = rest.InputLabelProps || {}
44
- rest.InputLabelProps.shrink = true
45
- }
46
+ const {
47
+ field: { onChange, value, ref, ...field },
48
+ fieldState: { invalid, error },
49
+ } = useController({
50
+ name,
51
+ rules,
52
+ control,
53
+ defaultValue,
54
+ disabled,
55
+ shouldUnregister,
56
+ })
57
+
58
+ // handle shrink on number input fields
59
+ if (type === 'number' && typeof value !== 'undefined') {
60
+ rest.InputLabelProps = rest.InputLabelProps || {}
61
+ rest.InputLabelProps.shrink = true
62
+ }
46
63
 
47
- return (
48
- <TextField
49
- {...rest}
50
- name={name}
51
- value={value ?? ''}
52
- onBlur={onBlur}
53
- onChange={(event) => {
54
- let item: number | string | O | undefined = event.target.value
55
- if (type === 'number') item = Number(item)
56
- rest.onChange?.(item)
57
- onChange(item)
58
- }}
59
- select
60
- required={required}
61
- error={invalid}
62
- helperText={error ? error.message : rest.helperText}
63
- >
64
- {isNativeSelect && <option />}
65
- {options.map((item) => (
66
- <ChildComponent key={item.id} value={item.id}>
67
- {item.label}
68
- </ChildComponent>
69
- ))}
70
- </TextField>
71
- )
64
+ return (
65
+ <TextField
66
+ {...rest}
67
+ value={value ?? ''}
68
+ {...field}
69
+ inputRef={ref}
70
+ onChange={(event) => {
71
+ let item: number | string | O | undefined = event.target.value
72
+ if (type === 'number') item = Number(item)
73
+ rest.onChange?.(item)
74
+ onChange(item)
75
+ }}
76
+ select
77
+ required={required}
78
+ error={invalid}
79
+ helperText={error ? error.message : rest.helperText}
80
+ InputProps={{
81
+ ...rest.InputProps,
82
+ endAdornment:
83
+ showValid && value && !error ? (
84
+ <InputCheckmark show={!error} />
85
+ ) : (
86
+ rest.InputProps?.endAdornment
87
+ ),
72
88
  }}
73
- />
89
+ >
90
+ {isNativeSelect && <option />}
91
+ {options.map((item) => (
92
+ <ChildComponent key={item.id} value={item.id}>
93
+ {item.label}
94
+ </ChildComponent>
95
+ ))}
96
+ </TextField>
74
97
  )
75
98
  }
@@ -16,6 +16,7 @@ import {
16
16
 
17
17
  export type SliderElementProps<T extends FieldValues> = Omit<SliderProps, 'control'> & {
18
18
  label?: string
19
+ /** @deprecated Form value parsing should happen in the handleSubmit function of the form */
19
20
  parseError?: (error: FieldError) => string
20
21
  required?: boolean
21
22
  formControlProps?: FormControlProps
@@ -39,7 +40,7 @@ export function SliderElement<TFieldValues extends FieldValues>({
39
40
  name={name}
40
41
  control={control}
41
42
  rules={rules}
42
- render={({ field: { onChange, value }, fieldState: { invalid, error } }) => {
43
+ render={({ field, fieldState: { invalid, error } }) => {
43
44
  const parsedHelperText = error
44
45
  ? typeof parseError === 'function'
45
46
  ? parseError(error)
@@ -52,12 +53,7 @@ export function SliderElement<TFieldValues extends FieldValues>({
52
53
  {label}
53
54
  </FormLabel>
54
55
  )}
55
- <Slider
56
- {...other}
57
- value={value}
58
- onChange={onChange}
59
- valueLabelDisplay={other.valueLabelDisplay || 'auto'}
60
- />
56
+ <Slider {...other} {...field} valueLabelDisplay={other.valueLabelDisplay || 'auto'} />
61
57
  {parsedHelperText && (
62
58
  <FormHelperText error={invalid}>{parsedHelperText}</FormHelperText>
63
59
  )}
@@ -1,10 +1,6 @@
1
1
  /* eslint-disable no-nested-ternary */
2
- import {
3
- Controller,
4
- FieldError,
5
- FieldValues,
6
- UseControllerProps,
7
- } from '@graphcommerce/react-hook-form'
2
+ import { InputCheckmark } from '@graphcommerce/next-ui'
3
+ import { FieldValues, UseControllerProps, useController } from '@graphcommerce/react-hook-form'
8
4
  import { i18n } from '@lingui/core'
9
5
  import { TextField, TextFieldProps } from '@mui/material'
10
6
 
@@ -12,8 +8,11 @@ export type TextFieldElementProps<T extends FieldValues = FieldValues> = Omit<
12
8
  TextFieldProps,
13
9
  'name' | 'defaultValue'
14
10
  > & {
11
+ /** @deprecated Please use the rules props instead */
15
12
  validation?: UseControllerProps<T>['rules']
16
- } & Omit<UseControllerProps<T>, 'rules'>
13
+
14
+ showValid?: boolean
15
+ } & UseControllerProps<T>
17
16
 
18
17
  export function TextFieldElement<TFieldValues extends FieldValues>({
19
18
  validation = {},
@@ -22,14 +21,16 @@ export function TextFieldElement<TFieldValues extends FieldValues>({
22
21
  name,
23
22
  control,
24
23
  defaultValue,
24
+ rules = validation,
25
+ showValid,
25
26
  ...rest
26
27
  }: TextFieldElementProps<TFieldValues>): JSX.Element {
27
- if (required && !validation.required) {
28
- validation.required = i18n._(/* i18n */ 'This field is required')
28
+ if (required && !rules.required) {
29
+ rules.required = i18n._(/* i18n */ 'This field is required')
29
30
  }
30
31
 
31
- if (type === 'email' && !validation.pattern) {
32
- validation.pattern = {
32
+ if (type === 'email' && !rules.pattern) {
33
+ rules.pattern = {
33
34
  // eslint-disable-next-line no-useless-escape
34
35
  value:
35
36
  /^(([^<>()\[\]\\.,;:\s@"]+(\.[^<>()\[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/,
@@ -37,31 +38,34 @@ export function TextFieldElement<TFieldValues extends FieldValues>({
37
38
  }
38
39
  }
39
40
 
41
+ const {
42
+ field: { onChange, ref, value = '', ...field },
43
+ fieldState: { error },
44
+ } = useController({ name, control, rules, defaultValue })
45
+
40
46
  return (
41
- <Controller
42
- name={name}
43
- control={control}
44
- rules={validation}
45
- defaultValue={defaultValue}
46
- render={({ field: { value, onChange, onBlur, ref }, fieldState: { error } }) => (
47
- <TextField
48
- {...rest}
49
- name={name}
50
- value={value ?? ''}
51
- onChange={(ev) => {
52
- onChange(
53
- type === 'number' && ev.target.value ? Number(ev.target.value) : ev.target.value,
54
- )
55
- rest.onChange?.(ev)
56
- }}
57
- onBlur={onBlur}
58
- required={required}
59
- type={type}
60
- error={Boolean(error) || rest.error}
61
- helperText={error ? error.message : rest.helperText}
62
- inputRef={ref}
63
- />
64
- )}
47
+ <TextField
48
+ {...rest}
49
+ {...field}
50
+ value={value}
51
+ onChange={(ev) => {
52
+ onChange(type === 'number' && ev.target.value ? Number(ev.target.value) : ev.target.value)
53
+ rest.onChange?.(ev)
54
+ }}
55
+ inputRef={ref}
56
+ required={required}
57
+ type={type}
58
+ error={Boolean(error) || rest.error}
59
+ helperText={error ? error.message : rest.helperText}
60
+ InputProps={{
61
+ ...rest.InputProps,
62
+ endAdornment:
63
+ showValid && value && !error ? (
64
+ <InputCheckmark show={!error} />
65
+ ) : (
66
+ rest.InputProps?.endAdornment
67
+ ),
68
+ }}
65
69
  />
66
70
  )
67
71
  }
@@ -25,6 +25,7 @@ type SingleToggleButtonProps = Omit<ToggleButtonProps, 'value' | 'children'> & {
25
25
  export type ToggleButtonGroupElementProps<T extends FieldValues> = ToggleButtonGroupProps & {
26
26
  required?: boolean
27
27
  label?: string
28
+ /** @deprecated Form value parsing should happen in the handleSubmit function of the form */
28
29
  parseError?: (error: FieldError) => string
29
30
  options: SingleToggleButtonProps[]
30
31
  formLabelProps?: FormLabelProps
@@ -1,9 +1,12 @@
1
- import { useIsomorphicLayoutEffect } from '@graphcommerce/framer-utils'
2
1
  import { QueryResult } from '@graphcommerce/graphql'
3
- import React, { startTransition, useEffect, useState } from 'react'
2
+ import { useIsSSR } from '@graphcommerce/next-ui'
3
+ import React from 'react'
4
4
 
5
5
  export type WaitForQueriesProps = {
6
6
  waitFor: QueryResult<any, any> | boolean | (QueryResult<any, any> | boolean)[] | undefined
7
+ /**
8
+ * @deprecated Will be automatically correct.
9
+ */
7
10
  noSsr?: boolean
8
11
  children: React.ReactNode
9
12
  fallback?: React.ReactNode
@@ -11,14 +14,9 @@ export type WaitForQueriesProps = {
11
14
 
12
15
  /** Shows the fallback during: SSR, Hydration and Query Loading. */
13
16
  export const WaitForQueries = (props: WaitForQueriesProps) => {
14
- const { waitFor, fallback, children, noSsr = true } = props
17
+ const { waitFor, fallback, children } = props
15
18
 
16
- // Make sure the first render is always the same as the server.
17
- // Make sure we we use startTransition to make sure we don't get into trouble with Suspense.
18
- const [mounted, setMounted] = useState(!noSsr)
19
- useEffect(() => {
20
- if (noSsr) setMounted(true)
21
- }, [noSsr])
19
+ const mounted = !useIsSSR()
22
20
 
23
21
  // We are done when all queries either have data or an error.
24
22
  const isDone = (Array.isArray(waitFor) ? waitFor : [waitFor]).every((res) => {
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": "8.1.0-canary.3",
5
+ "version": "8.1.0-canary.6",
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": "^8.1.0-canary.3",
16
- "@graphcommerce/graphql": "^8.1.0-canary.3",
17
- "@graphcommerce/next-ui": "^8.1.0-canary.3",
18
- "@graphcommerce/prettier-config-pwa": "^8.1.0-canary.3",
19
- "@graphcommerce/react-hook-form": "^8.1.0-canary.3",
20
- "@graphcommerce/typescript-config-pwa": "^8.1.0-canary.3",
15
+ "@graphcommerce/eslint-config-pwa": "^8.1.0-canary.6",
16
+ "@graphcommerce/graphql": "^8.1.0-canary.6",
17
+ "@graphcommerce/next-ui": "^8.1.0-canary.6",
18
+ "@graphcommerce/prettier-config-pwa": "^8.1.0-canary.6",
19
+ "@graphcommerce/react-hook-form": "^8.1.0-canary.6",
20
+ "@graphcommerce/typescript-config-pwa": "^8.1.0-canary.6",
21
21
  "@lingui/core": "^4.2.1",
22
22
  "@lingui/macro": "^4.2.1",
23
23
  "@lingui/react": "^4.2.1",