@saas-ui/forms 2.0.0-next.3 → 2.0.0-next.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +43 -0
- package/README.md +53 -6
- package/dist/ajv/index.d.ts +358 -11
- package/dist/ajv/index.js +7 -9
- package/dist/ajv/index.js.map +1 -1
- package/dist/ajv/index.mjs +7 -10
- package/dist/ajv/index.mjs.map +1 -1
- package/dist/index.d.ts +448 -247
- package/dist/index.js +707 -682
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +691 -666
- package/dist/index.mjs.map +1 -1
- package/dist/yup/index.d.ts +580 -21
- package/dist/yup/index.js +6 -10
- package/dist/yup/index.js.map +1 -1
- package/dist/yup/index.mjs +4 -8
- package/dist/yup/index.mjs.map +1 -1
- package/dist/zod/index.d.ts +580 -11
- package/dist/zod/index.js +5 -0
- package/dist/zod/index.js.map +1 -1
- package/dist/zod/index.mjs +5 -1
- package/dist/zod/index.mjs.map +1 -1
- package/package.json +19 -10
- package/src/array-field.tsx +82 -45
- package/src/auto-form.tsx +7 -3
- package/src/base-field.tsx +54 -0
- package/src/create-field.tsx +144 -0
- package/src/create-form.tsx +54 -0
- package/src/default-fields.tsx +163 -0
- package/src/display-field.tsx +9 -11
- package/src/display-if.tsx +20 -13
- package/src/field-resolver.ts +10 -8
- package/src/field.tsx +18 -445
- package/src/fields-context.tsx +23 -0
- package/src/fields.tsx +34 -21
- package/src/form-context.tsx +84 -0
- package/src/form.tsx +69 -52
- package/src/index.ts +44 -4
- 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 +13 -8
- package/src/password-input/password-input.stories.tsx +23 -2
- package/src/password-input/password-input.tsx +6 -6
- 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-context.tsx +130 -0
- package/src/select/select.stories.tsx +116 -85
- package/src/select/select.test.tsx +1 -1
- package/src/select/select.tsx +160 -146
- package/src/step-form.tsx +29 -11
- package/src/submit-button.tsx +5 -1
- package/src/types.ts +144 -0
- package/src/use-array-field.tsx +9 -3
- package/src/utils.ts +23 -1
- package/src/watch-field.tsx +2 -6
- /package/src/radio/{radio.test.tsx → radio-input.test.tsx} +0 -0
@@ -0,0 +1,54 @@
|
|
1
|
+
import React, { ForwardedRef } from 'react'
|
2
|
+
import { FieldsProvider } from './fields-context'
|
3
|
+
import { Form, FieldValues, FormProps, GetResolver } from './form'
|
4
|
+
import { WithFields } from './types'
|
5
|
+
import { objectFieldResolver } from './field-resolver'
|
6
|
+
import { GetFieldResolver } from './field-resolver'
|
7
|
+
import { forwardRef } from '@chakra-ui/react'
|
8
|
+
|
9
|
+
export interface CreateFormProps<FieldDefs> {
|
10
|
+
resolver?: GetResolver
|
11
|
+
fieldResolver?: GetFieldResolver
|
12
|
+
fields?: FieldDefs extends Record<string, React.FC<any>> ? FieldDefs : never
|
13
|
+
}
|
14
|
+
|
15
|
+
export function createForm<FieldDefs, Schema = any>({
|
16
|
+
resolver,
|
17
|
+
fieldResolver = objectFieldResolver,
|
18
|
+
fields,
|
19
|
+
}: CreateFormProps<FieldDefs> = {}) {
|
20
|
+
const CreateForm = forwardRef(
|
21
|
+
<
|
22
|
+
TFieldValues extends FieldValues,
|
23
|
+
TContext extends object = object,
|
24
|
+
TSchema extends Schema = Schema
|
25
|
+
>(
|
26
|
+
props: WithFields<FormProps<TFieldValues, TContext, TSchema>, FieldDefs>,
|
27
|
+
ref: ForwardedRef<HTMLFormElement>
|
28
|
+
) => {
|
29
|
+
const { schema, ...rest } = props
|
30
|
+
return (
|
31
|
+
<FieldsProvider value={fields || {}}>
|
32
|
+
<Form
|
33
|
+
ref={ref}
|
34
|
+
resolver={resolver?.(props.schema)}
|
35
|
+
fieldResolver={fieldResolver?.(schema)}
|
36
|
+
{...rest}
|
37
|
+
/>
|
38
|
+
</FieldsProvider>
|
39
|
+
)
|
40
|
+
}
|
41
|
+
) as (<
|
42
|
+
TFieldValues extends FieldValues,
|
43
|
+
TContext extends object = object,
|
44
|
+
TSchema extends Schema = Schema
|
45
|
+
>(
|
46
|
+
props: WithFields<FormProps<TFieldValues, TContext, TSchema>, FieldDefs> & {
|
47
|
+
ref?: React.ForwardedRef<HTMLFormElement>
|
48
|
+
}
|
49
|
+
) => React.ReactElement) & {
|
50
|
+
displayName?: string
|
51
|
+
}
|
52
|
+
|
53
|
+
return CreateForm
|
54
|
+
}
|
@@ -0,0 +1,163 @@
|
|
1
|
+
import * as React from 'react'
|
2
|
+
|
3
|
+
import {
|
4
|
+
forwardRef,
|
5
|
+
Input,
|
6
|
+
Textarea,
|
7
|
+
Checkbox,
|
8
|
+
Switch,
|
9
|
+
InputGroup,
|
10
|
+
InputProps,
|
11
|
+
TextareaProps,
|
12
|
+
SwitchProps,
|
13
|
+
CheckboxProps,
|
14
|
+
PinInputField,
|
15
|
+
HStack,
|
16
|
+
PinInput,
|
17
|
+
UsePinInputProps,
|
18
|
+
SystemProps,
|
19
|
+
} from '@chakra-ui/react'
|
20
|
+
|
21
|
+
import { NumberInput, NumberInputProps } from './number-input'
|
22
|
+
import { PasswordInput, PasswordInputProps } from './password-input'
|
23
|
+
import { RadioInput, RadioInputProps } from './radio'
|
24
|
+
|
25
|
+
import {
|
26
|
+
Select,
|
27
|
+
SelectButton,
|
28
|
+
SelectList,
|
29
|
+
SelectProps,
|
30
|
+
NativeSelect,
|
31
|
+
NativeSelectProps,
|
32
|
+
} from './select'
|
33
|
+
|
34
|
+
import { createField } from './create-field'
|
35
|
+
|
36
|
+
export interface InputFieldProps extends InputProps {
|
37
|
+
type?: string
|
38
|
+
leftAddon?: React.ReactNode
|
39
|
+
rightAddon?: React.ReactNode
|
40
|
+
}
|
41
|
+
|
42
|
+
export const InputField = createField<InputFieldProps>(
|
43
|
+
forwardRef(({ type = 'text', leftAddon, rightAddon, size, ...rest }, ref) => {
|
44
|
+
const input = <Input type={type} size={size} {...rest} ref={ref} />
|
45
|
+
if (leftAddon || rightAddon) {
|
46
|
+
return (
|
47
|
+
<InputGroup size={size}>
|
48
|
+
{leftAddon}
|
49
|
+
{input}
|
50
|
+
{rightAddon}
|
51
|
+
</InputGroup>
|
52
|
+
)
|
53
|
+
}
|
54
|
+
return input
|
55
|
+
})
|
56
|
+
)
|
57
|
+
|
58
|
+
export interface NumberInputFieldProps extends NumberInputProps {
|
59
|
+
type: 'number'
|
60
|
+
}
|
61
|
+
|
62
|
+
export const NumberInputField = createField<NumberInputFieldProps>(
|
63
|
+
NumberInput,
|
64
|
+
{
|
65
|
+
isControlled: true,
|
66
|
+
}
|
67
|
+
)
|
68
|
+
|
69
|
+
export const PasswordInputField = createField<PasswordInputProps>(
|
70
|
+
forwardRef((props, ref) => <PasswordInput ref={ref} {...props} />)
|
71
|
+
)
|
72
|
+
|
73
|
+
export const TextareaField = createField<TextareaProps>(Textarea)
|
74
|
+
|
75
|
+
export const SwitchField = createField<SwitchProps>(
|
76
|
+
forwardRef(({ type, value, ...rest }, ref) => {
|
77
|
+
return <Switch isChecked={!!value} {...rest} ref={ref} />
|
78
|
+
}),
|
79
|
+
{
|
80
|
+
isControlled: true,
|
81
|
+
}
|
82
|
+
)
|
83
|
+
|
84
|
+
export const SelectField = createField<SelectProps>(
|
85
|
+
forwardRef((props, ref) => {
|
86
|
+
return (
|
87
|
+
<Select ref={ref} {...props}>
|
88
|
+
<SelectButton />
|
89
|
+
<SelectList />
|
90
|
+
</Select>
|
91
|
+
)
|
92
|
+
}),
|
93
|
+
{
|
94
|
+
isControlled: true,
|
95
|
+
}
|
96
|
+
)
|
97
|
+
|
98
|
+
export const CheckboxField = createField<CheckboxProps>(
|
99
|
+
forwardRef(({ label, type, ...props }, ref) => {
|
100
|
+
return (
|
101
|
+
<Checkbox ref={ref} {...props}>
|
102
|
+
{label}
|
103
|
+
</Checkbox>
|
104
|
+
)
|
105
|
+
}),
|
106
|
+
{
|
107
|
+
hideLabel: true,
|
108
|
+
}
|
109
|
+
)
|
110
|
+
|
111
|
+
export const RadioField = createField<RadioInputProps>(RadioInput, {
|
112
|
+
isControlled: true,
|
113
|
+
})
|
114
|
+
|
115
|
+
export const NativeSelectField = createField<NativeSelectProps>(NativeSelect, {
|
116
|
+
isControlled: true,
|
117
|
+
})
|
118
|
+
|
119
|
+
export interface PinFieldProps extends Omit<UsePinInputProps, 'type'> {
|
120
|
+
pinLength?: number
|
121
|
+
pinType?: 'alphanumeric' | 'number'
|
122
|
+
spacing?: SystemProps['margin']
|
123
|
+
}
|
124
|
+
|
125
|
+
export const PinField = createField<PinFieldProps>(
|
126
|
+
forwardRef((props, ref) => {
|
127
|
+
const { pinLength = 4, pinType, spacing, ...inputProps } = props
|
128
|
+
|
129
|
+
const inputs: React.ReactNode[] = []
|
130
|
+
for (let i = 0; i < pinLength; i++) {
|
131
|
+
inputs.push(<PinInputField key={i} ref={ref} />)
|
132
|
+
}
|
133
|
+
|
134
|
+
return (
|
135
|
+
<HStack spacing={spacing}>
|
136
|
+
<PinInput {...inputProps} type={pinType}>
|
137
|
+
{inputs}
|
138
|
+
</PinInput>
|
139
|
+
</HStack>
|
140
|
+
)
|
141
|
+
}),
|
142
|
+
{
|
143
|
+
isControlled: true,
|
144
|
+
}
|
145
|
+
)
|
146
|
+
|
147
|
+
export const defaultFieldTypes = {
|
148
|
+
text: InputField,
|
149
|
+
email: InputField,
|
150
|
+
url: InputField,
|
151
|
+
phone: InputField,
|
152
|
+
number: NumberInputField,
|
153
|
+
password: PasswordInputField,
|
154
|
+
textarea: TextareaField,
|
155
|
+
switch: SwitchField,
|
156
|
+
select: SelectField,
|
157
|
+
checkbox: CheckboxField,
|
158
|
+
radio: RadioField,
|
159
|
+
pin: PinField,
|
160
|
+
'native-select': NativeSelectField,
|
161
|
+
}
|
162
|
+
|
163
|
+
export type DefaultFields = typeof defaultFieldTypes
|
package/src/display-field.tsx
CHANGED
@@ -1,7 +1,5 @@
|
|
1
1
|
import * as React from 'react'
|
2
|
-
import {
|
3
|
-
import { useFormContext } from 'react-hook-form'
|
4
|
-
|
2
|
+
import { useFormContext } from './form-context'
|
5
3
|
import {
|
6
4
|
Text,
|
7
5
|
FormControl,
|
@@ -9,12 +7,16 @@ import {
|
|
9
7
|
FormLabel,
|
10
8
|
} from '@chakra-ui/react'
|
11
9
|
|
12
|
-
import { FieldProps } from './
|
10
|
+
import { FieldProps } from './types'
|
13
11
|
|
14
12
|
export interface DisplayFieldProps
|
15
13
|
extends FormControlProps,
|
16
14
|
Omit<FieldProps, 'type' | 'label'> {}
|
17
|
-
|
15
|
+
/**
|
16
|
+
*
|
17
|
+
*
|
18
|
+
* @see Docs https://saas-ui.dev/
|
19
|
+
*/
|
18
20
|
export const DisplayField: React.FC<DisplayFieldProps> = ({
|
19
21
|
name,
|
20
22
|
label,
|
@@ -31,15 +33,11 @@ export const DisplayField: React.FC<DisplayFieldProps> = ({
|
|
31
33
|
)
|
32
34
|
}
|
33
35
|
|
34
|
-
|
35
|
-
DisplayField.displayName = 'DisplayField'
|
36
|
-
}
|
36
|
+
DisplayField.displayName = 'DisplayField'
|
37
37
|
|
38
38
|
export const FormValue: React.FC<{ name: string }> = ({ name }) => {
|
39
39
|
const { getValues } = useFormContext()
|
40
40
|
return getValues(name) || null
|
41
41
|
}
|
42
42
|
|
43
|
-
|
44
|
-
FormValue.displayName = 'FormValue'
|
45
|
-
}
|
43
|
+
FormValue.displayName = 'FormValue'
|
package/src/display-if.tsx
CHANGED
@@ -1,41 +1,48 @@
|
|
1
1
|
import * as React from 'react'
|
2
|
-
import { __DEV__ } from '@chakra-ui/utils'
|
3
2
|
import {
|
4
|
-
useFormContext,
|
5
3
|
useWatch,
|
6
4
|
FieldValues,
|
7
5
|
UseFormReturn,
|
6
|
+
FieldPath,
|
8
7
|
} from 'react-hook-form'
|
9
8
|
|
9
|
+
import { useFormContext } from './form-context'
|
10
|
+
|
10
11
|
export interface DisplayIfProps<
|
11
|
-
TFieldValues extends FieldValues = FieldValues
|
12
|
+
TFieldValues extends FieldValues = FieldValues,
|
13
|
+
TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>
|
12
14
|
> {
|
13
15
|
children: React.ReactElement
|
14
|
-
name:
|
16
|
+
name: TName
|
15
17
|
defaultValue?: unknown
|
16
18
|
isDisabled?: boolean
|
17
19
|
isExact?: boolean
|
18
20
|
condition?: (value: unknown, context: UseFormReturn<TFieldValues>) => boolean
|
19
21
|
}
|
20
|
-
|
21
|
-
|
22
|
+
/**
|
23
|
+
* Conditionally render parts of a form.
|
24
|
+
*
|
25
|
+
* @see Docs https://saas-ui.dev/docs/components/forms/form
|
26
|
+
*/
|
27
|
+
export const DisplayIf = <
|
28
|
+
TFieldValues extends FieldValues = FieldValues,
|
29
|
+
TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>
|
30
|
+
>({
|
22
31
|
children,
|
23
32
|
name,
|
24
33
|
defaultValue,
|
25
34
|
isDisabled,
|
26
35
|
isExact,
|
27
36
|
condition = (value) => !!value,
|
28
|
-
}: DisplayIfProps<TFieldValues>) => {
|
29
|
-
const value = useWatch({
|
37
|
+
}: DisplayIfProps<TFieldValues, TName>) => {
|
38
|
+
const value = useWatch<TFieldValues>({
|
30
39
|
name,
|
31
|
-
defaultValue,
|
40
|
+
defaultValue: defaultValue as any,
|
32
41
|
disabled: isDisabled,
|
33
42
|
exact: isExact,
|
34
43
|
})
|
35
|
-
const context = useFormContext
|
44
|
+
const context = useFormContext() as any
|
36
45
|
return condition(value, context) ? children : null
|
37
46
|
}
|
38
47
|
|
39
|
-
|
40
|
-
DisplayIf.displayName = 'DisplayIf'
|
41
|
-
}
|
48
|
+
DisplayIf.displayName = 'DisplayIf'
|
package/src/field-resolver.ts
CHANGED
@@ -1,20 +1,22 @@
|
|
1
|
-
import {
|
1
|
+
import { BaseFieldProps } from './types'
|
2
2
|
|
3
3
|
import { get } from '@chakra-ui/utils'
|
4
4
|
|
5
5
|
export type FieldResolver = {
|
6
|
-
getFields():
|
7
|
-
getNestedFields(name: string):
|
6
|
+
getFields(): BaseFieldProps[]
|
7
|
+
getNestedFields(name: string): BaseFieldProps[]
|
8
8
|
}
|
9
9
|
|
10
|
-
|
10
|
+
export type GetFieldResolver<TSchema = any> = (schema: TSchema) => FieldResolver
|
11
|
+
|
12
|
+
interface SchemaField extends BaseFieldProps {
|
11
13
|
items?: SchemaField[]
|
12
14
|
properties?: Record<string, SchemaField>
|
13
15
|
}
|
14
16
|
|
15
17
|
export type ObjectSchema = Record<string, SchemaField>
|
16
18
|
|
17
|
-
const mapFields = (schema: ObjectSchema):
|
19
|
+
const mapFields = (schema: ObjectSchema): BaseFieldProps[] =>
|
18
20
|
schema &&
|
19
21
|
Object.entries(schema).map(([name, { items, label, title, ...field }]) => {
|
20
22
|
return {
|
@@ -24,11 +26,11 @@ const mapFields = (schema: ObjectSchema): FieldProps[] =>
|
|
24
26
|
}
|
25
27
|
})
|
26
28
|
|
27
|
-
export const objectFieldResolver = (schema
|
28
|
-
const getFields = () => {
|
29
|
+
export const objectFieldResolver: GetFieldResolver<ObjectSchema> = (schema) => {
|
30
|
+
const getFields = (): BaseFieldProps[] => {
|
29
31
|
return mapFields(schema)
|
30
32
|
}
|
31
|
-
const getNestedFields = (name: string) => {
|
33
|
+
const getNestedFields = (name: string): BaseFieldProps[] => {
|
32
34
|
const field = get(schema, name)
|
33
35
|
|
34
36
|
if (!field) return []
|