'use client'; import * as React from 'react'; import { Check, ChevronDown, X } from 'lucide-react'; import { cn } from '@nice/ui/lib/utils'; import { Button } from '@nice/ui/components/button'; import { Input } from '@nice/ui/components/input'; import { Popover, PopoverContent, PopoverTrigger } from '@nice/ui/components/popover'; import { Command, CommandEmpty, CommandGroup, CommandItem, CommandList } from '@nice/ui/components/command'; import { useTranslation } from '@nice/i18n'; interface DutySelectProps { value?: string; onChange?: (value: string) => void; placeholder?: string; disabled?: boolean; className?: string; allowClear?: boolean; } export function DutySelect({ value = '', onChange, placeholder = '请选择或输入职务', disabled = false, className, allowClear = true, }: DutySelectProps) { const { t } = useTranslation(); const [open, setOpen] = React.useState(false); const [inputValue, setInputValue] = React.useState(value); const inputRef = React.useRef(null); const isSelectingRef = React.useRef(false); // 添加一个ref来跟踪是否是通过点击打开的 const isOpeningRef = React.useRef(false); // 获取预定义职务选项 const dutyOptions = React.useMemo(() => { const dutyEntries = Object.entries({}); return dutyEntries.map(([value, _]) => ({ value, label: t(`duty.${value}`), })); }, [t]); // 同步外部value到内部inputValue React.useEffect(() => { setInputValue(value); }, [value]); // 处理输入值变化 const handleInputChange = (e: React.ChangeEvent) => { const newValue = e.target.value; setInputValue(newValue); onChange?.(newValue); // 输入时显示下拉选项 if (!open) { setOpen(true); } }; // 处理选项选择 const handleSelect = (optionLabel: string) => { isSelectingRef.current = true; // 标记正在选择 setInputValue(optionLabel); onChange?.(optionLabel); // 延迟重置标记和重新聚焦 setTimeout(() => { isSelectingRef.current = false; inputRef.current?.focus(); // 重新聚焦到输入框 }, 50); }; // 清除值 const handleClear = (e: React.MouseEvent) => { e.stopPropagation(); e.preventDefault(); setInputValue(''); onChange?.(''); inputRef.current?.focus(); }; // 处理输入框获得焦点 - 显示下拉选项 const handleInputFocus = () => { if (!disabled && dutyOptions.length > 0) { // 延迟设置,避免与点击事件冲突 setTimeout(() => { if (!isOpeningRef.current) { setOpen(true); } isOpeningRef.current = false; }, 0); } }; // 处理输入框点击 const handleInputClick = (e: React.MouseEvent) => { e.stopPropagation(); if (!disabled && dutyOptions.length > 0) { isOpeningRef.current = true; setOpen(true); } }; // 处理输入框失去焦点 const handleInputBlur = (e: React.FocusEvent) => { // 如果正在选择选项,不要关闭菜单 if (isSelectingRef.current) { return; } // 延迟关闭,避免点击选项时立即关闭 setTimeout(() => { // 再次检查是否正在选择选项 if (isSelectingRef.current) { return; } // 检查焦点是否移动到了下拉选项中 const activeElement = document.activeElement; const popoverElement = document.querySelector('[data-radix-popover-content]'); if (!popoverElement?.contains(activeElement)) { setOpen(false); } }, 150); }; // 处理键盘事件 const handleKeyDown = (e: React.KeyboardEvent) => { if (e.key === 'ArrowDown' || e.key === 'ArrowUp') { e.preventDefault(); if (!open) { setOpen(true); } } if (e.key === 'Escape') { setOpen(false); inputRef.current?.blur(); } }; // 处理下拉箭头点击 const handleChevronClick = (e: React.MouseEvent) => { e.preventDefault(); e.stopPropagation(); if (disabled) return; setOpen(!open); inputRef.current?.focus(); }; // 过滤选项(根据输入值进行模糊匹配) const filteredOptions = React.useMemo(() => { if (!inputValue.trim()) return dutyOptions; return dutyOptions.filter(option => option.label.toLowerCase().includes(inputValue.toLowerCase()) || option.value.toLowerCase().includes(inputValue.toLowerCase()) ); }, [dutyOptions, inputValue]); return (
e.stopPropagation()} />
{allowClear && inputValue && ( )}
{ e.stopPropagation(); e.preventDefault(); }} className="flex items-center justify-center p-1 hover:bg-gray-100 rounded cursor-pointer" >
e.preventDefault()} // 防止内容获得焦点 > {filteredOptions.length === 0 ? ( {inputValue.trim() ? '当前无匹配的职务,可手动输入职务' : '请输入内容搜索职务'} ) : ( {filteredOptions.map((option) => ( handleSelect(option.label)} className={cn( "cursor-pointer", inputValue === option.label && "bg-accent text-accent-foreground" )} onMouseDown={(e) => { // 防止鼠标按下时输入框失去焦点 e.preventDefault(); }} > {option.label} ))} )}
); }