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)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy