131 lines
4.2 KiB
TypeScript
131 lines
4.2 KiB
TypeScript
import { type FC, type InputHTMLAttributes, useEffect, useState } from 'react'
|
|
import { clx } from '../helpers/utils';
|
|
import { Eye, EyeSlash, SearchNormal } from 'iconsax-react';
|
|
import Error from './Error';
|
|
|
|
type Variant = "floating_outlined" | "primary" | "search";
|
|
|
|
type Props = {
|
|
label?: string;
|
|
className?: string;
|
|
variant?: Variant;
|
|
error_text?: string;
|
|
onEnter?: () => void;
|
|
unit?: string;
|
|
seprator?: boolean;
|
|
isNotRequired?: boolean;
|
|
onChangeSearchFinal?: (value: string) => void;
|
|
} & InputHTMLAttributes<HTMLInputElement>
|
|
|
|
const formatNumber = (value: string | number): string => {
|
|
if (!value) return '';
|
|
const inputValue = String(value).replace(/,/g, '');
|
|
return inputValue.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ',');
|
|
};
|
|
|
|
const Input: FC<Props> = (props: Props) => {
|
|
|
|
const [formattedValue, setFormattedValue] = useState<string>(
|
|
props.value ? formatNumber(props.value as string) : ''
|
|
);
|
|
|
|
const [showPassword, setShowPassword] = useState<boolean>(false)
|
|
const [search, setSearch] = useState<string>('')
|
|
|
|
const inputClass = clx(
|
|
'w-full h-[34px] text-black block px-4 text-xs rounded-xl border border-border',
|
|
props.readOnly && 'bg-gray-100 border-0 text-description',
|
|
props.variant === 'search' && 'bg-[#EEF0F7] border-0 ps-10',
|
|
props.className
|
|
);
|
|
|
|
const handleInputChange = (event: React.ChangeEvent<HTMLInputElement>) => {
|
|
if (props.seprator) {
|
|
const inputValue = event.target.value.replace(/,/g, ''); // حذف کاماها
|
|
const formatted = formatNumber(inputValue);
|
|
|
|
// بهروزرسانی مقدار قالببندیشده
|
|
setFormattedValue(formatted);
|
|
|
|
// ارسال مقدار خام به `onChange` والد
|
|
props.onChange?.({
|
|
...event,
|
|
target: {
|
|
...event.target,
|
|
value: inputValue,
|
|
},
|
|
});
|
|
} else {
|
|
props.onChange?.(event);
|
|
}
|
|
};
|
|
|
|
useEffect(() => {
|
|
if (props.value) {
|
|
setFormattedValue(formatNumber(props.value as string));
|
|
} else {
|
|
setFormattedValue('');
|
|
}
|
|
}, [props.value])
|
|
|
|
useEffect(() => {
|
|
if (props.variant === 'search' && props.onChangeSearchFinal) {
|
|
const timeout = setTimeout(() => {
|
|
props.onChangeSearchFinal?.(search)
|
|
}, 1000)
|
|
return () => clearTimeout(timeout)
|
|
}
|
|
}, [search, props])
|
|
|
|
|
|
const resolvedType =
|
|
props.type === 'password'
|
|
? showPassword
|
|
? 'text'
|
|
: 'password'
|
|
: props.type;
|
|
|
|
return (
|
|
<div className='w-full'>
|
|
<label className='text-xs'>
|
|
{props.label}
|
|
</label>
|
|
|
|
<div className='w-full relative mt-1'>
|
|
<input {...props} onChange={(e) => {
|
|
setSearch(e.target.value)
|
|
handleInputChange(e)
|
|
}} value={props.seprator ? formattedValue : props.value} type={resolvedType} className={inputClass} />
|
|
|
|
{
|
|
props.type === 'password' &&
|
|
<button
|
|
type='button'
|
|
onClick={() => setShowPassword((oldValue) => !oldValue)}
|
|
className='absolute top-0 bottom-0 left-3 my-auto flex h-5 w-5 cursor-pointer items-center justify-center text-black'
|
|
>
|
|
{
|
|
showPassword
|
|
? <EyeSlash color='#8C90A3' size={20} />
|
|
: <Eye color='#8C90A3' size={20} />
|
|
}
|
|
</button>
|
|
}
|
|
|
|
{
|
|
props.variant === 'search' &&
|
|
<SearchNormal size={20} color='#8C90A3' className='absolute top-0 w-5 bottom-0 my-auto right-3' />
|
|
}
|
|
|
|
{
|
|
props.error_text &&
|
|
<Error
|
|
errorText={props.error_text}
|
|
/>
|
|
}
|
|
</div>
|
|
</div>
|
|
)
|
|
}
|
|
|
|
export default Input |