11import {
2+ Button ,
3+ Input ,
4+ InputOnChangeData ,
25 Menu ,
36 MenuButton ,
47 MenuDivider ,
@@ -12,8 +15,10 @@ import {
1215 BuildingMultipleFilled ,
1316 BuildingMultipleRegular ,
1417 bundleIcon ,
18+ Dismiss16Regular ,
19+ Search16Regular ,
1520} from "@fluentui/react-icons" ;
16- import React from "react" ;
21+ import React , { useCallback , useRef , useState } from "react" ;
1722import { OrganizationMenuProps } from "./organization-menu.types" ;
1823import { useStyles } from "./organization.styles" ;
1924
@@ -22,19 +27,49 @@ const OrganizationIcon = bundleIcon(
2227 BuildingMultipleRegular
2328) ;
2429
25- export const OrganizationMenu = (
26- { customContent, onChange, options, value } : OrganizationMenuProps
27- ) => {
30+ export const OrganizationMenu = ( {
31+ customContent,
32+ onChange,
33+ options,
34+ value,
35+ filter,
36+ } : OrganizationMenuProps ) => {
2837 const styles = useStyles ( ) ;
2938
39+ const [ filterText , setFilterText ] = useState < string > ( "" ) ;
40+ const filterRef = useRef < HTMLInputElement > ( null ) ;
41+
3042 const currentOrganization = options ?. find ( ( { id } ) => id === value ) ;
3143 const checkedValues = { org : [ value ] } ;
3244 const noDropDownContent = options ?. length === 1 && ! customContent
3345 && currentOrganization ;
3446 const onlyCustomContent = ! options ?. length && ! ! customContent ;
3547
48+ const filteredOptions = options
49+ ?. filter (
50+ ( opt ) =>
51+ filterText . length === 0
52+ || opt . label . toLowerCase ( ) . indexOf ( filterText ) >= 0
53+ )
54+ . sort (
55+ ( a , b ) =>
56+ a . label . toLowerCase ( ) . indexOf ( filterText )
57+ - b . label . toLowerCase ( ) . indexOf ( filterText )
58+ ) ;
59+
60+ const onFilterChange = useCallback (
61+ ( _ : React . ChangeEvent < HTMLInputElement > , data : InputOnChangeData ) => {
62+ if ( data . value . length ) {
63+ setFilterText ( data . value . toLowerCase ( ) ) ;
64+ } else {
65+ setFilterText ( "" ) ;
66+ }
67+ } ,
68+ [ ]
69+ ) ;
70+
3671 return (
37- < Menu checkedValues = { checkedValues } >
72+ < Menu checkedValues = { checkedValues } positioning = { "below-end" } >
3873 < MenuTrigger >
3974 < MenuButton
4075 appearance = "subtle"
@@ -45,17 +80,49 @@ export const OrganizationMenu = (
4580 data-testid = "organization-menu-button"
4681 icon = { < OrganizationIcon /> }
4782 menuIcon = { noDropDownContent ? null : undefined }
83+ onClick = { ( ) => setFilterText ( "" ) }
4884 >
4985 < span className = { styles . organizationlabel } >
5086 { currentOrganization ?. label ?? value }
5187 </ span >
5288 </ MenuButton >
5389 </ MenuTrigger >
5490 < MenuPopover >
91+ { filter ?. showFilter && (
92+ < >
93+ < Input
94+ ref = { filterRef }
95+ contentBefore = { < Search16Regular /> }
96+ placeholder = { filter . placeholderText }
97+ contentAfter = { filterText . length
98+ ? (
99+ < Button
100+ icon = { < Dismiss16Regular /> }
101+ appearance = "transparent"
102+ onClick = { ( ) => {
103+ setFilterText ( "" ) ;
104+ filterRef . current ?. focus ( ) ;
105+ } }
106+ />
107+ )
108+ : undefined }
109+ // To not get focus in search on open
110+ tabIndex = { - 1 }
111+ // To keep focus on search when hovering menu items
112+ onBlur = { ( ) => filterRef . current ?. focus ( ) }
113+ className = { styles . searchInput }
114+ appearance = "filled-lighter"
115+ value = { filterText }
116+ onChange = { onFilterChange }
117+ />
118+ < MenuDivider />
119+ </ >
120+ ) }
55121 < MenuList >
56122 { ! onlyCustomContent && (
57123 < div className = { styles . organizationSelection } >
58- { options ?. map ( ( { id, label } ) => {
124+ { filteredOptions ?. map ( ( { id, label } ) => {
125+ const match = label . toLowerCase ( ) . indexOf ( filterText ) ;
59126 return (
60127 < MenuItemRadio
61128 data-testid = { `organization-menu-item-${ id } ` }
@@ -65,7 +132,15 @@ export const OrganizationMenu = (
65132 onClick = { ( ) => onChange ( id ) }
66133 value = { id }
67134 >
68- { label }
135+ {
136+ < span >
137+ { label . substring ( 0 , match ) }
138+ < span className = { styles . bold } >
139+ { label . substring ( match , match + filterText . length ) }
140+ </ span >
141+ { label . substring ( match + filterText . length ) }
142+ </ span >
143+ }
69144 </ MenuItemRadio >
70145 ) ;
71146 } ) }
0 commit comments