tooltip.tsx 2.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103
  1. import {Fragment} from 'react';
  2. import {createPortal} from 'react-dom';
  3. import {SerializedStyles, useTheme} from '@emotion/react';
  4. import styled from '@emotion/styled';
  5. import {AnimatePresence} from 'framer-motion';
  6. import {Overlay, PositionWrapper} from 'sentry/components/overlay';
  7. import {IS_ACCEPTANCE_TEST} from 'sentry/constants/index';
  8. import space from 'sentry/styles/space';
  9. import {useHoverOverlay, UseHoverOverlayProps} from 'sentry/utils/useHoverOverlay';
  10. import {AcceptanceTestTooltip} from './acceptanceTestTooltip';
  11. export interface InternalTooltipProps extends UseHoverOverlayProps {
  12. children: React.ReactNode;
  13. /**
  14. * The content to show in the tooltip popover
  15. */
  16. title: React.ReactNode;
  17. /**
  18. * Disable the tooltip display entirely
  19. */
  20. disabled?: boolean;
  21. /**
  22. * Additional style rules for the tooltip content.
  23. */
  24. overlayStyle?: React.CSSProperties | SerializedStyles;
  25. }
  26. // Warning: This component is conditionally exported end-of-file based on IS_ACCEPTANCE_TEST env variable
  27. export function DO_NOT_USE_TOOLTIP({
  28. children,
  29. overlayStyle,
  30. title,
  31. disabled = false,
  32. ...hoverOverlayProps
  33. }: InternalTooltipProps) {
  34. const theme = useTheme();
  35. const {wrapTrigger, isOpen, overlayProps, placement, arrowData, arrowProps} =
  36. useHoverOverlay('tooltip', hoverOverlayProps);
  37. if (disabled || !title) {
  38. return <Fragment>{children}</Fragment>;
  39. }
  40. const tooltipContent = isOpen && (
  41. <PositionWrapper zIndex={theme.zIndex.tooltip} {...overlayProps}>
  42. <TooltipContent
  43. animated
  44. arrowProps={arrowProps}
  45. originPoint={arrowData}
  46. placement={placement}
  47. overlayStyle={overlayStyle}
  48. >
  49. {title}
  50. </TooltipContent>
  51. </PositionWrapper>
  52. );
  53. return (
  54. <Fragment>
  55. {wrapTrigger(children)}
  56. {createPortal(<AnimatePresence>{tooltipContent}</AnimatePresence>, document.body)}
  57. </Fragment>
  58. );
  59. }
  60. const TooltipContent = styled(Overlay)`
  61. padding: ${space(1)} ${space(1.5)};
  62. overflow-wrap: break-word;
  63. max-width: 225px;
  64. color: ${p => p.theme.textColor};
  65. font-size: ${p => p.theme.fontSizeSmall};
  66. line-height: 1.2;
  67. text-align: center;
  68. `;
  69. interface TooltipProps extends InternalTooltipProps {
  70. /**
  71. * Stops tooltip from being opened during tooltip visual acceptance.
  72. * Should be set to true if tooltip contains unisolated data (eg. dates)
  73. */
  74. disableForVisualTest?: boolean;
  75. }
  76. /**
  77. * Tooltip will enhance the internal tooltip with the open/close
  78. * functionality used in src/sentry/utils/pytest/selenium.py so that tooltips
  79. * can be opened and closed for specific snapshots.
  80. */
  81. function Tooltip({disableForVisualTest, ...props}: TooltipProps) {
  82. if (IS_ACCEPTANCE_TEST) {
  83. return disableForVisualTest ? (
  84. <Fragment>{props.children}</Fragment>
  85. ) : (
  86. <AcceptanceTestTooltip {...props} />
  87. );
  88. }
  89. return <DO_NOT_USE_TOOLTIP {...props} />;
  90. }
  91. export default Tooltip;