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.

StatelessAvatar.tsx 5.7KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221
  1. import React, { useCallback } from 'react';
  2. import { makeStyles } from 'tss-react/mui';
  3. import Icon from '../../../icons/components/Icon';
  4. import { IconUser } from '../../../icons/svg';
  5. import { withPixelLineHeight } from '../../../styles/functions.web';
  6. import { isIcon } from '../../functions';
  7. import { IAvatarProps } from '../../types';
  8. import { PRESENCE_AVAILABLE_COLOR, PRESENCE_AWAY_COLOR, PRESENCE_BUSY_COLOR, PRESENCE_IDLE_COLOR } from '../styles';
  9. interface IProps extends IAvatarProps {
  10. /**
  11. * External class name passed through props.
  12. */
  13. className?: string;
  14. /**
  15. * The default avatar URL if we want to override the app bundled one (e.g. AlwaysOnTop).
  16. */
  17. defaultAvatar?: string;
  18. /**
  19. * ID of the component to be rendered.
  20. */
  21. id?: string;
  22. /**
  23. * One of the expected status strings (e.g. 'available') to render a badge on the avatar, if necessary.
  24. */
  25. status?: string;
  26. /**
  27. * TestId of the element, if any.
  28. */
  29. testId?: string;
  30. /**
  31. * The URL of the avatar to render.
  32. */
  33. url?: string | Function;
  34. /**
  35. * Indicates whether to load the avatar using CORS or not.
  36. */
  37. useCORS?: boolean;
  38. }
  39. const useStyles = makeStyles()(theme => {
  40. return {
  41. avatar: {
  42. backgroundColor: '#AAA',
  43. borderRadius: '50%',
  44. fontWeight: '600',
  45. color: theme.palette?.text01 || '#fff',
  46. ...withPixelLineHeight(theme.typography?.heading1 ?? {}),
  47. fontSize: 'inherit',
  48. objectFit: 'cover',
  49. textAlign: 'center',
  50. overflow: 'hidden',
  51. '&.avatar-small': {
  52. height: '28px !important',
  53. width: '28px !important'
  54. },
  55. '&.avatar-xsmall': {
  56. height: '16px !important',
  57. width: '16px !important'
  58. },
  59. '& .jitsi-icon': {
  60. transform: 'translateY(50%)'
  61. },
  62. '& .avatar-svg': {
  63. height: '100%',
  64. width: '100%'
  65. }
  66. },
  67. initialsContainer: {
  68. width: '100%',
  69. height: '100%',
  70. display: 'flex',
  71. justifyContent: 'center',
  72. alignItems: 'center'
  73. },
  74. badge: {
  75. position: 'relative',
  76. '&.avatar-badge:after': {
  77. borderRadius: '50%',
  78. content: '""',
  79. display: 'block',
  80. height: '35%',
  81. position: 'absolute',
  82. bottom: 0,
  83. width: '35%'
  84. },
  85. '&.avatar-badge-available:after': {
  86. backgroundColor: PRESENCE_AVAILABLE_COLOR
  87. },
  88. '&.avatar-badge-away:after': {
  89. backgroundColor: PRESENCE_AWAY_COLOR
  90. },
  91. '&.avatar-badge-busy:after': {
  92. backgroundColor: PRESENCE_BUSY_COLOR
  93. },
  94. '&.avatar-badge-idle:after': {
  95. backgroundColor: PRESENCE_IDLE_COLOR
  96. }
  97. }
  98. };
  99. });
  100. const StatelessAvatar = ({
  101. className,
  102. color,
  103. id,
  104. initials,
  105. onAvatarLoadError,
  106. onAvatarLoadErrorParams,
  107. size,
  108. status,
  109. testId,
  110. url,
  111. useCORS
  112. }: IProps) => {
  113. const { classes, cx } = useStyles();
  114. const _getAvatarStyle = (backgroundColor?: string) => {
  115. return {
  116. background: backgroundColor || undefined,
  117. fontSize: size ? size * 0.4 : '180%',
  118. height: size || '100%',
  119. width: size || '100%'
  120. };
  121. };
  122. const _getAvatarClassName = (additional?: string) => cx('avatar', additional, className, classes.avatar);
  123. const _getBadgeClassName = () => {
  124. if (status) {
  125. return cx('avatar-badge', `avatar-badge-${status}`, classes.badge);
  126. }
  127. return '';
  128. };
  129. const _onAvatarLoadError = useCallback(() => {
  130. if (typeof onAvatarLoadError === 'function') {
  131. onAvatarLoadError(onAvatarLoadErrorParams);
  132. }
  133. }, [ onAvatarLoadError, onAvatarLoadErrorParams ]);
  134. if (isIcon(url)) {
  135. return (
  136. <div
  137. className = { cx(_getAvatarClassName(), _getBadgeClassName()) }
  138. data-testid = { testId }
  139. id = { id }
  140. style = { _getAvatarStyle(color) }>
  141. <Icon
  142. size = '50%'
  143. src = { url } />
  144. </div>
  145. );
  146. }
  147. if (url) {
  148. return (
  149. <div className = { _getBadgeClassName() }>
  150. <img
  151. alt = 'avatar'
  152. className = { _getAvatarClassName() }
  153. crossOrigin = { useCORS ? '' : undefined }
  154. data-testid = { testId }
  155. id = { id }
  156. onError = { _onAvatarLoadError }
  157. src = { url }
  158. style = { _getAvatarStyle() } />
  159. </div>
  160. );
  161. }
  162. if (initials) {
  163. return (
  164. <div
  165. className = { cx(_getAvatarClassName(), _getBadgeClassName()) }
  166. data-testid = { testId }
  167. id = { id }
  168. style = { _getAvatarStyle(color) }>
  169. <div className = { classes.initialsContainer }>
  170. {initials}
  171. </div>
  172. </div>
  173. );
  174. }
  175. // default avatar
  176. return (
  177. <div
  178. className = { cx(_getAvatarClassName('defaultAvatar'), _getBadgeClassName()) }
  179. data-testid = { testId }
  180. id = { id }
  181. style = { _getAvatarStyle() }>
  182. <Icon
  183. size = { '50%' }
  184. src = { IconUser } />
  185. </div>
  186. );
  187. };
  188. export default StatelessAvatar;