Docs
Input

Input

Displays a form input field or a component that looks like an input field.

Input text field

The input component can be used to create a text input field. The following example shows a basic text input field.

Input with email validation

The input component can be used to create an email input field. The following example shows an email input field.

Input with password validation

The input component can be used to create a password input field. The following example shows a password input field.

Input with file upload

The input component can be used to create a file upload input field. The following example shows a file upload input field.

Add a file

Input Counter

The input component can be used to create a number input field. The following example shows a number input field with a counter.

Installation

Create following files to use Input component

Label.tsx

1type LabelProps = {
2  text: string;
3  htmlFor?: string;
4  className?: string;
5};
6
7export const Label = ({ text, htmlFor, className }: LabelProps) => {
8  return (
9    <label
10      htmlFor={htmlFor}
11      className={"block font-geist text-sm font-normal text-rose-100 " + (className || "")}
12    >
13      {text}
14    </label>
15  );
16};
17

HelperText.tsx

1type HelperTextProps = {
2  text: string;
3  variant?: "default" | "error";
4  className?: string;
5};
6
7export const HelperText = ({
8  text,
9  variant = "default",
10  className,
11}: HelperTextProps) => {
12  const variantClass = variant === "error" ? "text-red-500" : "text-rose-200";
13  const combinedClass =
14    "mt-2 text-sm " + variantClass + " " + (className || "");
15  return <p className={combinedClass}>{text}</p>;
16};
17

Input.tsx

