red-form
Version:
A powerful, type-safe React form library that lets you create dynamic dialog-based forms using schema definitions — inspired by Formik but designed for real-time UI rendering and reusability.
346 lines (300 loc) • 9.68 kB
TypeScript
import * as react_jsx_runtime from 'react/jsx-runtime';
import { ReactNode, Dispatch, SetStateAction, CSSProperties } from 'react';
type ModalProps = {
isOpen: boolean;
onClose?: () => void;
title?: string;
children: React.ReactNode;
width?: number;
height?: number;
minHeight?: number;
};
type Adorment = {
start: ReactNode | undefined;
end: ReactNode | undefined;
};
type Option = string | { label: string; value: string };
type InputProps<T extends Schema, K extends keyof T> = { field: string; props: T[K]; form: FormInstance<T>; error: string[] | undefined; sx: FormSX };
type BaseField = {
label: string;
required?: boolean;
placeholder?: string;
helperText?: ReactNode;
information?: string;
disabled?: boolean;
span?: 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12;
validate?: <T extends Schema, K extends keyof T>({ field, props, form }: { field: string; props: T[K]; form: FormInstance<T> }) => string[];
};
type TextFieldProps = BaseField & {
component: "text";
value: string;
autoFill?: AutoFillField;
min?: number;
max?: number;
adorment?: Adorment;
};
type EmailFieldProps = BaseField & {
component: "email";
value: string;
adorment?: Adorment;
};
type SearchFieldProps = BaseField & {
component: "search";
value: string;
autoFill?: AutoFillField;
adorment?: Adorment;
};
type NumberFieldProps = BaseField & {
component: "number";
value: number;
min?: number;
max?: number;
step?: number;
adorment?: Adorment;
};
type PasswordFieldProps = BaseField & {
component: "password";
value: string;
min?: number;
max?: number;
adorment?: Adorment;
};
type DateFieldProps = BaseField & {
component: "date";
value: `${1 | 2}${number}${number}${number}-${0 | 1}${number}-${0 | 1 | 2 | 3}${number}` | "";
min?: string;
max?: string;
adorment?: Adorment;
};
type DateTimeFieldProps = BaseField & {
component: "datetime";
value: `${1 | 2}${number}${number}${number}-${0 | 1}${number}-${0 | 1 | 2 | 3}${number}T${0 | 1 | 2}${number}:${0 | 1 | 2 | 3 | 4 | 5 | 6}${number}` | "";
min?: string;
max?: string;
adorment?: Adorment;
};
type TimeFieldProps = BaseField & {
component: "time";
value: `${0 | 1 | 2}${number}:${0 | 1 | 2 | 3 | 4 | 5 | 6}${number}` | "";
min?: string;
max?: string;
adorment?: Adorment;
};
type WeekFieldProps = BaseField & {
component: "week";
value: `${number}${number}${number}${number}-W${number}${number}` | "";
min?: number;
max?: number;
adorment?: Adorment;
};
type MonthFieldProps = BaseField & {
component: "month";
value: `${1 | 2}${number}${number}${number}-${0 | 1}${number}` | "";
min?: number;
max?: number;
adorment?: Adorment;
};
type TelephoneFieldProps = BaseField & {
component: "telephone";
value: number;
min?: number;
max?: number;
adorment?: Adorment;
};
type TextAreaFieldProps = BaseField & {
component: "textarea";
value: string;
min?: number;
max?: number;
};
type CheckboxFieldProps = BaseField & {
component: "checkbox";
value: string[] | string | undefined;
direction?: "row" | "column";
options: (string | { label: string; value: string })[];
};
type RadioFieldProps = BaseField & {
component: "radio";
value: string;
direction?: "row" | "column";
options: Option[];
};
type RangeFieldProps = BaseField & {
component: "range";
value: number;
min: number;
max: number;
step?: number;
};
type ColorFieldProps = BaseField & {
component: "color";
value: `#${string}`;
};
type SwitchFieldProps = BaseField & {
component: "switch";
value: boolean;
};
type SelectFieldProps = BaseField & {
component: "select";
value: string;
options: Option[];
};
type MultiSelectFieldProps = BaseField & {
component: "multi-select";
value: string[];
options: Option[];
};
type TagsFieldProps = BaseField & {
component: "tags";
value: string[];
};
type ImageFieldProps = BaseField & {
component: "image";
value: string;
onSelect: (file: File) => Promise<string>;
};
type CustomFieldProps = BaseField & {
component: "custom";
value?: ReactNode;
inputBase?: boolean;
render: <T extends Schema, K extends keyof T>({ field, props, form }: InputProps<T, K>) => ReactNode;
};
type FieldSchema =
| TextFieldProps
| EmailFieldProps
| SearchFieldProps
| PasswordFieldProps
| NumberFieldProps
| DateFieldProps
| DateTimeFieldProps
| TimeFieldProps
| WeekFieldProps
| MonthFieldProps
| TimeFieldProps
| TelephoneFieldProps
| TextAreaFieldProps
| CheckboxFieldProps
| RadioFieldProps
| SwitchFieldProps
| RangeFieldProps
| ColorFieldProps
| SelectFieldProps
| MultiSelectFieldProps
| TagsFieldProps
| ImageFieldProps
| CustomFieldProps;
type Schema = Record<string, FieldSchema>;
// type Values<T extends Schema> = {
// [K in keyof T]: T[K]["type"] extends "number"
// ? T[K]["required"] extends true
// ? number
// : number | undefined
// : T[K]["type"] extends "text"
// ? T[K]["required"] extends true
// ? string
// : string | undefined
// : unknown;
// };
type Values<T extends Schema> = {
[K in keyof T]: T[K]["required"] extends true ? T[K]["value"] : T[K]["value"] | undefined;
};
type ValidateOnType = ("submit" | "change")[];
type FormSX = {
conteiner?: CSSProperties;
title?: CSSProperties;
description?: CSSProperties;
form?: CSSProperties;
actionArea?: CSSProperties;
resetButton?: CSSProperties;
submitButton?: CSSProperties;
inputContainer?: CSSProperties;
inputLabelContainer?: CSSProperties;
inputLabel?: CSSProperties;
tooltipContainer?: CSSProperties;
tooltipInfoIcon?: CSSProperties;
tooltip?: CSSProperties;
errorList?: CSSProperties;
errorItem?: CSSProperties;
helperText?: CSSProperties;
inputBase?: CSSProperties;
};
type FormProps<T extends Schema> = {
// FORM FIELDS
title?: string | ReactNode;
description?: ReactNode;
options?: {
validateOn?: FormOptions["validateOn"];
};
sx?: FormSX;
onSubmit?: (values: Values<T>) => void | Promise<void>;
onChange?: (values: Values<T>) => void;
onError?: (values: Errors<T>) => void;
onBlur?: (values: Touched<T>) => void;
disabled?: boolean;
schema: T;
// DIALOG FIELDS
open?: boolean;
close?: boolean;
width?: number;
height?: number;
minHeight?: number;
onClose: () => void;
};
type Errors<T extends Schema> = Partial<Record<keyof T, string[] | undefined>>;
type Touched<T extends Schema> = Partial<Record<keyof T, boolean>>;
type FormOptions = {
validateOn?: ValidateOnType;
};
interface FormInstance<T extends Schema> {
submitting: boolean;
setSubmitting: React.Dispatch<React.SetStateAction<boolean>>;
values: Values<T>;
setFieldValue: <K extends keyof T>(field: K, value: Values<T>[K]) => void;
setValues: React.Dispatch<React.SetStateAction<Values<T>>>;
errors: Errors<T>;
setFieldError: <K extends keyof T>(field: K, message: string) => void;
setErrors: Dispatch<SetStateAction<Partial<Record<keyof T, string[]>>>>;
touched: Touched<T>;
setFieldTouched: <K extends keyof T>(field: K, value: boolean) => void;
setTouched: Dispatch<SetStateAction<Partial<Record<keyof T, boolean>>>>;
handleBlur: (e: React.FocusEvent<HTMLInputElement>) => void;
activeField: string | undefined;
setFieldActive: <K extends keyof T>(field: K | undefined) => void;
handleFocus: (e: React.FocusEvent<HTMLInputElement>) => void;
handleChange: (e: React.ChangeEvent<HTMLInputElement>) => void;
handleSubmit: (e: React.FormEvent) => void;
validate: () => boolean;
resetForm: () => void;
submit: () => void;
// handleChange: <K extends keyof T>(field: K, value: Values<T>[K]) => void;
// handleBlur: <K extends keyof T>(field: K) => void;
getFieldProps: <K extends keyof T>(
field: K
) => {
name: K;
// value: Values<T>[K];
value: T[K]["value"] | undefined | any;
id: K;
required: boolean;
disabled: boolean;
ref: React.RefObject<HTMLInputElement | null> | undefined;
placeholder: string | undefined;
onChange: (e: React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement>) => void;
autoComplete: AutoFill | undefined;
onBlur: (e: React.FocusEvent<HTMLInputElement | HTMLTextAreaElement>) => void;
onFocus: (e: React.FocusEvent<HTMLInputElement | HTMLTextAreaElement>) => void;
onMouseDown: (e: React.MouseEvent<HTMLInputElement | HTMLTextAreaElement>) => void;
};
}
declare function useForm<T extends Schema>(schema: T, onSubmit: (values: Values<T>) => void, options?: FormOptions): FormInstance<T>;
declare const Form: <T extends Schema>({ schema, title, description, disabled, onSubmit, onChange, onError, onBlur, sx, options }: Omit<FormProps<T>, "open" | "close" | "minHeight" | "width" | "height" | "onClose">) => react_jsx_runtime.JSX.Element;
declare const FormProvider: ({ children }: {
children: ReactNode;
}) => react_jsx_runtime.JSX.Element;
declare const useModalForm: <T extends Schema>(schema: T, options: Omit<FormProps<T>, "schema" | "onClose">) => {
open: () => void;
close: () => void;
};
declare const Modal: (props: ModalProps) => react_jsx_runtime.JSX.Element | null;
declare const create: <T extends Schema>(schema: T) => T;
export { FormProvider, Modal, type Schema, create, Form as default, useForm, useModalForm };