diff --git a/stylesheets/_modules.scss b/stylesheets/_modules.scss index 6c2217e3bec..b7be6a3f483 100644 --- a/stylesheets/_modules.scss +++ b/stylesheets/_modules.scss @@ -6110,8 +6110,30 @@ button.module-image__border-overlay:focus { .module-calling-button { &__participants { @include color-svg('../images/icons/v2/group-solid-24.svg', $color-white); + display: inline-block; height: 22px; width: 22px; + + &--container { + @include button-reset; + border: none; + color: $color-white; + } + + &--shown { + background-color: $color-gray-75; + border-radius: 16px; + padding: 6px 8px; + padding-bottom: 2px; + margin-top: -6px; + margin-right: -8px; + } + + &--count { + @include font-body-2-bold; + margin-left: 5px; + vertical-align: top; + } } &__settings { @@ -6592,6 +6614,10 @@ button.module-image__border-overlay:focus { @include font-body-2-bold; } + &__contact-icon { + background-color: $color-gray-25; + } + &__list { height: 100%; margin-bottom: 0; diff --git a/ts/components/CallManager.tsx b/ts/components/CallManager.tsx index ecca960c995..81f18a05741 100644 --- a/ts/components/CallManager.tsx +++ b/ts/components/CallManager.tsx @@ -178,6 +178,7 @@ const ActiveCallManager: React.FC = ({ setLocalPreview={setLocalPreview} setLocalAudio={setLocalAudio} setLocalVideo={setLocalVideo} + showParticipantsList={showParticipantsList} toggleParticipants={toggleParticipants} toggleSettings={toggleSettings} /> diff --git a/ts/components/CallScreen.tsx b/ts/components/CallScreen.tsx index 664e8e4ae40..21fdd45a563 100644 --- a/ts/components/CallScreen.tsx +++ b/ts/components/CallScreen.tsx @@ -195,7 +195,11 @@ export const CallScreen: React.FC = ({ }); const remoteParticipants = - call.callMode === CallMode.Group ? call.remoteParticipants.length : 0; + call.callMode === CallMode.Group + ? activeCall.groupCallParticipants.length + : 0; + + const { showParticipantsList } = activeCall.activeCallState; return (
= ({ i18n={i18n} isGroupCall={call.callMode === CallMode.Group} remoteParticipants={remoteParticipants} + showParticipantsList={showParticipantsList} toggleParticipants={toggleParticipants} togglePip={togglePip} toggleSettings={toggleSettings} diff --git a/ts/components/CallingHeader.stories.tsx b/ts/components/CallingHeader.stories.tsx index 6da1aa1fa53..94da99a3467 100644 --- a/ts/components/CallingHeader.stories.tsx +++ b/ts/components/CallingHeader.stories.tsx @@ -21,6 +21,10 @@ const createProps = (overrideProps: Partial = {}): PropsType => ({ 'remoteParticipants', overrideProps.remoteParticipants || 0 ), + showParticipantsList: boolean( + 'showParticipantsList', + Boolean(overrideProps.showParticipantsList) + ), toggleParticipants: () => action('toggle-participants'), togglePip: () => action('toggle-pip'), toggleSettings: () => action('toggle-settings'), @@ -44,6 +48,17 @@ story.add('With Participants', () => ( /> )); +story.add('With Participants (shown)', () => ( + +)); + story.add('Long Title', () => ( void; togglePip?: () => void; toggleSettings: () => void; @@ -22,6 +24,7 @@ export const CallingHeader = ({ i18n, isGroupCall = false, remoteParticipants, + showParticipantsList, toggleParticipants, togglePip, toggleSettings, @@ -43,10 +46,20 @@ export const CallingHeader = ({ aria-label={i18n('calling__participants', [ String(remoteParticipants), ])} - className="module-calling-button__participants" + className={classNames( + 'module-calling-button__participants--container', + { + 'module-calling-button__participants--shown': showParticipantsList, + } + )} onClick={toggleParticipants} type="button" - /> + > + + + {remoteParticipants} + +
) : null} diff --git a/ts/components/CallingLobby.stories.tsx b/ts/components/CallingLobby.stories.tsx index f01929bc0ae..94179861597 100644 --- a/ts/components/CallingLobby.stories.tsx +++ b/ts/components/CallingLobby.stories.tsx @@ -39,6 +39,10 @@ const createProps = (overrideProps: Partial = {}): PropsType => ({ setLocalAudio: action('set-local-audio'), setLocalPreview: action('set-local-preview'), setLocalVideo: action('set-local-video'), + showParticipantsList: boolean( + 'showParticipantsList', + Boolean(overrideProps.showParticipantsList) + ), toggleParticipants: action('toggle-participants'), toggleSettings: action('toggle-settings'), }); @@ -115,3 +119,12 @@ story.add('Group Call - 4', () => { }); return ; }); + +story.add('Group Call - 4 (participants list)', () => { + const props = createProps({ + isGroupCall: true, + participantNames: ['Sam', 'Cayce', 'April', 'Logan', 'Carl'], + showParticipantsList: true, + }); + return ; +}); diff --git a/ts/components/CallingLobby.tsx b/ts/components/CallingLobby.tsx index d779ef888b4..23cf1f4397d 100644 --- a/ts/components/CallingLobby.tsx +++ b/ts/components/CallingLobby.tsx @@ -34,6 +34,7 @@ export type PropsType = { setLocalAudio: (_: SetLocalAudioType) => void; setLocalVideo: (_: SetLocalVideoType) => void; setLocalPreview: (_: SetLocalPreviewType) => void; + showParticipantsList: boolean; toggleParticipants: () => void; toggleSettings: () => void; }; @@ -52,6 +53,7 @@ export const CallingLobby = ({ setLocalAudio, setLocalPreview, setLocalVideo, + showParticipantsList, toggleParticipants, toggleSettings, }: PropsType): JSX.Element => { @@ -117,6 +119,7 @@ export const CallingLobby = ({ i18n={i18n} isGroupCall={isGroupCall} remoteParticipants={participantNames.length} + showParticipantsList={showParticipantsList} toggleParticipants={toggleParticipants} toggleSettings={toggleSettings} /> diff --git a/ts/components/CallingParticipantsList.stories.tsx b/ts/components/CallingParticipantsList.stories.tsx index 5072dd05bdd..8a962c162be 100644 --- a/ts/components/CallingParticipantsList.stories.tsx +++ b/ts/components/CallingParticipantsList.stories.tsx @@ -24,6 +24,7 @@ function createParticipant( hasRemoteAudio: Boolean(participantProps.hasRemoteAudio), hasRemoteVideo: Boolean(participantProps.hasRemoteVideo), isSelf: Boolean(participantProps.isSelf), + name: participantProps.name, profileName: participantProps.title, title: String(participantProps.title), videoAspectRatio: 1.3, @@ -64,6 +65,7 @@ story.add('Many Participants', () => { createParticipant({ hasRemoteAudio: true, hasRemoteVideo: true, + name: 'Rage Trunks', title: 'Rage Trunks', }), createParticipant({ @@ -73,6 +75,7 @@ story.add('Many Participants', () => { createParticipant({ hasRemoteAudio: true, hasRemoteVideo: true, + name: 'Goku Black', title: 'Goku Black', }), createParticipant({ diff --git a/ts/components/CallingParticipantsList.tsx b/ts/components/CallingParticipantsList.tsx index 42c4ab4dd2c..75506678591 100644 --- a/ts/components/CallingParticipantsList.tsx +++ b/ts/components/CallingParticipantsList.tsx @@ -7,6 +7,7 @@ import React from 'react'; import { createPortal } from 'react-dom'; import { Avatar } from './Avatar'; import { ContactName } from './conversation/ContactName'; +import { InContactsIcon } from './InContactsIcon'; import { LocalizerType } from '../types/Util'; import { GroupCallRemoteParticipantType } from '../types/Calling'; @@ -31,14 +32,24 @@ export const CallingParticipantsList = React.memo( }; }, []); + const handleCancel = React.useCallback( + (e: React.MouseEvent) => { + if (e.target === e.currentTarget) { + onClose(); + } + }, + [onClose] + ); + if (!root) { return null; } return createPortal(
@@ -80,11 +91,22 @@ export const CallingParticipantsList = React.memo( {i18n('you')} ) : ( - + <> + + {participant.name ? ( + + {' '} + + + ) : null} + )}
diff --git a/ts/components/InContactsIcon.tsx b/ts/components/InContactsIcon.tsx index 07a4de85b5b..4e2fb911026 100644 --- a/ts/components/InContactsIcon.tsx +++ b/ts/components/InContactsIcon.tsx @@ -2,26 +2,28 @@ // SPDX-License-Identifier: AGPL-3.0-only import React from 'react'; +import classNames from 'classnames'; import { Tooltip } from './Tooltip'; import { LocalizerType } from '../types/Util'; type PropsType = { + className?: string; i18n: LocalizerType; }; export const InContactsIcon = (props: PropsType): JSX.Element => { - const { i18n } = props; + const { className, i18n } = props; /* eslint-disable jsx-a11y/no-noninteractive-tabindex */ return ( diff --git a/ts/state/smart/CallManager.tsx b/ts/state/smart/CallManager.tsx index 64e8d3baaf5..5f911082aaa 100644 --- a/ts/state/smart/CallManager.tsx +++ b/ts/state/smart/CallManager.tsx @@ -70,6 +70,7 @@ const mapStateToActiveCallProp = (state: StateType) => { hasRemoteAudio: remoteParticipant.hasRemoteAudio, hasRemoteVideo: remoteParticipant.hasRemoteVideo, isSelf: remoteParticipant.isSelf, + name: remoteConversation.name, profileName: remoteConversation.profileName, title: remoteConversation.title, videoAspectRatio: remoteParticipant.videoAspectRatio, diff --git a/ts/types/Calling.ts b/ts/types/Calling.ts index 28f6fb253d5..1e7f86f2df2 100644 --- a/ts/types/Calling.ts +++ b/ts/types/Calling.ts @@ -66,6 +66,7 @@ export interface GroupCallRemoteParticipantType { hasRemoteAudio: boolean; hasRemoteVideo: boolean; isSelf: boolean; + name?: string; profileName?: string; title: string; videoAspectRatio: number; diff --git a/ts/util/lint/exceptions.json b/ts/util/lint/exceptions.json index bfbbc2cf7e5..2f4979e3f04 100644 --- a/ts/util/lint/exceptions.json +++ b/ts/util/lint/exceptions.json @@ -14400,7 +14400,7 @@ "rule": "React-useRef", "path": "ts/components/CallingLobby.tsx", "line": " const localVideoRef = React.useRef(null);", - "lineNumber": 58, + "lineNumber": 60, "reasonCategory": "usageTrusted", "updated": "2020-10-26T19:12:24.410Z", "reasonDetail": "Used to get the local video element for rendering."