237 lines
8.4 KiB
TypeScript
237 lines
8.4 KiB
TypeScript
import { FC, useState, useRef, useEffect } from 'react'
|
|
import { CloseCircle } from 'iconsax-react'
|
|
import { EmailSuggestion } from '@/services/EmailSuggestionsService'
|
|
|
|
interface EmailInputProps {
|
|
toEmails: string[]
|
|
onAddEmail: (email: string) => void
|
|
onRemoveEmail: (email: string) => void
|
|
suggestions: EmailSuggestion[]
|
|
onSearchSuggestions?: (query: string) => void
|
|
showCcBccButton?: boolean
|
|
onCcBccClick?: () => void
|
|
}
|
|
|
|
const EmailInput: FC<EmailInputProps> = ({
|
|
toEmails,
|
|
onAddEmail,
|
|
onRemoveEmail,
|
|
suggestions,
|
|
onSearchSuggestions,
|
|
showCcBccButton = false,
|
|
onCcBccClick
|
|
}) => {
|
|
const [to, setTo] = useState('')
|
|
const [filteredSuggestions, setFilteredSuggestions] = useState<EmailSuggestion[]>([])
|
|
const [showSuggestions, setShowSuggestions] = useState(false)
|
|
const [selectedSuggestionIndex, setSelectedSuggestionIndex] = useState(-1)
|
|
|
|
const typingTimeoutRef = useRef<number | null>(null)
|
|
const searchTimeoutRef = useRef<number | null>(null)
|
|
const inputRef = useRef<HTMLInputElement>(null)
|
|
|
|
const isValidEmail = (email: string) => {
|
|
const emailRegex = /^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/
|
|
return emailRegex.test(email)
|
|
}
|
|
|
|
const filterSuggestions = (input: string) => {
|
|
if (!input.trim()) {
|
|
setFilteredSuggestions([])
|
|
setShowSuggestions(false)
|
|
return
|
|
}
|
|
|
|
if (onSearchSuggestions) {
|
|
if (searchTimeoutRef.current) {
|
|
clearTimeout(searchTimeoutRef.current)
|
|
}
|
|
searchTimeoutRef.current = window.setTimeout(() => {
|
|
onSearchSuggestions(input)
|
|
}, 300)
|
|
}
|
|
|
|
setSelectedSuggestionIndex(-1)
|
|
}
|
|
|
|
useEffect(() => {
|
|
if (suggestions.length > 0) {
|
|
const filtered = suggestions
|
|
.filter(suggestion =>
|
|
!toEmails.includes(suggestion.address)
|
|
)
|
|
.slice(0, 5)
|
|
|
|
setFilteredSuggestions(filtered)
|
|
setShowSuggestions(filtered.length > 0 && to.trim().length > 0)
|
|
} else if (to.trim() === '') {
|
|
setFilteredSuggestions([])
|
|
setShowSuggestions(false)
|
|
}
|
|
}, [suggestions, to, toEmails])
|
|
|
|
const selectSuggestion = (suggestion: EmailSuggestion) => {
|
|
onAddEmail(suggestion.address)
|
|
setTo('')
|
|
setShowSuggestions(false)
|
|
setFilteredSuggestions([])
|
|
setSelectedSuggestionIndex(-1)
|
|
inputRef.current?.focus()
|
|
}
|
|
|
|
const addEmail = (email: string) => {
|
|
if (isValidEmail(email.trim())) {
|
|
onAddEmail(email.trim())
|
|
setTo('')
|
|
setShowSuggestions(false)
|
|
setFilteredSuggestions([])
|
|
}
|
|
}
|
|
|
|
const handleToChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
|
const newValue = e.target.value
|
|
setTo(newValue)
|
|
filterSuggestions(newValue)
|
|
|
|
if (typingTimeoutRef.current) {
|
|
clearTimeout(typingTimeoutRef.current)
|
|
}
|
|
|
|
if (newValue.trim()) {
|
|
typingTimeoutRef.current = window.setTimeout(() => {
|
|
if (isValidEmail(newValue.trim())) {
|
|
addEmail(newValue.trim())
|
|
}
|
|
}, 1500)
|
|
}
|
|
}
|
|
|
|
const handleToBlur = () => {
|
|
setTimeout(() => {
|
|
setShowSuggestions(false)
|
|
setFilteredSuggestions([])
|
|
}, 200)
|
|
|
|
if (typingTimeoutRef.current) {
|
|
clearTimeout(typingTimeoutRef.current)
|
|
}
|
|
|
|
if (to.trim() && isValidEmail(to.trim())) {
|
|
addEmail(to.trim())
|
|
}
|
|
}
|
|
|
|
const handleToFocus = () => {
|
|
if (to.trim()) {
|
|
filterSuggestions(to)
|
|
}
|
|
}
|
|
|
|
const handleToKeyDown = (e: React.KeyboardEvent<HTMLInputElement>) => {
|
|
if (showSuggestions && filteredSuggestions.length > 0) {
|
|
if (e.key === 'ArrowDown') {
|
|
e.preventDefault()
|
|
setSelectedSuggestionIndex(prev =>
|
|
prev < filteredSuggestions.length - 1 ? prev + 1 : 0
|
|
)
|
|
} else if (e.key === 'ArrowUp') {
|
|
e.preventDefault()
|
|
setSelectedSuggestionIndex(prev =>
|
|
prev > 0 ? prev - 1 : filteredSuggestions.length - 1
|
|
)
|
|
} else if (e.key === 'Enter' && selectedSuggestionIndex >= 0) {
|
|
e.preventDefault()
|
|
selectSuggestion(filteredSuggestions[selectedSuggestionIndex])
|
|
return
|
|
} else if (e.key === 'Escape') {
|
|
setShowSuggestions(false)
|
|
setFilteredSuggestions([])
|
|
setSelectedSuggestionIndex(-1)
|
|
return
|
|
}
|
|
}
|
|
|
|
if (e.key === 'Enter' && to.trim()) {
|
|
e.preventDefault()
|
|
if (typingTimeoutRef.current) {
|
|
clearTimeout(typingTimeoutRef.current)
|
|
}
|
|
addEmail(to.trim())
|
|
}
|
|
}
|
|
|
|
useEffect(() => {
|
|
return () => {
|
|
if (typingTimeoutRef.current) {
|
|
clearTimeout(typingTimeoutRef.current)
|
|
}
|
|
if (searchTimeoutRef.current) {
|
|
clearTimeout(searchTimeoutRef.current)
|
|
}
|
|
}
|
|
}, [])
|
|
|
|
return (
|
|
<div className='w-full relative'>
|
|
<div className="w-full bg-white dark:bg-[#252526] min-h-10 text-black dark:text-white flex flex-wrap items-center gap-1 px-4 py-2 text-xs rounded-xl border border-border">
|
|
{toEmails.map((email, index) => (
|
|
<div
|
|
key={index}
|
|
className="flex items-center gap-1 bg-[#EBEDF5] dark:bg-[#2d2d30] text-xs h-7 rounded-full px-3"
|
|
>
|
|
<span>{email}</span>
|
|
<CloseCircle
|
|
size={16}
|
|
color='#D52903'
|
|
className='cursor-pointer'
|
|
onClick={() => onRemoveEmail(email)}
|
|
/>
|
|
</div>
|
|
))}
|
|
|
|
<input
|
|
ref={inputRef}
|
|
value={to}
|
|
onChange={handleToChange}
|
|
onBlur={handleToBlur}
|
|
onFocus={handleToFocus}
|
|
onKeyDown={handleToKeyDown}
|
|
placeholder={toEmails.length === 0 ? "ایمیل را وارد کرده و Enter بزنید" : ""}
|
|
className="flex-1 min-w-32 bg-transparent outline-none"
|
|
/>
|
|
|
|
{showCcBccButton && (
|
|
<button
|
|
onClick={onCcBccClick}
|
|
className='text-xs text-gray-500 dark:text-gray-400 hover:text-gray-700 dark:hover:text-gray-200 transition-colors pt-[3px] cursor-pointer '
|
|
>
|
|
CC/BCC
|
|
</button>
|
|
)}
|
|
</div>
|
|
|
|
{showSuggestions && filteredSuggestions.length > 0 && (
|
|
<div className="absolute top-full left-0 right-0 bg-white dark:bg-[#252526] border border-gray-200 dark:border-[#3e3e42] rounded-lg shadow-lg z-[10000] mt-1">
|
|
{filteredSuggestions.map((suggestion, index) => (
|
|
<div
|
|
key={suggestion.address}
|
|
className={`px-4 py-2 cursor-pointer text-sm hover:bg-gray-100 dark:hover:bg-[#2d2d30] border-b border-gray-100 dark:border-[#2d2d30] last:border-b-0 ${selectedSuggestionIndex === index ? 'bg-blue-50 dark:bg-blue-900/20 text-blue-600 dark:text-blue-400' : 'text-gray-700 dark:text-gray-300'
|
|
}`}
|
|
onMouseDown={() => selectSuggestion(suggestion)}
|
|
onMouseEnter={() => setSelectedSuggestionIndex(index)}
|
|
>
|
|
<div className="flex flex-col">
|
|
<span className="font-medium">{suggestion.name || suggestion.address}</span>
|
|
{suggestion.name && (
|
|
<span className="text-xs text-gray-500 dark:text-gray-400">{suggestion.address}</span>
|
|
)}
|
|
</div>
|
|
</div>
|
|
))}
|
|
</div>
|
|
)}
|
|
</div>
|
|
)
|
|
}
|
|
|
|
export default EmailInput
|