Добавлены новые зависимости, обновлены стили и улучшена структура проекта. Обновлен README с описанием функционала и технологий. Реализована анимация и адаптивный дизайн. Настроена авторизация с использованием Apollo Client.
This commit is contained in:
108
src/components/ui/phone-input.tsx
Normal file
108
src/components/ui/phone-input.tsx
Normal file
@ -0,0 +1,108 @@
|
||||
"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 }
|
Reference in New Issue
Block a user