mirror of
https://github.com/luxfi/explore.git
synced 2026-09-24 00:28:18 +00:00
160 lines
4.9 KiB
TypeScript
160 lines
4.9 KiB
TypeScript
import { Tooltip as ChakraTooltip, Portal } from '@chakra-ui/react';
|
|
import { useClickAway } from '@uidotdev/usehooks';
|
|
import * as React from 'react';
|
|
|
|
import config from 'configs/app';
|
|
import useIsMobile from 'lib/hooks/useIsMobile';
|
|
|
|
export interface TooltipProps extends ChakraTooltip.RootProps {
|
|
selected?: boolean;
|
|
showArrow?: boolean;
|
|
portalled?: boolean;
|
|
portalRef?: React.RefObject<HTMLElement>;
|
|
content: React.ReactNode;
|
|
contentProps?: ChakraTooltip.ContentProps;
|
|
triggerProps?: ChakraTooltip.TriggerProps;
|
|
disabled?: boolean;
|
|
disableOnMobile?: boolean;
|
|
}
|
|
|
|
export const Tooltip = React.forwardRef<HTMLDivElement, TooltipProps>(
|
|
function Tooltip(props, ref) {
|
|
const {
|
|
showArrow: showArrowProp,
|
|
onOpenChange,
|
|
variant,
|
|
selected,
|
|
children,
|
|
disabled,
|
|
disableOnMobile,
|
|
portalled = true,
|
|
content,
|
|
contentProps,
|
|
portalRef,
|
|
defaultOpen = false,
|
|
lazyMount = true,
|
|
unmountOnExit = true,
|
|
triggerProps,
|
|
closeDelay = 100,
|
|
openDelay = 100,
|
|
interactive,
|
|
...rest
|
|
} = props;
|
|
|
|
const [ open, setOpen ] = React.useState<boolean>(defaultOpen);
|
|
const timeoutRef = React.useRef<number | null>(null);
|
|
|
|
const isMobile = useIsMobile();
|
|
|
|
const handleOpenChange = React.useCallback((details: { open: boolean }) => {
|
|
setOpen(details.open);
|
|
onOpenChange?.(details);
|
|
}, [ onOpenChange ]);
|
|
|
|
const handleOpenChangeManual = React.useCallback((nextOpen: boolean) => {
|
|
timeoutRef.current && window.clearTimeout(timeoutRef.current);
|
|
timeoutRef.current = window.setTimeout(() => {
|
|
setOpen(nextOpen);
|
|
onOpenChange?.({ open: nextOpen });
|
|
}, nextOpen ? openDelay : closeDelay);
|
|
}, [ closeDelay, openDelay, onOpenChange ]);
|
|
|
|
const handleClickAway = React.useCallback((event: Event) => {
|
|
if (interactive) {
|
|
const closest = (event.target as HTMLElement)?.closest('.chakra-tooltip__positioner');
|
|
if (closest) {
|
|
return;
|
|
}
|
|
}
|
|
|
|
handleOpenChangeManual(false);
|
|
}, [ interactive, handleOpenChangeManual ]);
|
|
|
|
const triggerRef = useClickAway<HTMLButtonElement>(handleClickAway);
|
|
|
|
const handleTriggerClick = React.useCallback(() => {
|
|
handleOpenChangeManual(!open);
|
|
}, [ handleOpenChangeManual, open ]);
|
|
|
|
const handleContentClick = React.useCallback((event: React.MouseEvent<HTMLDivElement>) => {
|
|
// otherwise, the event will be propagated to the trigger
|
|
// and if the trigger is a link, navigation will be triggered
|
|
event.stopPropagation();
|
|
|
|
if (interactive) {
|
|
const closestLink = (event.target as HTMLElement)?.closest('a');
|
|
if (closestLink) {
|
|
handleOpenChangeManual(false);
|
|
}
|
|
}
|
|
}, [ interactive, handleOpenChangeManual ]);
|
|
|
|
React.useEffect(() => {
|
|
return () => {
|
|
if (timeoutRef.current) {
|
|
clearTimeout(timeoutRef.current);
|
|
}
|
|
};
|
|
}, []);
|
|
|
|
if (disabled || (disableOnMobile && isMobile)) return children;
|
|
|
|
const defaultShowArrow = variant === 'popover' ? false : true;
|
|
const showArrow = showArrowProp !== undefined ? showArrowProp : defaultShowArrow;
|
|
|
|
const positioning = {
|
|
...rest.positioning,
|
|
overflowPadding: 4,
|
|
offset: {
|
|
mainAxis: 4,
|
|
...rest.positioning?.offset,
|
|
},
|
|
};
|
|
|
|
return (
|
|
<ChakraTooltip.Root
|
|
openDelay={ openDelay }
|
|
// FIXME: chakra closes tooltip too fast, so Playwright is not able to make a screenshot of its content
|
|
// so we need to increase the close delay in Playwright environment
|
|
closeDelay={ config.app.isPw ? 10_000 : closeDelay }
|
|
open={ open }
|
|
onOpenChange={ handleOpenChange }
|
|
closeOnClick={ false }
|
|
closeOnPointerDown={ false }
|
|
variant={ variant }
|
|
lazyMount={ lazyMount }
|
|
unmountOnExit={ unmountOnExit }
|
|
interactive={ interactive }
|
|
{ ...rest }
|
|
positioning={ positioning }
|
|
>
|
|
<ChakraTooltip.Trigger
|
|
ref={ open ? triggerRef : null }
|
|
asChild
|
|
onClick={ isMobile ? handleTriggerClick : undefined }
|
|
{ ...triggerProps }
|
|
>
|
|
{ children }
|
|
</ChakraTooltip.Trigger>
|
|
<Portal disabled={ !portalled } container={ portalRef }>
|
|
<ChakraTooltip.Positioner>
|
|
<ChakraTooltip.Content
|
|
ref={ ref }
|
|
onClick={ interactive ? handleContentClick : undefined }
|
|
{ ...(selected ? { 'data-selected': true } : {}) }
|
|
{ ...contentProps }
|
|
>
|
|
{ showArrow && (
|
|
<ChakraTooltip.Arrow>
|
|
<ChakraTooltip.ArrowTip/>
|
|
</ChakraTooltip.Arrow>
|
|
) }
|
|
{ content }
|
|
</ChakraTooltip.Content>
|
|
</ChakraTooltip.Positioner>
|
|
</Portal>
|
|
</ChakraTooltip.Root>
|
|
);
|
|
},
|
|
);
|