1import React, { useState } from "react";
2import { cn } from "@/utils/cn";
3import { Label } from "./Label";
4import { HelperText } from "./HelperText";
5import {
6  X,
7  AlertCircle,
8  Eye,
9  EyeOff,
10  ChevronUp,
11  ChevronDown,
12  Paperclip,
13} from "lucide-react";
14import { ClassNameValue } from "tailwind-merge";
15
16// !! button for file upload
17
18type ButtonProps = {
19  children?: React.ReactNode;
20  className?: ClassNameValue;
21} & React.ButtonHTMLAttributes<HTMLButtonElement>;
22
23const Button = ({ children, className, ...props }: ButtonProps) => {
24  return (
25    <button
26      className={cn(
27        "flex items-center gap-3 rounded-lg bg-rose-500 px-6 py-2 font-geist font-semibold text-rose-100 shadow-md duration-200 ease-in-out hover:shadow-lg hover:brightness-95",
28        className
29      )}
30      {...props}
31    >
32      {children}
33    </button>
34  );
35};
36
37// !! button for file upload
38
39type InputVariants =
40  | "default"
41  | "focused"
42  | "filled"
43  | "disabled"
44  | "error"
45  | "";
46
47type InputProps = {
48  label?: string;
49  helperText?: string;
50  placeholder?: string;
51  className?: string;
52  variant?: InputVariants;
53  type?: "text" | "email" | "password" | "file" | "number";
54  showIcon?: boolean;
55  min?: number;
56  max?: number;
57  step?: number;
58} & React.InputHTMLAttributes<HTMLInputElement>;
59
60export const Input = ({
61  label,
62  helperText,
63  placeholder,
64  className,
65  variant = "",
66  type = "text",
67  showIcon = false,
68  min = 0,
69  max = 100,
70  step = 1,
71  ...props
72}: InputProps) => {
73  const [inputValue, setInputValue] = useState(
74    type === "number" ? props.value || 0 : ""
75  );
76  const [showPassword, setShowPassword] = useState(false);
77  const [passwordStrength, setPasswordStrength] = useState<number | null>(null);
78  const [fileName, setFileName] = useState<string | null>(null);
79
80  const togglePasswordVisibility = () => setShowPassword((prev) => !prev);
81
82  const evaluatePassword = (password: string) => {
83    if (!password) return setPasswordStrength(null);
84    if (password.length < 6) return setPasswordStrength(1);
85    if (/^(?=.*[A-Z])(?=.*d)(?=.*[@$!%*?&]).{8,}$/.test(password))
86      return setPasswordStrength(4);
87    if (/^(?=.*[A-Z])(?=.*d).{6,}$/.test(password))
88      return setPasswordStrength(3);
89    return setPasswordStrength(2);
90  };
91
92  const getStrengthLabel = (strength: number | null) => {
93    switch (strength) {
94      case 1:
95        return "Weak";
96      case 2:
97        return "Moderate";
98      case 3:
99        return "Strong";
100      case 4:
101        return "Very Strong";
102      default:
103        return "";
104    }
105  };
106
107  const handleClear = () => setInputValue(type === "number" ? 0 : "");
108  const handleAlert = () => alert("Error: Please check your input.");
109
110  const handleIncrement = () => {
111    setInputValue((prev) => {
112      const newValue = Number(prev) + step;
113      return newValue <= max ? newValue : prev;
114    });
115  };
116
117  const handleDecrement = () => {
118    setInputValue((prev) => {
119      const newValue = Number(prev) - step;
120      return newValue >= min ? newValue : prev;
121    });
122  };
123
124  const handleFileChange = (event: React.ChangeEvent<HTMLInputElement>) => {
125    const file = event.target.files?.[0];
126    if (file) {
127      setFileName(file.name);
128    } else {
129      setFileName(null);
130    }
131  };
132
133  const getIcon = () => {
134    if (!showIcon) return null;
135    if (type === "password") {
136      return showPassword ? (
137        <EyeOff
138          onClick={togglePasswordVisibility}
139          className="absolute inset-y-4 right-0 flex items-center pr-3 text-white"
140          width={45}
141        />
142      ) : (
143        <Eye
144          width={45}
145          onClick={togglePasswordVisibility}
146          className="absolute inset-y-4 right-0 flex items-center pr-3 text-white"
147        />
148      );
149    }
150    return getIconForVariant(variant, handleClear, handleAlert);
151  };
152
153  const inputVariantClass = switchInputVariant(variant);
154  const iconClass = showIcon ? "pr-10" : "";
155
156  return (
157    <div className="relative space-y-1 font-geist font-normal text-zinc-100">
158      {label && (
159        <Label
160          text={label}
161          htmlFor={type === "file" ? "file-input" : undefined}
162        />
163      )}
164      <div className="relative flex flex-col items-start">
165        {type === "file" && (
166          <>
167            <input
168              type="file"
169              id="file-input"
170              className="hidden"
171              onChange={handleFileChange}
172              {...props}
173            />
174            <div className="flex min-h-14 min-w-80 items-center rounded-lg bg-transparent px-3 py-2 font-geist text-base font-normal text-zinc-100 outline outline-1 outline-gray-100/10 selection:bg-gray-400/40 placeholder:text-zinc-400 focus:outline focus:outline-gray-100/80 space-x-2">
175              <label htmlFor="file-input" className="cursor-pointer">
176                <Paperclip className="text-white" />
177              </label>
178              <span className="flex-1 text-zinc-400">
179                {fileName || "Add a file"}
180              </span>
181              <Button
182                className="p-2 bg-white text-black rounded-lg"
183                onClick={() => document.getElementById("file-input")?.click()}
184              >
185                Upload
186              </Button>
187            </div>
188          </>
189        )}
190        {type !== "file" && (
191          <>
192            {showIcon && (
193              <span className="absolute inset-y-0 z-10 right-0 flex cursor-pointer items-center justify-center pr-3 mb-9">
194                {getIcon()}
195              </span>
196            )}
197            <div
198              style={{
199                minHeight: "6rem" /* Adjust height as needed */,
200                position: "relative",
201              }}
202            >
203              <input
204                type={type === "password" && showPassword ? "text" : type}
205                placeholder={placeholder}
206                value={inputValue}
207                onChange={(e) => {
208                  setInputValue(e.target.value);
209                  if (type === "password") evaluatePassword(e.target.value);
210                }}
211                disabled={variant === "disabled"}
212                pattern={type === "email" ? ".*@.*" : undefined}
213                className={cn(
214                  "min-h-14 min-w-80 appearance-none rounded-lg bg-transparent px-3 py-2 font-geist text-base font-normal text-zinc-100 outline outline-1 outline-gray-100/10 selection:bg-gray-400/40 placeholder:text-zinc-400 focus:outline focus:outline-gray-100/80",
215                  className,
216                  iconClass,
217                  inputVariantClass
218                )}
219                {...props}
220              />
221              {type === "number" && (
222                <div className="absolute inset-y-0 right-0 flex flex-col items-center justify-center pr-3 mb-9">
223                  <ChevronUp
224                    className="cursor-pointer"
225                    onClick={handleIncrement}
226                  />
227                  <ChevronDown
228                    className="cursor-pointer"
229                    onClick={handleDecrement}
230                  />
231                </div>
232              )}
233              {type === "password" && (
234                <>
235                  <div className="mt-2 flex w-full">
236                    <div
237                      className={
238                        "h-1 flex-1 " +
239                        (passwordStrength !== null && passwordStrength >= 1
240                          ? "bg-red-500"
241                          : "bg-transparent")
242                      }
243                    ></div>
244                    <div
245                      className={
246                        "h-1 flex-1 " +
247                        (passwordStrength !== null && passwordStrength >= 2
248                          ? "bg-yellow-500"
249                          : "bg-transparent")
250                      }
251                    ></div>
252                    <div
253                      className={
254                        "h-1 flex-1 " +
255                        (passwordStrength !== null && passwordStrength >= 3
256                          ? "bg-amber-600"
257                          : "bg-transparent")
258                      }
259                    ></div>
260                    <div
261                      className={
262                        "h-1 flex-1 " +
263                        (passwordStrength !== null && passwordStrength >= 4
264                          ? "bg-green-500"
265                          : "bg-transparent")
266                      }
267                    ></div>
268                  </div>
269                  <div className="mt-1 text-sm text-red-100">
270                    {getStrengthLabel(passwordStrength)}
271                  </div>
272                </>
273              )}
274            </div>
275          </>
276        )}
277      </div>
278      {helperText && <HelperText text={helperText} />}
279    </div>
280  );
281};
282
283const getIconForVariant = (
284  variant: InputVariants,
285  handleClear: () => void,
286  handleAlert: () => void
287) => {
288  switch (variant) {
289    case "focused":
290    case "filled":
291      return <X className="text-white" onClick={handleClear} />;
292    case "disabled":
293      return <AlertCircle className="text-white" onClick={handleAlert} />;
294    case "error":
295      return <AlertCircle className="text-white" onClick={handleAlert} />;
296    default:
297      return null;
298  }
299};
300
301const switchInputVariant = (variant: InputVariants) => {
302  switch (variant) {
303    case "focused":
304      return "focus:outline focus:outline-rose-500";
305    case "filled":
306      return "bg-gray-700/50 focus:bg-transparent";
307    case "disabled":
308      return "bg-gray-500/40 cursor-not-allowed";
309    case "error":
310      return "border-rose-500 focus:outline focus:outline-rose-500";
311    default:
312      return "";
313  }
314};
315

Props

The input component accepts the following props:

PropTypeDefaultDescription
labelstringundefinedThe label for the input field.
helperTextstringundefinedHelper text to display below the input field.
placeholderstringundefinedPlaceholder text for the input field.
classNamestringundefinedAdditional CSS classes to apply to the input field.
variantdefault, focused, filled,""The visual variant of the input
disabled, error
typetext, email, password,textThe type of input field.
number, file
showIconbooleanfalseWhether to show an icon in the input field.
minnumber0The minimum value for a number input field.
maxnumber100The maximum value for a number input field.
stepnumber1The step value for a number input field.

FlaminUI

© 2024 FlaminUI. All rights reserved.

Made with ❤️ by FlaminUI Team