Files
netbird/proxy/web/src/components/Input.tsx
Pascal Fischer f53155562f [management, reverse proxy] Add reverse proxy feature (#5291)
* implement reverse proxy


---------

Co-authored-by: Alisdair MacLeod <git@alisdairmacleod.co.uk>
Co-authored-by: mlsmaycon <mlsmaycon@gmail.com>
Co-authored-by: Eduard Gert <kontakt@eduardgert.de>
Co-authored-by: Viktor Liu <viktor@netbird.io>
Co-authored-by: Diego Noguês <diego.sure@gmail.com>
Co-authored-by: Diego Noguês <49420+diegocn@users.noreply.github.com>
Co-authored-by: Bethuel Mmbaga <bethuelmbaga12@gmail.com>
Co-authored-by: Zoltan Papp <zoltan.pmail@gmail.com>
Co-authored-by: Ashley Mensah <ashleyamo982@gmail.com>
2026-02-13 19:37:43 +01:00

138 lines
3.9 KiB
TypeScript

import { cn } from "@/utils/helpers";
import { Eye, EyeOff } from "lucide-react";
import * as React from "react";
import { useState } from "react";
export interface InputProps
extends React.InputHTMLAttributes<HTMLInputElement> {
customPrefix?: React.ReactNode;
customSuffix?: React.ReactNode;
maxWidthClass?: string;
icon?: React.ReactNode;
error?: string;
prefixClassName?: string;
showPasswordToggle?: boolean;
variant?: "default" | "darker";
}
const variantStyles = {
default: [
"bg-nb-gray-900 placeholder:text-neutral-400/70 border-nb-gray-700",
"ring-offset-neutral-950/50 focus-visible:ring-neutral-500/20",
],
darker: [
"bg-nb-gray-920 placeholder:text-neutral-400/70 border-nb-gray-800",
"ring-offset-neutral-950/50 focus-visible:ring-neutral-500/20",
],
error: [
"bg-nb-gray-900 placeholder:text-neutral-400/70 border-red-500 text-red-500",
"ring-offset-red-500/10 focus-visible:ring-red-500/10",
],
};
const prefixSuffixStyles = {
default: "bg-nb-gray-900 border-nb-gray-700 text-nb-gray-300",
error: "bg-nb-gray-900 border-red-500 text-nb-gray-300 text-red-500",
};
const Input = React.forwardRef<HTMLInputElement, InputProps>(
(
{
className,
type,
customSuffix,
customPrefix,
icon,
maxWidthClass = "",
error,
variant = "default",
prefixClassName,
showPasswordToggle = false,
...props
},
ref
) => {
const [showPassword, setShowPassword] = useState(false);
const isPasswordType = type === "password";
const inputType = isPasswordType && showPassword ? "text" : type;
const passwordToggle =
isPasswordType && showPasswordToggle ? (
<button
type="button"
onClick={() => setShowPassword(!showPassword)}
className="hover:text-white transition-all"
aria-label="Toggle password visibility"
>
{showPassword ? <EyeOff size={18} /> : <Eye size={18} />}
</button>
) : null;
const suffix = passwordToggle || customSuffix;
const activeVariant = error ? "error" : variant;
return (
<>
<div className={cn("flex relative h-[42px]", maxWidthClass)}>
{customPrefix && (
<div
className={cn(
prefixSuffixStyles[error ? "error" : "default"],
"flex h-[42px] w-auto rounded-l-md px-3 py-2 text-sm",
"border items-center whitespace-nowrap",
props.disabled && "opacity-40",
prefixClassName
)}
>
{customPrefix}
</div>
)}
<div
className={cn(
"absolute left-0 top-0 h-full flex items-center text-xs text-nb-gray-300 pl-3 leading-[0]",
props.disabled && "opacity-40"
)}
>
{icon}
</div>
<input
type={inputType}
ref={ref}
{...props}
className={cn(
variantStyles[activeVariant],
"flex h-[42px] w-full rounded-md px-3 py-2 text-sm",
"file:bg-transparent file:text-sm file:font-medium file:border-0",
"focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-offset-2",
"disabled:cursor-not-allowed disabled:opacity-40",
"border",
customPrefix && "!border-l-0 !rounded-l-none",
suffix && "!pr-16",
icon && "!pl-10",
className
)}
/>
<div
className={cn(
"absolute right-0 top-0 h-full flex items-center text-xs text-nb-gray-300 pr-4 leading-[0] select-none",
props.disabled && "opacity-30"
)}
>
{suffix}
</div>
</div>
{error && (
<p className="text-xs text-red-500 mt-2">{error}</p>
)}
</>
);
}
);
Input.displayName = "Input";
export { Input };