Search

Ctrl + K

Textarea

This document outlines the steps to create Textarea component styled withTailwind CSS and using some npm dependency libraries.

Prerequisites

  • Node.js and npm installed on your machine.
  • Tailwind CSS installed in your project.
  • CVA(class-variance-authority) is a utility for managing CSS class names based on various conditions.
  • clsx is a tiny utility for constructing className strings conditionally.
Step 1: Create variant styles
1// textarea.helpers.ts
2import { cva } from "class-variance-authority";
3
4export const labelVariants = cva(
5  "mb-1 transition-all duration-150 transform translate-y-0 data-[invalid=true]:text-red-500",
6  {
7    variants: {
8      inputSize: {
9        small:
10          "text-xs font-semibold data-[float=true]:pl-1.5 data-[floating=true]:translate-y-[1.6rem]",
11        medium:
12          "text-sm font-semibold data-[float=true]:pl-2 data-[floating=true]:translate-y-[2.1rem]",
13        large:
14          "text-lg font-medium data-[float=true]:pl-4 data-[floating=true]:translate-y-[2.55rem]",
15      },
16    },
17    defaultVariants: {
18      inputSize: "medium",
19    },
20  },
21);
22
23export const textareaVariants = cva(
24  [
25    "rounded-md border focus:border-neutral-600 focus:outline-none text-sm disabled:bg-neutral-300 disabled:text-neutral-400 dark:disabled:bg-neutral-600 dark:disabled:text-neutral-500",
26    "border-neutral-300 dark:border-neutral-500 data-[invalid=true]:!border-red-500",
27  ],
28  {
29    variants: {
30      inputSize: {
31        small: "px-2 py-1 text-xs",
32        medium: "px-3 py-2 text-sm",
33        large: "px-4 py-2 text-lg",
34      },
35    },
36    defaultVariants: {
37      inputSize: "medium",
38    },
39  },
40);
41
42export const errorMessageVariants = cva("text-red-500 font-medium mt-1", {
43  variants: {
44    inputSize: {
45      small: "text-xs",
46      medium: "text-xs",
47      large: "text-base",
48    },
49  },
50  defaultVariants: {
51    inputSize: "medium",
52  },
53});
54
Step 2: Create Textarea component
1// textarea.component.tsx
2import clsx from "clsx";
3import { useEffect, useRef, useState } from "react";
4import {
5  errorMessageVariants,
6  labelVariants,
7  textareaVariants,
8} from "./textarea.helpers";
9
10const Textarea: React.FC<TextareaProps> = ({
11  inputSize,
12  label,
13  isFloatLabel,
14  error,
15  placeholder,
16  className,
17  wrapperClassName,
18  onFocus,
19  onBlur,
20  onChange,
21  ...props
22}) => {
23  const inputRef = useRef<HTMLTextAreaElement | null>(null);
24  const [hasValue, setHasValue] = useState<boolean>(false);
25  const [focusing, setFocusing] = useState<boolean>(false);
26
27  useEffect(() => {
28    const defaultValue = inputRef.current!.defaultValue;
29    setHasValue(defaultValue.trim() !== "");
30  }, []);
31
32  const handleFocus = (e: React.FocusEvent<HTMLTextAreaElement, Element>) => {
33    setFocusing(true);
34    onFocus && onFocus(e);
35  };
36
37  const handleBlur = (e: React.FocusEvent<HTMLTextAreaElement, Element>) => {
38    setFocusing(false);
39    onBlur && onBlur(e);
40  };
41
42  const handleChange = (e: React.ChangeEvent<HTMLTextAreaElement>) => {
43    setHasValue(e.target.value.trim() !== "");
44    onChange && onChange(e);
45  };
46
47  const handleClickLabel = () => {
48    if (!isFloatLabel || focusing) return;
49    inputRef.current!.focus();
50  };
51
52  const isFloatingLabel = isFloatLabel && !focusing && !hasValue;
53
54  return (
55    <div className={clsx("flex flex-col", wrapperClassName)}>
56      {label && (
57        <label
58          data-float={Boolean(isFloatLabel).toString()}
59          data-floating={Boolean(isFloatingLabel).toString()}
60          data-invalid={Boolean(error).toString()}
61          className={labelVariants({ inputSize })}
62          onClick={handleClickLabel}
63        >
64          {label}
65        </label>
66      )}
67      <textarea
68        {...props}
69        ref={inputRef}
70        placeholder={!isFloatLabel ? placeholder : undefined}
71        data-invalid={Boolean(error).toString()}
72        className={clsx(textareaVariants({ inputSize }), className)}
73        onFocus={handleFocus}
74        onBlur={handleBlur}
75        onChange={handleChange}
76      />
77      {error && <p className={errorMessageVariants({ inputSize })}>{error}</p>}
78    </div>
79  );
80};
81
82export default Textarea;
83