218 lines
4.6 KiB
TypeScript
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; |