Calling participants list

This commit is contained in:
Josh Perez 2020-10-15 15:53:21 -04:00 committed by Evan Hahn
parent 2491486aff
commit 7d29cb5edf
4 changed files with 278 additions and 0 deletions

View file

@ -1186,6 +1186,20 @@
"message": "Your video is off",
"description": "Label in the calling lobby indicating that your camera is off"
},
"calling__in-this-call--one": {
"message": "In this call · 1 person",
"description": "Shown in the participants list to describe how many people are in the call"
},
"calling__in-this-call--many": {
"message": "In this call · $people$ people",
"description": "Shown in the participants list to describe how many people are in the call",
"placeholders": {
"people": {
"content": "$1",
"example": "15"
}
}
},
"alwaysRelayCallsDescription": {
"message": "Always relay calls",
"description": "Description of the always relay calls setting"

View file

@ -6376,6 +6376,102 @@ button.module-image__border-overlay:focus {
}
}
.module-calling-participants-list {
background-color: $color-gray-80;
border-radius: 8px;
color: $color-white;
margin-right: 12px;
margin-top: 54px;
padding: 14px;
padding-bottom: 0;
width: 280px;
&__overlay {
display: flex;
height: 100vh;
justify-content: flex-end;
left: 0;
position: absolute;
top: 0;
width: 100vw;
z-index: 2;
}
&__title {
@include font-body-2-bold;
}
&__list {
margin: 0;
margin-top: 22px;
padding: 0;
}
&__contact {
@include font-body-1;
align-items: center;
display: flex;
justify-content: space-between;
list-style-type: none;
margin-bottom: 16px;
}
&__name {
display: inline-block;
margin-left: 8px;
max-width: 144px;
overflow: hidden;
text-overflow: ellipsis;
vertical-align: middle;
white-space: nowrap;
}
&__header {
display: flex;
justify-content: space-between;
}
&__close {
@include button-reset;
@include color-svg('../images/x-shadow-16.svg', $color-gray-15);
height: 16px;
width: 16px;
z-index: 2;
@include keyboard-mode {
&:focus {
outline: 2px solid $ultramarine-ui-light;
}
}
}
&__muted {
&--video {
@include color-svg(
'../images/icons/v2/video-off-solid-28.svg',
$color-white
);
display: inline-block;
margin-left: 18px;
height: 16px;
width: 16px;
}
&--audio {
@include color-svg(
'../images/icons/v2/mic-off-solid-28.svg',
$color-white
);
display: inline-block;
margin-left: 18px;
height: 16px;
width: 16px;
}
}
}
.module-call-need-permission-screen {
align-items: center;
background-color: $color-gray-95;

View file

@ -0,0 +1,64 @@
import * as React from 'react';
import { storiesOf } from '@storybook/react';
import { action } from '@storybook/addon-actions';
import { CallingParticipantsList, PropsType } from './CallingParticipantsList';
import { setup as setupI18n } from '../../js/modules/i18n';
import enMessages from '../../_locales/en/messages.json';
const i18n = setupI18n('en', enMessages);
const participant = {
title: 'Bardock',
};
const createProps = (overrideProps: Partial<PropsType> = {}): PropsType => ({
i18n,
onClose: action('on-close'),
participants: overrideProps.participants || [participant],
});
const story = storiesOf('Components/CallingParticipantsList', module);
story.add('Default', () => {
const props = createProps();
return <CallingParticipantsList {...props} />;
});
story.add('Many Participants', () => {
const props = createProps({
participants: [
{
color: 'blue',
profileName: 'Son Goku',
title: 'Son Goku',
audioMuted: true,
videoMuted: true,
},
{
color: 'deep_orange',
profileName: 'Rage Trunks',
title: 'Rage Trunks',
},
{
color: 'indigo',
profileName: 'Prince Vegeta',
title: 'Prince Vegeta',
videoMuted: true,
},
{
color: 'pink',
profileName: 'Goku Black',
title: 'Goku Black',
},
{
color: 'green',
profileName: 'Supreme Kai Zamasu',
title: 'Supreme Kai Zamasu',
audioMuted: true,
videoMuted: true,
},
],
});
return <CallingParticipantsList {...props} />;
});

View file

@ -0,0 +1,104 @@
/* eslint-disable react/no-array-index-key */
import React from 'react';
import { createPortal } from 'react-dom';
import { Avatar } from './Avatar';
import { ColorType } from '../types/Colors';
import { ContactName } from './conversation/ContactName';
import { LocalizerType } from '../types/Util';
type ParticipantType = {
audioMuted?: boolean;
avatarPath?: string;
color?: ColorType;
profileName?: string;
title: string;
videoMuted?: boolean;
};
export type PropsType = {
readonly i18n: LocalizerType;
readonly onClose: () => void;
readonly participants: Array<ParticipantType>;
};
export const CallingParticipantsList = React.memo(
({ i18n, onClose, participants }: PropsType) => {
const [root, setRoot] = React.useState<HTMLElement | null>(null);
React.useEffect(() => {
const div = document.createElement('div');
document.body.appendChild(div);
setRoot(div);
return () => {
document.body.removeChild(div);
setRoot(null);
};
}, []);
if (!root) {
return null;
}
return createPortal(
<div
role="presentation"
className="module-calling-participants-list__overlay"
>
<div className="module-calling-participants-list">
<div className="module-calling-participants-list__header">
<div className="module-calling-participants-list__title">
{participants.length > 1
? i18n('calling__in-this-call--many', [
String(participants.length),
])
: i18n('calling__in-this-call--one')}
</div>
<button
type="button"
className="module-calling-participants-list__close"
onClick={onClose}
tabIndex={0}
aria-label={i18n('close')}
/>
</div>
<ul className="module-calling-participants-list__list">
{participants.map((participant: ParticipantType, index: number) => (
<li
className="module-calling-participants-list__contact"
key={index}
>
<div>
<Avatar
avatarPath={participant.avatarPath}
color={participant.color}
conversationType="direct"
i18n={i18n}
profileName={participant.profileName}
title={participant.title}
size={32}
/>
<ContactName
i18n={i18n}
module="module-calling-participants-list__name"
title={participant.title}
/>
</div>
<div>
{participant.audioMuted ? (
<span className="module-calling-participants-list__muted--audio" />
) : null}
{participant.videoMuted ? (
<span className="module-calling-participants-list__muted--video" />
) : null}
</div>
</li>
))}
</ul>
</div>
</div>,
root
);
}
);