11import { useEffect , useMemo , type ComponentProps } from 'react' ;
2- import { Combobox , HStack , Portal , Span , Spinner , Skeleton } from '@chakra-ui/react' ;
2+ import { Combobox , HStack , Portal , Span , Spinner , Skeleton , Text , Button } from '@chakra-ui/react' ;
33import { useFilter , useListCollection } from '@chakra-ui/react' ;
44
55type Option = {
@@ -10,14 +10,15 @@ type Option = {
1010export interface ComboboxProps
1111 extends Omit <
1212 ComponentProps < typeof Combobox . Root > ,
13- 'onChange' | 'children' | 'value' | 'collection'
13+ 'onChange' | 'children' | 'value' | 'collection' | 'multiple'
1414 > {
1515 items : Option [ ] ;
16- selectedValue ?: string ;
17- onChange : ( value : string ) => void ;
16+ selectedValue ?: string | string [ ] ;
17+ onChange : ( value : string | string [ ] ) => void ;
1818 placeholder ?: string ;
1919 isLoading ?: boolean ;
2020 defaultToFirst ?: boolean ;
21+ isMulti ?: boolean ;
2122}
2223
2324export function ComboboxSelect ( {
@@ -27,11 +28,10 @@ export function ComboboxSelect({
2728 placeholder = 'Select an option' ,
2829 isLoading = false ,
2930 defaultToFirst = false ,
31+ isMulti = false ,
3032 ...rest
3133} : ComboboxProps ) {
32- const itemsWithAll = useMemo ( ( ) => {
33- return items . length > 0 && ! defaultToFirst ? [ { label : 'All' , value : '' } , ...items ] : items ;
34- } , [ items , defaultToFirst ] ) ;
34+ const itemsWithAll = useMemo ( ( ) => items , [ items ] ) ;
3535
3636 const { contains } = useFilter ( { sensitivity : 'base' } ) ;
3737 const { collection, set, filter } = useListCollection < Option > ( {
@@ -42,24 +42,24 @@ export function ComboboxSelect({
4242 } ) ;
4343
4444 useEffect ( ( ) => {
45- // Always update the collection
4645 set ( itemsWithAll ) ;
4746
48- // If defaultToFirst is true and nothing is selected yet, pick the first option
4947 if ( defaultToFirst && ! selectedValue && itemsWithAll . length > 0 ) {
50- onChange ( itemsWithAll [ 0 ] . value ) ;
48+ onChange ( isMulti ? [ itemsWithAll [ 0 ] . value ] : itemsWithAll [ 0 ] . value ) ;
5149 }
52- } , [ itemsWithAll , set , defaultToFirst , selectedValue , onChange ] ) ;
50+ } , [ itemsWithAll , set , defaultToFirst , selectedValue , onChange , isMulti ] ) ;
5351
5452 return (
5553 < Skeleton loading = { isLoading } w = 'full' >
5654 < Combobox . Root
5755 size = 'md'
5856 w = 'xs'
5957 collection = { collection }
60- value = { [ selectedValue ] }
58+ value = { Array . isArray ( selectedValue ) ? selectedValue : [ selectedValue ] }
59+ multiple = { isMulti }
60+ closeOnSelect = { ! isMulti }
6161 onValueChange = { e => {
62- const newValue = e . value [ 0 ] ?? '' ;
62+ const newValue = isMulti ? e . value : ( e . value [ 0 ] ?? '' ) ;
6363 onChange ( newValue ) ;
6464 } }
6565 onInputValueChange = { e => filter ( e . inputValue ) }
@@ -72,14 +72,17 @@ export function ComboboxSelect({
7272 < Combobox . Control >
7373 < Combobox . Input
7474 placeholder = { placeholder || 'Select an option' }
75- value = { collection . items . find ( item => item . value === selectedValue ) ?. label || '' }
75+ value = {
76+ isMulti
77+ ? collection . items
78+ . filter ( item => ( selectedValue as string [ ] ) . includes ( item . value ) )
79+ . map ( i => i . label )
80+ . join ( ', ' )
81+ : collection . items . find ( item => item . value === selectedValue ) ?. label || ''
82+ }
7683 />
7784 < Combobox . IndicatorGroup >
78- < Combobox . ClearTrigger
79- onClick = { ( ) => {
80- onChange ( '' ) ;
81- } }
82- />
85+ < Combobox . ClearTrigger onClick = { ( ) => onChange ( isMulti ? [ ] : '' ) } />
8386 < Combobox . Trigger />
8487 </ Combobox . IndicatorGroup >
8588 </ Combobox . Control >
@@ -94,14 +97,38 @@ export function ComboboxSelect({
9497 ) : collection . items . length === 0 ? (
9598 < Combobox . Empty > No options found</ Combobox . Empty >
9699 ) : (
97- collection . items . map ( item => (
98- < Combobox . Item key = { item . value } item = { item } >
99- < HStack justify = 'space-between' textStyle = 'sm' >
100- { item . label }
100+ < >
101+ { isMulti && (
102+ < HStack justify = 'flex-start' colorPalette = { 'blue' } gap = { 0 } >
103+ < Button
104+ size = '2xs'
105+ variant = 'plain'
106+ onClick = { ( ) => onChange ( collection . items . map ( i => i . value ) ) }
107+ textDecoration = 'underline'
108+ fontWeight = { 'light' }
109+ >
110+ Select All
111+ </ Button >
112+ < Button
113+ size = '2xs'
114+ variant = 'plain'
115+ onClick = { ( ) => onChange ( [ ] ) }
116+ textDecoration = 'underline'
117+ fontWeight = { 'light' }
118+ >
119+ < Text > Clear All</ Text >
120+ </ Button >
101121 </ HStack >
102- < Combobox . ItemIndicator />
103- </ Combobox . Item >
104- ) )
122+ ) }
123+ { collection . items . map ( item => (
124+ < Combobox . Item key = { item . value } item = { item } >
125+ < HStack justify = 'space-between' textStyle = 'sm' >
126+ { item . label }
127+ </ HStack >
128+ < Combobox . ItemIndicator />
129+ </ Combobox . Item >
130+ ) ) }
131+ </ >
105132 ) }
106133 </ Combobox . Content >
107134 </ Combobox . Positioner >
0 commit comments