@saas-ui/forms 1.0.0-rc.8 → 1.0.1
Sign up to get free protection for your applications and to get access to all the features.
- package/CHANGELOG.md +108 -0
- package/dist/ajv/index.js +94 -94
- package/dist/ajv/index.js.map +1 -1
- package/dist/ajv/index.modern.mjs +94 -94
- package/dist/ajv/index.modern.mjs.map +1 -1
- package/dist/array-field.d.ts +14 -3
- package/dist/array-field.d.ts.map +1 -1
- package/dist/field-resolver.d.ts.map +1 -1
- package/dist/field.d.ts +81 -26
- package/dist/field.d.ts.map +1 -1
- package/dist/fields.d.ts +1 -0
- package/dist/fields.d.ts.map +1 -1
- package/dist/form.d.ts +1 -1
- package/dist/form.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/index.modern.mjs +1 -1
- package/dist/index.modern.mjs.map +1 -1
- package/dist/step-form.d.ts +4 -4
- package/dist/step-form.d.ts.map +1 -1
- package/dist/submit-button.d.ts +2 -1
- package/dist/submit-button.d.ts.map +1 -1
- package/dist/use-step-form.d.ts +6 -2
- package/dist/use-step-form.d.ts.map +1 -1
- package/dist/zod/index.js.map +1 -1
- package/dist/zod/index.modern.mjs.map +1 -1
- package/dist/zod/zod-resolver.d.ts +2 -2
- package/dist/zod/zod-resolver.d.ts.map +1 -1
- package/package.json +10 -10
- package/src/array-field.tsx +21 -22
- package/src/auto-form.tsx +2 -2
- package/src/field-resolver.ts +2 -1
- package/src/field.tsx +183 -72
- package/src/fields.tsx +36 -25
- package/src/form.tsx +1 -2
- package/src/step-form.tsx +13 -8
- package/src/submit-button.tsx +32 -24
- package/src/use-step-form.tsx +24 -10
package/src/array-field.tsx
CHANGED
@@ -1,11 +1,11 @@
|
|
1
1
|
import * as React from 'react'
|
2
2
|
|
3
|
-
import { chakra, ResponsiveValue } from '@chakra-ui/system'
|
3
|
+
import { chakra, ResponsiveValue, forwardRef } from '@chakra-ui/system'
|
4
4
|
import { __DEV__ } from '@chakra-ui/utils'
|
5
5
|
import { AddIcon, MinusIcon } from '@chakra-ui/icons'
|
6
6
|
import { IconButton, ButtonProps } from '@saas-ui/button'
|
7
7
|
|
8
|
-
import { FormLayout } from './layout'
|
8
|
+
import { FormLayout, FormLayoutProps } from './layout'
|
9
9
|
import { BaseField, FieldProps } from './field'
|
10
10
|
|
11
11
|
import { mapNestedFields } from './utils'
|
@@ -28,7 +28,7 @@ interface ArrayField {
|
|
28
28
|
[key: string]: unknown
|
29
29
|
}
|
30
30
|
|
31
|
-
interface ArrayFieldRowProps {
|
31
|
+
interface ArrayFieldRowProps extends FormLayoutProps {
|
32
32
|
/**
|
33
33
|
* Amount of field columns
|
34
34
|
*/
|
@@ -41,21 +41,20 @@ interface ArrayFieldRowProps {
|
|
41
41
|
* The array index
|
42
42
|
*/
|
43
43
|
index: number
|
44
|
-
|
44
|
+
/**
|
45
|
+
* The fields
|
46
|
+
*/
|
45
47
|
children: React.ReactNode
|
46
48
|
}
|
47
49
|
|
48
50
|
export const ArrayFieldRow: React.FC<ArrayFieldRowProps> = ({
|
49
51
|
children,
|
50
|
-
columns,
|
51
|
-
spacing,
|
52
52
|
index,
|
53
|
+
...rowFieldsProps
|
53
54
|
}) => {
|
54
55
|
return (
|
55
56
|
<ArrayFieldRowContainer index={index}>
|
56
|
-
<ArrayFieldRowFields
|
57
|
-
{children}
|
58
|
-
</ArrayFieldRowFields>
|
57
|
+
<ArrayFieldRowFields {...rowFieldsProps}>{children}</ArrayFieldRowFields>
|
59
58
|
<ArrayFieldRemoveButton />
|
60
59
|
</ArrayFieldRowContainer>
|
61
60
|
)
|
@@ -65,7 +64,7 @@ if (__DEV__) {
|
|
65
64
|
ArrayFieldRow.displayName = 'ArrayFieldRow'
|
66
65
|
}
|
67
66
|
|
68
|
-
export interface ArrayFieldRowFieldsProps {
|
67
|
+
export interface ArrayFieldRowFieldsProps extends FormLayoutProps {
|
69
68
|
/**
|
70
69
|
* Amount of field columns
|
71
70
|
*/
|
@@ -74,25 +73,19 @@ export interface ArrayFieldRowFieldsProps {
|
|
74
73
|
* Spacing between fields
|
75
74
|
*/
|
76
75
|
spacing?: ResponsiveValue<string | number>
|
77
|
-
|
76
|
+
/**
|
77
|
+
* The fields
|
78
|
+
*/
|
78
79
|
children: React.ReactNode
|
79
80
|
}
|
80
81
|
|
81
82
|
export const ArrayFieldRowFields: React.FC<ArrayFieldRowFieldsProps> = ({
|
82
83
|
children,
|
83
|
-
columns,
|
84
|
-
spacing,
|
85
84
|
...layoutProps
|
86
85
|
}) => {
|
87
86
|
const { name } = useArrayFieldRowContext()
|
88
87
|
return (
|
89
|
-
<FormLayout
|
90
|
-
flex="1"
|
91
|
-
columns={columns}
|
92
|
-
gridGap={spacing}
|
93
|
-
mr="2"
|
94
|
-
{...layoutProps}
|
95
|
-
>
|
88
|
+
<FormLayout flex="1" mr="2" {...layoutProps}>
|
96
89
|
{mapNestedFields(name, children)}
|
97
90
|
</FormLayout>
|
98
91
|
)
|
@@ -162,7 +155,7 @@ export interface ArrayFieldProps
|
|
162
155
|
extends ArrayFieldOptions,
|
163
156
|
Omit<FieldProps, 'defaultValue'> {}
|
164
157
|
|
165
|
-
export const ArrayField =
|
158
|
+
export const ArrayField = forwardRef(
|
166
159
|
(props: ArrayFieldProps, ref: React.ForwardedRef<UseArrayFieldReturn>) => {
|
167
160
|
const { children, ...containerProps } = props
|
168
161
|
|
@@ -183,7 +176,13 @@ export const ArrayField = React.forwardRef(
|
|
183
176
|
</ArrayFieldContainer>
|
184
177
|
)
|
185
178
|
}
|
186
|
-
)
|
179
|
+
) as ((
|
180
|
+
props: ArrayFieldProps & {
|
181
|
+
ref?: React.ForwardedRef<UseArrayFieldReturn>
|
182
|
+
}
|
183
|
+
) => React.ReactElement) & {
|
184
|
+
displayName: string
|
185
|
+
}
|
187
186
|
|
188
187
|
if (__DEV__) {
|
189
188
|
ArrayField.displayName = 'ArrayField'
|
package/src/auto-form.tsx
CHANGED
@@ -6,7 +6,7 @@ import { __DEV__ } from '@chakra-ui/utils'
|
|
6
6
|
import { Form, FormProps } from './form'
|
7
7
|
import { FormLayout } from './layout'
|
8
8
|
import { Fields } from './fields'
|
9
|
-
import { SubmitButton
|
9
|
+
import { SubmitButton } from './submit-button'
|
10
10
|
import { FieldResolver } from '.'
|
11
11
|
|
12
12
|
interface AutoFormOptions {
|
@@ -48,7 +48,7 @@ export const AutoForm = forwardRef(
|
|
48
48
|
<Form {...rest} schema={schema} ref={ref}>
|
49
49
|
<FormLayout>
|
50
50
|
{<Fields schema={schema} fieldResolver={fieldResolver} />}
|
51
|
-
{submitLabel && <SubmitButton
|
51
|
+
{submitLabel && <SubmitButton>{submitLabel}</SubmitButton>}
|
52
52
|
{children}
|
53
53
|
</FormLayout>
|
54
54
|
</Form>
|
package/src/field-resolver.ts
CHANGED
@@ -16,10 +16,11 @@ export type ObjectSchema = Record<string, SchemaField>
|
|
16
16
|
|
17
17
|
const mapFields = (schema: ObjectSchema): FieldProps[] =>
|
18
18
|
schema &&
|
19
|
-
Object.entries(schema).map(([name, { items, ...field }]) => {
|
19
|
+
Object.entries(schema).map(([name, { items, label, title, ...field }]) => {
|
20
20
|
return {
|
21
21
|
...field,
|
22
22
|
name,
|
23
|
+
label: label || title, // json schema compatibility
|
23
24
|
}
|
24
25
|
})
|
25
26
|
|
package/src/field.tsx
CHANGED
@@ -22,15 +22,29 @@ import {
|
|
22
22
|
Checkbox,
|
23
23
|
Switch,
|
24
24
|
useMergeRefs,
|
25
|
+
InputGroup,
|
26
|
+
InputProps,
|
27
|
+
TextareaProps,
|
28
|
+
SwitchProps,
|
29
|
+
CheckboxProps,
|
30
|
+
PinInputField,
|
31
|
+
HStack,
|
32
|
+
PinInput,
|
33
|
+
UsePinInputProps,
|
34
|
+
SystemProps,
|
25
35
|
} from '@chakra-ui/react'
|
26
|
-
import { __DEV__ } from '@chakra-ui/utils'
|
36
|
+
import { __DEV__, FocusableElement } from '@chakra-ui/utils'
|
27
37
|
|
28
|
-
import { NumberInput } from '@saas-ui/number-input'
|
29
|
-
import { PasswordInput } from '@saas-ui/password-input'
|
30
|
-
import { RadioInput } from '@saas-ui/radio'
|
31
|
-
|
32
|
-
import {
|
33
|
-
|
38
|
+
import { NumberInput, NumberInputProps } from '@saas-ui/number-input'
|
39
|
+
import { PasswordInput, PasswordInputProps } from '@saas-ui/password-input'
|
40
|
+
import { RadioInput, RadioInputProps } from '@saas-ui/radio'
|
41
|
+
|
42
|
+
import {
|
43
|
+
Select,
|
44
|
+
SelectProps,
|
45
|
+
NativeSelect,
|
46
|
+
NativeSelectProps,
|
47
|
+
} from '@saas-ui/select'
|
34
48
|
|
35
49
|
export interface Option {
|
36
50
|
value: string
|
@@ -43,19 +57,6 @@ export type FieldRules = Pick<
|
|
43
57
|
'required' | 'min' | 'max' | 'maxLength' | 'minLength' | 'pattern'
|
44
58
|
>
|
45
59
|
|
46
|
-
export type FieldTypes =
|
47
|
-
| 'text'
|
48
|
-
| 'number'
|
49
|
-
| 'password'
|
50
|
-
| 'textarea'
|
51
|
-
| 'select'
|
52
|
-
| 'native-select'
|
53
|
-
| 'checkbox'
|
54
|
-
| 'radio'
|
55
|
-
| 'switch'
|
56
|
-
| 'pin'
|
57
|
-
| string
|
58
|
-
|
59
60
|
export interface FieldProps<
|
60
61
|
TFieldValues extends FieldValues = FieldValues,
|
61
62
|
TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>
|
@@ -84,11 +85,6 @@ export interface FieldProps<
|
|
84
85
|
'valueAsNumber' | 'valueAsDate' | 'setValueAs' | 'disabled'
|
85
86
|
>
|
86
87
|
/**
|
87
|
-
* Options used for selects and radio fields
|
88
|
-
*/
|
89
|
-
options?: Option[]
|
90
|
-
/**
|
91
|
-
* The field type
|
92
88
|
* Build-in types:
|
93
89
|
* - text
|
94
90
|
* - number
|
@@ -102,16 +98,15 @@ export interface FieldProps<
|
|
102
98
|
* - pin
|
103
99
|
*
|
104
100
|
* Will default to a text field if there is no matching type.
|
105
|
-
* @default 'text'
|
106
101
|
*/
|
107
|
-
type?:
|
102
|
+
type?: string
|
108
103
|
/**
|
109
104
|
* The input placeholder
|
110
105
|
*/
|
111
106
|
placeholder?: string
|
112
107
|
}
|
113
108
|
|
114
|
-
const inputTypes: Record<
|
109
|
+
const inputTypes: Record<string, React.FC<any>> = {}
|
115
110
|
|
116
111
|
const defaultInputType = 'text'
|
117
112
|
|
@@ -160,11 +155,30 @@ if (__DEV__) {
|
|
160
155
|
BaseField.displayName = 'BaseField'
|
161
156
|
}
|
162
157
|
|
163
|
-
export
|
158
|
+
export type As<Props = any> = React.ElementType<Props>
|
159
|
+
|
160
|
+
export type PropsOf<T extends As> = React.ComponentPropsWithoutRef<T> & {
|
161
|
+
type?: FieldTypes
|
162
|
+
}
|
163
|
+
|
164
|
+
/**
|
165
|
+
* Build-in types:
|
166
|
+
* - text
|
167
|
+
* - number
|
168
|
+
* - password
|
169
|
+
* - textarea
|
170
|
+
* - select
|
171
|
+
* - native-select
|
172
|
+
* - checkbox
|
173
|
+
* - radio
|
174
|
+
* - switch
|
175
|
+
* - pin
|
176
|
+
*
|
177
|
+
* Will default to a text field if there is no matching type.
|
178
|
+
*/
|
179
|
+
export const Field = React.forwardRef(
|
164
180
|
<TFieldValues extends FieldValues = FieldValues>(
|
165
|
-
props: FieldProps<TFieldValues>
|
166
|
-
[key: string]: unknown // Make sure attributes of custom components work. Need to change this to a global typedef at some point.
|
167
|
-
},
|
181
|
+
props: FieldProps<TFieldValues> | FieldTypeProps,
|
168
182
|
ref: React.ForwardedRef<FocusableElement>
|
169
183
|
) => {
|
170
184
|
const { type = defaultInputType } = props
|
@@ -172,13 +186,14 @@ export const Field = forwardRef(
|
|
172
186
|
|
173
187
|
return <InputComponent ref={ref} {...props} />
|
174
188
|
}
|
175
|
-
) as <TFieldValues extends FieldValues>(
|
176
|
-
props: FieldProps<TFieldValues> &
|
177
|
-
|
178
|
-
|
179
|
-
|
180
|
-
|
181
|
-
|
189
|
+
) as (<TFieldValues extends FieldValues>(
|
190
|
+
props: FieldProps<TFieldValues> &
|
191
|
+
FieldTypeProps & {
|
192
|
+
ref?: React.ForwardedRef<FocusableElement>
|
193
|
+
}
|
194
|
+
) => React.ReactElement) & {
|
195
|
+
displayName?: string
|
196
|
+
}
|
182
197
|
|
183
198
|
interface CreateFieldProps {
|
184
199
|
displayName: string
|
@@ -190,7 +205,7 @@ const createField = (
|
|
190
205
|
InputComponent: React.FC<any>,
|
191
206
|
{ displayName, hideLabel, BaseField }: CreateFieldProps
|
192
207
|
) => {
|
193
|
-
const Field = forwardRef
|
208
|
+
const Field = forwardRef((props, ref) => {
|
194
209
|
const {
|
195
210
|
id,
|
196
211
|
name,
|
@@ -239,7 +254,7 @@ const createField = (
|
|
239
254
|
return Field
|
240
255
|
}
|
241
256
|
|
242
|
-
export const withControlledInput = (InputComponent: any) => {
|
257
|
+
export const withControlledInput = (InputComponent: React.FC<any>) => {
|
243
258
|
return forwardRef<FieldProps, typeof InputComponent>(
|
244
259
|
({ name, rules, ...inputProps }, ref) => {
|
245
260
|
const { control } = useFormContext()
|
@@ -262,7 +277,7 @@ export const withControlledInput = (InputComponent: any) => {
|
|
262
277
|
)
|
263
278
|
}
|
264
279
|
|
265
|
-
export const withUncontrolledInput = (InputComponent: any) => {
|
280
|
+
export const withUncontrolledInput = (InputComponent: React.FC<any>) => {
|
266
281
|
return forwardRef<FieldProps, typeof InputComponent>(
|
267
282
|
({ name, rules, ...inputProps }, ref) => {
|
268
283
|
const { register } = useFormContext()
|
@@ -294,9 +309,9 @@ export interface RegisterFieldTypeOptions {
|
|
294
309
|
* @param options.isControlled Set this to true if this is a controlled field.
|
295
310
|
* @param options.hideLabel Hide the field label, for example for the checkbox field.
|
296
311
|
*/
|
297
|
-
export const registerFieldType = (
|
312
|
+
export const registerFieldType = <T extends object>(
|
298
313
|
type: string,
|
299
|
-
component: React.FC<
|
314
|
+
component: React.FC<T>,
|
300
315
|
options?: RegisterFieldTypeOptions
|
301
316
|
) => {
|
302
317
|
let InputComponent
|
@@ -313,25 +328,59 @@ export const registerFieldType = (
|
|
313
328
|
.join('')}Field`,
|
314
329
|
hideLabel: options?.hideLabel,
|
315
330
|
BaseField: options?.BaseField || BaseField,
|
316
|
-
})
|
331
|
+
}) as React.FC<T & FieldProps>
|
317
332
|
|
318
333
|
inputTypes[type] = Field
|
319
334
|
|
320
335
|
return Field
|
321
336
|
}
|
322
337
|
|
323
|
-
export
|
338
|
+
export interface InputFieldProps extends InputProps {
|
339
|
+
type?: string
|
340
|
+
leftAddon?: React.ReactNode
|
341
|
+
rightAddon?: React.ReactNode
|
342
|
+
}
|
343
|
+
|
344
|
+
export const InputField = registerFieldType<InputFieldProps>(
|
324
345
|
'text',
|
325
|
-
forwardRef(({ type = 'text', ...rest }, ref) => {
|
326
|
-
|
346
|
+
forwardRef(({ type = 'text', leftAddon, rightAddon, size, ...rest }, ref) => {
|
347
|
+
const input = <Input type={type} size={size} {...rest} ref={ref} />
|
348
|
+
if (leftAddon || rightAddon) {
|
349
|
+
return (
|
350
|
+
<InputGroup size={size}>
|
351
|
+
{leftAddon}
|
352
|
+
{input}
|
353
|
+
{rightAddon}
|
354
|
+
</InputGroup>
|
355
|
+
)
|
356
|
+
}
|
357
|
+
return input
|
327
358
|
})
|
328
359
|
)
|
329
|
-
|
330
|
-
|
331
|
-
|
332
|
-
|
333
|
-
|
334
|
-
export const
|
360
|
+
|
361
|
+
export interface NumberInputFieldProps extends NumberInputProps {
|
362
|
+
type: 'number'
|
363
|
+
}
|
364
|
+
|
365
|
+
export const NumberInputField = registerFieldType<NumberInputFieldProps>(
|
366
|
+
'number',
|
367
|
+
NumberInput,
|
368
|
+
{
|
369
|
+
isControlled: true,
|
370
|
+
}
|
371
|
+
)
|
372
|
+
|
373
|
+
export const PasswordInputField = registerFieldType<PasswordInputProps>(
|
374
|
+
'password',
|
375
|
+
forwardRef((props, ref) => <PasswordInput ref={ref} {...props} />)
|
376
|
+
)
|
377
|
+
|
378
|
+
export const TextareaField = registerFieldType<TextareaProps>(
|
379
|
+
'textarea',
|
380
|
+
Textarea
|
381
|
+
)
|
382
|
+
|
383
|
+
export const SwitchField = registerFieldType<SwitchProps>(
|
335
384
|
'switch',
|
336
385
|
forwardRef(({ type, ...rest }, ref) => {
|
337
386
|
return <Switch {...rest} ref={ref} />
|
@@ -340,32 +389,94 @@ export const SwitchField = registerFieldType(
|
|
340
389
|
isControlled: true,
|
341
390
|
}
|
342
391
|
)
|
343
|
-
|
392
|
+
|
393
|
+
export const SelectField = registerFieldType<SelectProps>('select', Select, {
|
344
394
|
isControlled: true,
|
345
395
|
})
|
346
|
-
|
396
|
+
|
397
|
+
export const CheckboxField = registerFieldType<CheckboxProps>(
|
347
398
|
'checkbox',
|
348
|
-
forwardRef(
|
349
|
-
(
|
350
|
-
|
351
|
-
|
352
|
-
|
353
|
-
|
354
|
-
|
355
|
-
}
|
356
|
-
),
|
399
|
+
forwardRef(({ label, type, ...props }, ref) => {
|
400
|
+
return (
|
401
|
+
<Checkbox ref={ref} {...props}>
|
402
|
+
{label}
|
403
|
+
</Checkbox>
|
404
|
+
)
|
405
|
+
}),
|
357
406
|
{
|
358
407
|
hideLabel: true,
|
359
408
|
}
|
360
409
|
)
|
361
|
-
|
362
|
-
|
363
|
-
|
364
|
-
|
365
|
-
|
366
|
-
|
367
|
-
|
410
|
+
|
411
|
+
export const RadioField = registerFieldType<RadioInputProps>(
|
412
|
+
'radio',
|
413
|
+
RadioInput,
|
414
|
+
{
|
415
|
+
isControlled: true,
|
416
|
+
}
|
417
|
+
)
|
418
|
+
|
419
|
+
export const NativeSelectField = registerFieldType<NativeSelectProps>(
|
368
420
|
'native-select',
|
369
421
|
NativeSelect,
|
370
422
|
{ isControlled: true }
|
371
423
|
)
|
424
|
+
|
425
|
+
export interface PinFieldProps extends Omit<UsePinInputProps, 'type'> {
|
426
|
+
pinLength?: number
|
427
|
+
pinType?: 'alphanumeric' | 'number'
|
428
|
+
spacing?: SystemProps['margin']
|
429
|
+
}
|
430
|
+
|
431
|
+
export const PinField = registerFieldType<PinFieldProps>(
|
432
|
+
'pin',
|
433
|
+
forwardRef((props, ref) => {
|
434
|
+
const { pinLength = 4, pinType, spacing, ...inputProps } = props
|
435
|
+
|
436
|
+
const inputs: React.ReactNode[] = []
|
437
|
+
for (let i = 0; i < pinLength; i++) {
|
438
|
+
inputs.push(<PinInputField key={i} ref={ref} />)
|
439
|
+
}
|
440
|
+
|
441
|
+
return (
|
442
|
+
<HStack spacing={spacing}>
|
443
|
+
<PinInput {...inputProps} type={pinType}>
|
444
|
+
{inputs}
|
445
|
+
</PinInput>
|
446
|
+
</HStack>
|
447
|
+
)
|
448
|
+
}),
|
449
|
+
{
|
450
|
+
isControlled: true,
|
451
|
+
}
|
452
|
+
)
|
453
|
+
|
454
|
+
const fieldTypes = {
|
455
|
+
text: InputField,
|
456
|
+
email: InputField,
|
457
|
+
url: InputField,
|
458
|
+
phone: InputField,
|
459
|
+
number: NumberInputField,
|
460
|
+
password: PasswordInputField,
|
461
|
+
textarea: TextareaField,
|
462
|
+
switch: SwitchField,
|
463
|
+
checkbox: CheckboxField,
|
464
|
+
radio: RadioField,
|
465
|
+
pin: PinField,
|
466
|
+
select: SelectField,
|
467
|
+
'native-select': NativeSelectField,
|
468
|
+
}
|
469
|
+
|
470
|
+
type FieldTypes = typeof fieldTypes
|
471
|
+
|
472
|
+
type FieldType<Props = any> = React.ElementType<Props>
|
473
|
+
|
474
|
+
type TypeProps<P extends FieldType, T> = React.ComponentPropsWithoutRef<P> & {
|
475
|
+
type: T
|
476
|
+
}
|
477
|
+
|
478
|
+
type FieldTypeProps =
|
479
|
+
| {
|
480
|
+
[Property in keyof FieldTypes]: TypeProps<FieldTypes[Property], Property>
|
481
|
+
}[keyof FieldTypes]
|
482
|
+
| { type?: string }
|
package/src/fields.tsx
CHANGED
@@ -8,10 +8,12 @@ import { Field, FieldProps } from './field'
|
|
8
8
|
import { ArrayField } from './array-field'
|
9
9
|
import { ObjectField } from './object-field'
|
10
10
|
import { FieldResolver } from './field-resolver'
|
11
|
+
import { useFormContext } from 'react-hook-form'
|
11
12
|
|
12
13
|
export interface FieldsProps {
|
13
14
|
schema: any
|
14
15
|
fieldResolver?: FieldResolver
|
16
|
+
focusFirstField?: boolean
|
15
17
|
}
|
16
18
|
|
17
19
|
const mapNestedFields = (resolver: FieldResolver, name: string) => {
|
@@ -27,6 +29,7 @@ const mapNestedFields = (resolver: FieldResolver, name: string) => {
|
|
27
29
|
export const Fields: React.FC<FieldsProps> = ({
|
28
30
|
schema,
|
29
31
|
fieldResolver,
|
32
|
+
focusFirstField,
|
30
33
|
...props
|
31
34
|
}) => {
|
32
35
|
const resolver = React.useMemo(
|
@@ -34,34 +37,42 @@ export const Fields: React.FC<FieldsProps> = ({
|
|
34
37
|
[schema, fieldResolver]
|
35
38
|
)
|
36
39
|
|
40
|
+
const fields = React.useMemo(() => resolver.getFields(), [resolver])
|
41
|
+
|
42
|
+
const form = useFormContext()
|
43
|
+
|
44
|
+
React.useEffect(() => {
|
45
|
+
if (focusFirstField && fields[0]?.name) {
|
46
|
+
form.setFocus(fields[0].name)
|
47
|
+
}
|
48
|
+
}, [schema, fieldResolver, focusFirstField])
|
49
|
+
|
37
50
|
return (
|
38
51
|
<FormLayout {...props}>
|
39
|
-
{
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
</ObjectField>
|
59
|
-
)
|
60
|
-
}
|
61
|
-
|
62
|
-
return <Field key={name} name={name} type={type} {...fieldProps} />
|
52
|
+
{fields.map(
|
53
|
+
({
|
54
|
+
name,
|
55
|
+
type,
|
56
|
+
defaultValue,
|
57
|
+
...fieldProps
|
58
|
+
}: FieldProps): React.ReactNode => {
|
59
|
+
if (type === 'array') {
|
60
|
+
return (
|
61
|
+
<ArrayField key={name} name={name} {...fieldProps}>
|
62
|
+
{mapNestedFields(resolver, name)}
|
63
|
+
</ArrayField>
|
64
|
+
)
|
65
|
+
} else if (type === 'object') {
|
66
|
+
return (
|
67
|
+
<ObjectField key={name} name={name} {...fieldProps}>
|
68
|
+
{mapNestedFields(resolver, name)}
|
69
|
+
</ObjectField>
|
70
|
+
)
|
63
71
|
}
|
64
|
-
|
72
|
+
|
73
|
+
return <Field key={name} name={name} type={type} {...fieldProps} />
|
74
|
+
}
|
75
|
+
)}
|
65
76
|
</FormLayout>
|
66
77
|
)
|
67
78
|
}
|
package/src/form.tsx
CHANGED
@@ -16,7 +16,6 @@ import {
|
|
16
16
|
ResolverResult,
|
17
17
|
} from 'react-hook-form'
|
18
18
|
import { objectFieldResolver, FieldResolver } from './field-resolver'
|
19
|
-
import { css } from '@emotion/react'
|
20
19
|
|
21
20
|
export type { UseFormReturn, FieldValues, SubmitHandler }
|
22
21
|
|
@@ -36,7 +35,7 @@ interface FormOptions<TFieldValues extends FieldValues = FieldValues> {
|
|
36
35
|
/**
|
37
36
|
* Ref on the HTMLFormElement.
|
38
37
|
*/
|
39
|
-
formRef?: React.
|
38
|
+
formRef?: React.RefObject<HTMLFormElement>
|
40
39
|
}
|
41
40
|
|
42
41
|
/**
|
package/src/step-form.tsx
CHANGED
@@ -16,7 +16,7 @@ import {
|
|
16
16
|
} from '@saas-ui/stepper'
|
17
17
|
import { Button, ButtonProps } from '@saas-ui/button'
|
18
18
|
|
19
|
-
import { Form
|
19
|
+
import { Form } from './form'
|
20
20
|
import { SubmitButton } from './submit-button'
|
21
21
|
|
22
22
|
import {
|
@@ -24,6 +24,7 @@ import {
|
|
24
24
|
useFormStep,
|
25
25
|
StepFormProvider,
|
26
26
|
UseStepFormProps,
|
27
|
+
FormStepSubmitHandler,
|
27
28
|
} from './use-step-form'
|
28
29
|
|
29
30
|
export interface StepFormProps<TFieldValues extends FieldValues = FieldValues>
|
@@ -53,7 +54,7 @@ export const StepForm = React.forwardRef(
|
|
53
54
|
)
|
54
55
|
}
|
55
56
|
) as <TFieldValues extends FieldValues>(
|
56
|
-
props:
|
57
|
+
props: StepFormProps<TFieldValues> & {
|
57
58
|
ref?: React.ForwardedRef<UseFormReturn<TFieldValues>>
|
58
59
|
}
|
59
60
|
) => React.ReactElement
|
@@ -113,11 +114,14 @@ export const FormStepper: React.FC<StepperStepsProps> = (props) => {
|
|
113
114
|
|
114
115
|
export interface FormStepProps
|
115
116
|
extends FormStepOptions,
|
116
|
-
HTMLChakraProps<'div'> {
|
117
|
+
Omit<HTMLChakraProps<'div'>, 'onSubmit'> {
|
118
|
+
onSubmit?: FormStepSubmitHandler
|
119
|
+
}
|
117
120
|
|
118
121
|
export const FormStep: React.FC<FormStepProps> = (props) => {
|
119
|
-
const { name, schema, resolver, children, className, ...rest } =
|
120
|
-
|
122
|
+
const { name, schema, resolver, children, className, onSubmit, ...rest } =
|
123
|
+
props
|
124
|
+
const step = useFormStep({ name, schema, resolver, onSubmit })
|
121
125
|
|
122
126
|
const { isActive } = step
|
123
127
|
|
@@ -160,11 +164,12 @@ export const NextButton: React.FC<NextButtonProps> = (props) => {
|
|
160
164
|
|
161
165
|
return (
|
162
166
|
<SubmitButton
|
163
|
-
isDisabled={isCompleted}
|
164
|
-
label={isLastStep || isCompleted ? submitLabel : label}
|
165
167
|
{...rest}
|
168
|
+
isDisabled={isCompleted}
|
166
169
|
className={cx('saas-form__next-button', props.className)}
|
167
|
-
|
170
|
+
>
|
171
|
+
{isLastStep || isCompleted ? submitLabel : label}
|
172
|
+
</SubmitButton>
|
168
173
|
)
|
169
174
|
}
|
170
175
|
|