Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature: Tooltip for workspace chat #45390

Merged
merged 47 commits into from
Aug 7, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
47 commits
Select commit Hold shift + click to select a range
cc1ee1f
feature: align tooltip by anchorAlignment on web
tienifr Jun 24, 2024
f6c3290
make anchorAlignment work on native
tienifr Jun 24, 2024
dfbda8d
Merge branch 'main' of https://github.com/tienifr/App into feature/to…
tienifr Jul 2, 2024
404a3a7
Merge branch 'main' of https://github.com/tienifr/App into feature/to…
tienifr Jul 3, 2024
cfd72f1
apply tooltip to lhn row
tienifr Jul 3, 2024
16f1bc0
Merge branch 'main' of https://github.com/tienifr/App into feature/to…
tienifr Jul 5, 2024
73d1599
Merge branch 'main' of https://github.com/tienifr/App into feature/to…
tienifr Jul 11, 2024
8353e39
add copy and icon
tienifr Jul 12, 2024
a0a183d
Merge branch 'main' of https://github.com/tienifr/App into feature/to…
tienifr Jul 15, 2024
e26e63c
hide tooltip when navigate
tienifr Jul 15, 2024
da051d6
fix test & perf
tienifr Jul 15, 2024
c8829de
revert: apply tooltip to lhn row
tienifr Jul 15, 2024
5de4ac7
implement tooltip ui
tienifr Jul 15, 2024
6b8e66c
Merge branch 'main' of https://github.com/tienifr/App into feature/to…
tienifr Jul 26, 2024
578834f
add nvp
tienifr Jul 26, 2024
6c3ad40
hide tooltip when press add or navigate to other pages
tienifr Jul 26, 2024
7440641
update Spanish copy
tienifr Jul 26, 2024
84e49a1
Merge branch 'main' of https://github.com/tienifr/App into feature/to…
tienifr Jul 29, 2024
0051bf1
dismiss tooltip
tienifr Jul 29, 2024
1c9b6c8
refactor shouldShow logic
tienifr Jul 29, 2024
7b204cb
hide tooltip on navigating
tienifr Jul 30, 2024
5a671d4
Merge branch 'main' of https://github.com/tienifr/App into feature/to…
tienifr Jul 31, 2024
1d1599e
fix: tooltip position on safari is incorrect when the page is transit…
tienifr Jul 31, 2024
99fc8bc
fix lint
tienifr Jul 31, 2024
4a79980
define shift in variables
tienifr Jul 31, 2024
be30303
export type
tienifr Jul 31, 2024
12a7df6
Merge branch 'main' of https://github.com/tienifr/App into feature/to…
tienifr Aug 1, 2024
6bf01c9
get onyx workspaceTooltip
tienifr Aug 1, 2024
ed6c7ed
fix: navigation object couldn't be found
tienifr Aug 1, 2024
af719c9
Merge branch 'main' of https://github.com/tienifr/App into feature/to…
tienifr Aug 2, 2024
fdacac4
rename resetSuggestions to onPress
tienifr Aug 2, 2024
27fd8dd
implement press anywhere to dismiss tooltip
tienifr Aug 2, 2024
5c4b812
fix tooltip position on native
tienifr Aug 2, 2024
0591c4c
add comments
tienifr Aug 2, 2024
78d43b8
Merge branch 'main' of https://github.com/tienifr/App into feature/to…
tienifr Aug 5, 2024
8c8cd2d
Merge branch 'main' of https://github.com/tienifr/App into feature/to…
tienifr Aug 6, 2024
04c4751
fix: android tooltip broken
tienifr Aug 6, 2024
63be42d
fix: wrong tooltip position for animated parents
tienifr Aug 6, 2024
def069f
revert the FullWindowOverlay approach
tienifr Aug 6, 2024
a5d35b0
QAB
tienifr Aug 6, 2024
f6a013c
fix lint
tienifr Aug 6, 2024
602bec7
delay measure
tienifr Aug 7, 2024
bef2353
fix: composer flickers when tap to hide tooltip
tienifr Aug 7, 2024
4b6d7e4
fix lint
tienifr Aug 7, 2024
a79ad14
Merge branch 'main' of https://github.com/tienifr/App into feature/to…
tienifr Aug 7, 2024
d106777
polish settimeout callback
tienifr Aug 7, 2024
b83cb82
fix lint
tienifr Aug 7, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions src/ONYXKEYS.ts
Original file line number Diff line number Diff line change
Expand Up @@ -205,6 +205,9 @@ const ONYXKEYS = {
/** The end date (epoch timestamp) of the workspace owner’s grace period after the free trial ends. */
NVP_PRIVATE_OWNER_BILLING_GRACE_PERIOD_END: 'nvp_private_billingGracePeriodEnd',

/** The NVP containing all information related to educational tooltip in workspace chat */
NVP_WORKSPACE_TOOLTIP: 'workspaceTooltip',

/** Does this user have push notifications enabled for this device? */
PUSH_NOTIFICATIONS_ENABLED: 'pushNotificationsEnabled',

Expand Down Expand Up @@ -873,6 +876,7 @@ type OnyxValuesMapping = {
[ONYXKEYS.NVP_BILLING_FUND_ID]: number;
[ONYXKEYS.NVP_PRIVATE_AMOUNT_OWED]: number;
[ONYXKEYS.NVP_PRIVATE_OWNER_BILLING_GRACE_PERIOD_END]: number;
[ONYXKEYS.NVP_WORKSPACE_TOOLTIP]: OnyxTypes.WorkspaceTooltip;
[ONYXKEYS.NVP_PRIVATE_CANCELLATION_DETAILS]: OnyxTypes.CancellationDetails[];
[ONYXKEYS.APPROVAL_WORKFLOW]: OnyxTypes.ApprovalWorkflow;
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,21 +8,21 @@ import useThemeStyles from '@hooks/useThemeStyles';
import CONST from '@src/CONST';

type TransparentOverlayProps = {
resetSuggestions: () => void;
onPress: () => void;
};

type OnPressHandler = PressableProps['onPress'];

function TransparentOverlay({resetSuggestions}: TransparentOverlayProps) {
function TransparentOverlay({onPress: onPressProp}: TransparentOverlayProps) {
const {translate} = useLocalize();
const styles = useThemeStyles();

const onResetSuggestions = useCallback<NonNullable<OnPressHandler>>(
const onPress = useCallback<NonNullable<OnPressHandler>>(
(event) => {
event?.preventDefault();
resetSuggestions();
onPressProp();
},
[resetSuggestions],
[onPressProp],
);

const handlePointerDown = useCallback((e: PointerEvent) => {
Expand All @@ -35,7 +35,7 @@ function TransparentOverlay({resetSuggestions}: TransparentOverlayProps) {
style={styles.fullScreen}
>
<PressableWithoutFeedback
onPress={onResetSuggestions}
onPress={onPress}
style={[styles.flex1, styles.cursorDefault]}
accessibilityLabel={translate('common.close')}
role={CONST.ROLE.BUTTON}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ function AutoCompleteSuggestionsPortal<TSuggestion>({left = 0, width = 0, bottom

return (
<Portal hostName="suggestions">
<TransparentOverlay resetSuggestions={resetSuggestions} />
<TransparentOverlay onPress={resetSuggestions} />
<View style={styles}>
{/* eslint-disable-next-line react/jsx-props-no-spreading */}
<BaseAutoCompleteSuggestions<TSuggestion>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ function AutoCompleteSuggestionsPortal<TSuggestion>({
bodyElement &&
ReactDOM.createPortal(
<>
<TransparentOverlay resetSuggestions={resetSuggestions} />
<TransparentOverlay onPress={resetSuggestions} />
<View style={StyleUtils.getBaseAutoCompleteSuggestionContainerStyle({left, width, bottom: bottom - getBottomSuggestionPadding()})}>{componentToRender}</View>
</>,
bodyElement,
Expand Down
22 changes: 16 additions & 6 deletions src/components/MenuItem.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import variables from '@styles/variables';
import * as Session from '@userActions/Session';
import CONST from '@src/CONST';
import type {Icon as IconType} from '@src/types/onyx/OnyxCommon';
import type {TooltipAnchorAlignment} from '@src/types/utils/AnchorAlignment';
import type IconAsset from '@src/types/utils/IconAsset';
import Avatar from './Avatar';
import Badge from './Badge';
Expand Down Expand Up @@ -299,12 +300,18 @@ type MenuItemBaseProps = {
/** Whether to show the tooltip */
shouldRenderTooltip?: boolean;

/** Whether to align the tooltip left */
shouldForceRenderingTooltipLeft?: boolean;
/** Anchor alignment of the tooltip */
tooltipAnchorAlignment?: TooltipAnchorAlignment;

/** Additional styles for tooltip wrapper */
tooltipWrapperStyle?: StyleProp<ViewStyle>;

/** Any additional amount to manually adjust the horizontal position of the tooltip */
tooltipShiftHorizontal?: number;

/** Any additional amount to manually adjust the vertical position of the tooltip */
tooltipShiftVertical?: number;

/** Render custom content inside the tooltip. */
renderTooltipContent?: () => ReactNode;

Expand Down Expand Up @@ -398,8 +405,10 @@ function MenuItem(
onBlur,
avatarID,
shouldRenderTooltip = false,
shouldForceRenderingTooltipLeft = false,
tooltipAnchorAlignment,
tooltipWrapperStyle = {},
tooltipShiftHorizontal = 0,
tooltipShiftVertical = 0,
renderTooltipContent,
}: MenuItemProps,
ref: PressableRef,
Expand Down Expand Up @@ -521,11 +530,12 @@ function MenuItem(
)}
<EducationalTooltip
shouldRender={shouldRenderTooltip}
shouldForceRenderingLeft={shouldForceRenderingTooltipLeft}
anchorAlignment={tooltipAnchorAlignment}
renderTooltipContent={renderTooltipContent}
wrapperStyle={tooltipWrapperStyle}
shiftHorizontal={styles.popoverMenuItem.paddingHorizontal}
shiftVertical={styles.popoverMenuItem.paddingVertical / 2}
shiftHorizontal={tooltipShiftHorizontal}
shiftVertical={tooltipShiftVertical}
shouldAutoDismiss
>
<View>
<Hoverable>
Expand Down
2 changes: 2 additions & 0 deletions src/components/Modal/BaseModal.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import {PortalHost} from '@gorhom/portal';
import React, {forwardRef, useCallback, useEffect, useMemo, useRef} from 'react';
import {View} from 'react-native';
import ReactNativeModal from 'react-native-modal';
Expand Down Expand Up @@ -256,6 +257,7 @@ function BaseModal(
customBackdrop={shouldUseCustomBackdrop ? <Overlay onPress={handleBackdropPress} /> : undefined}
>
<ModalContent onDismiss={handleDismissModal}>
<PortalHost name="modal" />
<FocusTrapForModal
active={isVisible}
initialFocus={initialFocus}
Expand Down
4 changes: 3 additions & 1 deletion src/components/PopoverMenu.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -259,7 +259,9 @@ function PopoverMenu({
success={item.success}
containerStyle={item.containerStyle}
shouldRenderTooltip={item.shouldRenderTooltip}
shouldForceRenderingTooltipLeft={item.shouldForceRenderingTooltipLeft}
tooltipAnchorAlignment={item.tooltipAnchorAlignment}
tooltipShiftHorizontal={item.tooltipShiftHorizontal}
tooltipShiftVertical={item.tooltipShiftVertical}
tooltipWrapperStyle={item.tooltipWrapperStyle}
renderTooltipContent={item.renderTooltipContent}
numberOfLinesTitle={item.numberOfLinesTitle}
Expand Down
85 changes: 45 additions & 40 deletions src/components/Tooltip/BaseGenericTooltip/index.native.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
import React, {useEffect, useMemo, useRef, useState} from 'react';
import {Animated, View} from 'react-native';
import {Portal} from '@gorhom/portal';
import React, {useMemo, useRef, useState} from 'react';
import {Animated, InteractionManager, View} from 'react-native';
// eslint-disable-next-line no-restricted-imports
import type {Text as RNText, View as RNView} from 'react-native';
import type {View as RNView} from 'react-native';
import TransparentOverlay from '@components/AutoCompleteSuggestions/AutoCompleteSuggestionsPortal/TransparentOverlay/TransparentOverlay';
import Text from '@components/Text';
import useStyleUtils from '@hooks/useStyleUtils';
import CONST from '@src/CONST';
import type {BaseGenericTooltipProps} from './types';

// Props will change frequently.
Expand All @@ -25,8 +28,13 @@ function BaseGenericTooltip({
maxWidth = 0,
renderTooltipContent,
shouldForceRenderingBelow = false,
shouldForceRenderingLeft = false,
anchorAlignment = {
horizontal: CONST.MODAL.ANCHOR_ORIGIN_HORIZONTAL.CENTER,
vertical: CONST.MODAL.ANCHOR_ORIGIN_VERTICAL.BOTTOM,
},
wrapperStyle = {},
shouldUseOverlay = false,
onPressOverlay = () => {},
}: BaseGenericTooltipProps) {
// The width of tooltip's inner content. Has to be undefined in the beginning
// as a width of 0 will cause the content to be rendered of a width of 0,
Expand All @@ -35,21 +43,10 @@ function BaseGenericTooltip({

// The height of tooltip's wrapper.
const [wrapperMeasuredHeight, setWrapperMeasuredHeight] = useState<number>();
const textContentRef = useRef<RNText>(null);
const viewContentRef = useRef<RNView>(null);
const rootWrapper = useRef<RNView>(null);

const StyleUtils = useStyleUtils();

// Measure content width
useEffect(() => {
if (!textContentRef.current && !viewContentRef.current) {
return;
}
const contentRef = viewContentRef.current ?? textContentRef.current;
contentRef?.measure((x, y, width) => setContentMeasuredWidth(width));
}, []);

const {animationStyle, rootWrapperStyle, textStyle, pointerWrapperStyle, pointerStyle} = useMemo(
() =>
StyleUtils.getTooltipStyles({
Expand All @@ -66,8 +63,9 @@ function BaseGenericTooltip({
manualShiftHorizontal: shiftHorizontal,
manualShiftVertical: shiftVertical,
shouldForceRenderingBelow,
shouldForceRenderingLeft,
anchorAlignment,
wrapperStyle,
shouldAddHorizontalPadding: false,
}),
[
StyleUtils,
Expand All @@ -83,47 +81,54 @@ function BaseGenericTooltip({
shiftHorizontal,
shiftVertical,
shouldForceRenderingBelow,
shouldForceRenderingLeft,
anchorAlignment,
wrapperStyle,
],
);

let content;
if (renderTooltipContent) {
content = <View ref={viewContentRef}>{renderTooltipContent()}</View>;
content = <View>{renderTooltipContent()}</View>;
} else {
content = (
<Text
numberOfLines={numberOfLines}
style={textStyle}
>
<Text
style={textStyle}
ref={textContentRef}
>
{text}
</Text>
<Text style={textStyle}>{text}</Text>
</Text>
);
}

return (
<Animated.View
ref={rootWrapper}
style={[rootWrapperStyle, animationStyle]}
onLayout={(e) => {
const {height} = e.nativeEvent.layout;
if (height === wrapperMeasuredHeight) {
return;
}
setWrapperMeasuredHeight(height);
}}
>
{content}
<View style={pointerWrapperStyle}>
<View style={pointerStyle} />
</View>
</Animated.View>
<Portal hostName={!shouldUseOverlay ? 'modal' : undefined}>
{shouldUseOverlay && <TransparentOverlay onPress={onPressOverlay} />}
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Teleport the portal to the modal layer, otherwise, the tooltip in any modal/popover would be obscured by that modal. For example the tooltip for QAB.

<Animated.View
ref={rootWrapper}
style={[rootWrapperStyle, animationStyle]}
onLayout={(e) => {
const {height} = e.nativeEvent.layout;
if (height === wrapperMeasuredHeight) {
return;
}
setWrapperMeasuredHeight(height);
// When tooltip is used inside an animated view (e.g. popover), we need to wait for the animation to finish before measuring content.
const target = e.target;
setTimeout(() => {
InteractionManager.runAfterInteractions(() => {
target.measure((x, y, width) => {
setContentMeasuredWidth(width);
});
});
}, CONST.ANIMATED_TRANSITION);
}}
>
{content}
<View style={pointerWrapperStyle}>
<View style={pointerStyle} />
</View>
</Animated.View>
</Portal>
);
}

Expand Down
34 changes: 22 additions & 12 deletions src/components/Tooltip/BaseGenericTooltip/index.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
import React, {useLayoutEffect, useMemo, useRef, useState} from 'react';
import ReactDOM from 'react-dom';
import {Animated, View} from 'react-native';
import TransparentOverlay from '@components/AutoCompleteSuggestions/AutoCompleteSuggestionsPortal/TransparentOverlay/TransparentOverlay';
import Text from '@components/Text';
import useStyleUtils from '@hooks/useStyleUtils';
import CONST from '@src/CONST';
import textRef from '@src/types/utils/textRef';
import viewRef from '@src/types/utils/viewRef';
import type {BaseGenericTooltipProps} from './types';
Expand All @@ -27,7 +29,12 @@ function BaseGenericTooltip({
renderTooltipContent,
shouldForceRenderingBelow = false,
wrapperStyle = {},
shouldForceRenderingLeft = false,
anchorAlignment = {
horizontal: CONST.MODAL.ANCHOR_ORIGIN_HORIZONTAL.CENTER,
vertical: CONST.MODAL.ANCHOR_ORIGIN_VERTICAL.BOTTOM,
},
shouldUseOverlay = false,
onPressOverlay = () => {},
}: BaseGenericTooltipProps) {
// The width of tooltip's inner content. Has to be undefined in the beginning
// as a width of 0 will cause the content to be rendered of a width of 0,
Expand Down Expand Up @@ -63,7 +70,7 @@ function BaseGenericTooltip({
manualShiftHorizontal: shiftHorizontal,
manualShiftVertical: shiftVertical,
shouldForceRenderingBelow,
shouldForceRenderingLeft,
anchorAlignment,
wrapperStyle,
}),
[
Expand All @@ -80,7 +87,7 @@ function BaseGenericTooltip({
shiftHorizontal,
shiftVertical,
shouldForceRenderingBelow,
shouldForceRenderingLeft,
anchorAlignment,
wrapperStyle,
],
);
Expand Down Expand Up @@ -111,15 +118,18 @@ function BaseGenericTooltip({
}

return ReactDOM.createPortal(
<Animated.View
ref={viewRef(rootWrapper)}
style={[rootWrapperStyle, animationStyle]}
>
{content}
<View style={pointerWrapperStyle}>
<View style={pointerStyle} />
</View>
</Animated.View>,
<>
{shouldUseOverlay && <TransparentOverlay onPress={onPressOverlay} />}
<Animated.View
ref={viewRef(rootWrapper)}
style={[rootWrapperStyle, animationStyle]}
>
{content}
<View style={pointerWrapperStyle}>
<View style={pointerStyle} />
</View>
</Animated.View>
</>,
body,
);
}
Expand Down
7 changes: 5 additions & 2 deletions src/components/Tooltip/BaseGenericTooltip/types.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import type {Animated} from 'react-native';
import type TooltipProps from '@components/Tooltip/types';
import type {SharedTooltipProps} from '@components/Tooltip/types';

type BaseGenericTooltipProps = {
/** Window width */
Expand Down Expand Up @@ -27,7 +27,10 @@ type BaseGenericTooltipProps = {
/** Any additional amount to manually adjust the vertical position of the tooltip.
A positive value shifts the tooltip down, and a negative value shifts it up. */
shiftVertical?: number;
} & Pick<TooltipProps, 'renderTooltipContent' | 'maxWidth' | 'numberOfLines' | 'text' | 'shouldForceRenderingBelow' | 'wrapperStyle' | 'shouldForceRenderingLeft'>;
} & Pick<
SharedTooltipProps,
'renderTooltipContent' | 'maxWidth' | 'numberOfLines' | 'text' | 'shouldForceRenderingBelow' | 'wrapperStyle' | 'anchorAlignment' | 'shouldUseOverlay' | 'onPressOverlay'
>;

// eslint-disable-next-line import/prefer-default-export
export type {BaseGenericTooltipProps};
Loading
Loading