@@ -10,6 +10,14 @@ import type {
10
10
} from "api/typesGenerated" ;
11
11
import { ErrorAlert } from "components/Alert/ErrorAlert" ;
12
12
import { Button } from "components/Button/Button" ;
13
+ import {
14
+ Command ,
15
+ CommandEmpty ,
16
+ CommandGroup ,
17
+ CommandInput ,
18
+ CommandItem ,
19
+ CommandList ,
20
+ } from "components/Command/Command" ;
13
21
import { ChooseOne , Cond } from "components/Conditionals/ChooseOne" ;
14
22
import {
15
23
Dialog ,
@@ -34,17 +42,16 @@ import {
34
42
type Option ,
35
43
} from "components/MultiSelectCombobox/MultiSelectCombobox" ;
36
44
import {
37
- Select ,
38
- SelectContent ,
39
- SelectItem ,
40
- SelectTrigger ,
41
- SelectValue ,
42
- } from "components/Select/Select" ;
45
+ Popover ,
46
+ PopoverContent ,
47
+ PopoverTrigger ,
48
+ } from "components/Popover/Popover" ;
43
49
import { Spinner } from "components/Spinner/Spinner" ;
44
50
import { Switch } from "components/Switch/Switch" ;
45
51
import { useFormik } from "formik" ;
46
- import { Plus , Trash } from "lucide-react" ;
47
- import { type FC , useId , useState } from "react" ;
52
+ import { Check , ChevronDown , CornerDownLeft , Plus , Trash } from "lucide-react" ;
53
+ import { type FC , type KeyboardEventHandler , useId , useState } from "react" ;
54
+ import { cn } from "utils/cn" ;
48
55
import { docs } from "utils/docs" ;
49
56
import { isUUID } from "utils/uuid" ;
50
57
import * as Yup from "yup" ;
@@ -102,11 +109,13 @@ export const IdpOrgSyncPageView: FC<IdpSyncPageViewProps> = ({
102
109
} ) ;
103
110
const [ coderOrgs , setCoderOrgs ] = useState < Option [ ] > ( [ ] ) ;
104
111
const [ idpOrgName , setIdpOrgName ] = useState ( "" ) ;
112
+ const [ inputValue , setInputValue ] = useState ( "" ) ;
105
113
const organizationMappingCount = form . values . mapping
106
114
? Object . entries ( form . values . mapping ) . length
107
115
: 0 ;
108
116
const [ isDialogOpen , setIsDialogOpen ] = useState ( false ) ;
109
117
const id = useId ( ) ;
118
+ const [ open , setOpen ] = useState ( false ) ;
110
119
111
120
const getOrgNames = ( orgIds : readonly string [ ] ) => {
112
121
return orgIds . map (
@@ -129,6 +138,19 @@ export const IdpOrgSyncPageView: FC<IdpSyncPageViewProps> = ({
129
138
form . handleSubmit ( ) ;
130
139
} ;
131
140
141
+ const handleKeyDown : KeyboardEventHandler < HTMLInputElement > = ( e ) => {
142
+ if (
143
+ e . key === "Enter" &&
144
+ inputValue &&
145
+ ! claimFieldValues ?. some ( ( value ) => value === inputValue . toLowerCase ( ) )
146
+ ) {
147
+ e . preventDefault ( ) ;
148
+ setIdpOrgName ( inputValue ) ;
149
+ setInputValue ( "" ) ;
150
+ setOpen ( false ) ;
151
+ }
152
+ } ;
153
+
132
154
return (
133
155
< div className = "flex flex-col gap-2" >
134
156
{ Boolean ( error ) && < ErrorAlert error = { error } /> }
@@ -204,21 +226,68 @@ export const IdpOrgSyncPageView: FC<IdpSyncPageViewProps> = ({
204
226
</ Label >
205
227
206
228
{ claimFieldValues ? (
207
- < Select
208
- onValueChange = { ( event ) => setIdpOrgName ( event ) }
209
- value = { idpOrgName }
210
- >
211
- < SelectTrigger id = { `${ id } -idp-org-name` } className = "w-72" >
212
- < SelectValue placeholder = "Select IdP organization" />
213
- </ SelectTrigger >
214
- < SelectContent className = "[&_*[role=option]>span]:end-2 [&_*[role=option]>span]:start-auto [&_*[role=option]]:pe-8 [&_*[role=option]]:ps-2" >
215
- { claimFieldValues . map ( ( value ) => (
216
- < SelectItem key = { value } value = { value } >
217
- { value }
218
- </ SelectItem >
219
- ) ) }
220
- </ SelectContent >
221
- </ Select >
229
+ < Popover open = { open } onOpenChange = { setOpen } >
230
+ < PopoverTrigger asChild >
231
+ < Button
232
+ variant = "outline"
233
+ aria-expanded = { open }
234
+ className = "w-72 justify-between"
235
+ >
236
+ < span
237
+ className = { cn (
238
+ ! idpOrgName && "text-content-secondary" ,
239
+ ) }
240
+ >
241
+ { idpOrgName || "Select IdP organization" }
242
+ </ span >
243
+ < ChevronDown className = "size-icon-sm cursor-pointer text-content-secondary hover:text-content-primary" />
244
+ </ Button >
245
+ </ PopoverTrigger >
246
+ < PopoverContent className = "w-72" >
247
+ < Command >
248
+ < CommandInput
249
+ placeholder = "Search or enter custom value"
250
+ value = { inputValue }
251
+ onValueChange = { setInputValue }
252
+ onKeyDown = { handleKeyDown }
253
+ />
254
+ < CommandList >
255
+ < CommandEmpty >
256
+ < p > No results found</ p >
257
+ < span className = "flex flex-row items-center justify-center gap-1" >
258
+ Enter custom value
259
+ < CornerDownLeft className = "size-icon-sm bg-surface-tertiary rounded-sm p-1" />
260
+ </ span >
261
+ </ CommandEmpty >
262
+ < CommandGroup >
263
+ { claimFieldValues . map ( ( value ) => (
264
+ < CommandItem
265
+ key = { value }
266
+ value = { value }
267
+ onSelect = { ( currentValue ) => {
268
+ setIdpOrgName (
269
+ currentValue === idpOrgName
270
+ ? ""
271
+ : currentValue ,
272
+ ) ;
273
+ setOpen ( false ) ;
274
+ } }
275
+ >
276
+ { value }
277
+ { idpOrgName === value && (
278
+ < Check
279
+ size = { 16 }
280
+ strokeWidth = { 2 }
281
+ className = "ml-auto"
282
+ />
283
+ ) }
284
+ </ CommandItem >
285
+ ) ) }
286
+ </ CommandGroup >
287
+ </ CommandList >
288
+ </ Command >
289
+ </ PopoverContent >
290
+ </ Popover >
222
291
) : (
223
292
< Input
224
293
id = { `${ id } -idp-org-name` }
0 commit comments