Hey is a decentralized and permissionless social media app built with Lens Protocol 馃尶
at main 138 lines 4.9 kB view raw
1import { 2 Listbox, 3 ListboxButton, 4 ListboxOption, 5 ListboxOptions, 6 Transition 7} from "@headlessui/react"; 8import { MagnifyingGlassIcon } from "@heroicons/react/24/outline"; 9import { CheckCircleIcon, ChevronDownIcon } from "@heroicons/react/24/solid"; 10import { Fragment, memo, type ReactNode, useState } from "react"; 11import { Input } from "@/components/Shared/UI"; 12import cn from "@/helpers/cn"; 13 14interface SelectProps { 15 className?: string; 16 defaultValue?: string; 17 iconClassName?: string; 18 onChange: (value: any) => any; 19 options?: { 20 disabled?: boolean; 21 helper?: string; 22 icon?: string; 23 label: string; 24 htmlLabel?: ReactNode; 25 selected?: boolean; 26 value: number | string; 27 }[]; 28 showSearch?: boolean; 29} 30 31const Select = ({ 32 className, 33 defaultValue, 34 iconClassName, 35 onChange, 36 options, 37 showSearch = false 38}: SelectProps) => { 39 const [searchValue, setSearchValue] = useState(""); 40 const selected = options?.find((option) => option.selected) || options?.[0]; 41 42 return ( 43 <Listbox onChange={onChange} value={defaultValue || selected?.value}> 44 <div className="relative"> 45 <ListboxButton 46 className={cn( 47 "flex w-full items-center justify-between space-x-3 rounded-xl border border-gray-300 bg-white px-3 py-2 text-left outline-hidden focus:border-gray-500 focus:ring-gray-400 dark:border-gray-700 dark:bg-gray-800", 48 className 49 )} 50 > 51 <span className="flex items-center space-x-2"> 52 {selected?.icon && ( 53 <img 54 alt={selected?.label} 55 className={iconClassName} 56 src={selected?.icon} 57 /> 58 )} 59 <span>{selected?.htmlLabel || selected?.label}</span> 60 </span> 61 <ChevronDownIcon className="mr-1 size-5 text-gray-400" /> 62 </ListboxButton> 63 <Transition 64 as={Fragment} 65 enter="transition ease-out duration-100" 66 enterFrom="transform opacity-0 scale-95" 67 enterTo="transform opacity-100 scale-100" 68 leave="transition ease-in duration-75" 69 leaveFrom="transform opacity-100 scale-100" 70 leaveTo="transform opacity-0 scale-95" 71 > 72 <ListboxOptions className="no-scrollbar absolute z-[5] mt-2 max-h-60 w-full overflow-auto rounded-xl border border-gray-200 bg-white shadow-xs focus:outline-hidden dark:border-gray-700 dark:bg-gray-900"> 73 {showSearch ? ( 74 <div className="mx-4 mt-4"> 75 <Input 76 className="w-full" 77 iconLeft={<MagnifyingGlassIcon />} 78 onChange={(event) => { 79 setSearchValue(event.target.value); 80 }} 81 placeholder="Search" 82 type="text" 83 value={searchValue} 84 /> 85 </div> 86 ) : null} 87 {options 88 ?.filter((option) => 89 option.label.toLowerCase().includes(searchValue.toLowerCase()) 90 ) 91 .map((option, id) => ( 92 <ListboxOption 93 className={({ focus }: { focus: boolean }) => 94 cn( 95 { "dropdown-active": focus }, 96 "m-2 cursor-pointer rounded-lg" 97 ) 98 } 99 disabled={option.disabled} 100 key={id} 101 value={option.value} 102 > 103 {({ selected }) => ( 104 <div className="mx-2 flex flex-col space-y-0 py-1.5"> 105 <span className="flex w-full items-center justify-between space-x-3 text-gray-700 dark:text-gray-200"> 106 <span className="flex items-center space-x-2"> 107 {option.icon && ( 108 <img 109 alt={option.label} 110 className={iconClassName} 111 src={option.icon} 112 /> 113 )} 114 <span className="block truncate"> 115 {option.htmlLabel || option.label} 116 </span> 117 </span> 118 {selected ? ( 119 <CheckCircleIcon className="size-5" /> 120 ) : null} 121 </span> 122 {option.helper ? ( 123 <span className="text-gray-500 text-xs dark:text-gray-200"> 124 {option.helper} 125 </span> 126 ) : null} 127 </div> 128 )} 129 </ListboxOption> 130 ))} 131 </ListboxOptions> 132 </Transition> 133 </div> 134 </Listbox> 135 ); 136}; 137 138export default memo(Select);