108 lines
4.4 KiB
TypeScript
108 lines
4.4 KiB
TypeScript
"use client"
|
||
|
||
import * as React from "react"
|
||
import { IMaskInput } from "react-imask"
|
||
import { cn } from "@/lib/utils"
|
||
|
||
export interface PhoneInputProps
|
||
extends Omit<React.InputHTMLAttributes<HTMLInputElement>, 'onChange' | 'value'> {
|
||
onChange?: (value: string) => void
|
||
value?: string
|
||
}
|
||
|
||
const PhoneInput = React.forwardRef<HTMLInputElement, PhoneInputProps>(
|
||
({ className, onChange, value, ...props }, ref) => {
|
||
const handleAccept = (value: string) => {
|
||
onChange?.(value)
|
||
}
|
||
|
||
// Фильтруем пропсы, которые могут конфликтовать с IMaskInput
|
||
const { min, max, step, ...filteredProps } = props
|
||
|
||
return (
|
||
<IMaskInput
|
||
mask="+7 (000) 000-00-00"
|
||
value={value}
|
||
onAccept={handleAccept}
|
||
inputRef={ref}
|
||
{...filteredProps}
|
||
className={cn(
|
||
"flex h-12 w-full rounded-lg border border-input bg-background px-4 py-3 text-base ring-offset-background file:border-0 file:bg-transparent file:text-sm file:font-medium placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50",
|
||
"transition-all duration-200 hover:border-primary/50 focus:border-primary",
|
||
"cursor-pointer", // Добавляем cursor pointer в соответствии с предпочтениями пользователя
|
||
className
|
||
)}
|
||
/>
|
||
)
|
||
}
|
||
)
|
||
PhoneInput.displayName = "PhoneInput"
|
||
|
||
const GlassPhoneInput = React.forwardRef<HTMLInputElement, PhoneInputProps>(
|
||
({ className, onChange, value, ...props }, ref) => {
|
||
const [isFocused, setIsFocused] = React.useState(false)
|
||
|
||
const handleAccept = (value: string) => {
|
||
onChange?.(value)
|
||
}
|
||
|
||
const handleFocus = (e: React.FocusEvent<HTMLInputElement>) => {
|
||
setIsFocused(true)
|
||
props.onFocus?.(e)
|
||
}
|
||
|
||
const handleBlur = (e: React.FocusEvent<HTMLInputElement>) => {
|
||
setIsFocused(false)
|
||
props.onBlur?.(e)
|
||
}
|
||
|
||
// Проверяем валидность номера
|
||
const isValid = value ? value.replace(/\D/g, '').length === 11 : false
|
||
const isEmpty = !value || value.replace(/\D/g, '').length === 0
|
||
|
||
// Фильтруем пропсы, которые могут конфликтовать с IMaskInput
|
||
const { min, max, step, onFocus, onBlur, ...filteredProps } = props
|
||
|
||
return (
|
||
<div className="relative">
|
||
<IMaskInput
|
||
mask="+7 (000) 000-00-00"
|
||
value={value}
|
||
onAccept={handleAccept}
|
||
onFocus={handleFocus}
|
||
onBlur={handleBlur}
|
||
inputRef={ref}
|
||
{...filteredProps}
|
||
className={cn(
|
||
"glass-input text-white placeholder:text-white/50 selection:bg-purple-500/30 flex h-12 w-full rounded-lg px-4 py-3 text-base font-medium outline-none cursor-pointer transition-all duration-300",
|
||
isFocused && "ring-2 ring-purple-400/50 border-purple-400/30",
|
||
isValid && !isFocused && "border-green-400/30 bg-green-500/5",
|
||
!isEmpty && !isValid && !isFocused && "border-yellow-400/30 bg-yellow-500/5",
|
||
className
|
||
)}
|
||
/>
|
||
|
||
{/* Индикатор валидности */}
|
||
<div className="absolute right-3 top-1/2 transform -translate-y-1/2 pointer-events-none">
|
||
{isValid && (
|
||
<div className="w-5 h-5 rounded-full bg-green-500/20 border border-green-400/30 flex items-center justify-center">
|
||
<svg className="w-3 h-3 text-green-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M5 13l4 4L19 7" />
|
||
</svg>
|
||
</div>
|
||
)}
|
||
{!isEmpty && !isValid && (
|
||
<div className="w-5 h-5 rounded-full bg-yellow-500/20 border border-yellow-400/30 flex items-center justify-center">
|
||
<svg className="w-3 h-3 text-yellow-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-2.5L13.732 4c-.77-.833-1.732-.833-2.464 0L4.34 16.5c-.77.833.192 2.5 1.732 2.5z" />
|
||
</svg>
|
||
</div>
|
||
)}
|
||
</div>
|
||
</div>
|
||
)
|
||
}
|
||
)
|
||
GlassPhoneInput.displayName = "GlassPhoneInput"
|
||
|
||
export { PhoneInput, GlassPhoneInput }
|