Aubia Logo
Espanol
HomeAbout Us

Input

Input Playground

Note: To use password visibility toggle, install react-icons with npm install react-icons.

Dependency

npm.shbash
Recommended path: terminal
1npm install react-icons

Use

InputExample.tsxtsx
Recommended path: app/components/input/InputExample.tsx
1import { Input } from "@/src/components/shared/Input";
2
3export default function Example() {
4  return (
5    <div className="max-w-md">
6      <Input
7        label="Email address"
8        defaultValue="you@company.com"
9        type="text"
10        variant="outline"
11        size="md"
12        rounded="xl"
13        color="#62009e"
14        textColor="#ffffff"
15        selectedBgColor="#ffffff"
16        selectedTextColor="#000000"
17        duration="300ms"
18        shadowOnFocus={true}
19      />
20    </div>
21  );
22}

Component

Input.tsxtsx
Recommended path: src/components/shared/Input.tsx
1"use client";
2import { useId, useState, type InputHTMLAttributes } from "react";
3import { FaEye, FaEyeSlash } from "react-icons/fa6";
4
5type InputProps = Omit<
6  InputHTMLAttributes<HTMLInputElement>,
7  "size" | "className" | "style"
8> & {
9  label: string;
10  color?: string;
11  textColor?: string;
12  selectedTextColor?: string;
13  selectedBgColor?: string;
14  rounded?: "none" | "lg" | "xl" | "full";
15  duration?: string;
16  variant?: "fill" | "outline";
17  size?: "sm" | "md" | "lg";
18  shadowOnFocus?: boolean;
19
20  labelClassName?: string;
21  inputClassName?: string;
22  fatherClassName?: string;
23};
24
25const hexToRgba = (hex: string, alpha: number) => {
26  const clean = hex.trim().replace(/^#/, "");
27  const normalized =
28    clean.length === 3
29      ? clean
30          .split("")
31          .map((char) => char + char)
32          .join("")
33      : clean;
34
35  if (!/^[\da-fA-F]{6}$/.test(normalized)) {
36    return `rgba(0, 0, 0, ${Math.min(Math.max(alpha, 0), 1)})`;
37  }
38
39  const r = parseInt(normalized.slice(0, 2), 16);
40  const g = parseInt(normalized.slice(2, 4), 16);
41  const b = parseInt(normalized.slice(4, 6), 16);
42  const safeAlpha = Math.min(Math.max(alpha, 0), 1);
43
44  return `rgba(${r}, ${g}, ${b}, ${safeAlpha})`;
45};
46
47export const Input: React.FC<InputProps> = ({
48  label = "Name",
49  type = "text",
50  id: providedId,
51  name: providedName,
52  color = "#ffffff",
53  textColor = "#ffffff",
54  selectedTextColor = "#000000",
55  selectedBgColor = "#ffffff",
56  rounded = "xl",
57  duration = "300ms",
58  variant = "outline",
59  size = "md",
60  shadowOnFocus = true,
61  labelClassName = "",
62  inputClassName = "",
63  fatherClassName = "",
64  onFocus,
65  onBlur,
66  ...inputProps
67}) => {
68  const [showPassword, setShowPassword] = useState(false);
69  const generatedId = useId();
70  const inputId = providedId ?? generatedId;
71  const inputName = providedName ?? label;
72  const isPasswordType = type === "password";
73  const resolvedType = isPasswordType && showPassword ? "text" : type;
74
75  const isName =
76    label.toLowerCase() === "name" || label.toLowerCase() === "nombre";
77
78  const isLastName =
79    label.toLowerCase() === "last name" || label.toLowerCase() === "apellido";
80
81  const roundedMap = {
82    none: "rounded-none",
83    lg: "rounded-lg",
84    xl: "rounded-xl",
85    full: "rounded-full",
86  };
87
88  const inputSizeMap = {
89    sm: "text-sm",
90    md: "text-base",
91    lg: "text-lg",
92  };
93
94  const floatingLabelSizeMap = {
95    sm: "peer-focus:text-xs peer-not-placeholder-shown:text-xs",
96    md: "peer-focus:text-sm peer-not-placeholder-shown:text-sm",
97    lg: "peer-focus:text-base peer-not-placeholder-shown:text-base",
98  };
99
100  const labelSizeMap = {
101    sm: "text-sm",
102    md: "text-base",
103    lg: "text-lg",
104  };
105
106  const floatingOffsetMap = {
107    sm: "-0.5rem",
108    md: "-0.625rem",
109    lg: "-0.75rem",
110  };
111
112  const wrapperPaddingMap = {
113    sm: "has-[input:focus]:pt-4 has-[input:not(:placeholder-shown)]:pt-4",
114    md: "has-[input:focus]:pt-5 has-[input:not(:placeholder-shown)]:pt-5",
115    lg: "has-[input:focus]:pt-6 has-[input:not(:placeholder-shown)]:pt-6",
116  };
117
118  return (
119    <div
120      data-input-id={inputId}
121      className={`
122        flex flex-col gap-2 relative
123        transition-all
124        w-full flex-1
125        ${wrapperPaddingMap[size]}
126        ${fatherClassName}
127      `}
128      style={{ transitionDuration: duration }}
129    >
130      <div className="relative w-full">
131        <input
132          {...inputProps}
133          type={resolvedType}
134          name={inputName}
135          id={inputId}
136          placeholder=" "
137          className={`
138            peer border p-2 px-4 w-full
139            focus:outline-none
140            transition-all
141            placeholder-transparent
142            ${inputSizeMap[size]}
143            ${inputClassName}
144            ${roundedMap[rounded]}
145            ${isPasswordType ? "pr-11" : ""}
146            ${isName || isLastName ? "capitalize" : ""}
147          `}
148          style={{
149            color: textColor,
150            transitionDuration: duration,
151          }}
152          onFocus={(e) => {
153            onFocus?.(e);
154            if (shadowOnFocus) {
155              e.currentTarget.style.boxShadow =
156                variant === "fill"
157                  ? `0px 0px 3px ${color}`
158                  : `0px 0px 8px ${color}`;
159            }
160          }}
161          onBlur={(e) => {
162            onBlur?.(e);
163            if (shadowOnFocus) {
164              e.currentTarget.style.boxShadow = "none";
165            }
166          }}
167        />
168
169        {isPasswordType ? (
170          <div className="absolute inset-y-0 right-3 flex items-center">
171            <button
172              type="button"
173              aria-label={showPassword ? "Hide password" : "Show password"}
174              onClick={() => setShowPassword((prev) => !prev)}
175              className="text-foreground/70 hover:text-foreground transition-colors"
176            >
177              {showPassword ? <FaEyeSlash size={14} /> : <FaEye size={14} />}
178            </button>
179          </div>
180        ) : null}
181      </div>
182
183      <label
184        htmlFor={inputId}
185        className={`
186          absolute inset-y-0 left-4
187          flex items-center
188          transition-all
189          pointer-events-none
190          ${labelSizeMap[size]}
191          ${floatingLabelSizeMap[size]}
192          ${labelClassName}
193        `}
194        style={{
195          transitionDuration: duration,
196        }}
197      >
198        {label}
199      </label>
200
201      <style>{`
202        [data-input-id="${inputId}"] label {
203          left: 1rem;
204          transition-duration: ${duration};
205          transition-property: color, opacity, top, bottom, left, transform, font-size;
206          color: ${textColor};
207          opacity: 0.3;
208        }
209
210        [data-input-id="${inputId}"] input {
211          border-color: ${variant === "outline" ? hexToRgba(color, 0.3) : "transparent"};
212          background: ${
213            variant === "fill" ? hexToRgba(color, 0.08) : "transparent"
214          };
215        }
216
217        [data-input-id="${inputId}"] input:focus {
218          border-color: ${variant === "outline" ? color : "transparent"};
219          background: ${
220            variant === "fill" ? hexToRgba(color, 0.16) : "transparent"
221          };
222        }
223
224        [data-input-id="${inputId}"]:has(input:focus) label {
225          top: ${floatingOffsetMap[size]};
226          bottom: auto;
227          left: 0;
228          transform: translateY(0);
229        }
230
231        [data-input-id="${inputId}"]:has(input:not(:placeholder-shown)) label {
232          top: ${floatingOffsetMap[size]};
233          bottom: auto;
234          left: 0;
235          transform: translateY(0);
236        }
237
238        [data-input-id="${inputId}"]:has(input:focus) label,
239        [data-input-id="${inputId}"]:has(input:not(:placeholder-shown)) label {
240          opacity: 1;
241          color: ${color};
242        }
243
244        [data-input-id="${inputId}"] input::selection {
245          background: ${selectedBgColor};
246          color: ${selectedTextColor};
247        }
248      `}</style>
249    </div>
250  );
251};
252