forked from elizaOS/eliza
-
Notifications
You must be signed in to change notification settings - Fork 5
/
Copy pathcombobox.tsx
129 lines (120 loc) · 4.12 KB
/
combobox.tsx
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
import { useState, useRef, useEffect } from 'react';
import { Input } from '@/components/ui/input';
import { Card } from '@/components/ui/card';
import { Badge } from '@/components/ui/badge';
import { ChevronDown, X } from 'lucide-react';
import { formatAgentName } from '@/lib/utils';
interface Option {
icon: string;
label: string;
}
interface MultiSelectComboboxProps {
options: Option[];
className?: string;
onSelect?: (selected: Option[]) => void;
}
export default function MultiSelectCombobox({
options = [],
className = '',
onSelect,
}: MultiSelectComboboxProps) {
const [selected, setSelected] = useState<Option[]>([]);
const [isOpen, setIsOpen] = useState(false);
const comboboxRef = useRef<HTMLDivElement>(null);
useEffect(() => {
const handleClickOutside = (event: MouseEvent) => {
if (comboboxRef.current && !comboboxRef.current.contains(event.target as Node)) {
setIsOpen(false);
}
};
document.addEventListener('mousedown', handleClickOutside);
return () => document.removeEventListener('mousedown', handleClickOutside);
}, []);
const toggleSelection = (option: Option) => {
setSelected((prev) => {
const newSelection = prev.some((item) => item.label === option.label)
? prev.filter((item) => item.label !== option.label)
: [...prev, option];
if (onSelect) onSelect(newSelection);
return newSelection;
});
};
const removeSelection = (option: Option) => {
setSelected((prev) => {
const newSelection = prev.filter((item) => item.label !== option.label);
if (onSelect) onSelect(newSelection);
return newSelection;
});
};
const removeExtraSelections = () => {
setSelected((prev) => {
const newSelection = prev.slice(0, 3); // Keep only the first 3
if (onSelect) onSelect(newSelection);
return newSelection;
});
};
return (
<div className={`relative w-80 bg-muted ${className}`} ref={comboboxRef}>
<div
className="flex items-center gap-2 border border-gray-300 p-2 rounded cursor-pointer"
onClick={() => setIsOpen(!isOpen)}
>
<div className="flex flex-wrap gap-1 w-full">
{selected.length > 0 ? (
<>
{selected.slice(0, 3).map((item, index) => (
<Badge key={index} className="flex items-center gap-1 px-2">
{item.label}
<X
size={12}
className="cursor-pointer"
onClick={(e) => {
e.stopPropagation();
removeSelection(item);
}}
/>
</Badge>
))}
{selected.length > 3 && (
<Badge
className="px-2 cursor-pointer"
onClick={(e) => {
e.stopPropagation();
removeExtraSelections();
}}
>
+{selected.length - 3} more
</Badge>
)}
</>
) : (
<span className="text-gray-500">Select options...</span>
)}
</div>
<ChevronDown size={16} />
</div>
{isOpen && (
<Card className="absolute left-0 mt-2 w-full p-2 shadow-md border border-gray-500 rounded z-40 max-h-60 overflow-y-auto">
{options.map((option, index) => (
<div
key={index}
className={`flex items-center gap-2 p-2 cursor-pointer rounded ${
selected.some((item) => item.label === option.label) ? 'bg-muted' : 'bg-card'
}`}
onClick={() => toggleSelection(option)}
>
<div className="bg-gray-500 rounded-full w-4 h-4 flex justify-center items-center overflow-hidden text-xs">
{option.icon ? (
<img src={option.icon} alt={option.label} className="w-full h-full" />
) : (
formatAgentName(option.label)
)}
</div>
{option.label}
</div>
))}
</Card>
)}
</div>
);
}