turiy-form
Version:
This package is built on shadcn, zod
574 lines (557 loc) • 21 kB
JavaScript
// src/form/Formx.tsx
import React9 from "react";
// src/ui/form.tsx
import * as React2 from "react";
import { Slot } from "@radix-ui/react-slot";
import {
Controller,
FormProvider,
useFormContext
} from "react-hook-form";
// src/lib/utils.ts
import { clsx } from "clsx";
import { twMerge } from "tailwind-merge";
function cn(...inputs) {
return twMerge(clsx(inputs));
}
// src/ui/label.tsx
import * as React from "react";
import * as LabelPrimitive from "@radix-ui/react-label";
import { cva } from "class-variance-authority";
var labelVariants = cva(
"text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70"
);
var Label = React.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ React.createElement(
LabelPrimitive.Root,
{
ref,
className: cn(labelVariants(), className),
...props
}
));
Label.displayName = LabelPrimitive.Root.displayName;
// src/ui/form.tsx
var Form = FormProvider;
var FormFieldContext = React2.createContext(
{}
);
var FormField = ({
...props
}) => {
return /* @__PURE__ */ React2.createElement(FormFieldContext.Provider, { value: { name: props.name } }, /* @__PURE__ */ React2.createElement(Controller, { ...props }));
};
var useFormField = () => {
const fieldContext = React2.useContext(FormFieldContext);
const itemContext = React2.useContext(FormItemContext);
const { getFieldState, formState } = useFormContext();
const fieldState = getFieldState(fieldContext.name, formState);
if (!fieldContext) {
throw new Error("useFormField should be used within <FormField>");
}
const { id } = itemContext;
return {
id,
name: fieldContext.name,
formItemId: `${id}-form-item`,
formDescriptionId: `${id}-form-item-description`,
formMessageId: `${id}-form-item-message`,
...fieldState
};
};
var FormItemContext = React2.createContext(
{}
);
var FormItem = React2.forwardRef(({ className, ...props }, ref) => {
const id = React2.useId();
return /* @__PURE__ */ React2.createElement(FormItemContext.Provider, { value: { id } }, /* @__PURE__ */ React2.createElement("div", { ref, className: cn("space-y-2", className), ...props }));
});
FormItem.displayName = "FormItem";
var FormLabel = React2.forwardRef(({ className, ...props }, ref) => {
const { error, formItemId } = useFormField();
return /* @__PURE__ */ React2.createElement(
Label,
{
ref,
className: cn(error && "text-destructive", className),
htmlFor: formItemId,
...props
}
);
});
FormLabel.displayName = "FormLabel";
var FormControl = React2.forwardRef(({ ...props }, ref) => {
const { error, formItemId, formDescriptionId, formMessageId } = useFormField();
return /* @__PURE__ */ React2.createElement(
Slot,
{
ref,
id: formItemId,
"aria-describedby": !error ? `${formDescriptionId}` : `${formDescriptionId} ${formMessageId}`,
"aria-invalid": !!error,
...props
}
);
});
FormControl.displayName = "FormControl";
var FormDescription = React2.forwardRef(({ className, ...props }, ref) => {
const { formDescriptionId } = useFormField();
return /* @__PURE__ */ React2.createElement(
"p",
{
ref,
id: formDescriptionId,
className: cn("text-sm text-muted-foreground", className),
...props
}
);
});
FormDescription.displayName = "FormDescription";
var FormMessage = React2.forwardRef(({ className, children, ...props }, ref) => {
const { error, formMessageId } = useFormField();
const body = error ? String(error?.message) : children;
if (!body) {
return null;
}
return /* @__PURE__ */ React2.createElement(
"p",
{
ref,
id: formMessageId,
className: cn("text-sm font-medium text-destructive", className),
...props
},
body
);
});
FormMessage.displayName = "FormMessage";
// src/form/FomxField.tsx
import React3 from "react";
var FormxField = React3.forwardRef((props, ref) => /* @__PURE__ */ React3.createElement(React3.Fragment, null));
FormxField.displayName = "FormxField";
// src/form/Formx.tsx
import { z as z2 } from "zod";
import { useForm } from "react-hook-form";
import { zodResolver } from "@hookform/resolvers/zod";
// src/form/FormxSelect.tsx
import React6 from "react";
// src/ui/select.tsx
import * as React4 from "react";
import * as SelectPrimitive from "@radix-ui/react-select";
import { Check, ChevronDown, ChevronUp } from "lucide-react";
var Select = SelectPrimitive.Root;
var SelectValue = SelectPrimitive.Value;
var SelectTrigger = React4.forwardRef(({ className, children, ...props }, ref) => /* @__PURE__ */ React4.createElement(
SelectPrimitive.Trigger,
{
ref,
className: cn(
"flex h-10 w-full items-center justify-between rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background placeholder:text-muted-foreground focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 [&>span]:line-clamp-1",
className
),
...props
},
children,
/* @__PURE__ */ React4.createElement(SelectPrimitive.Icon, { asChild: true }, /* @__PURE__ */ React4.createElement(ChevronDown, { className: "h-4 w-4 opacity-50" }))
));
SelectTrigger.displayName = SelectPrimitive.Trigger.displayName;
var SelectScrollUpButton = React4.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ React4.createElement(
SelectPrimitive.ScrollUpButton,
{
ref,
className: cn(
"flex cursor-default items-center justify-center py-1",
className
),
...props
},
/* @__PURE__ */ React4.createElement(ChevronUp, { className: "h-4 w-4" })
));
SelectScrollUpButton.displayName = SelectPrimitive.ScrollUpButton.displayName;
var SelectScrollDownButton = React4.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ React4.createElement(
SelectPrimitive.ScrollDownButton,
{
ref,
className: cn(
"flex cursor-default items-center justify-center py-1",
className
),
...props
},
/* @__PURE__ */ React4.createElement(ChevronDown, { className: "h-4 w-4" })
));
SelectScrollDownButton.displayName = SelectPrimitive.ScrollDownButton.displayName;
var SelectContent = React4.forwardRef(({ className, children, position = "popper", ...props }, ref) => /* @__PURE__ */ React4.createElement(SelectPrimitive.Portal, null, /* @__PURE__ */ React4.createElement(
SelectPrimitive.Content,
{
ref,
className: cn(
"relative z-50 max-h-96 min-w-[8rem] overflow-hidden rounded-md border bg-popover text-popover-foreground shadow-md data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2",
position === "popper" && "data-[side=bottom]:translate-y-1 data-[side=left]:-translate-x-1 data-[side=right]:translate-x-1 data-[side=top]:-translate-y-1",
className
),
position,
...props
},
/* @__PURE__ */ React4.createElement(SelectScrollUpButton, null),
/* @__PURE__ */ React4.createElement(
SelectPrimitive.Viewport,
{
className: cn(
"p-1",
position === "popper" && "h-[var(--radix-select-trigger-height)] w-full min-w-[var(--radix-select-trigger-width)]"
)
},
children
),
/* @__PURE__ */ React4.createElement(SelectScrollDownButton, null)
)));
SelectContent.displayName = SelectPrimitive.Content.displayName;
var SelectLabel = React4.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ React4.createElement(
SelectPrimitive.Label,
{
ref,
className: cn("py-1.5 pl-8 pr-2 text-sm font-semibold", className),
...props
}
));
SelectLabel.displayName = SelectPrimitive.Label.displayName;
var SelectItem = React4.forwardRef(({ className, children, ...props }, ref) => /* @__PURE__ */ React4.createElement(
SelectPrimitive.Item,
{
ref,
className: cn(
"relative flex w-full cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
className
),
...props
},
/* @__PURE__ */ React4.createElement("span", { className: "absolute left-2 flex h-3.5 w-3.5 items-center justify-center" }, /* @__PURE__ */ React4.createElement(SelectPrimitive.ItemIndicator, null, /* @__PURE__ */ React4.createElement(Check, { className: "h-4 w-4" }))),
/* @__PURE__ */ React4.createElement(SelectPrimitive.ItemText, null, children)
));
SelectItem.displayName = SelectPrimitive.Item.displayName;
var SelectSeparator = React4.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ React4.createElement(
SelectPrimitive.Separator,
{
ref,
className: cn("-mx-1 my-1 h-px bg-muted", className),
...props
}
));
SelectSeparator.displayName = SelectPrimitive.Separator.displayName;
// src/ui/button.tsx
import * as React5 from "react";
import { Slot as Slot2 } from "@radix-ui/react-slot";
import { cva as cva2 } from "class-variance-authority";
var buttonVariants = cva2(
"inline-flex items-center justify-center whitespace-nowrap rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50",
{
variants: {
variant: {
default: "bg-primary text-primary-foreground hover:bg-primary/90",
destructive: "bg-destructive text-destructive-foreground hover:bg-destructive/90",
outline: "border border-input bg-background hover:bg-accent hover:text-accent-foreground",
secondary: "bg-secondary text-secondary-foreground hover:bg-secondary/80",
ghost: "hover:bg-accent hover:text-accent-foreground",
link: "text-primary underline-offset-4 hover:underline"
},
size: {
default: "h-10 px-4 py-2",
sm: "h-9 rounded-md px-3",
lg: "h-11 rounded-md px-8",
icon: "h-10 w-10"
}
},
defaultVariants: {
variant: "default",
size: "default"
}
}
);
var Button = React5.forwardRef(
({ className, variant, size, asChild = false, ...props }, ref) => {
const Comp = asChild ? Slot2 : "button";
return /* @__PURE__ */ React5.createElement(
Comp,
{
className: cn(buttonVariants({ variant, size, className })),
ref,
...props
}
);
}
);
Button.displayName = "Button";
// src/form/FormxSelect.tsx
import { PlusIcon } from "lucide-react";
var FormxSelect = ({
control,
name,
label,
placeholder,
options,
className,
onAdd,
description,
disabled,
required,
autoFocus,
onFocusChanged
}) => {
const ref = React6.useRef(null);
React6.useEffect(() => {
if (!!ref.current && !!autoFocus) ref.current.focus();
}, [autoFocus, ref]);
return /* @__PURE__ */ React6.createElement("div", { className: cn(className) }, /* @__PURE__ */ React6.createElement("div", { className: "flex w-full gap-2" }, /* @__PURE__ */ React6.createElement("div", { className: "flex-1" }, /* @__PURE__ */ React6.createElement(
FormField,
{
control,
name,
render: ({ field }) => /* @__PURE__ */ React6.createElement(FormItem, null, /* @__PURE__ */ React6.createElement(FormLabel, null, " ", !disabled && !!required ? /* @__PURE__ */ React6.createElement("span", { className: "text-red-700" }, "*") : /* @__PURE__ */ React6.createElement(React6.Fragment, null), label), /* @__PURE__ */ React6.createElement(
Select,
{
onValueChange: field.onChange,
defaultValue: field.value,
disabled,
required: !disabled && !!required,
onOpenChange: (focused) => onFocusChanged(focused)
},
/* @__PURE__ */ React6.createElement(FormControl, null, /* @__PURE__ */ React6.createElement(SelectTrigger, { ref }, /* @__PURE__ */ React6.createElement(SelectValue, { placeholder }))),
/* @__PURE__ */ React6.createElement(SelectContent, null, options.map((op) => /* @__PURE__ */ React6.createElement(SelectItem, { key: op.value, value: op.value }, op.label)))
), !!description ? /* @__PURE__ */ React6.createElement(FormDescription, null, description) : /* @__PURE__ */ React6.createElement(React6.Fragment, null), /* @__PURE__ */ React6.createElement(FormMessage, null))
}
)), onAdd ? /* @__PURE__ */ React6.createElement("div", { className: "h-full flex flex-col items-center justify-center" }, /* @__PURE__ */ React6.createElement("div", { className: "flex-1" }), /* @__PURE__ */ React6.createElement(
Button,
{
type: "button",
variant: "secondary",
onClick: onAdd,
className: cn(!!onAdd ? "visible" : "hidden"),
disabled
},
/* @__PURE__ */ React6.createElement(PlusIcon, null)
)) : /* @__PURE__ */ React6.createElement(React6.Fragment, null)));
};
var FormxSelect_default = FormxSelect;
// src/form/FormxInput.tsx
import React8 from "react";
// src/ui/input.tsx
import * as React7 from "react";
var Input = React7.forwardRef(
({ className, type, ...props }, ref) => {
return /* @__PURE__ */ React7.createElement(
"input",
{
type,
className: cn(
"flex h-10 w-full rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background file:border-0 file:bg-transparent file:text-sm file:font-medium placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50",
className
),
ref,
...props
}
);
}
);
Input.displayName = "Input";
// src/form/FormxInput.tsx
var FormxInput = ({
control,
name,
label,
placeholder,
type,
className,
description,
disabled,
required,
autoFocus,
onFocusChanged
}) => {
const ref = React8.useRef(null);
React8.useEffect(() => {
if (!!ref.current && !!autoFocus) ref.current.focus();
}, [autoFocus, ref]);
return /* @__PURE__ */ React8.createElement("div", { className: cn(className) }, /* @__PURE__ */ React8.createElement(
FormField,
{
control,
name,
render: ({ field }) => /* @__PURE__ */ React8.createElement(FormItem, null, /* @__PURE__ */ React8.createElement(FormLabel, null, !disabled && !!required ? /* @__PURE__ */ React8.createElement("span", { className: "text-red-700" }, "*") : /* @__PURE__ */ React8.createElement(React8.Fragment, null), label), /* @__PURE__ */ React8.createElement(FormControl, null, /* @__PURE__ */ React8.createElement(
Input,
{
...field,
ref,
placeholder,
type,
disabled,
required: !disabled && !!required,
onFocus: () => onFocusChanged(true),
onBlur: () => onFocusChanged(true)
}
)), !!description ? /* @__PURE__ */ React8.createElement(FormDescription, null, description) : /* @__PURE__ */ React8.createElement(React8.Fragment, null), /* @__PURE__ */ React8.createElement(FormMessage, null))
}
));
};
var FormxInput_default = FormxInput;
// src/form/validation.ts
import { z } from "zod";
var zValid = (label, required, match) => z.string(required ? { required_error: `${label} is required!` } : void 0).refine(
(x) => required || x.length > 0 ? !!match ? !!match.type ? (
//for string
match.type === "string" && (!match.eq || x.length === match.eq) && (!match.min || x.length >= (match.min ?? x.length)) && (!match.max || x.length <= (match.max ?? x.length)) || //for number
!isNaN(x) && (!match.min || +x >= (match.min ?? +x)) && (!match.max || +x <= (match.max ?? +x))
) : x.match(match) : true : true,
!!match ? !!match.type ? `${label} ${match.type === "string" ? "length" : ""} should be ${!!match.eq ? `=${match.eq}` : ""} ${!!match.min ? `>=${match.min} ${!!match.max ? `<=${match.max}` : ""}` : ""} !` : `Invalid ${label}!` : ""
);
var regex = {
email: /^([A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,})$/i,
mobile: /^\+?[1-9]\d{1,14}$/,
url: /^(https?:\/\/)?([\w-]+(\.[\w-]+)+)(\/[\w-]*)*\/?$/,
pan: /^[A-Z]{5}[0-9]{4}[A-Z]{1}$/,
gstin: /^[0-9]{2}[A-Z]{5}[0-9]{4}[A-Z]{1}[1-9A-Z]{1}Z[0-9A-Z]{1}$/,
aadhar: /^[2-9]{1}[0-9]{3}[0-9]{4}[0-9]{4}$/,
cin: /^[LU][0-9]{5}[A-Z]{2}[0-9]{4}[A-Z]{3}[0-9]{6}$/,
epic: /^[A-Z]{3}[0-9]{7}$/,
pin: /^[1-9][0-9]{2}\s?[0-9]{3}$/,
password: /^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[^a-zA-Z\d]).{6,}$/
};
// src/form/Formx.tsx
var Formx = ({
children,
className,
onSubmit,
onChange,
onInit,
refine,
submitButton,
resetButton
}) => {
const validators = [];
const defaultValues = [];
const childs = [];
React9.Children.forEach(children, (child) => {
if (React9.isValidElement(child)) {
childs.push(child);
if (child.type === FormxField) {
validators.push([
child.props.name,
zValid(child.props.label, !!child.props.required, child.props.match)
]);
if (child.props.cached) {
const dv = localStorage.getItem(`cached_${child.props.name}`);
defaultValues.push([
child.props.name,
dv || child.props.defaultValue
]);
} else {
defaultValues.push([child.props.name, child.props.defaultValue]);
}
}
}
});
const validateSchema = z2.object(Object.fromEntries(validators));
const refinedSchema = !!refine ? validateSchema.refine((data) => refine.on(data), {
message: refine.message,
path: refine.path
}) : validateSchema;
const defaultSchemaValue = Object.fromEntries(defaultValues);
const form = useForm({
resolver: zodResolver(refinedSchema),
defaultValues: defaultSchemaValue
});
React9.useEffect(() => {
if (!!onInit) onInit(form);
}, [form]);
const _onSubmit = (data) => {
childs.forEach((c) => {
if (c.props.cached) {
localStorage.setItem(`cached_${c.props.name}`, data[c.props.name]);
}
});
onSubmit(data);
};
const [v, setV] = React9.useState({
selectedFieldName: ""
});
const values = form.watch();
React9.useEffect(() => {
if (!!onChange) {
const newV = { ...values, selectedFieldName: v.selectedFieldName };
if (JSON.stringify(v) !== JSON.stringify(newV)) {
setV(newV);
onChange(newV);
}
}
}, [values]);
return /* @__PURE__ */ React9.createElement(Form, { ...form }, /* @__PURE__ */ React9.createElement("form", { onSubmit: form.handleSubmit(_onSubmit), className: "space-y-8" }, /* @__PURE__ */ React9.createElement("div", { className: cn(className) }, childs.map(
(child) => !!child.props.selectProps ? /* @__PURE__ */ React9.createElement(
FormxSelect_default,
{
key: child.props.name,
control: form.control,
name: child.props.name,
label: child.props.label,
placeholder: child.props.placeholder,
options: child.props.selectProps.options,
className: child.props.className,
onAdd: child.props.selectProps.onAdd,
description: child.props.description,
disabled: child.props.disabled,
required: child.props.required,
autoFocus: child.props.autoFocus,
onFocusChanged: (focused) => {
if (focused) {
setV({ ...v, selectedFieldName: child.props.name });
}
}
}
) : /* @__PURE__ */ React9.createElement(
FormxInput_default,
{
key: child.props.name,
control: form.control,
name: child.props.name,
label: child.props.label,
placeholder: child.props.placeholder,
className: child.props.className,
type: child.props.type,
description: child.props.description,
disabled: child.props.disabled,
required: child.props.required,
autoFocus: child.props.autoFocus,
onFocusChanged: (focused) => {
if (focused) {
setV({ ...v, selectedFieldName: child.props.name });
}
}
}
)
), submitButton, resetButton)));
};
Formx.displayName = "Formx";
// src/form/util.ts
var reset = (form, defaultValues) => Object.entries(defaultValues).forEach(
([key, value]) => form.setValue(key, `${value}`)
);
function equal(obj1, obj2) {
const areObjects = isObject(obj1) && isObject(obj2);
if (!areObjects) return obj1 === obj2;
const keys1 = Object.keys(obj1);
const keys2 = Object.keys(obj2);
if (keys1.length !== keys2.length) return false;
for (const key of keys1) if (!equal(obj1[key], obj2[key])) return false;
return true;
}
function isObject(value) {
return value !== null && typeof value === "object";
}
export {
Button,
Formx,
FormxField,
buttonVariants,
equal,
isObject,
regex,
reset
};