70 lines
2.1 KiB
JavaScript
70 lines
2.1 KiB
JavaScript
import React, { forwardRef, useId } from 'react'
|
|
|
|
/**
|
|
* Nova Textarea
|
|
*
|
|
* @prop {string} label - optional label
|
|
* @prop {string} error - validation error
|
|
* @prop {string} hint - helper text
|
|
* @prop {boolean} required - red asterisk on label
|
|
* @prop {number} rows - visible rows (default 4)
|
|
* @prop {boolean} resize - allow manual resize (default false)
|
|
*/
|
|
const Textarea = forwardRef(function Textarea(
|
|
{ label, error, hint, required, rows = 4, resize = false, id, className = '', ...rest },
|
|
ref,
|
|
) {
|
|
const generatedId = useId()
|
|
const labelSlug = typeof label === 'string'
|
|
? label.toLowerCase().replace(/\s+/g, '-')
|
|
: null
|
|
const inputId = id ?? labelSlug ?? `textarea-${generatedId.replace(/[:]/g, '')}`
|
|
|
|
const inputClass = [
|
|
'block w-full rounded-xl border bg-white/[0.06] text-white text-sm',
|
|
'px-3.5 py-2.5 placeholder:text-slate-500',
|
|
'transition-all duration-150',
|
|
'focus:outline-none focus:ring-2 focus:ring-offset-0',
|
|
resize ? 'resize-y' : 'resize-none',
|
|
error
|
|
? 'border-red-500/60 focus:border-red-500/70 focus:ring-red-500/40'
|
|
: 'border-white/12 hover:border-white/20 focus:border-accent/50 focus:ring-accent/40',
|
|
'disabled:opacity-50 disabled:cursor-not-allowed',
|
|
className,
|
|
].join(' ')
|
|
|
|
return (
|
|
<div className="flex flex-col gap-1.5">
|
|
{label && (
|
|
<label htmlFor={inputId} className="text-sm font-medium text-white/85">
|
|
{label}
|
|
{required && <span className="text-red-400 ml-1">*</span>}
|
|
</label>
|
|
)}
|
|
|
|
<textarea
|
|
id={inputId}
|
|
ref={ref}
|
|
rows={rows}
|
|
className={inputClass}
|
|
aria-invalid={!!error}
|
|
aria-describedby={error ? `${inputId}-error` : hint ? `${inputId}-hint` : undefined}
|
|
{...rest}
|
|
/>
|
|
|
|
{error && (
|
|
<p id={`${inputId}-error`} role="alert" className="text-xs text-red-400">
|
|
{error}
|
|
</p>
|
|
)}
|
|
{!error && hint && (
|
|
<p id={`${inputId}-hint`} className="text-xs text-slate-500">
|
|
{hint}
|
|
</p>
|
|
)}
|
|
</div>
|
|
)
|
|
})
|
|
|
|
export default Textarea
|