diff --git a/apps/web/src/components/Menu/UserMenu/WalletInfo.tsx b/apps/web/src/components/Menu/UserMenu/WalletInfo.tsx index 7749dcf057353..d04effb430424 100644 --- a/apps/web/src/components/Menu/UserMenu/WalletInfo.tsx +++ b/apps/web/src/components/Menu/UserMenu/WalletInfo.tsx @@ -11,7 +11,7 @@ import { FlexGap, useTooltip, TooltipText, - InfoIcon, + InfoFilledIcon, } from '@pancakeswap/uikit' import { ChainId, WNATIVE } from '@pancakeswap/sdk' import { FetchStatus } from 'config/constants/types' @@ -68,15 +68,21 @@ const WalletInfo: React.FC = ({ hasLowNativeBalance, onDismiss targetRef: buyCryptoTargetRef, } = useTooltip( <> - - - {t('%currency% Balance Low. You need %currency% for transaction fees.', { - currency: native?.symbol, - })} - + + + + {t('%currency% Balance Low. You need %currency% for transaction fees.', { + currency: native?.symbol, + })} + + onDismiss?.()}> + + + , { + isInPortal: false, placement: isMobile ? 'top' : 'bottom', trigger: isMobile ? 'focus' : 'hover', ...(isMobile && { manualVisible: mobileTooltipShow }), @@ -172,7 +178,7 @@ const WalletInfo: React.FC = ({ hasLowNativeBalance, onDismiss fontWeight={Number(bnbBalance?.data?.value) === 0 ? 'bold' : 'normal'} color={Number(bnbBalance?.data?.value) === 0 ? 'warning' : 'normal'} > - {bnbBalance?.data?.value && formatBigInt(bnbBalance?.data?.value ?? 0n, 6)} + {formatBigInt(bnbBalance?.data?.value ?? 0n, 6)} = ({ hasLowNativeBalance, onDismiss display="flex" style={{ justifyContent: 'center' }} > - {Number(bnbBalance?.data?.value) === 0 ? : null} + {Number(bnbBalance?.data?.value) === 0 ? ( + + ) : null} {buyCryptoTooltipVisible && (!isMobile || mobileTooltipShow) && buyCryptoTooltip} diff --git a/packages/localization/src/config/translations.json b/packages/localization/src/config/translations.json index 22a89980ddf8e..573acd75c3c5b 100644 --- a/packages/localization/src/config/translations.json +++ b/packages/localization/src/config/translations.json @@ -2602,6 +2602,7 @@ "PancakeSwap Now Live on Polygon zkEVM!": "PancakeSwap Now Live on Polygon zkEVM!", "Polygon zkEVM is LIVE!": "Polygon zkEVM is LIVE!", "Swap and provide liquidity on Polygon zkEVM now": "Swap and provide liquidity on Polygon zkEVM now", + "Buy %currency%": "Buy %currency%", "Caution - METIS Token": "Caution - METIS Token", "Please exercise due caution when trading / providing liquidity for the METIS token. The protocol was recently affected by the": "Please exercise due caution when trading / providing liquidity for the METIS token. The protocol was recently affected by the", "PolyNetwork Exploit.": "PolyNetwork Exploit.", diff --git a/packages/uikit/src/hooks/useTooltip/types.ts b/packages/uikit/src/hooks/useTooltip/types.ts index cba7d8f5196f8..ed0c1cbb2e847 100644 --- a/packages/uikit/src/hooks/useTooltip/types.ts +++ b/packages/uikit/src/hooks/useTooltip/types.ts @@ -21,3 +21,13 @@ export interface TooltipOptions { } export type TriggerType = "click" | "hover" | "focus"; + +export interface DeviceAction { + start: string; + end: string; +} + +export enum Devices { + touchDevice = "touchDevice", + nonTouchDevice = "nonTouchDevice", +} diff --git a/packages/uikit/src/hooks/useTooltip/useTooltip.tsx b/packages/uikit/src/hooks/useTooltip/useTooltip.tsx index b20f8e0bdfafc..60c3c9e03ee07 100644 --- a/packages/uikit/src/hooks/useTooltip/useTooltip.tsx +++ b/packages/uikit/src/hooks/useTooltip/useTooltip.tsx @@ -1,14 +1,15 @@ import { AnimatePresence, Variants, LazyMotion, domAnimation } from "framer-motion"; -import React, { useCallback, useEffect, useRef, useState } from "react"; +import React, { useCallback, useEffect, useState } from "react"; import { createPortal } from "react-dom"; import { usePopper } from "react-popper"; import { isMobile } from "react-device-detect"; import { DefaultTheme, ThemeProvider, useTheme } from "styled-components"; +import debounce from "lodash/debounce"; import { dark, light } from "../../theme"; import getPortalRoot from "../../util/getPortalRoot"; import isTouchDevice from "../../util/isTouchDevice"; import { Arrow, StyledTooltip } from "./StyledTooltip"; -import { TooltipOptions, TooltipRefs } from "./types"; +import { DeviceAction, Devices, TooltipOptions, TooltipRefs } from "./types"; const animationVariants: Variants = { initial: { opacity: 0 }, @@ -22,6 +23,17 @@ const animationMap = { exit: "exit", }; +const deviceActions: { [device in Devices]: DeviceAction } = { + [Devices.touchDevice]: { + start: "touchstart", + end: "touchend", + }, + [Devices.nonTouchDevice]: { + start: "mouseenter", + end: "mouseleave", + }, +}; + const invertTheme = (currentTheme: DefaultTheme) => { if (currentTheme.isDark) { return light; @@ -49,65 +61,54 @@ const useTooltip = (content: React.ReactNode, options?: TooltipOptions): Tooltip const [arrowElement, setArrowElement] = useState(null); const [visible, setVisible] = useState(manualVisible); - const isHoveringOverTooltip = useRef(false); - const hideTimeoutRef = useRef(); useEffect(() => { setVisible(manualVisible); }, [manualVisible]); + // eslint-disable-next-line react-hooks/exhaustive-deps + const debouncedHide = useCallback( + debounce(() => { + setVisible(false); + }, hideTimeout), + [hideTimeout] + ); + // using lodash debounce we can get rid of hideTimeout cleanups + // loadash's debounce handles cleanup it its implementation const hideTooltip = useCallback( (e: Event) => { if (manualVisible) return; - const hide = () => { - if (!avoidToStopPropagation) { - e.stopPropagation(); - e.preventDefault(); - } - - setVisible(false); - }; - + if (!avoidToStopPropagation) { + e.stopPropagation(); + e.preventDefault(); + } if (trigger === "hover") { - if (hideTimeoutRef.current) { - window.clearTimeout(hideTimeoutRef.current); - } - if (e.target === tooltipElement) { - isHoveringOverTooltip.current = false; - } - if (!isHoveringOverTooltip.current) { - hideTimeoutRef.current = window.setTimeout(() => { - if (!isHoveringOverTooltip.current) { - hide(); - } - }, hideTimeout); - } + debouncedHide(); } else { - hide(); + setVisible(false); } }, - [manualVisible, trigger, avoidToStopPropagation, tooltipElement, hideTimeout] + [manualVisible, trigger, debouncedHide, avoidToStopPropagation] ); const showTooltip = useCallback( (e: Event) => { setVisible(true); if (trigger === "hover") { - if (e.target === targetElement) { - // If we were about to close the tooltip and got back to it - // then prevent closing it. - clearTimeout(hideTimeoutRef.current); - } - if (e.target === tooltipElement) { - isHoveringOverTooltip.current = true; - } + // we dont need to make a inTooltipRef anymore, when we leave + // the target, hide tooltip is called for leaving the target, but show tooltip + // is called for entering the tooltip. since we enact a delay in hidetooltip, + // by the time the dylay is over lodash debounce will be cancelled until we leave the + // tooltip calling hidetooltip onece again to close. clever method jackson pointed me + // onto. saves a lot of nedless states and refs and listeners + debouncedHide.cancel(); } if (!avoidToStopPropagation) { e.stopPropagation(); e.preventDefault(); } }, - [tooltipElement, targetElement, trigger, avoidToStopPropagation] + [trigger, avoidToStopPropagation, debouncedHide] ); const toggleTooltip = useCallback( @@ -122,32 +123,23 @@ const useTooltip = (content: React.ReactNode, options?: TooltipOptions): Tooltip useEffect(() => { if (targetElement === null || trigger !== "hover" || manualVisible) return undefined; - if (isTouchDevice()) { - targetElement.addEventListener("touchstart", showTooltip); - targetElement.addEventListener("touchend", hideTooltip); - } else { - targetElement.addEventListener("mouseenter", showTooltip); - targetElement.addEventListener("mouseleave", hideTooltip); - } - return () => { - targetElement.removeEventListener("touchstart", showTooltip); - targetElement.removeEventListener("touchend", hideTooltip); - targetElement.removeEventListener("mouseenter", showTooltip); - targetElement.removeEventListener("mouseleave", showTooltip); - }; - }, [trigger, targetElement, hideTooltip, showTooltip, manualVisible]); + const eventHandlers = isTouchDevice() ? deviceActions.touchDevice : deviceActions.nonTouchDevice; - // Keep tooltip open when cursor moves from the targetElement to the tooltip - useEffect(() => { - if (tooltipElement === null || trigger !== "hover" || manualVisible) return undefined; + [targetElement, tooltipElement].forEach((element) => { + element?.addEventListener(eventHandlers.start, showTooltip); + element?.addEventListener(eventHandlers.end, hideTooltip); + }); - tooltipElement.addEventListener("mouseenter", showTooltip); - tooltipElement.addEventListener("mouseleave", hideTooltip); return () => { - tooltipElement.removeEventListener("mouseenter", showTooltip); - tooltipElement.removeEventListener("mouseleave", hideTooltip); + [targetElement, tooltipElement].forEach((element) => { + element?.removeEventListener(eventHandlers.start, showTooltip); + element?.removeEventListener(eventHandlers.end, hideTooltip); + debouncedHide.cancel(); + }); }; - }, [trigger, tooltipElement, hideTooltip, showTooltip, manualVisible]); + }, [trigger, targetElement, hideTooltip, showTooltip, manualVisible, tooltipElement, debouncedHide]); + + // no longer need the extra useeffect // Trigger = click useEffect(() => {