veza/apps/web/src/components/ui/floating-input.tsx

74 lines
3 KiB
TypeScript
Raw Normal View History

2026-01-22 16:23:11 +00:00
import * as React from 'react';
import { cn } from '@/lib/utils';
import { useId } from 'react';
export interface FloatingInputProps
extends React.InputHTMLAttributes<HTMLInputElement> {
label: string;
error?: string;
icon?: React.ReactNode;
}
const FloatingInput = React.forwardRef<HTMLInputElement, FloatingInputProps>(
({ className, label, error, icon, type, id, ...props }, ref) => {
const generatedId = useId();
const inputId = id || generatedId;
return (
<div className="relative group w-full mb-5">
<div className="relative">
<input
type={type}
id={inputId}
className={cn(
"block px-4 pb-2.5 pt-5 w-full text-sm text-white bg-kodo-graphite/40 rounded-xl border appearance-none focus:outline-none focus:ring-0 peer transition-all duration-200",
// Borders & Colors
error
? "border-kodo-red focus:border-kodo-red"
: "border-white/10 hover:border-white/20 focus:border-kodo-cyan",
// Glassmorphism
"backdrop-blur-sm",
icon ? "pl-11" : "",
className
)}
placeholder=" "
ref={ref}
{...props}
/>
{/* Icon */}
{icon && (
<div className="absolute left-3.5 top-1/2 -translate-y-1/2 text-kodo-text-dim peer-focus:text-kodo-cyan transition-colors pointer-events-none">
{icon}
</div>
)}
{/* Floating Label */}
<label
htmlFor={inputId}
className={cn(
"absolute text-sm duration-200 transform -translate-y-3 scale-75 top-4 z-10 origin-[0] peer-placeholder-shown:scale-100 peer-placeholder-shown:translate-y-0 peer-focus:scale-75 peer-focus:-translate-y-3 pointer-events-none",
icon ? "left-11 peer-focus:left-11 peer-placeholder-shown:left-11" : "left-4 peer-focus:left-4 peer-placeholder-shown:left-4",
error
? "text-kodo-red"
: "text-kodo-text-dim peer-focus:text-kodo-cyan group-hover:text-white/70"
)}
>
{label}
</label>
</div>
{/* Error Message */}
{error && (
<p className="mt-1 text-xs text-kodo-red animate-slideDown">
{error}
</p>
)}
</div>
);
}
);
FloatingInput.displayName = 'FloatingInput';
export { FloatingInput };