Browse Source

Add support for avatar status badge (presence)

master
Bettenbuk Zoltan 4 years ago
parent
commit
e683d70a18

+ 38
- 3
css/_avatar.scss View File

@@ -1,12 +1,23 @@
1 1
 .avatar {
2
-    align-items: center;
3 2
     background-color: #AAA;
4
-    display: flex;
5 3
     border-radius: 50%;
6 4
     color: rgba(255, 255, 255, 0.6);
7 5
     font-weight: 100;
8
-    justify-content: center;
9 6
     object-fit: cover;
7
+
8
+    &.avatar-small {
9
+        height: 28px !important;
10
+        width: 28px !important;
11
+    }
12
+
13
+    &.avatar-xsmall {
14
+        height: 16px !important;
15
+        width: 16px !important;
16
+    }
17
+
18
+    .jitsi-icon {
19
+        transform: translateY(50%);
20
+    }
10 21
 }
11 22
 
12 23
 .avatar-foreign {
@@ -28,4 +39,28 @@
28 39
 
29 40
 .defaultAvatar {
30 41
     opacity: 0.6
42
+}
43
+
44
+.avatar-badge {
45
+    position: relative;
46
+
47
+    &-available::after {
48
+        @include avatarBadge;
49
+        background-color: $presence-available;
50
+    }
51
+
52
+    &-away::after {
53
+        @include avatarBadge;
54
+        background-color: $presence-away;
55
+    }
56
+
57
+    &-busy::after {
58
+        @include avatarBadge;
59
+        background-color: $presence-busy;
60
+    }
61
+
62
+    &-idle::after {
63
+        @include avatarBadge;
64
+        background-color: $presence-idle;
65
+    }
31 66
 }

+ 14
- 1
css/_mixins.scss View File

@@ -192,4 +192,17 @@
192 192
  */
193 193
 @mixin transparentBg($color, $alpha) {
194 194
     background-color: rgba(red($color), green($color), blue($color), $alpha);
195
-}
195
+}
196
+
197
+/**
198
+ * Avatar status badge mixin
199
+ */
200
+@mixin avatarBadge {
201
+    border-radius: 50%;
202
+    content: '';
203
+    display: block;
204
+    height: 35%;
205
+    position: absolute;
206
+    bottom: 0;
207
+    width: 35%;
208
+}

+ 4
- 0
css/_variables.scss View File

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

+ 0
- 32
package-lock.json View File

@@ -22,38 +22,6 @@
22 22
         "@atlaskit/type-helpers": "^2.0.0"
23 23
       }
24 24
     },
