Add support for avatar status badge (presence)

This commit is contained in:
Bettenbuk Zoltan 2019-12-06 16:02:51 +01:00 committed by Zoltan Bettenbuk
parent 9645391180
commit e683d70a18
12 changed files with 184 additions and 62 deletions

View File

@ -1,12 +1,23 @@
.avatar {
align-items: center;
background-color: #AAA;
display: flex;
border-radius: 50%;
color: rgba(255, 255, 255, 0.6);
font-weight: 100;
justify-content: center;
object-fit: cover;
&.avatar-small {
height: 28px !important;
width: 28px !important;
}
&.avatar-xsmall {
height: 16px !important;
width: 16px !important;
}
.jitsi-icon {
transform: translateY(50%);
}
}
.avatar-foreign {
@ -28,4 +39,28 @@
.defaultAvatar {
opacity: 0.6
}
.avatar-badge {
position: relative;
&-available::after {
@include avatarBadge;
background-color: $presence-available;
}
&-away::after {
@include avatarBadge;
background-color: $presence-away;
}
&-busy::after {
@include avatarBadge;
background-color: $presence-busy;
}
&-idle::after {
@include avatarBadge;
background-color: $presence-idle;
}
}

View File

@ -192,4 +192,17 @@
*/
@mixin transparentBg($color, $alpha) {
background-color: rgba(red($color), green($color), blue($color), $alpha);
}
}
/**
* Avatar status badge mixin
*/
@mixin avatarBadge {
border-radius: 50%;
content: '';
display: block;
height: 35%;
position: absolute;
bottom: 0;
width: 35%;
}

View File

@ -29,6 +29,10 @@ $defaultSideBarFontColor: #44A5FF;
$defaultSemiDarkColor: #ACACAC;
$defaultDarkColor: #2b3d5c;
$defaultWarningColor: rgb(215, 121, 118);
$presence-available: rgb(110, 176, 5);
$presence-away: rgb(250, 201, 20);
$presence-busy: rgb(233, 0, 27);
$presence-idle: rgb(172, 172, 172);
/**
* Toolbar

32
package-lock.json generated
View File

@ -22,38 +22,6 @@
"@atlaskit/type-helpers": "^2.0.0"
}
},
"@atlaskit/avatar": {
"version": "14.1.7",
"resolved": "https://registry.npmjs.org/@atlaskit/avatar/-/avatar-14.1.7.tgz",
"integrity": "sha512-KGtV0lRr3g+JX3XLZQKDGxGhtbVFRvM/Ku5C+CEJw2uDl1KFY0dJxfr2a/E32bEgUuvmqSL7D3ROrTrlHJ2fMA==",
"requires": {
"@atlaskit/analytics-next": "^3.1.2",
"@atlaskit/theme": "^7.0.1",
"@atlaskit/tooltip": "^12.1.13",
"@babel/runtime": "^7.0.0"
},
"dependencies": {
"@atlaskit/analytics-next": {
"version": "3.1.2",
"resolved": "https://registry.npmjs.org/@atlaskit/analytics-next/-/analytics-next-3.1.2.tgz",
"integrity": "sha512-bkYDvl3Ojsnim+bsc9BALfvOjiL7xdb2rTp/4yqUP9pfidtf5HudbOJ849+dKcRCmk/rFbfB/nhDBRU6rv1Ueg==",
"requires": {
"@babel/runtime": "^7.0.0",
"babel-runtime": "^6.26.0",
"prop-types": "^15.5.10"
}
},
"@atlaskit/theme": {
"version": "7.0.1",
"resolved": "https://registry.npmjs.org/@atlaskit/theme/-/theme-7.0.1.tgz",
"integrity": "sha512-wxXDnkUablJketNCrQuNUuazufYEA7kv0Y6Yzv6uvqfuyNpWUQt4H1psz/MW8DbZmCdku9dEYbNVK3nFP5TDGg==",
"requires": {
"@babel/runtime": "^7.0.0",
"prop-types": "^15.5.10"
}
}
}
},
"@atlaskit/blanket": {
"version": "8.0.3",
"resolved": "https://registry.npmjs.org/@atlaskit/blanket/-/blanket-8.0.3.tgz",

View File

@ -15,7 +15,6 @@
"author": "",
"readmeFilename": "README.md",
"dependencies": {
"@atlaskit/avatar": "14.1.7",
"@atlaskit/button": "10.1.1",
"@atlaskit/checkbox": "5.0.10",
"@atlaskit/dropdown-menu": "6.1.25",

View File

@ -54,6 +54,11 @@ export type Props = {
*/
size: number,
/**
* One of the expected status strings (e.g. 'available') to render a badge on the avatar, if necessary.
*/
status?: ?string,
/**
* URL of the avatar, if any.
*/
@ -117,6 +122,7 @@ class Avatar<P: Props> extends PureComponent<P, State> {
colorBase,
id,
size,
status,
url
} = this.props;
const { avatarFailed } = this.state;
@ -128,6 +134,7 @@ class Avatar<P: Props> extends PureComponent<P, State> {
initials: undefined,
onAvatarLoadError: undefined,
size,
status,
url: undefined
};

