This commit is contained in:
@@ -8,104 +8,104 @@ type InputKind = 'text' | 'password' | 'email';
|
||||
type Layout = 'stacked' | 'inline';
|
||||
|
||||
type InputFieldProps = {
|
||||
label?: string;
|
||||
placeholder?: string;
|
||||
type: InputKind;
|
||||
size?: ComponentSize;
|
||||
layout?: Layout;
|
||||
value: string;
|
||||
name?: string;
|
||||
onChange?: ChangeEventHandler<HTMLInputElement>;
|
||||
onBlur?: FocusEventHandler<HTMLInputElement>;
|
||||
inputRef?: Ref<HTMLInputElement>;
|
||||
disabled?: boolean;
|
||||
required?: boolean;
|
||||
error?: string;
|
||||
rightIcon?: ReactNode;
|
||||
className?: string;
|
||||
inputClassName?: string;
|
||||
label?: string;
|
||||
placeholder?: string;
|
||||
type: InputKind;
|
||||
size?: ComponentSize;
|
||||
layout?: Layout;
|
||||
value: string;
|
||||
name?: string;
|
||||
onChange?: ChangeEventHandler<HTMLInputElement>;
|
||||
onBlur?: FocusEventHandler<HTMLInputElement>;
|
||||
inputRef?: Ref<HTMLInputElement>;
|
||||
disabled?: boolean;
|
||||
required?: boolean;
|
||||
error?: string;
|
||||
rightIcon?: ReactNode;
|
||||
className?: string;
|
||||
inputClassName?: string;
|
||||
};
|
||||
|
||||
export function InputField({
|
||||
label,
|
||||
placeholder = '',
|
||||
type,
|
||||
size = 'md',
|
||||
layout = 'stacked',
|
||||
value,
|
||||
name,
|
||||
onChange,
|
||||
onBlur,
|
||||
inputRef,
|
||||
disabled = false,
|
||||
required = false,
|
||||
error,
|
||||
rightIcon,
|
||||
className = '',
|
||||
inputClassName = '',
|
||||
label,
|
||||
placeholder = '',
|
||||
type,
|
||||
size = 'md',
|
||||
layout = 'stacked',
|
||||
value,
|
||||
name,
|
||||
onChange,
|
||||
onBlur,
|
||||
inputRef,
|
||||
disabled = false,
|
||||
required = false,
|
||||
error,
|
||||
rightIcon,
|
||||
className = '',
|
||||
inputClassName = '',
|
||||
}: Readonly<InputFieldProps>) {
|
||||
const [showPassword, setShowPassword] = useState(false);
|
||||
const containerSizeClass = {
|
||||
sm: 'max-w-xs',
|
||||
md: 'max-w-sm',
|
||||
lg: 'max-w-md',
|
||||
full: 'max-w-none',
|
||||
}[size];
|
||||
const [showPassword, setShowPassword] = useState(false);
|
||||
const containerSizeClass = {
|
||||
sm: 'max-w-xs',
|
||||
md: 'max-w-sm',
|
||||
lg: 'max-w-md',
|
||||
full: 'max-w-none',
|
||||
}[size];
|
||||
|
||||
const inputSizeClass = {
|
||||
sm: 'h-8 !text-xs',
|
||||
md: 'h-10 text-sm',
|
||||
lg: 'h-12 text-sm',
|
||||
full: 'h-10 text-sm',
|
||||
}[size];
|
||||
const inputSizeClass = {
|
||||
sm: 'h-8 !text-xs',
|
||||
md: 'h-10 text-sm',
|
||||
lg: 'h-12 text-sm',
|
||||
full: 'h-10 text-sm',
|
||||
}[size];
|
||||
|
||||
const wrapperClass =
|
||||
layout === 'inline' ? 'inline-flex w-auto items-center gap-2' : 'block w-full gap-1';
|
||||
const labelClass = layout === 'inline' ? 'text-xs ui-body-secondary' : '';
|
||||
const isPasswordType = type === 'password';
|
||||
const resolvedType: InputKind = isPasswordType && showPassword ? 'text' : type;
|
||||
const hasTrailingIcon = isPasswordType || Boolean(rightIcon);
|
||||
const inputWrapperClass = layout === 'inline' ? 'relative' : 'relative mt-1';
|
||||
const wrapperClass =
|
||||
layout === 'inline' ? 'inline-flex w-auto items-center gap-2' : 'block w-full gap-1';
|
||||
const labelClass = layout === 'inline' ? 'text-xs ui-body-secondary' : '';
|
||||
const isPasswordType = type === 'password';
|
||||
const resolvedType: InputKind = isPasswordType && showPassword ? 'text' : type;
|
||||
const hasTrailingIcon = isPasswordType || Boolean(rightIcon);
|
||||
const inputWrapperClass = layout === 'inline' ? 'relative' : 'relative mt-1';
|
||||
|
||||
return (
|
||||
<label
|
||||
className={`${wrapperClass} text-sm font-medium ${disabled ? 'ui-label-disabled' : 'ui-label'} ${containerSizeClass} ${className}`.trim()}
|
||||
>
|
||||
{label ? <span className={labelClass}>{label}</span> : null}
|
||||
<div className={inputWrapperClass}>
|
||||
<input
|
||||
type={resolvedType}
|
||||
value={value}
|
||||
name={name}
|
||||
onChange={onChange}
|
||||
onBlur={onBlur}
|
||||
ref={inputRef}
|
||||
placeholder={placeholder}
|
||||
disabled={disabled}
|
||||
required={required}
|
||||
className={`field w-full ${hasTrailingIcon ? 'pr-10' : ''} ${inputSizeClass} ${error ? 'border-red-400/70 focus:border-red-400 focus:ring-red-400/30' : ''} ${inputClassName}`.trim()}
|
||||
/>
|
||||
{isPasswordType ? (
|
||||
<Button
|
||||
type="noborder"
|
||||
size="sm"
|
||||
icon={showPassword ? EyeSlashIcon : EyeIcon}
|
||||
onClick={() => setShowPassword((prev) => !prev)}
|
||||
disabled={disabled}
|
||||
className="absolute inset-y-0 right-2 my-auto !h-6 !w-6 !rounded-md !p-0 ui-body-secondary transition hover:opacity-80 disabled:cursor-not-allowed disabled:opacity-60"
|
||||
ariaLabel={showPassword ? 'Hide password' : 'Show password'}
|
||||
/>
|
||||
) : rightIcon ? (
|
||||
<span className="pointer-events-none absolute inset-y-0 right-2 inline-flex items-center justify-center px-1">
|
||||
{rightIcon}
|
||||
</span>
|
||||
) : null}
|
||||
</div>
|
||||
{error ? (
|
||||
<span className="mt-1 block text-xs" style={{ color: 'var(--error-text)' }}>
|
||||
{error}
|
||||
</span>
|
||||
) : null}
|
||||
</label>
|
||||
);
|
||||
return (
|
||||
<label
|
||||
className={`${wrapperClass} text-sm font-medium ${disabled ? 'ui-label-disabled' : 'ui-label'} ${containerSizeClass} ${className}`.trim()}
|
||||
>
|
||||
{label ? <span className={labelClass}>{label}</span> : null}
|
||||
<div className={inputWrapperClass}>
|
||||
<input
|
||||
type={resolvedType}
|
||||
value={value}
|
||||
name={name}
|
||||
onChange={onChange}
|
||||
onBlur={onBlur}
|
||||
ref={inputRef}
|
||||
placeholder={placeholder}
|
||||
disabled={disabled}
|
||||
required={required}
|
||||
className={`field w-full ${hasTrailingIcon ? 'pr-10' : ''} ${inputSizeClass} ${error ? 'border-red-400/70 focus:border-red-400 focus:ring-red-400/30' : ''} ${inputClassName}`.trim()}
|
||||
/>
|
||||
{isPasswordType ? (
|
||||
<Button
|
||||
type="noborder"
|
||||
size="sm"
|
||||
icon={showPassword ? EyeSlashIcon : EyeIcon}
|
||||
onClick={() => setShowPassword((prev) => !prev)}
|
||||
disabled={disabled}
|
||||
className="absolute inset-y-0 right-2 my-auto !h-6 !w-6 !rounded-md !p-0 ui-body-secondary transition hover:opacity-80 disabled:cursor-not-allowed disabled:opacity-60"
|
||||
ariaLabel={showPassword ? 'Hide password' : 'Show password'}
|
||||
/>
|
||||
) : rightIcon ? (
|
||||
<span className="pointer-events-none absolute inset-y-0 right-2 inline-flex items-center justify-center px-1">
|
||||
{rightIcon}
|
||||
</span>
|
||||
) : null}
|
||||
</div>
|
||||
{error ? (
|
||||
<span className="mt-1 block text-xs" style={{ color: 'var(--error-text)' }}>
|
||||
{error}
|
||||
</span>
|
||||
) : null}
|
||||
</label>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user