@saas-ui/forms 2.0.0-next.5 → 2.0.0-next.7
Sign up to get free protection for your applications and to get access to all the features.
- package/CHANGELOG.md +20 -0
- 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 +296 -194
- package/dist/index.js +345 -2584
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +347 -2580
- package/dist/index.mjs.map +1 -1
- package/dist/yup/index.d.ts +573 -106
- 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 +490 -14
- 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 +16 -9
- package/src/array-field.tsx +34 -17
- package/src/base-field.tsx +4 -9
- package/src/create-field.tsx +2 -1
- package/src/create-form.tsx +33 -10
- package/src/default-fields.tsx +21 -4
- package/src/display-field.tsx +1 -2
- package/src/display-if.tsx +14 -8
- package/src/field-resolver.ts +10 -8
- package/src/field.tsx +6 -3
- package/src/fields.tsx +16 -13
- package/src/form-context.tsx +84 -0
- package/src/form.tsx +44 -17
- package/src/index.ts +17 -15
- package/src/object-field.tsx +6 -2
- package/src/password-input/password-input.tsx +1 -1
- package/src/select/select-context.tsx +130 -0
- package/src/select/select.stories.tsx +116 -85
- package/src/select/select.tsx +154 -142
- package/src/types.ts +59 -6
- package/src/use-array-field.tsx +9 -3
- package/src/utils.ts +8 -1
- package/src/watch-field.tsx +2 -6
package/dist/zod/index.mjs.map
CHANGED
@@ -1 +1 @@
|
|
1
|
-
{"version":3,"sources":["../../zod/src/zod-resolver.ts","../../zod/src/create-zod-form.ts"],"names":[],"mappings":";AACA,SAAS,mBAAmB;AAC5B,SAAS,WAAW;AAUpB,IAAM,UAAU,CAAC,UAAwB;AACvC,UAAQ,MAAM,KAAK,UAAU;AAAA,IAC3B,KAAK;AACH,aAAO;AAAA,IACT,KAAK;AACH,aAAO;AAAA,IACT,KAAK;AACH,aAAO;AAAA,IACT,KAAK;AACH,aAAO;AAAA,IACT,KAAK;AAAA,IACL;AACE,aAAO;AAAA,EACX;AACF;AAEA,IAAM,iBAAiB,CAAC,OAAY,SAAiB;AA5BrD;AA6BE,UAAO,WAAM,KAAK,IAAI,MAAf,mBAAkB;AAC3B;AAQO,IAAM,sBAAsB,CAAC,WAAuC;AACzE,QAAM,SAAuB,CAAC;AAE9B,MAAI,eAAoC,CAAC;AACzC,MAAI,OAAO,KAAK,aAAa,YAAY;AACvC,mBAAe,OAAO,KAAK,KAAK;AAAA,EAClC,WAAW,OAAO,KAAK,aAAa,aAAa;AAC/C,mBAAe,OAAO,KAAK,MAAM;AAAA,EACnC,OAAO;AACL,WAAO;AAAA,EACT;AAEA,aAAW,QAAQ,cAAc;AAC/B,UAAM,QAAQ,aAAa,IAAI;AAE/B,UAAM,UAAmB,CAAC;AAC1B,QAAI,MAAM,KAAK,aAAa,YAAY;AACtC,cAAQ,MAAM,eAAe,OAAO,WAAW;AAC/C,cAAQ,MAAM,eAAe,OAAO,WAAW;AAAA,IACjD;AAEA,UAAM,OAAO,MAAM,eAAe,aAAa,MAAM,WAAW;AAEhE,WAAO,KAAK;AAAA,MACV;AAAA,MACA,QAAO,6BAAM,UAAS,MAAM,eAAe;AAAA,MAC3C,OAAM,6BAAM,SAAQ,QAAQ,KAAK;AAAA,MACjC,GAAG;AAAA,IACL,CAAC;AAAA,EACH;AACA,SAAO;AACT;AAEO,IAAM,kBAAkB,CAAC,QAAsB,SAAiB;AACrE,SAAO,IAAI,OAAO,KAAK,MAAM,GAAG,IAAI;AACtC;AAEO,IAAM,mBAAmB,CAAyB,WAAc;AACrE,SAAO;AAAA,IACL,YAAY;AACV,aAAO,oBAAoB,MAAM;AAAA,IACnC;AAAA,IACA,gBAAgB,MAAc;AAC5B,aAAO,oBAAoB,gBAAgB,QAAQ,IAAI,CAAC;AAAA,IAC1D;AAAA,EACF;AACF;AAOO,IAAM,UAAU,CAAC,SAAkB;AACxC,SAAO,KAAK,UAAU,IAAI;AAC5B;AAEO,IAAM,eAAe,CAAC,SAAiB;AAC5C,MAAI;AACF,WAAO,KAAK,MAAM,IAAI;AAAA,EACxB,SAAS,GAAP;AACA,WAAO;AAAA,EACT;AACF;;;ACrGA;AAAA,EACE;AAAA,OAIK;AAYA,IAAM,gBAAgB,CAC3B,YACG;AACH,SAAO,WAAW;AAAA,IAChB,UAAU,CAAC,WACT,YAAY,QAAQ,mCAAS,eAAe,mCAAS,eAAe;AAAA,IACtE,GAAG;AAAA,EACL,CAAC;
|
1
|
+
{"version":3,"sources":["../../zod/src/zod-resolver.ts","../../zod/src/create-zod-form.ts","../../zod/src/index.ts"],"names":[],"mappings":";AACA,SAAS,mBAAmB;AAC5B,SAAS,WAAW;AAUpB,IAAM,UAAU,CAAC,UAAwB;AACvC,UAAQ,MAAM,KAAK,UAAU;AAAA,IAC3B,KAAK;AACH,aAAO;AAAA,IACT,KAAK;AACH,aAAO;AAAA,IACT,KAAK;AACH,aAAO;AAAA,IACT,KAAK;AACH,aAAO;AAAA,IACT,KAAK;AAAA,IACL;AACE,aAAO;AAAA,EACX;AACF;AAEA,IAAM,iBAAiB,CAAC,OAAY,SAAiB;AA5BrD;AA6BE,UAAO,WAAM,KAAK,IAAI,MAAf,mBAAkB;AAC3B;AAQO,IAAM,sBAAsB,CAAC,WAAuC;AACzE,QAAM,SAAuB,CAAC;AAE9B,MAAI,eAAoC,CAAC;AACzC,MAAI,OAAO,KAAK,aAAa,YAAY;AACvC,mBAAe,OAAO,KAAK,KAAK;AAAA,EAClC,WAAW,OAAO,KAAK,aAAa,aAAa;AAC/C,mBAAe,OAAO,KAAK,MAAM;AAAA,EACnC,OAAO;AACL,WAAO;AAAA,EACT;AAEA,aAAW,QAAQ,cAAc;AAC/B,UAAM,QAAQ,aAAa,IAAI;AAE/B,UAAM,UAAmB,CAAC;AAC1B,QAAI,MAAM,KAAK,aAAa,YAAY;AACtC,cAAQ,MAAM,eAAe,OAAO,WAAW;AAC/C,cAAQ,MAAM,eAAe,OAAO,WAAW;AAAA,IACjD;AAEA,UAAM,OAAO,MAAM,eAAe,aAAa,MAAM,WAAW;AAEhE,WAAO,KAAK;AAAA,MACV;AAAA,MACA,QAAO,6BAAM,UAAS,MAAM,eAAe;AAAA,MAC3C,OAAM,6BAAM,SAAQ,QAAQ,KAAK;AAAA,MACjC,GAAG;AAAA,IACL,CAAC;AAAA,EACH;AACA,SAAO;AACT;AAEO,IAAM,kBAAkB,CAAC,QAAsB,SAAiB;AACrE,SAAO,IAAI,OAAO,KAAK,MAAM,GAAG,IAAI;AACtC;AAEO,IAAM,mBAAmB,CAAyB,WAAc;AACrE,SAAO;AAAA,IACL,YAAY;AACV,aAAO,oBAAoB,MAAM;AAAA,IACnC;AAAA,IACA,gBAAgB,MAAc;AAC5B,aAAO,oBAAoB,gBAAgB,QAAQ,IAAI,CAAC;AAAA,IAC1D;AAAA,EACF;AACF;AAOO,IAAM,UAAU,CAAC,SAAkB;AACxC,SAAO,KAAK,UAAU,IAAI;AAC5B;AAEO,IAAM,eAAe,CAAC,SAAiB;AAC5C,MAAI;AACF,WAAO,KAAK,MAAM,IAAI;AAAA,EACxB,SAAS,GAAP;AACA,WAAO;AAAA,EACT;AACF;;;ACrGA;AAAA,EACE;AAAA,OAIK;AAYA,IAAM,gBAAgB,CAC3B,YACG;AACH,SAAO,WAAW;AAAA,IAChB,UAAU,CAAC,WACT,YAAY,QAAQ,mCAAS,eAAe,mCAAS,eAAe;AAAA,IACtE,eAAe;AAAA,IACf,GAAG;AAAA,EACL,CAAC;AAWH;;;ACvBO,IAAM,OAAO,cAAc","sourcesContent":["import * as z from 'zod'\nimport { zodResolver } from '@hookform/resolvers/zod'\nimport { get } from '@chakra-ui/utils'\nimport { FieldProps } from '@saas-ui/forms'\n\nexport { zodResolver }\n\nexport type Options = {\n min?: number\n max?: number\n}\n\nconst getType = (field: z.ZodTypeAny) => {\n switch (field._def.typeName) {\n case 'ZodArray':\n return 'array'\n case 'ZodObject':\n return 'object'\n case 'ZodNumber':\n return 'number'\n case 'ZodDate':\n return 'date'\n case 'ZodString':\n default:\n return 'text'\n }\n}\n\nconst getArrayOption = (field: any, name: string) => {\n return field._def[name]?.value\n}\n\n/**\n * A helper function to render forms automatically based on a Yup schema\n *\n * @param schema The Yup schema\n * @returns {FieldProps[]}\n */\nexport const getFieldsFromSchema = (schema: z.ZodTypeAny): FieldProps[] => {\n const fields: FieldProps[] = []\n\n let schemaFields: Record<string, any> = {}\n if (schema._def.typeName === 'ZodArray') {\n schemaFields = schema._def.type.shape\n } else if (schema._def.typeName === 'ZodObject') {\n schemaFields = schema._def.shape()\n } else {\n return fields\n }\n\n for (const name in schemaFields) {\n const field = schemaFields[name]\n\n const options: Options = {}\n if (field._def.typeName === 'ZodArray') {\n options.min = getArrayOption(field, 'minLength')\n options.max = getArrayOption(field, 'maxLength')\n }\n\n const meta = field.description && zodParseMeta(field.description)\n\n fields.push({\n name,\n label: meta?.label || field.description || name,\n type: meta?.type || getType(field),\n ...options,\n })\n }\n return fields\n}\n\nexport const getNestedSchema = (schema: z.ZodTypeAny, path: string) => {\n return get(schema._def.shape(), path)\n}\n\nexport const zodFieldResolver = <T extends z.ZodTypeAny>(schema: T) => {\n return {\n getFields() {\n return getFieldsFromSchema(schema)\n },\n getNestedFields(name: string) {\n return getFieldsFromSchema(getNestedSchema(schema, name))\n },\n }\n}\n\nexport interface ZodMeta {\n label: string\n type?: string\n}\n\nexport const zodMeta = (meta: ZodMeta) => {\n return JSON.stringify(meta)\n}\n\nexport const zodParseMeta = (meta: string) => {\n try {\n return JSON.parse(meta)\n } catch (e) {\n return meta\n }\n}\n","import {\n createForm,\n CreateFormProps,\n WithFields,\n FormProps,\n} from '@saas-ui/forms'\nimport { zodFieldResolver, zodResolver } from './zod-resolver'\nimport { z } from 'zod'\n\ntype ResolverArgs = Parameters<typeof zodResolver>\n\nexport interface CreateZodFormProps<FieldDefs>\n extends CreateFormProps<FieldDefs> {\n schemaOptions?: ResolverArgs[1]\n resolverOptions?: ResolverArgs[2]\n}\n\nexport const createZodForm = <FieldDefs>(\n options?: CreateZodFormProps<FieldDefs>\n) => {\n return createForm({\n resolver: (schema) =>\n zodResolver(schema, options?.schemaOptions, options?.resolverOptions),\n fieldResolver: zodFieldResolver,\n ...options,\n }) as <\n TSchema extends z.AnyZodObject = z.AnyZodObject,\n TContext extends object = object\n >(\n props: WithFields<\n FormProps<z.infer<TSchema>, TContext, TSchema>,\n FieldDefs\n > & {\n ref?: React.ForwardedRef<HTMLFormElement>\n }\n ) => React.ReactElement\n}\n","export {\n zodResolver,\n getFieldsFromSchema,\n getNestedSchema,\n zodFieldResolver,\n zodMeta,\n zodParseMeta,\n} from './zod-resolver'\nexport type { Options, ZodMeta } from './zod-resolver'\nexport { createZodForm } from './create-zod-form'\n\nimport { createZodForm } from './create-zod-form'\n\nexport const Form = createZodForm()\n"]}
|
package/package.json
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
{
|
2
2
|
"name": "@saas-ui/forms",
|
3
|
-
"version": "2.0.0-next.
|
3
|
+
"version": "2.0.0-next.7",
|
4
4
|
"description": "Fully functional forms for Chakra UI.",
|
5
5
|
"source": "src/index.ts",
|
6
6
|
"exports": {
|
@@ -48,7 +48,8 @@
|
|
48
48
|
"build:ajv": "tsup ajv/src/index.ts --config ajv/tsup.config.ts",
|
49
49
|
"lint": "eslint src --ext .ts,.tsx,.js,.jsx --config ../../.eslintrc.js",
|
50
50
|
"lint:staged": "lint-staged --allow-empty --config ../../lint-staged.config.js",
|
51
|
-
"typecheck": "tsc --noEmit"
|
51
|
+
"typecheck": "tsc --noEmit",
|
52
|
+
"tsd": "tsd"
|
52
53
|
},
|
53
54
|
"files": [
|
54
55
|
"dist",
|
@@ -86,10 +87,10 @@
|
|
86
87
|
"url": "https://storybook.saas-ui.dev"
|
87
88
|
},
|
88
89
|
"dependencies": {
|
89
|
-
"@chakra-ui/react-utils": "^2.0.
|
90
|
-
"@chakra-ui/utils": "^2.0.
|
91
|
-
"@saas-ui/core": "2.0.0-next.
|
92
|
-
"react-hook-form": "^7.43.
|
90
|
+
"@chakra-ui/react-utils": "^2.0.12",
|
91
|
+
"@chakra-ui/utils": "^2.0.15",
|
92
|
+
"@saas-ui/core": "2.0.0-next.6",
|
93
|
+
"react-hook-form": "^7.43.9"
|
93
94
|
},
|
94
95
|
"peerDependencies": {
|
95
96
|
"@chakra-ui/react": ">=2.4.9",
|
@@ -100,10 +101,16 @@
|
|
100
101
|
"react-dom": ">=18.0.0"
|
101
102
|
},
|
102
103
|
"devDependencies": {
|
103
|
-
"@hookform/resolvers": "^
|
104
|
+
"@hookform/resolvers": "^3.0.1",
|
104
105
|
"@types/json-schema": "^7.0.11",
|
105
106
|
"ajv": "^8.12.0",
|
106
|
-
"
|
107
|
-
"
|
107
|
+
"ajv-errors": "^3.0.0",
|
108
|
+
"json-schema-to-ts": "^2.7.2",
|
109
|
+
"tsd": "^0.28.1",
|
110
|
+
"yup": "^1.0.2",
|
111
|
+
"zod": "^3.21.4"
|
112
|
+
},
|
113
|
+
"tsd": {
|
114
|
+
"directory": "tests"
|
108
115
|
}
|
109
116
|
}
|
package/src/array-field.tsx
CHANGED
@@ -7,7 +7,7 @@ import {
|
|
7
7
|
Button,
|
8
8
|
ButtonProps,
|
9
9
|
} from '@chakra-ui/react'
|
10
|
-
import {
|
10
|
+
import { PlusIcon, MinusIcon } from '@saas-ui/core/icons'
|
11
11
|
|
12
12
|
import { FormLayout, FormLayoutProps } from './layout'
|
13
13
|
import { BaseField } from './base-field'
|
@@ -27,6 +27,9 @@ import {
|
|
27
27
|
useArrayFieldAddButton,
|
28
28
|
UseArrayFieldReturn,
|
29
29
|
} from './use-array-field'
|
30
|
+
import { FieldPath, FieldValues } from 'react-hook-form'
|
31
|
+
import { isFunction } from '@chakra-ui/utils'
|
32
|
+
import { MaybeRenderProp } from '@chakra-ui/react-utils'
|
30
33
|
|
31
34
|
export interface ArrayFieldButtonProps extends ButtonProps {}
|
32
35
|
|
@@ -167,16 +170,23 @@ export const ArrayFieldAddButton: React.FC<ArrayFieldButtonProps> = (props) => {
|
|
167
170
|
{...useArrayFieldAddButton()}
|
168
171
|
{...props}
|
169
172
|
>
|
170
|
-
{props.children || <
|
173
|
+
{props.children || <PlusIcon />}
|
171
174
|
</Button>
|
172
175
|
)
|
173
176
|
}
|
174
177
|
|
175
178
|
ArrayFieldAddButton.displayName = 'ArrayFieldAddButton'
|
176
179
|
|
177
|
-
export interface ArrayFieldProps
|
178
|
-
extends
|
179
|
-
|
180
|
+
export interface ArrayFieldProps<
|
181
|
+
TFieldValues extends FieldValues = FieldValues,
|
182
|
+
TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>
|
183
|
+
> extends ArrayFieldOptions<TFieldValues, TName>,
|
184
|
+
Omit<
|
185
|
+
BaseFieldProps<TFieldValues, TName>,
|
186
|
+
'name' | 'defaultValue' | 'children'
|
187
|
+
> {
|
188
|
+
children: MaybeRenderProp<ArrayField[]>
|
189
|
+
}
|
180
190
|
|
181
191
|
/**
|
182
192
|
* The wrapper component that composes the default ArrayField functionality.
|
@@ -187,19 +197,21 @@ export const ArrayField = forwardRef(
|
|
187
197
|
(props: ArrayFieldProps, ref: React.ForwardedRef<UseArrayFieldReturn>) => {
|
188
198
|
const { children, ...containerProps } = props
|
189
199
|
|
200
|
+
const rowFn = isFunction(children)
|
201
|
+
? children
|
202
|
+
: (fields: ArrayField[]) => (
|
203
|
+
<>
|
204
|
+
{fields.map(({ id }, index: number) => (
|
205
|
+
<ArrayFieldRow key={id} index={index}>
|
206
|
+
{children}
|
207
|
+
</ArrayFieldRow>
|
208
|
+
)) || null}
|
209
|
+
</>
|
210
|
+
)
|
211
|
+
|
190
212
|
return (
|
191
213
|
<ArrayFieldContainer ref={ref} {...containerProps}>
|
192
|
-
<ArrayFieldRows>
|
193
|
-
{(fields: ArrayField[]) => (
|
194
|
-
<>
|
195
|
-
{fields.map(({ id }, index: number) => (
|
196
|
-
<ArrayFieldRow key={id} index={index}>
|
197
|
-
{children}
|
198
|
-
</ArrayFieldRow>
|
199
|
-
))}
|
200
|
-
</>
|
201
|
-
)}
|
202
|
-
</ArrayFieldRows>
|
214
|
+
<ArrayFieldRows>{rowFn as any}</ArrayFieldRows>
|
203
215
|
<ArrayFieldAddButton />
|
204
216
|
</ArrayFieldContainer>
|
205
217
|
)
|
@@ -227,6 +239,11 @@ export const ArrayFieldRows = ({
|
|
227
239
|
|
228
240
|
ArrayFieldRows.displayName = 'ArrayFieldRows'
|
229
241
|
|
242
|
+
export interface ArrayFieldContainerProps
|
243
|
+
extends Omit<ArrayFieldProps, 'children'> {
|
244
|
+
children: React.ReactNode
|
245
|
+
}
|
246
|
+
|
230
247
|
/**
|
231
248
|
* The container component provides context and state management.
|
232
249
|
*
|
@@ -242,7 +259,7 @@ export const ArrayFieldContainer = React.forwardRef(
|
|
242
259
|
max,
|
243
260
|
children,
|
244
261
|
...fieldProps
|
245
|
-
}:
|
262
|
+
}: ArrayFieldContainerProps,
|
246
263
|
ref: React.ForwardedRef<UseArrayFieldReturn>
|
247
264
|
) => {
|
248
265
|
const context = useArrayField({
|
package/src/base-field.tsx
CHANGED
@@ -1,11 +1,5 @@
|
|
1
1
|
import * as React from 'react'
|
2
|
-
import {
|
3
|
-
useFormContext,
|
4
|
-
FormState,
|
5
|
-
get,
|
6
|
-
RegisterOptions,
|
7
|
-
FieldValues,
|
8
|
-
} from 'react-hook-form'
|
2
|
+
import { FormState, get } from 'react-hook-form'
|
9
3
|
|
10
4
|
import {
|
11
5
|
Box,
|
@@ -14,8 +8,9 @@ import {
|
|
14
8
|
FormHelperText,
|
15
9
|
FormErrorMessage,
|
16
10
|
} from '@chakra-ui/react'
|
17
|
-
|
18
|
-
import {
|
11
|
+
|
12
|
+
import { useFormContext } from './form-context'
|
13
|
+
|
19
14
|
import { BaseFieldProps, FieldProps } from './types'
|
20
15
|
|
21
16
|
const getError = (name: string, formState: FormState<{ [x: string]: any }>) => {
|
package/src/create-field.tsx
CHANGED
@@ -1,10 +1,11 @@
|
|
1
1
|
import * as React from 'react'
|
2
|
-
import {
|
2
|
+
import { Controller } from 'react-hook-form'
|
3
3
|
|
4
4
|
import { forwardRef, useMergeRefs } from '@chakra-ui/react'
|
5
5
|
import { callAllHandlers } from '@chakra-ui/utils'
|
6
6
|
import { BaseFieldProps, FieldProps } from './types'
|
7
7
|
import { BaseField } from './base-field'
|
8
|
+
import { useFormContext } from './form-context'
|
8
9
|
|
9
10
|
interface CreateFieldProps {
|
10
11
|
displayName: string
|
package/src/create-form.tsx
CHANGED
@@ -1,30 +1,53 @@
|
|
1
|
-
import React from 'react'
|
1
|
+
import React, { ForwardedRef } from 'react'
|
2
2
|
import { FieldsProvider } from './fields-context'
|
3
3
|
import { Form, FieldValues, FormProps, GetResolver } from './form'
|
4
4
|
import { WithFields } from './types'
|
5
|
+
import { objectFieldResolver } from './field-resolver'
|
6
|
+
import { GetFieldResolver } from './field-resolver'
|
7
|
+
import { forwardRef } from '@chakra-ui/react'
|
5
8
|
|
6
9
|
export interface CreateFormProps<FieldDefs> {
|
7
10
|
resolver?: GetResolver
|
11
|
+
fieldResolver?: GetFieldResolver
|
8
12
|
fields?: FieldDefs extends Record<string, React.FC<any>> ? FieldDefs : never
|
9
13
|
}
|
10
14
|
|
11
15
|
export function createForm<FieldDefs, Schema = any>({
|
12
16
|
resolver,
|
17
|
+
fieldResolver = objectFieldResolver,
|
13
18
|
fields,
|
14
19
|
}: CreateFormProps<FieldDefs> = {}) {
|
15
|
-
const CreateForm =
|
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 (<
|
16
42
|
TFieldValues extends FieldValues,
|
17
43
|
TContext extends object = object,
|
18
44
|
TSchema extends Schema = Schema
|
19
45
|
>(
|
20
|
-
props: WithFields<FormProps<TFieldValues, TContext, TSchema>, FieldDefs>
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
<Form resolver={resolver?.(props.schema)} {...rest} />
|
26
|
-
</FieldsProvider>
|
27
|
-
)
|
46
|
+
props: WithFields<FormProps<TFieldValues, TContext, TSchema>, FieldDefs> & {
|
47
|
+
ref?: React.ForwardedRef<HTMLFormElement>
|
48
|
+
}
|
49
|
+
) => React.ReactElement) & {
|
50
|
+
displayName?: string
|
28
51
|
}
|
29
52
|
|
30
53
|
return CreateForm
|
package/src/default-fields.tsx
CHANGED
@@ -22,7 +22,14 @@ import { NumberInput, NumberInputProps } from './number-input'
|
|
22
22
|
import { PasswordInput, PasswordInputProps } from './password-input'
|
23
23
|
import { RadioInput, RadioInputProps } from './radio'
|
24
24
|
|
25
|
-
import {
|
25
|
+
import {
|
26
|
+
Select,
|
27
|
+
SelectButton,
|
28
|
+
SelectList,
|
29
|
+
SelectProps,
|
30
|
+
NativeSelect,
|
31
|
+
NativeSelectProps,
|
32
|
+
} from './select'
|
26
33
|
|
27
34
|
import { createField } from './create-field'
|
28
35
|
|
@@ -74,9 +81,19 @@ export const SwitchField = createField<SwitchProps>(
|
|
74
81
|
}
|
75
82
|
)
|
76
83
|
|
77
|
-
export const SelectField = createField<SelectProps>(
|
78
|
-
|
79
|
-
|
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
|
+
)
|
80
97
|
|
81
98
|
export const CheckboxField = createField<CheckboxProps>(
|
82
99
|
forwardRef(({ label, type, ...props }, ref) => {
|
package/src/display-field.tsx
CHANGED
package/src/display-if.tsx
CHANGED
@@ -1,16 +1,19 @@
|
|
1
1
|
import * as React from 'react'
|
2
2
|
import {
|
3
|
-
useFormContext,
|
4
3
|
useWatch,
|
5
4
|
FieldValues,
|
6
5
|
UseFormReturn,
|
6
|
+
FieldPath,
|
7
7
|
} from 'react-hook-form'
|
8
8
|
|
9
|
+
import { useFormContext } from './form-context'
|
10
|
+
|
9
11
|
export interface DisplayIfProps<
|
10
|
-
TFieldValues extends FieldValues = FieldValues
|
12
|
+
TFieldValues extends FieldValues = FieldValues,
|
13
|
+
TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>
|
11
14
|
> {
|
12
15
|
children: React.ReactElement
|
13
|
-
name:
|
16
|
+
name: TName
|
14
17
|
defaultValue?: unknown
|
15
18
|
isDisabled?: boolean
|
16
19
|
isExact?: boolean
|
@@ -21,21 +24,24 @@ export interface DisplayIfProps<
|
|
21
24
|
*
|
22
25
|
* @see Docs https://saas-ui.dev/docs/components/forms/form
|
23
26
|
*/
|
24
|
-
export const DisplayIf = <
|
27
|
+
export const DisplayIf = <
|
28
|
+
TFieldValues extends FieldValues = FieldValues,
|
29
|
+
TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>
|
30
|
+
>({
|
25
31
|
children,
|
26
32
|
name,
|
27
33
|
defaultValue,
|
28
34
|
isDisabled,
|
29
35
|
isExact,
|
30
36
|
condition = (value) => !!value,
|
31
|
-
}: DisplayIfProps<TFieldValues>) => {
|
32
|
-
const value = useWatch({
|
37
|
+
}: DisplayIfProps<TFieldValues, TName>) => {
|
38
|
+
const value = useWatch<TFieldValues>({
|
33
39
|
name,
|
34
|
-
defaultValue,
|
40
|
+
defaultValue: defaultValue as any,
|
35
41
|
disabled: isDisabled,
|
36
42
|
exact: isExact,
|
37
43
|
})
|
38
|
-
const context = useFormContext
|
44
|
+
const context = useFormContext() as any
|
39
45
|
return condition(value, context) ? children : null
|
40
46
|
}
|
41
47
|
|
package/src/field-resolver.ts
CHANGED
@@ -1,20 +1,22 @@
|
|
1
|
-
import { BaseFieldProps
|
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 []
|
package/src/field.tsx
CHANGED
@@ -4,6 +4,7 @@ import { RegisterOptions, FieldValues } from 'react-hook-form'
|
|
4
4
|
import { FocusableElement } from '@chakra-ui/utils'
|
5
5
|
import { useField } from './fields-context'
|
6
6
|
import { FieldProps } from './types'
|
7
|
+
import { useFieldProps } from './form-context'
|
7
8
|
|
8
9
|
export interface Option {
|
9
10
|
value: string
|
@@ -33,9 +34,11 @@ export const Field = React.forwardRef(
|
|
33
34
|
props: FieldProps<TFieldValues>,
|
34
35
|
ref: React.ForwardedRef<FocusableElement>
|
35
36
|
) => {
|
36
|
-
const { type = defaultInputType } = props
|
37
|
-
const
|
38
|
-
|
37
|
+
const { type = defaultInputType, name } = props
|
38
|
+
const overrides = useFieldProps(name)
|
39
|
+
const InputComponent = useField(overrides?.type || type)
|
40
|
+
|
41
|
+
return <InputComponent ref={ref} {...props} {...overrides} />
|
39
42
|
}
|
40
43
|
) as (<TFieldValues extends FieldValues>(
|
41
44
|
props: FieldProps<TFieldValues> & {
|
package/src/fields.tsx
CHANGED
@@ -1,6 +1,5 @@
|
|
1
1
|
import * as React from 'react'
|
2
2
|
|
3
|
-
import { Form } from './form'
|
4
3
|
import { FormLayout } from './layout'
|
5
4
|
import { BaseFieldProps } from './types'
|
6
5
|
import { Field } from './field'
|
@@ -8,10 +7,10 @@ import { Field } from './field'
|
|
8
7
|
import { ArrayField } from './array-field'
|
9
8
|
import { ObjectField } from './object-field'
|
10
9
|
import { FieldResolver } from './field-resolver'
|
11
|
-
import { useFormContext } from '
|
10
|
+
import { useFormContext } from './form-context'
|
12
11
|
|
13
|
-
export interface FieldsProps {
|
14
|
-
schema
|
12
|
+
export interface FieldsProps<TSchema = any> {
|
13
|
+
schema?: TSchema
|
15
14
|
fieldResolver?: FieldResolver
|
16
15
|
focusFirstField?: boolean
|
17
16
|
}
|
@@ -35,29 +34,33 @@ const mapNestedFields = (resolver: FieldResolver, name: string) => {
|
|
35
34
|
}
|
36
35
|
|
37
36
|
export const AutoFields: React.FC<FieldsProps> = ({
|
38
|
-
schema,
|
39
|
-
fieldResolver,
|
37
|
+
schema: schemaProp,
|
38
|
+
fieldResolver: fieldResolverProp,
|
40
39
|
focusFirstField,
|
41
40
|
...props
|
42
41
|
}) => {
|
43
|
-
const
|
44
|
-
|
45
|
-
|
46
|
-
)
|
42
|
+
const context = useFormContext()
|
43
|
+
const schema = schemaProp || context.schema
|
44
|
+
const fieldResolver = fieldResolverProp || context.fieldResolver
|
45
|
+
const resolver = React.useMemo(() => fieldResolver, [schema, fieldResolver])
|
47
46
|
|
48
|
-
const fields = React.useMemo(() => resolver
|
47
|
+
const fields = React.useMemo(() => resolver?.getFields(), [resolver])
|
49
48
|
|
50
49
|
const form = useFormContext()
|
51
50
|
|
52
51
|
React.useEffect(() => {
|
53
|
-
if (focusFirstField && fields[0]?.name) {
|
52
|
+
if (focusFirstField && fields?.[0]?.name) {
|
54
53
|
form.setFocus(fields[0].name)
|
55
54
|
}
|
56
55
|
}, [schema, fieldResolver, focusFirstField])
|
57
56
|
|
57
|
+
if (!resolver) {
|
58
|
+
return null
|
59
|
+
}
|
60
|
+
|
58
61
|
return (
|
59
62
|
<FormLayout {...props}>
|
60
|
-
{fields
|
63
|
+
{fields?.map(
|
61
64
|
({
|
62
65
|
name,
|
63
66
|
type,
|
@@ -0,0 +1,84 @@
|
|
1
|
+
import { createContext, useContext } 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 { BaseFieldProps, FieldProps } 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 = createContext<FormContextValue | null>(null)
|
36
|
+
|
37
|
+
export const useFormContext = <
|
38
|
+
TFieldValues extends FieldValues = FieldValues,
|
39
|
+
TContext = any,
|
40
|
+
TSchema = any
|
41
|
+
>() => {
|
42
|
+
const context = useContext(FormContext)
|
43
|
+
const hookContext = useHookFormContext()
|
44
|
+
|
45
|
+
if (!context) {
|
46
|
+
throw new Error('FormProvider must be used within a Form component')
|
47
|
+
}
|
48
|
+
|
49
|
+
return {
|
50
|
+
...hookContext,
|
51
|
+
...context,
|
52
|
+
}
|
53
|
+
}
|
54
|
+
|
55
|
+
export const useFieldProps = <TFieldValues extends FieldValues = FieldValues>(
|
56
|
+
name: string
|
57
|
+
): BaseFieldProps<TFieldValues> | undefined => {
|
58
|
+
const parsedName = name?.replace(/\.[0-9]/g, '.$')
|
59
|
+
const context = useFormContext()
|
60
|
+
return context?.fields?.[parsedName] as any
|
61
|
+
}
|
62
|
+
|
63
|
+
export type UseFormReturn<
|
64
|
+
TFieldValues extends FieldValues = FieldValues,
|
65
|
+
TContext = any,
|
66
|
+
TSchema = any
|
67
|
+
> = ReturnType<typeof useFormContext>
|
68
|
+
|
69
|
+
export const FormProvider = <
|
70
|
+
TFieldValues extends FieldValues = FieldValues,
|
71
|
+
TContext = any,
|
72
|
+
TSchema = any
|
73
|
+
>(
|
74
|
+
props: FormProviderProps<TFieldValues, TContext, TSchema>
|
75
|
+
) => {
|
76
|
+
const { children, fieldResolver, schema, fields, ...rest } = props
|
77
|
+
return (
|
78
|
+
<HookFormProvider {...rest}>
|
79
|
+
<FormContext.Provider value={{ fieldResolver, schema, fields }}>
|
80
|
+
{children}
|
81
|
+
</FormContext.Provider>
|
82
|
+
</HookFormProvider>
|
83
|
+
)
|
84
|
+
}
|