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

Sign up to get free protection for your applications and to get access to all the features.
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
+ }