152 lines
4.0 KiB
TypeScript
152 lines
4.0 KiB
TypeScript
"use client";
|
|
import React from "react";
|
|
import { Input } from "@/components/ui/input";
|
|
import { Label } from "@/components/ui/label";
|
|
import { Textarea } from "@/components/ui/textarea";
|
|
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select";
|
|
import { Checkbox } from "@/components/ui/checkbox";
|
|
|
|
interface FormFieldProps {
|
|
label: string;
|
|
name: string;
|
|
type?: "text" | "email" | "password" | "number" | "tel" | "url" | "textarea" | "select" | "checkbox";
|
|
placeholder?: string;
|
|
value: any;
|
|
onChange: (value: any) => void;
|
|
error?: string;
|
|
required?: boolean;
|
|
disabled?: boolean;
|
|
options?: Array<{ value: string | number; label: string }>;
|
|
helpText?: string;
|
|
className?: string;
|
|
min?: number;
|
|
max?: number;
|
|
step?: number;
|
|
rows?: number;
|
|
}
|
|
|
|
export const FormField: React.FC<FormFieldProps> = ({
|
|
label,
|
|
name,
|
|
type = "text",
|
|
placeholder,
|
|
value,
|
|
onChange,
|
|
error,
|
|
required = false,
|
|
disabled = false,
|
|
options = [],
|
|
helpText,
|
|
className = "",
|
|
min,
|
|
max,
|
|
step,
|
|
rows = 3,
|
|
}) => {
|
|
const fieldId = `field-${name}`;
|
|
const hasError = !!error;
|
|
|
|
const renderField = () => {
|
|
switch (type) {
|
|
case "textarea":
|
|
return (
|
|
<Textarea
|
|
id={fieldId}
|
|
placeholder={placeholder}
|
|
value={value || ""}
|
|
onChange={(e) => onChange(e.target.value)}
|
|
disabled={disabled}
|
|
className={`${hasError ? "border-red-500" : ""} ${className}`}
|
|
rows={rows}
|
|
/>
|
|
);
|
|
|
|
case "select":
|
|
return (
|
|
<Select
|
|
value={value?.toString() || ""}
|
|
onValueChange={(val) => onChange(val)}
|
|
disabled={disabled}
|
|
>
|
|
<SelectTrigger className={`${hasError ? "border-red-500" : ""} ${className}`}>
|
|
<SelectValue placeholder={placeholder || `Select ${label}`} />
|
|
</SelectTrigger>
|
|
<SelectContent>
|
|
{options.map((option) => (
|
|
<SelectItem key={option.value} value={option.value.toString()}>
|
|
{option.label}
|
|
</SelectItem>
|
|
))}
|
|
</SelectContent>
|
|
</Select>
|
|
);
|
|
|
|
case "checkbox":
|
|
return (
|
|
<div className="flex items-center space-x-2">
|
|
<Checkbox
|
|
id={fieldId}
|
|
checked={!!value}
|
|
onCheckedChange={(checked) => onChange(checked)}
|
|
disabled={disabled}
|
|
className={hasError ? "border-red-500" : ""}
|
|
/>
|
|
<Label htmlFor={fieldId} className="text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70">
|
|
{label}
|
|
</Label>
|
|
</div>
|
|
);
|
|
|
|
case "number":
|
|
return (
|
|
<Input
|
|
id={fieldId}
|
|
type="number"
|
|
placeholder={placeholder}
|
|
value={value || ""}
|
|
onChange={(e) => onChange(e.target.value ? Number(e.target.value) : "")}
|
|
disabled={disabled}
|
|
className={`${hasError ? "border-red-500" : ""} ${className}`}
|
|
min={min}
|
|
max={max}
|
|
step={step}
|
|
/>
|
|
);
|
|
|
|
default:
|
|
return (
|
|
<Input
|
|
id={fieldId}
|
|
type={type}
|
|
placeholder={placeholder}
|
|
value={value || ""}
|
|
onChange={(e) => onChange(e.target.value)}
|
|
disabled={disabled}
|
|
className={`${hasError ? "border-red-500" : ""} ${className}`}
|
|
/>
|
|
);
|
|
}
|
|
};
|
|
|
|
return (
|
|
<div className="space-y-2">
|
|
{type !== "checkbox" && (
|
|
<Label htmlFor={fieldId} className="text-sm font-medium">
|
|
{label}
|
|
{required && <span className="text-red-500 ml-1">*</span>}
|
|
</Label>
|
|
)}
|
|
|
|
{renderField()}
|
|
|
|
{helpText && (
|
|
<p className="text-xs text-gray-500">{helpText}</p>
|
|
)}
|
|
|
|
{hasError && (
|
|
<p className="text-red-500 text-xs">{error}</p>
|
|
)}
|
|
</div>
|
|
);
|
|
};
|