Skip to content

Commit b1af4b9

Browse files
committed
fix: replace dropdown with combobox
1 parent 6cb7b7e commit b1af4b9

File tree

1 file changed

+92
-23
lines changed

1 file changed

+92
-23
lines changed

site/src/pages/DeploymentSettingsPage/IdpOrgSyncPage/IdpOrgSyncPageView.tsx

Lines changed: 92 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,14 @@ import type {
1010
} from "api/typesGenerated";
1111
import { ErrorAlert } from "components/Alert/ErrorAlert";
1212
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";
1321
import { ChooseOne, Cond } from "components/Conditionals/ChooseOne";
1422
import {
1523
Dialog,
@@ -34,17 +42,16 @@ import {
3442
type Option,
3543
} from "components/MultiSelectCombobox/MultiSelectCombobox";
3644
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";
4349
import { Spinner } from "components/Spinner/Spinner";
4450
import { Switch } from "components/Switch/Switch";
4551
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";
4855
import { docs } from "utils/docs";
4956
import { isUUID } from "utils/uuid";
5057
import * as Yup from "yup";
@@ -102,11 +109,13 @@ export const IdpOrgSyncPageView: FC<IdpSyncPageViewProps> = ({
102109
});
103110
const [coderOrgs, setCoderOrgs] = useState<Option[]>([]);
104111
const [idpOrgName, setIdpOrgName] = useState("");
112+
const [inputValue, setInputValue] = useState("");
105113
const organizationMappingCount = form.values.mapping
106114
? Object.entries(form.values.mapping).length
107115
: 0;
108116
const [isDialogOpen, setIsDialogOpen] = useState(false);
109117
const id = useId();
118+
const [open, setOpen] = useState(false);
110119

111120
const getOrgNames = (orgIds: readonly string[]) => {
112121
return orgIds.map(
@@ -129,6 +138,19 @@ export const IdpOrgSyncPageView: FC<IdpSyncPageViewProps> = ({
129138
form.handleSubmit();
130139
};
131140

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+
132154
return (
133155
<div className="flex flex-col gap-2">
134156
{Boolean(error) && <ErrorAlert error={error} />}
@@ -204,21 +226,68 @@ export const IdpOrgSyncPageView: FC<IdpSyncPageViewProps> = ({
204226
</Label>
205227

206228
{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>
222291
) : (
223292
<Input
224293
id={`${id}-idp-org-name`}

0 commit comments

Comments
 (0)