@@ -21,7 +21,7 @@ type Option = {
21
21
value : string ;
22
22
} ;
23
23
24
- type ComboboxProps = {
24
+ type ComboboxMultiProps = {
25
25
options : Array < Option > ;
26
26
placeholderText : string ;
27
27
noOptionsText : string ;
@@ -30,7 +30,7 @@ type ComboboxProps = {
30
30
31
31
export const ComboboxMulti = React . forwardRef <
32
32
React . ElementRef < typeof PopoverPrimitive . Content > ,
33
- React . ComponentPropsWithoutRef < typeof PopoverPrimitive . Content > & ComboboxProps
33
+ React . ComponentPropsWithoutRef < typeof PopoverPrimitive . Content > & ComboboxMultiProps
34
34
> ( ( { className, options, placeholderText, noOptionsText, setValuesCallback, ...props } , ref ) => {
35
35
const [ open , setOpen ] = React . useState ( false ) ;
36
36
const [ selectedValues , setSelectedValues ] = React . useState < Array < string > > ( [ ] ) ;
@@ -100,3 +100,98 @@ export const ComboboxMulti = React.forwardRef<
100
100
</ Popover >
101
101
) ;
102
102
} ) ;
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