2024-06-17 16:56:06 +02:00
|
|
|
import React, { Ref, useCallback, useMemo } from 'react';
|
2024-07-04 21:38:50 +02:00
|
|
|
import { Platform, Pressable, TouchableOpacity } from 'react-native';
|
2024-09-10 23:19:31 +02:00
|
|
|
import { MenuView, MenuAction, NativeActionEvent } from '@react-native-menu/menu';
|
2024-06-28 16:49:24 +02:00
|
|
|
import {
|
|
|
|
ContextMenuView,
|
|
|
|
RenderItem,
|
|
|
|
OnPressMenuItemEventObject,
|
|
|
|
MenuState,
|
|
|
|
IconConfig,
|
|
|
|
MenuElementConfig,
|
|
|
|
} from 'react-native-ios-context-menu';
|
2024-06-17 04:26:43 +02:00
|
|
|
import { ToolTipMenuProps, Action } from './types';
|
2024-07-04 21:38:50 +02:00
|
|
|
import { useSettings } from '../hooks/context/useSettings';
|
2024-05-18 18:48:03 +02:00
|
|
|
|
2024-06-17 16:56:06 +02:00
|
|
|
const ToolTipMenu = React.memo((props: ToolTipMenuProps, ref?: Ref<any>) => {
|
2024-06-17 04:26:43 +02:00
|
|
|
const {
|
|
|
|
title = '',
|
|
|
|
isMenuPrimaryAction = false,
|
|
|
|
renderPreview,
|
|
|
|
disabled = false,
|
|
|
|
onPress,
|
|
|
|
onMenuWillShow,
|
|
|
|
onMenuWillHide,
|
|
|
|
buttonStyle,
|
|
|
|
onPressMenuItem,
|
|
|
|
children,
|
|
|
|
isButton = false,
|
|
|
|
...restProps
|
|
|
|
} = props;
|
|
|
|
|
2024-07-04 21:38:50 +02:00
|
|
|
const { language } = useSettings();
|
|
|
|
|
2024-09-10 23:19:31 +02:00
|
|
|
// Map Menu Items for iOS Context Menu
|
2024-06-17 04:26:43 +02:00
|
|
|
const mapMenuItemForContextMenuView = useCallback((action: Action) => {
|
2024-06-28 16:49:24 +02:00
|
|
|
if (!action.id) return null;
|
2024-06-17 04:26:43 +02:00
|
|
|
return {
|
|
|
|
actionKey: action.id.toString(),
|
|
|
|
actionTitle: action.text,
|
|
|
|
icon: action.icon?.iconValue ? ({ iconType: 'SYSTEM', iconValue: action.icon.iconValue } as IconConfig) : undefined,
|
2024-08-04 23:00:50 +02:00
|
|
|
state: action.menuState ?? undefined,
|
2024-06-17 04:26:43 +02:00
|
|
|
attributes: action.disabled ? ['disabled'] : [],
|
|
|
|
};
|
|
|
|
}, []);
|
|
|
|
|
2024-09-10 23:19:31 +02:00
|
|
|
// Map Menu Items for RN Menu (supports subactions and displayInline)
|
2024-06-28 16:49:24 +02:00
|
|
|
const mapMenuItemForMenuView = useCallback((action: Action): MenuAction | null => {
|
|
|
|
if (!action.id) return null;
|
2024-09-10 23:19:31 +02:00
|
|
|
|
|
|
|
// Check for subactions
|
|
|
|
const subactions =
|
|
|
|
action.subactions?.map(subaction => ({
|
|
|
|
id: subaction.id.toString(),
|
|
|
|
title: subaction.text,
|
|
|
|
subtitle: subaction.subtitle,
|
|
|
|
image: subaction.icon?.iconValue ? subaction.icon.iconValue : undefined,
|
|
|
|
state: subaction.menuState === undefined ? undefined : ((subaction.menuState ? 'on' : 'off') as MenuState),
|
|
|
|
attributes: { disabled: subaction.disabled, destructive: subaction.destructive, hidden: subaction.hidden },
|
|
|
|
})) || [];
|
|
|
|
|
2024-06-17 04:26:43 +02:00
|
|
|
return {
|
|
|
|
id: action.id.toString(),
|
|
|
|
title: action.text,
|
2024-09-10 23:19:31 +02:00
|
|
|
subtitle: action.subtitle,
|
2024-08-13 19:11:40 +02:00
|
|
|
image: action.icon?.iconValue ? action.icon.iconValue : undefined,
|
2024-08-04 23:00:50 +02:00
|
|
|
state: action.menuState === undefined ? undefined : ((action.menuState ? 'on' : 'off') as MenuState),
|
2024-09-10 23:19:31 +02:00
|
|
|
attributes: { disabled: action.disabled, destructive: action.destructive, hidden: action.hidden },
|
|
|
|
subactions: subactions.length > 0 ? subactions : undefined,
|
|
|
|
displayInline: action.displayInline || false,
|
2024-06-17 04:26:43 +02:00
|
|
|
};
|
|
|
|
}, []);
|
|
|
|
|
|
|
|
const contextMenuItems = useMemo(() => {
|
2024-06-28 16:49:24 +02:00
|
|
|
const flattenedActions = props.actions.flat().filter(action => action.id);
|
|
|
|
return flattenedActions.map(mapMenuItemForContextMenuView).filter(item => item !== null) as MenuElementConfig[];
|
2024-06-17 04:26:43 +02:00
|
|
|
}, [props.actions, mapMenuItemForContextMenuView]);
|
|
|
|
|
|
|
|
const menuViewItemsIOS = useMemo(() => {
|
2024-06-28 16:49:24 +02:00
|
|
|
return props.actions
|
|
|
|
.map(actionGroup => {
|
|
|
|
if (Array.isArray(actionGroup) && actionGroup.length > 0) {
|
|
|
|
return {
|
|
|
|
id: actionGroup[0].id.toString(),
|
|
|
|
title: '',
|
|
|
|
subactions: actionGroup
|
|
|
|
.filter(action => action.id)
|
|
|
|
.map(mapMenuItemForMenuView)
|
|
|
|
.filter(item => item !== null) as MenuAction[],
|
|
|
|
displayInline: true,
|
|
|
|
};
|
|
|
|
} else if (!Array.isArray(actionGroup) && actionGroup.id) {
|
|
|
|
return mapMenuItemForMenuView(actionGroup);
|
|
|
|
}
|
|
|
|
return null;
|
|
|
|
})
|
|
|
|
.filter(item => item !== null) as MenuAction[];
|
2024-06-17 04:26:43 +02:00
|
|
|
}, [props.actions, mapMenuItemForMenuView]);
|
|
|
|
|
|
|
|
const menuViewItemsAndroid = useMemo(() => {
|
2024-06-28 16:49:24 +02:00
|
|
|
const mergedActions = props.actions.flat().filter(action => action.id);
|
|
|
|
return mergedActions.map(mapMenuItemForMenuView).filter(item => item !== null) as MenuAction[];
|
2024-06-17 04:26:43 +02:00
|
|
|
}, [props.actions, mapMenuItemForMenuView]);
|
|
|
|
|
|
|
|
const handlePressMenuItemForContextMenuView = useCallback(
|
|
|
|
(event: OnPressMenuItemEventObject) => {
|
|
|
|
onPressMenuItem(event.nativeEvent.actionKey);
|
|
|
|
},
|
|
|
|
[onPressMenuItem],
|
|
|
|
);
|
|
|
|
|
|
|
|
const handlePressMenuItemForMenuView = useCallback(
|
|
|
|
({ nativeEvent }: NativeActionEvent) => {
|
|
|
|
onPressMenuItem(nativeEvent.event);
|
|
|
|
},
|
|
|
|
[onPressMenuItem],
|
|
|
|
);
|
|
|
|
|
|
|
|
const renderContextMenuView = () => {
|
|
|
|
return (
|
|
|
|
<ContextMenuView
|
|
|
|
lazyPreview
|
2024-07-04 21:38:50 +02:00
|
|
|
accessibilityLabel={props.accessibilityLabel}
|
|
|
|
accessibilityHint={props.accessibilityHint}
|
|
|
|
accessibilityRole={props.accessibilityRole}
|
|
|
|
accessibilityState={props.accessibilityState}
|
|
|
|
accessibilityLanguage={language}
|
2024-06-17 04:26:43 +02:00
|
|
|
shouldEnableAggressiveCleanup
|
|
|
|
internalCleanupMode="automatic"
|
|
|
|
onPressMenuItem={handlePressMenuItemForContextMenuView}
|
|
|
|
onMenuWillShow={onMenuWillShow}
|
|
|
|
onMenuWillHide={onMenuWillHide}
|
|
|
|
useActionSheetFallback={false}
|
|
|
|
menuConfig={{
|
|
|
|
menuTitle: title,
|
|
|
|
menuItems: contextMenuItems,
|
|
|
|
}}
|
|
|
|
{...(renderPreview
|
|
|
|
? {
|
|
|
|
previewConfig: {
|
|
|
|
previewType: 'CUSTOM',
|
|
|
|
backgroundColor: 'white',
|
|
|
|
},
|
|
|
|
renderPreview: renderPreview as RenderItem,
|
|
|
|
}
|
|
|
|
: {})}
|
|
|
|
>
|
|
|
|
{onPress ? (
|
|
|
|
<Pressable accessibilityRole="button" onPress={onPress} {...restProps}>
|
|
|
|
{children}
|
|
|
|
</Pressable>
|
|
|
|
) : (
|
|
|
|
children
|
|
|
|
)}
|
|
|
|
</ContextMenuView>
|
|
|
|
);
|
|
|
|
};
|
|
|
|
|
|
|
|
const renderMenuView = () => {
|
|
|
|
return (
|
2024-07-04 21:38:50 +02:00
|
|
|
<MenuView
|
|
|
|
title={title}
|
|
|
|
isAnchoredToRight
|
|
|
|
onPressAction={handlePressMenuItemForMenuView}
|
|
|
|
actions={Platform.OS === 'ios' ? menuViewItemsIOS : menuViewItemsAndroid}
|
|
|
|
shouldOpenOnLongPress={!isMenuPrimaryAction}
|
2024-09-10 23:19:31 +02:00
|
|
|
// @ts-ignore: Not exposed in types
|
2024-07-04 21:38:50 +02:00
|
|
|
accessibilityLabel={props.accessibilityLabel}
|
|
|
|
accessibilityHint={props.accessibilityHint}
|
|
|
|
accessibilityRole={props.accessibilityRole}
|
|
|
|
accessibilityLanguage={language}
|
|
|
|
>
|
|
|
|
{isMenuPrimaryAction || isButton ? (
|
|
|
|
<TouchableOpacity style={buttonStyle} disabled={disabled} onPress={onPress} {...restProps}>
|
|
|
|
{children}
|
|
|
|
</TouchableOpacity>
|
|
|
|
) : (
|
|
|
|
children
|
|
|
|
)}
|
|
|
|
</MenuView>
|
2024-06-17 04:26:43 +02:00
|
|
|
);
|
|
|
|
};
|
|
|
|
|
2024-06-28 16:49:24 +02:00
|
|
|
return props.actions.length > 0 ? (Platform.OS === 'ios' && renderPreview ? renderContextMenuView() : renderMenuView()) : null;
|
2024-06-17 16:56:06 +02:00
|
|
|
});
|
2024-05-18 20:34:54 +02:00
|
|
|
|
2024-05-18 18:48:03 +02:00
|
|
|
export default ToolTipMenu;
|