View File

@ -12,6 +12,11 @@ import styles from './styles';
type Props = AbstractProps & {
/**
* One of the expected status strings (e.g. 'available') to render a badge on the avatar, if necessary.
*/
status?: ?string,
/**
* External style passed to the componant.
*/
@ -46,18 +51,40 @@ export default class StatelessAvatar extends AbstractStatelessAvatar<Props> {
}
return (
<View
style = { [
styles.avatarContainer(size),
style
] }>
{ avatar }
<View>
<View
style = { [
styles.avatarContainer(size),
style
] }>
{ avatar }
</View>
{ this._renderAvatarStatus() }
</View>
);
}
_isIcon: (?string | ?Object) => boolean
/**
* Renders a badge representing the avatar status.
*
* @returns {React$Elementaa}
*/
_renderAvatarStatus() {
const { size, status } = this.props;
if (!status) {
return null;
}
return (
<View style = { styles.badgeContainer }>
<View style = { styles.badge(size, status) } />
</View>
);
}
/**
* Renders the default avatar.
*

View File

@ -1,5 +1,7 @@
// @flow
import { StyleSheet } from 'react-native';
import { ColorPalette } from '../../../styles';
const DEFAULT_SIZE = 65;
@ -27,6 +29,38 @@ export default {
};
},
badge: (size: number = DEFAULT_SIZE, status: string) => {
let color;
switch (status) {
case 'available':
color = 'rgb(110, 176, 5)';
break;
case 'away':
color = 'rgb(250, 201, 20)';
break;
case 'busy':
color = 'rgb(233, 0, 27)';
break;
case 'idle':
color = 'rgb(172, 172, 172)';
break;
}
return {
backgroundColor: color,
borderRadius: size / 2,
bottom: 0,
height: size * 0.3,
position: 'absolute',
width: size * 0.3
};
},
badgeContainer: {
...StyleSheet.absoluteFillObject
},
initialsContainer: {
alignItems: 'center',
alignSelf: 'stretch',

View File

@ -21,7 +21,12 @@ type Props = AbstractProps & {
/**
* ID of the component to be rendered.
*/
id?: string
id?: string,
/**
* One of the expected status strings (e.g. 'available') to render a badge on the avatar, if necessary.
*/
status?: ?string
};
/**
@ -40,7 +45,7 @@ export default class StatelessAvatar extends AbstractStatelessAvatar<Props> {
if (this._isIcon(url)) {
return (
<div
className = { this._getAvatarClassName() }
className = { `${this._getAvatarClassName()} ${this._getBadgeClassName()}` }
id = { this.props.id }
style = { this._getAvatarStyle(this.props.color) }>
<Icon
@ -52,19 +57,21 @@ export default class StatelessAvatar extends AbstractStatelessAvatar<Props> {
if (url) {
return (
<img
className = { this._getAvatarClassName() }
id = { this.props.id }
onError = { this.props.onAvatarLoadError }
src = { url }
style = { this._getAvatarStyle() } />
<div className = { this._getBadgeClassName() }>
<img
className = { this._getAvatarClassName() }
id = { this.props.id }
onError = { this.props.onAvatarLoadError }
src = { url }
style = { this._getAvatarStyle() } />
</div>
);
}
if (initials) {
return (
<div
className = { this._getAvatarClassName() }
className = { `${this._getAvatarClassName()} ${this._getBadgeClassName()}` }
id = { this.props.id }
style = { this._getAvatarStyle(this.props.color) }>
<svg
@ -87,11 +94,13 @@ export default class StatelessAvatar extends AbstractStatelessAvatar<Props> {
// default avatar
return (
<img
className = { this._getAvatarClassName('defaultAvatar') }
id = { this.props.id }
src = { this.props.defaultAvatar || 'images/avatar.png' }
style = { this._getAvatarStyle() } />
<div className = { this._getBadgeClassName() }>
<img
className = { this._getAvatarClassName('defaultAvatar') }
id = { this.props.id }
src = { this.props.defaultAvatar || 'images/avatar.png' }
style = { this._getAvatarStyle() } />
</div>
);
}
@ -122,5 +131,20 @@ export default class StatelessAvatar extends AbstractStatelessAvatar<Props> {
return `avatar ${additional || ''} ${this.props.className || ''}`;
}
/**
* Generates a class name to render a badge on the avatar, if necessary.
*
* @returns {string}
*/
_getBadgeClassName() {
const { status } = this.props;
if (status) {
return `avatar-badge avatar-badge-${status}`;
}
return '';
}
_isIcon: (?string | ?Object) => boolean
}

