@@ -10,7 +10,7 @@ import { usePopper } from "react-popper";
10
10
11
11
const { test } = fuzzy ;
12
12
13
- export type Option < T > = { label : string ; value : T } ;
13
+ export type Option < T > = { label : string ; value : T ; disabled ?: boolean } ;
14
14
15
15
export function Combobox < T > ( {
16
16
options,
@@ -53,7 +53,12 @@ export function Combobox<T>({
53
53
buttonProps ?: Omit < ButtonProps , "href" > ;
54
54
innerButtonClasses ?: string ;
55
55
allowCustomValue ?: boolean ;
56
- Option ?: React . ComponentType < { label : string ; value : T ; inButton : boolean } > ;
56
+ Option ?: React . ComponentType < {
57
+ label : string ;
58
+ value : T ;
59
+ inButton : boolean ;
60
+ disabled ?: boolean ;
61
+ } > ;
57
62
disabled ?: boolean ;
58
63
unknownLabel ?: ( value : T ) => string ;
59
64
processFilterOption ?: ( option : string ) => string ;
@@ -76,6 +81,10 @@ export function Combobox<T>({
76
81
77
82
const [ isOpen , setIsOpen ] = useState ( false ) ;
78
83
84
+ // Keeps track of the whether we should prevent the dropdown from closing
85
+ // This is used to prevent the dropdown from closing when clicking on a link or tooltip
86
+ const [ preventClose , setPreventClose ] = useState ( false ) ;
87
+
79
88
const { styles, attributes, update } = usePopper (
80
89
referenceElement ,
81
90
popperElement ,
@@ -123,14 +132,27 @@ export function Combobox<T>({
123
132
}
124
133
} , [ isOpen , update ] ) ;
125
134
135
+ // Reset preventClose after a short delay
136
+ useEffect ( ( ) => {
137
+ if ( preventClose ) {
138
+ const timer = setTimeout ( ( ) => {
139
+ setPreventClose ( false ) ;
140
+ } , 200 ) ;
141
+
142
+ return ( ) => clearTimeout ( timer ) ;
143
+ }
144
+ } , [ preventClose ] ) ;
145
+
126
146
return (
127
147
< HeadlessCombobox
128
148
value = {
129
149
options . find ( ( o ) => isEqual ( selectedOption , o . value ) ) ?. value || null
130
150
}
131
151
onChange = { ( option ) => {
132
- setSelectedOption ( option ) ;
133
- setQuery ( "" ) ;
152
+ if ( ! preventClose ) {
153
+ setSelectedOption ( option ) ;
154
+ setQuery ( "" ) ;
155
+ }
134
156
} }
135
157
disabled = { disabled }
136
158
>
@@ -146,7 +168,7 @@ export function Combobox<T>({
146
168
< >
147
169
< HeadlessCombobox . Label
148
170
hidden = { labelHidden }
149
- className = "text-left text-sm text-content-primary "
171
+ className = "text-content-primary text-left text-sm "
150
172
>
151
173
{ label }
152
174
</ HeadlessCombobox . Label >
@@ -179,6 +201,7 @@ export function Combobox<T>({
179
201
inButton
180
202
label = { selectedOptionData . label }
181
203
value = { selectedOptionData . value }
204
+ disabled = { selectedOptionData . disabled }
182
205
/>
183
206
) : (
184
207
selectedOptionData ?. label || (
@@ -228,7 +251,7 @@ export function Combobox<T>({
228
251
) }
229
252
< div className = "min-w-fit" >
230
253
{ ! disableSearch && (
231
- < div className = "sticky top-0 z-10 flex w-full items-center gap-2 border-b bg-background-secondary px-3 pt-1" >
254
+ < div className = "bg-background-secondary sticky top-0 z-10 flex w-full items-center gap-2 border-b px-3 pt-1" >
232
255
< MagnifyingGlassIcon className = "text-content-secondary" />
233
256
< HeadlessCombobox . Input
234
257
onChange = { ( event ) => setQuery ( event . target . value ) }
@@ -246,10 +269,13 @@ export function Combobox<T>({
246
269
< HeadlessCombobox . Option
247
270
key = { idx }
248
271
value = { option . value }
272
+ disabled = { option . disabled }
249
273
className = { ( { active } ) =>
250
274
cn (
251
275
"w-fit min-w-full relative cursor-pointer select-none py-1.5 px-3 text-content-primary" ,
252
276
active && "bg-background-tertiary" ,
277
+ option . disabled &&
278
+ "cursor-not-allowed text-content-secondary opacity-75" ,
253
279
)
254
280
}
255
281
>
@@ -259,6 +285,21 @@ export function Combobox<T>({
259
285
"block w-full whitespace-nowrap" ,
260
286
selected && "font-semibold" ,
261
287
) }
288
+ onPointerDownCapture = { ( e ) => {
289
+ // Stop propagation if clicking on a link or tooltip
290
+ if ( e . target instanceof HTMLElement ) {
291
+ const closest = e . target . closest (
292
+ 'a, [role="tooltip"]' ,
293
+ ) ;
294
+ if ( closest ) {
295
+ e . stopPropagation ( ) ;
296
+ e . preventDefault ( ) ;
297
+
298
+ // Set preventClose to true
299
+ setPreventClose ( true ) ;
300
+ }
301
+ }
302
+ } }
262
303
>
263
304
{ Option ? (
264
305
< Option
@@ -291,7 +332,7 @@ export function Combobox<T>({
291
332
) }
292
333
293
334
{ filtered . length === 0 && ! allowCustomValue && (
294
- < div className = "overflow-hidden text-ellipsis py-1 pl-4 text-content-primary " >
335
+ < div className = "text-content-primary overflow-hidden text-ellipsis py-1 pl-4" >
295
336
No options matching "{ query } ".
296
337
</ div >
297
338
) }
0 commit comments