You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

ContextMenu.js 5.3KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196
  1. // @flow
  2. import { makeStyles } from '@material-ui/core';
  3. import clsx from 'clsx';
  4. import React, { useEffect, useLayoutEffect, useRef, useState } from 'react';
  5. import { useSelector } from 'react-redux';
  6. import { getComputedOuterHeight } from '../../../participants-pane/functions';
  7. import { Drawer, JitsiPortal } from '../../../toolbox/components/web';
  8. import { showOverflowDrawer } from '../../../toolbox/functions.web';
  9. import participantsPaneTheme from '../themes/participantsPaneTheme.json';
  10. type Props = {
  11. /**
  12. * Children of the context menu.
  13. */
  14. children: React$Node,
  15. /**
  16. * Class name for context menu. Used to overwrite default styles.
  17. */
  18. className?: ?string,
  19. /**
  20. * The entity for which the context menu is displayed.
  21. */
  22. entity?: Object,
  23. /**
  24. * Whether or not the menu is hidden. Used to overwrite the internal isHidden.
  25. */
  26. hidden?: boolean,
  27. /**
  28. * Whether or not the menu is already in a drawer.
  29. */
  30. inDrawer?: ?boolean,
  31. /**
  32. * Whether or not drawer should be open.
  33. */
  34. isDrawerOpen?: boolean,
  35. /**
  36. * Target elements against which positioning calculations are made.
  37. */
  38. offsetTarget?: HTMLElement,
  39. /**
  40. * Callback for click on an item in the menu.
  41. */
  42. onClick?: Function,
  43. /**
  44. * Callback for drawer close.
  45. */
  46. onDrawerClose?: Function,
  47. /**
  48. * Callback for the mouse entering the component.
  49. */
  50. onMouseEnter?: Function,
  51. /**
  52. * Callback for the mouse leaving the component.
  53. */
  54. onMouseLeave?: Function
  55. };
  56. const MAX_HEIGHT = 400;
  57. const useStyles = makeStyles(theme => {
  58. return {
  59. contextMenu: {
  60. backgroundColor: theme.palette.ui02,
  61. borderRadius: `${theme.shape.borderRadius / 2}px`,
  62. boxShadow: '0px 3px 16px rgba(0, 0, 0, 0.6), 0px 0px 4px 1px rgba(0, 0, 0, 0.25)',
  63. color: theme.palette.text01,
  64. ...theme.typography.bodyShortRegular,
  65. lineHeight: `${theme.typography.bodyShortRegular.lineHeight}px`,
  66. marginTop: `${(participantsPaneTheme.panePadding * 2) + theme.typography.bodyShortRegular.fontSize}px`,
  67. position: 'absolute',
  68. right: `${participantsPaneTheme.panePadding}px`,
  69. top: 0,
  70. zIndex: 2,
  71. maxHeight: `${MAX_HEIGHT}px`,
  72. overflowY: 'auto'
  73. },
  74. contextMenuHidden: {
  75. pointerEvents: 'none',
  76. visibility: 'hidden'
  77. },
  78. drawer: {
  79. '& > div': {
  80. ...theme.typography.bodyShortRegularLarge,
  81. lineHeight: `${theme.typography.bodyShortRegularLarge.lineHeight}px`,
  82. '& svg': {
  83. fill: theme.palette.icon01
  84. }
  85. },
  86. '& > *:first-child': {
  87. paddingTop: '15px!important'
  88. }
  89. }
  90. };
  91. });
  92. const ContextMenu = ({
  93. children,
  94. className,
  95. entity,
  96. hidden,
  97. inDrawer,
  98. isDrawerOpen,
  99. offsetTarget,
  100. onClick,
  101. onDrawerClose,
  102. onMouseEnter,
  103. onMouseLeave
  104. }: Props) => {
  105. const [ isHidden, setIsHidden ] = useState(true);
  106. const containerRef = useRef<HTMLDivElement | null>(null);
  107. const styles = useStyles();
  108. const _overflowDrawer = useSelector(showOverflowDrawer);
  109. useLayoutEffect(() => {
  110. if (_overflowDrawer) {
  111. return;
  112. }
  113. if (entity && offsetTarget
  114. && containerRef.current
  115. && offsetTarget?.offsetParent
  116. && offsetTarget.offsetParent instanceof HTMLElement
  117. ) {
  118. const { current: container } = containerRef;
  119. const { offsetTop, offsetParent: { offsetHeight, scrollTop } } = offsetTarget;
  120. const outerHeight = getComputedOuterHeight(container);
  121. const height = Math.min(MAX_HEIGHT, outerHeight);
  122. container.style.top = offsetTop + height > offsetHeight + scrollTop
  123. ? `${offsetTop - outerHeight}`
  124. : `${offsetTop}`;
  125. setIsHidden(false);
  126. } else {
  127. setIsHidden(true);
  128. }
  129. }, [ entity, offsetTarget, _overflowDrawer ]);
  130. useEffect(() => {
  131. if (hidden !== undefined) {
  132. setIsHidden(hidden);
  133. }
  134. }, [ hidden ]);
  135. if (_overflowDrawer && inDrawer) {
  136. return (<div
  137. className = { styles.drawer }
  138. onClick = { onDrawerClose }>
  139. {children}
  140. </div>);
  141. }
  142. return _overflowDrawer
  143. ? <JitsiPortal>
  144. <Drawer
  145. isOpen = { isDrawerOpen && _overflowDrawer }
  146. onClose = { onDrawerClose }>
  147. <div
  148. className = { styles.drawer }
  149. onClick = { onDrawerClose }>
  150. {children}
  151. </div>
  152. </Drawer>
  153. </JitsiPortal>
  154. : <div
  155. className = { clsx(participantsPaneTheme.ignoredChildClassName,
  156. styles.contextMenu,
  157. isHidden && styles.contextMenuHidden,
  158. className
  159. ) }
  160. onClick = { onClick }
  161. onMouseEnter = { onMouseEnter }
  162. onMouseLeave = { onMouseLeave }
  163. ref = { containerRef }>
  164. {children}
  165. </div>
  166. ;
  167. };
  168. export default ContextMenu;