@graphcommerce/ecommerce-ui 8.1.0-canary.9 → 9.0.0-canary.101
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 +236 -37
- package/Config.graphqls +29 -0
- package/components/FormComponents/ActionCardListForm.tsx +96 -0
- package/components/FormComponents/AutoCompleteElement.tsx +101 -90
- package/components/FormComponents/CheckboxButtonGroup.tsx +27 -31
- package/components/FormComponents/CheckboxElement.tsx +56 -60
- package/components/FormComponents/EmailElement.tsx +27 -0
- package/components/FormComponents/MultiSelectElement.tsx +103 -110
- package/components/FormComponents/NumberFieldElement.tsx +9 -1
- package/components/FormComponents/RadioButtonGroup.tsx +27 -29
- package/components/FormComponents/SliderElement.tsx +28 -29
- package/components/FormComponents/SwitchElement.tsx +15 -13
- package/components/FormComponents/TelephoneElement.tsx +23 -0
- package/components/FormComponents/TextFieldElement.tsx +25 -5
- package/components/FormComponents/ToggleButtonGroup.tsx +49 -48
- package/components/FormComponents/index.ts +6 -3
- package/components/PreviewMode/LightTooltip.tsx +11 -0
- package/components/PreviewMode/PreviewMode.tsx +151 -0
- package/components/PreviewMode/PreviewModeActions.tsx +6 -0
- package/components/PreviewMode/PreviewModeToolbar.tsx +6 -0
- package/components/PreviewMode/index.ts +5 -0
- package/components/PreviewMode/previewModeDefaults.ts +5 -0
- package/components/PreviewMode/usePreviewModeForm.ts +6 -0
- package/components/index.ts +1 -0
- package/package.json +7 -7
- package/plugins/PreviewModeFramerNextPages.tsx +38 -0
- package/route/preview.ts +60 -0
|
@@ -1,11 +1,6 @@
|
|
|
1
1
|
/* eslint-disable no-nested-ternary */
|
|
2
2
|
import { IconSvg, iconClose } from '@graphcommerce/next-ui'
|
|
3
|
-
import {
|
|
4
|
-
Controller,
|
|
5
|
-
FieldError,
|
|
6
|
-
FieldValues,
|
|
7
|
-
ControllerProps,
|
|
8
|
-
} from '@graphcommerce/react-hook-form'
|
|
3
|
+
import { FieldValues, ControllerProps, useController } from '@graphcommerce/react-hook-form'
|
|
9
4
|
import { i18n } from '@lingui/core'
|
|
10
5
|
import {
|
|
11
6
|
Checkbox,
|
|
@@ -27,8 +22,6 @@ export type MultiSelectElementProps<T extends FieldValues> = Omit<SelectProps, '
|
|
|
27
22
|
itemValue?: string
|
|
28
23
|
itemLabel?: string
|
|
29
24
|
required?: boolean
|
|
30
|
-
/** @deprecated Form value parsing should happen in the handleSubmit function of the form */
|
|
31
|
-
parseError?: (error: FieldError) => string
|
|
32
25
|
minWidth?: number
|
|
33
26
|
menuMaxHeight?: number
|
|
34
27
|
menuMaxWidth?: number
|
|
@@ -44,7 +37,7 @@ const ITEM_PADDING_TOP = 8
|
|
|
44
37
|
export function MultiSelectElement<TFieldValues extends FieldValues>(
|
|
45
38
|
props: MultiSelectElementProps<TFieldValues>,
|
|
46
39
|
): JSX.Element {
|
|
47
|
-
|
|
40
|
+
const {
|
|
48
41
|
options,
|
|
49
42
|
label = '',
|
|
50
43
|
itemKey = 'id',
|
|
@@ -52,7 +45,6 @@ export function MultiSelectElement<TFieldValues extends FieldValues>(
|
|
|
52
45
|
itemLabel = 'label',
|
|
53
46
|
required = false,
|
|
54
47
|
rules = {},
|
|
55
|
-
parseError,
|
|
56
48
|
name,
|
|
57
49
|
menuMaxHeight = ITEM_HEIGHT * 4.5 + ITEM_PADDING_TOP,
|
|
58
50
|
menuMaxWidth = 250,
|
|
@@ -62,114 +54,115 @@ export function MultiSelectElement<TFieldValues extends FieldValues>(
|
|
|
62
54
|
control,
|
|
63
55
|
showCheckbox,
|
|
64
56
|
formControlProps,
|
|
57
|
+
shouldUnregister,
|
|
58
|
+
defaultValue,
|
|
59
|
+
disabled,
|
|
65
60
|
...rest
|
|
66
61
|
} = props
|
|
67
62
|
if (required && !rules.required) {
|
|
68
63
|
rules.required = i18n._(/* i18n */ 'This field is required')
|
|
69
64
|
}
|
|
70
65
|
|
|
66
|
+
const {
|
|
67
|
+
field: { value, onChange, ...field },
|
|
68
|
+
fieldState: { invalid, error },
|
|
69
|
+
} = useController({
|
|
70
|
+
name,
|
|
71
|
+
rules,
|
|
72
|
+
control,
|
|
73
|
+
defaultValue,
|
|
74
|
+
disabled,
|
|
75
|
+
shouldUnregister,
|
|
76
|
+
})
|
|
77
|
+
|
|
78
|
+
const parsedHelperText = error ? error.message : helperText
|
|
79
|
+
|
|
71
80
|
return (
|
|
72
|
-
<
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
81
|
+
<FormControl
|
|
82
|
+
{...formControlProps}
|
|
83
|
+
style={{
|
|
84
|
+
...formControlProps?.style,
|
|
85
|
+
minWidth,
|
|
86
|
+
}}
|
|
87
|
+
variant={rest.variant}
|
|
88
|
+
fullWidth={rest.fullWidth}
|
|
89
|
+
error={invalid}
|
|
90
|
+
size={rest.size}
|
|
91
|
+
>
|
|
92
|
+
{label && (
|
|
93
|
+
<InputLabel
|
|
94
|
+
size={rest.size === 'small' ? 'small' : undefined}
|
|
95
|
+
error={invalid}
|
|
96
|
+
htmlFor={rest.id || `select-multi-select-${name}`}
|
|
97
|
+
required={required}
|
|
98
|
+
>
|
|
99
|
+
{label}
|
|
100
|
+
</InputLabel>
|
|
101
|
+
)}
|
|
102
|
+
<Select
|
|
103
|
+
{...rest}
|
|
104
|
+
{...field}
|
|
105
|
+
id={rest.id || `select-multi-select-${name}`}
|
|
106
|
+
multiple
|
|
107
|
+
label={label || undefined}
|
|
108
|
+
error={invalid}
|
|
109
|
+
value={value || []}
|
|
110
|
+
required={required}
|
|
111
|
+
onChange={onChange}
|
|
112
|
+
MenuProps={{
|
|
113
|
+
...rest.MenuProps,
|
|
114
|
+
PaperProps: {
|
|
115
|
+
...(rest.MenuProps?.PaperProps ?? {
|
|
116
|
+
style: {
|
|
117
|
+
maxHeight: menuMaxHeight,
|
|
118
|
+
width: menuMaxWidth,
|
|
119
|
+
...rest.MenuProps?.PaperProps?.style,
|
|
120
|
+
},
|
|
121
|
+
}),
|
|
122
|
+
},
|
|
123
|
+
}}
|
|
124
|
+
renderValue={
|
|
125
|
+
typeof rest.renderValue === 'function'
|
|
126
|
+
? rest.renderValue
|
|
127
|
+
: showChips
|
|
128
|
+
? (selected) => (
|
|
129
|
+
<div style={{ display: 'flex', flexWrap: 'wrap' }}>
|
|
130
|
+
{((selected as any[]) || []).map((selectedValue) => (
|
|
131
|
+
<Chip
|
|
132
|
+
key={selectedValue}
|
|
133
|
+
label={selectedValue}
|
|
134
|
+
style={{ display: 'flex', flexWrap: 'wrap' }}
|
|
135
|
+
onDelete={() => {
|
|
136
|
+
onChange(value.filter((i: any) => i !== selectedValue))
|
|
137
|
+
// setValue(name, formValue.filter((i: any) => i !== value), { shouldValidate: true })
|
|
138
|
+
}}
|
|
139
|
+
deleteIcon={<IconSvg src={iconClose} />}
|
|
140
|
+
/>
|
|
141
|
+
))}
|
|
142
|
+
</div>
|
|
143
|
+
)
|
|
144
|
+
: (selected) => (Array.isArray(selected) ? selected.join(', ') : '')
|
|
145
|
+
}
|
|
146
|
+
>
|
|
147
|
+
{options.map((item) => {
|
|
148
|
+
const val: string | number = item[itemValue || itemKey] || item
|
|
149
|
+
const isChecked = Array.isArray(value) ? value.includes(val) : false
|
|
150
|
+
return (
|
|
151
|
+
<MenuItem
|
|
152
|
+
key={val}
|
|
153
|
+
value={val}
|
|
154
|
+
sx={{
|
|
155
|
+
fontWeight: (theme) =>
|
|
156
|
+
isChecked ? theme.typography.fontWeightBold : theme.typography.fontWeightRegular,
|
|
125
157
|
}}
|
|
126
|
-
renderValue={
|
|
127
|
-
typeof rest.renderValue === 'function'
|
|
128
|
-
? rest.renderValue
|
|
129
|
-
: showChips
|
|
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(', ') : '')
|
|
147
|
-
}
|
|
148
158
|
>
|
|
149
|
-
{
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
fontWeight: (theme) =>
|
|
158
|
-
isChecked
|
|
159
|
-
? theme.typography.fontWeightBold
|
|
160
|
-
: theme.typography.fontWeightRegular,
|
|
161
|
-
}}
|
|
162
|
-
>
|
|
163
|
-
{showCheckbox && <Checkbox checked={isChecked} />}
|
|
164
|
-
<ListItemText primary={item[itemLabel] || item} />
|
|
165
|
-
</MenuItem>
|
|
166
|
-
)
|
|
167
|
-
})}
|
|
168
|
-
</Select>
|
|
169
|
-
{helperText && <FormHelperText>{helperText}</FormHelperText>}
|
|
170
|
-
</FormControl>
|
|
171
|
-
)
|
|
172
|
-
}}
|
|
173
|
-
/>
|
|
159
|
+
{showCheckbox && <Checkbox checked={isChecked} />}
|
|
160
|
+
<ListItemText primary={item[itemLabel] || item} />
|
|
161
|
+
</MenuItem>
|
|
162
|
+
)
|
|
163
|
+
})}
|
|
164
|
+
</Select>
|
|
165
|
+
{parsedHelperText && <FormHelperText>{parsedHelperText}</FormHelperText>}
|
|
166
|
+
</FormControl>
|
|
174
167
|
)
|
|
175
168
|
}
|
|
@@ -44,6 +44,8 @@ export function NumberFieldElement<T extends FieldValues>(props: NumberFieldElem
|
|
|
44
44
|
required,
|
|
45
45
|
defaultValue,
|
|
46
46
|
variant = 'outlined',
|
|
47
|
+
disabled,
|
|
48
|
+
shouldUnregister,
|
|
47
49
|
...textFieldProps
|
|
48
50
|
} = props
|
|
49
51
|
|
|
@@ -61,6 +63,8 @@ export function NumberFieldElement<T extends FieldValues>(props: NumberFieldElem
|
|
|
61
63
|
control,
|
|
62
64
|
rules,
|
|
63
65
|
defaultValue,
|
|
66
|
+
disabled,
|
|
67
|
+
shouldUnregister,
|
|
64
68
|
})
|
|
65
69
|
|
|
66
70
|
const valueAsNumber = value ? parseFloat(value) : 0
|
|
@@ -115,7 +119,11 @@ export function NumberFieldElement<T extends FieldValues>(props: NumberFieldElem
|
|
|
115
119
|
aria-label={i18n._(/* i18n */ 'Decrease')}
|
|
116
120
|
size='smaller'
|
|
117
121
|
onClick={() => {
|
|
118
|
-
if (
|
|
122
|
+
if (
|
|
123
|
+
(valueAsNumber ?? Infinity) <= inputProps.min ||
|
|
124
|
+
(inputProps.min === 0 && valueAsNumber <= inputProps.min)
|
|
125
|
+
)
|
|
126
|
+
return
|
|
119
127
|
onChange(value - 1)
|
|
120
128
|
}}
|
|
121
129
|
sx={{
|
|
@@ -1,9 +1,4 @@
|
|
|
1
|
-
import {
|
|
2
|
-
FieldError,
|
|
3
|
-
useController,
|
|
4
|
-
FieldValues,
|
|
5
|
-
UseControllerProps,
|
|
6
|
-
} from '@graphcommerce/react-hook-form'
|
|
1
|
+
import { useController, FieldValues, UseControllerProps } from '@graphcommerce/react-hook-form'
|
|
7
2
|
import { i18n } from '@lingui/core'
|
|
8
3
|
import {
|
|
9
4
|
FormControl,
|
|
@@ -20,8 +15,6 @@ export type RadioButtonGroupProps<T extends FieldValues> = {
|
|
|
20
15
|
options: { label: string; id: string | number }[] | any[]
|
|
21
16
|
helperText?: string
|
|
22
17
|
required?: boolean
|
|
23
|
-
/** @deprecated Form value parsing should happen in the handleSubmit function of the form */
|
|
24
|
-
parseError?: (error: FieldError) => string
|
|
25
18
|
label?: string
|
|
26
19
|
labelKey?: string
|
|
27
20
|
valueKey?: string
|
|
@@ -32,21 +25,27 @@ export type RadioButtonGroupProps<T extends FieldValues> = {
|
|
|
32
25
|
row?: boolean
|
|
33
26
|
} & UseControllerProps<T>
|
|
34
27
|
|
|
35
|
-
export function RadioButtonGroup<TFieldValues extends FieldValues>(
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
28
|
+
export function RadioButtonGroup<TFieldValues extends FieldValues>(
|
|
29
|
+
props: RadioButtonGroupProps<TFieldValues>,
|
|
30
|
+
): JSX.Element {
|
|
31
|
+
const {
|
|
32
|
+
helperText,
|
|
33
|
+
options,
|
|
34
|
+
label,
|
|
35
|
+
name,
|
|
36
|
+
labelKey = 'label',
|
|
37
|
+
valueKey = 'id',
|
|
38
|
+
required,
|
|
39
|
+
emptyOptionLabel,
|
|
40
|
+
returnObject,
|
|
41
|
+
row,
|
|
42
|
+
control,
|
|
43
|
+
defaultValue,
|
|
44
|
+
disabled,
|
|
45
|
+
shouldUnregister,
|
|
46
|
+
...rest
|
|
47
|
+
} = props
|
|
48
|
+
|
|
50
49
|
const theme = useTheme()
|
|
51
50
|
const {
|
|
52
51
|
field: { value, onChange },
|
|
@@ -55,13 +54,12 @@ export function RadioButtonGroup<TFieldValues extends FieldValues>({
|
|
|
55
54
|
name,
|
|
56
55
|
rules: required ? { required: i18n._(/* i18n */ 'This field is required') } : undefined,
|
|
57
56
|
control,
|
|
57
|
+
defaultValue,
|
|
58
|
+
disabled,
|
|
59
|
+
shouldUnregister,
|
|
58
60
|
})
|
|
59
61
|
|
|
60
|
-
|
|
61
|
-
? typeof parseError === 'function'
|
|
62
|
-
? parseError(error)
|
|
63
|
-
: error.message
|
|
64
|
-
: helperText
|
|
62
|
+
const parsedHelperText = error ? error.message : helperText
|
|
65
63
|
|
|
66
64
|
const onRadioChange = (event: ChangeEvent<HTMLInputElement>) => {
|
|
67
65
|
const radioValue = (event.target as HTMLInputElement).value
|
|
@@ -125,7 +123,7 @@ export function RadioButtonGroup<TFieldValues extends FieldValues>({
|
|
|
125
123
|
)
|
|
126
124
|
})}
|
|
127
125
|
</RadioGroup>
|
|
128
|
-
{
|
|
126
|
+
{parsedHelperText && <FormHelperText>{parsedHelperText}</FormHelperText>}
|
|
129
127
|
</FormControl>
|
|
130
128
|
)
|
|
131
129
|
}
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import {
|
|
2
|
-
Controller,
|
|
3
2
|
ControllerProps,
|
|
4
3
|
FieldError,
|
|
5
4
|
FieldValues,
|
|
5
|
+
useController,
|
|
6
6
|
} from '@graphcommerce/react-hook-form'
|
|
7
7
|
import { i18n } from '@lingui/core'
|
|
8
8
|
import {
|
|
@@ -16,8 +16,6 @@ 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 */
|
|
20
|
-
parseError?: (error: FieldError) => string
|
|
21
19
|
required?: boolean
|
|
22
20
|
formControlProps?: FormControlProps
|
|
23
21
|
} & Omit<ControllerProps<T>, 'render'>
|
|
@@ -27,39 +25,40 @@ export function SliderElement<TFieldValues extends FieldValues>({
|
|
|
27
25
|
control,
|
|
28
26
|
label,
|
|
29
27
|
rules = {},
|
|
30
|
-
parseError,
|
|
31
28
|
required,
|
|
32
29
|
formControlProps,
|
|
30
|
+
defaultValue,
|
|
31
|
+
disabled,
|
|
32
|
+
shouldUnregister,
|
|
33
33
|
...other
|
|
34
34
|
}: SliderElementProps<TFieldValues>) {
|
|
35
35
|
if (required && !rules.required) {
|
|
36
36
|
rules.required = i18n._(/* i18n */ 'This field is required')
|
|
37
37
|
}
|
|
38
|
+
|
|
39
|
+
const {
|
|
40
|
+
field,
|
|
41
|
+
fieldState: { invalid, error },
|
|
42
|
+
} = useController({
|
|
43
|
+
name,
|
|
44
|
+
control,
|
|
45
|
+
rules,
|
|
46
|
+
defaultValue,
|
|
47
|
+
disabled,
|
|
48
|
+
shouldUnregister,
|
|
49
|
+
})
|
|
50
|
+
|
|
51
|
+
const parsedHelperText = error ? error.message : null
|
|
52
|
+
|
|
38
53
|
return (
|
|
39
|
-
<
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
: null
|
|
49
|
-
return (
|
|
50
|
-
<FormControl error={invalid} required={required} fullWidth {...formControlProps}>
|
|
51
|
-
{label && (
|
|
52
|
-
<FormLabel component='legend' error={invalid}>
|
|
53
|
-
{label}
|
|
54
|
-
</FormLabel>
|
|
55
|
-
)}
|
|
56
|
-
<Slider {...other} {...field} valueLabelDisplay={other.valueLabelDisplay || 'auto'} />
|
|
57
|
-
{parsedHelperText && (
|
|
58
|
-
<FormHelperText error={invalid}>{parsedHelperText}</FormHelperText>
|
|
59
|
-
)}
|
|
60
|
-
</FormControl>
|
|
61
|
-
)
|
|
62
|
-
}}
|
|
63
|
-
/>
|
|
54
|
+
<FormControl error={invalid} required={required} fullWidth {...formControlProps}>
|
|
55
|
+
{label && (
|
|
56
|
+
<FormLabel component='legend' error={invalid}>
|
|
57
|
+
{label}
|
|
58
|
+
</FormLabel>
|
|
59
|
+
)}
|
|
60
|
+
<Slider {...other} {...field} valueLabelDisplay={other.valueLabelDisplay || 'auto'} />
|
|
61
|
+
{parsedHelperText && <FormHelperText error={invalid}>{parsedHelperText}</FormHelperText>}
|
|
62
|
+
</FormControl>
|
|
64
63
|
)
|
|
65
64
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { FieldValues, ControllerProps, useController } from '@graphcommerce/react-hook-form'
|
|
2
2
|
import { FormControlLabel, FormControlLabelProps, Switch } from '@mui/material'
|
|
3
3
|
|
|
4
4
|
type IProps = Omit<FormControlLabelProps, 'control'>
|
|
@@ -8,18 +8,20 @@ export type SwitchElementProps<T extends FieldValues> = IProps & Omit<Controller
|
|
|
8
8
|
export function SwitchElement<TFieldValues extends FieldValues>({
|
|
9
9
|
name,
|
|
10
10
|
control,
|
|
11
|
+
defaultValue,
|
|
12
|
+
disabled,
|
|
13
|
+
shouldUnregister,
|
|
14
|
+
rules,
|
|
11
15
|
...other
|
|
12
16
|
}: SwitchElementProps<TFieldValues>) {
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
/>
|
|
24
|
-
)
|
|
17
|
+
const { field } = useController({
|
|
18
|
+
name,
|
|
19
|
+
control,
|
|
20
|
+
defaultValue,
|
|
21
|
+
disabled,
|
|
22
|
+
shouldUnregister,
|
|
23
|
+
rules,
|
|
24
|
+
})
|
|
25
|
+
|
|
26
|
+
return <FormControlLabel control={<Switch {...field} checked={!!field.value} />} {...other} />
|
|
25
27
|
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { FieldValues, phonePattern } from '@graphcommerce/react-hook-form'
|
|
2
|
+
import { Trans, t } from '@lingui/macro'
|
|
3
|
+
import { TextFieldElement, TextFieldElementProps } from './TextFieldElement'
|
|
4
|
+
|
|
5
|
+
export type TelephoneElementProps<T extends FieldValues> = TextFieldElementProps<T>
|
|
6
|
+
|
|
7
|
+
export function TelephoneElement<TFieldValues extends FieldValues>(
|
|
8
|
+
props: TelephoneElementProps<TFieldValues>,
|
|
9
|
+
): JSX.Element {
|
|
10
|
+
const { rules, ...rest } = props
|
|
11
|
+
return (
|
|
12
|
+
<TextFieldElement
|
|
13
|
+
type='text'
|
|
14
|
+
label={<Trans>Telephone</Trans>}
|
|
15
|
+
autoComplete='tel'
|
|
16
|
+
rules={{
|
|
17
|
+
pattern: { value: phonePattern, message: t`Invalid phone number` },
|
|
18
|
+
...rules,
|
|
19
|
+
}}
|
|
20
|
+
{...rest}
|
|
21
|
+
/>
|
|
22
|
+
)
|
|
23
|
+
}
|
|
@@ -1,8 +1,14 @@
|
|
|
1
1
|
/* eslint-disable no-nested-ternary */
|
|
2
2
|
import { InputCheckmark } from '@graphcommerce/next-ui'
|
|
3
|
-
import {
|
|
3
|
+
import {
|
|
4
|
+
FieldValues,
|
|
5
|
+
UseControllerProps,
|
|
6
|
+
emailPattern,
|
|
7
|
+
useController,
|
|
8
|
+
} from '@graphcommerce/react-hook-form'
|
|
4
9
|
import { i18n } from '@lingui/core'
|
|
5
10
|
import { TextField, TextFieldProps } from '@mui/material'
|
|
11
|
+
import React, { useState } from 'react'
|
|
6
12
|
|
|
7
13
|
export type TextFieldElementProps<T extends FieldValues = FieldValues> = Omit<
|
|
8
14
|
TextFieldProps,
|
|
@@ -22,7 +28,9 @@ export function TextFieldElement<TFieldValues extends FieldValues>({
|
|
|
22
28
|
control,
|
|
23
29
|
defaultValue,
|
|
24
30
|
rules = validation,
|
|
31
|
+
shouldUnregister,
|
|
25
32
|
showValid,
|
|
33
|
+
disabled,
|
|
26
34
|
...rest
|
|
27
35
|
}: TextFieldElementProps<TFieldValues>): JSX.Element {
|
|
28
36
|
if (required && !rules.required) {
|
|
@@ -31,9 +39,7 @@ export function TextFieldElement<TFieldValues extends FieldValues>({
|
|
|
31
39
|
|
|
32
40
|
if (type === 'email' && !rules.pattern) {
|
|
33
41
|
rules.pattern = {
|
|
34
|
-
|
|
35
|
-
value:
|
|
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,}))$/,
|
|
42
|
+
value: emailPattern,
|
|
37
43
|
message: i18n._(/* i18n */ 'Please enter a valid email address'),
|
|
38
44
|
}
|
|
39
45
|
}
|
|
@@ -41,13 +47,26 @@ export function TextFieldElement<TFieldValues extends FieldValues>({
|
|
|
41
47
|
const {
|
|
42
48
|
field: { onChange, ref, value = '', ...field },
|
|
43
49
|
fieldState: { error },
|
|
44
|
-
} = useController({ name, control, rules, defaultValue })
|
|
50
|
+
} = useController({ name, control, rules, defaultValue, shouldUnregister, disabled })
|
|
51
|
+
|
|
52
|
+
// https://stackoverflow.com/questions/76830737/chrome-autofill-causes-textbox-collision-for-textfield-label-and-value
|
|
53
|
+
const [hasAutofill, setHasAutofill] = useState(false)
|
|
54
|
+
const shrink = hasAutofill || rest.InputLabelProps?.shrink || Boolean(value)
|
|
55
|
+
const onAnimationStart = (e: React.AnimationEvent<HTMLInputElement | HTMLTextAreaElement>) => {
|
|
56
|
+
if (e.target instanceof HTMLElement) {
|
|
57
|
+
const autofilled = !!e.target.matches('*:-webkit-autofill')
|
|
58
|
+
if (e.animationName === 'mui-auto-fill') setHasAutofill(autofilled)
|
|
59
|
+
if (e.animationName === 'mui-auto-fill-cancel') setHasAutofill(autofilled)
|
|
60
|
+
}
|
|
61
|
+
return rest.inputProps?.onAnimationStart?.(e)
|
|
62
|
+
}
|
|
45
63
|
|
|
46
64
|
return (
|
|
47
65
|
<TextField
|
|
48
66
|
{...rest}
|
|
49
67
|
{...field}
|
|
50
68
|
value={value}
|
|
69
|
+
inputProps={{ ...rest.inputProps, onAnimationStart }}
|
|
51
70
|
onChange={(ev) => {
|
|
52
71
|
onChange(type === 'number' && ev.target.value ? Number(ev.target.value) : ev.target.value)
|
|
53
72
|
rest.onChange?.(ev)
|
|
@@ -57,6 +76,7 @@ export function TextFieldElement<TFieldValues extends FieldValues>({
|
|
|
57
76
|
type={type}
|
|
58
77
|
error={Boolean(error) || rest.error}
|
|
59
78
|
helperText={error ? error.message : rest.helperText}
|
|
79
|
+
InputLabelProps={{ ...rest.InputLabelProps, shrink }}
|
|
60
80
|
InputProps={{
|
|
61
81
|
...rest.InputProps,
|
|
62
82
|
endAdornment:
|