{ "$schema": "https://ui.shadcn.com/schema/registry-item.json", "name": "form", "type": "registry:component", "title": "Form", "description": "Comprehensive form component with validation, submission handling, and success messaging", "dependencies": [ "react", "@registry/react" ], "registryDependencies": [ "https://components.thgaltitude.com/r/react/button", "https://components.thgaltitude.com/r/react/alert" ], "files": [ { "path": "registry/react/component/form.tsx", "content": "import React, {\n useState,\n useRef,\n Children,\n isValidElement,\n cloneElement,\n} from \"react\";\nimport type { FormEvent, ReactElement } from \"react\";\nimport Button from \"@registry/react/ui/button\";\nimport Alert from \"@registry/react/ui/alert\";\n\ninterface FormProps {\n title: string;\n description?: string;\n\n onSubmit: (data: Record) => void;\n submitText: string;\n submittedText?: string;\n\n children?: React.ReactNode;\n\n className?: string;\n splitLayout?: boolean;\n}\n\nexport const Form: React.FC = ({\n title,\n description,\n onSubmit,\n submitText = \"Submit\",\n submittedText,\n children,\n className = \"\",\n splitLayout = false,\n}) => {\n const [isSubmitted, setIsSubmitted] = useState(false);\n const [isSubmitting, setIsSubmitting] = useState(false);\n const [invalidFields, setInvalidFields] = useState>(new Set());\n const formRef = useRef(null);\n\n const handleSubmit = async (e: FormEvent) => {\n e.preventDefault();\n\n setInvalidFields(new Set());\n\n const form = e.currentTarget;\n const formElements = Array.from(form.elements) as HTMLElement[];\n const newInvalidFields = new Set();\n\n formElements.forEach((el) => {\n const input = el as\n | HTMLInputElement\n | HTMLSelectElement\n | HTMLTextAreaElement;\n\n if (input.name && !input.checkValidity()) {\n newInvalidFields.add(input.name);\n }\n\n if (input.type === \"radio\" && input.required) {\n const radioInput = input as HTMLInputElement;\n const radioGroup = form.elements.namedItem(\n radioInput.name\n ) as RadioNodeList;\n\n if (radioGroup && radioGroup[0] === radioInput) {\n const isAnyChecked = Array.from(radioGroup).some(\n (radio) => (radio as HTMLInputElement).checked\n );\n\n if (!isAnyChecked) {\n newInvalidFields.add(radioInput.name);\n }\n }\n }\n });\n\n if (newInvalidFields.size > 0) {\n setInvalidFields(newInvalidFields);\n\n return;\n }\n\n setIsSubmitting(true);\n\n try {\n const formData = new FormData(e.currentTarget);\n const formDataObj: Record = {};\n\n formData.forEach((value, key) => {\n if (formDataObj[key]) {\n if (!Array.isArray(formDataObj[key])) {\n formDataObj[key] = [formDataObj[key]];\n }\n formDataObj[key].push(value);\n } else {\n formDataObj[key] = value;\n }\n });\n\n let isEmptyFunction = false;\n\n if (typeof onSubmit === \"function\") {\n const fnString = onSubmit.toString();\n if (fnString) {\n isEmptyFunction = fnString.replace(/\\s/g, \"\") === \"()=>{}\";\n }\n }\n\n if (isEmptyFunction || typeof onSubmit !== \"function\") {\n console.log(\"Form submitted with empty data:\", formDataObj);\n setIsSubmitted(true);\n } else {\n await onSubmit(formDataObj);\n setIsSubmitted(true);\n }\n } catch (error) {\n console.error(\"Form submission error:\", error);\n } finally {\n setIsSubmitting(false);\n }\n };\n\n const enhanceChildrenWithValidation = (\n children: React.ReactNode\n ): React.ReactNode => {\n return Children.map(children, (child) => {\n if (!isValidElement(child)) {\n return child;\n }\n\n const childElement = child as ReactElement;\n const props = childElement.props as any;\n\n if (props.name && invalidFields.has(props.name)) {\n const componentType = childElement.type as any;\n const isDropdown =\n componentType &&\n (componentType.name === \"Dropdown\" ||\n componentType.displayName === \"Dropdown\" ||\n props.id?.includes(\"dropdown\"));\n\n const isRatingInput =\n componentType &&\n (componentType.name === \"RatingInput\" ||\n componentType.displayName === \"RatingInput\" ||\n props.id?.includes(\"rating\"));\n\n if (isDropdown) {\n return cloneElement(childElement, {\n ...props,\n error: true,\n } as any);\n } else if (isRatingInput) {\n return cloneElement(childElement, {\n ...props,\n \"aria-invalid\": true,\n } as any);\n } else {\n return cloneElement(childElement, {\n ...props,\n \"aria-invalid\": true,\n } as any);\n }\n }\n\n if (props.children) {\n const newChildren = enhanceChildrenWithValidation(props.children);\n return cloneElement(childElement, props, newChildren);\n }\n\n return childElement;\n });\n };\n\n return (\n
\n \n
\n

{title}

\n {description &&

{description}

}\n
\n\n
\n \n
\n {enhanceChildrenWithValidation(children)}\n
\n\n
\n \n {isSubmitting ? \"Submitting...\" : submitText}\n \n
\n\n {isSubmitted && submittedText && (\n
\n {splitLayout ? (\n \n ) : (\n
\n {submittedText}\n
\n )}\n
\n )}\n \n
\n
\n \n );\n};\n\nexport default Form;\n", "type": "registry:component", "target": "react/component/form.tsx" } ] }