Files
klz-cables.com/components/forms/FormField.tsx
2025-12-29 18:18:48 +01:00

218 lines
4.6 KiB
TypeScript

import React from 'react';
import { cn } from '@/lib/utils';
import FormLabel from './FormLabel';
import FormError from './FormError';
import FormInput from './FormInput';
import FormTextarea from './FormTextarea';
import FormSelect from './FormSelect';
import FormCheckbox from './FormCheckbox';
import FormRadio from './FormRadio';
/**
* FormField Component
* Wrapper for form fields with label, input, and error
* Supports different input types and provides consistent form experience
*/
export type FormFieldType =
| 'text'
| 'email'
| 'tel'
| 'textarea'
| 'select'
| 'checkbox'
| 'radio'
| 'number'
| 'password'
| 'date'
| 'time'
| 'url';
export interface FormFieldProps {
type?: FormFieldType;
label?: string;
name: string;
value?: any;
error?: string | string[];
helpText?: string;
required?: boolean;
disabled?: boolean;
placeholder?: string;
className?: string;
containerClassName?: string;
// For select, checkbox, radio
options?: any[];
// For select
multiple?: boolean;
showSearch?: boolean;
// For checkbox/radio
layout?: 'vertical' | 'horizontal';
// For textarea
rows?: number;
showCharCount?: boolean;
autoResize?: boolean;
maxLength?: number;
// For input
prefix?: React.ReactNode;
suffix?: React.ReactNode;
showClear?: boolean;
iconPosition?: 'prefix' | 'suffix';
// Callbacks
onChange?: (value: any) => void;
onBlur?: () => void;
onClear?: () => void;
// Additional props
[key: string]: any;
}
export const FormField: React.FC<FormFieldProps> = ({
type = 'text',
label,
name,
value,
error,
helpText,
required = false,
disabled = false,
placeholder,
className,
containerClassName,
options = [],
multiple = false,
showSearch = false,
layout = 'vertical',
rows = 4,
showCharCount = false,
autoResize = false,
maxLength,
prefix,
suffix,
showClear = false,
iconPosition = 'prefix',
onChange,
onBlur,
onClear,
...props
}) => {
const commonProps = {
name,
value,
onChange,
onBlur,
disabled,
required,
placeholder,
'aria-label': label,
};
const renderInput = () => {
switch (type) {
case 'textarea':
return (
<FormTextarea
{...commonProps}
error={error}
helpText={helpText}
rows={rows}
showCharCount={showCharCount}
autoResize={autoResize}
maxLength={maxLength}
className={className}
/>
);
case 'select':
return (
<FormSelect
{...commonProps}
error={error}
helpText={helpText}
options={options}
multiple={multiple}
showSearch={showSearch}
placeholder={placeholder}
className={className}
/>
);
case 'checkbox':
return (
<FormCheckbox
label={label}
error={error}
helpText={helpText}
required={required}
checked={Array.isArray(value) ? value.length > 0 : !!value}
options={options}
value={Array.isArray(value) ? value : []}
onChange={onChange}
disabled={disabled}
containerClassName={className}
/>
);
case 'radio':
return (
<FormRadio
label={label}
error={error}
helpText={helpText}
required={required}
options={options}
value={value}
onChange={onChange}
disabled={disabled}
layout={layout}
containerClassName={className}
/>
);
default:
return (
<FormInput
{...commonProps}
type={type}
error={error}
helpText={helpText}
label={label}
prefix={prefix}
suffix={suffix}
showClear={showClear}
iconPosition={iconPosition}
onClear={onClear}
className={className}
/>
);
}
};
// For checkbox and radio, the label is handled internally
const showExternalLabel = type !== 'checkbox' && type !== 'radio';
return (
<div className={cn('flex flex-col gap-1.5', containerClassName)}>
{showExternalLabel && label && (
<FormLabel htmlFor={name} required={required}>
{label}
</FormLabel>
)}
{renderInput()}
{!showExternalLabel && error && (
<FormError errors={error} />
)}
</div>
);
};
FormField.displayName = 'FormField';
export default FormField;