| // src/components/genui/FormComponent.tsx | |
| import { useState } from "react"; | |
| import { Send, Loader2 } from "lucide-react"; | |
| interface FormField { | |
| id: string; | |
| label: string; | |
| type: "text" | "email" | "textarea" | "select"; | |
| options?: string[]; | |
| required?: boolean; | |
| } | |
| interface FormComponentProps { | |
| title?: string; | |
| fields?: FormField[]; | |
| onSubmit?: (data: Record<string, any>) => void; | |
| submitLabel?: string; | |
| } | |
| const DEFAULT_FIELDS: FormField[] = [ | |
| { id: "name", label: "Name", type: "text", required: true }, | |
| { id: "email", label: "Email", type: "email", required: true }, | |
| { id: "message", label: "Message", type: "textarea" }, | |
| ]; | |
| export function FormComponent({ | |
| title = "Contact Form", | |
| fields = DEFAULT_FIELDS, | |
| onSubmit, | |
| submitLabel = "Submit", | |
| }: FormComponentProps) { | |
| const [values, setValues] = useState<Record<string, any>>({}); | |
| const [submitting, setSubmitting] = useState(false); | |
| const handleChange = (id: string, value: any) => { | |
| setValues((prev) => ({ ...prev, [id]: value })); | |
| }; | |
| const handleSubmit = async (e: React.FormEvent) => { | |
| e.preventDefault(); | |
| setSubmitting(true); | |
| try { | |
| await onSubmit?.(values); | |
| } finally { | |
| setSubmitting(false); | |
| } | |
| }; | |
| return ( | |
| <div className="bg-white dark:bg-gray-800 rounded-xl p-6 shadow"> | |
| <h3 className="text-lg font-semibold mb-4">{title}</h3> | |
| <form onSubmit={handleSubmit} className="space-y-4"> | |
| {fields.map((field) => ( | |
| <div key={field.id}> | |
| <label htmlFor={field.id} className="block text-sm font-medium mb-1"> | |
| {field.label} {field.required && <span className="text-red-500">*</span>} | |
| </label> | |
| {field.type === "textarea" ? ( | |
| <textarea | |
| id={field.id} | |
| value={values[field.id] || ""} | |
| onChange={(e) => handleChange(field.id, e.target.value)} | |
| className="input w-full" | |
| rows={3} | |
| title={field.label} | |
| placeholder={`Enter ${field.label.toLowerCase()}`} | |
| /> | |
| ) : field.type === "select" ? ( | |
| <select | |
| id={field.id} | |
| value={values[field.id] || ""} | |
| onChange={(e) => handleChange(field.id, e.target.value)} | |
| className="input w-full" | |
| title={field.label} | |
| > | |
| <option value="">Select...</option> | |
| {field.options?.map((opt) => ( | |
| <option key={opt} value={opt}> | |
| {opt} | |
| </option> | |
| ))} | |
| </select> | |
| ) : ( | |
| <input | |
| type={field.type} | |
| id={field.id} | |
| value={values[field.id] || ""} | |
| onChange={(e) => handleChange(field.id, e.target.value)} | |
| className="input w-full" | |
| title={field.label} | |
| placeholder={`Enter ${field.label.toLowerCase()}`} | |
| /> | |
| )} | |
| </div> | |
| ))} | |
| <button | |
| type="submit" | |
| disabled={submitting} | |
| className="btn btn-primary w-full gap-1" | |
| title="Submit form" | |
| > | |
| {submitting ? ( | |
| <Loader2 className="w-4 h-4 animate-spin" /> | |
| ) : ( | |
| <Send className="w-4 h-4" /> | |
| )} | |
| {submitting ? "Submitting..." : submitLabel} | |
| </button> | |
| </form> | |
| </div> | |
| ); | |
| } |
Xet Storage Details
- Size:
- 3.5 kB
- Xet hash:
- b6faf21f0048be867f27a1f0d1936532903d097b52e14fb26a6efbc16d450f03
·
Xet efficiently stores files, intelligently splitting them into unique chunks and accelerating uploads and downloads. More info.