UNPKG

turiy-form

Version:

This package is built on shadcn, zod

574 lines (557 loc) 21 kB
// 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 };