View File

@ -23,6 +23,11 @@ type Props = {
*/
avatarSize?: number,
/**
* One of the expected status strings (e.g. 'available') to render a badge on the avatar, if necessary.
*/
avatarStatus?: ?string,
/**
* External style to be applied to the avatar (icon).
*/
@ -83,6 +88,7 @@ export default class AvatarListItem extends Component<Props> {
const {
avatarOnly,
avatarSize = AVATAR_SIZE,
avatarStatus,
avatarStyle
} = this.props;
const { avatar, colorBase, lines, title } = this.props.item;
@ -96,6 +102,7 @@ export default class AvatarListItem extends Component<Props> {
colorBase = { colorBase }
displayName = { title }
size = { avatarSize }
status = { avatarStatus }
style = { avatarStyle }
url = { avatar } />
{ avatarOnly || <Container style = { styles.listItemDetails }>

View File

@ -443,6 +443,7 @@ class AddPeopleDialog extends AbstractAddPeopleDialog<Props, State> {
<AvatarListItem
avatarOnly = { true }
avatarSize = { AVATAR_SIZE }
avatarStatus = { item.status }
avatarStyle = { styles.avatar }
avatarTextStyle = { styles.avatarText }
item = { renderableItem }
@ -497,6 +498,7 @@ class AddPeopleDialog extends AbstractAddPeopleDialog<Props, State> {
style = { styles.itemWrapper }>
<AvatarListItem
avatarSize = { AVATAR_SIZE }
avatarStatus = { item.status }
avatarStyle = { styles.avatar }
avatarTextStyle = { styles.avatarText }
item = { renderableItem }

View File

@ -1,11 +1,11 @@
// @flow
import Avatar from '@atlaskit/avatar';
import InlineMessage from '@atlaskit/inline-message';
import React from 'react';
import type { Dispatch } from 'redux';
import { createInviteDialogEvent, sendAnalytics } from '../../../../analytics';
import { Avatar } from '../../../../base/avatar';
import { Dialog, hideDialog } from '../../../../base/dialog';
import { translate, translateToHTML } from '../../../../base/i18n';
import { Icon, IconPhone } from '../../../../base/icons';
@ -289,13 +289,15 @@ class AddPeopleDialog extends AbstractAddPeopleDialog<Props, State> {
return {
content: user.name,
elemBefore: <Avatar
size = 'small'
src = { user.avatar } />,
className = { 'avatar-small' }
status = { user.status }
url = { user.avatar } />,
item: user,
tag: {
elemBefore: <Avatar
size = 'xsmall'
src = { user.avatar } />
className = { 'avatar-xsmall' }
status = { user.status }
url = { user.avatar } />
},
value: user.id || user.user_id
};