diff --git a/native/chat/multimedia-message-tooltip-modal.react.js b/native/chat/multimedia-message-tooltip-modal.react.js
index af1c600a2..7a0be5480 100644
--- a/native/chat/multimedia-message-tooltip-modal.react.js
+++ b/native/chat/multimedia-message-tooltip-modal.react.js
@@ -1,106 +1,106 @@
// @flow
import * as React from 'react';
import { useOnPressReport } from './message-report-utils.js';
import MultimediaMessageTooltipButton from './multimedia-message-tooltip-button.react.js';
import { useAnimatedNavigateToSidebar } from './sidebar-navigation.js';
import CommIcon from '../components/comm-icon.react.js';
import SWMansionIcon from '../components/swmansion-icon.react.js';
import { OverlayContext } from '../navigation/overlay-context.js';
import {
createTooltip,
type TooltipParams,
- type BaseTooltipProps,
+ type TooltipProps,
type TooltipMenuProps,
} from '../tooltip/tooltip.react.js';
import type { ChatMultimediaMessageInfoItem } from '../types/chat-types.js';
import type { VerticalBounds } from '../types/layout-types.js';
import type { TextStyle } from '../types/styles.js';
import { useNavigateToPinModal } from '../utils/toggle-pin-utils.js';
export type MultimediaMessageTooltipModalParams = TooltipParams<{
+item: ChatMultimediaMessageInfoItem,
+verticalBounds: VerticalBounds,
}>;
function TooltipMenu(
props: TooltipMenuProps<'MultimediaMessageTooltipModal'>,
): React.Node {
const { route, tooltipItem: TooltipItem } = props;
const overlayContext = React.useContext(OverlayContext);
const onPressTogglePin = useNavigateToPinModal(overlayContext, route);
const renderPinIcon = React.useCallback(
(style: TextStyle) => (
),
[],
);
const renderUnpinIcon = React.useCallback(
(style: TextStyle) => (
),
[],
);
const onPressSidebar = useAnimatedNavigateToSidebar(route.params.item);
const renderSidebarIcon = React.useCallback(
(style: TextStyle) => (
),
[],
);
const onPressReport = useOnPressReport(route);
const renderReportIcon = React.useCallback(
(style: TextStyle) => (
),
[],
);
return (
<>
>
);
}
const MultimediaMessageTooltipModal: React.ComponentType<
- BaseTooltipProps<'MultimediaMessageTooltipModal'>,
+ TooltipProps<'MultimediaMessageTooltipModal'>,
> = createTooltip<'MultimediaMessageTooltipModal'>(
MultimediaMessageTooltipButton,
TooltipMenu,
);
export default MultimediaMessageTooltipModal;
diff --git a/native/chat/robotext-message-tooltip-modal.react.js b/native/chat/robotext-message-tooltip-modal.react.js
index 30496767b..df5170549 100644
--- a/native/chat/robotext-message-tooltip-modal.react.js
+++ b/native/chat/robotext-message-tooltip-modal.react.js
@@ -1,54 +1,54 @@
// @flow
import * as React from 'react';
import RobotextMessageTooltipButton from './robotext-message-tooltip-button.react.js';
import { useAnimatedNavigateToSidebar } from './sidebar-navigation.js';
import SWMansionIcon from '../components/swmansion-icon.react.js';
import {
createTooltip,
type TooltipParams,
- type BaseTooltipProps,
+ type TooltipProps,
type TooltipMenuProps,
} from '../tooltip/tooltip.react.js';
import type { ChatRobotextMessageInfoItemWithHeight } from '../types/chat-types.js';
import type { TextStyle } from '../types/styles.js';
export type RobotextMessageTooltipModalParams = TooltipParams<{
+item: ChatRobotextMessageInfoItemWithHeight,
}>;
function TooltipMenu(
props: TooltipMenuProps<'RobotextMessageTooltipModal'>,
): React.Node {
const { route, tooltipItem: TooltipItem } = props;
const onPress = useAnimatedNavigateToSidebar(route.params.item);
const renderIcon = React.useCallback(
(style: TextStyle) => (
),
[],
);
return (
<>
>
);
}
const RobotextMessageTooltipModal: React.ComponentType<
- BaseTooltipProps<'RobotextMessageTooltipModal'>,
+ TooltipProps<'RobotextMessageTooltipModal'>,
> = createTooltip<'RobotextMessageTooltipModal'>(
RobotextMessageTooltipButton,
TooltipMenu,
);
export default RobotextMessageTooltipModal;
diff --git a/native/chat/settings/thread-settings-member-tooltip-modal.react.js b/native/chat/settings/thread-settings-member-tooltip-modal.react.js
index 1a3e956a3..cf34269db 100644
--- a/native/chat/settings/thread-settings-member-tooltip-modal.react.js
+++ b/native/chat/settings/thread-settings-member-tooltip-modal.react.js
@@ -1,114 +1,114 @@
// @flow
import * as React from 'react';
import { useRemoveUsersFromThread } from 'lib/actions/thread-actions.js';
import { removeMemberFromThread } from 'lib/shared/thread-actions-utils.js';
import { stringForUser } from 'lib/shared/user-utils.js';
import type {
RelativeMemberInfo,
ThreadInfo,
} from 'lib/types/minimally-encoded-thread-permissions-types.js';
import { useDispatchActionPromise } from 'lib/utils/redux-promise-utils.js';
import ThreadSettingsMemberTooltipButton from './thread-settings-member-tooltip-button.react.js';
import type { AppNavigationProp } from '../../navigation/app-navigator.react';
import { ChangeRolesScreenRouteName } from '../../navigation/route-names.js';
import {
- type BaseTooltipProps,
+ type TooltipProps,
createTooltip,
type TooltipMenuProps,
type TooltipParams,
type TooltipRoute,
} from '../../tooltip/tooltip.react.js';
import Alert from '../../utils/alert.js';
export type ThreadSettingsMemberTooltipModalParams = TooltipParams<{
+memberInfo: RelativeMemberInfo,
+threadInfo: ThreadInfo,
}>;
function useOnRemoveUser(
route: TooltipRoute<'ThreadSettingsMemberTooltipModal'>,
) {
const { memberInfo, threadInfo } = route.params;
const boundRemoveUsersFromThread = useRemoveUsersFromThread();
const dispatchActionPromise = useDispatchActionPromise();
const onConfirmRemoveUser = React.useCallback(
() =>
removeMemberFromThread(
threadInfo,
memberInfo,
dispatchActionPromise,
boundRemoveUsersFromThread,
),
[threadInfo, memberInfo, dispatchActionPromise, boundRemoveUsersFromThread],
);
const userText = stringForUser(memberInfo);
return React.useCallback(() => {
Alert.alert(
'Confirm removal',
`Are you sure you want to remove ${userText} from this chat?`,
[
{ text: 'Cancel', style: 'cancel' },
{ text: 'OK', onPress: onConfirmRemoveUser },
],
{ cancelable: true },
);
}, [onConfirmRemoveUser, userText]);
}
function useOnChangeRole(
route: TooltipRoute<'ThreadSettingsMemberTooltipModal'>,
navigation: AppNavigationProp<'ThreadSettingsMemberTooltipModal'>,
) {
const { threadInfo, memberInfo } = route.params;
return React.useCallback(() => {
navigation.navigate<'ChangeRolesScreen'>({
name: ChangeRolesScreenRouteName,
params: {
threadInfo,
memberInfo,
role: memberInfo.role,
},
key: route.key,
});
}, [navigation, route.key, threadInfo, memberInfo]);
}
function TooltipMenu(
props: TooltipMenuProps<'ThreadSettingsMemberTooltipModal'>,
): React.Node {
const { route, navigation, tooltipItem: TooltipItem } = props;
const onChangeRole = useOnChangeRole(route, navigation);
const onRemoveUser = useOnRemoveUser(route);
return (
<>
>
);
}
const ThreadSettingsMemberTooltipModal: React.ComponentType<
- BaseTooltipProps<'ThreadSettingsMemberTooltipModal'>,
+ TooltipProps<'ThreadSettingsMemberTooltipModal'>,
> = createTooltip<'ThreadSettingsMemberTooltipModal'>(
ThreadSettingsMemberTooltipButton,
TooltipMenu,
);
export default ThreadSettingsMemberTooltipModal;
diff --git a/native/chat/text-message-tooltip-modal.react.js b/native/chat/text-message-tooltip-modal.react.js
index 327ea75d5..ab6ace7e3 100644
--- a/native/chat/text-message-tooltip-modal.react.js
+++ b/native/chat/text-message-tooltip-modal.react.js
@@ -1,200 +1,200 @@
// @flow
import Clipboard from '@react-native-clipboard/clipboard';
import invariant from 'invariant';
import * as React from 'react';
import { createMessageReply } from 'lib/shared/message-utils.js';
import { MessageEditingContext } from './message-editing-context.react.js';
import { useNavigateToThread } from './message-list-types.js';
import { useOnPressReport } from './message-report-utils.js';
import { useAnimatedNavigateToSidebar } from './sidebar-navigation.js';
import TextMessageTooltipButton from './text-message-tooltip-button.react.js';
import CommIcon from '../components/comm-icon.react.js';
import SWMansionIcon from '../components/swmansion-icon.react.js';
import { InputStateContext } from '../input/input-state.js';
import { displayActionResultModal } from '../navigation/action-result-modal.js';
import { OverlayContext } from '../navigation/overlay-context.js';
import {
createTooltip,
type TooltipParams,
- type BaseTooltipProps,
+ type TooltipProps,
type TooltipMenuProps,
} from '../tooltip/tooltip.react.js';
import type { ChatTextMessageInfoItemWithHeight } from '../types/chat-types.js';
import type { TextStyle } from '../types/styles.js';
import { exitEditAlert } from '../utils/edit-messages-utils.js';
import { useNavigateToPinModal } from '../utils/toggle-pin-utils.js';
export type TextMessageTooltipModalParams = TooltipParams<{
+item: ChatTextMessageInfoItemWithHeight,
}>;
const confirmCopy = () => displayActionResultModal('copied!');
function TooltipMenu(
props: TooltipMenuProps<'TextMessageTooltipModal'>,
): React.Node {
const { route, tooltipItem: TooltipItem } = props;
const { threadInfo } = route.params.item;
const overlayContext = React.useContext(OverlayContext);
const inputState = React.useContext(InputStateContext);
const { text } = route.params.item.messageInfo;
const navigateToThread = useNavigateToThread();
const onPressReply = React.useCallback(() => {
invariant(
inputState,
'inputState should be set in TextMessageTooltipModal.onPressReply',
);
navigateToThread({ threadInfo });
inputState.editInputMessage({
message: createMessageReply(text),
mode: 'prepend',
});
}, [inputState, navigateToThread, threadInfo, text]);
const renderReplyIcon = React.useCallback(
(style: TextStyle) => ,
[],
);
const onPressSidebar = useAnimatedNavigateToSidebar(route.params.item);
const renderSidebarIcon = React.useCallback(
(style: TextStyle) => (
),
[],
);
const messageEditingContext = React.useContext(MessageEditingContext);
const { messageInfo } = route.params.item;
const onPressEdit = React.useCallback(() => {
invariant(
inputState && messageEditingContext,
'inputState and messageEditingContext should be set in ' +
'TextMessageTooltipModal.onPressEdit',
);
const updateInputBar = () => {
inputState.editInputMessage({
message: text,
mode: 'replace',
});
};
const enterEditMode = () => {
messageEditingContext.setEditedMessage(messageInfo, updateInputBar);
};
const { editedMessage, isEditedMessageChanged } =
messageEditingContext.editState;
if (isEditedMessageChanged && editedMessage) {
exitEditAlert({
onDiscard: enterEditMode,
});
} else {
enterEditMode();
}
}, [inputState, messageEditingContext, messageInfo, text]);
const renderEditIcon = React.useCallback(
(style: TextStyle) => (
),
[],
);
const onPressTogglePin = useNavigateToPinModal(overlayContext, route);
const renderPinIcon = React.useCallback(
(style: TextStyle) => (
),
[],
);
const renderUnpinIcon = React.useCallback(
(style: TextStyle) => (
),
[],
);
const onPressCopy = React.useCallback(() => {
Clipboard.setString(text);
setTimeout(confirmCopy);
}, [text]);
const renderCopyIcon = React.useCallback(
(style: TextStyle) => ,
[],
);
const onPressReport = useOnPressReport(route);
const renderReportIcon = React.useCallback(
(style: TextStyle) => (
),
[],
);
return (
<>
>
);
}
const TextMessageTooltipModal: React.ComponentType<
- BaseTooltipProps<'TextMessageTooltipModal'>,
+ TooltipProps<'TextMessageTooltipModal'>,
> = createTooltip<'TextMessageTooltipModal'>(
TextMessageTooltipButton,
TooltipMenu,
);
export default TextMessageTooltipModal;
diff --git a/native/profile/user-relationship-tooltip-modal.react.js b/native/profile/user-relationship-tooltip-modal.react.js
index 1610a122b..e6a3fc441 100644
--- a/native/profile/user-relationship-tooltip-modal.react.js
+++ b/native/profile/user-relationship-tooltip-modal.react.js
@@ -1,167 +1,167 @@
// @flow
import * as React from 'react';
import { TouchableOpacity } from 'react-native';
import { updateRelationshipsActionTypes } from 'lib/actions/relationship-actions.js';
import { useUpdateRelationships } from 'lib/hooks/relationship-hooks.js';
import { stringForUser } from 'lib/shared/user-utils.js';
import type { RelativeUserInfo } from 'lib/types/user-types.js';
import { useDispatchActionPromise } from 'lib/utils/redux-promise-utils.js';
import PencilIcon from '../components/pencil-icon.react.js';
import SWMansionIcon from '../components/swmansion-icon.react.js';
import { useColors } from '../themes/colors.js';
import {
createTooltip,
type TooltipParams,
- type BaseTooltipProps,
+ type TooltipProps,
type TooltipMenuProps,
type TooltipRoute,
} from '../tooltip/tooltip.react.js';
import type { UserProfileBottomSheetNavigationProp } from '../user-profile/user-profile-bottom-sheet-navigator.react.js';
import { unknownErrorAlertDetails } from '../utils/alert-messages.js';
import Alert from '../utils/alert.js';
type Action = 'unfriend' | 'block' | 'unblock';
type TooltipButtonIcon = 'pencil' | 'menu';
export type UserRelationshipTooltipModalParams = TooltipParams<{
+tooltipButtonIcon: TooltipButtonIcon,
+relativeUserInfo: RelativeUserInfo,
}>;
type OnRemoveUserProps = {
...UserRelationshipTooltipModalParams,
+action: Action,
};
function useRelationshipAction(input: OnRemoveUserProps) {
const updateRelationships = useUpdateRelationships();
const dispatchActionPromise = useDispatchActionPromise();
const userText = stringForUser(input.relativeUserInfo);
return React.useCallback(() => {
const callRemoveRelationships = async () => {
try {
return await updateRelationships(input.action, [
input.relativeUserInfo.id,
]);
} catch (e) {
Alert.alert(
unknownErrorAlertDetails.title,
unknownErrorAlertDetails.message,
[{ text: 'OK' }],
{
cancelable: true,
},
);
throw e;
}
};
const onConfirmRemoveUser = () => {
const customKeyName = `${updateRelationshipsActionTypes.started}:${input.relativeUserInfo.id}`;
void dispatchActionPromise(
updateRelationshipsActionTypes,
callRemoveRelationships(),
{ customKeyName },
);
};
const action = {
unfriend: 'removal',
block: 'block',
unblock: 'unblock',
}[input.action];
const message = {
unfriend: `remove ${userText} from friends?`,
block: `block ${userText}`,
unblock: `unblock ${userText}?`,
}[input.action];
Alert.alert(
`Confirm ${action}`,
`Are you sure you want to ${message}`,
[
{ text: 'Cancel', style: 'cancel' },
{ text: 'OK', onPress: onConfirmRemoveUser },
],
{ cancelable: true },
);
}, [updateRelationships, dispatchActionPromise, userText, input]);
}
function TooltipMenu(
props: TooltipMenuProps<'UserRelationshipTooltipModal'>,
): React.Node {
const { route, tooltipItem: TooltipItem } = props;
const onRemoveUser = useRelationshipAction({
...route.params,
action: 'unfriend',
});
const onBlockUser = useRelationshipAction({
...route.params,
action: 'block',
});
const onUnblockUser = useRelationshipAction({
...route.params,
action: 'unblock',
});
return (
<>
>
);
}
type Props = {
+navigation: UserProfileBottomSheetNavigationProp<'UserRelationshipTooltipModal'>,
+route: TooltipRoute<'UserRelationshipTooltipModal'>,
...
};
function UserRelationshipTooltipButton(props: Props): React.Node {
const { navigation, route } = props;
const { goBackOnce } = navigation;
const { tooltipButtonIcon } = route.params;
const colors = useColors();
const icon = React.useMemo(() => {
if (tooltipButtonIcon === 'pencil') {
return ;
}
return (
);
}, [colors.modalBackgroundLabel, tooltipButtonIcon]);
return {icon};
}
const UserRelationshipTooltipModal: React.ComponentType<
- BaseTooltipProps<'UserRelationshipTooltipModal'>,
+ TooltipProps<'UserRelationshipTooltipModal'>,
> = createTooltip<'UserRelationshipTooltipModal'>(
UserRelationshipTooltipButton,
TooltipMenu,
);
export default UserRelationshipTooltipModal;
diff --git a/native/tooltip/tooltip.react.js b/native/tooltip/tooltip.react.js
index 3e47d445b..a9fbf9456 100644
--- a/native/tooltip/tooltip.react.js
+++ b/native/tooltip/tooltip.react.js
@@ -1,646 +1,569 @@
// @flow
import type { RouteProp } from '@react-navigation/core';
import * as Haptics from 'expo-haptics';
import invariant from 'invariant';
import * as React from 'react';
import {
View,
TouchableWithoutFeedback,
Platform,
Keyboard,
} from 'react-native';
import Animated from 'react-native-reanimated';
import {
TooltipContextProvider,
TooltipContext,
- type TooltipContextType,
} from './tooltip-context.react.js';
import BaseTooltipItem, {
type TooltipItemBaseProps,
} from './tooltip-item.react.js';
-import { ChatContext, type ChatContextType } from '../chat/chat-context.js';
+import { ChatContext } from '../chat/chat-context.js';
import SWMansionIcon from '../components/swmansion-icon.react.js';
import type { AppNavigationProp } from '../navigation/app-navigator.react.js';
-import {
- OverlayContext,
- type OverlayContextType,
-} from '../navigation/overlay-context.js';
+import { OverlayContext } from '../navigation/overlay-context.js';
import type { TooltipModalParamList } from '../navigation/route-names.js';
-import { type DimensionsInfo } from '../redux/dimensions-updater.react.js';
import { useSelector } from '../redux/redux-utils.js';
import { useStyles } from '../themes/colors.js';
import {
type VerticalBounds,
type LayoutCoordinates,
} from '../types/layout-types.js';
import type { LayoutEvent } from '../types/react-native.js';
import {
AnimatedView,
type ViewStyle,
type AnimatedViewStyle,
type WritableAnimatedStyleObj,
type ReanimatedTransform,
} from '../types/styles.js';
const { Value, Node, Extrapolate, add, multiply, interpolateNode } = Animated;
const unboundStyles = {
backdrop: {
backgroundColor: 'black',
bottom: 0,
left: 0,
position: 'absolute',
right: 0,
top: 0,
},
container: {
flex: 1,
},
contentContainer: {
flex: 1,
overflow: 'hidden',
},
icon: {
color: 'modalForegroundLabel',
},
itemContainer: {
alignItems: 'center',
flex: 1,
flexDirection: 'row',
justifyContent: 'center',
padding: 10,
},
itemContainerFixed: {
flexDirection: 'column',
},
items: {
backgroundColor: 'tooltipBackground',
borderRadius: 5,
overflow: 'hidden',
},
itemsFixed: {
flex: 1,
flexDirection: 'row',
},
triangleDown: {
borderBottomColor: 'transparent',
borderBottomWidth: 0,
borderLeftColor: 'transparent',
borderLeftWidth: 10,
borderRightColor: 'transparent',
borderRightWidth: 10,
borderStyle: 'solid',
borderTopColor: 'tooltipBackground',
borderTopWidth: 10,
height: 10,
top: Platform.OS === 'android' ? -1 : 0,
width: 10,
},
triangleUp: {
borderBottomColor: 'tooltipBackground',
borderBottomWidth: 10,
borderLeftColor: 'transparent',
borderLeftWidth: 10,
borderRightColor: 'transparent',
borderRightWidth: 10,
borderStyle: 'solid',
borderTopColor: 'transparent',
borderTopWidth: 0,
bottom: Platform.OS === 'android' ? -1 : 0,
height: 10,
width: 10,
},
};
export type TooltipParams = {
...CustomProps,
+presentedFrom: string,
+initialCoordinates: LayoutCoordinates,
+verticalBounds: VerticalBounds,
+tooltipLocation?: 'above' | 'below' | 'fixed',
+margin?: number,
+visibleEntryIDs?: $ReadOnlyArray,
+chatInputBarHeight?: number,
+hideTooltip?: boolean,
};
export type TooltipRoute> = RouteProp<
TooltipModalParamList,
RouteName,
>;
-export type BaseTooltipProps = {
+export type TooltipProps = {
+navigation: AppNavigationProp,
+route: TooltipRoute,
};
type ButtonProps = {
...Base,
+progress: Node,
+isOpeningSidebar: boolean,
};
-type TooltipProps = {
- ...Base,
- // Redux state
- +dimensions: DimensionsInfo,
- +overlayContext: ?OverlayContextType,
- +chatContext: ?ChatContextType,
- +styles: $ReadOnly,
- +tooltipContext: TooltipContextType,
- +closeTooltip: () => mixed,
- +computedTooltipLocation: 'above' | 'below' | 'fixed',
- +opacityStyle: AnimatedViewStyle,
- +contentContainerStyle: ViewStyle,
- +buttonStyle: ViewStyle,
- +tooltipContainerStyle: AnimatedViewStyle,
- +boundTooltipItem: React.ComponentType,
- +onPressMore: () => void,
- +renderMoreIcon: () => React.Node,
- +onTooltipContainerLayout: (event: LayoutEvent) => void,
-};
export type TooltipMenuProps = {
- ...BaseTooltipProps,
+ ...TooltipProps,
+tooltipItem: React.ComponentType,
};
function createTooltip<
RouteName: $Keys,
- BaseTooltipPropsType: BaseTooltipProps = BaseTooltipProps,
+ TooltipPropsType: TooltipProps = TooltipProps,
>(
- ButtonComponent: React.ComponentType>,
+ ButtonComponent: React.ComponentType>,
MenuComponent: React.ComponentType>,
-): React.ComponentType {
- class Tooltip extends React.PureComponent<
- TooltipProps,
- > {
- render(): React.Node {
- const {
- dimensions,
- overlayContext,
- chatContext,
- styles,
- tooltipContext,
- closeTooltip,
- computedTooltipLocation,
- opacityStyle,
- contentContainerStyle,
- buttonStyle,
- tooltipContainerStyle: _tooltipContainerStyle,
- boundTooltipItem,
- onPressMore,
- renderMoreIcon,
- onTooltipContainerLayout,
- ...navAndRouteForFlow
- } = this.props;
-
- const tooltipContainerStyle: Array = [styles.itemContainer];
-
- if (computedTooltipLocation === 'fixed') {
- tooltipContainerStyle.push(styles.itemContainerFixed);
- }
-
- const items: Array = [
- ,
- ];
-
- if (this.props.tooltipContext.shouldShowMore()) {
- items.push(
- ,
- );
- }
-
- let triangleStyle;
- const { route } = this.props;
- const { initialCoordinates } = route.params;
- const { x, width } = initialCoordinates;
- const extraLeftSpace = x;
- const extraRightSpace = dimensions.width - width - x;
- if (extraLeftSpace < extraRightSpace) {
- triangleStyle = {
- alignSelf: 'flex-start',
- left: extraLeftSpace + (width - 20) / 2,
- };
- } else {
- triangleStyle = {
- alignSelf: 'flex-end',
- right: extraRightSpace + (width - 20) / 2,
- };
- }
-
- let triangleDown = null;
- let triangleUp = null;
- if (computedTooltipLocation === 'above') {
- triangleDown = ;
- } else if (computedTooltipLocation === 'below') {
- triangleUp = ;
- }
-
- invariant(overlayContext, 'Tooltip should have OverlayContext');
- const { position } = overlayContext;
- invariant(position, 'position should be defined in tooltip');
-
- const isOpeningSidebar = !!chatContext?.currentTransitionSidebarSourceID;
-
- const buttonProps: ButtonProps = {
- ...navAndRouteForFlow,
- progress: position,
- isOpeningSidebar,
- };
-
- const itemsStyles = [styles.items, styles.itemsFixed];
-
- let tooltip = null;
-
- if (computedTooltipLocation !== 'fixed') {
- tooltip = (
-
- {triangleUp}
- {items}
- {triangleDown}
-
- );
- } else if (
- computedTooltipLocation === 'fixed' &&
- !this.props.route.params.hideTooltip
- ) {
- tooltip = (
-
- {items}
-
- );
- }
-
- return (
-
-
-
-
-
-
-
-
- {tooltip}
-
-
- );
- }
- }
- function ConnectedTooltip(
+): React.ComponentType {
+ function Tooltip(
props: $ReadOnly<{
- ...BaseTooltipPropsType,
+ ...TooltipPropsType,
+hideTooltip: () => mixed,
}>,
) {
const dimensions = useSelector(state => state.dimensions);
const overlayContext = React.useContext(OverlayContext);
const chatContext = React.useContext(ChatContext);
const { params } = props.route;
const { tooltipLocation } = params;
const isFixed = tooltipLocation === 'fixed';
- const { hideTooltip, ...rest } = props;
+ const { hideTooltip, ...navAndRouteForFlow } = props;
React.useEffect(() => {
Haptics.impactAsync();
}, []);
const { goBackOnce } = props.navigation;
const closeTooltip = React.useCallback(() => {
goBackOnce();
if (isFixed) {
hideTooltip();
}
}, [isFixed, hideTooltip, goBackOnce]);
const styles = useStyles(unboundStyles);
const boundTooltipItem = React.useCallback(
(innerProps: TooltipItemBaseProps) => {
const containerStyle = isFixed
? [styles.itemContainer, styles.itemContainerFixed]
: styles.itemContainer;
return (
);
},
[isFixed, styles, closeTooltip],
);
const tooltipContext = React.useContext(TooltipContext);
invariant(tooltipContext, 'TooltipContext should be set in Tooltip');
const margin = React.useMemo(() => {
const customMargin = params.margin;
return customMargin !== null && customMargin !== undefined
? customMargin
: 20;
}, [params.margin]);
const tooltipHeight = React.useMemo(() => {
if (tooltipLocation === 'fixed') {
return fixedTooltipHeight;
} else {
return getTooltipHeight(tooltipContext.getNumVisibleEntries());
}
}, [tooltipLocation, tooltipContext]);
const computedTooltipLocation = React.useMemo(() => {
if (tooltipLocation) {
return tooltipLocation;
}
const { initialCoordinates, verticalBounds } = params;
const { y, height } = initialCoordinates;
const contentTop = y;
const contentBottom = y + height;
const boundsTop = verticalBounds.y;
const boundsBottom = verticalBounds.y + verticalBounds.height;
const fullHeight = tooltipHeight + margin;
if (
contentBottom + fullHeight > boundsBottom &&
contentTop - fullHeight > boundsTop
) {
return 'above';
}
return 'below';
}, [margin, tooltipHeight, params, tooltipLocation]);
invariant(overlayContext, 'Tooltip should have OverlayContext');
const { position } = overlayContext;
invariant(position, 'position should be defined in tooltip');
const backdropOpacity = React.useMemo(
() =>
interpolateNode(position, {
inputRange: [0, 1],
outputRange: [0, 0.7],
extrapolate: Extrapolate.CLAMP,
}),
[position],
);
const tooltipContainerOpacity = React.useMemo(
() =>
interpolateNode(position, {
inputRange: [0, 0.1],
outputRange: [0, 1],
extrapolate: Extrapolate.CLAMP,
}),
[position],
);
const tooltipVerticalAbove = React.useMemo(
() =>
interpolateNode(position, {
inputRange: [0, 1],
outputRange: [margin + tooltipHeight / 2, 0],
extrapolate: Extrapolate.CLAMP,
}),
[margin, tooltipHeight, position],
);
const tooltipVerticalBelow = React.useMemo(
() =>
interpolateNode(position, {
inputRange: [0, 1],
outputRange: [-margin - tooltipHeight / 2, 0],
extrapolate: Extrapolate.CLAMP,
}),
[margin, tooltipHeight, position],
);
const invertedPosition = React.useMemo(
() => add(1, multiply(-1, position)),
[position],
);
const tooltipHorizontalOffset = React.useRef(new Value(0));
const tooltipHorizontal = React.useMemo(
() => multiply(invertedPosition, tooltipHorizontalOffset.current),
[invertedPosition],
);
const tooltipScale = React.useMemo(
() =>
interpolateNode(position, {
inputRange: [0, 0.2, 0.8, 1],
outputRange: [0, 0, 1, 1],
extrapolate: Extrapolate.CLAMP,
}),
[position],
);
const fixedTooltipVertical = React.useMemo(
() => multiply(invertedPosition, dimensions.height),
[dimensions.height, invertedPosition],
);
const opacityStyle: AnimatedViewStyle = React.useMemo(() => {
return {
...styles.backdrop,
opacity: backdropOpacity,
};
}, [backdropOpacity, styles.backdrop]);
const contentContainerStyle: ViewStyle = React.useMemo(() => {
const { verticalBounds } = params;
const fullScreenHeight = dimensions.height;
const top = verticalBounds.y;
const bottom =
fullScreenHeight - verticalBounds.y - verticalBounds.height;
return {
...styles.contentContainer,
marginTop: top,
marginBottom: bottom,
};
}, [dimensions.height, params, styles.contentContainer]);
const buttonStyle: ViewStyle = React.useMemo(() => {
const { initialCoordinates, verticalBounds } = params;
const { x, y, width, height } = initialCoordinates;
return {
width: Math.ceil(width),
height: Math.ceil(height),
marginTop: y - verticalBounds.y,
marginLeft: x,
};
}, [params]);
const tooltipContainerStyle: AnimatedViewStyle = React.useMemo(() => {
const { initialCoordinates, verticalBounds, chatInputBarHeight } = params;
const { x, y, width, height } = initialCoordinates;
const style: WritableAnimatedStyleObj = {};
style.position = 'absolute';
style.alignItems = 'center';
style.opacity = tooltipContainerOpacity;
const transform: Array = [];
if (computedTooltipLocation !== 'fixed') {
transform.push({ translateX: tooltipHorizontal });
}
const extraLeftSpace = x;
const extraRightSpace = dimensions.width - width - x;
if (extraLeftSpace < extraRightSpace) {
style.left = 0;
style.minWidth = width + 2 * extraLeftSpace;
} else {
style.right = 0;
style.minWidth = width + 2 * extraRightSpace;
}
const inputBarHeight = chatInputBarHeight ?? 0;
if (computedTooltipLocation === 'fixed') {
const padding = 8;
style.minWidth = dimensions.width - 16;
style.left = 8;
style.right = 8;
style.bottom =
dimensions.height -
verticalBounds.height -
verticalBounds.y -
inputBarHeight +
padding;
transform.push({ translateY: fixedTooltipVertical });
} else if (computedTooltipLocation === 'above') {
style.bottom =
dimensions.height - Math.max(y, verticalBounds.y) + margin;
transform.push({ translateY: tooltipVerticalAbove });
} else {
style.top =
Math.min(y + height, verticalBounds.y + verticalBounds.height) +
margin;
transform.push({ translateY: tooltipVerticalBelow });
}
if (computedTooltipLocation !== 'fixed') {
transform.push({ scale: tooltipScale });
}
style.transform = transform;
return style;
}, [
dimensions.height,
dimensions.width,
fixedTooltipVertical,
margin,
computedTooltipLocation,
params,
tooltipContainerOpacity,
tooltipHorizontal,
tooltipScale,
tooltipVerticalAbove,
tooltipVerticalBelow,
]);
const onPressMore = React.useCallback(() => {
Keyboard.dismiss();
tooltipContext.showActionSheet();
}, [tooltipContext]);
const renderMoreIcon = React.useCallback((): React.Node => {
return (
);
}, [styles.icon]);
const onTooltipContainerLayout = React.useCallback(
(event: LayoutEvent) => {
const { x, width } = params.initialCoordinates;
const extraLeftSpace = x;
const extraRightSpace = dimensions.width - width - x;
const actualWidth = event.nativeEvent.layout.width;
if (extraLeftSpace < extraRightSpace) {
const minWidth = width + 2 * extraLeftSpace;
tooltipHorizontalOffset.current.setValue(
(minWidth - actualWidth) / 2,
);
} else {
const minWidth = width + 2 * extraRightSpace;
tooltipHorizontalOffset.current.setValue(
(actualWidth - minWidth) / 2,
);
}
},
[dimensions.width, params.initialCoordinates],
);
+ const tooltipItemContainerStyle: Array = [styles.itemContainer];
+
+ if (computedTooltipLocation === 'fixed') {
+ tooltipItemContainerStyle.push(styles.itemContainerFixed);
+ }
+
+ const items: Array = [
+ ,
+ ];
+
+ if (tooltipContext.shouldShowMore()) {
+ items.push(
+ ,
+ );
+ }
+
+ let triangleStyle;
+ const { initialCoordinates } = params;
+ const { x, width } = initialCoordinates;
+ const extraLeftSpace = x;
+ const extraRightSpace = dimensions.width - width - x;
+ if (extraLeftSpace < extraRightSpace) {
+ triangleStyle = {
+ alignSelf: 'flex-start',
+ left: extraLeftSpace + (width - 20) / 2,
+ };
+ } else {
+ triangleStyle = {
+ alignSelf: 'flex-end',
+ right: extraRightSpace + (width - 20) / 2,
+ };
+ }
+
+ let triangleDown = null;
+ let triangleUp = null;
+ if (computedTooltipLocation === 'above') {
+ triangleDown = ;
+ } else if (computedTooltipLocation === 'below') {
+ triangleUp = ;
+ }
+
+ const isOpeningSidebar = !!chatContext?.currentTransitionSidebarSourceID;
+
+ const buttonProps: ButtonProps = {
+ ...navAndRouteForFlow,
+ progress: position,
+ isOpeningSidebar,
+ };
+
+ const itemsStyles = [styles.items, styles.itemsFixed];
+
+ let tooltip = null;
+
+ if (computedTooltipLocation !== 'fixed') {
+ tooltip = (
+
+ {triangleUp}
+ {items}
+ {triangleDown}
+
+ );
+ } else if (computedTooltipLocation === 'fixed' && !params.hideTooltip) {
+ tooltip = (
+
+ {items}
+
+ );
+ }
+
return (
-
+
+
+
+
+
+
+
+
+ {tooltip}
+
+
);
}
- function MemoizedTooltip(props: BaseTooltipPropsType) {
+ function MemoizedTooltip(props: TooltipPropsType) {
const { visibleEntryIDs } = props.route.params;
const { goBackOnce } = props.navigation;
const { setParams } = props.navigation;
const hideTooltip = React.useCallback(() => {
const paramsUpdate: any = { hideTooltip: true };
setParams(paramsUpdate);
}, [setParams]);
return (
-
+
);
}
- return React.memo(MemoizedTooltip);
+ return React.memo(MemoizedTooltip);
}
function getTooltipHeight(numEntries: number): number {
// 10 (triangle) + 37 * numEntries (entries) + numEntries - 1 (padding)
return 9 + 38 * numEntries;
}
const fixedTooltipHeight: number = 53;
export { createTooltip, fixedTooltipHeight };