192 lines
4.7 KiB
TypeScript
192 lines
4.7 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 gap-3">
|
|
<Checkbox
|
|
checked={value}
|
|
onCheckedChange={(v) => onChange(!!v)}
|
|
className="
|
|
h-4 w-4
|
|
rounded-full
|
|
border border-black
|
|
text-white
|
|
data-[state=checked]:bg-black
|
|
focus-visible:ring-2 focus-visible:ring-black
|
|
"
|
|
/>
|
|
|
|
<Label
|
|
htmlFor={fieldId}
|
|
className="text-sm font-medium leading-none cursor-pointer"
|
|
>
|
|
{label}
|
|
</Label>
|
|
</div>
|
|
);
|
|
|
|
// 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>
|
|
);
|
|
};
|