25
-    "@atlaskit/avatar": {
26
-      "version": "14.1.7",
27
-      "resolved": "https://registry.npmjs.org/@atlaskit/avatar/-/avatar-14.1.7.tgz",
28
-      "integrity": "sha512-KGtV0lRr3g+JX3XLZQKDGxGhtbVFRvM/Ku5C+CEJw2uDl1KFY0dJxfr2a/E32bEgUuvmqSL7D3ROrTrlHJ2fMA==",
29
-      "requires": {
30
-        "@atlaskit/analytics-next": "^3.1.2",
31
-        "@atlaskit/theme": "^7.0.1",
32
-        "@atlaskit/tooltip": "^12.1.13",
33
-        "@babel/runtime": "^7.0.0"
34
-      },
35
-      "dependencies": {
36
-        "@atlaskit/analytics-next": {
37
-          "version": "3.1.2",
38
-          "resolved": "https://registry.npmjs.org/@atlaskit/analytics-next/-/analytics-next-3.1.2.tgz",
39
-          "integrity": "sha512-bkYDvl3Ojsnim+bsc9BALfvOjiL7xdb2rTp/4yqUP9pfidtf5HudbOJ849+dKcRCmk/rFbfB/nhDBRU6rv1Ueg==",
40
-          "requires": {
41
-            "@babel/runtime": "^7.0.0",
42
-            "babel-runtime": "^6.26.0",
43
-            "prop-types": "^15.5.10"
44
-          }
45
-        },
46
-        "@atlaskit/theme": {
47
-          "version": "7.0.1",
48
-          "resolved": "https://registry.npmjs.org/@atlaskit/theme/-/theme-7.0.1.tgz",
49
-          "integrity": "sha512-wxXDnkUablJketNCrQuNUuazufYEA7kv0Y6Yzv6uvqfuyNpWUQt4H1psz/MW8DbZmCdku9dEYbNVK3nFP5TDGg==",
50
-          "requires": {
51
-            "@babel/runtime": "^7.0.0",
52
-            "prop-types": "^15.5.10"
53
-          }
54
-        }
55
-      }
56
-    },
57 25
     "@atlaskit/blanket": {
58 26
       "version": "8.0.3",
59 27
       "resolved": "https://registry.npmjs.org/@atlaskit/blanket/-/blanket-8.0.3.tgz",

+ 0
- 1
package.json View File

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

+ 7
- 0
react/features/base/avatar/components/Avatar.js View File

@@ -54,6 +54,11 @@ export type Props = {
54 54
      */
55 55
     size: number,
56 56
 
57
+    /**
58
+     * One of the expected status strings (e.g. 'available') to render a badge on the avatar, if necessary.
59
+     */
60
+    status?: ?string,
61
+
57 62
     /**
58 63
      * URL of the avatar, if any.
59 64
      */
@@ -117,6 +122,7 @@ class Avatar<P: Props> extends PureComponent<P, State> {
117 122
             colorBase,
118 123
             id,
119 124
             size,
125
+            status,
120 126
             url
121 127
         } = this.props;
122 128
         const { avatarFailed } = this.state;
@@ -128,6 +134,7 @@ class Avatar<P: Props> extends PureComponent<P, State> {
128 134
             initials: undefined,
129 135
             onAvatarLoadError: undefined,
130 136
             size,
137
+            status,
131 138
             url: undefined
132 139
         };
133 140
 

+ 33
- 6
react/features/base/avatar/components/native/StatelessAvatar.js View File

@@ -12,6 +12,11 @@ import styles from './styles';
12 12
 
13 13
 type Props = AbstractProps & {
14 14
 
15
+    /**
16
+     * One of the expected status strings (e.g. 'available') to render a badge on the avatar, if necessary.
17
+     */
18
+    status?: ?string,
19
+
15 20
     /**
16 21
      * External style passed to the componant.
17 22
      */
@@ -46,18 +51,40 @@ export default class StatelessAvatar extends AbstractStatelessAvatar<Props> {
46 51
         }
47 52
 
48 53
         return (
49
-            <View
50
-                style = { [
51
-                    styles.avatarContainer(size),
52
-                    style
53
-                ] }>
54
-                { avatar }
54
+            <View>
55
+                <View
56
+                    style = { [
57
+                        styles.avatarContainer(size),
58
+                        style
59
+                    ] }>
60
+                    { avatar }
61
+                </View>
62
+                { this._renderAvatarStatus() }
55 63
             </View>
56 64
         );
57 65
     }
58 66
 
59 67
     _isIcon: (?string | ?Object) => boolean
60 68
 
69
+    /**
70
+     * Renders a badge representing the avatar status.
71
+     *
72
+     * @returns {React$Elementaa}
73
+     */
74
+    _renderAvatarStatus() {
75
+        const { size, status } = this.props;
76
+
77
+        if (!status) {
78
+            return null;
79
+        }
80
+
81
+        return (
82
+            <View style = { styles.badgeContainer }>
83
+                <View style = { styles.badge(size, status) } />
84
+            </View>
85
+        );
86
+    }
87
+
61 88
     /**
62 89
      * Renders the default avatar.
63 90
      *

+ 34
- 0
react/features/base/avatar/components/native/styles.js View File

@@ -1,5 +1,7 @@
1 1
 // @flow
2 2
 
3
+import { StyleSheet } from 'react-native';
4
+
3 5
 import { ColorPalette } from '../../../styles';
4 6
 
5 7
 const DEFAULT_SIZE = 65;
@@ -27,6 +29,38 @@ export default {
27 29
         };
28 30
     },
29 31
 
32
+    badge: (size: number = DEFAULT_SIZE, status: string) => {
33
+        let color;
34
+
35
+        switch (status) {
36
+        case 'available':
37
+            color = 'rgb(110, 176, 5)';
38
+            break;
39
+        case 'away':
40
+            color = 'rgb(250, 201, 20)';
41
+            break;
42
+        case 'busy':
43
+            color = 'rgb(233, 0, 27)';
44
+            break;
45
+        case 'idle':
46
+            color = 'rgb(172, 172, 172)';
47
+            break;
48
+        }
49
+
50
+        return {
51
+            backgroundColor: color,
52
+            borderRadius: size / 2,
53
+            bottom: 0,
54
+            height: size * 0.3,
55
+            position: 'absolute',
56
+            width: size * 0.3
57
+        };
58
+    },
59
+
60
+    badgeContainer: {
61
+        ...StyleSheet.absoluteFillObject
62
+    },
63
+
30 64
     initialsContainer: {
31 65
         alignItems: 'center',
32 66
         alignSelf: 'stretch',

+ 38
- 14
react/features/base/avatar/components/web/StatelessAvatar.js View File

@@ -21,7 +21,12 @@ type Props = AbstractProps & {
21 21
     /**
22 22
      * ID of the component to be rendered.
23 23
      */
24
-    id?: string
24
+    id?: string,
25
+
26
+    /**
27
+     * One of the expected status strings (e.g. 'available') to render a badge on the avatar, if necessary.
28
+     */
29
+    status?: ?string
25 30
 };
26 31
 
27 32
 /**
@@ -40,7 +45,7 @@ export default class StatelessAvatar extends AbstractStatelessAvatar<Props> {
40 45
         if (this._isIcon(url)) {
41 46
             return (
42 47
                 <div
43
-                    className = { this._getAvatarClassName() }
48
+                    className = { `${this._getAvatarClassName()} ${this._getBadgeClassName()}` }
44 49
                     id = { this.props.id }
45 50
                     style = { this._getAvatarStyle(this.props.color) }>
46 51
                     <Icon
@@ -52,19 +57,21 @@ export default class StatelessAvatar extends AbstractStatelessAvatar<Props> {
52 57
 
53 58
         if (url) {
54 59
             return (
55
-                <img
56
-                    className = { this._getAvatarClassName() }
57
-                    id = { this.props.id }
58
-                    onError = { this.props.onAvatarLoadError }
59
-                    src = { url }
60
-                    style = { this._getAvatarStyle() } />
60
+                <div className = { this._getBadgeClassName() }>
61
+                    <img
62
+                        className = { this._getAvatarClassName() }
63
+                        id = { this.props.id }
64
+                        onError = { this.props.onAvatarLoadError }
65
+                        src = { url }
66
+                        style = { this._getAvatarStyle() } />
67
+                </div>
61 68
             );
62 69
         }
63 70
 
64 71
         if (initials) {
65 72
             return (
66 73
                 <div
67
-                    className = { this._getAvatarClassName() }
74
+                    className = { `${this._getAvatarClassName()} ${this._getBadgeClassName()}` }
68 75
                     id = { this.props.id }
69 76
                     style = { this._getAvatarStyle(this.props.color) }>
70 77
                     <svg
@@ -87,11 +94,13 @@ export default class StatelessAvatar extends AbstractStatelessAvatar<Props> {
87 94
 
88 95
         // default avatar
89 96
         return (
90
-            <img
91
-                className = { this._getAvatarClassName('defaultAvatar') }
92
-                id = { this.props.id }
93
-                src = { this.props.defaultAvatar || 'images/avatar.png' }
94
-                style = { this._getAvatarStyle() } />
97
+            <div className = { this._getBadgeClassName() }>
98
+                <img
99
+                    className = { this._getAvatarClassName('defaultAvatar') }
100
+                    id = { this.props.id }
101
+                    src = { this.props.defaultAvatar || 'images/avatar.png' }
102
+                    style = { this._getAvatarStyle() } />
103
+            </div>
95 104
         );
96 105
     }
97 106
 
@@ -122,5 +131,20 @@ export default class StatelessAvatar extends AbstractStatelessAvatar<Props> {
122 131
         return `avatar ${additional || ''} ${this.props.className || ''}`;
123 132
     }
124 133
 
134
+    /**
135
+     * Generates a class name to render a badge on the avatar, if necessary.
136
+     *
137
+     * @returns {string}
138
+     */
139
+    _getBadgeClassName() {
140
+        const { status } = this.props;
141
+
142
+        if (status) {
143
+            return `avatar-badge avatar-badge-${status}`;
144
+        }
145
+
146
+        return '';
147
+    }
148
+
125 149
     _isIcon: (?string | ?Object) => boolean
126 150
 }

