@goutham1494/form-engine
Version:
A schema-driven dynamic form builder for React
257 lines (250 loc) • 8.67 kB
text/typescript
import React from 'react';
import { useFormContext } from 'react-hook-form';
declare const FIELD_TYPES: {
readonly TEXT: "text";
readonly NUMBER: "number";
readonly EMAIL: "email";
readonly SELECT: "select";
readonly CHECKBOX: "checkbox";
readonly GROUP: "group";
readonly RADIO: "radio";
readonly TEXTAREA: "textarea";
readonly ADDITIONAL_EMAIL: "additionalEmail";
};
type FieldType = (typeof FIELD_TYPES)[keyof typeof FIELD_TYPES];
/**
* Schema definition for a single form field used in DynamicForm
*/
interface FormFieldSchema {
/** Unique field name (used as key in form data) */
name: string;
/** Label to display above the field */
label: string;
/** Type of the field: text, select, radio, checkbox, group, etc. */
type: FieldType;
/** Default value for the field */
defaultValue?: unknown;
/** Whether the field is required */
required?: boolean;
/** Whether the field is disabled */
disabled?: boolean;
/** Input placeholder text */
placeholder?: string;
/** Whether to show error on blur instead of change */
showErrorOnBlur?: boolean;
/** Optional custom error message (overrides validation error messages) */
errorText?: string;
/** Optional dynamic error formatter */
getErrorMessage?: (error: any) => string;
/** Delay in milliseconds for debounced change handler */
debounceMs?: number;
/** Field color theme */
theme?: "dark" | "light";
tooltip?: string;
/** Optional icon to display in the field */
icon?: React.ReactNode;
/** Optional help text shown below the field */
helpText?: string;
helpTextClass?: string;
helpTextStyle?: React.CSSProperties;
/** Allowed pattern for character input */
allowedPattern?: RegExp;
/** Input mode (for better mobile keyboards) */
inputMode?: "text" | "numeric" | "decimal" | "tel" | "email" | "url" | "search";
/** Validation rules */
validation?: {
pattern?: {
value: RegExp;
message: string;
};
minLength?: {
value: number;
message: string;
};
maxLength?: {
value: number;
message: string;
};
custom?: (value: any) => boolean | string;
};
/** Conditional visibility of the field */
visibleWhen?: {
logic?: "AND" | "OR";
conditions: {
field: string;
value: any | any[];
operator?: "equals" | "notEquals" | "in" | "notIn" | "exists" | "notExists";
}[];
};
/** Whether to preserve value if field becomes hidden */
preserveValue?: boolean;
/** Declare field dependency */
dependsOn?: string;
maxLength?: number;
minLength?: number;
showWordCount?: boolean;
/** Optional prefix icon */
prefixIcon?: React.ReactNode;
/** Optional suffix icon */
suffixIcon?: React.ReactNode;
/** Options used for select, radio, or checkbox */
options?: {
label: string;
value: string;
helpText?: string;
disabled?: boolean;
icon?: React.ReactNode;
tooltip?: string;
helpTextAlignment?: "underLabel" | "underButton";
}[];
onBlur?: (event: React.FocusEvent<HTMLInputElement>) => void;
onFocus?: (event: React.FocusEvent<HTMLInputElement>) => void;
onClick?: (event: React.MouseEvent<HTMLInputElement>) => void;
onMouseEnter?: (event: React.MouseEvent<HTMLInputElement>) => void;
onMouseLeave?: (event: React.MouseEvent<HTMLInputElement>) => void;
onMouseOver?: (event: React.MouseEvent<HTMLInputElement>) => void;
onMouseDown?: (event: React.MouseEvent<HTMLInputElement>) => void;
onMouseUp?: (event: React.MouseEvent<HTMLInputElement>) => void;
/**
* Alignment of help text relative to radio checkbox options
* - 'underLabel' (default): aligns help text with label
* - 'underButton': aligns help text under the radio/checkbox circle
*/
helpTextAlignment?: "underLabel" | "underButton";
/** Optional endpoint to fetch dynamic options */
apiEndpoint?: string;
/** Callback to generate options dynamically */
getOptions?: (parentValue: any) => {
label: string;
value: string;
}[] | Promise<{
label: string;
value: string;
}[]>;
/** Raw change event handler */
onChange?: (event: React.ChangeEvent<HTMLInputElement>) => void;
/** Raw keyDown event handler */
onKeyDown?: (event: React.KeyboardEvent<HTMLInputElement>) => void;
/** Called when value changes (non-debounced) */
onValueChange?: (value: any, utils: {
setValue: ReturnType<typeof useFormContext>["setValue"];
getValues: ReturnType<typeof useFormContext>["getValues"];
trigger: ReturnType<typeof useFormContext>["trigger"];
}) => void;
/** Called when value changes (debounced) */
onValueChangeDebounced?: (value: any, utils: {
setValue: ReturnType<typeof useFormContext>["setValue"];
getValues: ReturnType<typeof useFormContext>["getValues"];
trigger: ReturnType<typeof useFormContext>["trigger"];
}) => Promise<void> | void;
/** Styling options */
wrapperClass?: string;
wrapperStyle?: React.CSSProperties;
labelClass?: string;
labelStyle?: React.CSSProperties;
inputClass?: string;
inputStyle?: React.CSSProperties;
errorClass?: string;
errorStyle?: React.CSSProperties;
/** Checkbox group styles */
checkboxGroupClass?: string;
checkBoxGroupStyle?: React.CSSProperties;
checkboxLabel?: string;
/** Radio group styles */
radioGroupClass?: string;
radioGroupStyle?: React.CSSProperties;
radioInputClass?: string;
radioInputStyle?: React.CSSProperties;
inline?: boolean;
/** Option wrapper styles */
optionWrapperClass?: string;
optionWrapperStyle?: React.CSSProperties;
/** Group field support */
children?: FormFieldSchema[];
layoutClass?: string;
layoutStyle?: React.CSSProperties;
/** Advanced rendering control */
render?: (fieldProps: {
name: string;
error?: any;
register: any;
defaultValue?: any;
}) => React.ReactNode;
overrideComponent?: React.FC<{
field: FormFieldSchema;
name: string;
error?: any;
register: any;
}>;
overrideComponentProps?: Record<string, any>;
useRenderFirst?: boolean;
/** Optional metadata */
metaData?: {
buttonText?: string;
};
stepDependencies?: {
stepIndex: number;
field: string;
value: any;
}[];
}
interface DynamicFormProps {
schema: FormFieldSchema[];
onSubmit: (values: any) => void;
formStyle?: React.CSSProperties;
formClassName?: string;
buttonStyle?: React.CSSProperties;
buttonClassName?: string;
submitButtonStyle?: React.CSSProperties;
submitButtonClassName?: string;
resetButtonStyle?: React.CSSProperties;
resetButtonClassName?: string;
submitLabel?: string;
resetLabel?: string;
hideSubmitButton?: boolean;
children?: React.ReactNode;
showReset?: boolean;
onReset?: () => void;
columns?: number;
gap?: string;
maxWidth?: number | string;
extraActions?: {
label: string;
onClick: () => void;
type?: "button" | "submit" | "reset";
style?: React.CSSProperties;
className?: string;
icon?: React.ReactNode;
}[];
}
declare const DynamicForm: React.FC<DynamicFormProps>;
interface Props {
field: FormFieldSchema;
parentName?: string;
}
declare const _default: React.NamedExoticComponent<Props>;
interface FormWizardProps {
steps: {
title?: string;
description?: string;
fields: FormFieldSchema[];
validationMode?: "onChange" | "onBlur" | "onSubmit" | "onTouched";
}[];
onSubmit: (values: any) => void;
onStepChange?: (currentStep: number) => void;
wizardStyle?: React.CSSProperties;
navigationStyle?: React.CSSProperties;
progressStyle?: React.CSSProperties;
showProgress?: boolean;
renderProgress?: (current: number, total: number) => React.ReactNode;
renderNavigation?: (helpers: {
currentStep: number;
totalSteps: number;
isFirst: boolean;
isLast: boolean;
handlePrev: () => void;
handleNext: () => Promise<void>;
}) => React.ReactNode;
}
declare const FormWizard: React.FC<FormWizardProps>;
export { DynamicForm, FIELD_TYPES, _default as FieldRenderer, type FieldType, type FormFieldSchema, FormWizard };