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

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 (60) hide show
  1. package/CHANGELOG.md +43 -0
  2. package/README.md +53 -6
  3. package/dist/ajv/index.d.ts +358 -11
  4. package/dist/ajv/index.js +7 -9
  5. package/dist/ajv/index.js.map +1 -1
  6. package/dist/ajv/index.mjs +7 -10
  7. package/dist/ajv/index.mjs.map +1 -1
  8. package/dist/index.d.ts +448 -247
  9. package/dist/index.js +707 -682
  10. package/dist/index.js.map +1 -1
  11. package/dist/index.mjs +691 -666
  12. package/dist/index.mjs.map +1 -1
  13. package/dist/yup/index.d.ts +580 -21
  14. package/dist/yup/index.js +6 -10
  15. package/dist/yup/index.js.map +1 -1
  16. package/dist/yup/index.mjs +4 -8
  17. package/dist/yup/index.mjs.map +1 -1
  18. package/dist/zod/index.d.ts +580 -11
  19. package/dist/zod/index.js +5 -0
  20. package/dist/zod/index.js.map +1 -1
  21. package/dist/zod/index.mjs +5 -1
  22. package/dist/zod/index.mjs.map +1 -1
  23. package/package.json +19 -10
  24. package/src/array-field.tsx +82 -45
  25. package/src/auto-form.tsx +7 -3
  26. package/src/base-field.tsx +54 -0
  27. package/src/create-field.tsx +144 -0
  28. package/src/create-form.tsx +54 -0
  29. package/src/default-fields.tsx +163 -0
  30. package/src/display-field.tsx +9 -11
  31. package/src/display-if.tsx +20 -13
  32. package/src/field-resolver.ts +10 -8
  33. package/src/field.tsx +18 -445
  34. package/src/fields-context.tsx +23 -0
  35. package/src/fields.tsx +34 -21
  36. package/src/form-context.tsx +84 -0
  37. package/src/form.tsx +69 -52
  38. package/src/index.ts +44 -4
  39. package/src/input-right-button/input-right-button.stories.tsx +1 -1
  40. package/src/input-right-button/input-right-button.tsx +0 -2
  41. package/src/layout.tsx +16 -11
  42. package/src/number-input/number-input.tsx +9 -5
  43. package/src/object-field.tsx +13 -8
  44. package/src/password-input/password-input.stories.tsx +23 -2
  45. package/src/password-input/password-input.tsx +6 -6
  46. package/src/pin-input/pin-input.tsx +1 -5
  47. package/src/radio/radio-input.stories.tsx +1 -1
  48. package/src/radio/radio-input.tsx +12 -10
  49. package/src/select/native-select.tsx +1 -4
  50. package/src/select/select-context.tsx +130 -0
  51. package/src/select/select.stories.tsx +116 -85
  52. package/src/select/select.test.tsx +1 -1
  53. package/src/select/select.tsx +160 -146
  54. package/src/step-form.tsx +29 -11
  55. package/src/submit-button.tsx +5 -1
  56. package/src/types.ts +144 -0
  57. package/src/use-array-field.tsx +9 -3
  58. package/src/utils.ts +23 -1
  59. package/src/watch-field.tsx +2 -6
  60. /package/src/radio/{radio.test.tsx → radio-input.test.tsx} +0 -0