+ 7
- 0
react/features/base/react/components/native/AvatarListItem.js View File

@@ -23,6 +23,11 @@ type Props = {
23 23
      */
24 24
     avatarSize?: number,
25 25
 
26
+    /**
27
+     * One of the expected status strings (e.g. 'available') to render a badge on the avatar, if necessary.
28
+     */
29
+    avatarStatus?: ?string,
30
+
26 31
     /**
27 32
      * External style to be applied to the avatar (icon).
28 33
      */
@@ -83,6 +88,7 @@ export default class AvatarListItem extends Component<Props> {
83 88
         const {
84 89
             avatarOnly,
85 90
             avatarSize = AVATAR_SIZE,
91
+            avatarStatus,
86 92
             avatarStyle
87 93
         } = this.props;
88 94
         const { avatar, colorBase, lines, title } = this.props.item;
@@ -96,6 +102,7 @@ export default class AvatarListItem extends Component<Props> {
96 102
                     colorBase = { colorBase }
97 103
                     displayName = { title }
98 104
                     size = { avatarSize }
105
+                    status = { avatarStatus }
99 106
                     style = { avatarStyle }
100 107
                     url = { avatar } />
101 108
                 { avatarOnly || <Container style = { styles.listItemDetails }>

+ 2
- 0
react/features/invite/components/add-people-dialog/native/AddPeopleDialog.js View File

@@ -443,6 +443,7 @@ class AddPeopleDialog extends AbstractAddPeopleDialog<Props, State> {
443 443
                     <AvatarListItem
444 444
                         avatarOnly = { true }
445 445
                         avatarSize = { AVATAR_SIZE }
446
+                        avatarStatus = { item.status }
446 447
                         avatarStyle = { styles.avatar }
447 448
                         avatarTextStyle = { styles.avatarText }
448 449
                         item = { renderableItem }
@@ -497,6 +498,7 @@ class AddPeopleDialog extends AbstractAddPeopleDialog<Props, State> {
497 498
                     style = { styles.itemWrapper }>
498 499
                     <AvatarListItem
499 500
                         avatarSize = { AVATAR_SIZE }
501
+                        avatarStatus = { item.status }
500 502
                         avatarStyle = { styles.avatar }
501 503
                         avatarTextStyle = { styles.avatarText }
502 504
                         item = { renderableItem }

+ 7
- 5
react/features/invite/components/add-people-dialog/web/AddPeopleDialog.js View File

@@ -1,11 +1,11 @@
1 1
 // @flow
2 2
 
3
-import Avatar from '@atlaskit/avatar';
4 3
 import InlineMessage from '@atlaskit/inline-message';
5 4
 import React from 'react';
6 5
 import type { Dispatch } from 'redux';
7 6
 
8 7
 import { createInviteDialogEvent, sendAnalytics } from '../../../../analytics';
8
+import { Avatar } from '../../../../base/avatar';
9 9
 import { Dialog, hideDialog } from '../../../../base/dialog';
10 10
 import { translate, translateToHTML } from '../../../../base/i18n';
11 11
 import { Icon, IconPhone } from '../../../../base/icons';
@@ -289,13 +289,15 @@ class AddPeopleDialog extends AbstractAddPeopleDialog<Props, State> {
289 289
             return {
290 290
                 content: user.name,
291 291
                 elemBefore: <Avatar
292
-                    size = 'small'
293
-                    src = { user.avatar } />,
292
+                    className = { 'avatar-small' }
293
+                    status = { user.status }
294
+                    url = { user.avatar } />,
294 295
                 item: user,
295 296
                 tag: {
296 297
                     elemBefore: <Avatar
297
-                        size = 'xsmall'
298
-                        src = { user.avatar } />
298
+                        className = { 'avatar-xsmall' }
299
+                        status = { user.status }
300
+                        url = { user.avatar } />
299 301
                 },
300 302
                 value: user.id || user.user_id
301 303
             };

Loading…
Cancel
Save