-
Notifications
You must be signed in to change notification settings - Fork 948
feat: add search to parameter dropdowns #18729
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
e012a2f
87fbd4a
a484433
b9037c2
3773179
5c96d66
e0cdebf
489068d
06e0a7b
01591de
557f8f7
4f87578
05b0ab5
1363438
39beb91
58fc0b7
ca089c0
d34ffc0
cb90821
30d528e
a902ea4
2ec4b7f
0a89a34
78e0264
d27ead2
7403512
d562495
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,3 +1,4 @@ | ||
import { Avatar } from "components/Avatar/Avatar"; | ||
import { Button } from "components/Button/Button"; | ||
import { | ||
Command, | ||
|
@@ -12,22 +13,36 @@ import { | |
PopoverContent, | ||
PopoverTrigger, | ||
} from "components/Popover/Popover"; | ||
import { | ||
Tooltip, | ||
TooltipContent, | ||
TooltipProvider, | ||
TooltipTrigger, | ||
} from "components/Tooltip/Tooltip"; | ||
import { Check, ChevronDown, CornerDownLeft } from "lucide-react"; | ||
import type { FC, KeyboardEventHandler } from "react"; | ||
import { Info } from "lucide-react"; | ||
import { type FC, type KeyboardEventHandler, useState } from "react"; | ||
import { cn } from "utils/cn"; | ||
|
||
interface ComboboxProps { | ||
value: string; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I know this was here originally, but I can't help but feel like There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think |
||
options?: readonly string[]; | ||
options?: Readonly<Array<string | ComboboxOption>>; | ||
placeholder?: string; | ||
open: boolean; | ||
onOpenChange: (open: boolean) => void; | ||
inputValue: string; | ||
onInputChange: (value: string) => void; | ||
open?: boolean; | ||
onOpenChange?: (open: boolean) => void; | ||
inputValue?: string; | ||
onInputChange?: (value: string) => void; | ||
onKeyDown?: KeyboardEventHandler<HTMLInputElement>; | ||
onSelect: (value: string) => void; | ||
} | ||
|
||
type ComboboxOption = { | ||
icon?: string; | ||
displayName: string; | ||
value: string; | ||
description?: string; | ||
}; | ||
|
||
export const Combobox: FC<ComboboxProps> = ({ | ||
value, | ||
options = [], | ||
|
@@ -39,16 +54,37 @@ export const Combobox: FC<ComboboxProps> = ({ | |
onKeyDown, | ||
onSelect, | ||
}) => { | ||
const [managedOpen, setManagedOpen] = useState(false); | ||
const [managedInputValue, setManagedInputValue] = useState(""); | ||
|
||
const optionsMap = new Map<string, ComboboxOption>( | ||
options.map((option) => | ||
typeof option === "string" | ||
? [option, { displayName: option, value: option }] | ||
: [option.value, option], | ||
), | ||
); | ||
const optionObjects = [...optionsMap.values()]; | ||
const showIcons = optionObjects.some((it) => it.icon); | ||
|
||
const isOpen = open ?? managedOpen; | ||
|
||
return ( | ||
<Popover open={open} onOpenChange={onOpenChange}> | ||
<Popover | ||
open={isOpen} | ||
onOpenChange={(newOpen) => { | ||
setManagedOpen(newOpen); | ||
onOpenChange?.(newOpen); | ||
}} | ||
> | ||
<PopoverTrigger asChild> | ||
<Button | ||
variant="outline" | ||
aria-expanded={open} | ||
aria-expanded={isOpen} | ||
className="w-72 justify-between group" | ||
> | ||
<span className={cn(!value && "text-content-secondary")}> | ||
{value || placeholder} | ||
{optionsMap.get(value)?.displayName || value || placeholder} | ||
</span> | ||
<ChevronDown className="size-icon-sm text-content-secondary group-hover:text-content-primary" /> | ||
</Button> | ||
|
@@ -57,8 +93,11 @@ export const Combobox: FC<ComboboxProps> = ({ | |
<Command> | ||
<CommandInput | ||
placeholder="Search or enter custom value" | ||
value={inputValue} | ||
onValueChange={onInputChange} | ||
value={inputValue ?? managedInputValue} | ||
onValueChange={(newValue) => { | ||
setManagedInputValue(newValue); | ||
onInputChange?.(newValue); | ||
}} | ||
onKeyDown={onKeyDown} | ||
/> | ||
<CommandList> | ||
|
@@ -70,18 +109,40 @@ export const Combobox: FC<ComboboxProps> = ({ | |
</span> | ||
</CommandEmpty> | ||
<CommandGroup> | ||
{options.map((option) => ( | ||
{optionObjects.map((option) => ( | ||
<CommandItem | ||
key={option} | ||
value={option} | ||
key={option.value} | ||
value={option.value} | ||
keywords={[option.displayName]} | ||
onSelect={(currentValue) => { | ||
onSelect(currentValue === value ? "" : currentValue); | ||
}} | ||
> | ||
{option} | ||
{value === option && ( | ||
<Check className="size-icon-sm ml-auto" /> | ||
{showIcons && ( | ||
<Avatar | ||
size="sm" | ||
src={option.icon} | ||
fallback={option.value} | ||
/> | ||
)} | ||
{option.displayName} | ||
<div className="flex flex-row items-center ml-auto gap-1"> | ||
{value === option.value && ( | ||
<Check className="size-icon-sm" /> | ||
)} | ||
{option.description && ( | ||
<TooltipProvider delayDuration={100}> | ||
<Tooltip> | ||
<TooltipTrigger asChild> | ||
<Info className="w-3.5 h-3.5 text-content-secondary" /> | ||
</TooltipTrigger> | ||
<TooltipContent side="right" sideOffset={10}> | ||
{option.description} | ||
</TooltipContent> | ||
</Tooltip> | ||
</TooltipProvider> | ||
)} | ||
</div> | ||
</CommandItem> | ||
))} | ||
</CommandGroup> | ||
|
Uh oh!
There was an error while loading. Please reload this page.