@saas-ui/forms 2.0.0-next.5 → 2.0.0-next.6

Sign up to get free protection for your applications and to get access to all the features.
Files changed (44) hide show
  1. package/CHANGELOG.md +13 -0
  2. package/dist/ajv/index.d.ts +358 -11
  3. package/dist/ajv/index.js +7 -9
  4. package/dist/ajv/index.js.map +1 -1
  5. package/dist/ajv/index.mjs +7 -10
  6. package/dist/ajv/index.mjs.map +1 -1
  7. package/dist/index.d.ts +296 -194
  8. package/dist/index.js +373 -2613
  9. package/dist/index.js.map +1 -1
  10. package/dist/index.mjs +373 -2607
  11. package/dist/index.mjs.map +1 -1
  12. package/dist/yup/index.d.ts +573 -106
  13. package/dist/yup/index.js +6 -10
  14. package/dist/yup/index.js.map +1 -1
  15. package/dist/yup/index.mjs +4 -8
  16. package/dist/yup/index.mjs.map +1 -1
  17. package/dist/zod/index.d.ts +490 -14
  18. package/dist/zod/index.js +5 -0
  19. package/dist/zod/index.js.map +1 -1
  20. package/dist/zod/index.mjs +5 -1
  21. package/dist/zod/index.mjs.map +1 -1
  22. package/package.json +15 -8
  23. package/src/array-field.tsx +34 -17
  24. package/src/base-field.tsx +4 -9
  25. package/src/create-field.tsx +2 -1
  26. package/src/create-form.tsx +33 -10
  27. package/src/default-fields.tsx +21 -4
  28. package/src/display-field.tsx +1 -2
  29. package/src/display-if.tsx +14 -8
  30. package/src/field-resolver.ts +10 -8
  31. package/src/field.tsx +6 -3
  32. package/src/fields.tsx +16 -13
  33. package/src/form-context.tsx +84 -0
  34. package/src/form.tsx +44 -17
  35. package/src/index.ts +17 -15
  36. package/src/object-field.tsx +6 -2
  37. package/src/password-input/password-input.tsx +1 -1
  38. package/src/select/select-context.tsx +130 -0
  39. package/src/select/select.stories.tsx +116 -85
  40. package/src/select/select.tsx +152 -142
  41. package/src/types.ts +59 -6
  42. package/src/use-array-field.tsx +9 -3
  43. package/src/utils.ts +8 -1
  44. package/src/watch-field.tsx +2 -6
@@ -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;AAMH","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 { 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 ...options,\n }) as <\n TSchema extends z.AnyZodObject = z.AnyZodObject,\n TContext extends object = object\n >(\n props: WithFields<FormProps<z.infer<TSchema>, TContext, TSchema>, FieldDefs>\n ) => React.ReactElement\n}\n"]}
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.5",
3
+ "version": "2.0.0-next.6",
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.11",
90
- "@chakra-ui/utils": "^2.0.14",
90
+ "@chakra-ui/react-utils": "^2.0.12",
91
+ "@chakra-ui/utils": "^2.0.15",
91
92
  "@saas-ui/core": "2.0.0-next.5",
92
- "react-hook-form": "^7.43.0"
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": "^2.9.3",
104
+ "@hookform/resolvers": "^3.0.1",
104
105
  "@types/json-schema": "^7.0.11",
105
106
  "ajv": "^8.12.0",
106
- "yup": "^0.32.11",
107
- "zod": "^3.20.2"
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
  }
@@ -7,7 +7,7 @@ import {
7
7
  Button,
8
8
  ButtonProps,
9
9
  } from '@chakra-ui/react'
10
- import { AddIcon, MinusIcon } from '@chakra-ui/icons'
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 || <AddIcon />}
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 ArrayFieldOptions,
179
- Omit<BaseFieldProps, 'defaultValue'> {}
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
- }: ArrayFieldProps,
262
+ }: ArrayFieldContainerProps,
246
263
  ref: React.ForwardedRef<UseArrayFieldReturn>
247
264
  ) => {
248
265
  const context = useArrayField({
@@ -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
- import { FocusableElement } from '@chakra-ui/utils'
18
- import { useField } from './fields-context'
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 }>) => {
@@ -1,10 +1,11 @@
1
1
  import * as React from 'react'
2
- import { useFormContext, Controller } from 'react-hook-form'
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
@@ -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
- const { schema, ...rest } = props
23
- return (
24
- <FieldsProvider value={fields || {}}>
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
@@ -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 { Select, SelectProps, NativeSelect, NativeSelectProps } from './select'
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>(Select, {
78
- isControlled: true,
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) => {
@@ -1,6 +1,5 @@
1
1
  import * as React from 'react'
2
- import { useFormContext } from 'react-hook-form'
3
-
2
+ import { useFormContext } from './form-context'
4
3
  import {
5
4
  Text,
6
5
  FormControl,
@@ -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: string
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 = <TFieldValues extends FieldValues = FieldValues>({
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<TFieldValues>()
44
+ const context = useFormContext() as any
39
45
  return condition(value, context) ? children : null
40
46
  }
41
47
 
@@ -1,20 +1,22 @@
1
- import { BaseFieldProps as FieldProps } from './types'
1
+ import { BaseFieldProps } from './types'
2
2
 
3
3
  import { get } from '@chakra-ui/utils'
4
4
 
5
5
  export type FieldResolver = {
6
- getFields(): FieldProps[]
7
- getNestedFields(name: string): FieldProps[]
6
+ getFields(): BaseFieldProps[]
7
+ getNestedFields(name: string): BaseFieldProps[]
8
8
  }
9
9
 
10
- interface SchemaField extends FieldProps {
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): FieldProps[] =>
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: ObjectSchema): FieldResolver => {
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 InputComponent = useField(type)
38
- return <InputComponent ref={ref} {...props} />
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 'react-hook-form'
10
+ import { useFormContext } from './form-context'
12
11
 
13
- export interface FieldsProps {
14
- schema: any
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 resolver = React.useMemo(
44
- () => fieldResolver || Form.getFieldResolver(schema),
45
- [schema, fieldResolver]
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.getFields(), [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.map(
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
+ }