@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.
Files changed (44) hide show
  1. package/CHANGELOG.md +20 -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 +345 -2584
  9. package/dist/index.js.map +1 -1
  10. package/dist/index.mjs +347 -2580
  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 +16 -9
  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 +154 -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.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.11",
90
- "@chakra-ui/utils": "^2.0.14",
91
- "@saas-ui/core": "2.0.0-next.5",
92
- "react-hook-form": "^7.43.0"
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": "^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
+ }