kontenhumas-fe/components/form/common/FormField.tsx

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>
);
};