Add support for avatar status badge (presence)
This commit is contained in:
parent
9645391180
commit
e683d70a18
@ -1,12 +1,23 @@
|
|||||||
.avatar {
|
.avatar {
|
||||||
align-items: center;
|
|
||||||
background-color: #AAA;
|
background-color: #AAA;
|
||||||
display: flex;
|
|
||||||
border-radius: 50%;
|
border-radius: 50%;
|
||||||
color: rgba(255, 255, 255, 0.6);
|
color: rgba(255, 255, 255, 0.6);
|
||||||
font-weight: 100;
|
font-weight: 100;
|
||||||
justify-content: center;
|
|
||||||
object-fit: cover;
|
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 {
|
.avatar-foreign {
|
||||||
@ -28,4 +39,28 @@
|
|||||||
|
|
||||||
.defaultAvatar {
|
.defaultAvatar {
|
||||||
opacity: 0.6
|
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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@ -192,4 +192,17 @@
|
|||||||
*/
|
*/
|
||||||
@mixin transparentBg($color, $alpha) {
|
@mixin transparentBg($color, $alpha) {
|
||||||
background-color: rgba(red($color), green($color), blue($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%;
|
||||||
|
}
|
||||||
|
|||||||
@ -29,6 +29,10 @@ $defaultSideBarFontColor: #44A5FF;
|
|||||||
$defaultSemiDarkColor: #ACACAC;
|
$defaultSemiDarkColor: #ACACAC;
|
||||||
$defaultDarkColor: #2b3d5c;
|
$defaultDarkColor: #2b3d5c;
|
||||||
$defaultWarningColor: rgb(215, 121, 118);
|
$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
|
* Toolbar
|
||||||
|
|||||||
32
package-lock.json
generated
32
package-lock.json
generated
@ -22,38 +22,6 @@
|
|||||||
"@atlaskit/type-helpers": "^2.0.0"
|
"@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": {
|
"@atlaskit/blanket": {
|
||||||
"version": "8.0.3",
|
"version": "8.0.3",
|
||||||
"resolved": "https://registry.npmjs.org/@atlaskit/blanket/-/blanket-8.0.3.tgz",
|
"resolved": "https://registry.npmjs.org/@atlaskit/blanket/-/blanket-8.0.3.tgz",
|
||||||
|
|||||||
@ -15,7 +15,6 @@
|
|||||||
"author": "",
|
"author": "",
|
||||||
"readmeFilename": "README.md",
|
"readmeFilename": "README.md",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@atlaskit/avatar": "14.1.7",
|
|
||||||
"@atlaskit/button": "10.1.1",
|
"@atlaskit/button": "10.1.1",
|
||||||
"@atlaskit/checkbox": "5.0.10",
|
"@atlaskit/checkbox": "5.0.10",
|
||||||
"@atlaskit/dropdown-menu": "6.1.25",
|
"@atlaskit/dropdown-menu": "6.1.25",
|
||||||
|
|||||||
@ -54,6 +54,11 @@ export type Props = {
|
|||||||
*/
|
*/
|
||||||
size: number,
|
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.
|
* URL of the avatar, if any.
|
||||||
*/
|
*/
|
||||||
@ -117,6 +122,7 @@ class Avatar<P: Props> extends PureComponent<P, State> {
|
|||||||
colorBase,
|
colorBase,
|
||||||
id,
|
id,
|
||||||
size,
|
size,
|
||||||
|
status,
|
||||||
url
|
url
|
||||||
} = this.props;
|
} = this.props;
|
||||||
const { avatarFailed } = this.state;
|
const { avatarFailed } = this.state;
|
||||||
@ -128,6 +134,7 @@ class Avatar<P: Props> extends PureComponent<P, State> {
|
|||||||
initials: undefined,
|
initials: undefined,
|
||||||
onAvatarLoadError: undefined,
|
onAvatarLoadError: undefined,
|
||||||
size,
|
size,
|
||||||
|
status,
|
||||||
url: undefined
|
url: undefined
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@ -12,6 +12,11 @@ import styles from './styles';
|
|||||||
|
|
||||||
type Props = AbstractProps & {
|
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.
|
* External style passed to the componant.
|
||||||
*/
|
*/
|
||||||
@ -46,18 +51,40 @@ export default class StatelessAvatar extends AbstractStatelessAvatar<Props> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<View
|
<View>
|
||||||
style = { [
|
<View
|
||||||
styles.avatarContainer(size),
|
style = { [
|
||||||
style
|
styles.avatarContainer(size),
|
||||||
] }>
|
style
|
||||||
{ avatar }
|
] }>
|
||||||
|
{ avatar }
|
||||||
|
</View>
|
||||||
|
{ this._renderAvatarStatus() }
|
||||||
</View>
|
</View>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
_isIcon: (?string | ?Object) => boolean
|
_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.
|
* Renders the default avatar.
|
||||||
*
|
*
|
||||||
|
|||||||
@ -1,5 +1,7 @@
|
|||||||
// @flow
|
// @flow
|
||||||
|
|
||||||
|
import { StyleSheet } from 'react-native';
|
||||||
|
|
||||||
import { ColorPalette } from '../../../styles';
|
import { ColorPalette } from '../../../styles';
|
||||||
|
|
||||||
const DEFAULT_SIZE = 65;
|
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: {
|
initialsContainer: {
|
||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
alignSelf: 'stretch',
|
alignSelf: 'stretch',
|
||||||
|
|||||||
@ -21,7 +21,12 @@ type Props = AbstractProps & {
|
|||||||
/**
|
/**
|
||||||
* ID of the component to be rendered.
|
* 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)) {
|
if (this._isIcon(url)) {
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className = { this._getAvatarClassName() }
|
className = { `${this._getAvatarClassName()} ${this._getBadgeClassName()}` }
|
||||||
id = { this.props.id }
|
id = { this.props.id }
|
||||||
style = { this._getAvatarStyle(this.props.color) }>
|
style = { this._getAvatarStyle(this.props.color) }>
|
||||||
<Icon
|
<Icon
|
||||||
@ -52,19 +57,21 @@ export default class StatelessAvatar extends AbstractStatelessAvatar<Props> {
|
|||||||
|
|
||||||
if (url) {
|
if (url) {
|
||||||
return (
|
return (
|
||||||
<img
|
<div className = { this._getBadgeClassName() }>
|
||||||
className = { this._getAvatarClassName() }
|
<img
|
||||||
id = { this.props.id }
|
className = { this._getAvatarClassName() }
|
||||||
onError = { this.props.onAvatarLoadError }
|
id = { this.props.id }
|
||||||
src = { url }
|
onError = { this.props.onAvatarLoadError }
|
||||||
style = { this._getAvatarStyle() } />
|
src = { url }
|
||||||
|
style = { this._getAvatarStyle() } />
|
||||||
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (initials) {
|
if (initials) {
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className = { this._getAvatarClassName() }
|
className = { `${this._getAvatarClassName()} ${this._getBadgeClassName()}` }
|
||||||
id = { this.props.id }
|
id = { this.props.id }
|
||||||
style = { this._getAvatarStyle(this.props.color) }>
|
style = { this._getAvatarStyle(this.props.color) }>
|
||||||
<svg
|
<svg
|
||||||
@ -87,11 +94,13 @@ export default class StatelessAvatar extends AbstractStatelessAvatar<Props> {
|
|||||||
|
|
||||||
// default avatar
|
// default avatar
|
||||||
return (
|
return (
|
||||||
<img
|
<div className = { this._getBadgeClassName() }>
|
||||||
className = { this._getAvatarClassName('defaultAvatar') }
|
<img
|
||||||
id = { this.props.id }
|
className = { this._getAvatarClassName('defaultAvatar') }
|
||||||
src = { this.props.defaultAvatar || 'images/avatar.png' }
|
id = { this.props.id }
|
||||||
style = { this._getAvatarStyle() } />
|
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 || ''}`;
|
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
|
_isIcon: (?string | ?Object) => boolean
|
||||||
}
|
}
|
||||||
|
|||||||
@ -23,6 +23,11 @@ type Props = {
|
|||||||
*/
|
*/
|
||||||
avatarSize?: number,
|
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).
|
* External style to be applied to the avatar (icon).
|
||||||
*/
|
*/
|
||||||
@ -83,6 +88,7 @@ export default class AvatarListItem extends Component<Props> {
|
|||||||
const {
|
const {
|
||||||
avatarOnly,
|
avatarOnly,
|
||||||
avatarSize = AVATAR_SIZE,
|
avatarSize = AVATAR_SIZE,
|
||||||
|
avatarStatus,
|
||||||
avatarStyle
|
avatarStyle
|
||||||
} = this.props;
|
} = this.props;
|
||||||
const { avatar, colorBase, lines, title } = this.props.item;
|
const { avatar, colorBase, lines, title } = this.props.item;
|
||||||
@ -96,6 +102,7 @@ export default class AvatarListItem extends Component<Props> {
|
|||||||
colorBase = { colorBase }
|
colorBase = { colorBase }
|
||||||
displayName = { title }
|
displayName = { title }
|
||||||
size = { avatarSize }
|
size = { avatarSize }
|
||||||
|
status = { avatarStatus }
|
||||||
style = { avatarStyle }
|
style = { avatarStyle }
|
||||||
url = { avatar } />
|
url = { avatar } />
|
||||||
{ avatarOnly || <Container style = { styles.listItemDetails }>
|
{ avatarOnly || <Container style = { styles.listItemDetails }>
|
||||||
|
|||||||
@ -443,6 +443,7 @@ class AddPeopleDialog extends AbstractAddPeopleDialog<Props, State> {
|
|||||||
<AvatarListItem
|
<AvatarListItem
|
||||||
avatarOnly = { true }
|
avatarOnly = { true }
|
||||||
avatarSize = { AVATAR_SIZE }
|
avatarSize = { AVATAR_SIZE }
|
||||||
|
avatarStatus = { item.status }
|
||||||
avatarStyle = { styles.avatar }
|
avatarStyle = { styles.avatar }
|
||||||
avatarTextStyle = { styles.avatarText }
|
avatarTextStyle = { styles.avatarText }
|
||||||
item = { renderableItem }
|
item = { renderableItem }
|
||||||
@ -497,6 +498,7 @@ class AddPeopleDialog extends AbstractAddPeopleDialog<Props, State> {
|
|||||||
style = { styles.itemWrapper }>
|
style = { styles.itemWrapper }>
|
||||||
<AvatarListItem
|
<AvatarListItem
|
||||||
avatarSize = { AVATAR_SIZE }
|
avatarSize = { AVATAR_SIZE }
|
||||||
|
avatarStatus = { item.status }
|
||||||
avatarStyle = { styles.avatar }
|
avatarStyle = { styles.avatar }
|
||||||
avatarTextStyle = { styles.avatarText }
|
avatarTextStyle = { styles.avatarText }
|
||||||
item = { renderableItem }
|
item = { renderableItem }
|
||||||
|
|||||||
@ -1,11 +1,11 @@
|
|||||||
// @flow
|
// @flow
|
||||||
|
|
||||||
import Avatar from '@atlaskit/avatar';
|
|
||||||
import InlineMessage from '@atlaskit/inline-message';
|
import InlineMessage from '@atlaskit/inline-message';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import type { Dispatch } from 'redux';
|
import type { Dispatch } from 'redux';
|
||||||
|
|
||||||
import { createInviteDialogEvent, sendAnalytics } from '../../../../analytics';
|
import { createInviteDialogEvent, sendAnalytics } from '../../../../analytics';
|
||||||
|
import { Avatar } from '../../../../base/avatar';
|
||||||
import { Dialog, hideDialog } from '../../../../base/dialog';
|
import { Dialog, hideDialog } from '../../../../base/dialog';
|
||||||
import { translate, translateToHTML } from '../../../../base/i18n';
|
import { translate, translateToHTML } from '../../../../base/i18n';
|
||||||
import { Icon, IconPhone } from '../../../../base/icons';
|
import { Icon, IconPhone } from '../../../../base/icons';
|
||||||
@ -289,13 +289,15 @@ class AddPeopleDialog extends AbstractAddPeopleDialog<Props, State> {
|
|||||||
return {
|
return {
|
||||||
content: user.name,
|
content: user.name,
|
||||||
elemBefore: <Avatar
|
elemBefore: <Avatar
|
||||||
size = 'small'
|
className = { 'avatar-small' }
|
||||||
src = { user.avatar } />,
|
status = { user.status }
|
||||||
|
url = { user.avatar } />,
|
||||||
item: user,
|
item: user,
|
||||||
tag: {
|
tag: {
|
||||||
elemBefore: <Avatar
|
elemBefore: <Avatar
|
||||||
size = 'xsmall'
|
className = { 'avatar-xsmall' }
|
||||||
src = { user.avatar } />
|
status = { user.status }
|
||||||
|
url = { user.avatar } />
|
||||||
},
|
},
|
||||||
value: user.id || user.user_id
|
value: user.id || user.user_id
|
||||||
};
|
};
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user