jiti-meet/react/features/base/avatar/components/web/StatelessAvatar.tsx

266 lines
7.3 KiB
TypeScript
Raw Normal View History

2022-09-13 07:36:00 +00:00
import { withStyles } from '@mui/styles';
import clsx from 'clsx';
2019-07-03 15:39:39 +00:00
import React from 'react';
import Icon from '../../../icons/components/Icon';
2019-07-03 15:39:39 +00:00
import AbstractStatelessAvatar, { type Props as AbstractProps } from '../AbstractStatelessAvatar';
import { PRESENCE_AVAILABLE_COLOR, PRESENCE_AWAY_COLOR, PRESENCE_BUSY_COLOR, PRESENCE_IDLE_COLOR } from '../styles';
2019-07-03 15:39:39 +00:00
type Props = AbstractProps & {
/**
* External class name passed through props.
*/
className?: string;
2019-07-03 15:39:39 +00:00
/**
* An object containing the CSS classes.
2019-07-03 15:39:39 +00:00
*/
classes: any;
2019-07-03 15:39:39 +00:00
/**
2021-11-04 21:10:43 +00:00
* The default avatar URL if we want to override the app bundled one (e.g. AlwaysOnTop).
2019-07-03 15:39:39 +00:00
*/
defaultAvatar?: string;
2019-07-03 15:39:39 +00:00
/**
* ID of the component to be rendered.
*/
id?: string;
/**
* One of the expected status strings (e.g. 'available') to render a badge on the avatar, if necessary.
*/
status?: string;
/**
* TestId of the element, if any.
*/
testId?: string;
2021-12-17 00:16:24 +00:00
/**
* Indicates whether to load the avatar using CORS or not.
*/
useCORS?: boolean;
2019-07-03 15:39:39 +00:00
};
/**
* Creates the styles for the component.
*
* @returns {Object}
*/
const styles = () => {
return {
avatar: {
backgroundColor: '#AAA',
borderRadius: '50%',
color: 'rgba(255, 255, 255, 1)',
fontWeight: '100',
2022-09-13 07:36:00 +00:00
objectFit: 'cover' as const,
textAlign: 'center' as const,
'&.avatar-small': {
height: '28px !important',
width: '28px !important'
},
'&.avatar-xsmall': {
height: '16px !important',
width: '16px !important'
},
'& .jitsi-icon': {
transform: 'translateY(50%)'
},
'& .avatar-svg': {
height: '100%',
width: '100%'
}
},
badge: {
2022-09-13 07:36:00 +00:00
position: 'relative' as const,
'&.avatar-badge:after': {
borderRadius: '50%',
content: '""',
display: 'block',
height: '35%',
position: 'absolute',
bottom: 0,
width: '35%'
},
'&.avatar-badge-available:after': {
backgroundColor: PRESENCE_AVAILABLE_COLOR
},
'&.avatar-badge-away:after': {
backgroundColor: PRESENCE_AWAY_COLOR
},
'&.avatar-badge-busy:after': {
backgroundColor: PRESENCE_BUSY_COLOR
},
'&.avatar-badge-idle:after': {
backgroundColor: PRESENCE_IDLE_COLOR
}
}
};
};
2019-07-03 15:39:39 +00:00
/**
* Implements a stateless avatar component that renders an avatar purely from what gets passed through
* props.
*/
class StatelessAvatar extends AbstractStatelessAvatar<Props> {
2021-12-17 00:16:24 +00:00
/**
* Instantiates a new {@code Component}.
*
* @inheritdoc
*/
constructor(props: Props) {
super(props);
this._onAvatarLoadError = this._onAvatarLoadError.bind(this);
}
2019-07-03 15:39:39 +00:00
/**
* Implements {@code Component#render}.
*
* @inheritdoc
*/
render() {
2021-12-17 00:16:24 +00:00
const { initials, url, useCORS } = this.props;
2019-08-30 16:39:06 +00:00
if (this._isIcon(url)) {
return (
<div
className = { clsx(this._getAvatarClassName(), this._getBadgeClassName()) }
data-testid = { this.props.testId }
id = { this.props.id }
style = { this._getAvatarStyle(this.props.color) }>
<Icon
size = '50%'
src = { url } />
</div>
);
}
2019-07-03 15:39:39 +00:00
if (url) {
return (
<div className = { this._getBadgeClassName() }>
<img
alt = 'avatar'
className = { this._getAvatarClassName() }
2021-12-17 00:16:24 +00:00
crossOrigin = { useCORS ? '' : undefined }
data-testid = { this.props.testId }
id = { this.props.id }
2021-12-17 00:16:24 +00:00
onError = { this._onAvatarLoadError }
src = { url }
style = { this._getAvatarStyle() } />
</div>
2019-07-03 15:39:39 +00:00
);
}
if (initials) {
return (
<div
className = { clsx(this._getAvatarClassName(), this._getBadgeClassName()) }
data-testid = { this.props.testId }
2019-07-03 15:39:39 +00:00
id = { this.props.id }
style = { this._getAvatarStyle(this.props.color) }>
2019-07-03 16:23:00 +00:00
<svg
className = 'avatar-svg'
viewBox = '0 0 100 100'
xmlns = 'http://www.w3.org/2000/svg'
xmlnsXlink = 'http://www.w3.org/1999/xlink'>
<text
dominantBaseline = 'central'
fill = 'rgba(255,255,255,1)'
fontSize = '40pt'
textAnchor = 'middle'
x = '50'
y = '50'>
{ initials }
</text>
2019-07-03 16:23:00 +00:00
</svg>
2019-07-03 15:39:39 +00:00
</div>
);
}
// default avatar
return (
<div className = { this._getBadgeClassName() }>
<img
alt = 'avatar'
className = { this._getAvatarClassName('defaultAvatar') }
data-testid = { this.props.testId }
id = { this.props.id }
src = { this.props.defaultAvatar || 'images/avatar.png' }
style = { this._getAvatarStyle() } />
</div>
2019-07-03 15:39:39 +00:00
);
}
/**
* Constructs a style object to be used on the avatars.
*
* @param {string} color - The desired background color.
2019-07-03 15:39:39 +00:00
* @returns {Object}
*/
_getAvatarStyle(color?: string) {
2019-07-03 15:39:39 +00:00
const { size } = this.props;
return {
background: color || undefined,
2019-07-03 15:39:39 +00:00
fontSize: size ? size * 0.5 : '180%',
height: size || '100%',
width: size || '100%'
};
}
/**
* Constructs a list of class names required for the avatar component.
*
* @param {string} additional - Any additional class to add.
* @returns {string}
*/
_getAvatarClassName(additional?: string) {
return clsx('avatar', additional, this.props.className, this.props.classes.avatar);
2019-07-03 15:39:39 +00:00
}
/**
* Generates a class name to render a badge on the avatar, if necessary.
*
* @returns {string}
*/
_getBadgeClassName() {
const { status } = this.props;
if (status) {
return clsx('avatar-badge', `avatar-badge-${status}`, this.props.classes.badge);
}
return '';
}
2021-12-17 00:16:24 +00:00
/**
* Handles avatar load errors.
*
* @returns {void}
*/
_onAvatarLoadError() {
const { onAvatarLoadError, onAvatarLoadErrorParams } = this.props;
if (typeof onAvatarLoadError === 'function') {
onAvatarLoadError(onAvatarLoadErrorParams);
}
}
2019-07-03 15:39:39 +00:00
}
export default withStyles(styles)(StatelessAvatar);