package/dist/zod/index.js CHANGED
@@ -77,14 +77,19 @@ var zodParseMeta = (meta) => {
77
77
  var createZodForm = (options) => {
78
78
  return forms.createForm({
79
79
  resolver: (schema) => zod.zodResolver(schema, options == null ? void 0 : options.schemaOptions, options == null ? void 0 : options.resolverOptions),
80
+ fieldResolver: zodFieldResolver,
80
81
  ...options
81
82
  });
82
83
  };
83
84
 
85
+ // zod/src/index.ts
86
+ var Form = createZodForm();
87
+
84
88
  Object.defineProperty(exports, 'zodResolver', {
85
89
  enumerable: true,
86
90
  get: function () { return zod.zodResolver; }
87
91
  });
92
+ exports.Form = Form;
88
93
  exports.createZodForm = createZodForm;
89
94
  exports.getFieldsFromSchema = getFieldsFromSchema;
90
95
  exports.getNestedSchema = getNestedSchema;
@@ -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,UAAX,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;AAE3B,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,SAAS,kBAA8C;AAWhD,IAAM,gBAAgB,CAAC,YAAiC;AAC7D,SAAO,WAA2B;AAAA,IAChC,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 { createForm, CreateFormProps, FormProps } from '@saas-ui/forms'\nimport { zodResolver } from './zod-resolver'\nimport { z } from 'zod'\n\ntype ResolverArgs = Parameters<typeof zodResolver>\n\nexport interface CreateZodFormProps extends CreateFormProps {\n schemaOptions?: ResolverArgs[1]\n resolverOptions?: ResolverArgs[2]\n}\n\nexport const createZodForm = (options?: CreateZodFormProps) => {\n return createForm<z.AnyZodObject>({\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: FormProps<z.infer<TSchema>, TContext, TSchema>\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"]}
@@ -76,10 +76,14 @@ var zodParseMeta = (meta) => {
76
76
  var createZodForm = (options) => {
77
77
  return createForm({
78
78
  resolver: (schema) => zodResolver(schema, options == null ? void 0 : options.schemaOptions, options == null ? void 0 : options.resolverOptions),
79
+ fieldResolver: zodFieldResolver,
79
80
  ...options
80
81
  });
81
82
  };
82
83
 
83
- export { createZodForm, getFieldsFromSchema, getNestedSchema, zodFieldResolver, zodMeta, zodParseMeta };
84
+ // zod/src/index.ts
85
+ var Form = createZodForm();
86
+
87
+ export { Form, createZodForm, getFieldsFromSchema, getNestedSchema, zodFieldResolver, zodMeta, zodParseMeta };
84
88
  //# sourceMappingURL=out.js.map
85
89
  //# sourceMappingURL=index.mjs.map
@@ -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,UAAX,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;AAE3B,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,SAAS,kBAA8C;AAWhD,IAAM,gBAAgB,CAAC,YAAiC;AAC7D,SAAO,WAA2B;AAAA,IAChC,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 { createForm, CreateFormProps, FormProps } from '@saas-ui/forms'\nimport { zodResolver } from './zod-resolver'\nimport { z } from 'zod'\n\ntype ResolverArgs = Parameters<typeof zodResolver>\n\nexport interface CreateZodFormProps extends CreateFormProps {\n schemaOptions?: ResolverArgs[1]\n resolverOptions?: ResolverArgs[2]\n}\n\nexport const createZodForm = (options?: CreateZodFormProps) => {\n return createForm<z.AnyZodObject>({\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: FormProps<z.infer<TSchema>, TContext, TSchema>\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.3",
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": {
@@ -13,6 +13,7 @@
13
13
  },
14
14
  "./yup": {
15
15
  "require": "./dist/yup/index.js",
16
+ "types": "./dist/yup/index.d.ts",
16
17
  "import": "./dist/yup/index.mjs"
17
18
  },
18
19
  "./yup/src": {
@@ -20,6 +21,7 @@
20
21
  },
21
22
  "./zod": {
22
23
  "require": "./dist/zod/index.js",
24
+ "types": "./dist/zod/index.d.ts",
23
25
  "import": "./dist/zod/index.mjs"
24
26
  },
25
27
  "./zod/src": {
@@ -27,6 +29,7 @@
27
29
  },
28
30
  "./ajv": {
29
31
  "require": "./dist/ajv/index.js",
32
+ "types": "./dist/zod/index.d.ts",
30
33
  "import": "./dist/ajv/index.mjs"
31
34
  },
32
35
  "./ajv/src": {
@@ -45,7 +48,8 @@
45
48
  "build:ajv": "tsup ajv/src/index.ts --config ajv/tsup.config.ts",
46
49
  "lint": "eslint src --ext .ts,.tsx,.js,.jsx --config ../../.eslintrc.js",
47
50
  "lint:staged": "lint-staged --allow-empty --config ../../lint-staged.config.js",
48
- "typecheck": "tsc --noEmit"
51
+ "typecheck": "tsc --noEmit",
52
+ "tsd": "tsd"
49
53
  },
50
54
  "files": [
51
55
  "dist",
@@ -83,11 +87,10 @@
83
87
  "url": "https://storybook.saas-ui.dev"
84
88
  },
85
89
  "dependencies": {
86
- "@chakra-ui/icons": "^2.0.16",
87
- "@chakra-ui/react-utils": "^2.0.11",
88
- "@chakra-ui/utils": "^2.0.14",
89
- "@saas-ui/core": "2.0.0-next.3",
90
- "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.5",
93
+ "react-hook-form": "^7.43.9"
91
94
  },
92
95
  "peerDependencies": {
93
96
  "@chakra-ui/react": ">=2.4.9",
@@ -98,10 +101,16 @@
98
101
  "react-dom": ">=18.0.0"
99
102
  },
100
103
  "devDependencies": {
101
- "@hookform/resolvers": "^2.9.3",
104
+ "@hookform/resolvers": "^3.0.1",
102
105
  "@types/json-schema": "^7.0.11",
103
106
  "ajv": "^8.12.0",
104
- "yup": "^0.32.11",
105
- "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"
106
115
  }
107
116
  }
@@ -7,11 +7,11 @@ import {
7
7
  Button,
8
8
  ButtonProps,
9
9
  } from '@chakra-ui/react'
10
- import { __DEV__ } from '@chakra-ui/utils'
11
- import { AddIcon, MinusIcon } from '@chakra-ui/icons'
10
+ import { PlusIcon, MinusIcon } from '@saas-ui/core/icons'
12
11
 
13
12
  import { FormLayout, FormLayoutProps } from './layout'
14
- import { BaseField, FieldProps } from './field'
13
+ import { BaseField } from './base-field'
14
+ import { BaseFieldProps } from './types'
15
15
 
16
16
  import { mapNestedFields } from './utils'
17
17
 
@@ -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
 
@@ -53,7 +56,11 @@ interface ArrayFieldRowProps extends FormLayoutProps {
53
56
  */
54
57
  children: React.ReactNode
55
58
  }
56
-
59
+ /**
60
+ * Render prop component, to get access to the internal fields state. Must be a child of ArrayFieldContainer.
61
+ *
62
+ * @see Docs https://saas-ui.dev/docs/components/forms/array-field
63
+ */
57
64
  export const ArrayFieldRow: React.FC<ArrayFieldRowProps> = ({
58
65
  children,
59
66
  index,
@@ -67,9 +74,7 @@ export const ArrayFieldRow: React.FC<ArrayFieldRowProps> = ({
67
74
  )
68
75
  }
69
76
 
70
- if (__DEV__) {
71
- ArrayFieldRow.displayName = 'ArrayFieldRow'
72
- }
77
+ ArrayFieldRow.displayName = 'ArrayFieldRow'
73
78
 
74
79
  export interface ArrayFieldRowFieldsProps extends FormLayoutProps {
75
80
  /**
@@ -85,7 +90,11 @@ export interface ArrayFieldRowFieldsProps extends FormLayoutProps {
85
90
  */
86
91
  children: React.ReactNode
87
92
  }
88
-
93
+ /**
94
+ * Add the name prefix to the fields and acts as a horizontal form layout by default.
95
+ *
96
+ * @see Docs https://saas-ui.dev/docs/components/forms/array-field
97
+ */
89
98
  export const ArrayFieldRowFields: React.FC<ArrayFieldRowFieldsProps> = ({
90
99
  children,
91
100
  ...layoutProps
@@ -98,13 +107,17 @@ export const ArrayFieldRowFields: React.FC<ArrayFieldRowFieldsProps> = ({
98
107
  )
99
108
  }
100
109
 
101
- if (__DEV__) {
102
- ArrayFieldRowFields.displayName = 'ArrayFieldRowFields'
103
- }
110
+ ArrayFieldRowFields.displayName = 'ArrayFieldRowFields'
104
111
 
112
+ /**
113
+ * The row container component providers row context.
114
+ *
115
+ * @see Docs https://saas-ui.dev/docs/components/forms/array-field
116
+ */
105
117
  export const ArrayFieldRowContainer: React.FC<ArrayFieldRowProps> = ({
106
118
  children,
107
119
  index,
120
+ ...rest
108
121
  }) => {
109
122
  const context = useArrayFieldRow({ index })
110
123
 
@@ -118,15 +131,20 @@ export const ArrayFieldRowContainer: React.FC<ArrayFieldRowProps> = ({
118
131
 
119
132
  return (
120
133
  <ArrayFieldRowProvider value={context}>
121
- <chakra.div __css={styles}>{children}</chakra.div>
134
+ <chakra.div {...rest} __css={styles}>
135
+ {children}
136
+ </chakra.div>
122
137
  </ArrayFieldRowProvider>
123
138
  )
124
139
  }
125
140
 
126
- if (__DEV__) {
127
- ArrayFieldRowContainer.displayName = 'ArrayFieldRowContainer'
128
- }
141
+ ArrayFieldRowContainer.displayName = 'ArrayFieldRowContainer'
129
142
 
143
+ /**
144
+ * The default remove button.
145
+ *
146
+ * @see Docs https://saas-ui.dev/docs/components/forms/array-field
147
+ */
130
148
  export const ArrayFieldRemoveButton: React.FC<ArrayFieldButtonProps> = (
131
149
  props
132
150
  ) => {
@@ -137,10 +155,13 @@ export const ArrayFieldRemoveButton: React.FC<ArrayFieldButtonProps> = (
137
155
  )
138
156
  }
139
157
 
140
- if (__DEV__) {
141
- ArrayFieldRemoveButton.displayName = 'ArrayFieldRemoveButton'
142
- }
158
+ ArrayFieldRemoveButton.displayName = 'ArrayFieldRemoveButton'
143
159
 
160
+ /**
161
+ * The default add button.
162
+ *
163
+ * @see Docs https://saas-ui.dev/docs/components/forms/array-field
164
+ */
144
165
  export const ArrayFieldAddButton: React.FC<ArrayFieldButtonProps> = (props) => {
145
166
  return (
146
167
  <Button
@@ -149,36 +170,48 @@ export const ArrayFieldAddButton: React.FC<ArrayFieldButtonProps> = (props) => {
149
170
  {...useArrayFieldAddButton()}
150
171
  {...props}
151
172
  >
152
- {props.children || <AddIcon />}
173
+ {props.children || <PlusIcon />}
153
174
  </Button>
154
175
  )
155
176
  }
156
177
 
157
- if (__DEV__) {
158
- ArrayFieldAddButton.displayName = 'ArrayFieldAddButton'
159
- }
178
+ ArrayFieldAddButton.displayName = 'ArrayFieldAddButton'
160
179
 
161
- export interface ArrayFieldProps
162
- extends ArrayFieldOptions,
163
- Omit<FieldProps, '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
+ }
164
190
 
191
+ /**
192
+ * The wrapper component that composes the default ArrayField functionality.
193
+ *
194
+ * @see Docs https://saas-ui.dev/docs/components/forms/array-field
195
+ */
165
196
  export const ArrayField = forwardRef(
166
197
  (props: ArrayFieldProps, ref: React.ForwardedRef<UseArrayFieldReturn>) => {
167
198
  const { children, ...containerProps } = props
168
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
+
169
212
  return (
170
213
  <ArrayFieldContainer ref={ref} {...containerProps}>
171
- <ArrayFieldRows>
172
- {(fields: ArrayField[]) => (
173
- <>
174
- {fields.map(({ id }, index: number) => (
175
- <ArrayFieldRow key={id} index={index}>
176
- {children}
177
- </ArrayFieldRow>
178
- ))}
179
- </>
180
- )}
181
- </ArrayFieldRows>
214
+ <ArrayFieldRows>{rowFn as any}</ArrayFieldRows>
182
215
  <ArrayFieldAddButton />
183
216
  </ArrayFieldContainer>
184
217
  )
@@ -191,9 +224,7 @@ export const ArrayField = forwardRef(
191
224
  displayName: string
192
225
  }
193
226
 
194
- if (__DEV__) {
195
- ArrayField.displayName = 'ArrayField'
196
- }
227
+ ArrayField.displayName = 'ArrayField'
197
228
 
198
229
  export interface ArrayFieldRowsProps {
199
230
  children: (fields: ArrayField[]) => React.ReactElement | null
@@ -206,10 +237,18 @@ export const ArrayFieldRows = ({
206
237
  return children(fields)
207
238
  }
208
239
 
209
- if (__DEV__) {
210
- ArrayFieldRows.displayName = 'ArrayFieldRows'
240
+ ArrayFieldRows.displayName = 'ArrayFieldRows'
241
+
242
+ export interface ArrayFieldContainerProps
243
+ extends Omit<ArrayFieldProps, 'children'> {
244
+ children: React.ReactNode
211
245
  }
212
246
 
247
+ /**
248
+ * The container component provides context and state management.
249
+ *
250
+ * @see Docs https://saas-ui.dev/docs/components/forms/array-field
251
+ */
213
252
  export const ArrayFieldContainer = React.forwardRef(
214
253
  (
215
254
  {
@@ -220,7 +259,7 @@ export const ArrayFieldContainer = React.forwardRef(
220
259
  max,
221
260
  children,
222
261
  ...fieldProps
223
- }: ArrayFieldProps,
262
+ }: ArrayFieldContainerProps,
224
263
  ref: React.ForwardedRef<UseArrayFieldReturn>
225
264
  ) => {
226
265
  const context = useArrayField({
@@ -244,6 +283,4 @@ export const ArrayFieldContainer = React.forwardRef(
244
283
  }
245
284
  )
246
285
 
247
- if (__DEV__) {
248
- ArrayFieldContainer.displayName = 'ArrayFieldContainer'
249
- }
286
+ ArrayFieldContainer.displayName = 'ArrayFieldContainer'
package/src/auto-form.tsx CHANGED
@@ -4,7 +4,7 @@ import { forwardRef } from '@chakra-ui/react'
4
4
 
5
5
  import { Form, FormProps } from './form'
6
6
  import { FormLayout } from './layout'
7
- import { Fields } from './fields'
7
+ import { AutoFields } from './fields'
8
8
  import { SubmitButton } from './submit-button'
9
9
 
10
10
  interface AutoFormOptions {
@@ -32,7 +32,11 @@ export interface AutoFormProps<
32
32
  AutoFormOptions {
33
33
  children?: React.ReactNode
34
34
  }
35
-
35
+ /**
36
+ * The wrapper component that manages context and state.
37
+ *
38
+ * @see Docs https://saas-ui.dev/docs/components/forms/auto-form
39
+ */
36
40
  export const AutoForm = forwardRef(
37
41
  <
38
42
  TFieldValues extends FieldValues = FieldValues,
@@ -52,7 +56,7 @@ export const AutoForm = forwardRef(
52
56
  return (
53
57
  <Form {...rest} schema={schema} ref={ref}>
54
58
  <FormLayout>
55
- {<Fields schema={schema} fieldResolver={fieldResolver} />}
59
+ {<AutoFields schema={schema} fieldResolver={fieldResolver} />}
56
60
  {submitLabel && <SubmitButton>{submitLabel}</SubmitButton>}
57
61
  {children}
58
62
  </FormLayout>
@@ -0,0 +1,54 @@
1
+ import * as React from 'react'
2
+ import { FormState, get } from 'react-hook-form'
3
+
4
+ import {
5
+ Box,
6
+ FormControl,
7
+ FormLabel,
8
+ FormHelperText,
9
+ FormErrorMessage,
10
+ } from '@chakra-ui/react'
11
+
12
+ import { useFormContext } from './form-context'
13
+
14
+ import { BaseFieldProps, FieldProps } from './types'
15
+
16
+ const getError = (name: string, formState: FormState<{ [x: string]: any }>) => {
17
+ return get(formState.errors, name)
18
+ }
19
+
20
+ const isTouched = (
21
+ name: string,
22
+ formState: FormState<{ [x: string]: any }>
23
+ ) => {
24
+ return get(formState.touchedFields, name)
25
+ }
26
+
27
+ /**
28
+ * The default BaseField component
29
+ * Composes the Chakra UI FormControl component, with FormLabel, FormHelperText and FormErrorMessage.
30
+ */
31
+ export const BaseField: React.FC<BaseFieldProps> = (props) => {
32
+ const { name, label, help, hideLabel, children, ...controlProps } = props
33
+
34
+ const { formState } = useFormContext()
35
+
36
+ const error = getError(name, formState)
37
+
38
+ return (
39
+ <FormControl {...controlProps} isInvalid={!!error}>
40
+ {label && !hideLabel ? <FormLabel>{label}</FormLabel> : null}
41
+ <Box>
42
+ {children}
43
+ {help && !error?.message ? (
44
+ <FormHelperText>{help}</FormHelperText>
45
+ ) : null}
46
+ {error?.message && (
47
+ <FormErrorMessage>{error?.message}</FormErrorMessage>
48
+ )}
49
+ </Box>
50
+ </FormControl>
51
+ )
52
+ }
53
+
54
+ BaseField.displayName = 'BaseField'
@@ -0,0 +1,144 @@
1
+ import * as React from 'react'
2
+ import { Controller } from 'react-hook-form'
3
+
4
+ import { forwardRef, useMergeRefs } from '@chakra-ui/react'
5
+ import { callAllHandlers } from '@chakra-ui/utils'
6
+ import { BaseFieldProps, FieldProps } from './types'
7
+ import { BaseField } from './base-field'
8
+ import { useFormContext } from './form-context'
9
+
10
+ interface CreateFieldProps {
11
+ displayName: string
12
+ hideLabel?: boolean
13
+ BaseField: React.FC<any>
14
+ }
15
+
16
+ const _createField = (
17
+ InputComponent: React.FC<any>,
18
+ { displayName, hideLabel, BaseField }: CreateFieldProps
19
+ ) => {
20
+ const Field = forwardRef((props, ref) => {
21
+ const {
22
+ id,
23
+ name,
24
+ label,
25
+ help,
26
+ isDisabled,
27
+ isInvalid,
28
+ isReadOnly,
29
+ isRequired,
30
+ rules,
31
+ ...inputProps
32
+ } = props
33
+
34
+ const inputRules = {
35
+ required: isRequired,
36
+ ...rules,
37
+ }
38
+
39
+ return (
40
+ <BaseField
41
+ id={id}
42
+ name={name}
43
+ label={label}
44
+ help={help}
45
+ hideLabel={hideLabel}
46
+ isDisabled={isDisabled}
47
+ isInvalid={isInvalid}
48
+ isReadOnly={isReadOnly}
49
+ isRequired={isRequired}
50
+ >
51
+ <InputComponent
52
+ ref={ref}
53
+ id={id}
54
+ name={name}
55
+ label={hideLabel ? label : undefined} // Only pass down the label when it should be inline.
56
+ rules={inputRules}
57
+ {...inputProps}
58
+ />
59
+ </BaseField>
60
+ )
61
+ })
62
+ Field.displayName = displayName
63
+
64
+ return Field
65
+ }
66
+
67
+ const withControlledInput = (InputComponent: React.FC<any>) => {
68
+ return forwardRef<FieldProps, typeof InputComponent>(
69
+ ({ name, rules, ...inputProps }, ref) => {
70
+ const { control } = useFormContext()
71
+
72
+ return (
73
+ <Controller
74
+ name={name}
75
+ control={control}
76
+ rules={rules}
77
+ render={({ field: { ref: _ref, ...field } }) => (
78
+ <InputComponent
79
+ {...field}
80
+ {...inputProps}
81
+ onChange={callAllHandlers(inputProps.onChange, field.onChange)}
82
+ onBlur={callAllHandlers(inputProps.onBlur, field.onBlur)}
83
+ ref={useMergeRefs(ref, _ref)}
84
+ />
85
+ )}
86
+ />
87
+ )
88
+ }
89
+ )
90
+ }
91
+
92
+ const withUncontrolledInput = (InputComponent: React.FC<any>) => {
93
+ return forwardRef<FieldProps, typeof InputComponent>(
94
+ ({ name, rules, ...inputProps }, ref) => {
95
+ const { register } = useFormContext()
96
+
97
+ const { ref: _ref, ...field } = register(name, rules)
98
+
99
+ return (
100
+ <InputComponent
101
+ {...field}
102
+ {...inputProps}
103
+ onChange={callAllHandlers(inputProps.onChange, field.onChange)}
104
+ onBlur={callAllHandlers(inputProps.onBlur, field.onBlur)}
105
+ ref={useMergeRefs(ref, _ref)}
106
+ />
107
+ )
108
+ }
109
+ )
110
+ }
111
+
112
+ export interface CreateFieldOptions {
113
+ isControlled?: boolean
114
+ hideLabel?: boolean
115
+ BaseField?: React.FC<any>
116
+ }
117
+
118
+ /**
119
+ * Register a new field type
120
+ * @param type The name for this field in kebab-case, eg `email` or `array-field`
121
+ * @param component The React component
122
+ * @param options
123
+ * @param options.isControlled Set this to true if this is a controlled field.
124
+ * @param options.hideLabel Hide the field label, for example for the checkbox field.
125
+ */
126
+ export const createField = <TProps extends object>(
127
+ component: React.FC<TProps>,
128
+ options?: CreateFieldOptions
129
+ ) => {
130
+ let InputComponent
131
+ if (options?.isControlled) {
132
+ InputComponent = withControlledInput(component)
133
+ } else {
134
+ InputComponent = withUncontrolledInput(component)
135
+ }
136
+
137
+ const Field = _createField(InputComponent, {
138
+ displayName: `${component.displayName ?? 'Custom'}Field`,
139
+ hideLabel: options?.hideLabel,
140
+ BaseField: options?.BaseField || BaseField,
141
+ }) as React.FC<TProps & BaseFieldProps>
142
+
143
+ return Field
144
+ }