Skip to content

Commit 6a2edf4

Browse files
zskhanaweiss-dev
authored andcommitted
feat: replace button with div and assigned role=button to it [WPB-21194] (#19846)
* feat: add aria label and also title on the close screen share icon * feat: replace button with div and assigned role=button to it. Move inline css to styles.ts * feat: call controls were not working using keyboard.
1 parent ff60216 commit 6a2edf4

File tree

3 files changed

+133
-79
lines changed

3 files changed

+133
-79
lines changed
Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
/*
2+
* Wire
3+
* Copyright (C) 2025 Wire Swiss GmbH
4+
*
5+
* This program is free software: you can redistribute it and/or modify
6+
* it under the terms of the GNU General Public License as published by
7+
* the Free Software Foundation, either version 3 of the License, or
8+
* (at your option) any later version.
9+
*
10+
* This program is distributed in the hope that it will be useful,
11+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
12+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13+
* GNU General Public License for more details.
14+
*
15+
* You should have received a copy of the GNU General Public License
16+
* along with this program. If not, see http://www.gnu.org/licenses/.
17+
*
18+
*/
19+
20+
import {CSSObject} from '@emotion/react';
21+
22+
const participantNameColor = (isActivelySpeaking: boolean, isAudioEstablished: boolean) => {
23+
if (!isAudioEstablished) {
24+
return 'var(--gray-60)';
25+
}
26+
if (isActivelySpeaking) {
27+
return 'var(--app-bg-secondary)';
28+
}
29+
return 'var(--white)';
30+
};
31+
32+
export const activelySpeakingBoxShadow = `inset 0px 0px 0px 1px var(--group-video-bg), inset 0px 0px 0px 4px var(--accent-color), inset 0px 0px 0px 7px var(--app-bg-secondary)`;
33+
34+
export const groupVideoBoxShadow = (participantCount: number): string =>
35+
participantCount > 1 ? 'inset 0px 0px 0px 2px var(--group-video-bg)' : 'initial';
36+
37+
export const groupVideoTileWrapper: CSSObject = {
38+
alignItems: 'center',
39+
backgroundColor: 'var(--group-video-tile-bg)',
40+
borderRadius: '10px',
41+
display: 'flex',
42+
height: '100%',
43+
justifyContent: 'center',
44+
width: '100%',
45+
};
46+
47+
export const groupVideoActiveSpeakerTile = (isActivelySpeaking: boolean, participantCount: number): CSSObject => ({
48+
borderRadius: '8px',
49+
bottom: 0,
50+
boxShadow: isActivelySpeaking ? activelySpeakingBoxShadow : groupVideoBoxShadow(participantCount),
51+
left: 0,
52+
position: 'absolute',
53+
right: 0,
54+
top: 0,
55+
transition: 'box-shadow 0.3s ease-in-out',
56+
});
57+
58+
export const groupVideoActiveSpeaker = (isActivelySpeaking: boolean): CSSObject => ({
59+
filter: isActivelySpeaking ? 'brightness(0.8)' : 'none',
60+
});
61+
62+
export const groupVideoParticipantNameWrapper = (
63+
isActivelySpeaking: boolean,
64+
isAudioEstablished: boolean,
65+
): CSSObject => ({
66+
overflow: 'hidden',
67+
display: 'flex',
68+
color: participantNameColor(isActivelySpeaking, isAudioEstablished),
69+
});
70+
71+
export const groupVideoParticipantName: CSSObject = {
72+
textOverflow: 'ellipsis',
73+
overflow: 'hidden',
74+
};
75+
76+
export const groupVideoParticipantAudioStatus = (
77+
isActivelySpeaking: boolean,
78+
isAudioEstablished: boolean,
79+
): CSSObject => {
80+
const color = participantNameColor(isActivelySpeaking, isAudioEstablished);
81+
return {
82+
color: 'var(--participant-audio-connecting-color)',
83+
flexShrink: 0,
84+
'&::before': {
85+
content: "' • '",
86+
whiteSpace: 'pre',
87+
color,
88+
},
89+
};
90+
};
91+
92+
export const groupVideoElementVideo = (fitContain: boolean, mirrorSelf: boolean): CSSObject => ({
93+
objectFit: fitContain ? 'contain' : 'cover',
94+
transform: mirrorSelf ? 'rotateY(180deg)' : 'initial',
95+
});
96+
97+
export const groupVideoPauseOverlayLabel = (minimized: boolean): CSSObject => ({
98+
fontSize: minimized ? '0.6875rem' : '0.875rem',
99+
});

src/script/components/calling/GroupVideoGridTile.tsx

Lines changed: 26 additions & 79 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,16 @@ import {VIDEO_STATE} from '@wireapp/avs';
2525
import {TabIndex} from '@wireapp/react-ui-kit';
2626

2727
import {Avatar, AVATAR_SIZE} from 'Components/Avatar';
28+
import {
29+
groupVideoActiveSpeaker,
30+
groupVideoActiveSpeakerTile,
31+
groupVideoElementVideo,
32+
groupVideoParticipantAudioStatus,
33+
groupVideoParticipantName,
34+
groupVideoParticipantNameWrapper,
35+
groupVideoPauseOverlayLabel,
36+
groupVideoTileWrapper,
37+
} from 'Components/calling/GroupVideoGridTile.styles';
2838
import * as Icon from 'Components/Icon';
2939
import type {Participant} from 'Repositories/calling/Participant';
3040
import {useKoSubscribableChildren} from 'Util/ComponentUtil';
@@ -42,24 +52,6 @@ interface GroupVideoGridTileProps {
4252
selfParticipant: Participant;
4353
}
4454

45-
const getParticipantNameColor = ({
46-
isActivelySpeaking,
47-
isAudioEstablished,
48-
}: {
49-
isActivelySpeaking: boolean;
50-
isAudioEstablished: boolean;
51-
}) => {
52-
if (!isAudioEstablished) {
53-
return 'var(--gray-60)';
54-
}
55-
56-
if (isActivelySpeaking) {
57-
return 'var(--app-bg-secondary)';
58-
}
59-
60-
return 'var(--white)';
61-
};
62-
6355
const GroupVideoGridTile = ({
6456
minimized,
6557
participant,
@@ -95,8 +87,6 @@ const GroupVideoGridTile = ({
9587
const hasPausedVideo = videoState === VIDEO_STATE.PAUSED;
9688
const doVideoReconnecting = videoState === VIDEO_STATE.RECONNECTING;
9789
const hasActiveVideo = (sharesCamera || sharesScreen) && !!videoStream;
98-
const activelySpeakingBoxShadow = `inset 0px 0px 0px 1px var(--group-video-bg), inset 0px 0px 0px 4px var(--accent-color), inset 0px 0px 0px 7px var(--app-bg-secondary)`;
99-
const groupVideoBoxShadow = participantCount > 1 ? 'inset 0px 0px 0px 2px var(--group-video-bg)' : 'initial';
10090

10191
const handleTileClick = () => onTileDoubleClick(participant?.user.qualifiedId, participant?.clientId);
10292

@@ -106,45 +96,21 @@ const GroupVideoGridTile = ({
10696
}
10797
};
10898

109-
const participantNameColor = getParticipantNameColor({isActivelySpeaking, isAudioEstablished});
110-
11199
const nameContainer = !minimized && (
112-
<div
113-
className="group-video-grid__element__label"
114-
css={{
115-
backgroundColor: isActivelySpeaking ? 'var(--accent-color)' : 'var(--black)',
116-
}}
117-
>
100+
<div className="group-video-grid__element__label" css={groupVideoActiveSpeaker(isActivelySpeaking)}>
118101
<span
119102
data-uie-name={isActivelySpeaking ? 'status-active-speaking' : isMuted ? 'status-audio-off' : 'status-audio-on'}
120-
css={{
121-
overflow: 'hidden',
122-
display: 'flex',
123-
color: participantNameColor,
124-
}}
103+
css={groupVideoParticipantNameWrapper(isActivelySpeaking, isAudioEstablished)}
125104
>
126105
<span
127106
data-uie-value={participant?.user.id}
128107
data-uie-name="call-participant-name"
129-
css={{
130-
textOverflow: 'ellipsis',
131-
overflow: 'hidden',
132-
}}
108+
css={groupVideoParticipantName}
133109
>
134110
{name}
135111
</span>
136112
{!isAudioEstablished && (
137-
<span
138-
css={{
139-
color: 'var(--participant-audio-connecting-color)',
140-
flexShrink: 0,
141-
'&::before': {
142-
content: "' • '",
143-
whiteSpace: 'pre',
144-
color: participantNameColor,
145-
},
146-
}}
147-
>
113+
<span css={groupVideoParticipantAudioStatus(isActivelySpeaking, isAudioEstablished)}>
148114
{t('videoCallParticipantConnecting')}
149115
</span>
150116
)}
@@ -153,13 +119,18 @@ const GroupVideoGridTile = ({
153119
);
154120

155121
return (
156-
<button
122+
<div
157123
data-uie-name="item-grid"
158124
data-user-id={participant?.user.id}
159125
className="group-video-grid__element"
160126
onDoubleClick={handleTileClick}
161127
onKeyDown={handleEnterTileClick}
162-
tabIndex={isMaximized ? TabIndex.FOCUSABLE : TabIndex.UNFOCUSABLE}
128+
role="button"
129+
// minimized is passed only from CallingCell where we don't want to focus individual the tile on the tab press
130+
tabIndex={
131+
(!minimized || isMaximized) && participant !== selfParticipant ? TabIndex.FOCUSABLE : TabIndex.UNFOCUSABLE
132+
}
133+
aria-label={`Focus video ${participant?.user.id}`}
163134
>
164135
{hasActiveVideo ? (
165136
<div className="tile-wrapper">
@@ -173,24 +144,11 @@ const GroupVideoGridTile = ({
173144
muted
174145
srcObject={blurredVideoStream?.stream ?? videoStream}
175146
className="group-video-grid__element-video"
176-
css={{
177-
objectFit: isMaximized || sharesScreen ? 'contain' : 'cover',
178-
transform: participant === selfParticipant && sharesCamera ? 'rotateY(180deg)' : 'initial',
179-
}}
147+
css={groupVideoElementVideo(isMaximized || sharesScreen, participant === selfParticipant && sharesCamera)}
180148
/>
181149
</div>
182150
) : (
183-
<div
184-
css={{
185-
alignItems: 'center',
186-
backgroundColor: 'var(--group-video-tile-bg)',
187-
borderRadius: '10px',
188-
display: 'flex',
189-
height: '100%',
190-
justifyContent: 'center',
191-
width: '100%',
192-
}}
193-
>
151+
<div css={groupVideoTileWrapper}>
194152
<Avatar
195153
avatarSize={minimized ? AVATAR_SIZE.MEDIUM : AVATAR_SIZE.LARGE}
196154
participant={participant?.user}
@@ -199,18 +157,7 @@ const GroupVideoGridTile = ({
199157
</div>
200158
)}
201159

202-
<div
203-
css={{
204-
borderRadius: '8px',
205-
bottom: 0,
206-
boxShadow: isActivelySpeaking ? activelySpeakingBoxShadow : groupVideoBoxShadow,
207-
left: 0,
208-
position: 'absolute',
209-
right: 0,
210-
top: 0,
211-
transition: 'box-shadow 0.3s ease-in-out',
212-
}}
213-
/>
160+
<div css={groupVideoActiveSpeakerTile(isActivelySpeaking, participantCount)} />
214161

215162
{!minimized && isMuted && (
216163
<span className="group-video-grid__element__label__icon">
@@ -243,15 +190,15 @@ const GroupVideoGridTile = ({
243190

244191
<div
245192
className="group-video-grid__pause-overlay__label"
246-
css={{fontsize: minimized ? '0.6875rem' : '0.875rem'}}
193+
css={groupVideoPauseOverlayLabel(minimized)}
247194
data-uie-name="status-video-paused"
248195
>
249196
{hasPausedVideo ? t('videoCallPaused') : t('videoCallParticipantConnecting')}
250197
</div>
251198
{nameContainer}
252199
</div>
253200
)}
254-
</button>
201+
</div>
255202
);
256203
};
257204

src/script/util/KeyboardUtil.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,7 +90,15 @@ export const handleKeyDown = ({
9090
keys: Array<(typeof KEY)[keyof typeof KEY]>;
9191
}) => {
9292
if (keys.includes(event.key as (typeof KEY)[keyof typeof KEY])) {
93+
if ('preventDefault' in event) {
94+
event.preventDefault();
95+
}
96+
if ('stopPropagation' in event) {
97+
event.stopPropagation();
98+
}
99+
93100
callback(event);
101+
return true;
94102
}
95103
return true;
96104
};

0 commit comments

Comments
 (0)