@saas-ui/forms 2.0.0-next.5 → 2.0.0-next.7
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 +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
|
+
}
|