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.

ListItem.tsx 7.6KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295
  1. import React, { ReactNode } from 'react';
  2. import { makeStyles } from 'tss-react/mui';
  3. import { ACTION_TRIGGER } from '../../../../participants-pane/constants';
  4. import participantsPaneTheme from '../../../components/themes/participantsPaneTheme.json';
  5. import { isMobileBrowser } from '../../../environment/utils';
  6. import { withPixelLineHeight } from '../../../styles/functions.web';
  7. interface IProps {
  8. /**
  9. * List item actions.
  10. */
  11. actions: ReactNode;
  12. /**
  13. * List item container class name.
  14. */
  15. className?: string;
  16. /**
  17. * The breakout name for aria-label.
  18. */
  19. defaultName?: string;
  20. /**
  21. * Whether or not the actions should be hidden.
  22. */
  23. hideActions?: boolean;
  24. /**
  25. * Icon to be displayed on the list item. (Avatar for participants).
  26. */
  27. icon: ReactNode;
  28. /**
  29. * Id of the container.
  30. */
  31. id?: string;
  32. /**
  33. * Indicators to be displayed on the list item.
  34. */
  35. indicators?: ReactNode;
  36. /**
  37. * Whether or not the item is highlighted.
  38. */
  39. isHighlighted?: boolean;
  40. /**
  41. * Click handler.
  42. */
  43. onClick?: (e?: React.MouseEvent) => void;
  44. /**
  45. * Long press handler.
  46. */
  47. onLongPress?: (e?: EventTarget) => void;
  48. /**
  49. * Mouse leave handler.
  50. */
  51. onMouseLeave?: (e?: React.MouseEvent) => void;
  52. /**
  53. * Data test id.
  54. */
  55. testId?: string;
  56. /**
  57. * Text children to be displayed on the list item.
  58. */
  59. textChildren: ReactNode | string;
  60. /**
  61. * The actions trigger. Can be Hover or Permanent.
  62. */
  63. trigger: string;
  64. }
  65. const useStyles = makeStyles()(theme => {
  66. return {
  67. container: {
  68. alignItems: 'center',
  69. color: theme.palette.text01,
  70. display: 'flex',
  71. ...withPixelLineHeight(theme.typography.bodyShortBold),
  72. margin: `0 -${participantsPaneTheme.panePadding}px`,
  73. padding: `${theme.spacing(2)} ${participantsPaneTheme.panePadding}px`,
  74. position: 'relative',
  75. boxShadow: 'inset 0px -1px 0px rgba(255, 255, 255, 0.15)',
  76. minHeight: '40px',
  77. '&:hover, &:focus-within': {
  78. backgroundColor: theme.palette.ui02,
  79. '& .indicators': {
  80. display: 'none'
  81. },
  82. '& .actions': {
  83. display: 'flex',
  84. position: 'relative',
  85. top: 'auto',
  86. boxShadow: `-15px 0px 10px -5px ${theme.palette.ui02}`,
  87. backgroundColor: theme.palette.ui02
  88. }
  89. },
  90. [`@media(max-width: ${participantsPaneTheme.MD_BREAKPOINT})`]: {
  91. ...withPixelLineHeight(theme.typography.bodyShortBoldLarge),
  92. padding: `${theme.spacing(3)} ${participantsPaneTheme.panePadding}px`
  93. }
  94. },
  95. highlighted: {
  96. backgroundColor: theme.palette.ui02,
  97. '& .actions': {
  98. display: 'flex',
  99. position: 'relative',
  100. top: 'auto',
  101. boxShadow: `-15px 0px 10px -5px ${theme.palette.ui02}`,
  102. backgroundColor: theme.palette.ui02
  103. }
  104. },
  105. detailsContainer: {
  106. display: 'flex',
  107. alignItems: 'center',
  108. flex: 1,
  109. height: '100%',
  110. overflow: 'hidden',
  111. position: 'relative'
  112. },
  113. name: {
  114. display: 'flex',
  115. flex: 1,
  116. marginRight: theme.spacing(2),
  117. overflow: 'hidden',
  118. flexDirection: 'column',
  119. justifyContent: 'flex-start'
  120. },
  121. indicators: {
  122. display: 'flex',
  123. justifyContent: 'flex-end',
  124. '& > *': {
  125. alignItems: 'center',
  126. display: 'flex',
  127. justifyContent: 'center'
  128. },
  129. '& > *:not(:last-child)': {
  130. marginRight: theme.spacing(2)
  131. },
  132. '& .jitsi-icon': {
  133. padding: '3px'
  134. }
  135. },
  136. indicatorsHidden: {
  137. display: 'none'
  138. },
  139. actionsContainer: {
  140. position: 'absolute',
  141. top: '-1000px',
  142. boxShadow: `-15px 0px 10px -5px ${theme.palette.ui02}`,
  143. backgroundColor: theme.palette.ui02
  144. },
  145. actionsPermanent: {
  146. display: 'flex',
  147. boxShadow: `-15px 0px 10px -5px ${theme.palette.ui01}`,
  148. backgroundColor: theme.palette.ui01
  149. },
  150. actionsVisible: {
  151. display: 'flex',
  152. boxShadow: `-15px 0px 10px -5px ${theme.palette.ui02}`,
  153. backgroundColor: theme.palette.ui02
  154. }
  155. };
  156. });
  157. const ListItem = ({
  158. actions,
  159. className,
  160. defaultName,
  161. icon,
  162. id,
  163. hideActions = false,
  164. indicators,
  165. isHighlighted,
  166. onClick,
  167. onLongPress,
  168. onMouseLeave,
  169. testId,
  170. textChildren,
  171. trigger
  172. }: IProps) => {
  173. const { classes, cx } = useStyles();
  174. const isMobile = isMobileBrowser();
  175. let timeoutHandler: number;
  176. /**
  177. * Set calling long press handler after x milliseconds.
  178. *
  179. * @param {TouchEvent} e - Touch start event.
  180. * @returns {void}
  181. */
  182. function _onTouchStart(e: React.TouchEvent) {
  183. const target = e.touches[0].target;
  184. timeoutHandler = window.setTimeout(() => onLongPress?.(target), 600);
  185. }
  186. /**
  187. * Cancel calling on long press after x milliseconds if the number of milliseconds is not reached
  188. * before a touch move(drag), or just clears the timeout.
  189. *
  190. * @returns {void}
  191. */
  192. function _onTouchMove() {
  193. clearTimeout(timeoutHandler);
  194. }
  195. /**
  196. * Cancel calling on long press after x milliseconds if the number of milliseconds is not reached yet,
  197. * or just clears the timeout.
  198. *
  199. * @returns {void}
  200. */
  201. function _onTouchEnd() {
  202. clearTimeout(timeoutHandler);
  203. }
  204. return (
  205. <div
  206. aria-label = { defaultName }
  207. className = { cx('list-item-container',
  208. classes.container,
  209. isHighlighted && classes.highlighted,
  210. className
  211. ) }
  212. data-testid = { testId }
  213. id = { id }
  214. onClick = { onClick }
  215. role = 'listitem'
  216. { ...(isMobile
  217. ? {
  218. onTouchEnd: _onTouchEnd,
  219. onTouchMove: _onTouchMove,
  220. onTouchStart: _onTouchStart
  221. }
  222. : {
  223. onMouseLeave
  224. }
  225. ) }>
  226. <div> {icon} </div>
  227. <div className = { classes.detailsContainer }>
  228. <div className = { classes.name }>
  229. {textChildren}
  230. </div>
  231. {indicators && (
  232. <div
  233. className = { cx('indicators',
  234. classes.indicators,
  235. (isHighlighted || trigger === ACTION_TRIGGER.PERMANENT) && classes.indicatorsHidden
  236. ) }>
  237. {indicators}
  238. </div>
  239. )}
  240. {!hideActions && (
  241. <div
  242. className = { cx('actions',
  243. classes.actionsContainer,
  244. trigger === ACTION_TRIGGER.PERMANENT && classes.actionsPermanent,
  245. isHighlighted && classes.actionsVisible
  246. ) }>
  247. {actions}
  248. </div>
  249. )}
  250. </div>
  251. </div>
  252. );
  253. };
  254. export default ListItem;