@saas-ui/forms 2.0.0-next.2 → 2.0.0-next.5
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 +36 -0
- package/README.md +53 -6
- package/dist/ajv/index.d.ts +1 -1
- package/dist/ajv/index.js.map +1 -1
- package/dist/ajv/index.mjs.map +1 -1
- package/dist/index.d.ts +265 -166
- package/dist/index.js +2821 -556
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +2814 -555
- package/dist/index.mjs.map +1 -1
- package/dist/yup/index.d.ts +98 -6
- package/dist/yup/index.js.map +1 -1
- package/dist/yup/index.mjs.map +1 -1
- package/dist/zod/index.d.ts +97 -4
- package/dist/zod/index.js.map +1 -1
- package/dist/zod/index.mjs.map +1 -1
- package/package.json +5 -3
- package/src/array-field.tsx +50 -30
- package/src/auto-form.tsx +7 -3
- package/src/base-field.tsx +59 -0
- package/src/create-field.tsx +143 -0
- package/src/create-form.tsx +31 -0
- package/src/default-fields.tsx +146 -0
- package/src/display-field.tsx +8 -9
- package/src/display-if.tsx +6 -5
- package/src/field-resolver.ts +1 -1
- package/src/field.tsx +14 -444
- package/src/fields-context.tsx +23 -0
- package/src/fields.tsx +18 -8
- package/src/form.tsx +27 -37
- package/src/index.ts +38 -0
- package/src/input-right-button/input-right-button.stories.tsx +1 -1
- package/src/input-right-button/input-right-button.tsx +0 -2
- package/src/layout.tsx +16 -11
- package/src/number-input/number-input.tsx +9 -5
- package/src/object-field.tsx +8 -7
- package/src/password-input/password-input.stories.tsx +23 -2
- package/src/password-input/password-input.tsx +5 -5
- package/src/pin-input/pin-input.tsx +1 -5
- package/src/radio/radio-input.stories.tsx +1 -1
- package/src/radio/radio-input.tsx +12 -10
- package/src/select/native-select.tsx +1 -4
- package/src/select/select.test.tsx +1 -1
- package/src/select/select.tsx +18 -14
- package/src/step-form.tsx +29 -11
- package/src/submit-button.tsx +5 -1
- package/src/types.ts +91 -0
- package/src/utils.ts +15 -0
- /package/src/radio/{radio.test.tsx → radio-input.test.tsx} +0 -0
package/src/field.tsx
CHANGED
@@ -1,45 +1,9 @@
|
|
1
1
|
import * as React from 'react'
|
2
|
-
import {
|
3
|
-
useFormContext,
|
4
|
-
FormState,
|
5
|
-
Controller,
|
6
|
-
get,
|
7
|
-
RegisterOptions,
|
8
|
-
FieldValues,
|
9
|
-
FieldPath,
|
10
|
-
} from 'react-hook-form'
|
11
|
-
|
12
|
-
import {
|
13
|
-
forwardRef,
|
14
|
-
Box,
|
15
|
-
FormControl,
|
16
|
-
FormControlProps,
|
17
|
-
FormLabel,
|
18
|
-
FormHelperText,
|
19
|
-
FormErrorMessage,
|
20
|
-
Input,
|
21
|
-
Textarea,
|
22
|
-
Checkbox,
|
23
|
-
Switch,
|
24
|
-
useMergeRefs,
|
25
|
-
InputGroup,
|
26
|
-
InputProps,
|
27
|
-
TextareaProps,
|
28
|
-
SwitchProps,
|
29
|
-
CheckboxProps,
|
30
|
-
PinInputField,
|
31
|
-
HStack,
|
32
|
-
PinInput,
|
33
|
-
UsePinInputProps,
|
34
|
-
SystemProps,
|
35
|
-
} from '@chakra-ui/react'
|
36
|
-
import { __DEV__, FocusableElement, callAllHandlers } from '@chakra-ui/utils'
|
37
|
-
|
38
|
-
import { NumberInput, NumberInputProps } from './number-input'
|
39
|
-
import { PasswordInput, PasswordInputProps } from './password-input'
|
40
|
-
import { RadioInput, RadioInputProps } from './radio'
|
2
|
+
import { RegisterOptions, FieldValues } from 'react-hook-form'
|
41
3
|
|
42
|
-
import {
|
4
|
+
import { FocusableElement } from '@chakra-ui/utils'
|
5
|
+
import { useField } from './fields-context'
|
6
|
+
import { FieldProps } from './types'
|
43
7
|
|
44
8
|
export interface Option {
|
45
9
|
value: string
|
@@ -52,425 +16,31 @@ export type FieldRules = Pick<
|
|
52
16
|
'required' | 'min' | 'max' | 'maxLength' | 'minLength' | 'pattern'
|
53
17
|
>
|
54
18
|
|
55
|
-
export interface FieldProps<
|
56
|
-
TFieldValues extends FieldValues = FieldValues,
|
57
|
-
TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>
|
58
|
-
> extends Omit<FormControlProps, 'label' | 'type'> {
|
59
|
-
/**
|
60
|
-
* The field name
|
61
|
-
*/
|
62
|
-
name: TName
|
63
|
-
/**
|
64
|
-
* The field label
|
65
|
-
*/
|
66
|
-
label?: string
|
67
|
-
/**
|
68
|
-
* Hide the field label
|
69
|
-
*/
|
70
|
-
hideLabel?: boolean
|
71
|
-
/**
|
72
|
-
* Field help text
|
73
|
-
*/
|
74
|
-
help?: string
|
75
|
-
/**
|
76
|
-
* React hook form rules
|
77
|
-
*/
|
78
|
-
rules?: Omit<
|
79
|
-
RegisterOptions<TFieldValues, TName>,
|
80
|
-
'valueAsNumber' | 'valueAsDate' | 'setValueAs' | 'disabled'
|
81
|
-
>
|
82
|
-
/**
|
83
|
-
* Build-in types:
|
84
|
-
* - text
|
85
|
-
* - number
|
86
|
-
* - password
|
87
|
-
* - textarea
|
88
|
-
* - select
|
89
|
-
* - native-select
|
90
|
-
* - checkbox
|
91
|
-
* - radio
|
92
|
-
* - switch
|
93
|
-
* - pin
|
94
|
-
*
|
95
|
-
* Will default to a text field if there is no matching type.
|
96
|
-
*/
|
97
|
-
type?: string
|
98
|
-
/**
|
99
|
-
* The input placeholder
|
100
|
-
*/
|
101
|
-
placeholder?: string
|
102
|
-
}
|
103
|
-
|
104
|
-
const inputTypes: Record<string, React.FC<any>> = {}
|
105
|
-
|
106
19
|
const defaultInputType = 'text'
|
107
20
|
|
108
|
-
const getInput = (type: string) => {
|
109
|
-
return inputTypes[type] || inputTypes[defaultInputType]
|
110
|
-
}
|
111
|
-
|
112
|
-
const getError = (name: string, formState: FormState<{ [x: string]: any }>) => {
|
113
|
-
return get(formState.errors, name)
|
114
|
-
}
|
115
|
-
|
116
|
-
const isTouched = (
|
117
|
-
name: string,
|
118
|
-
formState: FormState<{ [x: string]: any }>
|
119
|
-
) => {
|
120
|
-
return get(formState.touchedFields, name)
|
121
|
-
}
|
122
|
-
|
123
|
-
export const BaseField: React.FC<FieldProps> = (props) => {
|
124
|
-
const { name, label, help, hideLabel, children, ...controlProps } = props
|
125
|
-
|
126
|
-
const { formState } = useFormContext()
|
127
|
-
|
128
|
-
const error = getError(name, formState)
|
129
|
-
|
130
|
-
return (
|
131
|
-
<FormControl {...controlProps} isInvalid={!!error}>
|
132
|
-
{label && !hideLabel ? <FormLabel>{label}</FormLabel> : null}
|
133
|
-
<Box>
|
134
|
-
{children}
|
135
|
-
{help && !error?.message ? (
|
136
|
-
<FormHelperText>{help}</FormHelperText>
|
137
|
-
) : null}
|
138
|
-
{error?.message && (
|
139
|
-
<FormErrorMessage>{error?.message}</FormErrorMessage>
|
140
|
-
)}
|
141
|
-
</Box>
|
142
|
-
</FormControl>
|
143
|
-
)
|
144
|
-
}
|
145
|
-
|
146
|
-
if (__DEV__) {
|
147
|
-
BaseField.displayName = 'BaseField'
|
148
|
-
}
|
149
|
-
|
150
|
-
export type As<Props = any> = React.ElementType<Props>
|
151
|
-
|
152
|
-
export type PropsOf<T extends As> = React.ComponentPropsWithoutRef<T> & {
|
153
|
-
type?: FieldTypes
|
154
|
-
}
|
155
|
-
|
156
21
|
/**
|
22
|
+
* Form field component.
|
23
|
+
*
|
157
24
|
* Build-in types:
|
158
|
-
* -
|
159
|
-
* - number
|
160
|
-
* - password
|
161
|
-
* - textarea
|
162
|
-
* - select
|
163
|
-
* - native-select
|
164
|
-
* - checkbox
|
165
|
-
* - radio
|
166
|
-
* - switch
|
167
|
-
* - pin
|
25
|
+
* text, number, password, textarea, select, native-select, checkbox, radio, switch, pin
|
168
26
|
*
|
169
27
|
* Will default to a text field if there is no matching type.
|
28
|
+
|
29
|
+
* @see Docs https://saas-ui.dev/docs/components/forms/field
|
170
30
|
*/
|
171
31
|
export const Field = React.forwardRef(
|
172
32
|
<TFieldValues extends FieldValues = FieldValues>(
|
173
|
-
props: FieldProps<TFieldValues
|
33
|
+
props: FieldProps<TFieldValues>,
|
174
34
|
ref: React.ForwardedRef<FocusableElement>
|
175
35
|
) => {
|
176
36
|
const { type = defaultInputType } = props
|
177
|
-
const InputComponent =
|
178
|
-
|
37
|
+
const InputComponent = useField(type)
|
179
38
|
return <InputComponent ref={ref} {...props} />
|
180
39
|
}
|
181
40
|
) as (<TFieldValues extends FieldValues>(
|
182
|
-
props: FieldProps<TFieldValues> &
|
183
|
-
|
184
|
-
|
185
|
-
}
|
41
|
+
props: FieldProps<TFieldValues> & {
|
42
|
+
ref?: React.ForwardedRef<FocusableElement>
|
43
|
+
}
|
186
44
|
) => React.ReactElement) & {
|
187
45
|
displayName?: string
|
188
46
|
}
|
189
|
-
|
190
|
-
interface CreateFieldProps {
|
191
|
-
displayName: string
|
192
|
-
hideLabel?: boolean
|
193
|
-
BaseField: React.FC<any>
|
194
|
-
}
|
195
|
-
|
196
|
-
const createField = (
|
197
|
-
InputComponent: React.FC<any>,
|
198
|
-
{ displayName, hideLabel, BaseField }: CreateFieldProps
|
199
|
-
) => {
|
200
|
-
const Field = forwardRef((props, ref) => {
|
201
|
-
const {
|
202
|
-
id,
|
203
|
-
name,
|
204
|
-
label,
|
205
|
-
help,
|
206
|
-
isDisabled,
|
207
|
-
isInvalid,
|
208
|
-
isReadOnly,
|
209
|
-
isRequired,
|
210
|
-
rules,
|
211
|
-
...inputProps
|
212
|
-
} = props
|
213
|
-
|
214
|
-
const inputRules = {
|
215
|
-
required: isRequired,
|
216
|
-
...rules,
|
217
|
-
}
|
218
|
-
|
219
|
-
return (
|
220
|
-
<BaseField
|
221
|
-
id={id}
|
222
|
-
name={name}
|
223
|
-
label={label}
|
224
|
-
help={help}
|
225
|
-
hideLabel={hideLabel}
|
226
|
-
isDisabled={isDisabled}
|
227
|
-
isInvalid={isInvalid}
|
228
|
-
isReadOnly={isReadOnly}
|
229
|
-
isRequired={isRequired}
|
230
|
-
>
|
231
|
-
<InputComponent
|
232
|
-
ref={ref}
|
233
|
-
id={id}
|
234
|
-
name={name}
|
235
|
-
label={hideLabel ? label : undefined} // Only pass down the label when it should be inline.
|
236
|
-
rules={inputRules}
|
237
|
-
{...inputProps}
|
238
|
-
/>
|
239
|
-
</BaseField>
|
240
|
-
)
|
241
|
-
})
|
242
|
-
Field.displayName = displayName
|
243
|
-
|
244
|
-
return Field
|
245
|
-
}
|
246
|
-
|
247
|
-
export const withControlledInput = (InputComponent: React.FC<any>) => {
|
248
|
-
return forwardRef<FieldProps, typeof InputComponent>(
|
249
|
-
({ name, rules, ...inputProps }, ref) => {
|
250
|
-
const { control } = useFormContext()
|
251
|
-
|
252
|
-
return (
|
253
|
-
<Controller
|
254
|
-
name={name}
|
255
|
-
control={control}
|
256
|
-
rules={rules}
|
257
|
-
render={({ field: { ref: _ref, ...field } }) => (
|
258
|
-
<InputComponent
|
259
|
-
{...field}
|
260
|
-
{...inputProps}
|
261
|
-
onChange={callAllHandlers(inputProps.onChange, field.onChange)}
|
262
|
-
onBlur={callAllHandlers(inputProps.onBlur, field.onBlur)}
|
263
|
-
ref={useMergeRefs(ref, _ref)}
|
264
|
-
/>
|
265
|
-
)}
|
266
|
-
/>
|
267
|
-
)
|
268
|
-
}
|
269
|
-
)
|
270
|
-
}
|
271
|
-
|
272
|
-
export const withUncontrolledInput = (InputComponent: React.FC<any>) => {
|
273
|
-
return forwardRef<FieldProps, typeof InputComponent>(
|
274
|
-
({ name, rules, ...inputProps }, ref) => {
|
275
|
-
const { register } = useFormContext()
|
276
|
-
|
277
|
-
const { ref: _ref, ...field } = register(name, rules)
|
278
|
-
|
279
|
-
return (
|
280
|
-
<InputComponent
|
281
|
-
{...field}
|
282
|
-
{...inputProps}
|
283
|
-
onChange={callAllHandlers(inputProps.onChange, field.onChange)}
|
284
|
-
onBlur={callAllHandlers(inputProps.onBlur, field.onBlur)}
|
285
|
-
ref={useMergeRefs(ref, _ref)}
|
286
|
-
/>
|
287
|
-
)
|
288
|
-
}
|
289
|
-
)
|
290
|
-
}
|
291
|
-
|
292
|
-
export interface RegisterFieldTypeOptions {
|
293
|
-
isControlled?: boolean
|
294
|
-
hideLabel?: boolean
|
295
|
-
BaseField?: React.FC<any>
|
296
|
-
}
|
297
|
-
|
298
|
-
/**
|
299
|
-
* Register a new field type
|
300
|
-
* @param type The name for this field in kebab-case, eg `email` or `array-field`
|
301
|
-
* @param component The React component
|
302
|
-
* @param options
|
303
|
-
* @param options.isControlled Set this to true if this is a controlled field.
|
304
|
-
* @param options.hideLabel Hide the field label, for example for the checkbox field.
|
305
|
-
*/
|
306
|
-
export const registerFieldType = <T extends object>(
|
307
|
-
type: string,
|
308
|
-
component: React.FC<T>,
|
309
|
-
options?: RegisterFieldTypeOptions
|
310
|
-
) => {
|
311
|
-
let InputComponent
|
312
|
-
if (options?.isControlled) {
|
313
|
-
InputComponent = withControlledInput(component)
|
314
|
-
} else {
|
315
|
-
InputComponent = withUncontrolledInput(component)
|
316
|
-
}
|
317
|
-
|
318
|
-
const Field = createField(InputComponent, {
|
319
|
-
displayName: `${type
|
320
|
-
.split('-')
|
321
|
-
.map((part) => part.charAt(0).toUpperCase() + part.slice(1))
|
322
|
-
.join('')}Field`,
|
323
|
-
hideLabel: options?.hideLabel,
|
324
|
-
BaseField: options?.BaseField || BaseField,
|
325
|
-
}) as React.FC<T & FieldProps>
|
326
|
-
|
327
|
-
inputTypes[type] = Field
|
328
|
-
|
329
|
-
return Field
|
330
|
-
}
|
331
|
-
|
332
|
-
export interface InputFieldProps extends InputProps {
|
333
|
-
type?: string
|
334
|
-
leftAddon?: React.ReactNode
|
335
|
-
rightAddon?: React.ReactNode
|
336
|
-
}
|
337
|
-
|
338
|
-
export const InputField = registerFieldType<InputFieldProps>(
|
339
|
-
'text',
|
340
|
-
forwardRef(({ type = 'text', leftAddon, rightAddon, size, ...rest }, ref) => {
|
341
|
-
const input = <Input type={type} size={size} {...rest} ref={ref} />
|
342
|
-
if (leftAddon || rightAddon) {
|
343
|
-
return (
|
344
|
-
<InputGroup size={size}>
|
345
|
-
{leftAddon}
|
346
|
-
{input}
|
347
|
-
{rightAddon}
|
348
|
-
</InputGroup>
|
349
|
-
)
|
350
|
-
}
|
351
|
-
return input
|
352
|
-
})
|
353
|
-
)
|
354
|
-
|
355
|
-
export interface NumberInputFieldProps extends NumberInputProps {
|
356
|
-
type: 'number'
|
357
|
-
}
|
358
|
-
|
359
|
-
export const NumberInputField = registerFieldType<NumberInputFieldProps>(
|
360
|
-
'number',
|
361
|
-
NumberInput,
|
362
|
-
{
|
363
|
-
isControlled: true,
|
364
|
-
}
|
365
|
-
)
|
366
|
-
|
367
|
-
export const PasswordInputField = registerFieldType<PasswordInputProps>(
|
368
|
-
'password',
|
369
|
-
forwardRef((props, ref) => <PasswordInput ref={ref} {...props} />)
|
370
|
-
)
|
371
|
-
|
372
|
-
export const TextareaField = registerFieldType<TextareaProps>(
|
373
|
-
'textarea',
|
374
|
-
Textarea
|
375
|
-
)
|
376
|
-
|
377
|
-
export const SwitchField = registerFieldType<SwitchProps>(
|
378
|
-
'switch',
|
379
|
-
forwardRef(({ type, value, ...rest }, ref) => {
|
380
|
-
return <Switch isChecked={!!value} {...rest} ref={ref} />
|
381
|
-
}),
|
382
|
-
{
|
383
|
-
isControlled: true,
|
384
|
-
}
|
385
|
-
)
|
386
|
-
|
387
|
-
export const SelectField = registerFieldType<SelectProps>('select', Select, {
|
388
|
-
isControlled: true,
|
389
|
-
})
|
390
|
-
|
391
|
-
export const CheckboxField = registerFieldType<CheckboxProps>(
|
392
|
-
'checkbox',
|
393
|
-
forwardRef(({ label, type, ...props }, ref) => {
|
394
|
-
return (
|
395
|
-
<Checkbox ref={ref} {...props}>
|
396
|
-
{label}
|
397
|
-
</Checkbox>
|
398
|
-
)
|
399
|
-
}),
|
400
|
-
{
|
401
|
-
hideLabel: true,
|
402
|
-
}
|
403
|
-
)
|
404
|
-
|
405
|
-
export const RadioField = registerFieldType<RadioInputProps>(
|
406
|
-
'radio',
|
407
|
-
RadioInput,
|
408
|
-
{
|
409
|
-
isControlled: true,
|
410
|
-
}
|
411
|
-
)
|
412
|
-
|
413
|
-
export const NativeSelectField = registerFieldType<NativeSelectProps>(
|
414
|
-
'native-select',
|
415
|
-
NativeSelect,
|
416
|
-
{ isControlled: true }
|
417
|
-
)
|
418
|
-
|
419
|
-
export interface PinFieldProps extends Omit<UsePinInputProps, 'type'> {
|
420
|
-
pinLength?: number
|
421
|
-
pinType?: 'alphanumeric' | 'number'
|
422
|
-
spacing?: SystemProps['margin']
|
423
|
-
}
|
424
|
-
|
425
|
-
export const PinField = registerFieldType<PinFieldProps>(
|
426
|
-
'pin',
|
427
|
-
forwardRef((props, ref) => {
|
428
|
-
const { pinLength = 4, pinType, spacing, ...inputProps } = props
|
429
|
-
|
430
|
-
const inputs: React.ReactNode[] = []
|
431
|
-
for (let i = 0; i < pinLength; i++) {
|
432
|
-
inputs.push(<PinInputField key={i} ref={ref} />)
|
433
|
-
}
|
434
|
-
|
435
|
-
return (
|
436
|
-
<HStack spacing={spacing}>
|
437
|
-
<PinInput {...inputProps} type={pinType}>
|
438
|
-
{inputs}
|
439
|
-
</PinInput>
|
440
|
-
</HStack>
|
441
|
-
)
|
442
|
-
}),
|
443
|
-
{
|
444
|
-
isControlled: true,
|
445
|
-
}
|
446
|
-
)
|
447
|
-
|
448
|
-
const fieldTypes = {
|
449
|
-
text: InputField,
|
450
|
-
email: InputField,
|
451
|
-
url: InputField,
|
452
|
-
phone: InputField,
|
453
|
-
number: NumberInputField,
|
454
|
-
password: PasswordInputField,
|
455
|
-
textarea: TextareaField,
|
456
|
-
switch: SwitchField,
|
457
|
-
checkbox: CheckboxField,
|
458
|
-
radio: RadioField,
|
459
|
-
pin: PinField,
|
460
|
-
select: SelectField,
|
461
|
-
'native-select': NativeSelectField,
|
462
|
-
}
|
463
|
-
|
464
|
-
type FieldTypes = typeof fieldTypes
|
465
|
-
|
466
|
-
type FieldType<Props = any> = React.ElementType<Props>
|
467
|
-
|
468
|
-
type TypeProps<P extends FieldType, T> = React.ComponentPropsWithoutRef<P> & {
|
469
|
-
type: T
|
470
|
-
}
|
471
|
-
|
472
|
-
type FieldTypeProps =
|
473
|
-
| {
|
474
|
-
[Property in keyof FieldTypes]: TypeProps<FieldTypes[Property], Property>
|
475
|
-
}[keyof FieldTypes]
|
476
|
-
| { type?: string }
|
@@ -0,0 +1,23 @@
|
|
1
|
+
import React from 'react'
|
2
|
+
import { defaultFieldTypes, InputField } from './default-fields'
|
3
|
+
|
4
|
+
const FieldsContext = React.createContext<Record<string, React.FC<any>> | null>(
|
5
|
+
null
|
6
|
+
)
|
7
|
+
|
8
|
+
export const FieldsProvider: React.FC<{
|
9
|
+
value: Record<string, React.FC<any>>
|
10
|
+
children: React.ReactNode
|
11
|
+
}> = (props) => {
|
12
|
+
const fields = { ...defaultFieldTypes, ...props.value }
|
13
|
+
return (
|
14
|
+
<FieldsContext.Provider value={fields}>
|
15
|
+
{props.children}
|
16
|
+
</FieldsContext.Provider>
|
17
|
+
)
|
18
|
+
}
|
19
|
+
|
20
|
+
export const useField = (type: string): React.FC<any> => {
|
21
|
+
const context = React.useContext(FieldsContext)
|
22
|
+
return context?.[type] || InputField
|
23
|
+
}
|
package/src/fields.tsx
CHANGED
@@ -1,9 +1,9 @@
|
|
1
1
|
import * as React from 'react'
|
2
|
-
import { __DEV__ } from '@chakra-ui/utils'
|
3
2
|
|
4
3
|
import { Form } from './form'
|
5
4
|
import { FormLayout } from './layout'
|
6
|
-
import {
|
5
|
+
import { BaseFieldProps } from './types'
|
6
|
+
import { Field } from './field'
|
7
7
|
|
8
8
|
import { ArrayField } from './array-field'
|
9
9
|
import { ObjectField } from './object-field'
|
@@ -20,13 +20,21 @@ const mapNestedFields = (resolver: FieldResolver, name: string) => {
|
|
20
20
|
return resolver
|
21
21
|
.getNestedFields(name)
|
22
22
|
?.map(
|
23
|
-
(
|
24
|
-
|
23
|
+
(
|
24
|
+
{ name, type, ...nestedFieldProps }: BaseFieldProps,
|
25
|
+
i
|
26
|
+
): React.ReactNode => (
|
27
|
+
<Field
|
28
|
+
key={name || i}
|
29
|
+
name={name}
|
30
|
+
type={type as any}
|
31
|
+
{...nestedFieldProps}
|
32
|
+
/>
|
25
33
|
)
|
26
34
|
)
|
27
35
|
}
|
28
36
|
|
29
|
-
export const
|
37
|
+
export const AutoFields: React.FC<FieldsProps> = ({
|
30
38
|
schema,
|
31
39
|
fieldResolver,
|
32
40
|
focusFirstField,
|
@@ -55,7 +63,7 @@ export const Fields: React.FC<FieldsProps> = ({
|
|
55
63
|
type,
|
56
64
|
defaultValue,
|
57
65
|
...fieldProps
|
58
|
-
}:
|
66
|
+
}: BaseFieldProps): React.ReactNode => {
|
59
67
|
if (type === 'array') {
|
60
68
|
return (
|
61
69
|
<ArrayField key={name} name={name} {...fieldProps}>
|
@@ -70,11 +78,13 @@ export const Fields: React.FC<FieldsProps> = ({
|
|
70
78
|
)
|
71
79
|
}
|
72
80
|
|
73
|
-
return
|
81
|
+
return (
|
82
|
+
<Field key={name} name={name} type={type as any} {...fieldProps} />
|
83
|
+
)
|
74
84
|
}
|
75
85
|
)}
|
76
86
|
</FormLayout>
|
77
87
|
)
|
78
88
|
}
|
79
89
|
|
80
|
-
|
90
|
+
AutoFields.displayName = 'Fields'
|
package/src/form.tsx
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
import * as React from 'react'
|
2
2
|
|
3
3
|
import { chakra, HTMLChakraProps, forwardRef } from '@chakra-ui/react'
|
4
|
-
import { cx, runIfFn
|
4
|
+
import { cx, runIfFn } from '@chakra-ui/utils'
|
5
5
|
|
6
6
|
import {
|
7
7
|
useForm,
|
@@ -20,19 +20,23 @@ import { MaybeRenderProp } from '@chakra-ui/react-utils'
|
|
20
20
|
|
21
21
|
export type { UseFormReturn, FieldValues, SubmitHandler }
|
22
22
|
|
23
|
-
import {
|
23
|
+
import { FieldProps } from './types'
|
24
24
|
|
25
|
-
|
25
|
+
import { Field as DefaultField } from './field'
|
26
|
+
|
27
|
+
export interface FormRenderContext<
|
26
28
|
TFieldValues extends FieldValues = FieldValues,
|
27
|
-
TContext extends object = object
|
29
|
+
TContext extends object = object,
|
30
|
+
TFieldTypes = FieldProps<TFieldValues>
|
28
31
|
> extends UseFormReturn<TFieldValues, TContext> {
|
29
|
-
Field: React.FC<
|
32
|
+
Field: React.FC<TFieldTypes>
|
30
33
|
}
|
31
34
|
|
32
35
|
interface FormOptions<
|
33
36
|
TFieldValues extends FieldValues = FieldValues,
|
34
37
|
TContext extends object = object,
|
35
|
-
TSchema = any
|
38
|
+
TSchema = any,
|
39
|
+
TFieldTypes = FieldProps<TFieldValues>
|
36
40
|
> {
|
37
41
|
/**
|
38
42
|
* The form schema, supports Yup, Zod, and AJV.
|
@@ -57,25 +61,34 @@ interface FormOptions<
|
|
57
61
|
/**
|
58
62
|
* The form children, can be a render prop or a ReactNode.
|
59
63
|
*/
|
60
|
-
children?: MaybeRenderProp<
|
64
|
+
children?: MaybeRenderProp<
|
65
|
+
FormRenderContext<TFieldValues, TContext, TFieldTypes>
|
66
|
+
>
|
61
67
|
}
|
62
68
|
|
63
69
|
export interface FormProps<
|
64
70
|
TFieldValues extends FieldValues = FieldValues,
|
65
71
|
TContext extends object = object,
|
66
|
-
TSchema = any
|
72
|
+
TSchema = any,
|
73
|
+
TFieldTypes = FieldProps<TFieldValues>
|
67
74
|
> extends UseFormProps<TFieldValues, TContext>,
|
68
75
|
Omit<
|
69
76
|
HTMLChakraProps<'form'>,
|
70
77
|
'children' | 'onChange' | 'onSubmit' | 'onError'
|
71
78
|
>,
|
72
|
-
FormOptions<TFieldValues, TContext, TSchema> {}
|
79
|
+
FormOptions<TFieldValues, TContext, TSchema, TFieldTypes> {}
|
73
80
|
|
81
|
+
/**
|
82
|
+
* The wrapper component provides context, state, and focus management.
|
83
|
+
*
|
84
|
+
* @see Docs https://saas-ui.dev/docs/components/forms/form
|
85
|
+
*/
|
74
86
|
export const Form = forwardRef(
|
75
87
|
<
|
76
88
|
TFieldValues extends FieldValues = FieldValues,
|
77
89
|
TContext extends object = object,
|
78
|
-
TSchema = any
|
90
|
+
TSchema = any,
|
91
|
+
TFieldTypes = FieldProps<TFieldValues>
|
79
92
|
>(
|
80
93
|
props: FormProps<TFieldValues, TContext, TSchema>,
|
81
94
|
ref: React.ForwardedRef<HTMLFormElement>
|
@@ -135,11 +148,6 @@ export const Form = forwardRef(
|
|
135
148
|
return () => subscription?.unsubscribe()
|
136
149
|
}, [methods, onChange])
|
137
150
|
|
138
|
-
const Field: React.FC<FieldProps<TFieldValues>> = React.useMemo(
|
139
|
-
() => (props) => <DefaultField<TFieldValues> {...props} />,
|
140
|
-
[]
|
141
|
-
)
|
142
|
-
|
143
151
|
return (
|
144
152
|
<FormProvider {...methods}>
|
145
153
|
<chakra.form
|
@@ -149,7 +157,7 @@ export const Form = forwardRef(
|
|
149
157
|
className={cx('sui-form', props.className)}
|
150
158
|
>
|
151
159
|
{runIfFn(children, {
|
152
|
-
Field,
|
160
|
+
Field: DefaultField as any,
|
153
161
|
...methods,
|
154
162
|
})}
|
155
163
|
</chakra.form>
|
@@ -159,9 +167,10 @@ export const Form = forwardRef(
|
|
159
167
|
) as (<
|
160
168
|
TFieldValues extends FieldValues,
|
161
169
|
TContext extends object = object,
|
162
|
-
TSchema = any
|
170
|
+
TSchema = any,
|
171
|
+
TFieldTypes = FieldProps<TFieldValues>
|
163
172
|
>(
|
164
|
-
props: FormProps<TFieldValues, TContext, TSchema> & {
|
173
|
+
props: FormProps<TFieldValues, TContext, TSchema, TFieldTypes> & {
|
165
174
|
ref?: React.ForwardedRef<HTMLFormElement>
|
166
175
|
}
|
167
176
|
) => React.ReactElement) & {
|
@@ -186,22 +195,3 @@ export type GetResolver = <
|
|
186
195
|
) => Promise<ResolverResult<TFieldValues>>
|
187
196
|
|
188
197
|
export type GetFieldResolver = (schema: any) => FieldResolver
|
189
|
-
|
190
|
-
export interface CreateFormProps {
|
191
|
-
resolver?: GetResolver
|
192
|
-
}
|
193
|
-
|
194
|
-
export function createForm<Schema = any>({ resolver }: CreateFormProps) {
|
195
|
-
const CreateForm = <
|
196
|
-
TFieldValues extends FieldValues,
|
197
|
-
TContext extends object = object,
|
198
|
-
TSchema extends Schema = Schema
|
199
|
-
>(
|
200
|
-
props: FormProps<TFieldValues, TContext, TSchema>
|
201
|
-
) => {
|
202
|
-
const { schema, ...rest } = props
|
203
|
-
return <Form resolver={resolver?.(props.schema)} {...rest} />
|
204
|
-
}
|
205
|
-
|
206
|
-
return CreateForm
|
207
|
-
}
|