Skip to content

Instantly share code, notes, and snippets.

@chirag-chhajed
Created February 17, 2025 04:43
Show Gist options
  • Save chirag-chhajed/9da8a4485f9ffe49a7ff8502391c9eeb to your computer and use it in GitHub Desktop.
Save chirag-chhajed/9da8a4485f9ffe49a7ff8502391c9eeb to your computer and use it in GitHub Desktop.
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