️ Types - Added Types For ContextMenuButton

This commit is contained in:
Dominic Go 2021-11-14 15:14:58 +08:00
parent 0a68d6dd00
commit e0bc64f027
3 changed files with 261 additions and 225 deletions

View File

@ -1,214 +0,0 @@
import React from 'react';
import { StyleSheet, View, TouchableOpacity } from 'react-native';
import Proptypes from 'prop-types';
import { RNIContextMenuButton } from '../native_components/RNIContextMenuButton';
import { ActionSheetFallback } from '../functions/ActionSheetFallback';
import { ContextMenuView } from './ContextMenuView';
import { LIB_ENV } from '../constants/LibEnv';
const NATIVE_PROP_KEYS = {
// props: values --------
menuConfig: 'menuConfig',
// props: flags ---------------------------
enableContextMenu : 'enableContextMenu' ,
isMenuPrimaryAction: 'isMenuPrimaryAction',
// props: events --------------------
onMenuWillShow : 'onMenuWillShow' ,
onMenuWillHide : 'onMenuWillHide' ,
onMenuWillCancel: 'onMenuWillCancel',
onMenuDidShow : 'onMenuDidShow' ,
onMenuDidHide : 'onMenuDidHide' ,
onMenuDidCancel : 'onMenuDidCancel' ,
// props: onPress events ----------
onPressMenuItem: 'onPressMenuItem',
};
export class ContextMenuButton extends React.PureComponent {
static proptypes = {
menuConfig: Proptypes.object,
// flags ------------------------------
enableContextMenu : Proptypes.bool,
isMenuPrimaryAction : Proptypes.bool,
useActionSheetFallback: Proptypes.bool,
wrapNativeComponent : Proptypes.bool,
// events -----------------------
onMenuWillShow : Proptypes.func,
onMenuWillHide : Proptypes.func,
onMenuWillCancel: Proptypes.func,
onMenuDidShow : Proptypes.func,
onMenuDidHide : Proptypes.func,
onMenuDidCancel : Proptypes.func,
// onPress events --------------
onPressMenuItem: Proptypes.func,
};
static defaultProps = {
enableContextMenu : true,
wrapNativeComponent : true,
useActionSheetFallback: !LIB_ENV.isContextMenuViewSupported,
};
constructor(props){
super(props);
this.state = {
menuVisible: false,
};
};
getProps(){
const otherProps = { ...this.props };
const nativeProps = {};
const nativeKeys = Object.keys(NATIVE_PROP_KEYS);
for (const key of nativeKeys) {
nativeProps[key] = otherProps[key];
delete otherProps[key];
};
return { nativeProps, ...otherProps };
};
//#region - Event Handlers
_handleOnLongPress = async () => {
const { menuConfig, ...props } = this.props;
const item = await ActionSheetFallback.show(menuConfig);
if(item == null){
// cancelled pressed
props.onMenuDidCancel?.();
} else {
props.onPressMenuItem?.({nativeEvent: {...item}});
};
};
_handleOnMenuWillShow = (event) => {
const { onMenuWillShow } = this.props;
onMenuWillShow?.(event);
this.setState({menuVisible: true});
};
_handleOnMenuWillHide = (event) => {
const { onMenuWillHide } = this.props;
onMenuWillHide?.(event);
this.setState({menuVisible: false});
};
_handleOnMenuWillCancel = (event) => {
const { onMenuWillCancel } = this.props;
onMenuWillCancel?.(event);
};
_handleOnMenuDidShow = (event) => {
const { onMenuDidShow } = this.props;
onMenuDidShow?.(event);
};
_handleOnMenuDidHide = (event) => {
const { onMenuDidHide } = this.props;
onMenuDidHide?.(event);
};
_handleOnMenuDidCancel = (event) => {
const { onMenuDidCancel } = this.props;
onMenuDidCancel?.(event);
};
_handleOnPressMenuItem = (event) => {
this.props.onPressMenuItem?.(event);
};
//#endregion
_renderContextMenuView(){
const { nativeProps, style, children, ...props } = this.getProps();
const { menuVisible } = this.state;
const nativeCompProps = {
...nativeProps,
// Native Props: Events ------------------------------------------
[NATIVE_PROP_KEYS.onMenuWillShow ]: this._handleOnMenuWillShow ,
[NATIVE_PROP_KEYS.onMenuWillHide ]: this._handleOnMenuWillHide ,
[NATIVE_PROP_KEYS.onMenuWillCancel]: this._handleOnMenuWillCancel,
[NATIVE_PROP_KEYS.onMenuDidShow ]: this._handleOnMenuDidShow ,
[NATIVE_PROP_KEYS.onMenuDidHide ]: this._handleOnMenuDidHide ,
[NATIVE_PROP_KEYS.onMenuDidCancel ]: this._handleOnMenuDidCancel ,
// Native Props: OnPress Events --------------------------------
[NATIVE_PROP_KEYS.onPressMenuItem]: this._handleOnPressMenuItem,
};
const childItems = React.Children.map(children, child =>
React.cloneElement(child, {menuVisible})
);
const nativeComp = (LIB_ENV.isContextMenuButtonSupported? (
<RNIContextMenuButton
{...nativeCompProps}
style={(props.wrapNativeComponent
? styles.wrappedMenuButton
: [styles.menuButton, style]
)}
>
{childItems}
</RNIContextMenuButton>
):(
<ContextMenuView
{...nativeCompProps}
>
{childItems}
</ContextMenuView>
));
if(props.wrapNativeComponent){
return(
<TouchableOpacity
activeOpacity={0.8}
{...{style, ...props}}
>
{nativeComp}
</TouchableOpacity>
);
} else {
return nativeComp;
};
};
render(){
const { useActionSheetFallback } = this.props;
const useContextMenu =
(LIB_ENV.isContextMenuViewSupported && !useActionSheetFallback);
return(
useContextMenu? this._renderContextMenuView() :
useActionSheetFallback? (
<TouchableOpacity
onLongPress={this._handleOnLongPress}
activeOpacity={0.8}
{...this.props}
>
{this.props.children}
</TouchableOpacity>
):(
<View {...this.props}>
{this.props.children}
</View>
)
);
};
};
const styles = StyleSheet.create({
menuButton: {
backgroundColor: 'transparent',
},
wrappedMenuButton: {
flex: 1,
},
});

