Вы не можете выбрать более 25 тем Темы должны начинаться с буквы или цифры, могут содержать дефисы(-) и должны содержать не более 35 символов.

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 { withPixelLineHeight } from '../../../styles/functions.web';
  5. import { isIcon } from '../../functions';
  6. import { IAvatarProps } from '../../types';
  7. import { PRESENCE_AVAILABLE_COLOR, PRESENCE_AWAY_COLOR, PRESENCE_BUSY_COLOR, PRESENCE_IDLE_COLOR } from '../styles';
  8. interface IProps extends IAvatarProps {
  9. /**
  10. * External class name passed through props.
  11. */
  12. className?: string;
  13. /**
  14. * The default avatar URL if we want to override the app bundled one (e.g. AlwaysOnTop).
  15. */
  16. defaultAvatar?: string;
  17. /**
  18. * ID of the component to be rendered.
  19. */
  20. id?: string;
  21. /**
  22. * One of the expected status strings (e.g. 'available') to render a badge on the avatar, if necessary.
  23. */
  24. status?: string;
  25. /**
  26. * TestId of the element, if any.
  27. */
  28. testId?: string;
  29. /**
  30. * The URL of the avatar to render.
  31. */
  32. url?: string | Function;
  33. /**
  34. * Indicates whether to load the avatar using CORS or not.
  35. */
  36. useCORS?: boolean;
  37. }
  38. const useStyles = makeStyles()(theme => {
  39. return {
  40. avatar: {
  41. backgroundColor: '#AAA',
  42. borderRadius: '50%',
  43. fontWeight: '600',
  44. color: theme.palette?.text01 || '#fff',
  45. ...withPixelLineHeight(theme.typography?.heading1 ?? {}),
  46. fontSize: 'inherit',
  47. objectFit: 'cover',
  48. textAlign: 'center',
  49. overflow: 'hidden',
  50. '&.avatar-small': {
  51. height: '28px !important',
  52. width: '28px !important'
  53. },
  54. '&.avatar-xsmall': {
  55. height: '16px !important',
  56. width: '16px !important'
  57. },
  58. '& .jitsi-icon': {
  59. transform: 'translateY(50%)'
  60. },
  61. '& .avatar-svg': {
  62. height: '100%',
  63. width: '100%'
  64. }
  65. },
  66. initialsContainer: {
  67. width: '100%',
  68. height: '100%',
  69. display: 'flex',
  70. justifyContent: 'center',
  71. alignItems: 'center'
  72. },
  73. badge: {
  74. position: 'relative',
  75. '&.avatar-badge:after': {
  76. borderRadius: '50%',
  77. content: '""',
  78. display: 'block',
  79. height: '35%',
  80. position: 'absolute',
  81. bottom: 0,
  82. width: '35%'
  83. },
  84. '&.avatar-badge-available:after': {
  85. backgroundColor: PRESENCE_AVAILABLE_COLOR
  86. },
  87. '&.avatar-badge-away:after': {
  88. backgroundColor: PRESENCE_AWAY_COLOR
  89. },
  90. '&.avatar-badge-busy:after': {
  91. backgroundColor: PRESENCE_BUSY_COLOR
  92. },
  93. '&.avatar-badge-idle:after': {
  94. backgroundColor: PRESENCE_IDLE_COLOR
  95. }
  96. }
  97. };
  98. });
  99. const StatelessAvatar = ({
  100. className,
  101. color,
  102. iconUser,
  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;