@saas-ui/forms 3.0.0-alpha.1 → 3.0.0-alpha.3

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/src/fields.tsx DELETED
@@ -1,93 +0,0 @@
1
- import * as React from 'react'
2
-
3
- import { ArrayField } from './array-field'
4
- import { Field } from './field'
5
- import { FieldResolver } from './field-resolver'
6
- import { useFormContext } from './form-context'
7
- import { FormLayout } from './form-layout'
8
- import { ObjectField } from './object-field'
9
- import { BaseFieldProps } from './types'
10
-
11
- export interface FieldsProps<TSchema = any> {
12
- schema?: TSchema
13
- fieldResolver?: FieldResolver
14
- focusFirstField?: boolean
15
- }
16
-
17
- const mapNestedFields = (resolver: FieldResolver, name: string) => {
18
- return resolver
19
- .getNestedFields(name)
20
- ?.map(
21
- (
22
- { name, type, ...nestedFieldProps }: BaseFieldProps,
23
- i,
24
- ): React.ReactNode => (
25
- <Field
26
- key={name || i}
27
- name={name}
28
- type={type as any}
29
- {...nestedFieldProps}
30
- />
31
- ),
32
- )
33
- }
34
-
35
- export const AutoFields: React.FC<FieldsProps> = ({
36
- schema: schemaProp,
37
- fieldResolver: fieldResolverProp,
38
- focusFirstField,
39
- ...props
40
- }) => {
41
- const context = useFormContext()
42
- const schema = schemaProp || context.schema
43
- const fieldResolver = fieldResolverProp || context.fieldResolver
44
- const resolver = React.useMemo(() => fieldResolver, [schema, fieldResolver])
45
-
46
- const fields = React.useMemo(() => resolver?.getFields(), [resolver])
47
-
48
- const form = useFormContext()
49
-
50
- React.useEffect(() => {
51
- if (focusFirstField && fields?.[0]?.name) {
52
- form.setFocus(fields[0].name)
53
- }
54
- }, [schema, fieldResolver, focusFirstField])
55
-
56
- if (!resolver) {
57
- return null
58
- }
59
-
60
- return (
61
- <FormLayout {...props}>
62
- {fields?.map(
63
- ({ name, type, ...fieldProps }: BaseFieldProps): React.ReactNode => {
64
- if (type === 'array') {
65
- return (
66
- <ArrayField key={name} name={name} {...fieldProps}>
67
- {mapNestedFields(resolver, name)}
68
- </ArrayField>
69
- )
70
- } else if (type === 'object') {
71
- return (
72
- <ObjectField key={name} name={name} {...fieldProps}>
73
- {mapNestedFields(resolver, name)}
74
- </ObjectField>
75
- )
76
- }
77
-
78
- return (
79
- <Field
80
- key={name}
81
- name={name}
82
- type={type as any}
83
- // defaultValue={defaultValue}
84
- {...fieldProps}
85
- />
86
- )
87
- },
88
- )}
89
- </FormLayout>
90
- )
91
- }
92
-
93
- AutoFields.displayName = 'Fields'
@@ -1,80 +0,0 @@
1
- import * as React from 'react'
2
- import {
3
- FormProvider as HookFormProvider,
4
- FormProviderProps as HookFormProviderProps,
5
- useFormContext as useHookFormContext,
6
- FieldValues,
7
- } from 'react-hook-form'
8
- import { FieldResolver } from './field-resolver'
9
- import type { BaseFieldProps } from './types'
10
-
11
- export type FormContextValue<
12
- TFieldValues extends FieldValues = FieldValues,
13
- TContext = any,
14
- TSchema = any,
15
- > = {
16
- fieldResolver?: FieldResolver
17
- schema?: TSchema
18
- fields?: {
19
- [key: string]: unknown
20
- }
21
- }
22
-
23
- export type FormProviderProps<
24
- TFieldValues extends FieldValues = FieldValues,
25
- TContext = any,
26
- TSchema = any,
27
- > = HookFormProviderProps<TFieldValues, TContext> & {
28
- fieldResolver?: FieldResolver
29
- schema?: TSchema
30
- fields?: {
31
- [key: string]: unknown
32
- }
33
- }
34
-
35
- const FormContext = React.createContext<FormContextValue | null>(null)
36
-
37
- export const useFormContext = <
38
- TFieldValues extends FieldValues = FieldValues,
39
- TContext = any,
40
- TSchema = any,
41
- >() => {
42
- const context = React.useContext(FormContext)
43
- const hookContext = useHookFormContext<TFieldValues, TContext>()
44
-
45
- return {
46
- ...hookContext,
47
- ...context,
48
- }
49
- }
50
-
51
- export const useFieldProps = <TFieldValues extends FieldValues = FieldValues>(
52
- name: string
53
- ): BaseFieldProps<TFieldValues> | undefined => {
54
- const parsedName = name?.replace(/\.[0-9]/g, '.$')
55
- const context = useFormContext()
56
- return (context?.fields?.[parsedName] as any) || {}
57
- }
58
-
59
- export type UseFormReturn<
60
- TFieldValues extends FieldValues = FieldValues,
61
- TContext = any,
62
- TSchema = any,
63
- > = ReturnType<typeof useFormContext<TFieldValues, TContext, TSchema>>
64
-
65
- export const FormProvider = <
66
- TFieldValues extends FieldValues = FieldValues,
67
- TContext = any,
68
- TSchema = any,
69
- >(
70
- props: FormProviderProps<TFieldValues, TContext, TSchema>
71
- ) => {
72
- const { children, fieldResolver, schema, fields, ...rest } = props
73
- return (
74
- <HookFormProvider {...rest}>
75
- <FormContext.Provider value={{ fieldResolver, schema, fields }}>
76
- {children}
77
- </FormContext.Provider>
78
- </HookFormProvider>
79
- )
80
- }
@@ -1,59 +0,0 @@
1
- import { forwardRef } from 'react'
2
-
3
- import {
4
- type HTMLChakraProps,
5
- type RecipeProps,
6
- SimpleGrid,
7
- SimpleGridProps,
8
- useRecipe,
9
- } from '@chakra-ui/react'
10
- import { cx } from '@saas-ui/core/utils'
11
-
12
- export interface FormLayoutOptions {
13
- columns?: SimpleGridProps['columns']
14
- gap?: SimpleGridProps['gap']
15
- }
16
-
17
- export interface FormLayoutProps
18
- extends RecipeProps<'formLayout'>,
19
- FormLayoutOptions,
20
- Omit<HTMLChakraProps<'div'>, 'columns'> {}
21
-
22
- /**
23
- * Create consistent field spacing and positioning.
24
- *
25
- * Renders form items in a `SimpleGrid`
26
- * @see https://chakra-ui.com/docs/layout/simple-grid
27
- *
28
- * @see https://saas-ui.dev/docs/components/forms/form
29
- */
30
- export const FormLayout = forwardRef<HTMLDivElement, FormLayoutProps>(
31
- ({ children, rowGap = 4, ...props }, ref) => {
32
- const recipe = useRecipe({
33
- key: 'formLayout',
34
- })
35
-
36
- const [variantProps, gridProps] = recipe.splitVariantProps(props)
37
-
38
- const styles = recipe(variantProps)
39
-
40
- return (
41
- <SimpleGrid
42
- ref={ref}
43
- {...gridProps}
44
- className={cx('sui-form-layout', props.className)}
45
- css={[
46
- {
47
- rowGap,
48
- },
49
- styles,
50
- props.css,
51
- ]}
52
- >
53
- {children}
54
- </SimpleGrid>
55
- )
56
- },
57
- )
58
-
59
- FormLayout.displayName = 'FormLayout'
package/src/form.tsx DELETED
@@ -1,252 +0,0 @@
1
- import React, { forwardRef } from 'react'
2
-
3
- import { HTMLChakraProps, chakra } from '@chakra-ui/react'
4
- import { type MaybeRenderProp, cx, runIfFn } from '@saas-ui/core/utils'
5
- import {
6
- FieldValues,
7
- ResolverOptions,
8
- ResolverResult,
9
- SubmitErrorHandler,
10
- SubmitHandler,
11
- UseFormProps,
12
- UseFormReturn,
13
- WatchObserver,
14
- useForm,
15
- } from 'react-hook-form'
16
-
17
- import { ArrayField, ArrayFieldProps } from './array-field.tsx'
18
- import { DisplayIf, DisplayIfProps } from './display-if.tsx'
19
- import { FieldResolver } from './field-resolver.tsx'
20
- import { Field as DefaultField } from './field.tsx'
21
- import { AutoFields } from './fields.tsx'
22
- import { FormProvider } from './form-context.tsx'
23
- import { FormLayout } from './form-layout.tsx'
24
- import { ObjectField, ObjectFieldProps } from './object-field.tsx'
25
- import { SubmitButton } from './submit-button.tsx'
26
- import {
27
- DefaultFieldOverrides,
28
- FieldProps,
29
- type FocusableElement,
30
- } from './types.ts'
31
- import { UseArrayFieldReturn } from './use-array-field.tsx'
32
-
33
- export type { UseFormReturn, FieldValues, SubmitHandler }
34
-
35
- export interface FormRenderContext<
36
- TFieldValues extends FieldValues = FieldValues,
37
- TContext extends object = object,
38
- TFieldTypes = FieldProps<TFieldValues>,
39
- > extends UseFormReturn<TFieldValues, TContext> {
40
- Field: React.FC<TFieldTypes & React.RefAttributes<FocusableElement>>
41
- DisplayIf: React.FC<DisplayIfProps<TFieldValues>>
42
- ArrayField: React.FC<
43
- ArrayFieldProps<TFieldValues> & React.RefAttributes<UseArrayFieldReturn>
44
- >
45
- ObjectField: React.FC<ObjectFieldProps<TFieldValues>>
46
- }
47
-
48
- interface FormOptions<
49
- TSchema = unknown,
50
- TFieldValues extends FieldValues = FieldValues,
51
- TContext extends object = object,
52
- TExtraFieldProps extends object = object,
53
- TFieldTypes = FieldProps<TFieldValues, TExtraFieldProps>,
54
- > {
55
- /**
56
- * The form schema.
57
- */
58
- schema?: TSchema
59
- /**
60
- * Triggers when any of the field change.
61
- */
62
- onChange?: WatchObserver<TFieldValues>
63
- /**
64
- * The submit handler.
65
- */
66
- onSubmit: SubmitHandler<TFieldValues>
67
- /**
68
- * Triggers when there are validation errors.
69
- */
70
- onError?: SubmitErrorHandler<TFieldValues>
71
- /**
72
- * The Hook Form state ref.
73
- */
74
- formRef?: React.Ref<UseFormReturn<TFieldValues, TContext>>
75
- /**
76
- * The form children, can be a render prop or a ReactNode.
77
- */
78
- children?: MaybeRenderProp<
79
- FormRenderContext<TFieldValues, TContext, TFieldTypes>
80
- >
81
- /**
82
- * The field resolver, used to resolve the fields from schemas.
83
- */
84
- fieldResolver?: FieldResolver
85
- /**
86
- * Field overrides
87
- */
88
- fields?: DefaultFieldOverrides
89
- }
90
-
91
- export interface FormProps<
92
- TSchema = unknown,
93
- TFieldValues extends FieldValues = FieldValues,
94
- TContext extends object = object,
95
- TExtraFieldProps extends object = object,
96
- TFieldTypes = FieldProps<TFieldValues, TExtraFieldProps>,
97
- > extends UseFormProps<TFieldValues, TContext>,
98
- Omit<
99
- HTMLChakraProps<'form'>,
100
- 'children' | 'onChange' | 'onSubmit' | 'onError'
101
- >,
102
- FormOptions<
103
- TSchema,
104
- TFieldValues,
105
- TContext,
106
- TExtraFieldProps,
107
- TFieldTypes
108
- > {}
109
-
110
- /**
111
- * The wrapper component provides context, state, and focus management.
112
- *
113
- * @see Docs https://saas-ui.dev/docs/components/forms/form
114
- */
115
- export const Form = forwardRef(
116
- <
117
- TSchema = any,
118
- TFieldValues extends FieldValues = FieldValues,
119
- TContext extends object = object,
120
- TExtraFieldProps extends object = object,
121
- TFieldTypes = FieldProps<TFieldValues>,
122
- >(
123
- props: FormProps<
124
- TSchema,
125
- TFieldValues,
126
- TContext,
127
- TExtraFieldProps,
128
- TFieldTypes
129
- >,
130
- ref: React.ForwardedRef<HTMLFormElement>,
131
- ) => {
132
- const {
133
- mode = 'all',
134
- resolver,
135
- fieldResolver,
136
- fields,
137
- reValidateMode,
138
- shouldFocusError,
139
- shouldUnregister,
140
- shouldUseNativeValidation,
141
- criteriaMode,
142
- delayError,
143
- schema,
144
- defaultValues,
145
- values,
146
- context,
147
- resetOptions,
148
- onChange,
149
- onSubmit,
150
- onError,
151
- formRef,
152
- children,
153
- ...rest
154
- } = props
155
-
156
- const form = {
157
- mode,
158
- resolver,
159
- defaultValues,
160
- values,
161
- reValidateMode,
162
- shouldFocusError,
163
- shouldUnregister,
164
- shouldUseNativeValidation,
165
- criteriaMode,
166
- delayError,
167
- context,
168
- resetOptions,
169
- }
170
-
171
- const methods = useForm<TFieldValues, TContext>(form)
172
- const { handleSubmit } = methods
173
-
174
- // This exposes the useForm api through the forwarded ref
175
- React.useImperativeHandle(formRef, () => methods, [formRef, methods])
176
-
177
- React.useEffect(() => {
178
- let subscription: any
179
- if (onChange) {
180
- subscription = methods.watch(onChange)
181
- }
182
- return () => subscription?.unsubscribe()
183
- }, [methods, onChange])
184
-
185
- let _children = children
186
- if (!_children && fieldResolver) {
187
- _children = (
188
- <FormLayout>
189
- <AutoFields />
190
- <SubmitButton {...fields?.submit} />
191
- </FormLayout>
192
- )
193
- }
194
-
195
- return (
196
- <FormProvider
197
- {...methods}
198
- schema={schema}
199
- fieldResolver={fieldResolver}
200
- fields={fields}
201
- >
202
- <chakra.form
203
- ref={ref}
204
- onSubmit={handleSubmit(onSubmit, onError)}
205
- {...rest}
206
- className={cx('sui-form', props.className)}
207
- >
208
- {runIfFn(_children, {
209
- Field: DefaultField as any,
210
- DisplayIf: DisplayIf as any,
211
- ArrayField: ArrayField as any,
212
- ObjectField: ObjectField as any,
213
- ...methods,
214
- })}
215
- </chakra.form>
216
- </FormProvider>
217
- )
218
- },
219
- ) as FormComponent
220
-
221
- export type FormComponent = (<
222
- TSchema = unknown,
223
- TFieldValues extends FieldValues = FieldValues,
224
- TContext extends object = object,
225
- TExtraFieldProps extends object = object,
226
- TFieldTypes = FieldProps<TFieldValues>,
227
- >(
228
- props: FormProps<
229
- TSchema,
230
- TFieldValues,
231
- TContext,
232
- TExtraFieldProps,
233
- TFieldTypes
234
- > & {
235
- ref?: React.ForwardedRef<HTMLFormElement>
236
- },
237
- ) => React.ReactElement) & {
238
- displayName?: string
239
- }
240
-
241
- Form.displayName = 'Form'
242
-
243
- export type GetResolver = <
244
- TFieldValues extends FieldValues,
245
- TContext extends object,
246
- >(
247
- schema: unknown,
248
- ) => (
249
- values: TFieldValues,
250
- context: TContext | undefined,
251
- options: ResolverOptions<TFieldValues>,
252
- ) => Promise<ResolverResult<TFieldValues>>