109 lines
4.1 KiB
TypeScript
109 lines
4.1 KiB
TypeScript
"use client";
|
|
|
|
import { InputHTMLAttributes, forwardRef, useState, ReactNode } from "react";
|
|
import { EyeIcon, EyeSlashIcon, ExclamationCircleIcon } from "@heroicons/react/24/outline";
|
|
|
|
interface InputProps extends InputHTMLAttributes<HTMLInputElement> {
|
|
label?: string;
|
|
error?: string;
|
|
helperText?: string;
|
|
leftIcon?: ReactNode;
|
|
rightIcon?: ReactNode;
|
|
onRightIconClick?: () => void;
|
|
}
|
|
|
|
const Input = forwardRef<HTMLInputElement, InputProps>(
|
|
(
|
|
{
|
|
label,
|
|
error,
|
|
helperText,
|
|
leftIcon,
|
|
rightIcon,
|
|
onRightIconClick,
|
|
className = "",
|
|
type,
|
|
...props
|
|
},
|
|
ref
|
|
) => {
|
|
const [showPassword, setShowPassword] = useState(false);
|
|
const isPassword = type === "password";
|
|
|
|
const inputType = isPassword ? (showPassword ? "text" : "password") : type;
|
|
|
|
return (
|
|
<div className="w-full">
|
|
{label && (
|
|
<label className="block text-[13px] font-semibold text-zinc-900 dark:text-white mb-2">
|
|
{label}
|
|
{props.required && <span className="text-brand-500 ml-1">*</span>}
|
|
</label>
|
|
)}
|
|
<div className="relative">
|
|
{leftIcon && (
|
|
<div className="absolute left-3.5 top-1/2 -translate-y-1/2 text-[#7D7D7D] dark:text-gray-400 w-5 h-5">
|
|
{leftIcon}
|
|
</div>
|
|
)}
|
|
<input
|
|
ref={ref}
|
|
type={inputType}
|
|
className={`
|
|
w-full px-4 py-2.5 text-sm font-normal
|
|
border rounded-lg bg-white dark:bg-gray-800 dark:text-white
|
|
placeholder:text-gray-400 dark:placeholder:text-gray-500
|
|
transition-all duration-200
|
|
${leftIcon ? "pl-11" : ""}
|
|
${isPassword || rightIcon ? "pr-11" : ""}
|
|
${error
|
|
? "border-red-500 focus:border-red-500 focus:ring-4 focus:ring-red-500/10"
|
|
: "border-zinc-200 dark:border-zinc-700 focus:border-zinc-400 dark:focus:border-zinc-500 focus:ring-0"
|
|
}
|
|
outline-none
|
|
disabled:bg-gray-50 disabled:text-gray-500 disabled:cursor-not-allowed
|
|
${className}
|
|
`}
|
|
{...props}
|
|
/>
|
|
{isPassword && (
|
|
<button
|
|
type="button"
|
|
onClick={() => setShowPassword(!showPassword)}
|
|
className="absolute right-3.5 top-1/2 -translate-y-1/2 text-zinc-500 hover:text-zinc-900 transition-colors cursor-pointer"
|
|
>
|
|
{showPassword ? (
|
|
<EyeSlashIcon className="w-5 h-5" />
|
|
) : (
|
|
<EyeIcon className="w-5 h-5" />
|
|
)}
|
|
</button>
|
|
)}
|
|
{!isPassword && rightIcon && (
|
|
<button
|
|
type="button"
|
|
onClick={onRightIconClick}
|
|
className="absolute right-3.5 top-1/2 -translate-y-1/2 text-zinc-500 hover:text-zinc-900 transition-colors cursor-pointer"
|
|
>
|
|
<div className="w-5 h-5">{rightIcon}</div>
|
|
</button>
|
|
)}
|
|
</div>
|
|
{error && (
|
|
<p className="mt-1 text-[13px] text-red-500 flex items-center gap-1">
|
|
<ExclamationCircleIcon className="w-4 h-4" />
|
|
{error}
|
|
</p>
|
|
)}
|
|
{helperText && !error && (
|
|
<p className="mt-1 text-[13px] text-zinc-500">{helperText}</p>
|
|
)}
|
|
</div>
|
|
);
|
|
}
|
|
);
|
|
|
|
Input.displayName = "Input";
|
|
|
|
export default Input;
|