Press n or j to go to the next uncovered block, b, p or k for the previous block.
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 | 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x | 'use client';
/**
* FormTextarea Component
*
* A textarea component designed to work with the form validation framework.
* Wraps the base Input component with touched state support and
* optimized error display.
*
* Supports both legacy props (error, disabled) and standard props
* (isError, isDisabled, errorMessage) for backward compatibility.
*/
import { forwardRef } from 'react';
import { Input } from '../Input';
import { cn } from '@/lib/core';
import type { CommonSize, StateProps } from '../types';
export interface FormTextareaProps
extends StateProps,
Omit<React.TextareaHTMLAttributes<HTMLTextAreaElement>, 'size'> {
/** Textarea label */
label: string;
/**
* Validation error message (standard prop)
* Takes precedence over legacy `error` prop
*/
errorMessage?: string;
/**
* @deprecated Use `errorMessage` instead
* Validation error message (legacy prop)
*/
error?: string | null;
/** Whether the field has been touched/blurred */
touched?: boolean;
/** Helper text shown below the textarea */
helperText?: string;
/** Textarea size variant */
size?: CommonSize;
/** Number of visible rows */
rows?: number;
/** Whether to allow resizing */
resize?: 'none' | 'vertical' | 'horizontal' | 'both';
/** Show character count */
showCharCount?: boolean;
/** Additional wrapper class name */
wrapperClassName?: string;
}
/**
* FormTextarea Component
*
* A textarea component that integrates with the form validation framework.
* Only shows errors when the field has been touched.
*
* @example
* ```tsx
* // With standard props
* <FormTextarea
* label="Message"
* name="message"
* rows={4}
* value={message}
* onChange={handleChange}
* isError={!!errors.message}
* errorMessage={errors.message}
* maxLength={500}
* showCharCount
* required
* />
*
* // With form validation framework
* const { errors, touched, getFieldProps } = useForm({ ... });
*
* <FormTextarea
* label="Message"
* rows={4}
* {...getFieldProps('message')}
* error={errors.message}
* touched={touched.message}
* maxLength={500}
* showCharCount
* required
* />
* ```
*/
export const FormTextarea = forwardRef<HTMLTextAreaElement, FormTextareaProps>(
(
{
label,
// Standard props
errorMessage,
isLoading,
isDisabled,
isError,
// Legacy props
error,
touched,
// Common props
helperText,
size = 'md',
rows = 4,
resize = 'vertical',
showCharCount,
maxLength,
wrapperClassName,
className,
id,
required,
disabled,
value,
...props
},
ref
) => {
// Resolve error message (prefer standard prop, convert null to undefined)
const resolvedErrorMessage = errorMessage ?? error ?? undefined;
// Only show error if the field has been touched (when using touched prop)
// or if isError is explicitly set
const hasError = isError ?? (touched ? !!resolvedErrorMessage : false);
const displayedError = hasError ? resolvedErrorMessage : undefined;
// Resolve disabled state (combine standard and legacy)
const resolvedDisabled = isDisabled ?? disabled;
// Calculate character count
const charCount = typeof value === 'string' ? value.length : 0;
const charCountText = maxLength ? `${charCount}/${maxLength}` : String(charCount);
// Resize styles
const resizeStyles = {
none: 'resize-none',
vertical: 'resize-y',
horizontal: 'resize-x',
both: 'resize'
};
return (
<div className={cn('form-textarea-wrapper', wrapperClassName)}>
<Input
ref={ref as React.Ref<HTMLTextAreaElement>}
as="textarea"
id={id}
label={label}
error={displayedError}
helperText={helperText}
size={size}
required={required}
disabled={resolvedDisabled || isLoading}
fullWidth
rows={rows}
maxLength={maxLength}
value={value}
className={cn(resizeStyles[resize], className)}
{...props}
/>
{showCharCount && (
<div className="flex justify-end mt-1">
<span
className={cn(
'text-xs',
maxLength && charCount >= maxLength
? 'text-red-500'
: maxLength && charCount >= maxLength * 0.9
? 'text-yellow-500'
: 'text-gray-400 dark:text-gray-500'
)}
>
{charCountText}
</span>
</div>
)}
</div>
);
}
);
FormTextarea.displayName = 'FormTextarea';
export default FormTextarea;
|