diff --git a/package-lock.json b/package-lock.json index cffa4cb..ff61351 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@ts-react/form", - "version": "1.0.6", + "version": "1.4.5", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@ts-react/form", - "version": "1.0.6", + "version": "1.4.5", "license": "ISC", "devDependencies": { "@hookform/resolvers": "^2.9.10", @@ -17,9 +17,12 @@ "@testing-library/user-event": "^14.4.3", "@types/jest": "^29.2.4", "@types/react": "^18.0.26", + "@types/testing-library__jest-dom": "^5.14.5", + "@types/yargs": "^17.0.23", "expect-type": "^0.15.0", "jest": "^29.3.1", "jest-environment-jsdom": "^29.3.1", + "prettier": "^2.8.4", "react": "^18.2.0", "react-dom": "^18.2.0", "react-hook-form": "^7.41.3", @@ -1467,9 +1470,9 @@ "dev": true }, "node_modules/@types/yargs": { - "version": "17.0.22", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.22.tgz", - "integrity": "sha512-pet5WJ9U8yPVRhkwuEIp5ktAeAqRZOq4UdAyWLWzxbtpyXnzbtLdKiXAjJzi/KLmPGS9wk86lUFWZFN6sISo4g==", + "version": "17.0.23", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.23.tgz", + "integrity": "sha512-yuogunc04OnzGQCrfHx+Kk883Q4X0aSwmYZhKjI21m+SVYzjIbrWl8dOOwSv5hf2Um2pdCOXWo9isteZTNXUZQ==", "dev": true, "dependencies": { "@types/yargs-parser": "*" @@ -4617,6 +4620,21 @@ "node": ">= 0.8.0" } }, + "node_modules/prettier": { + "version": "2.8.6", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.8.6.tgz", + "integrity": "sha512-mtuzdiBbHwPEgl7NxWlqOkithPyp4VN93V7VeHVWBF+ad3I5avc0RVDT4oImXQy9H/AqxA2NSQH8pSxHW6FYbQ==", + "dev": true, + "bin": { + "prettier": "bin-prettier.js" + }, + "engines": { + "node": ">=10.13.0" + }, + "funding": { + "url": "https://github.com/prettier/prettier?sponsor=1" + } + }, "node_modules/pretty-format": { "version": "27.5.1", "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-27.5.1.tgz", diff --git a/package.json b/package.json index 49bee5b..02e7d19 100644 --- a/package.json +++ b/package.json @@ -35,6 +35,7 @@ "@types/jest": "^29.2.4", "@types/react": "^18.0.26", "@types/testing-library__jest-dom": "^5.14.5", + "@types/yargs": "^17.0.23", "expect-type": "^0.15.0", "jest": "^29.3.1", "jest-environment-jsdom": "^29.3.1", @@ -57,4 +58,4 @@ "react-hook-form": "^7.39.0", "zod": "^3.19.0" } -} +} \ No newline at end of file diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index aed3fd0..d98b5d5 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -10,6 +10,7 @@ specifiers: '@types/jest': ^29.2.4 '@types/react': ^18.0.26 '@types/testing-library__jest-dom': ^5.14.5 + '@types/yargs': ^17.0.23 expect-type: ^0.15.0 jest: ^29.3.1 jest-environment-jsdom: ^29.3.1 @@ -32,6 +33,7 @@ devDependencies: '@types/jest': 29.4.0 '@types/react': 18.0.28 '@types/testing-library__jest-dom': 5.14.5 + '@types/yargs': 17.0.23 expect-type: 0.15.0 jest: 29.5.0 jest-environment-jsdom: 29.5.0 @@ -631,7 +633,7 @@ packages: '@types/istanbul-lib-coverage': 2.0.4 '@types/istanbul-reports': 3.0.1 '@types/node': 18.14.6 - '@types/yargs': 17.0.22 + '@types/yargs': 17.0.23 chalk: 4.1.2 dev: true @@ -920,8 +922,8 @@ packages: resolution: {integrity: sha512-iO9ZQHkZxHn4mSakYV0vFHAVDyEOIJQrV2uZ06HxEPcx+mt8swXoZHIbaaJ2crJYFfErySgktuTZ3BeLz+XmFA==} dev: true - /@types/yargs/17.0.22: - resolution: {integrity: sha512-pet5WJ9U8yPVRhkwuEIp5ktAeAqRZOq4UdAyWLWzxbtpyXnzbtLdKiXAjJzi/KLmPGS9wk86lUFWZFN6sISo4g==} + /@types/yargs/17.0.23: + resolution: {integrity: sha512-yuogunc04OnzGQCrfHx+Kk883Q4X0aSwmYZhKjI21m+SVYzjIbrWl8dOOwSv5hf2Um2pdCOXWo9isteZTNXUZQ==} dependencies: '@types/yargs-parser': 21.0.0 dev: true diff --git a/src/FieldContext.tsx b/src/FieldContext.tsx index 624bb3a..1453bcb 100644 --- a/src/FieldContext.tsx +++ b/src/FieldContext.tsx @@ -8,6 +8,21 @@ import { } from "react-hook-form"; import { printUseEnumWarning } from "./logging"; import { errorFromRhfErrorObject } from "./zodObjectErrors"; +import { RTFSupportedZodTypes } from "./supportedZodTypes"; +import { UnwrapZodType, unwrap } from "./unwrap"; +import { + RTFSupportedZodFirstPartyTypeKind, + RTFSupportedZodFirstPartyTypeKindMap, + isTypeOf, + isZodArray, + isZodDefaultDef, +} from "./isZodTypeEqual"; + +import { + PickPrimitiveObjectProperties, + pickPrimitiveObjectProperties, +} from "./utilities"; +import { ZodDefaultDef } from "zod"; export const FieldContext = createContext; @@ -15,6 +30,7 @@ export const FieldContext = createContext void; removeFromCoerceUndefined: (v: string) => void; }>(null); @@ -26,6 +42,7 @@ export function FieldContextProvider({ label, placeholder, enumValues, + zodType, addToCoerceUndefined, removeFromCoerceUndefined, }: { @@ -35,6 +52,7 @@ export function FieldContextProvider({ placeholder?: string; enumValues?: string[]; children: ReactNode; + zodType: RTFSupportedZodTypes; addToCoerceUndefined: (v: string) => void; removeFromCoerceUndefined: (v: string) => void; }) { @@ -46,6 +64,7 @@ export function FieldContextProvider({ label, placeholder, enumValues, + zodType, addToCoerceUndefined, removeFromCoerceUndefined, }} @@ -201,6 +220,14 @@ export function enumValuesNotPassedError() { return `Enum values not passed. Any component that calls useEnumValues should be rendered from an '.enum()' zod field.`; } +export function fieldSchemaMismatchHookError( + hookName: string, + { expectedType, receivedType }: { expectedType: string; receivedType: string } +) { + return `Make sure that the '${hookName}' hook is being called inside of a custom form component which matches the correct type. + The expected type is '${expectedType}' but the received type was '${receivedType}'`; +} + /** * Gets an enum fields values. Throws an error if there are no enum values found (IE you mapped a z.string() to a component * that calls this hook). @@ -228,3 +255,171 @@ export function useEnumValues() { if (!enumValues) throw new Error(enumValuesNotPassedError()); return enumValues; } + +function getFieldInfo< + TZodType extends RTFSupportedZodTypes, + TUnwrapZodType extends UnwrapZodType = UnwrapZodType +>(zodType: TZodType) { + const { type, _rtf_id } = unwrap(zodType); + + function getDefaultValue() { + const def = zodType._def; + if (isZodDefaultDef(def)) { + const defaultValue = (def as ZodDefaultDef).defaultValue(); + return defaultValue; + } + return undefined; + } + + return { + type: type as TUnwrapZodType, + zodType, + uniqueId: _rtf_id ?? undefined, + isOptional: zodType.isOptional(), + isNullable: zodType.isNullable(), + defaultValue: getDefaultValue(), + }; +} + +/** + * @internal + */ +export function internal_useFieldInfo< + TZodType extends RTFSupportedZodTypes = RTFSupportedZodTypes, + TUnwrappedZodType extends UnwrapZodType = UnwrapZodType +>(hookName: string) { + const { zodType, label, placeholder } = useContextProt(hookName); + + const fieldInfo = getFieldInfo( + zodType as TZodType + ); + + return { ...fieldInfo, label, placeholder }; +} + +/** + * Returns schema-related information for a field + * + * @returns The Zod type for the field. + */ +export function useFieldInfo() { + return internal_useFieldInfo("useFieldInfo"); +} + +/** + * The zod type objects contain virtual properties which requires us to + * manually pick the properties we'd like inorder to get their values. + */ +export function usePickZodFields< + TZodKindName extends RTFSupportedZodFirstPartyTypeKind, + TZodType extends RTFSupportedZodFirstPartyTypeKindMap[TZodKindName] = RTFSupportedZodFirstPartyTypeKindMap[TZodKindName], + TUnwrappedZodType extends UnwrapZodType = UnwrapZodType, + TPick extends Partial< + PickPrimitiveObjectProperties + > = Partial> +>(zodKindName: TZodKindName, pick: TPick, hookName: string) { + const fieldInfo = internal_useFieldInfo( + hookName + ); + + function getType() { + const { type } = fieldInfo; + + if (zodKindName !== "ZodArray" && isZodArray(type)) { + const element = type.element; + return element as any; + } + + return type; + } + + const type = getType(); + + if (!isTypeOf(type, zodKindName)) { + throw new Error( + fieldSchemaMismatchHookError(hookName, { + expectedType: zodKindName, + receivedType: type._def.typeName, + }) + ); + } + + return { + ...pickPrimitiveObjectProperties(type, pick), + ...fieldInfo, + }; +} + +/** + * Returns schema-related information for a ZodString field + * + * @example + * ```tsx + * const CustomComponent = () => { + * const { minLength, maxLength, uniqueId } = useStringFieldInfo(); + * + * return ; + * }; + * ``` + * @returns Information for a ZodString field + */ +export function useStringFieldInfo() { + return usePickZodFields( + "ZodString", + { + isCUID: true, + isCUID2: true, + isDatetime: true, + isEmail: true, + isEmoji: true, + isIP: true, + isULID: true, + isURL: true, + isUUID: true, + maxLength: true, + minLength: true, + }, + "useStringFieldInfo" + ); +} + +/** + * Returns schema-related information for a ZodString field + * + * @example + * ```tsx + * const CustomComponent = () => { + * const { minLength, maxLength, uniqueId } = useStringFieldInfo(); + * + * return ; + * }; + * ``` + * @returns Information for a ZodString field + */ +export function useArrayFieldInfo() { + return usePickZodFields( + "ZodArray", + { + description: true, + }, + "useArrayFieldInfo" + ); +} + +/** + * Returns schema-related information for a ZodNumber field + * + * @returns data for a ZodNumber field + */ +export function useNumberFieldInfo() { + return usePickZodFields( + "ZodNumber", + { + isFinite: true, + isInt: true, + maxValue: true, + minValue: true, + }, + "useNumberFieldInfo" + ); +} diff --git a/src/__tests__/createSchemaForm.test.tsx b/src/__tests__/createSchemaForm.test.tsx index 2db2318..1037a54 100644 --- a/src/__tests__/createSchemaForm.test.tsx +++ b/src/__tests__/createSchemaForm.test.tsx @@ -25,6 +25,8 @@ import { useEnumValues, useReqDescription, useTsController, + useStringFieldInfo, + useFieldInfo, } from "../FieldContext"; import { expectTypeOf } from "expect-type"; import { createUniqueFieldSchema } from "../createFieldSchema"; @@ -1181,4 +1183,175 @@ describe("createSchemaForm", () => { expect(screen.queryByText("one")).toBeInTheDocument(); expect(screen.queryByText("two")).toBeInTheDocument(); }); + it("should be possible to get ZodAny information using `useFieldInfo`", () => { + const testData = { + requiredTextField: { + label: "required-label", + placeholder: "required-placeholder", + uniqueId: "required-text-field", + }, + optionalTextField: { + label: "optional-label", + placeholder: "optional-placeholder", + uniqueId: "optional-text-field", + }, + }; + + const description = (k: keyof typeof testData) => + `${testData[k].label}${DESCRIPTION_SEPARATOR_SYMBOL}${testData[k].placeholder}`; + + const RequiredTextFieldSchema = createUniqueFieldSchema( + z.string(), + testData.requiredTextField.uniqueId + ); + + const OptionalTextFieldSchema = createUniqueFieldSchema( + z.string().optional(), + testData.optionalTextField.uniqueId + ); + + function RequiredTextField() { + const fieldInfo = useFieldInfo(); + + expect(fieldInfo.isOptional).toBeFalsy(); + expect(fieldInfo.label).toBe(testData.requiredTextField.label); + expect(fieldInfo.placeholder).toBe( + testData.requiredTextField.placeholder + ); + expect(fieldInfo.uniqueId).toBe(testData.requiredTextField.uniqueId); + + return ; + } + + function OptionalTextField() { + const fieldInfo = useFieldInfo(); + + expect(fieldInfo.isOptional).toBe(true); + expect(fieldInfo.label).toBe(testData.optionalTextField.label); + expect(fieldInfo.placeholder).toBe( + testData.optionalTextField.placeholder + ); + expect(fieldInfo.uniqueId).toBe(testData.optionalTextField.uniqueId); + + return ; + } + + const defaultEmail = "john@example.com"; + + const DefaultTextField = () => { + // @ts-expect-error + const { defaultValue, type, zodType } = useFieldInfo(); + + expect(defaultValue).toBe(defaultEmail); + + return ; + }; + + const schema = z.object({ + email: z.string().default(defaultEmail), + name: RequiredTextFieldSchema.describe(description("requiredTextField")), + nickName: OptionalTextFieldSchema.describe( + description("optionalTextField") + ), + }); + + const mapping = [ + [z.string(), DefaultTextField], + [RequiredTextFieldSchema, RequiredTextField], + [OptionalTextFieldSchema, OptionalTextField], + ] as const; + + const Form = createTsForm(mapping); + + render(
{}} />); + }); + it("should be possible to get ZodString information using `useStringFieldInfo`", () => { + const testData = { + textField: { + uniqueId: "text-field-id", + label: "text-field-label", + placeholder: "text-field-placeholder", + min: 5, + max: 16, + get schema() { + const { min, max, uniqueId } = this; + return createUniqueFieldSchema( + z.string().min(min).max(max), + uniqueId + ); + }, + + get component() { + const { min, max, label, uniqueId } = this; + + const TextFieldComponent = () => { + const fieldInfo = useStringFieldInfo(); + + expect(fieldInfo.minLength).toBe(min); + expect(fieldInfo.maxLength).toBe(max); + expect(fieldInfo.label).toBe(label); + expect(fieldInfo.uniqueId).toBe(uniqueId); + + return
{fieldInfo.label}
; + }; + + return TextFieldComponent; + }, + }, + arrayTextField: { + uniqueId: "array-text-field-id", + label: "array-text-field-label", + placeholder: "array-text-field-placeholder", + min: 5, + max: 16, + get schema() { + const { min, max, uniqueId } = this; + return createUniqueFieldSchema( + z.string().min(min).max(max).array(), + uniqueId + ); + }, + get component() { + const { min, max, label, uniqueId } = this; + + const ArrayTextFieldComponent = () => { + const fieldInfo = useStringFieldInfo(); + + expect(fieldInfo.minLength).toBe(min); + expect(fieldInfo.maxLength).toBe(max); + expect(fieldInfo.label).toBe(label); + expect(fieldInfo.uniqueId).toBe(uniqueId); + + return
{fieldInfo.label}
; + }; + + return ArrayTextFieldComponent; + }, + }, + }; + + const description = (k: keyof typeof testData) => + `${testData[k].label}${DESCRIPTION_SEPARATOR_SYMBOL}${testData[k].placeholder}`; + + const { textField, arrayTextField } = testData; + + const schema = z.object({ + name: textField.schema.describe(description("textField")), + users: arrayTextField.schema.describe(description("arrayTextField")), + }); + + const mapping = [ + [textField.schema, textField.component], + [arrayTextField.schema, arrayTextField.component], + ] as const; + + const Form = createTsForm(mapping); + + render( {}} />); + + expect(screen.queryByText(testData.textField.label)).toBeInTheDocument(); + expect( + screen.queryByText(testData.arrayTextField.label) + ).toBeInTheDocument(); + }); }); diff --git a/src/createSchemaForm.tsx b/src/createSchemaForm.tsx index 29573a4..535680a 100644 --- a/src/createSchemaForm.tsx +++ b/src/createSchemaForm.tsx @@ -420,6 +420,7 @@ export function createTsForm< control={control} name={stringKey} label={ctxLabel} + zodType={type} placeholder={ctxPlaceholder} enumValues={meta.enumValues as string[] | undefined} addToCoerceUndefined={submitter.addToCoerceUndefined} diff --git a/src/index.ts b/src/index.ts index 750dce7..eaff8d1 100644 --- a/src/index.ts +++ b/src/index.ts @@ -5,5 +5,8 @@ export { useReqDescription, useEnumValues, useTsController, + useFieldInfo, + useStringFieldInfo, + useNumberFieldInfo } from "./FieldContext"; export type { RTFSupportedZodTypes } from "./supportedZodTypes"; diff --git a/src/isZodTypeEqual.tsx b/src/isZodTypeEqual.tsx index 9c2c592..e0fd73d 100644 --- a/src/isZodTypeEqual.tsx +++ b/src/isZodTypeEqual.tsx @@ -1,4 +1,14 @@ -import { ZodFirstPartyTypeKind } from "zod"; +import { + AnyZodObject, + ZodArray, + ZodBoolean, + ZodDate, + ZodDefaultDef, + ZodFirstPartyTypeKind, + ZodNumber, + ZodString, + z, +} from "zod"; import { RTFSupportedZodTypes } from "./supportedZodTypes"; import { unwrap } from "./unwrap"; @@ -19,6 +29,8 @@ export function isZodTypeEqual( if (a._def.typeName !== b._def.typeName) return false; + // array + if ( a._def.typeName === ZodFirstPartyTypeKind.ZodArray && b._def.typeName === ZodFirstPartyTypeKind.ZodArray @@ -27,6 +39,8 @@ export function isZodTypeEqual( return false; } + // set + if ( a._def.typeName === ZodFirstPartyTypeKind.ZodSet && b._def.typeName === ZodFirstPartyTypeKind.ZodSet @@ -35,6 +49,8 @@ export function isZodTypeEqual( return false; } + // map + if ( a._def.typeName === ZodFirstPartyTypeKind.ZodMap && b._def.typeName === ZodFirstPartyTypeKind.ZodMap @@ -103,3 +119,66 @@ export function isZodTypeEqual( } return true; } + +// Guards + +export function isZodString( + zodType: RTFSupportedZodTypes +): zodType is ZodString { + return isTypeOf(zodType, "ZodString"); +} + +export function isZodNumber( + zodType: RTFSupportedZodTypes +): zodType is ZodNumber { + return isTypeOf(zodType, "ZodNumber"); +} + +export function isZodBoolean( + zodType: RTFSupportedZodTypes +): zodType is ZodBoolean { + return isTypeOf(zodType, "ZodBoolean"); +} + +export function isZodArray( + zodType: RTFSupportedZodTypes +): zodType is ZodArray { + return isTypeOf(zodType, "ZodArray"); +} + +export function isZodObject( + zodType: RTFSupportedZodTypes +): zodType is AnyZodObject { + return isTypeOf(zodType, "ZodObject"); +} + +export function isZodDefaultDef(zodDef: unknown): zodDef is ZodDefaultDef { + return Boolean( + zodDef && + typeof zodDef === "object" && + "defaultValue" in zodDef && + typeof zodDef.defaultValue === "function" + ); +} + +export function isZodDate(zodType: RTFSupportedZodTypes): zodType is ZodDate { + return isTypeOf(zodType, "ZodDate"); +} + +export function isTypeOf(zodType: RTFSupportedZodTypes, type: ZodKindName) { + return zodType._def.typeName === ZodFirstPartyTypeKind[type]; +} + +type ZodKindName = keyof typeof z.ZodFirstPartyTypeKind; + +export type ZodKindNameToType = + InstanceType<(typeof z)[K]>; + +export type RTFSupportedZodFirstPartyTypeKindMap = { + [K in ZodKindName as ZodKindNameToType extends RTFSupportedZodTypes + ? K + : never]: ZodKindNameToType; +}; + +export type RTFSupportedZodFirstPartyTypeKind = + keyof RTFSupportedZodFirstPartyTypeKindMap; diff --git a/src/typeUtilities.ts b/src/typeUtilities.ts index a3ead5c..b8f7797 100644 --- a/src/typeUtilities.ts +++ b/src/typeUtilities.ts @@ -101,3 +101,24 @@ export type IndexOf = { : never : never; }[keyof Indexes]; + +/** + * @internal + */ +export type ExpandRecursively = T extends object + ? T extends infer O + ? { [K in keyof O]: ExpandRecursively } + : never + : T; + +/** + * @internal + */ +export type NullToUndefined = T extends null ? undefined : T; + +/** + * @internal + */ +export type RemoveNull = ExpandRecursively<{ + [K in keyof T]: NullToUndefined; +}>; diff --git a/src/unwrap.tsx b/src/unwrap.tsx index 9177b16..54d5f7a 100644 --- a/src/unwrap.tsx +++ b/src/unwrap.tsx @@ -18,10 +18,14 @@ const unwrappable = new Set([ z.ZodFirstPartyTypeKind.ZodDefault, ]); -export function unwrap(type: RTFSupportedZodTypes): { +export type UnwrappedRTFSupportedZodTypes = { type: RTFSupportedZodTypes; [HIDDEN_ID_PROPERTY]: string | null; -} { +}; + +export function unwrap( + type: RTFSupportedZodTypes +): UnwrappedRTFSupportedZodTypes { // Realized zod has a built in "unwrap()" function after writing this. // Not sure if it's super necessary. let r = type; diff --git a/src/utilities.ts b/src/utilities.ts new file mode 100644 index 0000000..225ff1c --- /dev/null +++ b/src/utilities.ts @@ -0,0 +1,50 @@ +import { Primitive as ZodPrimitive } from "zod"; +import { RemoveNull } from "./typeUtilities"; + +type InternalKey = `_${string}`; + +type Primitive = Exclude; + +export type PickPrimitiveObjectProperties< + T, + TValue extends false | unknown = false +> = { + [K in keyof T as T[K] extends Exclude + ? Exclude + : never]: TValue extends false ? T[K] : TValue; +}; + +type PickResult = Pick< + T, + Extract +>; + +/** + * Picks only properties with primitive values + * Picks only properties with primitive values from an object and returns a new object with those properties. + * + * @returns A new object containing only the properties with primitive values. + * + */ +export function pickPrimitiveObjectProperties< + T extends object, + TPick extends Partial>, + TObj extends PickPrimitiveObjectProperties = PickPrimitiveObjectProperties, + TResult extends RemoveNull> = RemoveNull< + PickResult + > +>(obj: T, pick: TPick): TResult { + return Object.entries(pick).reduce((result, [key]) => { + const value = obj[key as keyof typeof obj]; + if ( + typeof value === "string" || + typeof value === "number" || + typeof value === "boolean" || + typeof value === "bigint" || + value === undefined + ) { + (result as any)[key] = value; + } + return result; + }, {} as Pick>) as TResult; +} diff --git a/www/docs/docs/usage/field-schema-info.md b/www/docs/docs/usage/field-schema-info.md new file mode 100644 index 0000000..3ee2c50 --- /dev/null +++ b/www/docs/docs/usage/field-schema-info.md @@ -0,0 +1,76 @@ +# Access Schema Data + +From a custom form component, we're able to access field schema information through a set of hooks.
+This allows components to access information about the type and validation rules of the corresponding form field. + + +## `useFieldInfo` + +Returns schema-related information for any field + +```tsx +import { useFieldInfo } from "@ts-react/form"; + +const MyCustomField = () => { + const { label, placeholder, isOptional, zodType } = useFieldInfo(); + return ( +
+ {label} + +
+ ); +}; +``` + + +## `useStringFieldInfo` + +Returns schema-related information for a ZodString field + + +```tsx +import { useStringFieldInfo } from "@ts-react/form"; + +const MyStringCustomField = () => { + + const { label, placeholder, isOptional, minLength, maxLength, zodType } = + useStringFieldInfo(); + + return ( +
+ {label} + +
+ ); +}; +``` + +## `useNumberFieldInfo` + +Returns schema-related information for a ZodNumber field + +```tsx +import { useNumberFieldInfo } from "@ts-react/form"; + +const MyNumberCustomField = () => { + const { label, placeholder, isOptional, minValue, maxValue, isInt, zodType } = + useNumberFieldInfo(); + return ( +
+ {label} + +
+ ); +}; +``` diff --git a/www/package-lock.json b/www/package-lock.json index 0254969..a5cebb1 100644 --- a/www/package-lock.json +++ b/www/package-lock.json @@ -19,6 +19,8 @@ }, "devDependencies": { "@docusaurus/module-type-aliases": "2.3.1", + "@docusaurus/theme-common": "^2.3.1", + "@docusaurus/types": "^2.3.1", "@tsconfig/docusaurus": "^1.0.5", "autoprefixer": "^10.4.13", "docusaurus-tailwindcss": "^0.1.0", @@ -29,6 +31,9 @@ }, "engines": { "node": ">=16.14" + }, + "peerDependencies": { + "@docusaurus/theme-common": "^2.1.0" } }, "node_modules/@algolia/autocomplete-core": { diff --git a/www/sidebars.js b/www/sidebars.js index 43c3e0f..b68777b 100644 --- a/www/sidebars.js +++ b/www/sidebars.js @@ -30,6 +30,7 @@ const sidebars = { { type: "doc", id: "docs/usage/default-values" }, { type: "doc", id: "docs/usage/form-state" }, { type: "doc", id: "docs/usage/labels-placeholders" }, + { type: "doc", id: "docs/usage/field-schema-info" }, ], }, {