All files / src/components/ui Textarea.tsx

85.71% Statements 12/14
88.88% Branches 16/18
66.66% Functions 2/3
100% Lines 12/12

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              54x   89x 89x 89x 89x   89x 31x 31x 31x 31x   89x                                                                   54x      
import React from "react";
 
interface TextareaProps extends React.TextareaHTMLAttributes<HTMLTextAreaElement> {
  label?: string;
  error?: string;
}
 
const Textarea = React.forwardRef<HTMLTextAreaElement, TextareaProps>(
  ({ label, error, className = "", id, rows = 3, ...props }, ref) => {
    const generatedId = React.useId();
    const textareaId = id ?? generatedId;
    const inner = React.useRef<HTMLTextAreaElement>(null);
    React.useImperativeHandle(ref, () => inner.current!);
    // Grow to the content so a filled story never clips mid-line; `rows` is the floor.
    React.useLayoutEffect(() => {
      const el = inner.current;
      Iif (!el) return;
      el.style.height = "auto";
      el.style.height = `${el.scrollHeight + el.offsetHeight - el.clientHeight}px`;
    }, [props.value]);
    return (
      <div className="w-full">
        {label && (
          <label htmlFor={textareaId} className="block text-sm font-medium text-ink mb-2">
            {label}
            {props.required && <span className="text-error-deep">*</span>}
          </label>
        )}
        <textarea
          id={textareaId}
          aria-invalid={error ? true : undefined}
          aria-describedby={error ? `${textareaId}-error` : undefined}
          className={`
            w-full bg-glass border border-hairline rounded-control px-4 py-3 text-ink
            transition-shadow duration-200 ease-gentle
            placeholder:text-ink-soft focus:outline-hidden focus-visible:ring-2 focus-visible:ring-primary
            focus-visible:border-transparent disabled:opacity-50 disabled:cursor-not-allowed
            resize-none
            ${error ? "border-error focus-visible:ring-error" : ""}
            ${className}
          `}
          ref={inner}
          rows={rows}
          {...props}
        />
        {error && (
          <p id={`${textareaId}-error`} className="text-error-deep text-sm mt-2">
            {error}
          </p>
        )}
      </div>
    );
  }
);
Textarea.displayName = "Textarea";
 
export { Textarea };