View File

@ -1,11 +0,0 @@
import React from 'react';
import { View } from 'react-native';
export function ContextMenuButton(props){
return(
<View {...props}>
{props.children}
</View>
);
};

View File

@ -0,0 +1,261 @@
import React from 'react';
import { StyleSheet, View, TouchableOpacity, ViewProps } from 'react-native';
import { RNIContextMenuButton, RNIContextMenuButtonBaseProps } from '../native_components/RNIContextMenuButton';
import type { OnMenuWillShowEvent, OnMenuWillHideEvent, OnMenuDidShowEvent, OnMenuDidHideEvent, OnMenuWillCancelEvent, OnMenuDidCancelEvent, OnPressMenuItemEvent } from '../types/MenuEvents';
// @ts-ignore - TODO
import { ActionSheetFallback } from '../functions/ActionSheetFallback';
import { ContextMenuView } from './ContextMenuView';
import { LIB_ENV, IS_PLATFORM_IOS } from '../constants/LibEnv';
export type ContextMenuButtonBaseProps = Pick<RNIContextMenuButtonBaseProps,
| 'enableContextMenu'
| 'isMenuPrimaryAction'
| 'menuConfig'
// Lifecycle Events
| 'onMenuWillShow'
| 'onMenuWillHide'
| 'onMenuWillCancel'
| 'onMenuDidShow'
| 'onMenuDidHide'
| 'onMenuDidCancel'
// `OnPress` Events
| 'onPressMenuItem'
> & {
useActionSheetFallback?: boolean;
wrapNativeComponent?: boolean;
};
export type ContextMenuButtonProps =
ViewProps & ContextMenuButtonBaseProps;
export type ContextMenuButtonState = {
menuVisible: boolean;
};
export class ContextMenuButton extends React.PureComponent<ContextMenuButtonProps, ContextMenuButtonState> {
constructor(props: ContextMenuButtonProps){
super(props);
this.state = {
menuVisible: false,
};
};
getProps = () => {
const {
menuConfig,
enableContextMenu,
isMenuPrimaryAction,
useActionSheetFallback,
wrapNativeComponent,
onMenuWillShow,
onMenuWillHide,
onMenuWillCancel,
onMenuDidShow,
onMenuDidHide,
onMenuDidCancel,
onPressMenuItem,
...viewProps
} = this.props;
return {
// A. Provide default values to props...
enableContextMenu: (
enableContextMenu ?? true
),
wrapNativeComponent: (
wrapNativeComponent ?? true
),
useActionSheetFallback: (
useActionSheetFallback ?? !LIB_ENV.isContextMenuViewSupported
),
// B. Pass down props...
menuConfig,
isMenuPrimaryAction,
onMenuWillShow,
onMenuWillHide,
onMenuWillCancel,
onMenuDidShow,
onMenuDidHide,
onMenuDidCancel,
onPressMenuItem,
// C. Move all the default view-related
// props here...
viewProps
};
};
//#region - Event Handlers
_handleOnLongPress = async () => {
const props = this.props;
const selectedItem =
await ActionSheetFallback.show(props.menuConfig);
if(selectedItem == null){
// A. cancelled pressed
props.onMenuDidCancel?.({
isUsingActionSheetFallback: true
});
} else {
// B. an item was selected
props.onPressMenuItem?.({
isUsingActionSheetFallback: true,
nativeEvent: {
...selectedItem,
}
});
};
};
_handleOnMenuWillShow: OnMenuWillShowEvent = (event) => {
this.props.onMenuWillShow?.(event);
event.stopPropagation();
this.setState({menuVisible: true});
};
_handleOnMenuWillHide: OnMenuWillHideEvent = (event) => {
this.props.onMenuWillHide?.(event);
event.stopPropagation();
this.setState({menuVisible: false});
};
_handleOnMenuWillCancel: OnMenuWillCancelEvent = (event) => {
this.props.onMenuWillCancel?.(event);
event.stopPropagation();
};
_handleOnMenuDidShow: OnMenuDidShowEvent = (event) => {
this.props.onMenuDidShow?.(event);
event.stopPropagation();
};
_handleOnMenuDidHide: OnMenuDidHideEvent = (event) => {
this.props.onMenuDidHide?.(event);
event.stopPropagation();
};
_handleOnMenuDidCancel: OnMenuDidCancelEvent = (event) => {
this.props.onMenuDidCancel?.(event);
// guard: event is a native event
if(event.isUsingActionSheetFallback) return;
event.stopPropagation();
};
_handleOnPressMenuItem: OnPressMenuItemEvent = (event) => {
this.props.onPressMenuItem?.(event);
// guard: event is a native event
if(event.isUsingActionSheetFallback) return;
event.stopPropagation();
};
//#endregion
render(){
const props = this.getProps();
const { menuVisible } = this.state;
const isNativeComponentSupported = (
LIB_ENV.isContextMenuButtonSupported &&
!props.useActionSheetFallback
);
const shouldUseActionSheetFallback = (
IS_PLATFORM_IOS && props.useActionSheetFallback
);
const nativeComponentProps: RNIContextMenuButtonBaseProps = {
menuConfig: props.menuConfig,
enableContextMenu: props.enableContextMenu,
isMenuPrimaryAction: props.isMenuPrimaryAction,
// event handlers
onMenuWillShow : this._handleOnMenuWillShow ,
onMenuWillHide : this._handleOnMenuWillHide ,
onMenuDidShow : this._handleOnMenuDidShow ,
onMenuDidHide : this._handleOnMenuDidHide ,
onMenuDidCancel : this._handleOnMenuDidCancel ,
onMenuWillCancel: this._handleOnMenuWillCancel,
onPressMenuItem : this._handleOnPressMenuItem ,
};
if(isNativeComponentSupported){
const childItems = React.Children.map(this.props.children, child =>
//@ts-ignore
React.cloneElement(child, {menuVisible})
);
const nativeComponent = (LIB_ENV.isContextMenuButtonSupported? (
<RNIContextMenuButton
{...(props.wrapNativeComponent && props.viewProps)}
{...nativeComponentProps}
// override style prop
style={(props.wrapNativeComponent
? styles.wrappedMenuButton
: [styles.menuButton, props.viewProps.style]
)}
>
{childItems}
</RNIContextMenuButton>
):(
<ContextMenuView
{...props.viewProps}
{...nativeComponentProps}
>
{childItems}
</ContextMenuView>
));
if(props.wrapNativeComponent){
return(
<TouchableOpacity
{...props.viewProps}
activeOpacity={0.8}
>
{nativeComponent}
</TouchableOpacity>
);
} else {
return nativeComponent;
};
} else if(shouldUseActionSheetFallback){
return (
<TouchableOpacity
onLongPress={this._handleOnLongPress}
activeOpacity={0.8}
{...props.viewProps}
>
{this.props.children}
</TouchableOpacity>
);
} else {
return (
<View {...props.viewProps}>
{this.props.children}
</View>
);
};
};
};
const styles = StyleSheet.create({
menuButton: {
backgroundColor: 'transparent',
},
wrappedMenuButton: {
flex: 1,
},
});