@saas-ui/forms 1.0.0-rc.8 → 1.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +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
|
|