license import React from 'react' ; import { Animated , GestureResponderEvent , ImageProps , ListRenderItemInfo , NativeSyntheticEvent , Platform , StyleSheet , TargetedEvent , TextProps , TextStyle , View , ViewProps , } from 'react-native' ; import { ChildrenWithProps , EvaInputSize , EvaStatus , FalsyFC , FalsyText , IndexPath , RenderProp , TouchableWeb , TouchableWebElement , TouchableWebProps , Overwrite , LiteralUnion , } from '../../devsupport' ; import { Interaction , styled , StyledComponentProps , StyleType , } from '../../theme' ; import { List } from '../list/list.component' ; import { Popover } from '../popover/popover.component' ; import { ChevronDown } from '../shared/chevronDown.component' ; import { SelectGroupProps } from './selectGroup.component' ; import { SelectItemElement , SelectItemProps , } from './selectItem.component' ; import { SelectItemDescriptor , SelectService , } from './select.service' ; type SelectStyledProps = Overwrite < StyledComponentProps , { appearance ? : LiteralUnion < 'default' > ; } > ; export interface SelectProps extends TouchableWebProps , SelectStyledProps { children ? : ChildrenWithProps < SelectItemProps | SelectGroupProps > ; selectedIndex ? : IndexPath | IndexPath [ ] ; onSelect ? : ( index : IndexPath | IndexPath [ ] ) => void ; value ? : RenderProp < TextProps > | React . ReactText ; multiSelect ? : boolean ; placeholder ? : RenderProp < TextProps > | React . ReactText ; label ? : RenderProp < TextProps > | React . ReactText ; caption ? : RenderProp < TextProps > | React . ReactText ; accessoryLeft ? : RenderProp < Partial < ImageProps > > ; accessoryRight ? : RenderProp < Partial < ImageProps > > ; status ? : EvaStatus ; size ? : EvaInputSize ; } export type SelectElement = React . ReactElement < SelectProps > ; interface State { listVisible : boolean ; } const CHEVRON_DEG_COLLAPSED = - 180 ; const CHEVRON_DEG_EXPANDED = 0 ; const CHEVRON_ANIM_DURATION = 200 ; extends React . Component method method method method method method property property property property property property property property property property property property property property property overview-example overview-example overview-example overview-example overview-example overview-example overview-example overview-example overview-example overview-example overview-example overview-example @ styled ( 'Select' ) export class Select extends React . Component < SelectProps , State > { static defaultProps = { placeholder : 'Select Option' , selectedIndex : [ ] , } ; public state : State = { listVisible : false , } ; private service : SelectService = new SelectService ( ) ; private popoverRef = React . createRef < Popover > ( ) ; private expandAnimation : Animated . Value = new Animated . Value ( 0 ) ; private get isMultiSelect ( ) : boolean { return this . props . multiSelect ; } private get data ( ) : any [ ] { return React . Children . toArray ( this . props . children || [ ] ) ; } private get selectedIndices ( ) : IndexPath [ ] { if ( ! this . props . selectedIndex ) { return [ ] ; } return Array . isArray ( this . props . selectedIndex ) ? this . props . selectedIndex : [ this . props . selectedIndex ] ; } private get expandToRotateInterpolation ( ) : Animated . AnimatedInterpolation { return this . expandAnimation . interpolate ( { inputRange : [ CHEVRON_DEG_COLLAPSED , CHEVRON_DEG_EXPANDED ] , outputRange : [ `${ CHEVRON_DEG_COLLAPSED }deg` , `${ CHEVRON_DEG_EXPANDED }deg` ] , } ) ; } public show = ( ) : void => { this . popoverRef . current ?. show ( ) ; } ; public hide = ( ) : void => { this . popoverRef . current ?. hide ( ) ; } ; public focus = ( ) : void => { this . setOptionsListVisible ( ) ; } ; public blur = ( ) : void => { this . setOptionsListInvisible ( ) ; } ; public isFocused = ( ) : boolean => { return this . state . listVisible ; } ; public clear = ( ) : void => { this . props . onSelect && this . props . onSelect ( null ) ; } ; private onMouseEnter = ( event ) : void => { this . props . eva . dispatch ( [ Interaction . HOVER ] ) ; this . props . onMouseEnter && this . props . onMouseEnter ( event ) ; } ; private onMouseLeave = ( event ) : void => { this . props . eva . dispatch ( [ ] ) ; this . props . onMouseLeave && this . props . onMouseLeave ( event ) ; } ; private onPress = ( ) : void => { this . setOptionsListVisible ( ) ; } ; private onPressIn = ( event ) : void => { this . props . eva . dispatch ( [ Interaction . ACTIVE ] ) ; this . props . onPressIn && this . props . onPressIn ( event ) ; } ; private onPressOut = ( event ) : void => { this . props . eva . dispatch ( [ ] ) ; this . props . onPressOut && this . props . onPressOut ( event ) ; } ; private onItemPress = ( descriptor ) : void => { if ( this . props . onSelect ) { const selectedIndices = this . service . selectItem ( this . isMultiSelect , descriptor , this . selectedIndices ) ; ! this . isMultiSelect && this . setOptionsListInvisible ( ) ; this . props . onSelect ( selectedIndices ) ; } } ; private onBackdropPress = ( ) : void => { this . setOptionsListInvisible ( ) ; } ; private onListVisible = ( ) : void => { this . props . eva . dispatch ( [ Interaction . ACTIVE ] ) ; this . createExpandAnimation ( - CHEVRON_DEG_COLLAPSED ) . start ( ( ) => { this . props . onFocus && this . props . onFocus ( null ) ; } ) ; } ; private onListInvisible = ( ) : void => { this . props . eva . dispatch ( [ ] ) ; this . createExpandAnimation ( CHEVRON_DEG_EXPANDED ) . start ( ( ) => { this . props . onBlur && this . props . onBlur ( null ) ; } ) ; } ; private getComponentStyle = ( style ) => { const { textMarginHorizontal , textFontFamily , textFontSize , textFontWeight , textColor , placeholderColor , placeholderFontSize , placeholderFontWeight , placeholderFontFamily , iconWidth , iconHeight , iconMarginHorizontal , iconTintColor , labelColor , labelFontSize , labelMarginBottom , labelFontWeight , labelFontFamily , captionColor , captionFontSize , captionFontWeight , captionFontFamily , captionIconWidth , captionIconHeight , captionIconMarginRight , captionIconTintColor , popoverMaxHeight , popoverBorderRadius , popoverBorderColor , popoverBorderWidth , ... inputParameters } = style ; return { input : inputParameters , text : { marginHorizontal : textMarginHorizontal , fontFamily : textFontFamily , fontSize : textFontSize , fontWeight : textFontWeight , color : textColor , } , placeholder : { marginHorizontal : textMarginHorizontal , fontSize : placeholderFontSize , fontWeight : placeholderFontWeight , fontFamily : placeholderFontFamily , color : placeholderColor , } , icon : { width : iconWidth , height : iconHeight , marginHorizontal : iconMarginHorizontal , tintColor : iconTintColor , } , label : { marginBottom : labelMarginBottom , fontSize : labelFontSize , fontWeight : labelFontWeight , fontFamily : labelFontFamily , color : labelColor , } , caption : { fontSize : captionFontSize , fontWeight : captionFontWeight , fontFamily : captionFontFamily , color : captionColor , } , popover : { maxHeight : popoverMaxHeight , borderRadius : popoverBorderRadius , borderWidth : popoverBorderWidth , borderColor : popoverBorderColor , } , } ; } ; private setOptionsListVisible = ( ) : void => { const hasData = this . data . length > 0 ; hasData && this . setState ( { listVisible : true } , this . onListVisible ) ; } ; private setOptionsListInvisible = ( ) : void => { this . setState ( { listVisible : false } , this . onListInvisible ) ; } ; private createExpandAnimation = ( toValue ) : Animated . CompositeAnimation => { return Animated . timing ( this . expandAnimation , { toValue , duration : CHEVRON_ANIM_DURATION , useNativeDriver : Platform . OS !== 'web' , } ) ; } ; private cloneItemWithProps = ( el , props ) : SelectItemElement => { const nestedElements = React . Children . map ( el . props . children , ( nestedEl , index ) => { const descriptor = this . service . createDescriptorForNestedElement ( nestedEl , props . descriptor , index ) ; const selected = this . service . isSelected ( descriptor , this . selectedIndices ) ; return this . cloneItemWithProps ( nestedEl , { ... props , descriptor , selected , disabled : false } ) ; } ) ; return React . cloneElement ( el , { ... props , ... el . props } , nestedElements ) ; } ; private renderItem = ( info ) : SelectItemElement => { const descriptor = this . service . createDescriptorForElement ( info . item , this . isMultiSelect , info . index ) ; const selected = this . service . isSelected ( descriptor , this . selectedIndices ) ; const disabled = this . service . isDisabled ( descriptor ) ; return this . cloneItemWithProps ( info . item , { descriptor , selected , disabled , onPress : this . onItemPress } ) ; } ; private renderDefaultIconElement = ( evaStyle ) : React . ReactElement => { const { tintColor , ... svgStyle } = evaStyle ; return ( < Animated . View style = { { transform : [ { rotate : this . expandToRotateInterpolation } ] } } > < ChevronDown style = { svgStyle } fill = { tintColor } / > < /Animated.View>); } ; private renderInputElement = ( props , evaStyle ) : TouchableWebElement => { const value = props . value || this . service . toStringSelected ( this . selectedIndices ) ; const textStyle = value && evaStyle . text ; return ( < TouchableWeb testID = { props . testID } style = { [ styles . input , evaStyle . input ] } onPress = { this . onPress } onMouseEnter = { this . onMouseEnter } onMouseLeave = { this . onMouseLeave } onPressIn = { this . onPressIn } onPressOut = { this . onPressOut } disabled = { props . disabled } > < FalsyFC style = { evaStyle . icon } component = { props . accessoryLeft } / > < FalsyText style = { [ styles . text , evaStyle . placeholder , textStyle ] } numberOfLines = { 1 } ellipsizeMode = 'tail' component = { value || props . placeholder } /> < FalsyFC style = { evaStyle . icon } component = { props . accessoryRight } fallback = { this . renderDefaultIconElement ( evaStyle . icon ) } / > < /TouchableWeb>); } ; public render ( ) { const { eva , style , label , caption , children , ... touchableProps } = this . props ; const evaStyle = this . getComponentStyle ( eva . style ) ; return ( < View style = { style } > < FalsyText style = { [ styles . label , evaStyle . label ] } component = { label } / > < Popover ref = { this . popoverRef } style = { [ styles . popover , evaStyle . popover ] } visible = { this . state . listVisible } fullWidth = { true } anchor = { ( ) => this . renderInputElement ( touchableProps , evaStyle ) } onBackdropPress = { this . onBackdropPress } > < List style = { styles . list } data = { this . data } bounces = { false } renderItem = { this . renderItem } / > < /Popover> < FalsyText style = { [ styles . caption , evaStyle . caption ] } component = { caption } / > < /View>); } } const styles = StyleSheet . create ( { input : { flexDirection : 'row' , alignItems : 'center' , justifyContent : 'space-between' , } , popover : { overflow : 'hidden' , } , list : { flexGrow : 0 , } , text : { flex : 1 , textAlign : 'left' , } , label : { textAlign : 'left' , } , caption : { textAlign : 'left' , } , } ) ;