diff --git a/src/components/MenuItem.tsx b/src/components/MenuItem.tsx index fa58b5cd5f5f..2667eca0833c 100644 --- a/src/components/MenuItem.tsx +++ b/src/components/MenuItem.tsx @@ -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'; @@ -290,8 +291,8 @@ 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; @@ -383,7 +384,7 @@ function MenuItem( onBlur, avatarID, shouldRenderTooltip = false, - shouldForceRenderingTooltipLeft = false, + tooltipAnchorAlignment, tooltipWrapperStyle = {}, renderTooltipContent, }: MenuItemProps, @@ -490,7 +491,7 @@ function MenuItem( )} diff --git a/src/components/Tooltip/BaseGenericTooltip/index.native.tsx b/src/components/Tooltip/BaseGenericTooltip/index.native.tsx index e5a9b873dbc0..c4ce198cf5ae 100644 --- a/src/components/Tooltip/BaseGenericTooltip/index.native.tsx +++ b/src/components/Tooltip/BaseGenericTooltip/index.native.tsx @@ -25,7 +25,6 @@ function BaseGenericTooltip({ maxWidth = 0, renderTooltipContent, shouldForceRenderingBelow = false, - shouldForceRenderingLeft = false, wrapperStyle = {}, }: BaseGenericTooltipProps) { // The width of tooltip's inner content. Has to be undefined in the beginning @@ -66,7 +65,6 @@ function BaseGenericTooltip({ manualShiftHorizontal: shiftHorizontal, manualShiftVertical: shiftVertical, shouldForceRenderingBelow, - shouldForceRenderingLeft, wrapperStyle, }), [ @@ -83,7 +81,6 @@ function BaseGenericTooltip({ shiftHorizontal, shiftVertical, shouldForceRenderingBelow, - shouldForceRenderingLeft, wrapperStyle, ], ); diff --git a/src/components/Tooltip/BaseGenericTooltip/index.tsx b/src/components/Tooltip/BaseGenericTooltip/index.tsx index bb02e17f07d9..e41e4eeea26f 100644 --- a/src/components/Tooltip/BaseGenericTooltip/index.tsx +++ b/src/components/Tooltip/BaseGenericTooltip/index.tsx @@ -3,6 +3,7 @@ import ReactDOM from 'react-dom'; import {Animated, View} from 'react-native'; 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'; @@ -27,7 +28,10 @@ function BaseGenericTooltip({ renderTooltipContent, shouldForceRenderingBelow = false, wrapperStyle = {}, - shouldForceRenderingLeft = false, + anchorAlignment = { + horizontal: CONST.MODAL.ANCHOR_ORIGIN_HORIZONTAL.CENTER, + vertical: CONST.MODAL.ANCHOR_ORIGIN_VERTICAL.BOTTOM, + }, }: 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, @@ -63,7 +67,7 @@ function BaseGenericTooltip({ manualShiftHorizontal: shiftHorizontal, manualShiftVertical: shiftVertical, shouldForceRenderingBelow, - shouldForceRenderingLeft, + anchorAlignment, wrapperStyle, }), [ @@ -80,7 +84,7 @@ function BaseGenericTooltip({ shiftHorizontal, shiftVertical, shouldForceRenderingBelow, - shouldForceRenderingLeft, + anchorAlignment, wrapperStyle, ], ); diff --git a/src/components/Tooltip/BaseGenericTooltip/types.ts b/src/components/Tooltip/BaseGenericTooltip/types.ts index 662905fc1ec6..35624e54d78f 100644 --- a/src/components/Tooltip/BaseGenericTooltip/types.ts +++ b/src/components/Tooltip/BaseGenericTooltip/types.ts @@ -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 */ @@ -27,7 +27,7 @@ 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; +} & Pick; // eslint-disable-next-line import/prefer-default-export export type {BaseGenericTooltipProps}; diff --git a/src/components/Tooltip/GenericTooltip.tsx b/src/components/Tooltip/GenericTooltip.tsx index 2b48fa91141f..4be9c1422380 100644 --- a/src/components/Tooltip/GenericTooltip.tsx +++ b/src/components/Tooltip/GenericTooltip.tsx @@ -29,7 +29,10 @@ function GenericTooltip({ shiftVertical = 0, shouldForceRenderingBelow = false, wrapperStyle = {}, - shouldForceRenderingLeft = false, + anchorAlignment = { + horizontal: CONST.MODAL.ANCHOR_ORIGIN_HORIZONTAL.CENTER, + vertical: CONST.MODAL.ANCHOR_ORIGIN_VERTICAL.BOTTOM, + }, shouldForceAnimate = false, }: GenericTooltipProps) { const {preferredLocale} = useLocalize(); @@ -164,7 +167,7 @@ function GenericTooltip({ key={[text, ...renderTooltipContentKey, preferredLocale].join('-')} shouldForceRenderingBelow={shouldForceRenderingBelow} wrapperStyle={wrapperStyle} - shouldForceRenderingLeft={shouldForceRenderingLeft} + anchorAlignment={anchorAlignment} /> )} diff --git a/src/components/Tooltip/types.ts b/src/components/Tooltip/types.ts index cf2218abf5b3..aba8567b2125 100644 --- a/src/components/Tooltip/types.ts +++ b/src/components/Tooltip/types.ts @@ -1,6 +1,7 @@ import type {ReactNode} from 'react'; import type React from 'react'; import type {LayoutRectangle, StyleProp, ViewStyle} from 'react-native'; +import type {TooltipAnchorAlignment} from '@src/types/utils/AnchorAlignment'; import type ChildrenProps from '@src/types/utils/ChildrenProps'; type SharedTooltipProps = { @@ -27,8 +28,8 @@ type SharedTooltipProps = { /** Unique key of renderTooltipContent to rerender the tooltip when one of the key changes */ renderTooltipContentKey?: string[]; - /** Whether to left align the tooltip relative to wrapped component */ - shouldForceRenderingLeft?: boolean; + /** The anchor alignment of the tooltip */ + anchorAlignment?: TooltipAnchorAlignment; /** Whether to display tooltip below the wrapped component */ shouldForceRenderingBelow?: boolean; @@ -64,7 +65,7 @@ type TooltipProps = ChildrenProps & shouldHandleScroll?: boolean; }; -type EducationalTooltipProps = ChildrenProps & TooltipProps; +type EducationalTooltipProps = ChildrenProps & SharedTooltipProps; type TooltipExtendedProps = (EducationalTooltipProps | TooltipProps) & { /** Whether the actual Tooltip should be rendered. If false, it's just going to return the children */ @@ -72,4 +73,4 @@ type TooltipExtendedProps = (EducationalTooltipProps | TooltipProps) & { }; export default TooltipProps; -export type {EducationalTooltipProps, GenericTooltipProps, TooltipExtendedProps}; +export type {EducationalTooltipProps, GenericTooltipProps, SharedTooltipProps, TooltipExtendedProps}; diff --git a/src/pages/home/sidebar/SidebarScreen/FloatingActionButtonAndPopover.tsx b/src/pages/home/sidebar/SidebarScreen/FloatingActionButtonAndPopover.tsx index ff5cfa05b57b..3fe4fa6c2aa6 100644 --- a/src/pages/home/sidebar/SidebarScreen/FloatingActionButtonAndPopover.tsx +++ b/src/pages/home/sidebar/SidebarScreen/FloatingActionButtonAndPopover.tsx @@ -471,8 +471,11 @@ function FloatingActionButtonAndPopover( numberOfLinesDescription: 1, onSelected: () => interceptAnonymousUser(() => navigateToQuickAction()), shouldShowSubscriptRightAvatar: ReportUtils.isPolicyExpenseChat(quickActionReport), - shouldRenderTooltip: quickAction?.isFirstQuickAction, - shouldForceRenderingTooltipLeft: true, + shouldRenderTooltip: true, + tooltipAnchorAlignment: { + vertical: CONST.MODAL.ANCHOR_ORIGIN_VERTICAL.BOTTOM, + horizontal: CONST.MODAL.ANCHOR_ORIGIN_HORIZONTAL.LEFT, + }, renderTooltipContent: renderQuickActionTooltip, tooltipWrapperStyle: styles.quickActionTooltipWrapper, }, diff --git a/src/styles/utils/generators/TooltipStyleUtils/index.ts b/src/styles/utils/generators/TooltipStyleUtils/index.ts index 846848ab25bd..8e98c5b23218 100644 --- a/src/styles/utils/generators/TooltipStyleUtils/index.ts +++ b/src/styles/utils/generators/TooltipStyleUtils/index.ts @@ -11,6 +11,7 @@ import spacing from '@styles/utils/spacing'; // eslint-disable-next-line no-restricted-imports import titleBarHeight from '@styles/utils/titleBarHeight'; import variables from '@styles/variables'; +import CONST from '@src/CONST'; import type {GetTooltipStylesStyleUtil} from './types'; /** This defines the proximity with the edge of the window in which tooltips should not be displayed. @@ -120,7 +121,7 @@ function isOverlappingAtTop(tooltip: View | HTMLDivElement, xOffset: number, yOf * @param [manualShiftVertical] - Any additional amount to manually shift the tooltip up or down. * A positive value shifts it down, and a negative value shifts it up. * @param [shouldForceRenderingBelow] - Should display tooltip below the wrapped component. - * @param [shouldForceRenderingLeft] - Align the tooltip left relative to the wrapped component instead of horizontally align center. + * @param [anchorAlignment] - Align tooltip anchor horizontally and vertically. * @param [wrapperStyle] - Any additional styles for the root wrapper. */ const createTooltipStyleUtils: StyleUtilGenerator = ({theme, styles}) => ({ @@ -138,7 +139,10 @@ const createTooltipStyleUtils: StyleUtilGenerator = ( manualShiftHorizontal = 0, manualShiftVertical = 0, shouldForceRenderingBelow = false, - shouldForceRenderingLeft = false, + anchorAlignment = { + horizontal: CONST.MODAL.ANCHOR_ORIGIN_HORIZONTAL.CENTER, + vertical: CONST.MODAL.ANCHOR_ORIGIN_VERTICAL.BOTTOM, + }, wrapperStyle = {}, }) => { const customWrapperStyle = StyleSheet.flatten(wrapperStyle); @@ -171,7 +175,8 @@ const createTooltipStyleUtils: StyleUtilGenerator = ( shouldShowBelow = shouldForceRenderingBelow || yOffset - tooltipHeight - POINTER_HEIGHT < GUTTER_WIDTH + titleBarHeight || - !!(tooltip && isOverlappingAtTop(tooltip, xOffset, yOffset, tooltipTargetWidth, tooltipTargetHeight)); + !!(tooltip && isOverlappingAtTop(tooltip, xOffset, yOffset, tooltipTargetWidth, tooltipTargetHeight)) || + anchorAlignment.vertical === CONST.MODAL.ANCHOR_ORIGIN_VERTICAL.TOP; // When the tooltip size is ready, we can start animating the scale. scale = currentSize; @@ -202,22 +207,6 @@ const createTooltipStyleUtils: StyleUtilGenerator = ( : // We need to shift the tooltip up above the component. So shift the tooltip up (-) by... yOffset - (tooltipHeight + POINTER_HEIGHT) + manualShiftVertical; - // Next, we'll position it horizontally. - // we will use xOffset to position the tooltip relative to the Wrapped Component - // To shift the tooltip right, we'll give `left` a positive value. - // To shift the tooltip left, we'll give `left` a negative value. - // - // So we'll: - // 1a) Horizontally align left: No need for shifting. - // 1b) Horizontally align center: - // - Shift the tooltip right (+) to the center of the component, - // so the left edge lines up with the component center. - // - Shift it left (-) to by half the tooltip's width, - // so the tooltip's center lines up with the center of the wrapped component. - // 2) Add the horizontal shift (left or right) computed above to keep it out of the gutters. - // 3) Lastly, add the manual horizontal shift passed in as a parameter. - rootWrapperLeft = xOffset + (shouldForceRenderingLeft ? 0 : tooltipTargetWidth / 2 - tooltipWidth / 2) + horizontalShift + manualShiftHorizontal; - // By default, the pointer's top-left will align with the top-left of the tooltip wrapper. // // To align it vertically, we'll: @@ -228,6 +217,22 @@ const createTooltipStyleUtils: StyleUtilGenerator = ( // so that the bottom of the pointer lines up with the top of the tooltip pointerWrapperTop = shouldShowBelow ? -POINTER_HEIGHT : tooltipHeight; + // Horizontal tooltip position: + // we will use xOffset to position the tooltip relative to the Wrapped Component + // To shift the tooltip right, we'll give `left` a positive value. + // To shift the tooltip left, we'll give `left` a negative value. + // + // So we'll: + // 1) Add the horizontal shift (left or right) computed above to keep it out of the gutters. + // 2) Add the manual horizontal shift passed in as a parameter. + // 3a) Horizontally align left: No need for shifting. + // 3b) Horizontally align center: + // - Shift the tooltip right (+) to the center of the component, + // so the left edge lines up with the component center. + // - Shift it left (-) to by half the tooltip's width, + // so the tooltip's center lines up with the center of the wrapped component. + + // Horizontal pointer position: // 1) Left align: Shift the pointer to the right (+) by half the pointer's width, // so the left edge of the pointer does not overlap with the wrapper's border radius. // 2) Center align: @@ -237,7 +242,20 @@ const createTooltipStyleUtils: StyleUtilGenerator = ( // so the pointer's center lines up with the tooltipWidth's center. // - Remove the wrapper's horizontalShift to maintain the pointer // at the center of the hovered component. - pointerWrapperLeft = shouldForceRenderingLeft ? POINTER_WIDTH / 2 : horizontalShiftPointer + (tooltipWidth / 2 - POINTER_WIDTH / 2); + rootWrapperLeft = xOffset + horizontalShift + manualShiftHorizontal; + switch (anchorAlignment.horizontal) { + case CONST.MODAL.ANCHOR_ORIGIN_HORIZONTAL.LEFT: + pointerWrapperLeft = POINTER_WIDTH / 2; + break; + case CONST.MODAL.ANCHOR_ORIGIN_HORIZONTAL.RIGHT: + pointerWrapperLeft = horizontalShiftPointer + (tooltipWidth - POINTER_WIDTH * 1.5); + rootWrapperLeft += tooltipTargetWidth - tooltipWidth; + break; + case CONST.MODAL.ANCHOR_ORIGIN_HORIZONTAL.CENTER: + default: + pointerWrapperLeft = horizontalShiftPointer + (tooltipWidth / 2 - POINTER_WIDTH / 2); + rootWrapperLeft += tooltipTargetWidth / 2 - tooltipWidth / 2; + } pointerAdditionalStyle = shouldShowBelow ? styles.flipUpsideDown : {}; } diff --git a/src/styles/utils/generators/TooltipStyleUtils/types.ts b/src/styles/utils/generators/TooltipStyleUtils/types.ts index 1907309e1bf5..7965ec151485 100644 --- a/src/styles/utils/generators/TooltipStyleUtils/types.ts +++ b/src/styles/utils/generators/TooltipStyleUtils/types.ts @@ -1,4 +1,5 @@ import type {Animated, StyleProp, TextStyle, View, ViewStyle} from 'react-native'; +import type {TooltipAnchorAlignment} from '@src/types/utils/AnchorAlignment'; type TooltipStyles = { animationStyle: ViewStyle; @@ -24,6 +25,7 @@ type TooltipParams = { shouldForceRenderingBelow?: boolean; shouldForceRenderingLeft?: boolean; wrapperStyle: StyleProp; + anchorAlignment?: TooltipAnchorAlignment; }; type GetTooltipStylesStyleUtil = {getTooltipStyles: (props: TooltipParams) => TooltipStyles}; diff --git a/src/types/utils/AnchorAlignment.ts b/src/types/utils/AnchorAlignment.ts index 899e3d9e277b..5ed043d36b18 100644 --- a/src/types/utils/AnchorAlignment.ts +++ b/src/types/utils/AnchorAlignment.ts @@ -9,4 +9,13 @@ type AnchorAlignment = { vertical: ValueOf; }; +type TooltipAnchorAlignment = { + /** The horizontal anchor alignment of the tooltip */ + horizontal: ValueOf; + + /** The vertical anchor alignment of the tooltip */ + vertical: Exclude, typeof CONST.MODAL.ANCHOR_ORIGIN_VERTICAL.CENTER>; +}; + +export type {TooltipAnchorAlignment}; export default AnchorAlignment;