Created
February 17, 2025 04:43
-
-
Save chirag-chhajed/9da8a4485f9ffe49a7ff8502391c9eeb to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| import { useEffect, useState } from 'react' | |
| import { LuCheck, LuChevronDown, LuLoaderCircle } from 'react-icons/lu' | |
| import { cn } from '~/utils/tailwind' | |
| import { | |
| Command, | |
| CommandEmpty, | |
| CommandGroup, | |
| CommandInput, | |
| CommandItem, | |
| CommandList, | |
| } from '~/components/ui/command' | |
| import { | |
| Popover, | |
| PopoverContent, | |
| PopoverTrigger, | |
| } from '~/components/ui/popover' | |
| type Option = { | |
| value: string | |
| label: string | |
| } | |
| type SearchableSelectProps = { | |
| placeholder?: string | |
| onSelect: (value: string) => void | |
| options: Option[] | |
| loading?: boolean | |
| defaultValue?: string | |
| } | |
| export function SearchableSelect({ | |
| placeholder = 'Select an option', | |
| onSelect, | |
| options, | |
| loading = false, | |
| defaultValue, | |
| }: SearchableSelectProps) { | |
| const [open, setOpen] = useState(false) | |
| const [value, setValue] = useState('') | |
| useEffect(() => { | |
| if (defaultValue !== undefined) { | |
| setValue(defaultValue) | |
| } | |
| }, [defaultValue]) | |
| return ( | |
| <Popover open={open} onOpenChange={setOpen}> | |
| <PopoverTrigger asChild> | |
| <button | |
| role="combobox" | |
| aria-expanded={open} | |
| className="w-[225px] h-9 px-4 py-2 inline-flex items-center gap-2 whitespace-nowrap rounded-md text-sm transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-neutral-950 disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0 justify-between hover:bg-neutral-100 hover:text-neutral-900 font-semibold text-gray-8000" | |
| disabled={loading} | |
| type="button" | |
| > | |
| <span className="flex-1 text-left overflow-hidden text-ellipsis whitespace-nowrap"> | |
| {loading ? ( | |
| <span className="text-gray-500">{placeholder}</span> | |
| ) : value ? ( | |
| options.find((option) => option.value === value)?.label | |
| ) : ( | |
| <span className="text-gray-500">{placeholder}</span> | |
| )} | |
| </span> | |
| {loading ? ( | |
| <LuLoaderCircle className="ml-2 h-4 w-4 animate-spin" /> | |
| ) : ( | |
| <LuChevronDown | |
| className={cn( | |
| 'ml-2 h-4 w-4 shrink-0 transition-transform duration-200', | |
| open && 'rotate-180' | |
| )} | |
| /> | |
| )} | |
| </button> | |
| </PopoverTrigger> | |
| <PopoverContent className="w-[225px] p-0"> | |
| <Command> | |
| <CommandInput | |
| className="focus:outline-none focus:ring-0 focus:border-none " | |
| placeholder="Search options..." | |
| /> | |
| <CommandList> | |
| <CommandEmpty>No option found.</CommandEmpty> | |
| <CommandGroup> | |
| {options.map((option) => ( | |
| <CommandItem | |
| key={option.value} | |
| onSelect={(currentValue) => { | |
| setValue(currentValue === value ? '' : option.value) | |
| onSelect(currentValue === value ? '' : option.value) | |
| setOpen(false) | |
| }} | |
| className="font-semibold text-gray-8000" | |
| > | |
| <LuCheck | |
| className={cn( | |
| 'mr-2 h-4 w-4', | |
| value === option.value ? 'opacity-100' : 'opacity-0' | |
| )} | |
| /> | |
| {option.label} | |
| </CommandItem> | |
| ))} | |
| </CommandGroup> | |
| </CommandList> | |
| </Command> | |
| </PopoverContent> | |
| </Popover> | |
| ) | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment