import React from 'react' ; import { Animated , GestureResponderEvent , ImageProps , StyleSheet , ViewProps , ViewStyle , } from 'react-native' ; import { ChildrenWithProps , Frame , MeasureElement , MeasuringElement , Point , } from '../../devsupport' ; import { ChevronDown } from '../shared/chevronDown.component' ; import { MenuItem , MenuItemElement , MenuItemProps , } from './menuItem.component' ; import { ModalService } from '../../theme' ; import { MenuItemDescriptor } from './menu.service' ; export interface MenuGroupProps extends MenuItemProps { children ? : ChildrenWithProps < MenuItemProps > ; } export type MenuGroupElement = React . ReactElement < MenuGroupProps > ; interface State { submenuHeight : number ; } const CHEVRON_DEG_COLLAPSED = - 180 ; const CHEVRON_DEG_EXPANDED = 0 ; const CHEVRON_ANIM_DURATION = 200 ; const POSITION_OUTSCREEN = Point . outscreen ( ) ; extends React . Component property property property property property overview-example export class MenuGroup extends React . Component < MenuGroupProps , State > { public state : State = { submenuHeight : 1 , } ; private expandAnimation : Animated . Value = new Animated . Value ( 0 ) ; private get hasSubmenu ( ) : boolean { return React . Children . count ( this . props . children ) > 0 ; } private get shouldMeasureSubmenu ( ) : boolean { return this . state . submenuHeight === 1 ; } private get expandAnimationValue ( ) : number { return this . expandAnimation . _value ; } private get expandToRotateInterpolation ( ) : Animated . AnimatedInterpolation { return this . expandAnimation . interpolate ( { inputRange : [ - this . state . submenuHeight , CHEVRON_DEG_EXPANDED ] , outputRange : [ `${ CHEVRON_DEG_COLLAPSED }deg` , `${ CHEVRON_DEG_EXPANDED }deg` ] , } ) ; } private get submenuStyle ( ) : ViewStyle { return this . shouldMeasureSubmenu ? styles . outscreen : { height : this . expandAnimation } ; } private get defaultItemProps ( ) : MenuItemProps { return { appearance : 'grouped' } ; } private onPress = ( descriptor , event ) : void => { if ( this . hasSubmenu ) { const expandValue = this . expandAnimationValue > 0 ? 0 : this . state . submenuHeight ; this . createExpandAnimation ( expandValue ) . start ( ) ; this . props . onPress && this . props . onPress ( descriptor , event ) ; } } ; private onSubmenuMeasure = ( frame ) : void => { this . setState ( { submenuHeight : frame . size . height } ) ; } ; private createExpandAnimation = ( toValue ) : Animated . CompositeAnimation => { return Animated . timing ( this . expandAnimation , { toValue : toValue , duration : CHEVRON_ANIM_DURATION , useNativeDriver : false , } ) ; } ; private renderAccessoryIfNeeded = ( evaProps ) : React . ReactElement => { if ( ! this . hasSubmenu ) { return null ; } const style = StyleSheet . flatten ( evaProps . style ) ; return ( < Animated . View style = { { transform : [ { rotate : this . expandToRotateInterpolation } ] } } > < ChevronDown { ... evaProps } fill = { style . tintColor } / > < /Animated.View>); } ; private renderItemsWithDefaultProps = ( ) : React . ReactNode => { return React . Children . map ( this . props . children , ( item ) : MenuItemElement => { return React . cloneElement ( item , this . defaultItemProps , null ) ; } ) ; } ; private renderGroupedItems = ( evaStyle ) : React . ReactElement < ViewProps > => { return ( < Animated . View style = { [ styles . submenu , this . submenuStyle , evaStyle ] } > { this . renderItemsWithDefaultProps ( ) } < /Animated.View>); } ; private renderMeasuringGroupedItems = ( evaStyle ) : MeasuringElement => { return ( < MeasureElement shouldUseTopInsets = { ModalService . getShouldUseTopInsets } onMeasure = { this . onSubmenuMeasure } > { this . renderGroupedItems ( evaStyle ) } < /MeasureElement>); } ; private renderGroupedItemsIfNeeded = ( evaStyle ) : React . ReactNode => { if ( ! this . hasSubmenu ) { return null ; } if ( this . shouldMeasureSubmenu ) { return this . renderMeasuringGroupedItems ( evaStyle ) ; } return this . renderGroupedItems ( evaStyle ) ; } ; public render ( ) { const { children , ... itemProps } = this . props ; return ( < React . Fragment > < MenuItem accessoryRight = { this . renderAccessoryIfNeeded } { ... itemProps } onPress = { this . onPress } / > { this . renderGroupedItemsIfNeeded ( { } ) } < /React.Fragment>); } } const styles = StyleSheet . create ( { outscreen : { position : 'absolute' , left : POSITION_OUTSCREEN . x , top : POSITION_OUTSCREEN . y , } , submenu : { overflow : 'hidden' , } , } ) ;