Quellcode durchsuchen

refactor(chat): Don't display chat inside a dialog

* In order to be able to customize the background of the chat
it had not be displayed inside of a dialog. This also removes
the need to extensively use 'TouchmoveHack' component.

* Adds the ability to change panes (Chat vs Polls) using the keyboard.

* Adds accessibility attributes to panes.
master
Vlad Piersec vor 3 Jahren
Ursprung
Commit
9e3084ef48

+ 16
- 37
css/_chat.scss Datei anzeigen

2
     background-color: $chatBackgroundColor;
2
     background-color: $chatBackgroundColor;
3
     box-sizing: border-box;
3
     box-sizing: border-box;
4
     color: #FFF;
4
     color: #FFF;
5
-    display: flex;
6
-    flex-direction: column;
7
     height: 100%;
5
     height: 100%;
8
-    left: -$sidebarWidth;
9
-    overflow: hidden;
10
     position: absolute;
6
     position: absolute;
11
     top: 0;
7
     top: 0;
12
     width: $sidebarWidth;
8
     width: $sidebarWidth;
13
     z-index: $sideToolbarContainerZ;
9
     z-index: $sideToolbarContainerZ;
14
 
10
 
15
-    /**
16
-     * The sidebar (chat) is off-screen when hidden. Move it flush to the left
17
-     * side of the window when it should be visible.
18
-     */
19
-    &.slideInExt {
20
-        left: 0;
11
+    @media (max-width: 580px) {
12
+        width: 100%;
21
     }
13
     }
22
 }
14
 }
23
 
15
 
16
+.chat-panel {
17
+    display: flex;
18
+    flex-direction: column;
19
+    // extract header + tabs height
20
+    height: calc(100% - 102px);
21
+}
22
+
23
+.chat-panel-no-tabs {
24
+    // extract header height
25
+    height: calc(100% - 70px);
26
+}
27
+
24
 #chatconversation {
28
 #chatconversation {
25
     box-sizing: border-box;
29
     box-sizing: border-box;
26
     flex: 1;
30
     flex: 1;
27
     font-size: 10pt;
31
     font-size: 10pt;
32
+    // extract message input height
33
+    height: calc(100% - 68px);
28
     line-height: 20px;
34
     line-height: 20px;
29
     overflow: auto;
35
     overflow: auto;
30
     padding: 16px;
36
     padding: 16px;
31
     text-align: left;
37
     text-align: left;
32
-    width: $sidebarWidth;
33
     word-wrap: break-word;
38
     word-wrap: break-word;
34
 
39
 
35
     display: flex;
40
     display: flex;
58
     a:active {
63
     a:active {
59
         color: black;
64
         color: black;
60
     }
65
     }
61
-
62
-    &::-webkit-scrollbar {
63
-        background: #06a5df;
64
-        width: 7px;
65
-    }
66
-
67
-    &::-webkit-scrollbar-button {
68
-        display: none;
69
-    }
70
-
71
-    &::-webkit-scrollbar-track {
72
-        background: black;
73
-    }
74
-
75
-    &::-webkit-scrollbar-track-piece {
76
-        background: black;
77
-    }
78
-
79
-    &::-webkit-scrollbar-thumb {
80
-        background: #06a5df;
81
-        border-radius: 4px;
82
-    }
83
 }
66
 }
84
 
67
 
85
 #chat-recipient {
68
 #chat-recipient {
319
         text-overflow: ellipsis;
302
         text-overflow: ellipsis;
320
         overflow: hidden;
303
         overflow: hidden;
321
     }
304
     }
322
-
323
-    @media (max-width: 580px) {
324
-        display: none !important;
325
-    }
326
 }
305
 }
327
 
306
 
