ソースを参照

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 3年前
コミット
9e3084ef48

+ 16
- 37
css/_chat.scss ファイルの表示

@@ -2,34 +2,39 @@
2 2
     background-color: $chatBackgroundColor;
3 3
     box-sizing: border-box;
4 4
     color: #FFF;
5
-    display: flex;
6
-    flex-direction: column;
7 5
     height: 100%;
8
-    left: -$sidebarWidth;
9
-    overflow: hidden;
10 6
     position: absolute;
11 7
     top: 0;
12 8
     width: $sidebarWidth;
13 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 28
 #chatconversation {
25 29
     box-sizing: border-box;
26 30
     flex: 1;
27 31
     font-size: 10pt;
32
+    // extract message input height
33
+    height: calc(100% - 68px);
28 34
     line-height: 20px;
29 35
     overflow: auto;
30 36
     padding: 16px;
31 37
     text-align: left;
32
-    width: $sidebarWidth;
33 38
     word-wrap: break-word;
34 39
 
35 40
     display: flex;
@@ -58,28 +63,6 @@
58 63
     a:active {
59 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 68
 #chat-recipient {
@@ -319,10 +302,6 @@
319 302
         text-overflow: ellipsis;
320 303
         overflow: hidden;
321 304
     }
322
-
323
-    @media (max-width: 580px) {
324
-        display: none !important;
325
-    }
326 305
 }
327 306
 
328 307
 .sr-only {

+ 0
- 4
css/_polls.scss ファイルの表示

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

+ 2
- 2
css/_variables.scss ファイルの表示

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

+ 119
- 122
react/features/chat/components/web/Chat.js ファイルの表示

@@ -1,5 +1,6 @@
1 1
 // @flow
2 2
 
3
+import clsx from 'clsx';
3 4
 import React from 'react';
4 5
 
5 6
 import { translate } from '../../../base/i18n';
@@ -11,14 +12,12 @@ import AbstractChat, {
11 12
     type Props
12 13
 } from '../AbstractChat';
13 14
 
14
-import ChatDialog from './ChatDialog';
15
-import Header from './ChatDialogHeader';
15
+import ChatHeader from './ChatHeader';
16 16
 import ChatInput from './ChatInput';
17 17
 import DisplayNameForm from './DisplayNameForm';
18 18
 import KeyboardAvoider from './KeyboardAvoider';
19 19
 import MessageContainer from './MessageContainer';
20 20
 import MessageRecipient from './MessageRecipient';
21
-import TouchmoveHack from './TouchmoveHack';
22 21
 
23 22
 /**
24 23
  * React Component for holding the chat feature in a side panel that slides in
@@ -26,12 +25,6 @@ import TouchmoveHack from './TouchmoveHack';
26 25
  */
27 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 29
      * Reference to the React Component for displaying chat messages. Used for
37 30
      * scrolling to the end of the chat messages.
@@ -47,13 +40,13 @@ class Chat extends AbstractChat<Props> {
47 40
     constructor(props: Props) {
48 41
         super(props);
49 42
 
50
-        this._isExited = true;
51 43
         this._messageContainerRef = React.createRef();
52 44
 
53 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 47
         this._onChatInputResize = this._onChatInputResize.bind(this);
56 48
         this._onEscClick = this._onEscClick.bind(this);
49
+        this._onPollsTabKeyDown = this._onPollsTabKeyDown.bind(this);
57 50
         this._onToggleChat = this._onToggleChat.bind(this);
58 51
     }
59 52
 
@@ -78,21 +71,6 @@ class Chat extends AbstractChat<Props> {
78 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 76
      * Implements React's {@link Component#render()}.
@@ -101,10 +79,22 @@ class Chat extends AbstractChat<Props> {
101 79
      * @returns {ReactElement}
102 80
      */
103 81
     render() {
82
+        const { _isOpen, _isPollsEnabled, _showNamePrompt } = this.props;
83
+
104 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,6 +111,54 @@ class Chat extends AbstractChat<Props> {
121 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 163
      * Returns a React Element for showing chat messages and a form to send new
126 164
      * chat messages.
@@ -129,12 +167,18 @@ class Chat extends AbstractChat<Props> {
129 167
      * @returns {ReactElement}
130 168
      */
131 169
     _renderChat() {
170
+        const { _isPollsEnabled, _isPollsTabFocused } = this.props;
132 171
 
133
-        if (this.props._isPollsTabFocused) {
172
+        if (_isPollsTabFocused) {
134 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 182
                     <KeyboardAvoider />
139 183
                 </>
140 184
             );
@@ -142,19 +186,21 @@ class Chat extends AbstractChat<Props> {
142 186
 
143 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 195
                     <MessageContainer
150 196
                         messages = { this.props._messages }
151 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,36 +212,55 @@ class Chat extends AbstractChat<Props> {
166 212
      * @returns {ReactElement}
167 213
      */
168 214
     _renderTabs() {
215
+        const { _isPollsEnabled, _isPollsTabFocused, _nbUnreadMessages, _nbUnreadPolls, t } = this.props;
169 216
 
170 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 222
                 <div
223
+                    aria-controls = 'chat-panel'
224
+                    aria-label = { t('chat.tabs.chat') }
225
+                    aria-selected = { !_isPollsTabFocused }
173 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 237
                     </span>
180 238
                     {this.props._isPollsTabFocused
181
-                        && this.props._nbUnreadMessages > 0 && (
239
+                        && _nbUnreadMessages > 0 && (
182 240
                         <span className = { 'chat-tab-badge' }>
183
-                            {this.props._nbUnreadMessages}
241
+                            {_nbUnreadMessages}
184 242
                         </span>
185 243
                     )}
186 244
                 </div>
187 245
                 <div
246
+                    aria-controls = 'polls-panel'
247
+                    aria-label = { t('chat.tabs.polls') }
248
+                    aria-selected = { _isPollsTabFocused }
188 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 257
                     <span className = { 'chat-tab-title' }>
193
-                        {this.props.t('chat.tabs.polls')}
258
+                        {t('chat.tabs.polls')}
194 259
                     </span>
195
-                    {!this.props._isPollsTabFocused
260
+                    {!_isPollsTabFocused
196 261
                         && this.props._nbUnreadPolls > 0 && (
197 262
                         <span className = { 'chat-tab-badge' }>
198
-                            {this.props._nbUnreadPolls}
263
+                            {_nbUnreadPolls}
199 264
                         </span>
200 265
                     )}
201 266
                 </div>
@@ -203,74 +268,6 @@ class Chat extends AbstractChat<Props> {
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 272
      * Scrolls the chat messages so the latest message is visible.
276 273
      *

+ 0
- 43
react/features/chat/components/web/ChatDialog.js ファイルの表示

@@ -1,43 +0,0 @@
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 ファイルの表示


読み込み中…
キャンセル
保存