photum/components/Input.tsx
2025-12-01 10:59:24 -03:00

120 lines
No EOL
4 KiB
TypeScript

import React, { useState } from 'react';
import { Eye, EyeOff } from 'lucide-react';
interface InputProps extends React.InputHTMLAttributes<HTMLInputElement> {
label: string;
error?: string;
mask?: 'phone' | 'cnpj' | 'cep';
}
export const Input: React.FC<InputProps> = ({ label, error, className = '', type, mask, onChange, ...props }) => {
const [showPassword, setShowPassword] = useState(false);
const isPassword = type === 'password';
const inputType = isPassword && showPassword ? 'text' : type;
const applyMask = (value: string, maskType?: 'phone' | 'cnpj' | 'cep') => {
if (!maskType) return value;
const numbers = value.replace(/\D/g, '');
switch (maskType) {
case 'phone':
// Limita a 11 dígitos (celular)
const phoneNumbers = numbers.slice(0, 11);
if (phoneNumbers.length <= 10) {
return phoneNumbers.replace(/(\d{2})(\d{4})(\d{0,4})/, '($1) $2-$3');
}
return phoneNumbers.replace(/(\d{2})(\d{5})(\d{0,4})/, '($1) $2-$3');
case 'cnpj':
// Limita a 14 dígitos
const cnpjNumbers = numbers.slice(0, 14);
return cnpjNumbers.replace(/(\d{2})(\d{3})(\d{3})(\d{4})(\d{0,2})/, '$1.$2.$3/$4-$5');
case 'cep':
// Limita a 8 dígitos
const cepNumbers = numbers.slice(0, 8);
return cepNumbers.replace(/(\d{5})(\d{0,3})/, '$1-$2');
default:
return value;
}
};
const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
if (mask) {
const maskedValue = applyMask(e.target.value, mask);
e.target.value = maskedValue;
}
onChange?.(e);
};
// Define maxLength baseado na máscara
const getMaxLength = () => {
if (!mask) return undefined;
switch (mask) {
case 'phone': return 15; // (00) 00000-0000
case 'cnpj': return 18; // 00.000.000/0000-00
case 'cep': return 9; // 00000-000
default: return undefined;
}
};
return (
<div className="w-full">
<label className="block text-sm font-medium text-gray-700 mb-1 tracking-wide uppercase text-xs">
{label}
</label>
<div className="relative">
<input
className={`w-full px-4 py-2 border rounded-sm focus:outline-none focus:ring-1 focus:ring-brand-gold focus:border-brand-gold transition-colors
${error ? 'border-red-500' : 'border-gray-300'}
${isPassword ? 'pr-10' : ''}
${className}`}
type={inputType}
onChange={handleChange}
maxLength={getMaxLength()}
{...props}
/>
{isPassword && (
<button
type="button"
onClick={() => setShowPassword(!showPassword)}
className="absolute right-3 top-1/2 -translate-y-1/2 text-gray-400 hover:text-gray-600 transition-colors"
>
{showPassword ? <EyeOff size={18} /> : <Eye size={18} />}
</button>
)}
</div>
{error && <span className="text-xs text-red-500 mt-1">{error}</span>}
</div>
);
};
interface SelectProps extends React.SelectHTMLAttributes<HTMLSelectElement> {
label: string;
options: { value: string; label: string }[];
error?: string;
}
export const Select: React.FC<SelectProps> = ({ label, options, error, className = '', ...props }) => {
return (
<div className="w-full">
<label className="block text-sm font-medium text-gray-700 mb-1 tracking-wide uppercase text-xs">
{label}
</label>
<select
className={`w-full px-4 py-2 border rounded-sm focus:outline-none focus:ring-1 focus:ring-brand-gold focus:border-brand-gold transition-colors appearance-none bg-white
${error ? 'border-red-500' : 'border-gray-300'}
${className}`}
{...props}
>
<option value="" disabled>Selecione uma opção</option>
{options.map(opt => (
<option key={opt.value} value={opt.value}>{opt.label}</option>
))}
</select>
{error && <span className="text-xs text-red-500 mt-1">{error}</span>}
</div>
);
};