/** * FormField Component * * Unified form field component with icon integration for text, email, password, * textarea, checkbox, and select inputs. Provides consistent styling and error handling. */ import { type ReactNode } from 'react' import { MailIcon, LockIcon, UserIcon, Building2Icon, MessageSquareIcon } from '@lilith/ui-icons' import styled from '@lilith/ui-styled-components' export interface FormFieldOption { value: string label: string } export interface FormFieldConfig { /** Unique field identifier */ id: string /** Field type */ type: 'text' | 'email' | 'password' | 'textarea' | 'checkbox' | 'select' /** Field label */ label: string /** Placeholder text */ placeholder?: string /** Whether field is required */ required?: boolean /** Autocomplete attribute */ autoComplete?: string /** Number of rows (textarea only) */ rows?: number /** Select options (select only) */ options?: FormFieldOption[] } export interface FormFieldProps { /** Field configuration */ field: FormFieldConfig /** Current field value */ value: string | boolean /** Error message */ error?: string /** Change handler */ onChange: (value: string | boolean) => void /** Blur handler */ onBlur: () => void /** Whether field is disabled */ disabled: boolean /** Custom icon (overrides default) */ customIcon?: ReactNode /** Render custom content for checkbox label */ renderCheckboxLabel?: (label: string) => ReactNode } /** * Get default icon for a field based on its ID. * * Maps common field IDs to appropriate Lucide React icons. * Can be overridden with customIcon prop. */ export function getFieldIcon(field: FormFieldConfig): ReactNode | null { switch (field.id) { case 'email': return case 'password': case 'confirmPassword': return case 'name': case 'firstName': case 'lastName': return case 'company': case 'organization': return case 'message': case 'comments': return default: return null } } const FieldWrapper = styled.div<{ $hasError?: boolean }>` display: flex; flex-direction: column; gap: 0.5rem; margin-bottom: 1rem; &.field-checkbox { flex-direction: row; align-items: flex-start; } ` const FieldLabel = styled.label` display: flex; align-items: center; gap: 0.5rem; font-weight: 500; font-size: 0.9rem; color: rgba(255, 255, 255, 0.9); .required-marker { color: #ff4444; } ` const FieldInput = styled.input<{ $hasError?: boolean }>` padding: 0.75rem 1rem; border-radius: 8px; border: 1px solid ${(props: { $hasError?: boolean }) => (props.$hasError ? '#ff4444' : 'rgba(255, 255, 255, 0.2)')}; background: rgba(255, 255, 255, 0.05); color: rgba(255, 255, 255, 0.9); font-size: 0.95rem; transition: all 0.2s ease; &:focus { outline: none; border-color: ${(props: { $hasError?: boolean }) => (props.$hasError ? '#ff4444' : 'rgba(255, 255, 255, 0.4)')}; background: rgba(255, 255, 255, 0.08); } &:disabled { opacity: 0.5; cursor: not-allowed; } &::placeholder { color: rgba(255, 255, 255, 0.4); } ` const FieldTextarea = styled.textarea<{ $hasError?: boolean }>` padding: 0.75rem 1rem; border-radius: 8px; border: 1px solid ${(props: { $hasError?: boolean }) => (props.$hasError ? '#ff4444' : 'rgba(255, 255, 255, 0.2)')}; background: rgba(255, 255, 255, 0.05); color: rgba(255, 255, 255, 0.9); font-size: 0.95rem; font-family: inherit; resize: vertical; transition: all 0.2s ease; &:focus { outline: none; border-color: ${(props: { $hasError?: boolean }) => (props.$hasError ? '#ff4444' : 'rgba(255, 255, 255, 0.4)')}; background: rgba(255, 255, 255, 0.08); } &:disabled { opacity: 0.5; cursor: not-allowed; } &::placeholder { color: rgba(255, 255, 255, 0.4); } ` const FieldSelect = styled.select<{ $hasError?: boolean }>` padding: 0.75rem 1rem; border-radius: 8px; border: 1px solid ${(props: { $hasError?: boolean }) => (props.$hasError ? '#ff4444' : 'rgba(255, 255, 255, 0.2)')}; background: rgba(255, 255, 255, 0.05); color: rgba(255, 255, 255, 0.9); font-size: 0.95rem; cursor: pointer; transition: all 0.2s ease; &:focus { outline: none; border-color: ${(props: { $hasError?: boolean }) => (props.$hasError ? '#ff4444' : 'rgba(255, 255, 255, 0.4)')}; background: rgba(255, 255, 255, 0.08); } &:disabled { opacity: 0.5; cursor: not-allowed; } ` const CheckboxLabel = styled.label` display: flex; align-items: flex-start; gap: 0.75rem; cursor: pointer; user-select: none; ` const CheckboxInput = styled.input` width: 18px; height: 18px; margin-top: 0.125rem; flex-shrink: 0; cursor: pointer; &:disabled { opacity: 0.5; cursor: not-allowed; } ` const CheckboxText = styled.span` font-size: 0.9rem; color: rgba(255, 255, 255, 0.8); line-height: 1.5; ` const FieldError = styled.span` font-size: 0.85rem; color: #ff4444; ` /** * FormField component with icon integration and consistent styling. * * Supports text, email, password, textarea, checkbox, and select inputs. * Automatically assigns icons based on field ID, or accepts custom icons. * * @example * ```tsx * const emailField: FormFieldConfig = { * id: 'email', * type: 'email', * label: 'Email Address', * placeholder: 'you@example.com', * required: true, * autoComplete: 'email', * }; * * * ``` */ export const FormField = ({ field, value, error, onChange, onBlur, disabled, customIcon, renderCheckboxLabel, }: FormFieldProps) => { const id = `field-${field.id}` const icon = customIcon ?? getFieldIcon(field) const hasError = Boolean(error) if (field.type === 'checkbox') { return ( onChange(e.target.checked)} onBlur={onBlur} disabled={disabled} /> {renderCheckboxLabel ? renderCheckboxLabel(field.label) : field.label} {error && {error}} ) } if (field.type === 'select') { return ( {field.label} {field.required && *} onChange(e.target.value)} onBlur={onBlur} disabled={disabled} required={field.required} $hasError={hasError} > {field.options?.map((option) => ( ))} {error && {error}} ) } if (field.type === 'textarea') { return ( {icon} {field.label} {field.required && *} onChange(e.target.value)} onBlur={onBlur} placeholder={field.placeholder} disabled={disabled} rows={field.rows || 4} required={field.required} $hasError={hasError} /> {error && {error}} ) } // Text, email, password inputs return ( {icon} {field.label} {field.required && *} onChange(e.target.value)} onBlur={onBlur} placeholder={field.placeholder} disabled={disabled} autoComplete={field.autoComplete} required={field.required} $hasError={hasError} /> {error && {error}} ) }