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.

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