'use client'; import { cn } from '@lib/utils'; import { CaretDownIcon, MagnifyingGlassIcon } from '@phosphor-icons/react'; import { useCallback, useEffect, useRef, useState } from 'react'; type SelectUserItem = { id: string; name: string; email: string; }; const userLabel = (u: { name: string; email: string }) => `${u.name} - ${u.email}`; type SelectUserProps = { value: string; onValueChange: (userId: string) => void; values: SelectUserItem[]; placeholder?: string; /** Khi truyền cùng onKeywordChange: tìm kiếm theo API (keyword gửi lên server) */ keyword?: string; onKeywordChange?: (value: string) => void; searchPlaceholder?: string; name?: string; id?: string; 'aria-invalid'?: boolean; disabled?: boolean; className?: string; selectKey?: 'id' | 'email'; }; export function SelectUser({ value, onValueChange, values, placeholder, keyword, onKeywordChange, searchPlaceholder = 'Tìm theo tên hoặc email...', name, id, 'aria-invalid': ariaInvalid, disabled = false, className, selectKey = 'id', }: SelectUserProps) { const [open, setOpen] = useState(false); const [localQuery, setLocalQuery] = useState(''); const wrapperRef = useRef(null); const searchInputRef = useRef(null); const useServerSearch = keyword !== undefined && onKeywordChange != null; const searchValue = useServerSearch ? keyword : localQuery; const setSearchValue = useServerSearch ? onKeywordChange! : setLocalQuery; const selectedUser = value != null && value !== '' ? values.find((u) => u[selectKey] === value) : null; const displayValue = selectedUser ? userLabel(selectedUser) : ''; const filtered = useServerSearch ? values : (() => { const q = localQuery.trim().toLowerCase(); return q === '' ? values : values.filter( (u) => u.name.toLowerCase().includes(q) || u.email.toLowerCase().includes(q), ); })(); const close = useCallback(() => { setOpen(false); if (!useServerSearch) setLocalQuery(''); }, [useServerSearch]); useEffect(() => { if (!open) return; searchInputRef.current?.focus(); }, [open]); useEffect(() => { if (!open) return; const onMouseDown = (e: MouseEvent) => { if ( wrapperRef.current && !wrapperRef.current.contains(e.target as Node) ) { close(); } }; document.addEventListener('mousedown', onMouseDown); return () => document.removeEventListener('mousedown', onMouseDown); }, [open, close]); const handleSelect = (userId: string) => { onValueChange(userId); close(); }; const controlId = id ?? name; const listboxId = controlId ? `${controlId}-listbox` : undefined; return (
{name != null && ( )}
!disabled && setOpen((o) => !o)} > {displayValue || ( {placeholder} )}
{open && (
setSearchValue(e.target.value)} onKeyDown={(e) => e.stopPropagation()} className="min-w-0 flex-1 bg-transparent py-1 text-xs outline-none placeholder:text-muted-foreground" />
{filtered.length === 0 ? (
Không có kết quả
) : ( filtered.map((u) => ( )) )}
)}
); }