import React , { useRef } from 'react' ; import ReactSelect , { components , Props as SelectProps , OptionsType , GroupedOptionsType , } from 'react-select' ; import styled , { css } from 'styled-components' ; import { variables } from '../../../config' ; import { getStateColor , useTheme } from '../../../utils' ; import { InputVariant , InputState , SuiteThemeColors } from '../../../support/types' ; const selectStyle = ( isSearchable , withDropdownIndicator = true , variant , hideTextCursor , isClean , minWidth , borderRadius , borderWidth , theme , selectState ? ) => ( { singleValue : ( base ) => ( { ... base , display : 'flex' , alignItems : 'center' , width : '100%' , maxWidth : 'initial' , margin : 0 , padding : '0 8px' , color : isClean ? theme . TYPE_LIGHT_GREY : theme . TYPE_DARK_GREY , fontSize : variables . NEUE_FONT_SIZE . SMALL , fontWeight : variables . FONT_WEIGHT . MEDIUM , borderStyle : 'none' , justifyContent : isClean ? 'flex-end' : 'flex-start' , '&:hover' : { cursor : hideTextCursor || ! isSearchable ? 'pointer' : 'text' , } , } ) , control : ( base , { isDisabled , isFocused } ) => { let height = variant === 'small' ? '36px' : '48px' ; const borderColor = selectState ? getStateColor ( selectState , theme ) : theme . STROKE_GREY ; if ( isClean ) height = '22px' ; return { ... base , display : 'flex' , alignItems : 'center' , fontSize : variables . FONT_SIZE . SMALL , height , borderRadius : `${ borderRadius }px` , borderWidth : `${ borderWidth }px` , borderColor , borderStyle : isClean ? 'none' : 'solid' , backgroundColor : 'transparent' , boxShadow : 'none' , '&:hover, &:focus' : { cursor : 'pointer' , borderRadius : `${ borderRadius }px` , borderWidth : `${ borderWidth }px` , borderColor , } , } ; } , valueContainer : ( base ) => ( { ... base , border : 0 , padding : isClean ? '0 3px 0 0' : '2px 8px' , fontWeight : isClean ? variables . FONT_WEIGHT . MEDIUM : variables . FONT_WEIGHT . REGULAR , minWidth , display : 'flex' , flexWrap : 'nowrap' , justifyContent : isClean ? 'flex-end' : 'flex-start' , } as const ) , indicatorSeparator : ( ) => ( { display : 'none' , } ) , dropdownIndicator : ( base , { isDisabled } ) => ( { ... base , display : ! withDropdownIndicator || isDisabled ? 'none' : 'flex' , alignItems : 'center' , color : isClean ? theme . TYPE_LIGHTER_GREY : theme . TYPE_LIGHT_GREY , cursor : 'pointer' , path : '' , padding : isClean ? 0 : '8px' , '&:hover' : { color : isClean ? theme . TYPE_LIGHT_GREY : theme . TYPE_DARK_GREY , } , } ) , menu : ( base ) => ( { ... base , background : theme . BG_WHITE_ALT , margin : '5px 0' , boxShadow : `box-shadow: 0 4px 10px 0 ${ theme . BOX_SHADOW_BLACK_20 }` , zIndex : 9 , } ) , menuList : ( base ) => ( { ... base , boxShadow : '0 1px 2px 0 rgba(0, 0, 0, 0.2)' , background : theme . BG_WHITE_ALT , borderRadius : '4px' , padding : '8px' , } ) , groupHeading : ( base ) => ( { ... base , fontSize : variables . NEUE_FONT_SIZE . TINY , textTransform : 'initial' , margin : 0 , padding : '8px' , } ) , group : ( base ) => ( { ... base , padding : 0 , '& + &' : { borderTop : `1px solid ${ theme . BG_WHITE_ALT_HOVER }` , paddingTop : '4px' , marginTop : '4px' , } , } ) , option : ( base , { isFocused } ) => ( { ... base , color : theme . TYPE_DARK_GREY , background : isFocused ? theme . BG_WHITE_ALT_HOVER : theme . BG_WHITE_ALT , borderRadius : '5px' , padding : '8px' , fontSize : variables . NEUE_FONT_SIZE . SMALL , fontWeight : variables . FONT_WEIGHT . MEDIUM , '&:hover' : { cursor : 'pointer' , background : theme . BG_WHITE_ALT_HOVER , } , '&:active' : { background : theme . BG_WHITE_ALT_HOVER , } , } ) , input : ( base ) => ( { ... base , fontSize : variables . NEUE_FONT_SIZE . NORMAL , color : hideTextCursor ? 'transparent' : theme . TYPE_DARK_GREY , '& input' : { textShadow : hideTextCursor ? `0 0 0 ${ theme . TYPE_DARK_GREY } !important` : 'none' , } , } ) , placeholder : ( base ) => ( { ... base , fontWeight : variables . FONT_WEIGHT . MEDIUM , fontSize : variables . NEUE_FONT_SIZE . SMALL , padding : '0 6px' , } ) , } ) ; const Wrapper = styled . div < Props > `
    width: ${ props => ( props . width ? `${ props . width }px` : '100%' ) };

    ${ props => ! props . isClean && css `
            display: flex;
            flex-direction: column;
            justify-content: flex-start;
        ` }

    .react-select__single-value {
        position: static;
        transform: none;
    }
` ; const Label = styled . span `
    min-height: 32px;
    font-size: ${ variables . FONT_SIZE . NORMAL };
    font-weight: ${ variables . FONT_WEIGHT . MEDIUM };
` ; const BottomText = styled . div < Props > `
    display: flex;
    font-size: ${ variables . FONT_SIZE . TINY };
    color: ${ props => getStateColor ( props . state , props . theme ) };
    padding: 10px 10px 0 10px;
    min-height: 27px;
` ; interface Option { value : string ; label : string ; } /** Custom Type Guards to check if options are grouped or not */ const isOptionGrouped = ( x ) : x is GroupedOptionsType < Option > => ( x as GroupedOptionsType < Option > ) [ 0 ] ?. options !== undefined ; interface CommonProps extends Omit < SelectProps , 'components' | 'isSearchable' > { withDropdownIndicator ? : boolean ; isClean ? : boolean ; label ? : React . ReactNode ; wrapperProps ? : Record < string , any > ; variant ? : InputVariant ; noTopLabel ? : boolean ; noError ? : boolean ; bottomText ? : React . ReactNode ; hideTextCursor ? : boolean ; minWidth ? : string ; borderWidth ? : number ; borderRadius ? : number ; state ? : InputState ; } type KeyPressScrollProps = { useKeyPressScroll : true ; isSearchable ? : never ; } | { useKeyPressScroll ? : false ; isSearchable ? : boolean ; } ; type Props = CommonProps & KeyPressScrollProps ; const Select = ( { hideTextCursor = false , withDropdownIndicator = true , className , wrapperProps , isClean = false , label , width , variant = 'large' , noTopLabel = false , noError = true , bottomText , useKeyPressScroll , isSearchable = false , minWidth = 'initial' , borderWidth = 2 , borderRadius = 4 , state , ... props } ) => { const selectRef = useRef ( null ) ; const theme = useTheme ( ) ; const lastKeyPressTimestamp = useRef ( 0 ) ; const searchedTerm = useRef ( '' ) ; const Control = ( controlProps ) => ( < components . Control { ... controlProps } innerProps = { { ... controlProps . innerProps , 'data-test' : `${ props [ 'data-test' ] }/input` , } } />); const Option = ( optionProps ) => ( < components . Option { ... optionProps } innerProps = { { ... optionProps . innerProps , 'data-test' : `${ props [ 'data-test' ] }/option/${ optionProps . value }` , } } />); const GroupHeading = ( groupHeadingProps ) => groupHeadingProps ?. data ?. label ? < components . GroupHeading { ... groupHeadingProps } / > : null ; const findOption = ( options , query ) => { let foundOption ; let lowestIndexOfFirstOccurrence = Infinity ; for ( let i = 0 ; i < options . length ; i ++ ) { const indexOfFirstOccurrence = ( options [ i ] . label || '' ) . toLowerCase ( ) . indexOf ( query . toLowerCase ( ) ) ; if ( indexOfFirstOccurrence >= 0 && indexOfFirstOccurrence < lowestIndexOfFirstOccurrence ) { lowestIndexOfFirstOccurrence = indexOfFirstOccurrence ; foundOption = options [ i ] ; } } return foundOption ; } ; const scrollToOption = ( option ) => { if ( selectRef . current ) { selectRef . current . select . scrollToFocusedOptionOnUpdate = true ; selectRef . current . select . setState ( { focusedValue : null , focusedOption : option , } ) ; } } ; const onKeyDown = async ( event ) => { if ( useKeyPressScroll ) { const charValue = event . key ; const currentTimestamp = new Date ( ) . getTime ( ) ; const timeSincePreviousKeyPress = currentTimestamp - lastKeyPressTimestamp . current ; lastKeyPressTimestamp . current = currentTimestamp ; if ( timeSincePreviousKeyPress > 800 ) { searchedTerm . current = charValue ; } else { searchedTerm . current += charValue ; } if ( selectRef . current ) { const { options } = selectRef . current . select . props ; if ( options && options . length > 1 ) { let optionsToSearchThrough = [ ] ; if ( isOptionGrouped ( options ) ) { options . forEach ( o => { optionsToSearchThrough = optionsToSearchThrough . concat ( o . options ) ; } ) ; } else { optionsToSearchThrough = options ; } const optionToFocusOn = findOption ( optionsToSearchThrough , searchedTerm . current ) ; const lastOption = optionsToSearchThrough [ optionsToSearchThrough . length - 1 ] ; if ( optionToFocusOn && lastOption ) { await scrollToOption ( lastOption ) ; scrollToOption ( optionToFocusOn ) ; } } } } } ; return ( < Wrapper className = { className } width = { width } isClean = { isClean } { ... wrapperProps } > { ! noTopLabel && < Label > { label } < /Label>} < ReactSelect ref = { selectRef } onKeyDown = { onKeyDown } classNamePrefix = "react-select" styles = { selectStyle ( isSearchable , withDropdownIndicator , variant , hideTextCursor , isClean , minWidth , borderRadius , borderWidth , theme , state ) } isSearchable = { isSearchable } { ... props } components = { { Control , Option , GroupHeading , ... props . components } } /> { ! noError && < BottomText state = { state } > { bottomText } < /BottomText>} < /Wrapper>); } ; export { Select , Props as SelectProps } ;