interface ComboBoxOption < T > { label : string ; target : T ; } interface ComboBoxGroupedOption < T > { label : string ; options : Array < ComboBoxOption < T > > ; } type GroupOrOption < T > = ComboBoxGroupedOption < T > | ComboBoxOption < T > ; export type ComboBoxGroupedOptions < T > = Array < GroupOrOption < T > > ; param objects param groupBy param labelName returns export function groupAndSortBy < T extends Record < TGroupBy | TLabelName , string > , TGroupBy extends string = 'type' , TLabelName extends string = 'title' > ( objects , groupBy , labelName ) { const groupedOptions = objects . reduce ( ( array , obj ) => { const group = array . find ( ( element ) => element . label === obj [ groupBy ] ) ; const option = { label : obj [ labelName ] , target : obj , } ; if ( group && group . options ) { group . options . push ( option ) ; } else { array . push ( { label : obj [ groupBy ] , options : [ option ] } ) ; } return array ; } , [ ] as Array < ComboBoxGroupedOption < T > > ) ; groupedOptions . sort ( sortByLabel ) ; groupedOptions . forEach ( ( group ) => { if ( Array . isArray ( group . options ) ) { group . options . sort ( sortByLabel ) ; } } ) ; if ( groupedOptions . length === 1 && ! groupedOptions [ 0 ] . label ) { return groupedOptions [ 0 ] . options || [ ] ; } return groupedOptions ; } function sortByLabel < T > ( a , b ) { return ( a . label || '' ) . toLowerCase ( ) . localeCompare ( ( b . label || '' ) . toLowerCase ( ) ) ; }