From e683d70a18bbda97c25b6cdb6a5eb366a63ec270 Mon Sep 17 00:00:00 2001 From: Bettenbuk Zoltan Date: Fri, 6 Dec 2019 16:02:51 +0100 Subject: [PATCH] Add support for avatar status badge (presence) --- css/_avatar.scss | 41 +++++++++++++-- css/_mixins.scss | 15 +++++- css/_variables.scss | 4 ++ package-lock.json | 32 ------------ package.json | 1 - .../features/base/avatar/components/Avatar.js | 7 +++ .../components/native/StatelessAvatar.js | 39 +++++++++++--- .../base/avatar/components/native/styles.js | 34 ++++++++++++ .../avatar/components/web/StatelessAvatar.js | 52 ++++++++++++++----- .../react/components/native/AvatarListItem.js | 7 +++ .../native/AddPeopleDialog.js | 2 + .../add-people-dialog/web/AddPeopleDialog.js | 12 +++-- 12 files changed, 184 insertions(+), 62 deletions(-) diff --git a/css/_avatar.scss b/css/_avatar.scss index cb47d7d89..347d4bd18 100644 --- a/css/_avatar.scss +++ b/css/_avatar.scss @@ -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; + } } \ No newline at end of file diff --git a/css/_mixins.scss b/css/_mixins.scss index 27795c8f4..11ec2a6e7 100644 --- a/css/_mixins.scss +++ b/css/_mixins.scss @@ -192,4 +192,17 @@ */ @mixin transparentBg($color, $alpha) { background-color: rgba(red($color), green($color), blue($color), $alpha); -} \ No newline at end of file +} + +/** + * Avatar status badge mixin + */ +@mixin avatarBadge { + border-radius: 50%; + content: ''; + display: block; + height: 35%; + position: absolute; + bottom: 0; + width: 35%; +} diff --git a/css/_variables.scss b/css/_variables.scss index 20645aca7..3c0cdf582 100644 --- a/css/_variables.scss +++ b/css/_variables.scss @@ -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 diff --git a/package-lock.json b/package-lock.json index c188bd607..e9b7b43ed 100644 --- a/package-lock.json +++ b/package-lock.json @@ -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", diff --git a/package.json b/package.json index c11a914b0..36eacfd20 100644 --- a/package.json +++ b/package.json @@ -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", diff --git a/react/features/base/avatar/components/Avatar.js b/react/features/base/avatar/components/Avatar.js index 9b08feba8..fcb93efcb 100644 --- a/react/features/base/avatar/components/Avatar.js +++ b/react/features/base/avatar/components/Avatar.js @@ -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 extends PureComponent { colorBase, id, size, + status, url } = this.props; const { avatarFailed } = this.state; @@ -128,6 +134,7 @@ class Avatar extends PureComponent { initials: undefined, onAvatarLoadError: undefined, size, + status, url: undefined }; diff --git a/react/features/base/avatar/components/native/StatelessAvatar.js b/react/features/base/avatar/components/native/StatelessAvatar.js index 08008c7e5..cb22c5194 100644 --- a/react/features/base/avatar/components/native/StatelessAvatar.js +++ b/react/features/base/avatar/components/native/StatelessAvatar.js @@ -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 { } return ( - - { avatar } + + + { avatar } + + { this._renderAvatarStatus() } ); } _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 ( + + + + ); + } + /** * Renders the default avatar. * diff --git a/react/features/base/avatar/components/native/styles.js b/react/features/base/avatar/components/native/styles.js index 5c602c09f..a153c06b5 100644 --- a/react/features/base/avatar/components/native/styles.js +++ b/react/features/base/avatar/components/native/styles.js @@ -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', diff --git a/react/features/base/avatar/components/web/StatelessAvatar.js b/react/features/base/avatar/components/web/StatelessAvatar.js index 30eff9437..619046e97 100644 --- a/react/features/base/avatar/components/web/StatelessAvatar.js +++ b/react/features/base/avatar/components/web/StatelessAvatar.js @@ -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 { if (this._isIcon(url)) { return (
{ if (url) { return ( - +
+ +
); } if (initials) { return (
{ // default avatar return ( - +
+ +
); } @@ -122,5 +131,20 @@ export default class StatelessAvatar extends AbstractStatelessAvatar { 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 } diff --git a/react/features/base/react/components/native/AvatarListItem.js b/react/features/base/react/components/native/AvatarListItem.js index 77e8a3431..1f0a0d34b 100644 --- a/react/features/base/react/components/native/AvatarListItem.js +++ b/react/features/base/react/components/native/AvatarListItem.js @@ -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 { 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 { colorBase = { colorBase } displayName = { title } size = { avatarSize } + status = { avatarStatus } style = { avatarStyle } url = { avatar } /> { avatarOnly || diff --git a/react/features/invite/components/add-people-dialog/native/AddPeopleDialog.js b/react/features/invite/components/add-people-dialog/native/AddPeopleDialog.js index de1a3fe44..879f8f926 100644 --- a/react/features/invite/components/add-people-dialog/native/AddPeopleDialog.js +++ b/react/features/invite/components/add-people-dialog/native/AddPeopleDialog.js @@ -443,6 +443,7 @@ class AddPeopleDialog extends AbstractAddPeopleDialog { { style = { styles.itemWrapper }> { return { content: user.name, elemBefore: , + className = { 'avatar-small' } + status = { user.status } + url = { user.avatar } />, item: user, tag: { elemBefore: + className = { 'avatar-xsmall' } + status = { user.status } + url = { user.avatar } /> }, value: user.id || user.user_id };