選択できるのは25トピックまでです。 トピックは、先頭が英数字で、英数字とダッシュ('-')を使用した35文字以内のものにしてください。

Tooltip.tsx 4.6KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158
  1. import React, { ReactElement, useCallback, useEffect, useRef, useState } from 'react';
  2. import { useDispatch, useSelector } from 'react-redux';
  3. import { keyframes } from 'tss-react';
  4. import { makeStyles } from 'tss-react/mui';
  5. import { IReduxState } from '../../../app/types';
  6. import { isMobileBrowser } from '../../environment/utils';
  7. import Popover from '../../popover/components/Popover.web';
  8. import { withPixelLineHeight } from '../../styles/functions.web';
  9. import { TOOLTIP_POSITION } from '../../ui/constants.any';
  10. import { hideTooltip, showTooltip } from '../actions';
  11. const TOOLTIP_DELAY = 300;
  12. const ANIMATION_DURATION = 0.2;
  13. interface IProps {
  14. children: ReactElement;
  15. containerClassName?: string;
  16. content: string | ReactElement;
  17. position?: TOOLTIP_POSITION;
  18. }
  19. const useStyles = makeStyles()(theme => {
  20. return {
  21. container: {
  22. backgroundColor: theme.palette.uiBackground,
  23. borderRadius: '3px',
  24. padding: theme.spacing(2),
  25. ...withPixelLineHeight(theme.typography.labelRegular),
  26. color: theme.palette.text01,
  27. position: 'relative',
  28. '&.mounting-animation': {
  29. animation: `${keyframes`
  30. 0% {
  31. opacity: 0;
  32. }
  33. 100% {
  34. opacity: 1;
  35. }
  36. `} ${ANIMATION_DURATION}s forwards ease-in`
  37. },
  38. '&.unmounting': {
  39. animation: `${keyframes`
  40. 0% {
  41. opacity: 1;
  42. }
  43. 100% {
  44. opacity: 0;
  45. }
  46. `} ${ANIMATION_DURATION}s forwards ease-out`
  47. }
  48. }
  49. };
  50. });
  51. const Tooltip = ({ containerClassName, content, children, position = 'top' }: IProps) => {
  52. const dispatch = useDispatch();
  53. const [ visible, setVisible ] = useState(false);
  54. const [ isUnmounting, setIsUnmounting ] = useState(false);
  55. const overflowDrawer = useSelector((state: IReduxState) => state['features/toolbox'].overflowDrawer);
  56. const { classes, cx } = useStyles();
  57. const timeoutID = useRef({
  58. open: 0,
  59. close: 0
  60. });
  61. const {
  62. content: storeContent,
  63. previousContent,
  64. visible: isVisible
  65. } = useSelector((state: IReduxState) => state['features/base/tooltip']);
  66. const contentComponent = (
  67. <div
  68. className = { cx(classes.container, previousContent === '' && 'mounting-animation',
  69. isUnmounting && 'unmounting') }>
  70. {content}
  71. </div>
  72. );
  73. const openPopover = () => {
  74. setVisible(true);
  75. dispatch(showTooltip(content));
  76. };
  77. const closePopover = () => {
  78. setVisible(false);
  79. dispatch(hideTooltip(content));
  80. setIsUnmounting(false);
  81. };
  82. const onPopoverOpen = useCallback(() => {
  83. if (isUnmounting) {
  84. return;
  85. }
  86. clearTimeout(timeoutID.current.close);
  87. timeoutID.current.close = 0;
  88. if (!visible) {
  89. if (isVisible) {
  90. openPopover();
  91. } else {
  92. timeoutID.current.open = window.setTimeout(() => {
  93. openPopover();
  94. }, TOOLTIP_DELAY);
  95. }
  96. }
  97. }, [ visible, isVisible, isUnmounting ]);
  98. const onPopoverClose = useCallback(() => {
  99. clearTimeout(timeoutID.current.open);
  100. if (visible) {
  101. timeoutID.current.close = window.setTimeout(() => {
  102. setIsUnmounting(true);
  103. }, TOOLTIP_DELAY);
  104. }
  105. }, [ visible ]);
  106. useEffect(() => {
  107. if (isUnmounting) {
  108. setTimeout(() => {
  109. if (timeoutID.current.close !== 0) {
  110. closePopover();
  111. }
  112. }, (ANIMATION_DURATION * 1000) + 10);
  113. }
  114. }, [ isUnmounting ]);
  115. useEffect(() => {
  116. if (storeContent !== content) {
  117. closePopover();
  118. clearTimeout(timeoutID.current.close);
  119. timeoutID.current.close = 0;
  120. }
  121. }, [ storeContent ]);
  122. if (isMobileBrowser() || overflowDrawer) {
  123. return children;
  124. }
  125. return (
  126. <Popover
  127. allowClick = { true }
  128. className = { containerClassName }
  129. content = { contentComponent }
  130. focusable = { false }
  131. onPopoverClose = { onPopoverClose }
  132. onPopoverOpen = { onPopoverOpen }
  133. position = { position }
  134. visible = { visible }>
  135. {children}
  136. </Popover>
  137. );
  138. };
  139. export default Tooltip;