@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.
- package/CHANGELOG.md +43 -0
- package/README.md +53 -6
- package/dist/ajv/index.d.ts +358 -11
- package/dist/ajv/index.js +7 -9
- package/dist/ajv/index.js.map +1 -1
- package/dist/ajv/index.mjs +7 -10
- package/dist/ajv/index.mjs.map +1 -1
- package/dist/index.d.ts +448 -247
- package/dist/index.js +707 -682
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +691 -666
- package/dist/index.mjs.map +1 -1
- package/dist/yup/index.d.ts +580 -21
- package/dist/yup/index.js +6 -10
- package/dist/yup/index.js.map +1 -1
- package/dist/yup/index.mjs +4 -8
- package/dist/yup/index.mjs.map +1 -1
- package/dist/zod/index.d.ts +580 -11
- package/dist/zod/index.js +5 -0
- package/dist/zod/index.js.map +1 -1
- package/dist/zod/index.mjs +5 -1
- package/dist/zod/index.mjs.map +1 -1
- package/package.json +19 -10
- package/src/array-field.tsx +82 -45
- package/src/auto-form.tsx +7 -3
- package/src/base-field.tsx +54 -0
- package/src/create-field.tsx +144 -0
- package/src/create-form.tsx +54 -0
- package/src/default-fields.tsx +163 -0
- package/src/display-field.tsx +9 -11
- package/src/display-if.tsx +20 -13
- package/src/field-resolver.ts +10 -8
- package/src/field.tsx +18 -445
- package/src/fields-context.tsx +23 -0
- package/src/fields.tsx +34 -21
- package/src/form-context.tsx +84 -0
- package/src/form.tsx +69 -52
- package/src/index.ts +44 -4
- package/src/input-right-button/input-right-button.stories.tsx +1 -1
- package/src/input-right-button/input-right-button.tsx +0 -2
- package/src/layout.tsx +16 -11
- package/src/number-input/number-input.tsx +9 -5
- package/src/object-field.tsx +13 -8
- package/src/password-input/password-input.stories.tsx +23 -2
- package/src/password-input/password-input.tsx +6 -6
- package/src/pin-input/pin-input.tsx +1 -5
- package/src/radio/radio-input.stories.tsx +1 -1
- package/src/radio/radio-input.tsx +12 -10
- package/src/select/native-select.tsx +1 -4
- package/src/select/select-context.tsx +130 -0
- package/src/select/select.stories.tsx +116 -85
- package/src/select/select.test.tsx +1 -1
- package/src/select/select.tsx +160 -146
- package/src/step-form.tsx +29 -11
- package/src/submit-button.tsx +5 -1
- package/src/types.ts +144 -0
- package/src/use-array-field.tsx +9 -3
- package/src/utils.ts +23 -1
- package/src/watch-field.tsx +2 -6
- /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;
|
package/dist/zod/index.js.map
CHANGED
@@ -1 +1 @@
|
|
1
|
-
{"version":3,"sources":["../../zod/src/zod-resolver.ts","../../zod/src/create-zod-form.ts"],"names":[],"mappings":";AACA,SAAS,mBAAmB;AAC5B,SAAS,WAAW;AAUpB,IAAM,UAAU,CAAC,UAAwB;AACvC,UAAQ,MAAM,KAAK,UAAU;AAAA,IAC3B,KAAK;AACH,aAAO;AAAA,IACT,KAAK;AACH,aAAO;AAAA,IACT,KAAK;AACH,aAAO;AAAA,IACT,KAAK;AACH,aAAO;AAAA,IACT,KAAK;AAAA,IACL;AACE,aAAO;AAAA,EACX;AACF;AAEA,IAAM,iBAAiB,CAAC,OAAY,SAAiB;AA5BrD;AA6BE,UAAO,WAAM,KAAK,
|
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/dist/zod/index.mjs
CHANGED
@@ -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
|
-
|
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
|
package/dist/zod/index.mjs.map
CHANGED
@@ -1 +1 @@
|
|
1
|
-
{"version":3,"sources":["../../zod/src/zod-resolver.ts","../../zod/src/create-zod-form.ts"],"names":[],"mappings":";AACA,SAAS,mBAAmB;AAC5B,SAAS,WAAW;AAUpB,IAAM,UAAU,CAAC,UAAwB;AACvC,UAAQ,MAAM,KAAK,UAAU;AAAA,IAC3B,KAAK;AACH,aAAO;AAAA,IACT,KAAK;AACH,aAAO;AAAA,IACT,KAAK;AACH,aAAO;AAAA,IACT,KAAK;AACH,aAAO;AAAA,IACT,KAAK;AAAA,IACL;AACE,aAAO;AAAA,EACX;AACF;AAEA,IAAM,iBAAiB,CAAC,OAAY,SAAiB;AA5BrD;AA6BE,UAAO,WAAM,KAAK,
|
1
|
+
{"version":3,"sources":["../../zod/src/zod-resolver.ts","../../zod/src/create-zod-form.ts","../../zod/src/index.ts"],"names":[],"mappings":";AACA,SAAS,mBAAmB;AAC5B,SAAS,WAAW;AAUpB,IAAM,UAAU,CAAC,UAAwB;AACvC,UAAQ,MAAM,KAAK,UAAU;AAAA,IAC3B,KAAK;AACH,aAAO;AAAA,IACT,KAAK;AACH,aAAO;AAAA,IACT,KAAK;AACH,aAAO;AAAA,IACT,KAAK;AACH,aAAO;AAAA,IACT,KAAK;AAAA,IACL;AACE,aAAO;AAAA,EACX;AACF;AAEA,IAAM,iBAAiB,CAAC,OAAY,SAAiB;AA5BrD;AA6BE,UAAO,WAAM,KAAK,IAAI,MAAf,mBAAkB;AAC3B;AAQO,IAAM,sBAAsB,CAAC,WAAuC;AACzE,QAAM,SAAuB,CAAC;AAE9B,MAAI,eAAoC,CAAC;AACzC,MAAI,OAAO,KAAK,aAAa,YAAY;AACvC,mBAAe,OAAO,KAAK,KAAK;AAAA,EAClC,WAAW,OAAO,KAAK,aAAa,aAAa;AAC/C,mBAAe,OAAO,KAAK,MAAM;AAAA,EACnC,OAAO;AACL,WAAO;AAAA,EACT;AAEA,aAAW,QAAQ,cAAc;AAC/B,UAAM,QAAQ,aAAa,IAAI;AAE/B,UAAM,UAAmB,CAAC;AAC1B,QAAI,MAAM,KAAK,aAAa,YAAY;AACtC,cAAQ,MAAM,eAAe,OAAO,WAAW;AAC/C,cAAQ,MAAM,eAAe,OAAO,WAAW;AAAA,IACjD;AAEA,UAAM,OAAO,MAAM,eAAe,aAAa,MAAM,WAAW;AAEhE,WAAO,KAAK;AAAA,MACV;AAAA,MACA,QAAO,6BAAM,UAAS,MAAM,eAAe;AAAA,MAC3C,OAAM,6BAAM,SAAQ,QAAQ,KAAK;AAAA,MACjC,GAAG;AAAA,IACL,CAAC;AAAA,EACH;AACA,SAAO;AACT;AAEO,IAAM,kBAAkB,CAAC,QAAsB,SAAiB;AACrE,SAAO,IAAI,OAAO,KAAK,MAAM,GAAG,IAAI;AACtC;AAEO,IAAM,mBAAmB,CAAyB,WAAc;AACrE,SAAO;AAAA,IACL,YAAY;AACV,aAAO,oBAAoB,MAAM;AAAA,IACnC;AAAA,IACA,gBAAgB,MAAc;AAC5B,aAAO,oBAAoB,gBAAgB,QAAQ,IAAI,CAAC;AAAA,IAC1D;AAAA,EACF;AACF;AAOO,IAAM,UAAU,CAAC,SAAkB;AACxC,SAAO,KAAK,UAAU,IAAI;AAC5B;AAEO,IAAM,eAAe,CAAC,SAAiB;AAC5C,MAAI;AACF,WAAO,KAAK,MAAM,IAAI;AAAA,EACxB,SAAS,GAAP;AACA,WAAO;AAAA,EACT;AACF;;;ACrGA;AAAA,EACE;AAAA,OAIK;AAYA,IAAM,gBAAgB,CAC3B,YACG;AACH,SAAO,WAAW;AAAA,IAChB,UAAU,CAAC,WACT,YAAY,QAAQ,mCAAS,eAAe,mCAAS,eAAe;AAAA,IACtE,eAAe;AAAA,IACf,GAAG;AAAA,EACL,CAAC;AAWH;;;ACvBO,IAAM,OAAO,cAAc","sourcesContent":["import * as z from 'zod'\nimport { zodResolver } from '@hookform/resolvers/zod'\nimport { get } from '@chakra-ui/utils'\nimport { FieldProps } from '@saas-ui/forms'\n\nexport { zodResolver }\n\nexport type Options = {\n min?: number\n max?: number\n}\n\nconst getType = (field: z.ZodTypeAny) => {\n switch (field._def.typeName) {\n case 'ZodArray':\n return 'array'\n case 'ZodObject':\n return 'object'\n case 'ZodNumber':\n return 'number'\n case 'ZodDate':\n return 'date'\n case 'ZodString':\n default:\n return 'text'\n }\n}\n\nconst getArrayOption = (field: any, name: string) => {\n return field._def[name]?.value\n}\n\n/**\n * A helper function to render forms automatically based on a Yup schema\n *\n * @param schema The Yup schema\n * @returns {FieldProps[]}\n */\nexport const getFieldsFromSchema = (schema: z.ZodTypeAny): FieldProps[] => {\n const fields: FieldProps[] = []\n\n let schemaFields: Record<string, any> = {}\n if (schema._def.typeName === 'ZodArray') {\n schemaFields = schema._def.type.shape\n } else if (schema._def.typeName === 'ZodObject') {\n schemaFields = schema._def.shape()\n } else {\n return fields\n }\n\n for (const name in schemaFields) {\n const field = schemaFields[name]\n\n const options: Options = {}\n if (field._def.typeName === 'ZodArray') {\n options.min = getArrayOption(field, 'minLength')\n options.max = getArrayOption(field, 'maxLength')\n }\n\n const meta = field.description && zodParseMeta(field.description)\n\n fields.push({\n name,\n label: meta?.label || field.description || name,\n type: meta?.type || getType(field),\n ...options,\n })\n }\n return fields\n}\n\nexport const getNestedSchema = (schema: z.ZodTypeAny, path: string) => {\n return get(schema._def.shape(), path)\n}\n\nexport const zodFieldResolver = <T extends z.ZodTypeAny>(schema: T) => {\n return {\n getFields() {\n return getFieldsFromSchema(schema)\n },\n getNestedFields(name: string) {\n return getFieldsFromSchema(getNestedSchema(schema, name))\n },\n }\n}\n\nexport interface ZodMeta {\n label: string\n type?: string\n}\n\nexport const zodMeta = (meta: ZodMeta) => {\n return JSON.stringify(meta)\n}\n\nexport const zodParseMeta = (meta: string) => {\n try {\n return JSON.parse(meta)\n } catch (e) {\n return meta\n }\n}\n","import {\n createForm,\n CreateFormProps,\n WithFields,\n FormProps,\n} from '@saas-ui/forms'\nimport { zodFieldResolver, zodResolver } from './zod-resolver'\nimport { z } from 'zod'\n\ntype ResolverArgs = Parameters<typeof zodResolver>\n\nexport interface CreateZodFormProps<FieldDefs>\n extends CreateFormProps<FieldDefs> {\n schemaOptions?: ResolverArgs[1]\n resolverOptions?: ResolverArgs[2]\n}\n\nexport const createZodForm = <FieldDefs>(\n options?: CreateZodFormProps<FieldDefs>\n) => {\n return createForm({\n resolver: (schema) =>\n zodResolver(schema, options?.schemaOptions, options?.resolverOptions),\n fieldResolver: zodFieldResolver,\n ...options,\n }) as <\n TSchema extends z.AnyZodObject = z.AnyZodObject,\n TContext extends object = object\n >(\n props: WithFields<\n FormProps<z.infer<TSchema>, TContext, TSchema>,\n FieldDefs\n > & {\n ref?: React.ForwardedRef<HTMLFormElement>\n }\n ) => React.ReactElement\n}\n","export {\n zodResolver,\n getFieldsFromSchema,\n getNestedSchema,\n zodFieldResolver,\n zodMeta,\n zodParseMeta,\n} from './zod-resolver'\nexport type { Options, ZodMeta } from './zod-resolver'\nexport { createZodForm } from './create-zod-form'\n\nimport { createZodForm } from './create-zod-form'\n\nexport const Form = createZodForm()\n"]}
|
package/package.json
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
{
|
2
2
|
"name": "@saas-ui/forms",
|
3
|
-
"version": "2.0.0-next.
|
3
|
+
"version": "2.0.0-next.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/
|
87
|
-
"@chakra-ui/
|
88
|
-
"@
|
89
|
-
"
|
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": "^
|
104
|
+
"@hookform/resolvers": "^3.0.1",
|
102
105
|
"@types/json-schema": "^7.0.11",
|
103
106
|
"ajv": "^8.12.0",
|
104
|
-
"
|
105
|
-
"
|
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
|
}
|
package/src/array-field.tsx
CHANGED
@@ -7,11 +7,11 @@ import {
|
|
7
7
|
Button,
|
8
8
|
ButtonProps,
|
9
9
|
} from '@chakra-ui/react'
|
10
|
-
import {
|
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
|
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
|
-
|
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
|
-
|
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}>
|
134
|
+
<chakra.div {...rest} __css={styles}>
|
135
|
+
{children}
|
136
|
+
</chakra.div>
|
122
137
|
</ArrayFieldRowProvider>
|
123
138
|
)
|
124
139
|
}
|
125
140
|
|
126
|
-
|
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
|
-
|
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 || <
|
173
|
+
{props.children || <PlusIcon />}
|
153
174
|
</Button>
|
154
175
|
)
|
155
176
|
}
|
156
177
|
|
157
|
-
|
158
|
-
ArrayFieldAddButton.displayName = 'ArrayFieldAddButton'
|
159
|
-
}
|
178
|
+
ArrayFieldAddButton.displayName = 'ArrayFieldAddButton'
|
160
179
|
|
161
|
-
export interface ArrayFieldProps
|
162
|
-
extends
|
163
|
-
|
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
|
-
|
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
|
-
|
210
|
-
|
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
|
-
}:
|
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
|
-
|
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 {
|
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
|
-
{<
|
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
|
+
}
|