@@ -21,7 +21,7 @@ type Option = {
2121 value : string ;
2222} ;
2323
24- type ComboboxProps = {
24+ type ComboboxMultiProps = {
2525 options : Array < Option > ;
2626 placeholderText : string ;
2727 noOptionsText : string ;
@@ -30,7 +30,7 @@ type ComboboxProps = {
3030
3131export const ComboboxMulti = React . forwardRef <
3232 React . ElementRef < typeof PopoverPrimitive . Content > ,
33- React . ComponentPropsWithoutRef < typeof PopoverPrimitive . Content > & ComboboxProps
33+ React . ComponentPropsWithoutRef < typeof PopoverPrimitive . Content > & ComboboxMultiProps
3434> ( ( { className, options, placeholderText, noOptionsText, setValuesCallback, ...props } , ref ) => {
3535 const [ open , setOpen ] = React . useState ( false ) ;
3636 const [ selectedValues , setSelectedValues ] = React . useState < Array < string > > ( [ ] ) ;
@@ -100,3 +100,98 @@ export const ComboboxMulti = React.forwardRef<
100100 </ Popover >
101101 ) ;
102102} ) ;
103+
104+ type ComboboxExternalProps = {
105+ options : Array < {
106+ value : string ;
107+ label : string ;
108+ } > ;
109+ itemName : string ;
110+ chosenOptions : Array < string > ;
111+ setChosenOptions : ( values : Array < string > ) => void ;
112+ } ;
113+
114+ export const ComboboxExternal = React . forwardRef <
115+ React . ElementRef < typeof PopoverPrimitive . Content > ,
116+ React . ComponentPropsWithoutRef < typeof PopoverPrimitive . Content > & ComboboxExternalProps
117+ > ( ( { className, itemName, options, chosenOptions, setChosenOptions, ...props } , ref ) => {
118+ const [ isOpen , setIsOpen ] = React . useState ( false ) ;
119+ const [ inputVal , setInputVal ] = React . useState ( '' ) ;
120+
121+ const onAddNewOption = ( ) => {
122+ const opt = inputVal . replace ( / ^ [ a - z A - Z ] / , ( c ) => c . toUpperCase ( ) ) ;
123+
124+ if ( ! chosenOptions . includes ( opt ) ) {
125+ setChosenOptions ( [ ...chosenOptions , opt ] ) ;
126+ }
127+
128+ setInputVal ( '' ) ;
129+ setIsOpen ( false ) ;
130+ } ;
131+
132+ return (
133+ < Popover open = { isOpen } onOpenChange = { setIsOpen } >
134+ < PopoverTrigger asChild >
135+ < Button
136+ variant = 'outline'
137+ role = 'combobox'
138+ aria-expanded = { isOpen }
139+ className = 'w-[200px] justify-between'
140+ >
141+ < span > Select { itemName } ...</ span >
142+ < ChevronsUpDown className = 'opacity-50' />
143+ </ Button >
144+ </ PopoverTrigger >
145+ < PopoverContent ref = { ref } { ...props } className = { cn ( 'w-[200px] p-0' , className ) } >
146+ < Command >
147+ < CommandInput
148+ value = { inputVal }
149+ onValueChange = { setInputVal }
150+ placeholder = { `Search or add ${ itemName } ...` }
151+ className = 'h-9'
152+ onKeyDown = { ( event ) => {
153+ if ( event . key === 'Enter' ) {
154+ event . preventDefault ( ) ;
155+ onAddNewOption ( ) ;
156+ }
157+ } }
158+ />
159+ < CommandList >
160+ < CommandEmpty
161+ className = 'hover:bg-secondary hover:text-secondary-foreground p-3 text-sm hover:cursor-pointer'
162+ onClick = { onAddNewOption }
163+ >
164+ Add { `"${ inputVal } "` }
165+ </ CommandEmpty >
166+ < CommandGroup >
167+ { options . map ( ( option ) => (
168+ < CommandItem
169+ className = 'hover:cursor-pointer'
170+ key = { option . value }
171+ value = { option . value }
172+ onSelect = { ( currentValue ) => {
173+ if ( chosenOptions . includes ( currentValue ) ) {
174+ setChosenOptions ( chosenOptions . filter ( ( v ) => v !== currentValue ) ) ;
175+ } else {
176+ setChosenOptions ( [ ...chosenOptions , currentValue ] ) ;
177+ }
178+
179+ setIsOpen ( false ) ;
180+ } }
181+ >
182+ { option . label }
183+ < Check
184+ className = { cn (
185+ 'ml-auto' ,
186+ chosenOptions . includes ( option . value ) ? 'opacity-100' : 'opacity-0'
187+ ) }
188+ />
189+ </ CommandItem >
190+ ) ) }
191+ </ CommandGroup >
192+ </ CommandList >
193+ </ Command >
194+ </ PopoverContent >
195+ </ Popover >
196+ ) ;
197+ } ) ;
0 commit comments