328
 .sr-only {
307
 .sr-only {

+ 0
- 4
css/_polls.scss Datei anzeigen

238
 .polls-pane-content {
238
 .polls-pane-content {
239
     height: calc(100% - 102px);
239
     height: calc(100% - 102px);
240
     position: relative;
240
     position: relative;
241
-
242
-    @media (max-width: 580px) {
243
-        height: 100%;
244
-    }
245
 }
241
 }
246
 
242
 
247
 .pane-content{
243
 .pane-content{

+ 2
- 2
css/_variables.scss Datei anzeigen

122
 $reloadZ: 20;
122
 $reloadZ: 20;
123
 $poweredByZ: 100;
123
 $poweredByZ: 100;
124
 $ringingZ: 300;
124
 $ringingZ: 300;
125
-$sideToolbarContainerZ: 200;
125
+$sideToolbarContainerZ: 300;
126
 $toolbarZ: 250;
126
 $toolbarZ: 250;
127
 $drawerZ: 351;
127
 $drawerZ: 351;
128
 $tooltipsZ: 401;
128
 $tooltipsZ: 401;
269
 * Prejoin / premeeting screen
269
 * Prejoin / premeeting screen
270
 */
270
 */
271
 
271
 
272
-$prejoinDefaultContentWidth: 336px;
272
+$prejoinDefaultContentWidth: 336px;

+ 119
- 122
react/features/chat/components/web/Chat.js Datei anzeigen

1
 // @flow
1
 // @flow
2
 
2
 
3
+import clsx from 'clsx';
3
 import React from 'react';
4
 import React from 'react';
4
 
5
 
5
 import { translate } from '../../../base/i18n';
6
 import { translate } from '../../../base/i18n';
11
     type Props
12
     type Props
12
 } from '../AbstractChat';
13
 } from '../AbstractChat';
13
 
14
 
14
-import ChatDialog from './ChatDialog';
15
-import Header from './ChatDialogHeader';
15
+import ChatHeader from './ChatHeader';
16
 import ChatInput from './ChatInput';
16
 import ChatInput from './ChatInput';
17
 import DisplayNameForm from './DisplayNameForm';
17
 import DisplayNameForm from './DisplayNameForm';
18
 import KeyboardAvoider from './KeyboardAvoider';
18
 import KeyboardAvoider from './KeyboardAvoider';
19
 import MessageContainer from './MessageContainer';
19
 import MessageContainer from './MessageContainer';
20
 import MessageRecipient from './MessageRecipient';
20
 import MessageRecipient from './MessageRecipient';
21
-import TouchmoveHack from './TouchmoveHack';
22
 
21
 
23
 /**
22
 /**
24
  * React Component for holding the chat feature in a side panel that slides in
23
  * React Component for holding the chat feature in a side panel that slides in
26
  */
25
  */
27
 class Chat extends AbstractChat<Props> {
26
 class Chat extends AbstractChat<Props> {
28
 
27
 
29
-    /**
30
-     * Whether or not the {@code Chat} component is off-screen, having finished
31
-     * its hiding animation.
32
-     */
33
-    _isExited: boolean;
34
-
35
     /**
28
     /**
36
      * Reference to the React Component for displaying chat messages. Used for
29
      * Reference to the React Component for displaying chat messages. Used for
37
      * scrolling to the end of the chat messages.
30
      * scrolling to the end of the chat messages.
47
     constructor(props: Props) {
40
     constructor(props: Props) {
48
         super(props);
41
         super(props);
49
 
42
 
50
-        this._isExited = true;
51
         this._messageContainerRef = React.createRef();
43
         this._messageContainerRef = React.createRef();
52
 
44
 
53
         // Bind event handlers so they are only bound once for every instance.
45
         // Bind event handlers so they are only bound once for every instance.
54
-        this._renderPanelContent = this._renderPanelContent.bind(this);
46
+        this._onChatTabKeyDown = this._onChatTabKeyDown.bind(this);
55
         this._onChatInputResize = this._onChatInputResize.bind(this);
47
         this._onChatInputResize = this._onChatInputResize.bind(this);
56
         this._onEscClick = this._onEscClick.bind(this);
48
         this._onEscClick = this._onEscClick.bind(this);
49
+        this._onPollsTabKeyDown = this._onPollsTabKeyDown.bind(this);
57
         this._onToggleChat = this._onToggleChat.bind(this);
50
         this._onToggleChat = this._onToggleChat.bind(this);
58
     }
51
     }
59
 
52
 
78
             this._scrollMessageContainerToBottom(false);
71
             this._scrollMessageContainerToBottom(false);
79
         }
72
         }
80
     }
73
     }
81
-    _onEscClick: (KeyboardEvent) => void;
82
-
83
-    /**
84
-     * Click handler for the chat sidenav.
85
-     *
86
-     * @param {KeyboardEvent} event - Esc key click to close the popup.
87
-     * @returns {void}
88
-     */
89
-    _onEscClick(event) {
90
-        if (event.key === 'Escape' && this.props._isOpen) {
91
-            event.preventDefault();
92
-            event.stopPropagation();
93
-            this._onToggleChat();
94
-        }
95
-    }
96
 
74
 
97
     /**
75
     /**
98
      * Implements React's {@link Component#render()}.
76
      * Implements React's {@link Component#render()}.
101
      * @returns {ReactElement}
79
      * @returns {ReactElement}
102
      */
80
      */
103
     render() {
81
     render() {
82
+        const { _isOpen, _isPollsEnabled, _showNamePrompt } = this.props;
83
+
104
         return (
84
         return (
105
-            <>
106
-                { this._renderPanelContent() }
107
-            </>
85
+            _isOpen ? <div
86
+                className = 'sideToolbarContainer'
87
+                id = 'sideToolbarContainer'
88
+                onKeyDown = { this._onEscClick } >
89
+                <ChatHeader
90
+                    className = 'chat-header'
91
+                    id = 'chat-header'
92
+                    isPollsEnabled = { _isPollsEnabled }
93
+                    onCancel = { this._onToggleChat } />
94
+                { _showNamePrompt
95
+                    ? <DisplayNameForm isPollsEnabled = { _isPollsEnabled } />
96
+                    : this._renderChat() }
97
+            </div> : null
108
         );
98
         );
109
     }
99
     }
110
 
100
 
121
         this._messageContainerRef.current.maybeUpdateBottomScroll();
111
         this._messageContainerRef.current.maybeUpdateBottomScroll();
122
     }
112
     }
123
 
113
 
114
+    _onChatTabKeyDown: (KeyboardEvent) => void;
115
+
116
+    /**
117
+     * Key press handler for the chat tab.
118
+     *
119
+     * @param {KeyboardEvent} event - The event.
120
+     * @returns {void}
121
+     */
122
+    _onChatTabKeyDown(event) {
123
+        if (event.key === 'Enter' || event.key === ' ') {
124
+            event.preventDefault();
125
+            event.stopPropagation();
126
+            this._onToggleChatTab();
127
+        }
128
+    }
129
+
130
+    _onEscClick: (KeyboardEvent) => void;
131
+
132
+    /**
133
+     * Click handler for the chat sidenav.
134
+     *
135
+     * @param {KeyboardEvent} event - Esc key click to close the popup.
136
+     * @returns {void}
137
+     */
138
+    _onEscClick(event) {
139
+        if (event.key === 'Escape' && this.props._isOpen) {
140
+            event.preventDefault();
141
+            event.stopPropagation();
142
+            this._onToggleChat();
143
+        }
144
+    }
145
+
146
+    _onPollsTabKeyDown: (KeyboardEvent) => void;
147
+
148
+    /**
149
+     * Key press handler for the polls tab.
150
+     *
151
+     * @param {KeyboardEvent} event - The event.
152
+     * @returns {void}
153
+     */
154
+    _onPollsTabKeyDown(event) {
155
+        if (event.key === 'Enter' || event.key === ' ') {
156
+            event.preventDefault();
157
+            event.stopPropagation();
158
+            this._onTogglePollsTab();
159
+        }
160
+    }
161
+
124
     /**
162
     /**
125
      * Returns a React Element for showing chat messages and a form to send new
163
      * Returns a React Element for showing chat messages and a form to send new
126
      * chat messages.
164
      * chat messages.
129
      * @returns {ReactElement}
167
      * @returns {ReactElement}
130
      */
168
      */
131
     _renderChat() {
169
     _renderChat() {
170
+        const { _isPollsEnabled, _isPollsTabFocused } = this.props;
132
 
171
 
133
-        if (this.props._isPollsTabFocused) {
172
+        if (_isPollsTabFocused) {
134
             return (
173
             return (
135
                 <>
174
                 <>
136
-                    { this.props._isPollsEnabled && this._renderTabs()}
137
-                    <TouchmoveHack isModal = { true }><PollsPane /></TouchmoveHack>
175
+                    {_isPollsEnabled && this._renderTabs()}
176
+                    <div
177
+                        aria-labelledby = 'polls-tab'
178
+                        id = 'polls-panel'
179
+                        role = 'tabpanel'>
180
+                        <PollsPane />
181
+                    </div>
138
                     <KeyboardAvoider />
182
                     <KeyboardAvoider />
139
                 </>
183
                 </>
140
             );
184
             );
142
 
186
 
143
         return (
187
         return (
144
             <>
188
             <>
145
-                {this.props._isPollsEnabled && this._renderTabs()}
146
-                <TouchmoveHack
147
-                    flex = { true }
148
-                    isModal = { this.props._isModal }>
189
+                {_isPollsEnabled && this._renderTabs()}
190
+                <div
191
+                    aria-labelledby = 'chat-tab'
192
+                    className = { clsx('chat-panel', !_isPollsEnabled && 'chat-panel-no-tabs') }
193
+                    id = 'chat-panel'
194
+                    role = 'tabpanel'>
149
                     <MessageContainer
195
                     <MessageContainer
150
                         messages = { this.props._messages }
196
                         messages = { this.props._messages }
151
                         ref = { this._messageContainerRef } />
197
                         ref = { this._messageContainerRef } />
152
-                </TouchmoveHack>
153
-                <MessageRecipient />
154
-                <ChatInput
155
-                    onResize = { this._onChatInputResize }
156
-                    onSend = { this._onSendMessage } />
157
-                <KeyboardAvoider />
198
+                    <MessageRecipient />
199
+                    <ChatInput
200
+                        onResize = { this._onChatInputResize }
201
+                        onSend = { this._onSendMessage } />
202
+                    <KeyboardAvoider />
203
+                </div>
158
             </>
204
             </>
159
         );
205
         );
160
     }
206
     }
166
      * @returns {ReactElement}
212
      * @returns {ReactElement}
167
      */
213
      */
168
     _renderTabs() {
214
     _renderTabs() {
215
+        const { _isPollsEnabled, _isPollsTabFocused, _nbUnreadMessages, _nbUnreadPolls, t } = this.props;
169
 
216
 
170
         return (
217
         return (
171
-            <div className = { 'chat-tabs-container' }>
218
+            <div
219
+                ariaLabel = { t(_isPollsEnabled ? 'chat.titleWithPolls' : 'chat.title') }
220
+                className = { 'chat-tabs-container' }
221
+                role = 'tablist'>
172
                 <div
222
                 <div
223
+                    aria-controls = 'chat-panel'
224
+                    aria-label = { t('chat.tabs.chat') }
225
+                    aria-selected = { !_isPollsTabFocused }
173
                     className = { `chat-tab ${
226
                     className = { `chat-tab ${
174
-                        this.props._isPollsTabFocused ? '' : 'chat-tab-focus'
227
+                        _isPollsTabFocused ? '' : 'chat-tab-focus'
175
                     }` }
228
                     }` }
176
-                    onClick = { this._onToggleChatTab }>
177
-                    <span className = { 'chat-tab-title' }>
178
-                        {this.props.t('chat.tabs.chat')}
229
+                    id = 'chat-tab'
230
+                    onClick = { this._onToggleChatTab }
231
+                    onKeyDown = { this._onChatTabKeyDown }
232
+                    role = 'tab'
233
+                    tabIndex = '0'>
234
+                    <span
235
+                        className = { 'chat-tab-title' }>
236
+                        {t('chat.tabs.chat')}
179
                     </span>
237
                     </span>
180
                     {this.props._isPollsTabFocused
238
                     {this.props._isPollsTabFocused
181
-                        && this.props._nbUnreadMessages > 0 && (
239
+                        && _nbUnreadMessages > 0 && (
182
                         <span className = { 'chat-tab-badge' }>
240
                         <span className = { 'chat-tab-badge' }>
183
-                            {this.props._nbUnreadMessages}
241
+                            {_nbUnreadMessages}
184
                         </span>
242
                         </span>
185
                     )}
243
                     )}
186
                 </div>
244
                 </div>
187
                 <div
245
                 <div
246
+                    aria-controls = 'polls-panel'
247
+                    aria-label = { t('chat.tabs.polls') }
248
+                    aria-selected = { _isPollsTabFocused }
188
                     className = { `chat-tab ${
249
                     className = { `chat-tab ${
189
-                        this.props._isPollsTabFocused ? 'chat-tab-focus' : ''
250
+                        _isPollsTabFocused ? 'chat-tab-focus' : ''
190
                     }` }
251
                     }` }
191
-                    onClick = { this._onTogglePollsTab }>
252
+                    id = 'polls-tab'
253
+                    onClick = { this._onTogglePollsTab }
254
+                    onKeyDown = { this._onPollsTabKeyDown }
255
+                    role = 'tab'
256
+                    tabIndex = '0'>
192
                     <span className = { 'chat-tab-title' }>
257
                     <span className = { 'chat-tab-title' }>
193
-                        {this.props.t('chat.tabs.polls')}
258
+                        {t('chat.tabs.polls')}
194
                     </span>
259
                     </span>
195
-                    {!this.props._isPollsTabFocused
260
+                    {!_isPollsTabFocused
196
                         && this.props._nbUnreadPolls > 0 && (
261
                         && this.props._nbUnreadPolls > 0 && (
197
                         <span className = { 'chat-tab-badge' }>
262
                         <span className = { 'chat-tab-badge' }>
198
-                            {this.props._nbUnreadPolls}
263
+                            {_nbUnreadPolls}
199
                         </span>
264
                         </span>
200
                     )}
265
                     )}
201
                 </div>
266
                 </div>
203
         );
268
         );
204
     }
269
     }
205
 
270
 
206
-    /**
207
-     * Instantiates a React Element to display at the top of {@code Chat} to
208
-     * close {@code Chat}.
209
-     *
210
-     * @private
211
-     * @returns {ReactElement}
212
-     */
213
-    _renderChatHeader() {
214
-        return (
215
-            <Header
216
-                className = 'chat-header'
217
-                id = 'chat-header'
218
-                isPollsEnabled = { this.props._isPollsEnabled }
219
-                onCancel = { this._onToggleChat } />
220
-        );
221
-    }
222
-
223
-    _renderPanelContent: () => React$Node | null;
224
-
225
-    /**
226
-     * Renders the contents of the chat panel.
227
-     *
228
-     * @private
229
-     * @returns {ReactElement | null}
230
-     */
231
-    _renderPanelContent() {
232
-        const { _isModal, _isOpen, _showNamePrompt } = this.props;
233
-        let ComponentToRender = null;
234
-
235
-        if (_isOpen) {
236
-            if (_isModal) {
237
-                ComponentToRender = (
238
-                    <ChatDialog isPollsEnabled = { this.props._isPollsEnabled }>
239
-                        { _showNamePrompt
240
-                            ? <DisplayNameForm isPollsEnabled = { this.props._isPollsEnabled } />
241
-                            : this._renderChat() }
242
-                    </ChatDialog>
243
-                );
244
-            } else {
245
-                ComponentToRender = (
246
-                    <>
247
-                        { this._renderChatHeader() }
248
-                        { _showNamePrompt
249
-                            ? <DisplayNameForm isPollsEnabled = { this.props._isPollsEnabled } />
250
-                            : this._renderChat() }
251
-                    </>
252
-                );
253
-            }
254
-        }
255
-        let className = '';
256
-
257
-        if (_isOpen) {
258
-            className = 'slideInExt';
259
-        } else if (this._isExited) {
260
-            className = 'invisible';
261
-        }
262
-
263
-        return (
264
-            <div
265
-                aria-haspopup = 'true'
266
-                className = { `sideToolbarContainer ${className}` }
267
-                id = 'sideToolbarContainer'
268
-                onKeyDown = { this._onEscClick } >
269
-                { ComponentToRender }
270
-            </div>
271
-        );
272
-    }
273
-
274
     /**
271
     /**
275
      * Scrolls the chat messages so the latest message is visible.
272
      * Scrolls the chat messages so the latest message is visible.
276
      *
273
      *

+ 0
- 43
react/features/chat/components/web/ChatDialog.js Datei anzeigen

1
-// @flow
2
-
3
-import React from 'react';
4
-
5
-import { Dialog } from '../../../base/dialog';
6
-
7
-import Header from './ChatDialogHeader';
8
-
9
-type Props = {
10
-
11
-    /**
12
-     * Children of the component.
13
-     */
14
-    children: React$Node,
15
-
16
-    /**
17
-     * Whether the polls feature is enabled or not.
18
-     */
19
-    isPollsEnabled: boolean
20
-}
21
-
22
-/**
23
- * Component that renders the content of the chat in a modal.
24
- *
25
- * @returns {React$Element<any>}
26
- */
27
-function ChatDialog({ children, isPollsEnabled }: Props) {
28
-    return (
29
-        <Dialog
30
-            customHeader = { Header }
31
-            disableEnter = { true }
32
-            disableFooter = { true }
33
-            hideCancelButton = { true }
34
-            submitDisabled = { true }
35
-            titleKey = { isPollsEnabled ? 'chat.titleWithPolls' : 'chat.title' } >
36
-            <div className = 'chat-dialog'>
37
-                {children}
38
-            </div>
39
-        </Dialog>
40
-    );
41
-}
42
-
43
-export default ChatDialog;

react/features/chat/components/web/ChatDialogHeader.js → react/features/chat/components/web/ChatHeader.js Datei anzeigen


Laden…
Abbrechen
Speichern